Skip to content

Commit a3a88fd

Browse files
BrunoQuaresmamtojek
authored andcommitted
chore(site): add storybook for terminal page (#12441)
1 parent 6281c4a commit a3a88fd

File tree

5 files changed

+177
-20
lines changed

5 files changed

+177
-20
lines changed

site/src/@types/storybook.d.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import type { QueryKey } from "react-query";
33
import type { Experiments, FeatureName } from "api/typesGenerated";
44

55
declare module "@storybook/react" {
6+
type WebSocketEvent = { event: "message"; data: string } | { event: "error" };
67
interface Parameters {
78
features?: FeatureName[];
89
experiments?: Experiments;
910
queries?: { key: QueryKey; data: unknown }[];
10-
webSocket?: {
11-
messages: string[];
12-
};
11+
webSocket?: WebSocketEvent[];
1312
}
1413
}

site/src/contexts/auth/AuthProvider.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ export const AuthContext = createContext<AuthContextValue | undefined>(
4848
export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
4949
const queryClient = useQueryClient();
5050
const meOptions = me();
51-
5251
const userQuery = useQuery(meOptions);
5352
const authMethodsQuery = useQuery(authMethods());
5453
const hasFirstUserQuery = useQuery(hasFirstUser());

site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ export const Logs: Story = {
4646
},
4747
decorators: [withWebSocket],
4848
parameters: {
49-
webSocket: {
50-
messages: MockWorkspaceBuildLogs.map((log) => JSON.stringify(log)),
51-
},
49+
webSocket: MockWorkspaceBuildLogs.map((log) => ({
50+
event: "message",
51+
data: JSON.stringify(log),
52+
})),
5253
},
5354
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import {
3+
reactRouterOutlet,
4+
reactRouterParameters,
5+
} from "storybook-addon-react-router-v6";
6+
import { getAuthorizationKey } from "api/queries/authCheck";
7+
import { workspaceByOwnerAndNameKey } from "api/queries/workspaces";
8+
import type { Workspace, WorkspaceAgentLifecycle } from "api/typesGenerated";
9+
import { AuthProvider } from "contexts/auth/AuthProvider";
10+
import { permissionsToCheck } from "contexts/auth/permissions";
11+
import { RequireAuth } from "contexts/auth/RequireAuth";
12+
import {
13+
MockAppearanceConfig,
14+
MockAuthMethodsAll,
15+
MockBuildInfo,
16+
MockEntitlements,
17+
MockExperiments,
18+
MockUser,
19+
MockWorkspace,
20+
MockWorkspaceAgent,
21+
} from "testHelpers/entities";
22+
import { withWebSocket } from "testHelpers/storybook";
23+
import TerminalPage from "./TerminalPage";
24+
25+
const createWorkspaceWithAgent = (lifecycle: WorkspaceAgentLifecycle) => {
26+
return {
27+
key: workspaceByOwnerAndNameKey(
28+
MockWorkspace.owner_name,
29+
MockWorkspace.name,
30+
),
31+
data: {
32+
...MockWorkspace,
33+
latest_build: {
34+
...MockWorkspace.latest_build,
35+
resources: [
36+
{
37+
...MockWorkspace.latest_build.resources[0],
38+
agents: [{ ...MockWorkspaceAgent, lifecycle_state: lifecycle }],
39+
},
40+
],
41+
},
42+
} satisfies Workspace,
43+
};
44+
};
45+
46+
const meta = {
47+
title: "pages/Terminal",
48+
component: RequireAuth,
49+
parameters: {
50+
layout: "fullscreen",
51+
reactRouter: reactRouterParameters({
52+
location: {
53+
pathParams: {
54+
username: `@${MockWorkspace.owner_name}`,
55+
workspace: MockWorkspace.name,
56+
},
57+
},
58+
routing: reactRouterOutlet(
59+
{
60+
path: `/:username/:workspace/terminal`,
61+
},
62+
<TerminalPage />,
63+
),
64+
}),
65+
queries: [
66+
{ key: ["me"], data: MockUser },
67+
{ key: ["authMethods"], data: MockAuthMethodsAll },
68+
{ key: ["hasFirstUser"], data: true },
69+
{ key: ["buildInfo"], data: MockBuildInfo },
70+
{ key: ["entitlements"], data: MockEntitlements },
71+
{ key: ["experiments"], data: MockExperiments },
72+
{ key: ["appearance"], data: MockAppearanceConfig },
73+
{
74+
key: getAuthorizationKey({ checks: permissionsToCheck }),
75+
data: { editWorkspaceProxies: true },
76+
},
77+
],
78+
},
79+
decorators: [
80+
(Story) => (
81+
<AuthProvider>
82+
<Story />
83+
</AuthProvider>
84+
),
85+
],
86+
} satisfies Meta<typeof TerminalPage>;
87+
88+
export default meta;
89+
type Story = StoryObj<typeof TerminalPage>;
90+
91+
export const Starting: Story = {
92+
decorators: [withWebSocket],
93+
parameters: {
94+
...meta.parameters,
95+
webSocket: [
96+
{
97+
event: "message",
98+
// Copied and pasted this from browser
99+
data: `➜ codergit:(bq/refactor-web-term-notifications) ✗`,
100+
},
101+
],
102+
queries: [...meta.parameters.queries, createWorkspaceWithAgent("starting")],
103+
},
104+
};
105+
106+
export const Ready: Story = {
107+
decorators: [withWebSocket],
108+
parameters: {
109+
...meta.parameters,
110+
webSocket: [
111+
{
112+
event: "message",
113+
// Copied and pasted this from browser
114+
data: `➜ codergit:(bq/refactor-web-term-notifications) ✗`,
115+
},
116+
],
117+
queries: [...meta.parameters.queries, createWorkspaceWithAgent("ready")],
118+
},
119+
};
120+
121+
export const StartError: Story = {
122+
decorators: [withWebSocket],
123+
parameters: {
124+
...meta.parameters,
125+
webSocket: [],
126+
queries: [
127+
...meta.parameters.queries,
128+
createWorkspaceWithAgent("start_error"),
129+
],
130+
},
131+
};
132+
133+
export const ConnectionError: Story = {
134+
decorators: [withWebSocket],
135+
parameters: {
136+
...meta.parameters,
137+
webSocket: [
138+
{
139+
event: "error",
140+
},
141+
],
142+
queries: [...meta.parameters.queries, createWorkspaceWithAgent("ready")],
143+
},
144+
};

site/src/testHelpers/storybook.tsx

+27-13
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,39 @@ export const withDashboardProvider = (
4545
);
4646
};
4747

48+
type MessageEvent = Record<"data", string>;
49+
type CallbackFn = (ev?: MessageEvent) => void;
50+
4851
export const withWebSocket = (Story: FC, { parameters }: StoryContext) => {
49-
if (!parameters.webSocket) {
50-
console.warn(
51-
"Looks like you forgot to add websocket messages to the story",
52-
);
52+
const events = parameters.webSocket;
53+
54+
if (!events) {
55+
console.warn("You forgot to add `parameters.webSocket` to your story");
56+
return <Story />;
5357
}
5458

59+
const listeners = new Map<string, CallbackFn>();
60+
let callEventsDelay: number;
61+
5562
// @ts-expect-error -- TS doesn't know about the global WebSocket
5663
window.WebSocket = function () {
5764
return {
58-
addEventListener: (
59-
type: string,
60-
callback: (ev: Record<"data", string>) => void,
61-
) => {
62-
if (type === "message") {
63-
parameters.webSocket?.messages.forEach((message) => {
64-
callback({ data: message });
65-
});
66-
}
65+
addEventListener: (type: string, callback: CallbackFn) => {
66+
listeners.set(type, callback);
67+
68+
// Runs when the last event listener is added
69+
clearTimeout(callEventsDelay);
70+
callEventsDelay = window.setTimeout(() => {
71+
for (const entry of events) {
72+
const callback = listeners.get(entry.event);
73+
74+
if (callback) {
75+
entry.event === "message"
76+
? callback({ data: entry.data })
77+
: callback();
78+
}
79+
}
80+
}, 0);
6781
},
6882
close: () => {},
6983
};

0 commit comments

Comments
 (0)