@remit/web-client 0.0.300 → 0.0.301
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/auth/amplify-config.ts +2 -3
- package/src/auth/cognito-sign-in.stories.tsx +90 -0
- package/src/auth/email-sign-in.stories.tsx +128 -0
- package/src/components/onboarding/onboarding.stories.tsx +460 -0
- package/src/components/settings/AccountServices.render.test.ts +254 -0
- package/src/components/settings/AccountServices.tsx +181 -0
- package/src/components/settings/AccountServicesCard.stories.tsx +71 -0
- package/src/components/settings/AccountServicesCard.tsx +76 -0
- package/src/components/settings/accounts.stories.tsx +363 -0
- package/src/components/settings/advanced.stories.tsx +162 -0
- package/src/components/settings/appearance.stories.tsx +33 -0
- package/src/components/settings/folders.stories.tsx +89 -0
- package/src/components/settings/self-update.stories.tsx +270 -0
- package/src/components/settings/senders.stories.tsx +155 -0
- package/src/mocks/story-frame/AppStory.tsx +21 -6
- package/src/mocks/story-frame/mail-handlers.ts +69 -7
- package/src/mocks/story-frame/mail-world.ts +22 -4
- package/src/mocks/story-frame/onboarding-handlers.ts +97 -0
- package/src/mocks/story-frame/session.tsx +19 -0
- package/src/routes/settings/-accounts.test.ts +1 -0
- package/src/routes/settings/accounts.tsx +28 -20
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { HttpResponse, http } from "msw";
|
|
3
|
+
import { expect, userEvent, waitFor, within } from "storybook/test";
|
|
4
|
+
import { AppStory } from "@/mocks/story-frame/AppStory";
|
|
5
|
+
import { mailHandlers } from "@/mocks/story-frame/mail-handlers";
|
|
6
|
+
import { mailWorld, NOW } from "@/mocks/story-frame/mail-world";
|
|
7
|
+
import { signedInAs } from "@/mocks/story-frame/session";
|
|
8
|
+
import { makeAccount } from "@/test-support/fixtures";
|
|
9
|
+
|
|
10
|
+
const accounts = [
|
|
11
|
+
makeAccount({
|
|
12
|
+
accountId: "acc-personal",
|
|
13
|
+
email: "alice.tan@gmail.example",
|
|
14
|
+
displayName: "Personal",
|
|
15
|
+
lastSyncAt: NOW,
|
|
16
|
+
}),
|
|
17
|
+
makeAccount({
|
|
18
|
+
accountId: "acc-work",
|
|
19
|
+
email: "alice@northwind.example",
|
|
20
|
+
displayName: "Work",
|
|
21
|
+
lastSyncAt: NOW,
|
|
22
|
+
lastError:
|
|
23
|
+
"AUTHENTICATIONFAILED: [ALERT] Application-specific password required",
|
|
24
|
+
}),
|
|
25
|
+
makeAccount({
|
|
26
|
+
accountId: "acc-forum",
|
|
27
|
+
email: "alice@synthcollective.example",
|
|
28
|
+
displayName: "Synthwave Forum",
|
|
29
|
+
lastSyncAt: NOW,
|
|
30
|
+
muted: { value: true, setAt: NOW },
|
|
31
|
+
}),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const world = mailWorld({ accounts });
|
|
35
|
+
|
|
36
|
+
const meta = {
|
|
37
|
+
title: "Playground/Shipped/Settings/Accounts",
|
|
38
|
+
component: AppStory,
|
|
39
|
+
args: { url: "/settings/accounts" },
|
|
40
|
+
parameters: {
|
|
41
|
+
layout: "fullscreen",
|
|
42
|
+
msw: { handlers: mailHandlers(world) },
|
|
43
|
+
},
|
|
44
|
+
} satisfies Meta<typeof AppStory>;
|
|
45
|
+
export default meta;
|
|
46
|
+
|
|
47
|
+
type Story = StoryObj<typeof meta>;
|
|
48
|
+
|
|
49
|
+
const desktop = { viewport: { value: "desktop", isRotated: false } };
|
|
50
|
+
const tablet = { viewport: { value: "tablet", isRotated: false } };
|
|
51
|
+
const phone = { viewport: { value: "mobile", isRotated: false } };
|
|
52
|
+
|
|
53
|
+
const page = within(document.body);
|
|
54
|
+
|
|
55
|
+
const openEditPanel = async (canvasElement: HTMLElement, index: number) => {
|
|
56
|
+
const canvas = within(canvasElement);
|
|
57
|
+
await canvas.findAllByRole("button", { name: "Manage" });
|
|
58
|
+
const manage = canvas.getAllByRole("button", { name: "Manage" })[index];
|
|
59
|
+
if (manage) await userEvent.click(manage);
|
|
60
|
+
return page.findByRole("dialog", { name: "Edit Account" });
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Accounts: Story = {
|
|
64
|
+
globals: desktop,
|
|
65
|
+
play: async ({ canvasElement }) => {
|
|
66
|
+
const canvas = within(canvasElement);
|
|
67
|
+
await expect(await canvas.findByText("Personal")).toBeVisible();
|
|
68
|
+
await expect(canvas.getByText("3 accounts")).toBeVisible();
|
|
69
|
+
await expect(
|
|
70
|
+
canvas.getByText(/Application-specific password required/),
|
|
71
|
+
).toBeVisible();
|
|
72
|
+
await expect(
|
|
73
|
+
canvas.getByRole("button", { name: "Reconnect" }),
|
|
74
|
+
).toBeVisible();
|
|
75
|
+
await expect(
|
|
76
|
+
canvas.getAllByRole("button", { name: "Manage" }),
|
|
77
|
+
).toHaveLength(2);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const AccountsScrolling: Story = {
|
|
82
|
+
name: "Accounts — desktop, beyond the fold",
|
|
83
|
+
globals: desktop,
|
|
84
|
+
parameters: {
|
|
85
|
+
msw: {
|
|
86
|
+
handlers: mailHandlers(
|
|
87
|
+
mailWorld({
|
|
88
|
+
accounts: Array.from({ length: 9 }, (_, index) =>
|
|
89
|
+
makeAccount({
|
|
90
|
+
accountId: `acc-${index + 1}`,
|
|
91
|
+
email: `alice+${index + 1}@northwind.example`,
|
|
92
|
+
displayName: `Account ${index + 1}`,
|
|
93
|
+
lastSyncAt: NOW,
|
|
94
|
+
}),
|
|
95
|
+
),
|
|
96
|
+
}),
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
play: async ({ canvasElement }) => {
|
|
101
|
+
const canvas = within(canvasElement);
|
|
102
|
+
await canvas.findByText("Account 9");
|
|
103
|
+
const leave = canvas.getByRole("button", {
|
|
104
|
+
name: "Delete your Remit account",
|
|
105
|
+
});
|
|
106
|
+
leave.scrollIntoView();
|
|
107
|
+
await expect(leave).toBeVisible();
|
|
108
|
+
await expect(
|
|
109
|
+
canvas.getByRole("heading", { name: "Accounts" }),
|
|
110
|
+
).toBeVisible();
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const AccountsMissingSyncDate: Story = {
|
|
115
|
+
parameters: {
|
|
116
|
+
msw: {
|
|
117
|
+
handlers: mailHandlers(
|
|
118
|
+
mailWorld({
|
|
119
|
+
accounts: [
|
|
120
|
+
makeAccount({
|
|
121
|
+
accountId: "acc-personal",
|
|
122
|
+
email: "alice.tan@gmail.example",
|
|
123
|
+
displayName: "Personal",
|
|
124
|
+
}),
|
|
125
|
+
],
|
|
126
|
+
}),
|
|
127
|
+
),
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
play: async ({ canvasElement }) => {
|
|
131
|
+
const canvas = within(canvasElement);
|
|
132
|
+
await expect(await canvas.findByText(/never synced/)).toBeVisible();
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const ShellPhone: Story = {
|
|
137
|
+
name: "Shell — phone",
|
|
138
|
+
globals: phone,
|
|
139
|
+
play: async ({ canvasElement }) => {
|
|
140
|
+
const canvas = within(canvasElement);
|
|
141
|
+
await canvas.findByText("Personal");
|
|
142
|
+
await expect(
|
|
143
|
+
canvas.getByRole("button", { name: "Open settings menu" }),
|
|
144
|
+
).toBeVisible();
|
|
145
|
+
await expect(
|
|
146
|
+
canvas.getByRole("button", { name: "Back to mail" }),
|
|
147
|
+
).toBeVisible();
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const ShellTablet: Story = {
|
|
152
|
+
name: "Shell — tablet",
|
|
153
|
+
globals: tablet,
|
|
154
|
+
play: async ({ canvasElement }) => {
|
|
155
|
+
const canvas = within(canvasElement);
|
|
156
|
+
await canvas.findByText("Personal");
|
|
157
|
+
await expect(
|
|
158
|
+
canvas.getByRole("button", { name: "Open settings menu" }),
|
|
159
|
+
).toBeVisible();
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const AccountsEmpty: Story = {
|
|
164
|
+
name: "Accounts — empty",
|
|
165
|
+
parameters: {
|
|
166
|
+
msw: { handlers: mailHandlers(mailWorld({ accounts: [] })) },
|
|
167
|
+
},
|
|
168
|
+
play: async ({ canvasElement }) => {
|
|
169
|
+
const canvas = within(canvasElement);
|
|
170
|
+
await expect(
|
|
171
|
+
await canvas.findByText("No accounts configured."),
|
|
172
|
+
).toBeVisible();
|
|
173
|
+
await expect(
|
|
174
|
+
canvas.getByRole("button", { name: "Add your first account" }),
|
|
175
|
+
).toBeVisible();
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const AccountsLoading: Story = {
|
|
180
|
+
name: "Accounts — loading",
|
|
181
|
+
parameters: {
|
|
182
|
+
msw: {
|
|
183
|
+
handlers: [
|
|
184
|
+
http.get("/api/config", () => new Promise<never>(() => undefined)),
|
|
185
|
+
...mailHandlers(world),
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
play: async ({ canvasElement }) => {
|
|
190
|
+
const canvas = within(canvasElement);
|
|
191
|
+
await expect(
|
|
192
|
+
await canvas.findByRole("status", { name: "Loading accounts" }),
|
|
193
|
+
).toBeVisible();
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const AccountsOauthSuccess: Story = {
|
|
198
|
+
name: "Accounts — OAuth success",
|
|
199
|
+
args: { url: "/settings/accounts?connected=acc-personal" },
|
|
200
|
+
play: async ({ canvasElement }) => {
|
|
201
|
+
const canvas = within(canvasElement);
|
|
202
|
+
await expect(
|
|
203
|
+
await canvas.findByText("Account connected successfully."),
|
|
204
|
+
).toBeVisible();
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const AccountsOauthError: Story = {
|
|
209
|
+
name: "Accounts — OAuth error",
|
|
210
|
+
args: { url: "/settings/accounts?oauthError=consent_required" },
|
|
211
|
+
play: async ({ canvasElement }) => {
|
|
212
|
+
const canvas = within(canvasElement);
|
|
213
|
+
await expect(
|
|
214
|
+
await canvas.findByText(/admin needs to approve Remit/),
|
|
215
|
+
).toBeVisible();
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const AccountsDeleteConfirm: Story = {
|
|
220
|
+
name: "Accounts — delete confirm",
|
|
221
|
+
play: async ({ canvasElement }) => {
|
|
222
|
+
const canvas = within(canvasElement);
|
|
223
|
+
await canvas.findByText("Work");
|
|
224
|
+
const remove = canvas.getAllByRole("button", { name: "Delete account" })[1];
|
|
225
|
+
if (remove) await userEvent.click(remove);
|
|
226
|
+
const dialog = within(
|
|
227
|
+
await page.findByRole("dialog", { name: "Delete account" }),
|
|
228
|
+
);
|
|
229
|
+
await expect(dialog.getByText("Are you sure?")).toBeVisible();
|
|
230
|
+
await expect(dialog.getByText("alice@northwind.example")).toBeVisible();
|
|
231
|
+
await expect(
|
|
232
|
+
dialog.getByRole("button", { name: "Delete account" }),
|
|
233
|
+
).toBeEnabled();
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const AccountEdit: Story = {
|
|
238
|
+
name: "Accounts — edit",
|
|
239
|
+
globals: desktop,
|
|
240
|
+
play: async ({ canvasElement }) => {
|
|
241
|
+
const panel = within(await openEditPanel(canvasElement, 0));
|
|
242
|
+
await expect(panel.getByLabelText("Display name (optional)")).toHaveValue(
|
|
243
|
+
"Personal",
|
|
244
|
+
);
|
|
245
|
+
await expect(panel.getByLabelText("Email Address")).toHaveValue(
|
|
246
|
+
"alice.tan@gmail.example",
|
|
247
|
+
);
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
export const AccountEditClosed: Story = {
|
|
252
|
+
name: "Accounts — edit panel closed",
|
|
253
|
+
globals: desktop,
|
|
254
|
+
play: async ({ canvasElement }) => {
|
|
255
|
+
const canvas = within(canvasElement);
|
|
256
|
+
await expect(await canvas.findByText("Personal")).toBeVisible();
|
|
257
|
+
await expect(
|
|
258
|
+
page.queryByRole("dialog", { name: "Edit Account" }),
|
|
259
|
+
).toBeNull();
|
|
260
|
+
await expect(
|
|
261
|
+
canvas.getByRole("button", { name: "Add account" }),
|
|
262
|
+
).toBeVisible();
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export const AccountEditPhone: Story = {
|
|
267
|
+
name: "Accounts — edit, phone",
|
|
268
|
+
globals: phone,
|
|
269
|
+
play: async ({ canvasElement }) => {
|
|
270
|
+
const panel = await openEditPanel(canvasElement, 0);
|
|
271
|
+
await expect(panel).toBeVisible();
|
|
272
|
+
await expect(
|
|
273
|
+
within(panel).getByLabelText("Display name (optional)"),
|
|
274
|
+
).toBeVisible();
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export const AccountEditNoDisplayName: Story = {
|
|
279
|
+
name: "Accounts — edit, no display name",
|
|
280
|
+
parameters: {
|
|
281
|
+
msw: {
|
|
282
|
+
handlers: mailHandlers(
|
|
283
|
+
mailWorld({
|
|
284
|
+
accounts: [
|
|
285
|
+
makeAccount({
|
|
286
|
+
accountId: "acc-personal",
|
|
287
|
+
email: "alice.tan@gmail.example",
|
|
288
|
+
lastSyncAt: NOW,
|
|
289
|
+
}),
|
|
290
|
+
],
|
|
291
|
+
}),
|
|
292
|
+
),
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
play: async ({ canvasElement }) => {
|
|
296
|
+
const canvas = within(canvasElement);
|
|
297
|
+
await expect(await canvas.findByText("Alice Tan")).toBeVisible();
|
|
298
|
+
const panel = within(await openEditPanel(canvasElement, 0));
|
|
299
|
+
const field = panel.getByLabelText("Display name (optional)");
|
|
300
|
+
await expect(field).toHaveValue("");
|
|
301
|
+
await expect(field).toHaveAttribute("placeholder", "Alice");
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export const DangerZone: Story = {
|
|
306
|
+
name: "Danger zone",
|
|
307
|
+
play: async ({ canvasElement }) => {
|
|
308
|
+
const canvas = within(canvasElement);
|
|
309
|
+
await canvas.findByText("Personal");
|
|
310
|
+
await expect(
|
|
311
|
+
canvas.getByText(/erases Remit's copy of your mail/),
|
|
312
|
+
).toBeInTheDocument();
|
|
313
|
+
await expect(
|
|
314
|
+
canvas.getByRole("button", { name: "Delete your Remit account" }),
|
|
315
|
+
).toBeInTheDocument();
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
export const DangerZoneConfirm: Story = {
|
|
320
|
+
name: "Danger zone — confirm dialog",
|
|
321
|
+
args: { authProvider: signedInAs("alice.tan@gmail.example") },
|
|
322
|
+
play: async ({ canvasElement }) => {
|
|
323
|
+
const canvas = within(canvasElement);
|
|
324
|
+
await canvas.findByText("Personal");
|
|
325
|
+
await userEvent.click(
|
|
326
|
+
canvas.getByRole("button", { name: "Delete your Remit account" }),
|
|
327
|
+
);
|
|
328
|
+
const dialog = within(
|
|
329
|
+
await page.findByRole("dialog", { name: "Delete your Remit account" }),
|
|
330
|
+
);
|
|
331
|
+
await expect(dialog.getByText("alice.tan@gmail.example")).toBeVisible();
|
|
332
|
+
await userEvent.type(
|
|
333
|
+
dialog.getByLabelText(/to confirm/),
|
|
334
|
+
"someone@else.example",
|
|
335
|
+
);
|
|
336
|
+
await userEvent.click(
|
|
337
|
+
dialog.getByRole("button", { name: "Delete everything" }),
|
|
338
|
+
);
|
|
339
|
+
await waitFor(() =>
|
|
340
|
+
expect(dialog.getByRole("alert")).toHaveTextContent(
|
|
341
|
+
"That doesn't match your account email",
|
|
342
|
+
),
|
|
343
|
+
);
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export const AccountsUnreadable: Story = {
|
|
348
|
+
name: "Accounts — unreadable",
|
|
349
|
+
parameters: {
|
|
350
|
+
msw: {
|
|
351
|
+
handlers: [
|
|
352
|
+
http.get("/api/config", () => HttpResponse.error()),
|
|
353
|
+
...mailHandlers(world),
|
|
354
|
+
],
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
play: async ({ canvasElement }) => {
|
|
358
|
+
const canvas = within(canvasElement);
|
|
359
|
+
await expect(
|
|
360
|
+
await canvas.findByText("Couldn't load accounts"),
|
|
361
|
+
).toBeVisible();
|
|
362
|
+
},
|
|
363
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { RemitImapQuarantineResponse } from "@remit/api-http-client/types.gen.ts";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
3
|
+
import { HttpResponse, http } from "msw";
|
|
4
|
+
import { expect, within } from "storybook/test";
|
|
5
|
+
import { AppStory } from "@/mocks/story-frame/AppStory";
|
|
6
|
+
import { mailHandlers } from "@/mocks/story-frame/mail-handlers";
|
|
7
|
+
import {
|
|
8
|
+
mailboxIdFor,
|
|
9
|
+
mailWorld,
|
|
10
|
+
NOW,
|
|
11
|
+
PERSONAL,
|
|
12
|
+
} from "@/mocks/story-frame/mail-world";
|
|
13
|
+
import { withRuntimeConfig } from "@/mocks/story-frame/session";
|
|
14
|
+
|
|
15
|
+
const setAside = (
|
|
16
|
+
id: string,
|
|
17
|
+
role: "Inbox" | "Archive",
|
|
18
|
+
failureCode: RemitImapQuarantineResponse["failureCode"],
|
|
19
|
+
failureMessage: string,
|
|
20
|
+
): RemitImapQuarantineResponse => ({
|
|
21
|
+
quarantineId: `q-${id}`,
|
|
22
|
+
accountConfigId: "cfg-1",
|
|
23
|
+
accountId: PERSONAL,
|
|
24
|
+
mailboxId: mailboxIdFor(PERSONAL, role),
|
|
25
|
+
uidValidity: 1_712_000_000,
|
|
26
|
+
uid: 40_217,
|
|
27
|
+
mailboxRole: role,
|
|
28
|
+
mailboxPath: role === "Inbox" ? "INBOX" : "Archive",
|
|
29
|
+
quarantinedAt: NOW,
|
|
30
|
+
attempts: 3,
|
|
31
|
+
failureStage: "BodyParse",
|
|
32
|
+
failureCode,
|
|
33
|
+
failureMessage,
|
|
34
|
+
workerVersion: "worker 1.0.0",
|
|
35
|
+
contentType: "multipart/mixed",
|
|
36
|
+
sizeBytes: 184_233,
|
|
37
|
+
structure: [{ depth: 0, contentType: "multipart/mixed" }],
|
|
38
|
+
createdAt: NOW,
|
|
39
|
+
updatedAt: NOW,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const unreadableBody = setAside(
|
|
43
|
+
"1",
|
|
44
|
+
"Inbox",
|
|
45
|
+
"UnreadableBody",
|
|
46
|
+
"multipart boundary was never closed",
|
|
47
|
+
);
|
|
48
|
+
const unknownCharset = setAside(
|
|
49
|
+
"2",
|
|
50
|
+
"Archive",
|
|
51
|
+
"UnknownCharset",
|
|
52
|
+
"declared charset is not a known encoding",
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const meta = {
|
|
56
|
+
title: "Playground/Shipped/Settings/Advanced/Overview",
|
|
57
|
+
component: AppStory,
|
|
58
|
+
args: { url: "/settings/advanced" },
|
|
59
|
+
parameters: {
|
|
60
|
+
layout: "fullscreen",
|
|
61
|
+
msw: { handlers: mailHandlers(mailWorld()) },
|
|
62
|
+
},
|
|
63
|
+
} satisfies Meta<typeof AppStory>;
|
|
64
|
+
export default meta;
|
|
65
|
+
|
|
66
|
+
type Story = StoryObj<typeof meta>;
|
|
67
|
+
|
|
68
|
+
export const Advanced: Story = {
|
|
69
|
+
play: async ({ canvasElement }) => {
|
|
70
|
+
const canvas = within(canvasElement);
|
|
71
|
+
await expect(
|
|
72
|
+
await canvas.findByText(/Every message has been read successfully/),
|
|
73
|
+
).toBeVisible();
|
|
74
|
+
await expect(canvas.getByText("About")).toBeVisible();
|
|
75
|
+
await expect(
|
|
76
|
+
canvas.queryByRole("link", { name: "Download root certificate" }),
|
|
77
|
+
).toBeNull();
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const AdvancedOneQuarantined: Story = {
|
|
82
|
+
parameters: {
|
|
83
|
+
msw: {
|
|
84
|
+
handlers: mailHandlers(mailWorld({ quarantine: [unreadableBody] })),
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
play: async ({ canvasElement }) => {
|
|
88
|
+
const canvas = within(canvasElement);
|
|
89
|
+
await expect(
|
|
90
|
+
await canvas.findByText(/multipart boundary was never closed/),
|
|
91
|
+
).toBeVisible();
|
|
92
|
+
await expect(canvas.queryByText(/messages could not be read/)).toBeNull();
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const AdvancedQuarantineAlert: Story = {
|
|
97
|
+
parameters: {
|
|
98
|
+
msw: {
|
|
99
|
+
handlers: mailHandlers(
|
|
100
|
+
mailWorld({ quarantine: [unreadableBody, unknownCharset] }),
|
|
101
|
+
),
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
play: async ({ canvasElement }) => {
|
|
105
|
+
const canvas = within(canvasElement);
|
|
106
|
+
await expect(
|
|
107
|
+
await canvas.findByText("2 messages could not be read."),
|
|
108
|
+
).toBeVisible();
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const AdvancedQuarantineLoading: Story = {
|
|
113
|
+
parameters: {
|
|
114
|
+
msw: {
|
|
115
|
+
handlers: [
|
|
116
|
+
http.get(
|
|
117
|
+
"/api/me/quarantine",
|
|
118
|
+
() => new Promise<never>(() => undefined),
|
|
119
|
+
),
|
|
120
|
+
...mailHandlers(mailWorld()),
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
play: async ({ canvasElement }) => {
|
|
125
|
+
const canvas = within(canvasElement);
|
|
126
|
+
await expect(
|
|
127
|
+
await canvas.findByText("Checking for messages set aside…"),
|
|
128
|
+
).toBeVisible();
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const AdvancedQuarantineUnreadable: Story = {
|
|
133
|
+
parameters: {
|
|
134
|
+
msw: {
|
|
135
|
+
handlers: [
|
|
136
|
+
http.get("/api/me/quarantine", () => HttpResponse.error()),
|
|
137
|
+
...mailHandlers(mailWorld()),
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
play: async ({ canvasElement }) => {
|
|
142
|
+
const canvas = within(canvasElement);
|
|
143
|
+
await expect(
|
|
144
|
+
await canvas.findByText(
|
|
145
|
+
"The list of set-aside messages could not be read.",
|
|
146
|
+
),
|
|
147
|
+
).toBeVisible();
|
|
148
|
+
await expect(
|
|
149
|
+
canvas.getByRole("button", { name: "Report this" }),
|
|
150
|
+
).toBeVisible();
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export const AdvancedTlsInternal: Story = {
|
|
155
|
+
beforeEach: withRuntimeConfig({ tlsMode: "internal" }),
|
|
156
|
+
play: async ({ canvasElement }) => {
|
|
157
|
+
const canvas = within(canvasElement);
|
|
158
|
+
await expect(
|
|
159
|
+
await canvas.findByRole("link", { name: "Download root certificate" }),
|
|
160
|
+
).toHaveAttribute("href", "/tls-root-ca.crt");
|
|
161
|
+
},
|
|
162
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, within } from "storybook/test";
|
|
3
|
+
import { AppStory } from "@/mocks/story-frame/AppStory";
|
|
4
|
+
import { mailHandlers } from "@/mocks/story-frame/mail-handlers";
|
|
5
|
+
import { mailWorld } from "@/mocks/story-frame/mail-world";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Playground/Shipped/Settings/Appearance",
|
|
9
|
+
component: AppStory,
|
|
10
|
+
args: { url: "/settings/appearance" },
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: "fullscreen",
|
|
13
|
+
msw: { handlers: mailHandlers(mailWorld()) },
|
|
14
|
+
},
|
|
15
|
+
} satisfies Meta<typeof AppStory>;
|
|
16
|
+
export default meta;
|
|
17
|
+
|
|
18
|
+
type Story = StoryObj<typeof meta>;
|
|
19
|
+
|
|
20
|
+
export const Appearance: Story = {
|
|
21
|
+
play: async ({ canvasElement }) => {
|
|
22
|
+
const canvas = within(canvasElement);
|
|
23
|
+
const density = await canvas.findByRole("radiogroup", { name: "Density" });
|
|
24
|
+
await expect(
|
|
25
|
+
within(density).getByRole("radio", { name: "Comfortable" }),
|
|
26
|
+
).toBeChecked();
|
|
27
|
+
const theme = canvas.getByRole("radiogroup", { name: "Theme" });
|
|
28
|
+
await expect(within(theme).getAllByRole("radio")).toHaveLength(3);
|
|
29
|
+
await expect(
|
|
30
|
+
canvas.getByText(/System default follows your OS preference/),
|
|
31
|
+
).toBeVisible();
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, userEvent, within } from "storybook/test";
|
|
3
|
+
import { AppStory } from "@/mocks/story-frame/AppStory";
|
|
4
|
+
import { mailHandlers } from "@/mocks/story-frame/mail-handlers";
|
|
5
|
+
import {
|
|
6
|
+
accountWithFolders,
|
|
7
|
+
foldersOf,
|
|
8
|
+
mailWorld,
|
|
9
|
+
} from "@/mocks/story-frame/mail-world";
|
|
10
|
+
import { makeMailbox } from "@/test-support/fixtures";
|
|
11
|
+
|
|
12
|
+
const ACCOUNT_ID = "acc-work";
|
|
13
|
+
const EMAIL = "alice@northwind.example";
|
|
14
|
+
|
|
15
|
+
const folder = (path: string, messageCount: number) =>
|
|
16
|
+
makeMailbox({
|
|
17
|
+
accountId: ACCOUNT_ID,
|
|
18
|
+
mailboxId: `mbx-${path.toLowerCase().replaceAll("/", "-")}`,
|
|
19
|
+
fullPath: path,
|
|
20
|
+
messageCount,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const world = mailWorld({
|
|
24
|
+
accounts: [accountWithFolders(ACCOUNT_ID, EMAIL, "Work")],
|
|
25
|
+
mailboxes: [
|
|
26
|
+
...foldersOf(ACCOUNT_ID),
|
|
27
|
+
folder("Travel", 0),
|
|
28
|
+
folder("Travel/Flights", 41),
|
|
29
|
+
folder("Travel/Hotels", 96),
|
|
30
|
+
folder("Finance", 0),
|
|
31
|
+
folder("Finance/Invoices", 12),
|
|
32
|
+
folder("Family", 230),
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const meta = {
|
|
37
|
+
title: "Playground/Shipped/Settings/Folder roles",
|
|
38
|
+
component: AppStory,
|
|
39
|
+
args: { url: "/settings/folders" },
|
|
40
|
+
parameters: {
|
|
41
|
+
layout: "fullscreen",
|
|
42
|
+
msw: { handlers: mailHandlers(world) },
|
|
43
|
+
},
|
|
44
|
+
} satisfies Meta<typeof AppStory>;
|
|
45
|
+
export default meta;
|
|
46
|
+
|
|
47
|
+
type Story = StoryObj<typeof meta>;
|
|
48
|
+
|
|
49
|
+
const page = within(document.body);
|
|
50
|
+
|
|
51
|
+
export const Desktop: Story = {
|
|
52
|
+
globals: { viewport: { value: "desktop", isRotated: false } },
|
|
53
|
+
play: async ({ canvasElement }) => {
|
|
54
|
+
const canvas = within(canvasElement);
|
|
55
|
+
await expect(
|
|
56
|
+
await canvas.findByRole("heading", { name: `Your folders — ${EMAIL}` }),
|
|
57
|
+
).toBeVisible();
|
|
58
|
+
await expect(
|
|
59
|
+
canvas.getByRole("tree", { name: `All folders for ${EMAIL}` }),
|
|
60
|
+
).toBeVisible();
|
|
61
|
+
await expect(
|
|
62
|
+
canvas.getByRole("button", { name: "Rename Travel" }),
|
|
63
|
+
).toBeInTheDocument();
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const Phone: Story = {
|
|
68
|
+
globals: { viewport: { value: "mobile", isRotated: false } },
|
|
69
|
+
play: async ({ canvasElement }) => {
|
|
70
|
+
const canvas = within(canvasElement);
|
|
71
|
+
await expect(
|
|
72
|
+
await canvas.findByRole("tree", { name: `All folders for ${EMAIL}` }),
|
|
73
|
+
).toBeVisible();
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const Renaming: Story = {
|
|
78
|
+
globals: { viewport: { value: "desktop", isRotated: false } },
|
|
79
|
+
play: async ({ canvasElement }) => {
|
|
80
|
+
const canvas = within(canvasElement);
|
|
81
|
+
await userEvent.click(
|
|
82
|
+
await canvas.findByRole("button", { name: "Rename Travel" }),
|
|
83
|
+
);
|
|
84
|
+
const dialog = within(
|
|
85
|
+
await page.findByRole("dialog", { name: "Rename Travel" }),
|
|
86
|
+
);
|
|
87
|
+
await expect(dialog.getByRole("textbox")).toBeVisible();
|
|
88
|
+
},
|
|
89
|
+
};
|