@khanacademy/wonder-blocks-popover 3.2.14 → 3.2.16
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/CHANGELOG.md +22 -0
- package/package.json +7 -7
- package/src/components/__tests__/focus-manager.test.tsx +0 -180
- package/src/components/__tests__/initial-focus.test.tsx +0 -73
- package/src/components/__tests__/popover-anchor.test.tsx +0 -61
- package/src/components/__tests__/popover-content.test.tsx +0 -76
- package/src/components/__tests__/popover-content.typestest.tsx +0 -38
- package/src/components/__tests__/popover-dialog.test.tsx +0 -98
- package/src/components/__tests__/popover-event-listener.test.tsx +0 -98
- package/src/components/__tests__/popover.test.tsx +0 -932
- package/src/components/close-button.tsx +0 -61
- package/src/components/focus-manager.tsx +0 -344
- package/src/components/initial-focus.ts +0 -87
- package/src/components/popover-anchor.ts +0 -93
- package/src/components/popover-content-core.tsx +0 -143
- package/src/components/popover-content.tsx +0 -319
- package/src/components/popover-context.ts +0 -40
- package/src/components/popover-dialog.tsx +0 -150
- package/src/components/popover-event-listener.ts +0 -96
- package/src/components/popover.tsx +0 -410
- package/src/index.ts +0 -5
- package/src/util/__tests__/util.test.tsx +0 -38
- package/src/util/util.ts +0 -20
- package/tsconfig-build.json +0 -17
- package/tsconfig-build.tsbuildinfo +0 -1
|
@@ -1,932 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {render, screen, waitFor} from "@testing-library/react";
|
|
3
|
-
import {userEvent, PointerEventsCheckLevel} from "@testing-library/user-event";
|
|
4
|
-
|
|
5
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
7
|
-
|
|
8
|
-
import {fireEvent} from "@storybook/test";
|
|
9
|
-
import Popover from "../popover";
|
|
10
|
-
import PopoverContent from "../popover-content";
|
|
11
|
-
import {PopoverContentCore} from "../../index";
|
|
12
|
-
|
|
13
|
-
describe("Popover", () => {
|
|
14
|
-
it("should set the anchor as the popover ref", async () => {
|
|
15
|
-
// Arrange
|
|
16
|
-
const ref: React.RefObject<HTMLButtonElement> = React.createRef();
|
|
17
|
-
|
|
18
|
-
render(
|
|
19
|
-
<Popover
|
|
20
|
-
placement="top"
|
|
21
|
-
content={<PopoverContent title="Title" content="content" />}
|
|
22
|
-
>
|
|
23
|
-
{({open}: any) => (
|
|
24
|
-
<button data-anchor onClick={open} ref={ref}>
|
|
25
|
-
Open default popover
|
|
26
|
-
</button>
|
|
27
|
-
)}
|
|
28
|
-
</Popover>,
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
// Act
|
|
32
|
-
|
|
33
|
-
// Assert
|
|
34
|
-
await waitFor(() => {
|
|
35
|
-
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("should hide the popover dialog by default", async () => {
|
|
40
|
-
// Arrange, Act
|
|
41
|
-
render(
|
|
42
|
-
<Popover
|
|
43
|
-
placement="top"
|
|
44
|
-
content={<PopoverContent title="Title" content="content" />}
|
|
45
|
-
>
|
|
46
|
-
{({open}: any) => (
|
|
47
|
-
<button data-anchor onClick={open}>
|
|
48
|
-
Open default popover
|
|
49
|
-
</button>
|
|
50
|
-
)}
|
|
51
|
-
</Popover>,
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
// Assert
|
|
55
|
-
expect(screen.queryByText("Title")).not.toBeInTheDocument();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("should render the popover content after clicking the trigger", async () => {
|
|
59
|
-
// Arrange
|
|
60
|
-
render(
|
|
61
|
-
<Popover
|
|
62
|
-
placement="top"
|
|
63
|
-
content={<PopoverContent title="Title" content="content" />}
|
|
64
|
-
>
|
|
65
|
-
{({open}: any) => (
|
|
66
|
-
<button data-anchor onClick={open}>
|
|
67
|
-
Open default popover
|
|
68
|
-
</button>
|
|
69
|
-
)}
|
|
70
|
-
</Popover>,
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
// Act
|
|
74
|
-
await userEvent.click(await screen.findByRole("button"));
|
|
75
|
-
|
|
76
|
-
// Assert
|
|
77
|
-
expect(await screen.findByText("Title")).toBeInTheDocument();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("should close the popover from inside the content", async () => {
|
|
81
|
-
// Arrange
|
|
82
|
-
const onCloseMock = jest.fn();
|
|
83
|
-
|
|
84
|
-
render(
|
|
85
|
-
<Popover
|
|
86
|
-
placement="top"
|
|
87
|
-
onClose={onCloseMock}
|
|
88
|
-
content={({close}: any) => (
|
|
89
|
-
<PopoverContentCore>
|
|
90
|
-
<span>custom popover</span>
|
|
91
|
-
<button data-close-button onClick={close}>
|
|
92
|
-
close popover
|
|
93
|
-
</button>
|
|
94
|
-
</PopoverContentCore>
|
|
95
|
-
)}
|
|
96
|
-
>
|
|
97
|
-
{({open}: any) => (
|
|
98
|
-
<button data-anchor onClick={open}>
|
|
99
|
-
Open default popover
|
|
100
|
-
</button>
|
|
101
|
-
)}
|
|
102
|
-
</Popover>,
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
// open the popover
|
|
106
|
-
await userEvent.click(await screen.findByRole("button"));
|
|
107
|
-
|
|
108
|
-
// Act
|
|
109
|
-
// we try to close it from inside the content
|
|
110
|
-
await userEvent.click(
|
|
111
|
-
await screen.findByRole("button", {name: "close popover"}),
|
|
112
|
-
{
|
|
113
|
-
pointerEventsCheck: PointerEventsCheckLevel.Never,
|
|
114
|
-
},
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
// Assert
|
|
118
|
-
expect(screen.queryByText("Title")).not.toBeInTheDocument();
|
|
119
|
-
expect(onCloseMock).toBeCalled();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("should close the Popover using the default close button", async () => {
|
|
123
|
-
// Arrange
|
|
124
|
-
const onCloseMock = jest.fn();
|
|
125
|
-
|
|
126
|
-
render(
|
|
127
|
-
<Popover
|
|
128
|
-
placement="top"
|
|
129
|
-
onClose={onCloseMock}
|
|
130
|
-
content={
|
|
131
|
-
<PopoverContent
|
|
132
|
-
title="Title"
|
|
133
|
-
content="content"
|
|
134
|
-
closeButtonVisible={true}
|
|
135
|
-
closeButtonLabel="Click to close popover"
|
|
136
|
-
/>
|
|
137
|
-
}
|
|
138
|
-
>
|
|
139
|
-
{({open}: any) => (
|
|
140
|
-
<button data-anchor onClick={open}>
|
|
141
|
-
Open default popover
|
|
142
|
-
</button>
|
|
143
|
-
)}
|
|
144
|
-
</Popover>,
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// open the popover
|
|
148
|
-
await userEvent.click(await screen.findByRole("button"));
|
|
149
|
-
|
|
150
|
-
// Act
|
|
151
|
-
// we try to close it using the default close button
|
|
152
|
-
await userEvent.click(
|
|
153
|
-
await screen.findByRole("button", {name: "Click to close popover"}),
|
|
154
|
-
{
|
|
155
|
-
pointerEventsCheck: PointerEventsCheckLevel.Never,
|
|
156
|
-
},
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
// Assert
|
|
160
|
-
expect(screen.queryByText("Title")).not.toBeInTheDocument();
|
|
161
|
-
expect(onCloseMock).toBeCalled();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("should shift-tab back to the anchor after popover is closed", async () => {
|
|
165
|
-
// Arrange
|
|
166
|
-
const PopoverComponent = () => {
|
|
167
|
-
const [opened, setOpened] = React.useState(true);
|
|
168
|
-
return (
|
|
169
|
-
<View>
|
|
170
|
-
<Popover
|
|
171
|
-
opened={opened}
|
|
172
|
-
onClose={() => {
|
|
173
|
-
setOpened(false);
|
|
174
|
-
}}
|
|
175
|
-
content={({close}) => (
|
|
176
|
-
<PopoverContent
|
|
177
|
-
title="Controlled popover"
|
|
178
|
-
content="This popover is controlled programatically."
|
|
179
|
-
actions={
|
|
180
|
-
<Button
|
|
181
|
-
onClick={() => {
|
|
182
|
-
close();
|
|
183
|
-
}}
|
|
184
|
-
>
|
|
185
|
-
Click to close the popover
|
|
186
|
-
</Button>
|
|
187
|
-
}
|
|
188
|
-
/>
|
|
189
|
-
)}
|
|
190
|
-
>
|
|
191
|
-
<Button>Anchor element</Button>
|
|
192
|
-
</Popover>
|
|
193
|
-
<Button onClick={() => setOpened(true)}>
|
|
194
|
-
Outside button (click here to re-open the popover)
|
|
195
|
-
</Button>
|
|
196
|
-
</View>
|
|
197
|
-
);
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
render(<PopoverComponent />);
|
|
201
|
-
|
|
202
|
-
// Act
|
|
203
|
-
const closeButton = await screen.findByRole("button", {
|
|
204
|
-
name: "Click to close the popover",
|
|
205
|
-
});
|
|
206
|
-
closeButton.click();
|
|
207
|
-
|
|
208
|
-
// At this point, the focus returns to the anchor element
|
|
209
|
-
|
|
210
|
-
// Shift-tab over to the document body
|
|
211
|
-
await userEvent.tab({shift: true});
|
|
212
|
-
|
|
213
|
-
// Shift-tab over to the outside button
|
|
214
|
-
await userEvent.tab({shift: true});
|
|
215
|
-
|
|
216
|
-
// Shift-tab over to the anchor element
|
|
217
|
-
await userEvent.tab({shift: true});
|
|
218
|
-
|
|
219
|
-
// Assert
|
|
220
|
-
const anchorButton = await screen.findByRole("button", {
|
|
221
|
-
name: "Anchor element",
|
|
222
|
-
});
|
|
223
|
-
expect(anchorButton).toHaveFocus();
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it("should close the popover when pressing Enter on the close button", async () => {
|
|
227
|
-
// Arrange
|
|
228
|
-
render(
|
|
229
|
-
<Popover
|
|
230
|
-
placement="top"
|
|
231
|
-
onClose={jest.fn()}
|
|
232
|
-
content={
|
|
233
|
-
<PopoverContent
|
|
234
|
-
title="Title"
|
|
235
|
-
content="content"
|
|
236
|
-
closeButtonVisible={true}
|
|
237
|
-
closeButtonLabel="Click to close popover"
|
|
238
|
-
/>
|
|
239
|
-
}
|
|
240
|
-
>
|
|
241
|
-
<Button onClick={jest.fn()}>Open default popover</Button>
|
|
242
|
-
</Popover>,
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
// open the popover by focusing on the trigger element
|
|
246
|
-
await userEvent.tab();
|
|
247
|
-
await userEvent.keyboard("{enter}");
|
|
248
|
-
|
|
249
|
-
// Act
|
|
250
|
-
// Close the popover by pressing Enter on the close button.
|
|
251
|
-
// NOTE: we need to use fireEvent here because await userEvent doesn't support
|
|
252
|
-
// keyUp/Down events and we use these handlers to override the default
|
|
253
|
-
// behavior of the button.
|
|
254
|
-
// eslint-disable-next-line testing-library/prefer-user-event
|
|
255
|
-
fireEvent.keyDown(
|
|
256
|
-
await screen.findByRole("button", {name: "Click to close popover"}),
|
|
257
|
-
{key: "Enter", code: "Enter", charCode: 13},
|
|
258
|
-
);
|
|
259
|
-
// eslint-disable-next-line testing-library/prefer-user-event
|
|
260
|
-
fireEvent.keyDown(
|
|
261
|
-
await screen.findByRole("button", {name: "Click to close popover"}),
|
|
262
|
-
{key: "Enter", code: "Enter", charCode: 13},
|
|
263
|
-
);
|
|
264
|
-
// eslint-disable-next-line testing-library/prefer-user-event
|
|
265
|
-
fireEvent.keyUp(
|
|
266
|
-
await screen.findByRole("button", {name: "Click to close popover"}),
|
|
267
|
-
{key: "Enter", code: "Enter", charCode: 13},
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
// Assert
|
|
271
|
-
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
describe("return focus", () => {
|
|
275
|
-
it("should return focus to the trigger element by default", async () => {
|
|
276
|
-
// Arrange
|
|
277
|
-
render(
|
|
278
|
-
<Popover
|
|
279
|
-
dismissEnabled={true}
|
|
280
|
-
content={
|
|
281
|
-
<PopoverContent
|
|
282
|
-
closeButtonVisible={true}
|
|
283
|
-
title="Returning focus to a specific element"
|
|
284
|
-
content='After dismissing the popover, the focus will be set on the button labeled "Focus here after close."'
|
|
285
|
-
/>
|
|
286
|
-
}
|
|
287
|
-
>
|
|
288
|
-
<Button>Open popover</Button>
|
|
289
|
-
</Popover>,
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
const anchorButton = await screen.findByRole("button", {
|
|
293
|
-
name: "Open popover",
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// open the popover
|
|
297
|
-
await userEvent.click(anchorButton);
|
|
298
|
-
await screen.findByRole("dialog");
|
|
299
|
-
|
|
300
|
-
// Act
|
|
301
|
-
const closeButton = await screen.findByRole("button", {
|
|
302
|
-
name: "Close Popover",
|
|
303
|
-
});
|
|
304
|
-
closeButton.click();
|
|
305
|
-
|
|
306
|
-
// Assert
|
|
307
|
-
expect(anchorButton).toHaveFocus();
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it("should return focus to a specific element if closedFocusId is set", async () => {
|
|
311
|
-
// Arrange
|
|
312
|
-
render(
|
|
313
|
-
<View>
|
|
314
|
-
<Button id="button-to-focus-on">
|
|
315
|
-
Focus here after close
|
|
316
|
-
</Button>
|
|
317
|
-
<Popover
|
|
318
|
-
closedFocusId="button-to-focus-on"
|
|
319
|
-
dismissEnabled={true}
|
|
320
|
-
content={
|
|
321
|
-
<PopoverContent
|
|
322
|
-
closeButtonVisible={true}
|
|
323
|
-
title="Returning focus to a specific element"
|
|
324
|
-
content='After dismissing the popover, the focus will be set on the button labeled "Focus here after close."'
|
|
325
|
-
/>
|
|
326
|
-
}
|
|
327
|
-
>
|
|
328
|
-
<Button>Open popover</Button>
|
|
329
|
-
</Popover>
|
|
330
|
-
</View>,
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
const anchorButton = await screen.findByRole("button", {
|
|
334
|
-
name: "Open popover",
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// open the popover
|
|
338
|
-
await userEvent.click(anchorButton);
|
|
339
|
-
await screen.findByRole("dialog");
|
|
340
|
-
|
|
341
|
-
// Act
|
|
342
|
-
const closeButton = await screen.findByRole("button", {
|
|
343
|
-
name: "Close Popover",
|
|
344
|
-
});
|
|
345
|
-
closeButton.click();
|
|
346
|
-
|
|
347
|
-
// Assert
|
|
348
|
-
const buttonToFocusOn = await screen.findByRole("button", {
|
|
349
|
-
name: "Focus here after close",
|
|
350
|
-
});
|
|
351
|
-
expect(buttonToFocusOn).toHaveFocus();
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
describe("dismissEnabled", () => {
|
|
356
|
-
it("should close the Popover if dismissEnabled is set", async () => {
|
|
357
|
-
// Arrange
|
|
358
|
-
render(
|
|
359
|
-
<Popover
|
|
360
|
-
dismissEnabled={true}
|
|
361
|
-
placement="top"
|
|
362
|
-
content={<PopoverContent title="Title" content="content" />}
|
|
363
|
-
>
|
|
364
|
-
{({open}: any) => (
|
|
365
|
-
<button data-anchor onClick={open}>
|
|
366
|
-
Open default popover
|
|
367
|
-
</button>
|
|
368
|
-
)}
|
|
369
|
-
</Popover>,
|
|
370
|
-
);
|
|
371
|
-
|
|
372
|
-
// open the popover
|
|
373
|
-
await userEvent.click(
|
|
374
|
-
await screen.findByRole("button", {
|
|
375
|
-
name: "Open default popover",
|
|
376
|
-
}),
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
// Act
|
|
380
|
-
// we try to close it using the same trigger element
|
|
381
|
-
await userEvent.click(
|
|
382
|
-
await screen.findByRole("button", {
|
|
383
|
-
name: "Open default popover",
|
|
384
|
-
}),
|
|
385
|
-
);
|
|
386
|
-
|
|
387
|
-
// Assert
|
|
388
|
-
await waitFor(() => {
|
|
389
|
-
expect(screen.queryByText("Title")).not.toBeInTheDocument();
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
// TODO(FEI-5533): Key press events aren't working correctly with
|
|
394
|
-
// user-event v14. We need to investigate and fix this.
|
|
395
|
-
it.skip("should return focus to the anchor element when pressing Esc", async () => {
|
|
396
|
-
// Arrange
|
|
397
|
-
render(
|
|
398
|
-
<Popover
|
|
399
|
-
dismissEnabled={true}
|
|
400
|
-
placement="top"
|
|
401
|
-
content={<PopoverContent title="Title" content="content" />}
|
|
402
|
-
>
|
|
403
|
-
{({open}: any) => (
|
|
404
|
-
<button data-anchor onClick={open}>
|
|
405
|
-
Open default popover
|
|
406
|
-
</button>
|
|
407
|
-
)}
|
|
408
|
-
</Popover>,
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
// open the popover
|
|
412
|
-
await userEvent.click(
|
|
413
|
-
await screen.findByRole("button", {
|
|
414
|
-
name: "Open default popover",
|
|
415
|
-
}),
|
|
416
|
-
);
|
|
417
|
-
|
|
418
|
-
// Act
|
|
419
|
-
// we try to close it pressing the Escape key
|
|
420
|
-
await userEvent.keyboard("{esc}");
|
|
421
|
-
|
|
422
|
-
// Assert
|
|
423
|
-
expect(
|
|
424
|
-
await screen.findByRole("button", {
|
|
425
|
-
name: "Open default popover",
|
|
426
|
-
}),
|
|
427
|
-
).toHaveFocus();
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
// NOTE(john): This is failing after upgrading to user-event v14.
|
|
431
|
-
// The focus is not being returned to the anchor element after clicking
|
|
432
|
-
// outside the popover. We need to investigate and fix this.
|
|
433
|
-
it.skip("should return focus to the anchor element when clicking outside", async () => {
|
|
434
|
-
// Arrange
|
|
435
|
-
const {container} = render(
|
|
436
|
-
<Popover
|
|
437
|
-
dismissEnabled={true}
|
|
438
|
-
placement="top"
|
|
439
|
-
content={<PopoverContent title="Title" content="content" />}
|
|
440
|
-
>
|
|
441
|
-
{({open}: any) => (
|
|
442
|
-
<button data-anchor onClick={open}>
|
|
443
|
-
Open default popover
|
|
444
|
-
</button>
|
|
445
|
-
)}
|
|
446
|
-
</Popover>,
|
|
447
|
-
);
|
|
448
|
-
|
|
449
|
-
// open the popover
|
|
450
|
-
await userEvent.click(
|
|
451
|
-
await screen.findByRole("button", {
|
|
452
|
-
name: "Open default popover",
|
|
453
|
-
}),
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
// Act
|
|
457
|
-
// we try to close it clicking outside the popover
|
|
458
|
-
await userEvent.click(container);
|
|
459
|
-
|
|
460
|
-
// Assert
|
|
461
|
-
expect(
|
|
462
|
-
await screen.findByRole("button", {
|
|
463
|
-
name: "Open default popover",
|
|
464
|
-
}),
|
|
465
|
-
).toHaveFocus();
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
it("should NOT return focus to the anchor element when clicking on an interactive element", async () => {
|
|
469
|
-
// Arrange
|
|
470
|
-
render(
|
|
471
|
-
<View>
|
|
472
|
-
<Popover
|
|
473
|
-
dismissEnabled={true}
|
|
474
|
-
placement="top"
|
|
475
|
-
content={
|
|
476
|
-
<PopoverContent title="Title" content="content" />
|
|
477
|
-
}
|
|
478
|
-
>
|
|
479
|
-
{({open}: any) => (
|
|
480
|
-
<button data-anchor onClick={open}>
|
|
481
|
-
Open default popover
|
|
482
|
-
</button>
|
|
483
|
-
)}
|
|
484
|
-
</Popover>
|
|
485
|
-
<Button>Next button outside</Button>
|
|
486
|
-
</View>,
|
|
487
|
-
);
|
|
488
|
-
|
|
489
|
-
// open the popover
|
|
490
|
-
await userEvent.click(
|
|
491
|
-
await screen.findByRole("button", {
|
|
492
|
-
name: "Open default popover",
|
|
493
|
-
}),
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
// Act
|
|
497
|
-
// we try to close it clicking outside the popover
|
|
498
|
-
await userEvent.click(
|
|
499
|
-
await screen.findByRole("button", {
|
|
500
|
-
name: "Next button outside",
|
|
501
|
-
}),
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
// Assert
|
|
505
|
-
// The focus should remain on the button outside the popover
|
|
506
|
-
expect(
|
|
507
|
-
await screen.findByRole("button", {
|
|
508
|
-
name: "Next button outside",
|
|
509
|
-
}),
|
|
510
|
-
).toHaveFocus();
|
|
511
|
-
});
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
describe("a11y", () => {
|
|
515
|
-
it("should announce a popover correctly by reading the title contents", async () => {
|
|
516
|
-
// Arrange
|
|
517
|
-
render(
|
|
518
|
-
<Popover
|
|
519
|
-
onClose={jest.fn()}
|
|
520
|
-
content={
|
|
521
|
-
<PopoverContent
|
|
522
|
-
title="The title is read by the screen reader"
|
|
523
|
-
content="content"
|
|
524
|
-
closeButtonVisible={true}
|
|
525
|
-
closeButtonLabel="Click to close popover"
|
|
526
|
-
/>
|
|
527
|
-
}
|
|
528
|
-
>
|
|
529
|
-
<Button>Open default popover</Button>
|
|
530
|
-
</Popover>,
|
|
531
|
-
);
|
|
532
|
-
|
|
533
|
-
// Act
|
|
534
|
-
// Open the popover
|
|
535
|
-
await userEvent.click(
|
|
536
|
-
await screen.findByRole("button", {
|
|
537
|
-
name: "Open default popover",
|
|
538
|
-
}),
|
|
539
|
-
);
|
|
540
|
-
|
|
541
|
-
// Assert
|
|
542
|
-
expect(
|
|
543
|
-
await screen.findByRole("dialog", {
|
|
544
|
-
name: "The title is read by the screen reader",
|
|
545
|
-
}),
|
|
546
|
-
).toBeInTheDocument();
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
it("should announce a custom popover correctly by reading the title contents", async () => {
|
|
550
|
-
// Arrange
|
|
551
|
-
render(
|
|
552
|
-
<Popover
|
|
553
|
-
onClose={jest.fn()}
|
|
554
|
-
id="custom-popover"
|
|
555
|
-
content={
|
|
556
|
-
<PopoverContentCore closeButtonVisible={true}>
|
|
557
|
-
<h1 id="custom-popover-title">
|
|
558
|
-
This is a custom popover title
|
|
559
|
-
</h1>
|
|
560
|
-
<p id="custom-popover-content">
|
|
561
|
-
The custom popover description
|
|
562
|
-
</p>
|
|
563
|
-
</PopoverContentCore>
|
|
564
|
-
}
|
|
565
|
-
>
|
|
566
|
-
<Button>Open default popover</Button>
|
|
567
|
-
</Popover>,
|
|
568
|
-
);
|
|
569
|
-
|
|
570
|
-
// Act
|
|
571
|
-
// Open the popover
|
|
572
|
-
await userEvent.click(
|
|
573
|
-
await screen.findByRole("button", {
|
|
574
|
-
name: "Open default popover",
|
|
575
|
-
}),
|
|
576
|
-
);
|
|
577
|
-
|
|
578
|
-
// Assert
|
|
579
|
-
expect(
|
|
580
|
-
await screen.findByRole("dialog", {
|
|
581
|
-
name: "This is a custom popover title",
|
|
582
|
-
}),
|
|
583
|
-
).toBeInTheDocument();
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
it("should announce a popover correctly by reading the aria-label attribute", async () => {
|
|
587
|
-
// Arrange
|
|
588
|
-
render(
|
|
589
|
-
<Popover
|
|
590
|
-
id="test-popover"
|
|
591
|
-
onClose={jest.fn()}
|
|
592
|
-
aria-label="Popover Aria Label"
|
|
593
|
-
content={
|
|
594
|
-
<PopoverContentCore closeButtonVisible={true}>
|
|
595
|
-
<h1 id="test-popover-title">
|
|
596
|
-
This is a popover title
|
|
597
|
-
</h1>
|
|
598
|
-
<p id="test-popover-content">
|
|
599
|
-
This is a popover description
|
|
600
|
-
</p>
|
|
601
|
-
</PopoverContentCore>
|
|
602
|
-
}
|
|
603
|
-
>
|
|
604
|
-
<Button>Open default popover</Button>
|
|
605
|
-
</Popover>,
|
|
606
|
-
);
|
|
607
|
-
|
|
608
|
-
// Act
|
|
609
|
-
await userEvent.click(
|
|
610
|
-
await screen.findByRole("button", {
|
|
611
|
-
name: "Open default popover",
|
|
612
|
-
}),
|
|
613
|
-
);
|
|
614
|
-
|
|
615
|
-
// Assert
|
|
616
|
-
expect(
|
|
617
|
-
await screen.findByRole("dialog", {
|
|
618
|
-
name: "Popover Aria Label",
|
|
619
|
-
}),
|
|
620
|
-
).toBeInTheDocument();
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
it("should announce a popover correctly by reading the title contents", async () => {
|
|
624
|
-
// Arrange
|
|
625
|
-
render(
|
|
626
|
-
<Popover
|
|
627
|
-
id="test-popover"
|
|
628
|
-
onClose={jest.fn()}
|
|
629
|
-
aria-describedby="describing-popover-id"
|
|
630
|
-
content={
|
|
631
|
-
<PopoverContentCore closeButtonVisible={true}>
|
|
632
|
-
<h1 id="test-popover-title">
|
|
633
|
-
This is a popover title
|
|
634
|
-
</h1>
|
|
635
|
-
<p id="describing-popover-id">
|
|
636
|
-
This is a popover description
|
|
637
|
-
</p>
|
|
638
|
-
</PopoverContentCore>
|
|
639
|
-
}
|
|
640
|
-
>
|
|
641
|
-
<Button>Open default popover</Button>
|
|
642
|
-
</Popover>,
|
|
643
|
-
);
|
|
644
|
-
|
|
645
|
-
// Act
|
|
646
|
-
await userEvent.click(
|
|
647
|
-
await screen.findByRole("button", {
|
|
648
|
-
name: "Open default popover",
|
|
649
|
-
}),
|
|
650
|
-
);
|
|
651
|
-
|
|
652
|
-
//Assert
|
|
653
|
-
expect(
|
|
654
|
-
await screen.findByRole("dialog", {
|
|
655
|
-
name: "This is a popover title",
|
|
656
|
-
}),
|
|
657
|
-
).toBeInTheDocument();
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
it("should announce a popover correctly by reading the describing contents", async () => {
|
|
661
|
-
// Arrange
|
|
662
|
-
render(
|
|
663
|
-
<Popover
|
|
664
|
-
id="test-popover"
|
|
665
|
-
onClose={jest.fn()}
|
|
666
|
-
aria-describedby="describing-popover-id"
|
|
667
|
-
content={
|
|
668
|
-
<PopoverContentCore closeButtonVisible={true}>
|
|
669
|
-
<h1 id="test-popover-title">
|
|
670
|
-
This is a popover title
|
|
671
|
-
</h1>
|
|
672
|
-
<p id="describing-popover-id">
|
|
673
|
-
This is a popover description
|
|
674
|
-
</p>
|
|
675
|
-
</PopoverContentCore>
|
|
676
|
-
}
|
|
677
|
-
>
|
|
678
|
-
<Button>Open default popover</Button>
|
|
679
|
-
</Popover>,
|
|
680
|
-
);
|
|
681
|
-
|
|
682
|
-
// Act
|
|
683
|
-
await userEvent.click(
|
|
684
|
-
await screen.findByRole("button", {
|
|
685
|
-
name: "Open default popover",
|
|
686
|
-
}),
|
|
687
|
-
);
|
|
688
|
-
|
|
689
|
-
//Assert
|
|
690
|
-
expect(
|
|
691
|
-
await screen.findByRole("dialog", {
|
|
692
|
-
description: "This is a popover description",
|
|
693
|
-
}),
|
|
694
|
-
).toBeInTheDocument();
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
it("should correctly describe the popover content core's aria label", async () => {
|
|
698
|
-
// Arrange
|
|
699
|
-
render(
|
|
700
|
-
<Popover
|
|
701
|
-
onClose={jest.fn()}
|
|
702
|
-
content={
|
|
703
|
-
<PopoverContentCore aria-label="Popover Content Core">
|
|
704
|
-
<button data-close-button onClick={close}>
|
|
705
|
-
Close Popover
|
|
706
|
-
</button>
|
|
707
|
-
</PopoverContentCore>
|
|
708
|
-
}
|
|
709
|
-
>
|
|
710
|
-
<Button>Open default popover</Button>
|
|
711
|
-
</Popover>,
|
|
712
|
-
);
|
|
713
|
-
|
|
714
|
-
// Act
|
|
715
|
-
// Open the popover
|
|
716
|
-
const openButton = await screen.findByRole("button", {
|
|
717
|
-
name: "Open default popover",
|
|
718
|
-
});
|
|
719
|
-
|
|
720
|
-
await userEvent.click(openButton);
|
|
721
|
-
const popover = await screen.findByRole("dialog");
|
|
722
|
-
|
|
723
|
-
// disabling this check because we need to access the popover content core
|
|
724
|
-
// in order to verify the aria-label is getting passed correctly
|
|
725
|
-
// eslint-disable-next-line testing-library/no-node-access
|
|
726
|
-
const popoverContentCore = popover.firstChild as HTMLElement;
|
|
727
|
-
|
|
728
|
-
// Assert
|
|
729
|
-
expect(popoverContentCore.getAttribute("aria-label")).toBe(
|
|
730
|
-
"Popover Content Core",
|
|
731
|
-
);
|
|
732
|
-
});
|
|
733
|
-
});
|
|
734
|
-
|
|
735
|
-
describe.each([true, false])("keyboard navigation", (portal) => {
|
|
736
|
-
it(`when portal=${portal}, should move focus to the first focusable element after popover is open`, async () => {
|
|
737
|
-
// Arrange
|
|
738
|
-
render(
|
|
739
|
-
<>
|
|
740
|
-
<Button>Prev focusable element outside</Button>
|
|
741
|
-
<Popover
|
|
742
|
-
onClose={jest.fn()}
|
|
743
|
-
portal={portal}
|
|
744
|
-
content={
|
|
745
|
-
<PopoverContent
|
|
746
|
-
title="Popover title"
|
|
747
|
-
content="content"
|
|
748
|
-
actions={
|
|
749
|
-
<>
|
|
750
|
-
<Button>Button 1 inside popover</Button>
|
|
751
|
-
<Button>Button 2 inside popover</Button>
|
|
752
|
-
</>
|
|
753
|
-
}
|
|
754
|
-
/>
|
|
755
|
-
}
|
|
756
|
-
>
|
|
757
|
-
<Button>Open default popover</Button>
|
|
758
|
-
</Popover>
|
|
759
|
-
<Button>Next focusable element outside</Button>
|
|
760
|
-
</>,
|
|
761
|
-
);
|
|
762
|
-
|
|
763
|
-
// Focus on the first element outside the popover
|
|
764
|
-
await userEvent.tab();
|
|
765
|
-
// open the popover by focusing on the trigger element
|
|
766
|
-
await userEvent.tab();
|
|
767
|
-
await userEvent.keyboard("{enter}");
|
|
768
|
-
|
|
769
|
-
// Act
|
|
770
|
-
// Wait for the popover to be open.
|
|
771
|
-
await screen.findByRole("dialog");
|
|
772
|
-
|
|
773
|
-
// Assert
|
|
774
|
-
// Focus should move to the first button inside the popover
|
|
775
|
-
expect(
|
|
776
|
-
await screen.findByRole("button", {
|
|
777
|
-
name: "Button 1 inside popover",
|
|
778
|
-
}),
|
|
779
|
-
).toHaveFocus();
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
it(`when portal=${portal}, should allow flowing focus correctly even if the popover remains open`, async () => {
|
|
783
|
-
// Arrange
|
|
784
|
-
render(
|
|
785
|
-
<>
|
|
786
|
-
<Button>Prev focusable element outside</Button>
|
|
787
|
-
<Popover
|
|
788
|
-
onClose={jest.fn()}
|
|
789
|
-
portal={portal}
|
|
790
|
-
content={
|
|
791
|
-
<PopoverContent
|
|
792
|
-
title="Popover title"
|
|
793
|
-
content="content"
|
|
794
|
-
actions={<Button>Button inside popover</Button>}
|
|
795
|
-
/>
|
|
796
|
-
}
|
|
797
|
-
>
|
|
798
|
-
<Button>Open default popover</Button>
|
|
799
|
-
</Popover>
|
|
800
|
-
<Button>Next focusable element outside</Button>
|
|
801
|
-
</>,
|
|
802
|
-
);
|
|
803
|
-
|
|
804
|
-
// Focus on the first element outside the popover
|
|
805
|
-
await userEvent.tab();
|
|
806
|
-
// open the popover by focusing on the trigger element
|
|
807
|
-
await userEvent.tab();
|
|
808
|
-
await userEvent.keyboard("{enter}");
|
|
809
|
-
|
|
810
|
-
// Wait for the popover to be open.
|
|
811
|
-
await screen.findByRole("dialog");
|
|
812
|
-
|
|
813
|
-
// Act
|
|
814
|
-
// Focus on the next element after the popover
|
|
815
|
-
await userEvent.tab();
|
|
816
|
-
|
|
817
|
-
// Assert
|
|
818
|
-
expect(
|
|
819
|
-
await screen.findByRole("button", {
|
|
820
|
-
name: "Next focusable element outside",
|
|
821
|
-
}),
|
|
822
|
-
).toHaveFocus();
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
it(`when portal=${portal}, should allow circular navigation when the popover is open`, async () => {
|
|
826
|
-
// Arrange
|
|
827
|
-
render(
|
|
828
|
-
<>
|
|
829
|
-
<Button>Prev focusable element outside</Button>
|
|
830
|
-
<Popover
|
|
831
|
-
onClose={jest.fn()}
|
|
832
|
-
portal={portal}
|
|
833
|
-
content={
|
|
834
|
-
<PopoverContent
|
|
835
|
-
title="Popover title"
|
|
836
|
-
content="content"
|
|
837
|
-
actions={<Button>Button inside popover</Button>}
|
|
838
|
-
/>
|
|
839
|
-
}
|
|
840
|
-
>
|
|
841
|
-
<Button>Open default popover</Button>
|
|
842
|
-
</Popover>
|
|
843
|
-
<Button>Next focusable element outside</Button>
|
|
844
|
-
</>,
|
|
845
|
-
);
|
|
846
|
-
|
|
847
|
-
// Focus on the first element outside the popover
|
|
848
|
-
await userEvent.tab();
|
|
849
|
-
// open the popover by focusing on the trigger element
|
|
850
|
-
await userEvent.tab();
|
|
851
|
-
await userEvent.keyboard("{enter}");
|
|
852
|
-
|
|
853
|
-
// Wait for the popover to be open.
|
|
854
|
-
await screen.findByRole("dialog");
|
|
855
|
-
|
|
856
|
-
// Focus on the next element after the popover
|
|
857
|
-
await userEvent.tab();
|
|
858
|
-
|
|
859
|
-
// Focus on the document body
|
|
860
|
-
await userEvent.tab();
|
|
861
|
-
|
|
862
|
-
// Act
|
|
863
|
-
// Focus again on the first element in the document.
|
|
864
|
-
await userEvent.tab();
|
|
865
|
-
|
|
866
|
-
// Assert
|
|
867
|
-
expect(
|
|
868
|
-
await screen.findByRole("button", {
|
|
869
|
-
name: "Prev focusable element outside",
|
|
870
|
-
}),
|
|
871
|
-
).toHaveFocus();
|
|
872
|
-
});
|
|
873
|
-
|
|
874
|
-
it(`when portal=${portal}, should allow navigating backwards when the popover is open`, async () => {
|
|
875
|
-
// Arrange
|
|
876
|
-
render(
|
|
877
|
-
<>
|
|
878
|
-
<Button>Prev focusable element outside</Button>
|
|
879
|
-
<Popover
|
|
880
|
-
onClose={jest.fn()}
|
|
881
|
-
portal={portal}
|
|
882
|
-
content={
|
|
883
|
-
<PopoverContent
|
|
884
|
-
title="Popover title"
|
|
885
|
-
content="content"
|
|
886
|
-
actions={<Button>Button inside popover</Button>}
|
|
887
|
-
/>
|
|
888
|
-
}
|
|
889
|
-
>
|
|
890
|
-
<Button>Open default popover</Button>
|
|
891
|
-
</Popover>
|
|
892
|
-
<Button>Next focusable element outside</Button>
|
|
893
|
-
</>,
|
|
894
|
-
);
|
|
895
|
-
|
|
896
|
-
// Open the popover
|
|
897
|
-
await userEvent.click(
|
|
898
|
-
await screen.findByRole("button", {
|
|
899
|
-
name: "Open default popover",
|
|
900
|
-
}),
|
|
901
|
-
);
|
|
902
|
-
|
|
903
|
-
// Wait for the popover to be open.
|
|
904
|
-
await screen.findByRole("dialog");
|
|
905
|
-
|
|
906
|
-
// At this point, the focus moves to the focusable element inside
|
|
907
|
-
// the popover, so we need to move the focus back to the trigger
|
|
908
|
-
// element.
|
|
909
|
-
await userEvent.tab({shift: true});
|
|
910
|
-
|
|
911
|
-
// Focus on the first element in the document
|
|
912
|
-
await userEvent.tab({shift: true});
|
|
913
|
-
|
|
914
|
-
// Focus on the document body
|
|
915
|
-
await userEvent.tab({shift: true});
|
|
916
|
-
|
|
917
|
-
// Focus on the last element in the document
|
|
918
|
-
await userEvent.tab({shift: true});
|
|
919
|
-
|
|
920
|
-
// Act
|
|
921
|
-
// Focus again on element inside the popover.
|
|
922
|
-
await userEvent.tab({shift: true});
|
|
923
|
-
|
|
924
|
-
// Assert
|
|
925
|
-
expect(
|
|
926
|
-
await screen.findByRole("button", {
|
|
927
|
-
name: "Button inside popover",
|
|
928
|
-
}),
|
|
929
|
-
).toHaveFocus();
|
|
930
|
-
});
|
|
931
|
-
});
|
|
932
|
-
});
|