chore(site): add storybook for terminal page (#12441)

This commit is contained in:
Bruno Quaresma
2024-03-07 14:17:38 +00:00
committed by GitHub
parent b96f6b48a4
commit db02c72ac6
5 changed files with 177 additions and 20 deletions
+2 -3
View File
@@ -3,12 +3,11 @@ import type { QueryKey } from "react-query";
import type { Experiments, FeatureName } from "api/typesGenerated"; import type { Experiments, FeatureName } from "api/typesGenerated";
declare module "@storybook/react" { declare module "@storybook/react" {
type WebSocketEvent = { event: "message"; data: string } | { event: "error" };
interface Parameters { interface Parameters {
features?: FeatureName[]; features?: FeatureName[];
experiments?: Experiments; experiments?: Experiments;
queries?: { key: QueryKey; data: unknown }[]; queries?: { key: QueryKey; data: unknown }[];
webSocket?: { webSocket?: WebSocketEvent[];
messages: string[];
};
} }
} }
-1
View File
@@ -48,7 +48,6 @@ export const AuthContext = createContext<AuthContextValue | undefined>(
export const AuthProvider: FC<PropsWithChildren> = ({ children }) => { export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const meOptions = me(); const meOptions = me();
const userQuery = useQuery(meOptions); const userQuery = useQuery(meOptions);
const authMethodsQuery = useQuery(authMethods()); const authMethodsQuery = useQuery(authMethods());
const hasFirstUserQuery = useQuery(hasFirstUser()); const hasFirstUserQuery = useQuery(hasFirstUser());
@@ -46,8 +46,9 @@ export const Logs: Story = {
}, },
decorators: [withWebSocket], decorators: [withWebSocket],
parameters: { parameters: {
webSocket: { webSocket: MockWorkspaceBuildLogs.map((log) => ({
messages: MockWorkspaceBuildLogs.map((log) => JSON.stringify(log)), event: "message",
}, data: JSON.stringify(log),
})),
}, },
}; };
@@ -0,0 +1,144 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
reactRouterOutlet,
reactRouterParameters,
} from "storybook-addon-react-router-v6";
import { getAuthorizationKey } from "api/queries/authCheck";
import { workspaceByOwnerAndNameKey } from "api/queries/workspaces";
import type { Workspace, WorkspaceAgentLifecycle } from "api/typesGenerated";
import { AuthProvider } from "contexts/auth/AuthProvider";
import { permissionsToCheck } from "contexts/auth/permissions";
import { RequireAuth } from "contexts/auth/RequireAuth";
import {
MockAppearanceConfig,
MockAuthMethodsAll,
MockBuildInfo,
MockEntitlements,
MockExperiments,
MockUser,
MockWorkspace,
MockWorkspaceAgent,
} from "testHelpers/entities";
import { withWebSocket } from "testHelpers/storybook";
import TerminalPage from "./TerminalPage";
const createWorkspaceWithAgent = (lifecycle: WorkspaceAgentLifecycle) => {
return {
key: workspaceByOwnerAndNameKey(
MockWorkspace.owner_name,
MockWorkspace.name,
),
data: {
...MockWorkspace,
latest_build: {
...MockWorkspace.latest_build,
resources: [
{
...MockWorkspace.latest_build.resources[0],
agents: [{ ...MockWorkspaceAgent, lifecycle_state: lifecycle }],
},
],
},
} satisfies Workspace,
};
};
const meta = {
title: "pages/Terminal",
component: RequireAuth,
parameters: {
layout: "fullscreen",
reactRouter: reactRouterParameters({
location: {
pathParams: {
username: `@${MockWorkspace.owner_name}`,
workspace: MockWorkspace.name,
},
},
routing: reactRouterOutlet(
{
path: `/:username/:workspace/terminal`,
},
<TerminalPage />,
),
}),
queries: [
{ key: ["me"], data: MockUser },
{ key: ["authMethods"], data: MockAuthMethodsAll },
{ key: ["hasFirstUser"], data: true },
{ key: ["buildInfo"], data: MockBuildInfo },
{ key: ["entitlements"], data: MockEntitlements },
{ key: ["experiments"], data: MockExperiments },
{ key: ["appearance"], data: MockAppearanceConfig },
{
key: getAuthorizationKey({ checks: permissionsToCheck }),
data: { editWorkspaceProxies: true },
},
],
},
decorators: [
(Story) => (
<AuthProvider>
<Story />
</AuthProvider>
),
],
} satisfies Meta<typeof TerminalPage>;
export default meta;
type Story = StoryObj<typeof TerminalPage>;
export const Starting: Story = {
decorators: [withWebSocket],
parameters: {
...meta.parameters,
webSocket: [
{
event: "message",
// Copied and pasted this from browser
data: `➜ codergit:(bq/refactor-web-term-notifications) ✗`,
},
],
queries: [...meta.parameters.queries, createWorkspaceWithAgent("starting")],
},
};
export const Ready: Story = {
decorators: [withWebSocket],
parameters: {
...meta.parameters,
webSocket: [
{
event: "message",
// Copied and pasted this from browser
data: `➜ codergit:(bq/refactor-web-term-notifications) ✗`,
},
],
queries: [...meta.parameters.queries, createWorkspaceWithAgent("ready")],
},
};
export const StartError: Story = {
decorators: [withWebSocket],
parameters: {
...meta.parameters,
webSocket: [],
queries: [
...meta.parameters.queries,
createWorkspaceWithAgent("start_error"),
],
},
};
export const ConnectionError: Story = {
decorators: [withWebSocket],
parameters: {
...meta.parameters,
webSocket: [
{
event: "error",
},
],
queries: [...meta.parameters.queries, createWorkspaceWithAgent("ready")],
},
};
+27 -13
View File
@@ -45,25 +45,39 @@ export const withDashboardProvider = (
); );
}; };
type MessageEvent = Record<"data", string>;
type CallbackFn = (ev?: MessageEvent) => void;
export const withWebSocket = (Story: FC, { parameters }: StoryContext) => { export const withWebSocket = (Story: FC, { parameters }: StoryContext) => {
if (!parameters.webSocket) { const events = parameters.webSocket;
console.warn(
"Looks like you forgot to add websocket messages to the story", if (!events) {
); console.warn("You forgot to add `parameters.webSocket` to your story");
return <Story />;
} }
const listeners = new Map<string, CallbackFn>();
let callEventsDelay: number;
// @ts-expect-error -- TS doesn't know about the global WebSocket // @ts-expect-error -- TS doesn't know about the global WebSocket
window.WebSocket = function () { window.WebSocket = function () {
return { return {
addEventListener: ( addEventListener: (type: string, callback: CallbackFn) => {
type: string, listeners.set(type, callback);
callback: (ev: Record<"data", string>) => void,
) => { // Runs when the last event listener is added
if (type === "message") { clearTimeout(callEventsDelay);
parameters.webSocket?.messages.forEach((message) => { callEventsDelay = window.setTimeout(() => {
callback({ data: message }); for (const entry of events) {
}); const callback = listeners.get(entry.event);
}
if (callback) {
entry.event === "message"
? callback({ data: entry.data })
: callback();
}
}
}, 0);
}, },
close: () => {}, close: () => {},
}; };