@purpurds/popover 0.0.1
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/dist/LICENSE.txt +905 -0
- package/dist/metadata.js +8 -0
- package/dist/popover-back.d.ts +9 -0
- package/dist/popover-back.d.ts.map +1 -0
- package/dist/popover-button.d.ts +37 -0
- package/dist/popover-button.d.ts.map +1 -0
- package/dist/popover-content.d.ts +93 -0
- package/dist/popover-content.d.ts.map +1 -0
- package/dist/popover-flow.d.ts +65 -0
- package/dist/popover-flow.d.ts.map +1 -0
- package/dist/popover-footer.d.ts +16 -0
- package/dist/popover-footer.d.ts.map +1 -0
- package/dist/popover-header.d.ts +7 -0
- package/dist/popover-header.d.ts.map +1 -0
- package/dist/popover-internal-context.d.ts +15 -0
- package/dist/popover-internal-context.d.ts.map +1 -0
- package/dist/popover-next.d.ts +9 -0
- package/dist/popover-next.d.ts.map +1 -0
- package/dist/popover-standalone.d.ts +12 -0
- package/dist/popover-standalone.d.ts.map +1 -0
- package/dist/popover-steps.d.ts +6 -0
- package/dist/popover-steps.d.ts.map +1 -0
- package/dist/popover-trigger.d.ts +27 -0
- package/dist/popover-trigger.d.ts.map +1 -0
- package/dist/popover-walkthrough.d.ts +13 -0
- package/dist/popover-walkthrough.d.ts.map +1 -0
- package/dist/popover.cjs.js +42 -0
- package/dist/popover.cjs.js.map +1 -0
- package/dist/popover.d.ts +36 -0
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.es.js +3849 -0
- package/dist/popover.es.js.map +1 -0
- package/dist/styles.css +1 -0
- package/dist/use-screen-size.hook.d.ts +7 -0
- package/dist/use-screen-size.hook.d.ts.map +1 -0
- package/dist/use-smooth-scroll.d.ts +5 -0
- package/dist/use-smooth-scroll.d.ts.map +1 -0
- package/dist/usePopoverTrigger.d.ts +5 -0
- package/dist/usePopoverTrigger.d.ts.map +1 -0
- package/dist/usePopoverWalkthrough.d.ts +7 -0
- package/dist/usePopoverWalkthrough.d.ts.map +1 -0
- package/eslint.config.mjs +2 -0
- package/package.json +82 -0
- package/src/global.d.ts +4 -0
- package/src/popover-back.test.tsx +63 -0
- package/src/popover-back.tsx +40 -0
- package/src/popover-button.test.tsx +51 -0
- package/src/popover-button.tsx +84 -0
- package/src/popover-content.test.tsx +1122 -0
- package/src/popover-content.tsx +277 -0
- package/src/popover-flow.tsx +170 -0
- package/src/popover-footer.test.tsx +21 -0
- package/src/popover-footer.tsx +32 -0
- package/src/popover-header.test.tsx +22 -0
- package/src/popover-header.tsx +32 -0
- package/src/popover-internal-context.tsx +28 -0
- package/src/popover-next.test.tsx +61 -0
- package/src/popover-next.tsx +40 -0
- package/src/popover-standalone.tsx +48 -0
- package/src/popover-steps.tsx +32 -0
- package/src/popover-trigger.tsx +71 -0
- package/src/popover-walkthrough.test.tsx +346 -0
- package/src/popover-walkthrough.tsx +45 -0
- package/src/popover.module.scss +315 -0
- package/src/popover.stories.tsx +1157 -0
- package/src/popover.test.tsx +642 -0
- package/src/popover.tsx +76 -0
- package/src/use-screen-size.hook.ts +39 -0
- package/src/use-smooth-scroll.ts +62 -0
- package/src/usePopoverTrigger.ts +59 -0
- package/src/usePopoverWalkthrough.ts +85 -0
- package/vitest.setup.ts +30 -0
|
@@ -0,0 +1,1122 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Button } from "@purpurds/button";
|
|
3
|
+
import { IconInfo } from "@purpurds/icon/info";
|
|
4
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
5
|
+
import userEvent from "@testing-library/user-event";
|
|
6
|
+
import { describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { axe } from "vitest-axe";
|
|
8
|
+
|
|
9
|
+
import { Popover } from "./popover";
|
|
10
|
+
import { PopoverButton } from "./popover-button";
|
|
11
|
+
import { PopoverContent } from "./popover-content";
|
|
12
|
+
import { PopoverFlow } from "./popover-flow";
|
|
13
|
+
import { PopoverFooter } from "./popover-footer";
|
|
14
|
+
import { PopoverTrigger } from "./popover-trigger";
|
|
15
|
+
|
|
16
|
+
describe("PopoverContent", () => {
|
|
17
|
+
describe("Basic Rendering", () => {
|
|
18
|
+
it("should render with required props", async () => {
|
|
19
|
+
const user = userEvent.setup({ delay: null });
|
|
20
|
+
render(
|
|
21
|
+
<Popover>
|
|
22
|
+
<PopoverTrigger>
|
|
23
|
+
<Button variant="primary">Trigger</Button>
|
|
24
|
+
</PopoverTrigger>
|
|
25
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="This is the body text" />
|
|
26
|
+
</Popover>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
30
|
+
|
|
31
|
+
await waitFor(() => {
|
|
32
|
+
expect(screen.getByText("This is the body text")).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should render with header", async () => {
|
|
37
|
+
const user = userEvent.setup({ delay: null });
|
|
38
|
+
render(
|
|
39
|
+
<Popover>
|
|
40
|
+
<PopoverTrigger>
|
|
41
|
+
<Button variant="primary">Trigger</Button>
|
|
42
|
+
</PopoverTrigger>
|
|
43
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test Title" body="Body text" />
|
|
44
|
+
</Popover>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
48
|
+
|
|
49
|
+
await waitFor(() => {
|
|
50
|
+
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should render with header icon", async () => {
|
|
55
|
+
const user = userEvent.setup({ delay: null });
|
|
56
|
+
render(
|
|
57
|
+
<Popover>
|
|
58
|
+
<PopoverTrigger>
|
|
59
|
+
<Button variant="primary">Trigger</Button>
|
|
60
|
+
</PopoverTrigger>
|
|
61
|
+
<PopoverContent
|
|
62
|
+
closeIconAriaLabel="Close"
|
|
63
|
+
title="Test Title"
|
|
64
|
+
icon={<IconInfo data-testid="header-icon" />}
|
|
65
|
+
body="Body text"
|
|
66
|
+
/>
|
|
67
|
+
</Popover>
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
71
|
+
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
expect(screen.getByTestId("header-icon")).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should render close button with correct aria-label", async () => {
|
|
78
|
+
const user = userEvent.setup({ delay: null });
|
|
79
|
+
render(
|
|
80
|
+
<Popover>
|
|
81
|
+
<PopoverTrigger>
|
|
82
|
+
<Button variant="primary">Trigger</Button>
|
|
83
|
+
</PopoverTrigger>
|
|
84
|
+
<PopoverContent closeIconAriaLabel="Close dialog" title="Test" body="Body text" />
|
|
85
|
+
</Popover>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
89
|
+
|
|
90
|
+
await waitFor(() => {
|
|
91
|
+
expect(screen.getByRole("button", { name: "Close dialog" })).toBeInTheDocument();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should render with custom footer", async () => {
|
|
96
|
+
const user = userEvent.setup({ delay: null });
|
|
97
|
+
render(
|
|
98
|
+
<Popover>
|
|
99
|
+
<PopoverTrigger>
|
|
100
|
+
<Button variant="primary">Trigger</Button>
|
|
101
|
+
</PopoverTrigger>
|
|
102
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text">
|
|
103
|
+
<PopoverFooter>
|
|
104
|
+
<PopoverButton>Custom Action</PopoverButton>
|
|
105
|
+
</PopoverFooter>
|
|
106
|
+
</PopoverContent>
|
|
107
|
+
</Popover>
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
111
|
+
|
|
112
|
+
await waitFor(() => {
|
|
113
|
+
expect(screen.getByRole("button", { name: "Custom Action" })).toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("Visual Variants", () => {
|
|
119
|
+
it("should apply negative variant", async () => {
|
|
120
|
+
const user = userEvent.setup({ delay: null });
|
|
121
|
+
render(
|
|
122
|
+
<Popover>
|
|
123
|
+
<PopoverTrigger>
|
|
124
|
+
<Button variant="primary">Trigger</Button>
|
|
125
|
+
</PopoverTrigger>
|
|
126
|
+
<PopoverContent
|
|
127
|
+
closeIconAriaLabel="Close"
|
|
128
|
+
title="Test Title"
|
|
129
|
+
body="Body text"
|
|
130
|
+
negative
|
|
131
|
+
data-testid="popover-content"
|
|
132
|
+
/>
|
|
133
|
+
</Popover>
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
137
|
+
|
|
138
|
+
await waitFor(() => {
|
|
139
|
+
const content = screen.getByRole("dialog");
|
|
140
|
+
expect(content).toHaveClass("purpur-popover__content--negative");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should apply custom className", async () => {
|
|
145
|
+
const user = userEvent.setup({ delay: null });
|
|
146
|
+
render(
|
|
147
|
+
<Popover>
|
|
148
|
+
<PopoverTrigger>
|
|
149
|
+
<Button variant="primary">Trigger</Button>
|
|
150
|
+
</PopoverTrigger>
|
|
151
|
+
<PopoverContent
|
|
152
|
+
closeIconAriaLabel="Close"
|
|
153
|
+
title="Test Title"
|
|
154
|
+
body="Body text"
|
|
155
|
+
className="custom-class"
|
|
156
|
+
/>
|
|
157
|
+
</Popover>
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
161
|
+
|
|
162
|
+
await waitFor(() => {
|
|
163
|
+
const content = screen.getByTestId("popover-content");
|
|
164
|
+
expect(content).toHaveClass("custom-class");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should render arrow by default", async () => {
|
|
169
|
+
const user = userEvent.setup({ delay: null });
|
|
170
|
+
render(
|
|
171
|
+
<Popover>
|
|
172
|
+
<PopoverTrigger>
|
|
173
|
+
<Button variant="primary">Trigger</Button>
|
|
174
|
+
</PopoverTrigger>
|
|
175
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text" />
|
|
176
|
+
</Popover>
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
180
|
+
|
|
181
|
+
await waitFor(() => {
|
|
182
|
+
const arrow = document.querySelector(".purpur-popover__arrow");
|
|
183
|
+
expect(arrow).toBeInTheDocument();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should hide arrow when beakPosition is None", async () => {
|
|
188
|
+
const user = userEvent.setup({ delay: null });
|
|
189
|
+
render(
|
|
190
|
+
<Popover>
|
|
191
|
+
<PopoverTrigger>
|
|
192
|
+
<Button variant="primary">Trigger</Button>
|
|
193
|
+
</PopoverTrigger>
|
|
194
|
+
<PopoverContent
|
|
195
|
+
closeIconAriaLabel="Close"
|
|
196
|
+
title="Test"
|
|
197
|
+
body="Body text"
|
|
198
|
+
beakPosition="none"
|
|
199
|
+
/>
|
|
200
|
+
</Popover>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
204
|
+
|
|
205
|
+
await waitFor(() => {
|
|
206
|
+
const arrow = document.querySelector(".purpur-popover__arrow");
|
|
207
|
+
expect(arrow).not.toBeInTheDocument();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("Positioning", () => {
|
|
213
|
+
it("should map beakPosition 'up' to Radix side 'bottom'", async () => {
|
|
214
|
+
const user = userEvent.setup({ delay: null });
|
|
215
|
+
render(
|
|
216
|
+
<Popover>
|
|
217
|
+
<PopoverTrigger>
|
|
218
|
+
<Button variant="primary">Trigger</Button>
|
|
219
|
+
</PopoverTrigger>
|
|
220
|
+
<PopoverContent
|
|
221
|
+
closeIconAriaLabel="Close"
|
|
222
|
+
title="Test"
|
|
223
|
+
body="Body text"
|
|
224
|
+
beakPosition="up"
|
|
225
|
+
/>
|
|
226
|
+
</Popover>
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
230
|
+
|
|
231
|
+
await waitFor(() => {
|
|
232
|
+
const content = screen.getByTestId("popover-content");
|
|
233
|
+
expect(content).toHaveAttribute("data-side", "bottom");
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should map beakPosition 'down' to Radix side 'top'", async () => {
|
|
238
|
+
const user = userEvent.setup({ delay: null });
|
|
239
|
+
render(
|
|
240
|
+
<Popover>
|
|
241
|
+
<PopoverTrigger>
|
|
242
|
+
<Button variant="primary">Trigger</Button>
|
|
243
|
+
</PopoverTrigger>
|
|
244
|
+
<PopoverContent
|
|
245
|
+
closeIconAriaLabel="Close"
|
|
246
|
+
title="Test"
|
|
247
|
+
body="Body text"
|
|
248
|
+
beakPosition="down"
|
|
249
|
+
/>
|
|
250
|
+
</Popover>
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
254
|
+
|
|
255
|
+
await waitFor(() => {
|
|
256
|
+
const content = screen.getByTestId("popover-content");
|
|
257
|
+
expect(content).toHaveAttribute("data-side", "top");
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("should map beakPosition 'left' to Radix side 'right'", async () => {
|
|
262
|
+
const user = userEvent.setup({ delay: null });
|
|
263
|
+
render(
|
|
264
|
+
<Popover>
|
|
265
|
+
<PopoverTrigger>
|
|
266
|
+
<Button variant="primary">Trigger</Button>
|
|
267
|
+
</PopoverTrigger>
|
|
268
|
+
<PopoverContent
|
|
269
|
+
closeIconAriaLabel="Close"
|
|
270
|
+
title="Test"
|
|
271
|
+
body="Body text"
|
|
272
|
+
beakPosition="left"
|
|
273
|
+
/>
|
|
274
|
+
</Popover>
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
278
|
+
|
|
279
|
+
await waitFor(() => {
|
|
280
|
+
const content = screen.getByTestId("popover-content");
|
|
281
|
+
expect(content).toHaveAttribute("data-side", "right");
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("should map beakPosition 'right' to Radix side 'left'", async () => {
|
|
286
|
+
const user = userEvent.setup({ delay: null });
|
|
287
|
+
render(
|
|
288
|
+
<Popover>
|
|
289
|
+
<PopoverTrigger>
|
|
290
|
+
<Button variant="primary">Trigger</Button>
|
|
291
|
+
</PopoverTrigger>
|
|
292
|
+
<PopoverContent
|
|
293
|
+
closeIconAriaLabel="Close"
|
|
294
|
+
title="Test"
|
|
295
|
+
body="Body text"
|
|
296
|
+
beakPosition="right"
|
|
297
|
+
/>
|
|
298
|
+
</Popover>
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
302
|
+
|
|
303
|
+
await waitFor(() => {
|
|
304
|
+
const content = screen.getByTestId("popover-content");
|
|
305
|
+
expect(content).toHaveAttribute("data-side", "left");
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("should map beakPosition 'none' to Radix side 'bottom' (default)", async () => {
|
|
310
|
+
const user = userEvent.setup({ delay: null });
|
|
311
|
+
render(
|
|
312
|
+
<Popover>
|
|
313
|
+
<PopoverTrigger>
|
|
314
|
+
<Button variant="primary">Trigger</Button>
|
|
315
|
+
</PopoverTrigger>
|
|
316
|
+
<PopoverContent
|
|
317
|
+
closeIconAriaLabel="Close"
|
|
318
|
+
title="Test"
|
|
319
|
+
body="Body text"
|
|
320
|
+
beakPosition="none"
|
|
321
|
+
/>
|
|
322
|
+
</Popover>
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
326
|
+
|
|
327
|
+
await waitFor(() => {
|
|
328
|
+
const content = screen.getByTestId("popover-content");
|
|
329
|
+
expect(content).toHaveAttribute("data-side", "bottom");
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("should default to 'down' (Radix 'top') when beakPosition is not provided", async () => {
|
|
334
|
+
const user = userEvent.setup({ delay: null });
|
|
335
|
+
render(
|
|
336
|
+
<Popover>
|
|
337
|
+
<PopoverTrigger>
|
|
338
|
+
<Button variant="primary">Trigger</Button>
|
|
339
|
+
</PopoverTrigger>
|
|
340
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text" />
|
|
341
|
+
</Popover>
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
345
|
+
|
|
346
|
+
await waitFor(() => {
|
|
347
|
+
const content = screen.getByTestId("popover-content");
|
|
348
|
+
expect(content).toHaveAttribute("data-side", "top");
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("should apply custom align", async () => {
|
|
353
|
+
const user = userEvent.setup({ delay: null });
|
|
354
|
+
render(
|
|
355
|
+
<Popover>
|
|
356
|
+
<PopoverTrigger>
|
|
357
|
+
<Button variant="primary">Trigger</Button>
|
|
358
|
+
</PopoverTrigger>
|
|
359
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text" align="start" />
|
|
360
|
+
</Popover>
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
364
|
+
|
|
365
|
+
await waitFor(() => {
|
|
366
|
+
const content = screen.getByTestId("popover-content");
|
|
367
|
+
expect(content).toHaveAttribute("data-align", "start");
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("should apply custom zIndex", async () => {
|
|
372
|
+
const user = userEvent.setup({ delay: null });
|
|
373
|
+
render(
|
|
374
|
+
<Popover>
|
|
375
|
+
<PopoverTrigger>
|
|
376
|
+
<Button variant="primary">Trigger</Button>
|
|
377
|
+
</PopoverTrigger>
|
|
378
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text" zIndex={999} />
|
|
379
|
+
</Popover>
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
383
|
+
|
|
384
|
+
await waitFor(() => {
|
|
385
|
+
const content = screen.getByTestId("popover-content");
|
|
386
|
+
expect(content).toHaveStyle({ "--popover-z-index": "999" });
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe("Interactions", () => {
|
|
392
|
+
it("should close when close button is clicked", async () => {
|
|
393
|
+
const user = userEvent.setup({ delay: null });
|
|
394
|
+
render(
|
|
395
|
+
<Popover>
|
|
396
|
+
<PopoverTrigger>
|
|
397
|
+
<Button variant="primary">Trigger</Button>
|
|
398
|
+
</PopoverTrigger>
|
|
399
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text" />
|
|
400
|
+
</Popover>
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
404
|
+
|
|
405
|
+
await waitFor(() => {
|
|
406
|
+
expect(screen.getByText("Body text")).toBeInTheDocument();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
await user.click(screen.getByRole("button", { name: "Close" }));
|
|
410
|
+
|
|
411
|
+
await waitFor(() => {
|
|
412
|
+
expect(screen.queryByText("Body text")).not.toBeInTheDocument();
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("should call onAction when close button is clicked", async () => {
|
|
417
|
+
const onAction = vi.fn();
|
|
418
|
+
const user = userEvent.setup({ delay: null });
|
|
419
|
+
render(
|
|
420
|
+
<Popover>
|
|
421
|
+
<PopoverTrigger>
|
|
422
|
+
<Button variant="primary">Trigger</Button>
|
|
423
|
+
</PopoverTrigger>
|
|
424
|
+
<PopoverContent
|
|
425
|
+
closeIconAriaLabel="Close"
|
|
426
|
+
title="Test"
|
|
427
|
+
body="Body text"
|
|
428
|
+
onAction={onAction}
|
|
429
|
+
/>
|
|
430
|
+
</Popover>
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
434
|
+
|
|
435
|
+
await waitFor(() => {
|
|
436
|
+
expect(screen.getByText("Body text")).toBeInTheDocument();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
await user.click(screen.getByRole("button", { name: "Close" }));
|
|
440
|
+
|
|
441
|
+
await waitFor(() => {
|
|
442
|
+
expect(onAction).toHaveBeenCalledWith({ type: "dismiss", step: undefined });
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it("should prevent default focus behavior", async () => {
|
|
447
|
+
const preventDefault = vi.fn();
|
|
448
|
+
const user = userEvent.setup({ delay: null });
|
|
449
|
+
render(
|
|
450
|
+
<Popover>
|
|
451
|
+
<PopoverTrigger>
|
|
452
|
+
<Button variant="primary">Trigger</Button>
|
|
453
|
+
</PopoverTrigger>
|
|
454
|
+
<PopoverContent
|
|
455
|
+
closeIconAriaLabel="Close"
|
|
456
|
+
title="Test"
|
|
457
|
+
body="Body text"
|
|
458
|
+
onOpenAutoFocus={(e) => {
|
|
459
|
+
preventDefault();
|
|
460
|
+
e.preventDefault();
|
|
461
|
+
}}
|
|
462
|
+
/>
|
|
463
|
+
</Popover>
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
467
|
+
|
|
468
|
+
await waitFor(() => {
|
|
469
|
+
expect(preventDefault).toHaveBeenCalled();
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("should focus content on open", async () => {
|
|
474
|
+
const user = userEvent.setup({ delay: null });
|
|
475
|
+
render(
|
|
476
|
+
<Popover>
|
|
477
|
+
<PopoverTrigger>
|
|
478
|
+
<Button variant="primary">Trigger</Button>
|
|
479
|
+
</PopoverTrigger>
|
|
480
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text" />
|
|
481
|
+
</Popover>
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
485
|
+
|
|
486
|
+
await waitFor(() => {
|
|
487
|
+
const content = screen.getByTestId("popover-content");
|
|
488
|
+
expect(content).toHaveAttribute("tabindex", "-1");
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
describe("Accessibility", () => {
|
|
494
|
+
it("should have role dialog", async () => {
|
|
495
|
+
const user = userEvent.setup({ delay: null });
|
|
496
|
+
render(
|
|
497
|
+
<Popover>
|
|
498
|
+
<PopoverTrigger>
|
|
499
|
+
<Button variant="primary">Trigger</Button>
|
|
500
|
+
</PopoverTrigger>
|
|
501
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text" />
|
|
502
|
+
</Popover>
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
506
|
+
|
|
507
|
+
await waitFor(() => {
|
|
508
|
+
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("should have aria-modal attribute", async () => {
|
|
513
|
+
const user = userEvent.setup({ delay: null });
|
|
514
|
+
render(
|
|
515
|
+
<Popover>
|
|
516
|
+
<PopoverTrigger>
|
|
517
|
+
<Button variant="primary">Trigger</Button>
|
|
518
|
+
</PopoverTrigger>
|
|
519
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text" />
|
|
520
|
+
</Popover>
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
524
|
+
|
|
525
|
+
await waitFor(() => {
|
|
526
|
+
const content = screen.getByRole("dialog");
|
|
527
|
+
expect(content).toHaveAttribute("aria-modal", "true");
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("should use header title as aria-label", async () => {
|
|
532
|
+
const user = userEvent.setup({ delay: null });
|
|
533
|
+
render(
|
|
534
|
+
<Popover>
|
|
535
|
+
<PopoverTrigger>
|
|
536
|
+
<Button variant="primary">Trigger</Button>
|
|
537
|
+
</PopoverTrigger>
|
|
538
|
+
<PopoverContent closeIconAriaLabel="Close" title="Important Info" body="Body text" />
|
|
539
|
+
</Popover>
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
543
|
+
|
|
544
|
+
await waitFor(() => {
|
|
545
|
+
const content = screen.getByRole("dialog");
|
|
546
|
+
expect(content).toHaveAttribute("aria-labelledby", "popover-heading");
|
|
547
|
+
const heading = document.getElementById("popover-heading");
|
|
548
|
+
expect(heading).toHaveTextContent("Important Info");
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("should use custom aria-label when provided", async () => {
|
|
553
|
+
const user = userEvent.setup({ delay: null });
|
|
554
|
+
render(
|
|
555
|
+
<Popover>
|
|
556
|
+
<PopoverTrigger>
|
|
557
|
+
<Button variant="primary">Trigger</Button>
|
|
558
|
+
</PopoverTrigger>
|
|
559
|
+
<PopoverContent
|
|
560
|
+
closeIconAriaLabel="Close"
|
|
561
|
+
title="Title"
|
|
562
|
+
body="Body text"
|
|
563
|
+
aria-label="Custom label"
|
|
564
|
+
/>
|
|
565
|
+
</Popover>
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
569
|
+
|
|
570
|
+
await waitFor(() => {
|
|
571
|
+
const content = screen.getByRole("dialog");
|
|
572
|
+
expect(content).toHaveAttribute("aria-label", "Custom label");
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("should have aria-hidden on arrow", async () => {
|
|
577
|
+
const user = userEvent.setup({ delay: null });
|
|
578
|
+
render(
|
|
579
|
+
<Popover>
|
|
580
|
+
<PopoverTrigger>
|
|
581
|
+
<Button variant="primary">Trigger</Button>
|
|
582
|
+
</PopoverTrigger>
|
|
583
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text" />
|
|
584
|
+
</Popover>
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
588
|
+
|
|
589
|
+
await waitFor(() => {
|
|
590
|
+
const arrow = document.querySelector(".purpur-popover__arrow");
|
|
591
|
+
expect(arrow).toHaveAttribute("aria-hidden", "true");
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it("should pass accessibility checks for content with header and footer", async () => {
|
|
596
|
+
const user = userEvent.setup({ delay: null });
|
|
597
|
+
render(
|
|
598
|
+
<Popover>
|
|
599
|
+
<PopoverTrigger>
|
|
600
|
+
<Button variant="primary">Trigger</Button>
|
|
601
|
+
</PopoverTrigger>
|
|
602
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test Title" body="Body text">
|
|
603
|
+
<PopoverFooter>
|
|
604
|
+
<PopoverButton>Got it</PopoverButton>
|
|
605
|
+
</PopoverFooter>
|
|
606
|
+
</PopoverContent>
|
|
607
|
+
</Popover>
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
611
|
+
|
|
612
|
+
await waitFor(() => {
|
|
613
|
+
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// Test only the content dialog, not the trigger which has known ARIA issues
|
|
617
|
+
const dialog = screen.getByRole("dialog");
|
|
618
|
+
const results = await axe(dialog);
|
|
619
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
620
|
+
// @ts-ignore
|
|
621
|
+
expect(results).toHaveNoViolations();
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it("should pass accessibility checks for content with icon", async () => {
|
|
625
|
+
const user = userEvent.setup({ delay: null });
|
|
626
|
+
render(
|
|
627
|
+
<Popover>
|
|
628
|
+
<PopoverTrigger>
|
|
629
|
+
<Button variant="primary">Trigger</Button>
|
|
630
|
+
</PopoverTrigger>
|
|
631
|
+
<PopoverContent
|
|
632
|
+
closeIconAriaLabel="Close popover"
|
|
633
|
+
title="Information"
|
|
634
|
+
icon={<IconInfo aria-hidden="true" />}
|
|
635
|
+
body="This is important information"
|
|
636
|
+
/>
|
|
637
|
+
</Popover>
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
641
|
+
|
|
642
|
+
await waitFor(() => {
|
|
643
|
+
expect(screen.getByText("Information")).toBeInTheDocument();
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
const dialog = screen.getByRole("dialog");
|
|
647
|
+
const results = await axe(dialog);
|
|
648
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
649
|
+
// @ts-ignore
|
|
650
|
+
expect(results).toHaveNoViolations();
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it("should pass accessibility checks for negative variant", async () => {
|
|
654
|
+
const user = userEvent.setup({ delay: null });
|
|
655
|
+
render(
|
|
656
|
+
<Popover>
|
|
657
|
+
<PopoverTrigger>
|
|
658
|
+
<Button variant="primary">Trigger</Button>
|
|
659
|
+
</PopoverTrigger>
|
|
660
|
+
<PopoverContent
|
|
661
|
+
closeIconAriaLabel="Close"
|
|
662
|
+
title="Warning"
|
|
663
|
+
body="This is a warning message"
|
|
664
|
+
negative
|
|
665
|
+
/>
|
|
666
|
+
</Popover>
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
670
|
+
|
|
671
|
+
await waitFor(() => {
|
|
672
|
+
expect(screen.getByText("Warning")).toBeInTheDocument();
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
const dialog = screen.getByRole("dialog");
|
|
676
|
+
const results = await axe(dialog);
|
|
677
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
678
|
+
// @ts-ignore
|
|
679
|
+
expect(results).toHaveNoViolations();
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it("should have proper keyboard navigation", async () => {
|
|
683
|
+
const user = userEvent.setup({ delay: null });
|
|
684
|
+
render(
|
|
685
|
+
<Popover>
|
|
686
|
+
<PopoverTrigger>
|
|
687
|
+
<Button variant="primary">Trigger</Button>
|
|
688
|
+
</PopoverTrigger>
|
|
689
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text">
|
|
690
|
+
<PopoverFooter>
|
|
691
|
+
<PopoverButton>Action</PopoverButton>
|
|
692
|
+
</PopoverFooter>
|
|
693
|
+
</PopoverContent>
|
|
694
|
+
</Popover>
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
698
|
+
|
|
699
|
+
await waitFor(() => {
|
|
700
|
+
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// Check that close button is focusable
|
|
704
|
+
const closeButton = screen.getByRole("button", { name: "Close" });
|
|
705
|
+
expect(closeButton).toHaveAttribute("type", "button");
|
|
706
|
+
expect(closeButton).not.toHaveAttribute("disabled");
|
|
707
|
+
|
|
708
|
+
// Check that action button is focusable
|
|
709
|
+
const actionButton = screen.getByRole("button", { name: "Action" });
|
|
710
|
+
expect(actionButton).toHaveAttribute("type", "button");
|
|
711
|
+
expect(actionButton).not.toHaveAttribute("disabled");
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it("should have descriptive labels for screen readers", async () => {
|
|
715
|
+
const user = userEvent.setup({ delay: null });
|
|
716
|
+
render(
|
|
717
|
+
<Popover>
|
|
718
|
+
<PopoverTrigger>
|
|
719
|
+
<Button variant="primary">Trigger</Button>
|
|
720
|
+
</PopoverTrigger>
|
|
721
|
+
<PopoverContent
|
|
722
|
+
closeIconAriaLabel="Close information dialog"
|
|
723
|
+
title="Important Information"
|
|
724
|
+
body="This is the content"
|
|
725
|
+
aria-label="Important Information"
|
|
726
|
+
/>
|
|
727
|
+
</Popover>
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
731
|
+
|
|
732
|
+
await waitFor(() => {
|
|
733
|
+
const dialog = screen.getByRole("dialog");
|
|
734
|
+
expect(dialog).toHaveAttribute("aria-label", "Important Information");
|
|
735
|
+
expect(dialog).toHaveAttribute("aria-modal", "true");
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
const closeButton = screen.getByRole("button", { name: "Close information dialog" });
|
|
739
|
+
expect(closeButton).toBeInTheDocument();
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it("should pass accessibility checks for walkthrough mode", async () => {
|
|
743
|
+
render(
|
|
744
|
+
<PopoverFlow
|
|
745
|
+
separatorText="of"
|
|
746
|
+
stepText="Step"
|
|
747
|
+
backLabel="Back"
|
|
748
|
+
nextLabel="Next"
|
|
749
|
+
finishLabel="Finish"
|
|
750
|
+
>
|
|
751
|
+
<Popover multistep step={1}>
|
|
752
|
+
<PopoverTrigger>
|
|
753
|
+
<Button variant="primary">Step 1</Button>
|
|
754
|
+
</PopoverTrigger>
|
|
755
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 1" body="First step content" />
|
|
756
|
+
</Popover>
|
|
757
|
+
<Popover multistep step={2}>
|
|
758
|
+
<PopoverTrigger>
|
|
759
|
+
<Button variant="primary">Step 2</Button>
|
|
760
|
+
</PopoverTrigger>
|
|
761
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 2" body="Second step content" />
|
|
762
|
+
</Popover>
|
|
763
|
+
</PopoverFlow>
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
await waitFor(
|
|
767
|
+
() => {
|
|
768
|
+
expect(screen.getByText("First step content")).toBeInTheDocument();
|
|
769
|
+
},
|
|
770
|
+
{ timeout: 5000 }
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
const dialog = screen.getByRole("dialog");
|
|
774
|
+
const results = await axe(dialog);
|
|
775
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
776
|
+
// @ts-ignore
|
|
777
|
+
expect(results).toHaveNoViolations();
|
|
778
|
+
|
|
779
|
+
// Check that step indicator is announced to screen readers
|
|
780
|
+
const srOnlyStepText = document.querySelector('[role="status"][aria-live="polite"]');
|
|
781
|
+
expect(srOnlyStepText).toBeInTheDocument();
|
|
782
|
+
expect(srOnlyStepText).toHaveTextContent("Step 1 of 2");
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it("should maintain focus trap within dialog", async () => {
|
|
786
|
+
const user = userEvent.setup({ delay: null });
|
|
787
|
+
render(
|
|
788
|
+
<Popover>
|
|
789
|
+
<PopoverTrigger>
|
|
790
|
+
<Button variant="primary">Trigger</Button>
|
|
791
|
+
</PopoverTrigger>
|
|
792
|
+
<PopoverContent closeIconAriaLabel="Close" title="Test" body="Body text">
|
|
793
|
+
<PopoverFooter>
|
|
794
|
+
<PopoverButton>Action 1</PopoverButton>
|
|
795
|
+
</PopoverFooter>
|
|
796
|
+
</PopoverContent>
|
|
797
|
+
</Popover>
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
801
|
+
|
|
802
|
+
await waitFor(() => {
|
|
803
|
+
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// Verify focus guards exist (provided by Radix UI)
|
|
807
|
+
const focusGuards = document.querySelectorAll("[data-radix-focus-guard]");
|
|
808
|
+
expect(focusGuards.length).toBeGreaterThan(0);
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
describe("Walkthrough Mode", () => {
|
|
813
|
+
it("should auto-generate footer for first step", async () => {
|
|
814
|
+
const onAction = vi.fn();
|
|
815
|
+
render(
|
|
816
|
+
<PopoverFlow
|
|
817
|
+
separatorText="of"
|
|
818
|
+
stepText="Step"
|
|
819
|
+
backLabel="Back"
|
|
820
|
+
nextLabel="Next"
|
|
821
|
+
finishLabel="Finish"
|
|
822
|
+
>
|
|
823
|
+
<Popover multistep step={1}>
|
|
824
|
+
<PopoverTrigger>
|
|
825
|
+
<Button variant="primary">Step 1</Button>
|
|
826
|
+
</PopoverTrigger>
|
|
827
|
+
<PopoverContent
|
|
828
|
+
closeIconAriaLabel="Close"
|
|
829
|
+
title="Step 1"
|
|
830
|
+
body="First step"
|
|
831
|
+
onAction={onAction}
|
|
832
|
+
/>
|
|
833
|
+
</Popover>
|
|
834
|
+
<Popover multistep step={2}>
|
|
835
|
+
<PopoverTrigger>
|
|
836
|
+
<Button variant="primary">Step 2</Button>
|
|
837
|
+
</PopoverTrigger>
|
|
838
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 2" body="Second step" />
|
|
839
|
+
</Popover>
|
|
840
|
+
</PopoverFlow>
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
await waitFor(
|
|
844
|
+
() => {
|
|
845
|
+
expect(screen.getByText("First step")).toBeInTheDocument();
|
|
846
|
+
},
|
|
847
|
+
{ timeout: 5000 }
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
expect(screen.getByRole("button", { name: "Next" })).toBeInTheDocument();
|
|
851
|
+
expect(screen.queryByRole("button", { name: "Back" })).not.toBeInTheDocument();
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it("should auto-generate footer for middle step", async () => {
|
|
855
|
+
render(
|
|
856
|
+
<PopoverFlow
|
|
857
|
+
separatorText="of"
|
|
858
|
+
stepText="Step"
|
|
859
|
+
backLabel="Back"
|
|
860
|
+
nextLabel="Next"
|
|
861
|
+
finishLabel="Finish"
|
|
862
|
+
initialStep={2}
|
|
863
|
+
>
|
|
864
|
+
<Popover multistep step={1}>
|
|
865
|
+
<PopoverTrigger>
|
|
866
|
+
<Button variant="primary">Step 1</Button>
|
|
867
|
+
</PopoverTrigger>
|
|
868
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 1" body="First step" />
|
|
869
|
+
</Popover>
|
|
870
|
+
<Popover multistep step={2}>
|
|
871
|
+
<PopoverTrigger>
|
|
872
|
+
<Button variant="primary">Step 2</Button>
|
|
873
|
+
</PopoverTrigger>
|
|
874
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 2" body="Second step" />
|
|
875
|
+
</Popover>
|
|
876
|
+
<Popover multistep step={3}>
|
|
877
|
+
<PopoverTrigger>
|
|
878
|
+
<Button variant="primary">Step 3</Button>
|
|
879
|
+
</PopoverTrigger>
|
|
880
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 3" body="Third step" />
|
|
881
|
+
</Popover>
|
|
882
|
+
</PopoverFlow>
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
await waitFor(
|
|
886
|
+
() => {
|
|
887
|
+
expect(screen.getByText("Second step")).toBeInTheDocument();
|
|
888
|
+
},
|
|
889
|
+
{ timeout: 5000 }
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
expect(screen.getByRole("button", { name: "Next" })).toBeInTheDocument();
|
|
893
|
+
expect(screen.getByRole("button", { name: "Back" })).toBeInTheDocument();
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it("should auto-generate footer for last step", async () => {
|
|
897
|
+
render(
|
|
898
|
+
<PopoverFlow
|
|
899
|
+
separatorText="of"
|
|
900
|
+
stepText="Step"
|
|
901
|
+
backLabel="Back"
|
|
902
|
+
nextLabel="Next"
|
|
903
|
+
finishLabel="Finish"
|
|
904
|
+
initialStep={2}
|
|
905
|
+
>
|
|
906
|
+
<Popover multistep step={1}>
|
|
907
|
+
<PopoverTrigger>
|
|
908
|
+
<Button variant="primary">Step 1</Button>
|
|
909
|
+
</PopoverTrigger>
|
|
910
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 1" body="First step" />
|
|
911
|
+
</Popover>
|
|
912
|
+
<Popover multistep step={2}>
|
|
913
|
+
<PopoverTrigger>
|
|
914
|
+
<Button variant="primary">Step 2</Button>
|
|
915
|
+
</PopoverTrigger>
|
|
916
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 2" body="Second step" />
|
|
917
|
+
</Popover>
|
|
918
|
+
</PopoverFlow>
|
|
919
|
+
);
|
|
920
|
+
|
|
921
|
+
await waitFor(
|
|
922
|
+
() => {
|
|
923
|
+
expect(screen.getByText("Second step")).toBeInTheDocument();
|
|
924
|
+
},
|
|
925
|
+
{ timeout: 5000 }
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
expect(screen.getByRole("button", { name: "Finish" })).toBeInTheDocument();
|
|
929
|
+
expect(screen.getByRole("button", { name: "Back" })).toBeInTheDocument();
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
it("should call onAction with correct step on back click", async () => {
|
|
933
|
+
const onAction = vi.fn();
|
|
934
|
+
const user = userEvent.setup({ delay: null });
|
|
935
|
+
render(
|
|
936
|
+
<PopoverFlow
|
|
937
|
+
separatorText="of"
|
|
938
|
+
stepText="Step"
|
|
939
|
+
backLabel="Back"
|
|
940
|
+
nextLabel="Next"
|
|
941
|
+
finishLabel="Finish"
|
|
942
|
+
initialStep={2}
|
|
943
|
+
>
|
|
944
|
+
<Popover multistep step={1}>
|
|
945
|
+
<PopoverTrigger>
|
|
946
|
+
<Button variant="primary">Step 1</Button>
|
|
947
|
+
</PopoverTrigger>
|
|
948
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 1" body="First step" />
|
|
949
|
+
</Popover>
|
|
950
|
+
<Popover multistep step={2}>
|
|
951
|
+
<PopoverTrigger>
|
|
952
|
+
<Button variant="primary">Step 2</Button>
|
|
953
|
+
</PopoverTrigger>
|
|
954
|
+
<PopoverContent
|
|
955
|
+
closeIconAriaLabel="Close"
|
|
956
|
+
title="Step 2"
|
|
957
|
+
body="Second step"
|
|
958
|
+
onAction={onAction}
|
|
959
|
+
/>
|
|
960
|
+
</Popover>
|
|
961
|
+
</PopoverFlow>
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
await waitFor(
|
|
965
|
+
() => {
|
|
966
|
+
expect(screen.getByRole("button", { name: "Back" })).toBeInTheDocument();
|
|
967
|
+
},
|
|
968
|
+
{ timeout: 5000 }
|
|
969
|
+
);
|
|
970
|
+
|
|
971
|
+
await user.click(screen.getByRole("button", { name: "Back" }));
|
|
972
|
+
|
|
973
|
+
expect(onAction).toHaveBeenCalledWith({ type: "back", step: 2 });
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
it("should call onAction with correct step on next click", async () => {
|
|
977
|
+
const onAction = vi.fn();
|
|
978
|
+
const user = userEvent.setup({ delay: null });
|
|
979
|
+
render(
|
|
980
|
+
<PopoverFlow
|
|
981
|
+
separatorText="of"
|
|
982
|
+
stepText="Step"
|
|
983
|
+
backLabel="Back"
|
|
984
|
+
nextLabel="Next"
|
|
985
|
+
finishLabel="Finish"
|
|
986
|
+
>
|
|
987
|
+
<Popover multistep step={1}>
|
|
988
|
+
<PopoverTrigger>
|
|
989
|
+
<Button variant="primary">Step 1</Button>
|
|
990
|
+
</PopoverTrigger>
|
|
991
|
+
<PopoverContent
|
|
992
|
+
closeIconAriaLabel="Close"
|
|
993
|
+
title="Step 1"
|
|
994
|
+
body="First step"
|
|
995
|
+
onAction={onAction}
|
|
996
|
+
/>
|
|
997
|
+
</Popover>
|
|
998
|
+
<Popover multistep step={2}>
|
|
999
|
+
<PopoverTrigger>
|
|
1000
|
+
<Button variant="primary">Step 2</Button>
|
|
1001
|
+
</PopoverTrigger>
|
|
1002
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 2" body="Second step" />
|
|
1003
|
+
</Popover>
|
|
1004
|
+
</PopoverFlow>
|
|
1005
|
+
);
|
|
1006
|
+
|
|
1007
|
+
await waitFor(
|
|
1008
|
+
() => {
|
|
1009
|
+
expect(screen.getByRole("button", { name: "Next" })).toBeInTheDocument();
|
|
1010
|
+
},
|
|
1011
|
+
{ timeout: 5000 }
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
1015
|
+
|
|
1016
|
+
expect(onAction).toHaveBeenCalledWith({ type: "next", step: 1 });
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
it("should call onAction with correct step on finish click", async () => {
|
|
1020
|
+
const onAction = vi.fn();
|
|
1021
|
+
const user = userEvent.setup({ delay: null });
|
|
1022
|
+
render(
|
|
1023
|
+
<PopoverFlow
|
|
1024
|
+
separatorText="of"
|
|
1025
|
+
stepText="Step"
|
|
1026
|
+
backLabel="Back"
|
|
1027
|
+
nextLabel="Next"
|
|
1028
|
+
finishLabel="Finish"
|
|
1029
|
+
initialStep={2}
|
|
1030
|
+
>
|
|
1031
|
+
<Popover multistep step={1}>
|
|
1032
|
+
<PopoverTrigger>
|
|
1033
|
+
<Button variant="primary">Step 1</Button>
|
|
1034
|
+
</PopoverTrigger>
|
|
1035
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 1" body="First step" />
|
|
1036
|
+
</Popover>
|
|
1037
|
+
<Popover multistep step={2}>
|
|
1038
|
+
<PopoverTrigger>
|
|
1039
|
+
<Button variant="primary">Step 2</Button>
|
|
1040
|
+
</PopoverTrigger>
|
|
1041
|
+
<PopoverContent
|
|
1042
|
+
closeIconAriaLabel="Close"
|
|
1043
|
+
title="Step 2"
|
|
1044
|
+
body="Second step"
|
|
1045
|
+
onAction={onAction}
|
|
1046
|
+
/>
|
|
1047
|
+
</Popover>
|
|
1048
|
+
</PopoverFlow>
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
await waitFor(
|
|
1052
|
+
() => {
|
|
1053
|
+
expect(screen.getByRole("button", { name: "Finish" })).toBeInTheDocument();
|
|
1054
|
+
},
|
|
1055
|
+
{ timeout: 5000 }
|
|
1056
|
+
);
|
|
1057
|
+
|
|
1058
|
+
await user.click(screen.getByRole("button", { name: "Finish" }));
|
|
1059
|
+
|
|
1060
|
+
expect(onAction).toHaveBeenCalledWith({ type: "finish", step: 2 });
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
it("should not override custom footer in walkthrough mode", async () => {
|
|
1064
|
+
render(
|
|
1065
|
+
<PopoverFlow
|
|
1066
|
+
separatorText="of"
|
|
1067
|
+
stepText="Step"
|
|
1068
|
+
backLabel="Back"
|
|
1069
|
+
nextLabel="Next"
|
|
1070
|
+
finishLabel="Finish"
|
|
1071
|
+
>
|
|
1072
|
+
<Popover multistep step={1}>
|
|
1073
|
+
<PopoverTrigger>
|
|
1074
|
+
<Button variant="primary">Step 1</Button>
|
|
1075
|
+
</PopoverTrigger>
|
|
1076
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 1" body="First step">
|
|
1077
|
+
<PopoverFooter>
|
|
1078
|
+
<PopoverButton>Custom Footer</PopoverButton>
|
|
1079
|
+
</PopoverFooter>
|
|
1080
|
+
</PopoverContent>
|
|
1081
|
+
</Popover>
|
|
1082
|
+
<Popover multistep step={2}>
|
|
1083
|
+
<PopoverTrigger>
|
|
1084
|
+
<Button variant="primary">Step 2</Button>
|
|
1085
|
+
</PopoverTrigger>
|
|
1086
|
+
<PopoverContent closeIconAriaLabel="Close" title="Step 2" body="Second step" />
|
|
1087
|
+
</Popover>
|
|
1088
|
+
</PopoverFlow>
|
|
1089
|
+
);
|
|
1090
|
+
|
|
1091
|
+
await waitFor(
|
|
1092
|
+
() => {
|
|
1093
|
+
expect(screen.getByRole("button", { name: "Custom Footer" })).toBeInTheDocument();
|
|
1094
|
+
},
|
|
1095
|
+
{ timeout: 5000 }
|
|
1096
|
+
);
|
|
1097
|
+
|
|
1098
|
+
expect(screen.queryByRole("button", { name: "Next" })).not.toBeInTheDocument();
|
|
1099
|
+
});
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
describe("Forward Ref", () => {
|
|
1103
|
+
it("should forward ref to content element", async () => {
|
|
1104
|
+
const ref = React.createRef<HTMLDivElement>();
|
|
1105
|
+
const user = userEvent.setup({ delay: null });
|
|
1106
|
+
render(
|
|
1107
|
+
<Popover>
|
|
1108
|
+
<PopoverTrigger>
|
|
1109
|
+
<Button variant="primary">Trigger</Button>
|
|
1110
|
+
</PopoverTrigger>
|
|
1111
|
+
<PopoverContent ref={ref} closeIconAriaLabel="Close" title="Test" body="Body text" />
|
|
1112
|
+
</Popover>
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1115
|
+
await user.click(screen.getByRole("button", { name: "Trigger" }));
|
|
1116
|
+
|
|
1117
|
+
await waitFor(() => {
|
|
1118
|
+
expect(ref.current).toBeInstanceOf(HTMLElement);
|
|
1119
|
+
});
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
1122
|
+
});
|