@remit/web-client 0.0.302 → 0.0.304

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.
@@ -0,0 +1,171 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, userEvent, waitFor, within } from "storybook/test";
3
+ import { AppStory } from "@/mocks/story-frame/AppStory";
4
+ import {
5
+ composeWorld,
6
+ INBOX,
7
+ messageUrl,
8
+ } from "@/mocks/story-frame/compose-world";
9
+ import { mailHandlers } from "@/mocks/story-frame/mail-handlers";
10
+
11
+ const world = composeWorld();
12
+
13
+ const meta = {
14
+ title: "Playground/Shipped/Mail/Detail surface",
15
+ component: AppStory,
16
+ args: { url: "/mail/brief" },
17
+ parameters: {
18
+ layout: "fullscreen",
19
+ msw: { handlers: mailHandlers(world) },
20
+ },
21
+ } satisfies Meta<typeof AppStory>;
22
+ export default meta;
23
+
24
+ type Story = StoryObj<typeof meta>;
25
+
26
+ const page = () => within(document.body);
27
+
28
+ const rows = (): HTMLElement[] =>
29
+ Array.from(document.querySelectorAll<HTMLElement>("[data-list-row]"));
30
+
31
+ const unsent = ["Design review notes", "Re: contract"];
32
+
33
+ const composeAndSearch = async () => {
34
+ await userEvent.click(await page().findByRole("button", { name: "Compose" }));
35
+ await expect(await page().findByLabelText("To:")).toBeVisible();
36
+ const search = page().getByLabelText("Search mail");
37
+ await userEvent.type(search, "invoice");
38
+ await expect(search).toHaveFocus();
39
+ await expect(page().getAllByLabelText("To:")).toHaveLength(1);
40
+ };
41
+
42
+ export const SheetOpensOverTheRail: Story = {
43
+ args: { url: `${messageUrl("q3-planning")}#intelligence` },
44
+ globals: { viewport: { value: "desktop", isRotated: false } },
45
+ play: async () => {
46
+ await expect(await page().findByText("Intelligence")).toBeInTheDocument();
47
+
48
+ await userEvent.keyboard("?");
49
+ const sheet = await page().findByRole("dialog", {
50
+ name: "Keyboard shortcuts",
51
+ });
52
+ await expect(sheet).toBeVisible();
53
+ await expect(page().getByText("Intelligence")).toBeInTheDocument();
54
+
55
+ await userEvent.keyboard("j");
56
+ await expect(page().getByRole("dialog")).toBeVisible();
57
+ await expect(page().getByText("Intelligence")).toBeInTheDocument();
58
+
59
+ await userEvent.keyboard("{Escape}");
60
+ await waitFor(() => expect(page().queryByRole("dialog")).toBeNull());
61
+ await expect(page().getByText("Intelligence")).toBeInTheDocument();
62
+ },
63
+ };
64
+
65
+ export const DeleteConfirmOutlastsAKeystroke: Story = {
66
+ args: { url: `/mail/${INBOX}` },
67
+ play: async () => {
68
+ await page().findByText("Q3 planning");
69
+ rows()[0]?.focus();
70
+ await userEvent.keyboard("j");
71
+ await userEvent.keyboard("k");
72
+ await userEvent.keyboard("#");
73
+ const dialog = await page().findByRole("dialog");
74
+ await expect(
75
+ within(dialog).getByText("Move 1 message to Trash?"),
76
+ ).toBeVisible();
77
+
78
+ await userEvent.keyboard("j");
79
+ await expect(page().getByRole("dialog")).toBeVisible();
80
+
81
+ await userEvent.click(
82
+ within(dialog).getByRole("button", { name: "Cancel" }),
83
+ );
84
+ await waitFor(() => expect(page().queryByRole("dialog")).toBeNull());
85
+ await expect(rows()[0]).toHaveTextContent("Q3 planning");
86
+ },
87
+ };
88
+
89
+ export const ComposeOverTheBrief: Story = {
90
+ play: async () => {
91
+ await page().findByText("Q3 planning");
92
+ await composeAndSearch();
93
+ },
94
+ };
95
+
96
+ export const ComposeOverStarred: Story = {
97
+ args: { url: "/mail/flagged" },
98
+ play: async () => {
99
+ await page().findByText("Invoice #1042");
100
+ await composeAndSearch();
101
+ await expect(page().getByText("Invoice #1042")).toBeVisible();
102
+ },
103
+ };
104
+
105
+ export const ComposeOverAFolder: Story = {
106
+ args: { url: `/mail/${INBOX}` },
107
+ play: async () => {
108
+ await page().findByText("Q3 planning");
109
+ await userEvent.click(page().getByRole("button", { name: "Compose" }));
110
+ await expect(await page().findByLabelText("To:")).toBeVisible();
111
+ await expect(page().getByText("Q3 planning")).toBeVisible();
112
+ },
113
+ };
114
+
115
+ export const ComposeOnThePhone: Story = {
116
+ globals: { viewport: { value: "mobile", isRotated: false } },
117
+ play: async () => {
118
+ await page().findByText("Q3 planning");
119
+ await userEvent.click(
120
+ page().getByRole("button", { name: "Compose new message" }),
121
+ );
122
+ const sheet = await page().findByRole("dialog");
123
+ await expect(within(sheet).getByLabelText("To:")).toBeVisible();
124
+ },
125
+ };
126
+
127
+ export const EditADraftFromTheOutbox: Story = {
128
+ args: { url: "/mail/outbox" },
129
+ play: async () => {
130
+ await page().findByText("Design review notes");
131
+ const edits = page().getAllByRole("button", { name: "Edit as draft" });
132
+ const [first] = edits;
133
+ if (!first) throw new Error("no outbox row offers Edit as draft");
134
+ let row: HTMLElement = first;
135
+ while (
136
+ row.parentElement &&
137
+ within(row.parentElement).queryAllByRole("button", {
138
+ name: "Edit as draft",
139
+ }).length === 1
140
+ )
141
+ row = row.parentElement;
142
+ const subject = unsent.find((candidate) =>
143
+ row.textContent?.includes(candidate),
144
+ );
145
+ await userEvent.click(first);
146
+
147
+ const field = await page().findByPlaceholderText("Subject");
148
+ await waitFor(() => expect(field).toHaveValue(subject));
149
+ const others = unsent.filter((candidate) => candidate !== subject);
150
+ await expect(page().getByText(others[0] ?? "")).toBeVisible();
151
+ },
152
+ };
153
+
154
+ export const ReplyUnderTheMessage: Story = {
155
+ args: { url: messageUrl("q3-planning") },
156
+ play: async () => {
157
+ await page().findByRole("heading", { level: 1, name: "Q3 planning" });
158
+ await userEvent.click(page().getByRole("button", { name: "Reply" }));
159
+
160
+ const subject = await page().findByPlaceholderText("Subject");
161
+ await waitFor(() => expect(subject).toHaveValue("Re: Q3 planning"));
162
+ await expect(page().getByTestId("conversation-messages")).toBeVisible();
163
+
164
+ const search = page().getByLabelText("Search mail");
165
+ await userEvent.type(search, "invoice");
166
+ await expect(search).toHaveFocus();
167
+ await expect(page().getAllByLabelText("To:")).toHaveLength(1);
168
+ await expect(subject).toHaveValue("Re: Q3 planning");
169
+ await expect(page().getByTestId("conversation-messages")).toBeVisible();
170
+ },
171
+ };
@@ -0,0 +1,82 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, userEvent, waitFor, within } from "storybook/test";
3
+ import { AppStory } from "@/mocks/story-frame/AppStory";
4
+ import { mailHandlers } from "@/mocks/story-frame/mail-handlers";
5
+ import {
6
+ mailboxIdFor,
7
+ mailWorld,
8
+ senderAddress,
9
+ WORK,
10
+ } from "@/mocks/story-frame/mail-world";
11
+
12
+ const openMessage = `/mail/${mailboxIdFor(WORK, "Inbox")}/thread-q3-planning/msg-q3-planning`;
13
+
14
+ const world = mailWorld();
15
+
16
+ const meta = {
17
+ title: "Playground/Shipped/Mail/Mail pickers",
18
+ component: AppStory,
19
+ args: { url: openMessage },
20
+ parameters: {
21
+ layout: "fullscreen",
22
+ msw: { handlers: mailHandlers(world) },
23
+ },
24
+ } satisfies Meta<typeof AppStory>;
25
+ export default meta;
26
+
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ const page = () => within(document.body);
30
+
31
+ export const MovePicker: Story = {
32
+ play: async () => {
33
+ await page().findByRole("heading", { level: 1, name: "Q3 planning" });
34
+ const trigger = await waitFor(() => {
35
+ const button = page().getByRole("button", { name: "Move to mailbox" });
36
+ expect(button).toHaveAttribute("aria-haspopup", "tree");
37
+ return button;
38
+ });
39
+ await userEvent.click(trigger);
40
+ const filter = await page().findByLabelText("Filter folders");
41
+ await expect(filter).toBeVisible();
42
+ for (const folder of ["Archive", "Trash"]) {
43
+ await expect(
44
+ await page().findByRole("treeitem", { name: new RegExp(folder) }),
45
+ ).toBeVisible();
46
+ }
47
+ },
48
+ };
49
+
50
+ export const ReclassifyDialog: Story = {
51
+ parameters: {
52
+ msw: {
53
+ handlers: mailHandlers({
54
+ ...world,
55
+ addresses: world.threads
56
+ .filter((row) => row.messageId === "msg-q3-planning")
57
+ .map(senderAddress),
58
+ }),
59
+ },
60
+ },
61
+ args: { url: `${openMessage}#intelligence` },
62
+ globals: { viewport: { value: "desktop", isRotated: false } },
63
+ play: async () => {
64
+ const reclassify = await page().findByRole("button", {
65
+ name: "reclassify",
66
+ });
67
+ await waitFor(() => expect(reclassify).toBeEnabled());
68
+ await userEvent.click(reclassify);
69
+ const dialog = await page().findByRole("dialog", {
70
+ name: "Reclassify sender",
71
+ });
72
+ await expect(
73
+ within(dialog).getByText("Reclassify this sender"),
74
+ ).toBeVisible();
75
+ await expect(
76
+ within(dialog).getByRole("button", { name: /Newsletter/ }),
77
+ ).toBeVisible();
78
+ await expect(
79
+ within(dialog).getByRole("button", { name: "Cancel" }),
80
+ ).toBeVisible();
81
+ },
82
+ };
@@ -0,0 +1,280 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, userEvent, waitFor, within } from "storybook/test";
3
+ import { AppStory } from "@/mocks/story-frame/AppStory";
4
+ import { mailHandlers } from "@/mocks/story-frame/mail-handlers";
5
+ import {
6
+ HOUR,
7
+ mailboxIdFor,
8
+ mailWorld,
9
+ PERSONAL,
10
+ threadRow,
11
+ WORK,
12
+ } from "@/mocks/story-frame/mail-world";
13
+
14
+ const base = mailWorld();
15
+ const world = {
16
+ ...base,
17
+ threads: [
18
+ ...base.threads,
19
+ threadRow({
20
+ id: "spam-invoice",
21
+ accountId: PERSONAL,
22
+ role: "Junk",
23
+ fromName: "Accounts Dept",
24
+ fromEmail: "billing@lottery.example",
25
+ subject: "Overdue invoice, act now",
26
+ snippet: "Pay within 24 hours to avoid suspension.",
27
+ category: "marketing",
28
+ ago: 3 * HOUR,
29
+ }),
30
+ threadRow({
31
+ id: "spam-invoice-2",
32
+ accountId: PERSONAL,
33
+ role: "Junk",
34
+ fromName: "Prize Desk",
35
+ fromEmail: "claims@lottery.example",
36
+ subject: "Invoice for your prize",
37
+ snippet: "Claim your prize by settling the attached invoice.",
38
+ category: "marketing",
39
+ ago: 4 * HOUR,
40
+ }),
41
+ ],
42
+ };
43
+ const inbox = mailboxIdFor(WORK, "Inbox");
44
+ const junk = mailboxIdFor(PERSONAL, "Junk");
45
+ const phone = { viewport: { value: "mobile", isRotated: false } };
46
+ const widePhone = { viewport: { value: "mobileShort", isRotated: false } };
47
+
48
+ const meta = {
49
+ title: "Playground/Shipped/Mail/Search",
50
+ component: AppStory,
51
+ args: { url: "/mail/brief?q=invoice" },
52
+ parameters: {
53
+ layout: "fullscreen",
54
+ msw: { handlers: mailHandlers(world) },
55
+ },
56
+ } satisfies Meta<typeof AppStory>;
57
+ export default meta;
58
+
59
+ type Story = StoryObj<typeof meta>;
60
+
61
+ const page = () => within(document.body);
62
+
63
+ const shown = async (text: string): Promise<HTMLElement> => {
64
+ const [match] = await page().findAllByText(
65
+ (_, element) =>
66
+ element?.textContent === text &&
67
+ ![...element.children].some((child) => child.textContent === text),
68
+ );
69
+ if (!match) throw new Error(`"${text}" is not on screen`);
70
+ return match;
71
+ };
72
+
73
+ const skeletonRows = (): number =>
74
+ document.querySelectorAll(".animate-pulse").length;
75
+
76
+ const openPhoneSearch = async () => {
77
+ await page().findByText("Q3 planning");
78
+ await userEvent.click(page().getByRole("button", { name: "Search" }));
79
+ };
80
+
81
+ const recentSearches = {
82
+ beforeEach: () => {
83
+ localStorage.setItem(
84
+ "remit.recentSearches",
85
+ JSON.stringify(["invoice", "from:ada"]),
86
+ );
87
+ return () => localStorage.removeItem("remit.recentSearches");
88
+ },
89
+ };
90
+
91
+ export const Global: Story = {
92
+ play: async () => {
93
+ await expect(await shown("Invoice #1042")).toBeVisible();
94
+ await expect(page().queryByText("Overdue invoice, act now")).toBeNull();
95
+ const offer = await page().findByRole("button", { name: "Go to Spam" });
96
+ await expect(offer.parentElement).toHaveTextContent("2 results from Spam");
97
+ await expect(page().queryByText("Q3 planning")).toBeNull();
98
+ },
99
+ };
100
+
101
+ export const GoToSpam: Story = {
102
+ play: async () => {
103
+ await userEvent.click(
104
+ await page().findByRole("button", { name: "Go to Spam" }),
105
+ );
106
+ await expect(
107
+ await page().findByText("Overdue invoice, act now"),
108
+ ).toBeVisible();
109
+ await expect(page().getByText("Invoice for your prize")).toBeVisible();
110
+ await expect(page().queryByText("Invoice #1042")).toBeNull();
111
+ },
112
+ };
113
+
114
+ export const ScopedToSpam: Story = {
115
+ args: { url: `/mail/${junk}?q=invoice` },
116
+ play: async () => {
117
+ await expect(
118
+ await page().findByText("Overdue invoice, act now"),
119
+ ).toBeVisible();
120
+ await expect(
121
+ page().queryByRole("button", { name: "Go to Spam" }),
122
+ ).toBeNull();
123
+ },
124
+ };
125
+
126
+ export const ScopedToFolder: Story = {
127
+ args: { url: `/mail/${inbox}?q=invoice` },
128
+ play: async () => {
129
+ await expect(await shown("Invoice #1042")).toBeVisible();
130
+ await expect(page().queryByText("Q3 planning")).toBeNull();
131
+ await expect(page().queryByText("Overdue invoice, act now")).toBeNull();
132
+ await expect(
133
+ page().getByRole("button", { name: "Make this a filter" }),
134
+ ).toBeVisible();
135
+ },
136
+ };
137
+
138
+ export const UnsearchedFolder: Story = {
139
+ args: { url: `/mail/${inbox}` },
140
+ play: async () => {
141
+ await expect(await page().findByText("Q3 planning")).toBeVisible();
142
+ await expect(page().getByLabelText("Expand filters")).toBeVisible();
143
+ await expect(
144
+ page().queryByRole("button", { name: "Make this a filter" }),
145
+ ).toBeNull();
146
+ },
147
+ };
148
+
149
+ export const NothingToConvert: Story = {
150
+ args: { url: `/mail/${inbox}?q=has:attachment` },
151
+ play: async () => {
152
+ await userEvent.click(
153
+ await page().findByRole("button", { name: "Make this a filter" }),
154
+ );
155
+ await expect(
156
+ await page().findByText(/isn't a filter condition/, { selector: "p" }),
157
+ ).toBeVisible();
158
+ },
159
+ };
160
+
161
+ export const Loading: Story = {
162
+ args: { url: "/mail/brief" },
163
+ parameters: {
164
+ msw: { handlers: mailHandlers(world, { search: "holds" }) },
165
+ },
166
+ play: async () => {
167
+ await page().findByText("Q3 planning");
168
+ await userEvent.type(page().getByLabelText("Search mail"), "invoice");
169
+ await waitFor(() => expect(skeletonRows()).toBeGreaterThan(0));
170
+ await expect(page().queryByText("Invoice #1042")).toBeNull();
171
+ },
172
+ };
173
+
174
+ export const NoMatches: Story = {
175
+ args: { url: "/mail/brief?q=hedgehog" },
176
+ play: async () => {
177
+ await expect(
178
+ await page().findByText("No threads match these filters."),
179
+ ).toBeVisible();
180
+ },
181
+ };
182
+
183
+ export const SuggestingTokens: Story = {
184
+ args: { url: `/mail/${inbox}` },
185
+ globals: { viewport: { value: "tablet", isRotated: false } },
186
+ play: async () => {
187
+ await page().findByText("Q3 planning");
188
+ await userEvent.click(page().getByRole("button", { name: "Search" }));
189
+ await userEvent.type(page().getByLabelText("Search mail"), "is:u");
190
+ const suggestions = await page().findByRole("listbox", {
191
+ name: "Search suggestions",
192
+ });
193
+ await expect(
194
+ within(suggestions).getByRole("option", { name: "Unread" }),
195
+ ).toBeVisible();
196
+ },
197
+ };
198
+
199
+ export const PhoneTakeover: Story = {
200
+ args: { url: `/mail/${inbox}` },
201
+ globals: phone,
202
+ play: async () => {
203
+ await openPhoneSearch();
204
+ await userEvent.type(page().getByLabelText("Search mail"), "invoice");
205
+ await expect(await shown("Invoice #1042")).toBeVisible();
206
+ await expect(
207
+ page().getByRole("button", { name: "Clear and close search" }),
208
+ ).toBeVisible();
209
+ },
210
+ };
211
+
212
+ export const PhoneWithFilterTokens: Story = {
213
+ args: { url: "/mail/brief" },
214
+ globals: phone,
215
+ play: async () => {
216
+ await openPhoneSearch();
217
+ await userEvent.type(
218
+ page().getByLabelText("Search mail"),
219
+ "from:billing invoice",
220
+ );
221
+ await expect(await shown("Invoice #1042")).toBeVisible();
222
+ await expect(
223
+ await page().findByRole("button", { name: /^Remove filter: .*billing/ }),
224
+ ).toBeVisible();
225
+ },
226
+ };
227
+
228
+ export const PhoneBriefTakeover: Story = {
229
+ args: { url: "/mail/brief" },
230
+ globals: widePhone,
231
+ play: async () => {
232
+ await openPhoneSearch();
233
+ await userEvent.type(page().getByLabelText("Search mail"), "invoice");
234
+ await expect(await shown("Invoice #1042")).toBeVisible();
235
+ await expect(
236
+ page().getByRole("button", { name: "Clear and close search" }),
237
+ ).toBeVisible();
238
+ },
239
+ };
240
+
241
+ export const PhoneFolderTakeover: Story = {
242
+ args: { url: `/mail/${inbox}` },
243
+ globals: widePhone,
244
+ play: async () => {
245
+ await openPhoneSearch();
246
+ await userEvent.type(page().getByLabelText("Search mail"), "invoice");
247
+ await expect(await shown("Invoice #1042")).toBeVisible();
248
+ await expect(
249
+ page().getByRole("button", { name: "Make this a filter" }),
250
+ ).toBeVisible();
251
+ },
252
+ };
253
+
254
+ export const PhoneSuggestingFolders: Story = {
255
+ args: { url: "/mail/brief" },
256
+ globals: widePhone,
257
+ play: async () => {
258
+ await openPhoneSearch();
259
+ await userEvent.type(page().getByLabelText("Search mail"), "in:");
260
+ const suggestions = await page().findByRole("listbox", {
261
+ name: "Search suggestions",
262
+ });
263
+ await expect(
264
+ within(suggestions).getByRole("option", { name: /Archive/ }),
265
+ ).toBeVisible();
266
+ },
267
+ };
268
+
269
+ export const PhoneRecentSearches: Story = {
270
+ ...recentSearches,
271
+ args: { url: `/mail/${inbox}` },
272
+ globals: phone,
273
+ play: async () => {
274
+ await openPhoneSearch();
275
+ await expect(await page().findByText("from:ada")).toBeVisible();
276
+ await userEvent.click(page().getByText("invoice"));
277
+ await expect(page().getByLabelText("Search mail")).toHaveValue("invoice");
278
+ await expect(await shown("Invoice #1042")).toBeVisible();
279
+ },
280
+ };
@@ -0,0 +1,160 @@
1
+ import { matchDoorLabel } from "@remit/ui";
2
+ import type { Meta, StoryObj } from "@storybook/react-vite";
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 { mailboxIdFor, mailWorld, WORK } from "@/mocks/story-frame/mail-world";
7
+ import { longPress } from "@/mocks/story-frame/touch";
8
+
9
+ const inbox = `/mail/${mailboxIdFor(WORK, "Inbox")}`;
10
+ const desktop = { viewport: { value: "desktop", isRotated: false } };
11
+
12
+ const meta = {
13
+ title: "Playground/Shipped/Mail/Selection wizard",
14
+ component: AppStory,
15
+ args: { url: inbox },
16
+ parameters: {
17
+ layout: "fullscreen",
18
+ msw: { handlers: mailHandlers(mailWorld()) },
19
+ },
20
+ globals: desktop,
21
+ } satisfies Meta<typeof AppStory>;
22
+ export default meta;
23
+
24
+ type Story = StoryObj<typeof meta>;
25
+
26
+ const page = () => within(document.body);
27
+
28
+ const rows = (): HTMLElement[] =>
29
+ Array.from(document.querySelectorAll<HTMLElement>("[data-list-row]"));
30
+
31
+ const tick = async (count: number) => {
32
+ await page().findByText("Q3 planning");
33
+ for (const row of rows().slice(0, count)) {
34
+ const toggle = row.querySelector<HTMLElement>(
35
+ '[aria-label="Select message"]',
36
+ );
37
+ if (toggle) await userEvent.click(toggle);
38
+ }
39
+ await page().findByText(`${count} messages selected`);
40
+ };
41
+
42
+ const openWizard = async (count: number, verb: string) => {
43
+ await tick(count);
44
+ await userEvent.click(page().getByRole("button", { name: verb }));
45
+ };
46
+
47
+ const cont = () =>
48
+ userEvent.click(page().getByRole("button", { name: "Continue" }));
49
+
50
+ export const OrganizeTickedRows: Story = {
51
+ play: async () => {
52
+ await openWizard(3, "Organize selected messages");
53
+ await expect(
54
+ await page().findByText("What should this apply to?"),
55
+ ).toBeVisible();
56
+ await expect(page().getByText(matchDoorLabel("selected", 3))).toBeVisible();
57
+ await expect(
58
+ page().getByText(matchDoorLabel("properties", 3)),
59
+ ).toBeVisible();
60
+ },
61
+ };
62
+
63
+ export const OrganizePropertiesDoor: Story = {
64
+ play: async () => {
65
+ await openWizard(3, "Organize selected messages");
66
+ await userEvent.click(
67
+ await page().findByText(matchDoorLabel("properties", 3)),
68
+ );
69
+ await cont();
70
+ await expect(
71
+ await page().findByText("Which properties have to match?"),
72
+ ).toBeVisible();
73
+ },
74
+ };
75
+
76
+ export const DeleteReview: Story = {
77
+ play: async () => {
78
+ await openWizard(2, "Move selected messages to Trash");
79
+ await page().findByText("What should this apply to?");
80
+ await cont();
81
+ await expect(await page().findByText("Check before it runs")).toBeVisible();
82
+ },
83
+ };
84
+
85
+ export const MoveFolder: Story = {
86
+ play: async () => {
87
+ await openWizard(2, "Move selected messages");
88
+ await page().findByText("What should this apply to?");
89
+ await cont();
90
+ await expect(await page().findByText("Pick a destination")).toBeVisible();
91
+ await expect(
92
+ await page().findByRole("treeitem", { name: /Archive/ }),
93
+ ).toBeVisible();
94
+ },
95
+ };
96
+
97
+ export const MakeThisAFilter: Story = {
98
+ args: { url: `${inbox}?q=invoice` },
99
+ play: async () => {
100
+ await userEvent.click(
101
+ await page().findByRole("button", { name: "Make this a filter" }),
102
+ );
103
+ await expect(
104
+ await page().findByText("Which properties have to match?"),
105
+ ).toBeVisible();
106
+ await waitFor(() =>
107
+ expect(page().getAllByText(/invoice/i).length).toBeGreaterThan(0),
108
+ );
109
+ },
110
+ };
111
+
112
+ export const SearchThenSelect: Story = {
113
+ args: { url: `${inbox}?q=the` },
114
+ play: async () => {
115
+ await page().findByText("Linus mentioned you in #design");
116
+ for (const row of rows()) {
117
+ const toggle = row.querySelector<HTMLElement>(
118
+ '[aria-label="Select message"]',
119
+ );
120
+ if (toggle) await userEvent.click(toggle);
121
+ }
122
+ await page().findByText("All 2 loaded selected");
123
+ await userEvent.click(
124
+ page().getByRole("button", { name: "Organize selected messages" }),
125
+ );
126
+ await expect(
127
+ await page().findByText("What should this apply to?"),
128
+ ).toBeVisible();
129
+ await expect(page().getByText(matchDoorLabel("selected", 2))).toBeVisible();
130
+ },
131
+ };
132
+
133
+ export const MakeThisAFilterFromStarred: Story = {
134
+ args: { url: "/mail/flagged?q=invoice" },
135
+ play: async () => {
136
+ await userEvent.click(
137
+ await page().findByRole("button", { name: "Make this a filter" }),
138
+ );
139
+ await expect(
140
+ await page().findByText("Which properties have to match?"),
141
+ ).toBeVisible();
142
+ },
143
+ };
144
+
145
+ export const Phone: Story = {
146
+ args: { url: "/mail/brief" },
147
+ globals: { viewport: { value: "mobileShort", isRotated: false } },
148
+ play: async () => {
149
+ await page().findByText("Q3 planning");
150
+ const first = rows()[0];
151
+ if (first) await longPress(first);
152
+ await page().findByText("1 message selected");
153
+ await userEvent.click(
154
+ page().getByRole("button", { name: "Organize selected messages" }),
155
+ );
156
+ await expect(
157
+ await page().findByText("What should this apply to?"),
158
+ ).toBeVisible();
159
+ },
160
+ };