@khanacademy/wonder-blocks-dropdown 2.6.7 → 2.6.8
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 +6 -0
- package/dist/es/index.js +202 -79
- package/dist/index.js +310 -174
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +1 -1
- package/src/__tests__/generated-snapshot.test.js +1 -1
- package/src/components/__tests__/action-menu.test.js +126 -166
- package/src/components/__tests__/dropdown-core-virtualized.test.js +20 -35
- package/src/components/__tests__/dropdown-core.test.js +724 -652
- package/src/components/__tests__/dropdown-popper.test.js +51 -0
- package/src/components/__tests__/multi-select.test.js +717 -518
- package/src/components/__tests__/single-select.test.js +52 -41
- package/src/components/action-item.js +2 -0
- package/src/components/dropdown-core.js +159 -90
- package/src/components/dropdown-popper.js +105 -0
- package/src/components/single-select.md +1 -1
- package/src/components/single-select.stories.js +49 -1
|
@@ -1,42 +1,515 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import "jest-enzyme";
|
|
3
|
+
import {fireEvent, render, screen} from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
6
5
|
|
|
7
6
|
import OptionItem from "../option-item.js";
|
|
8
7
|
import SearchTextInput from "../search-text-input.js";
|
|
9
8
|
import DropdownCore from "../dropdown-core.js";
|
|
10
|
-
import {keyCodes} from "../../util/constants.js";
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
const items = [
|
|
11
|
+
{
|
|
12
|
+
component: (
|
|
13
|
+
<OptionItem testId="item-0" label="item 0" value="0" key="0" />
|
|
14
|
+
),
|
|
15
|
+
focusable: true,
|
|
16
|
+
populatedProps: {},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
component: (
|
|
20
|
+
<OptionItem testId="item-1" label="item 1" value="1" key="1" />
|
|
21
|
+
),
|
|
22
|
+
focusable: true,
|
|
23
|
+
populatedProps: {},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
component: (
|
|
27
|
+
<OptionItem testId="item-2" label="item 2" value="2" key="2" />
|
|
28
|
+
),
|
|
29
|
+
focusable: true,
|
|
30
|
+
populatedProps: {},
|
|
31
|
+
},
|
|
32
|
+
];
|
|
18
33
|
|
|
19
34
|
describe("DropdownCore", () => {
|
|
20
|
-
|
|
35
|
+
it("should throw for invalid role", () => {
|
|
36
|
+
// Arrange
|
|
37
|
+
// Passing an invalid role will throw an error.
|
|
38
|
+
jest.spyOn(console, "error").mockImplementation(() => {});
|
|
21
39
|
|
|
22
|
-
|
|
40
|
+
// Act
|
|
41
|
+
const underTest = () =>
|
|
42
|
+
render(
|
|
43
|
+
<div>
|
|
44
|
+
<button data-test-id="external-button" />
|
|
45
|
+
<DropdownCore
|
|
46
|
+
initialFocusedIndex={0}
|
|
47
|
+
// mock the items
|
|
48
|
+
items={[]}
|
|
49
|
+
role={("invalid": any)}
|
|
50
|
+
open={true}
|
|
51
|
+
// mock the opener elements
|
|
52
|
+
opener={<button />}
|
|
53
|
+
openerElement={null}
|
|
54
|
+
onOpenChanged={jest.fn()}
|
|
55
|
+
/>
|
|
56
|
+
</div>,
|
|
57
|
+
);
|
|
23
58
|
|
|
24
|
-
|
|
25
|
-
|
|
59
|
+
// Assert
|
|
60
|
+
expect(underTest).toThrow();
|
|
61
|
+
});
|
|
26
62
|
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
it("focus on the correct option", () => {
|
|
64
|
+
// Arrange
|
|
65
|
+
render(
|
|
66
|
+
<DropdownCore
|
|
67
|
+
initialFocusedIndex={0}
|
|
68
|
+
// mock the items
|
|
69
|
+
items={items}
|
|
70
|
+
role="listbox"
|
|
71
|
+
open={true}
|
|
72
|
+
// mock the opener elements
|
|
73
|
+
opener={<button />}
|
|
74
|
+
openerElement={null}
|
|
75
|
+
onOpenChanged={jest.fn()}
|
|
76
|
+
/>,
|
|
35
77
|
);
|
|
36
78
|
|
|
79
|
+
// Act
|
|
80
|
+
const item = screen.getByTestId("item-0");
|
|
81
|
+
|
|
82
|
+
// Assert
|
|
83
|
+
expect(item).toHaveFocus();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("handles basic keyboard navigation as expected", () => {
|
|
87
|
+
// Arrange
|
|
37
88
|
const dummyOpener = <button />;
|
|
38
89
|
const openChanged = jest.fn();
|
|
39
|
-
|
|
90
|
+
|
|
91
|
+
render(
|
|
92
|
+
<DropdownCore
|
|
93
|
+
initialFocusedIndex={0}
|
|
94
|
+
// mock the items
|
|
95
|
+
items={items}
|
|
96
|
+
role="listbox"
|
|
97
|
+
open={true}
|
|
98
|
+
// mock the opener elements
|
|
99
|
+
opener={dummyOpener}
|
|
100
|
+
openerElement={null}
|
|
101
|
+
onOpenChanged={openChanged}
|
|
102
|
+
/>,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Act
|
|
106
|
+
// navigate down two times
|
|
107
|
+
userEvent.keyboard("{arrowdown}"); // 0 -> 1
|
|
108
|
+
userEvent.keyboard("{arrowdown}"); // 1 -> 2
|
|
109
|
+
|
|
110
|
+
// Assert
|
|
111
|
+
expect(screen.queryByTestId("item-2")).toHaveFocus();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("keyboard works backwards as expected", () => {
|
|
115
|
+
// Arrange
|
|
116
|
+
render(
|
|
117
|
+
<DropdownCore
|
|
118
|
+
initialFocusedIndex={0}
|
|
119
|
+
// mock the items
|
|
120
|
+
items={items}
|
|
121
|
+
role="listbox"
|
|
122
|
+
open={true}
|
|
123
|
+
// mock the opener elements
|
|
124
|
+
opener={<button />}
|
|
125
|
+
openerElement={null}
|
|
126
|
+
onOpenChanged={jest.fn()}
|
|
127
|
+
/>,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Act
|
|
131
|
+
// navigate down tree times
|
|
132
|
+
userEvent.keyboard("{arrowdown}"); // 0 -> 1
|
|
133
|
+
userEvent.keyboard("{arrowdown}"); // 1 -> 2
|
|
134
|
+
userEvent.keyboard("{arrowdown}"); // 2 -> 0
|
|
135
|
+
|
|
136
|
+
// navigate up back two times
|
|
137
|
+
userEvent.keyboard("{arrowup}"); // 0 -> 2
|
|
138
|
+
userEvent.keyboard("{arrowup}"); // 2 -> 1
|
|
139
|
+
|
|
140
|
+
// Assert
|
|
141
|
+
expect(screen.getByTestId("item-1")).toHaveFocus();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("closes on tab as expected", () => {
|
|
145
|
+
// Arrange
|
|
146
|
+
const handleOpenChangedMock = jest.fn();
|
|
147
|
+
|
|
148
|
+
render(
|
|
149
|
+
<DropdownCore
|
|
150
|
+
initialFocusedIndex={0}
|
|
151
|
+
// mock the items
|
|
152
|
+
items={items}
|
|
153
|
+
role="listbox"
|
|
154
|
+
open={true}
|
|
155
|
+
// mock the opener elements
|
|
156
|
+
opener={<button />}
|
|
157
|
+
openerElement={null}
|
|
158
|
+
onOpenChanged={handleOpenChangedMock}
|
|
159
|
+
/>,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Act
|
|
163
|
+
// close the dropdown by tabbing out
|
|
164
|
+
userEvent.tab();
|
|
165
|
+
|
|
166
|
+
// Assert
|
|
167
|
+
expect(handleOpenChangedMock).toHaveBeenNthCalledWith(1, false);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("closes on escape as expected", () => {
|
|
171
|
+
// Arrange
|
|
172
|
+
const handleOpenChangedMock = jest.fn();
|
|
173
|
+
|
|
174
|
+
render(
|
|
175
|
+
<DropdownCore
|
|
176
|
+
initialFocusedIndex={0}
|
|
177
|
+
// mock the items
|
|
178
|
+
items={items}
|
|
179
|
+
role="listbox"
|
|
180
|
+
open={true}
|
|
181
|
+
// mock the opener elements
|
|
182
|
+
opener={<button />}
|
|
183
|
+
openerElement={null}
|
|
184
|
+
onOpenChanged={handleOpenChangedMock}
|
|
185
|
+
/>,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Act
|
|
189
|
+
// close the dropdown by pressing "Escape"
|
|
190
|
+
userEvent.keyboard("{escape}");
|
|
191
|
+
|
|
192
|
+
// Assert
|
|
193
|
+
expect(handleOpenChangedMock).toHaveBeenNthCalledWith(1, false);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("closes on external mouse click", () => {
|
|
197
|
+
// Arrange
|
|
198
|
+
const handleOpenChangedMock = jest.fn();
|
|
199
|
+
|
|
200
|
+
const {container} = render(
|
|
201
|
+
<div>
|
|
202
|
+
<button data-test-id="external-button" />
|
|
203
|
+
<DropdownCore
|
|
204
|
+
initialFocusedIndex={0}
|
|
205
|
+
// mock the items
|
|
206
|
+
items={items}
|
|
207
|
+
role="listbox"
|
|
208
|
+
open={true}
|
|
209
|
+
// mock the opener elements
|
|
210
|
+
opener={<button />}
|
|
211
|
+
openerElement={null}
|
|
212
|
+
onOpenChanged={handleOpenChangedMock}
|
|
213
|
+
/>
|
|
214
|
+
</div>,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Act
|
|
218
|
+
// close the dropdown by clicking outside the dropdown
|
|
219
|
+
userEvent.click(container);
|
|
220
|
+
|
|
221
|
+
// Assert
|
|
222
|
+
expect(handleOpenChangedMock).toHaveBeenNthCalledWith(1, false);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("doesn't close on external mouse click if already closed", () => {
|
|
226
|
+
// Arrange
|
|
227
|
+
const handleOpenChangedMock = jest.fn();
|
|
228
|
+
|
|
229
|
+
render(
|
|
230
|
+
<DropdownCore
|
|
231
|
+
initialFocusedIndex={0}
|
|
232
|
+
// mock the items
|
|
233
|
+
items={items}
|
|
234
|
+
role="listbox"
|
|
235
|
+
open={false}
|
|
236
|
+
// mock the opener elements
|
|
237
|
+
opener={<button />}
|
|
238
|
+
openerElement={null}
|
|
239
|
+
onOpenChanged={handleOpenChangedMock}
|
|
240
|
+
/>,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Act
|
|
244
|
+
// click outside the dropdown
|
|
245
|
+
userEvent.click(document.body);
|
|
246
|
+
|
|
247
|
+
// Assert
|
|
248
|
+
expect(handleOpenChangedMock).toHaveBeenCalledTimes(0);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("opens on down key as expected", () => {
|
|
252
|
+
// Arrange
|
|
253
|
+
const handleOpenChangedMock = jest.fn();
|
|
254
|
+
const opener = <button data-test-id="opener" />;
|
|
255
|
+
|
|
256
|
+
render(
|
|
257
|
+
<DropdownCore
|
|
258
|
+
initialFocusedIndex={0}
|
|
259
|
+
// mock the items
|
|
260
|
+
items={items}
|
|
261
|
+
role="listbox"
|
|
262
|
+
open={false}
|
|
263
|
+
// mock the opener elements
|
|
264
|
+
opener={opener}
|
|
265
|
+
openerElement={null}
|
|
266
|
+
onOpenChanged={handleOpenChangedMock}
|
|
267
|
+
/>,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const openerElement = screen.getByTestId("opener");
|
|
271
|
+
openerElement.focus();
|
|
272
|
+
|
|
273
|
+
// Act
|
|
274
|
+
userEvent.keyboard("{arrowdown}");
|
|
275
|
+
|
|
276
|
+
// Assert
|
|
277
|
+
expect(handleOpenChangedMock).toHaveBeenNthCalledWith(1, true);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("selects correct item when starting off at an undefined index", () => {
|
|
281
|
+
// Arrange
|
|
282
|
+
render(
|
|
283
|
+
<DropdownCore
|
|
284
|
+
initialFocusedIndex={undefined}
|
|
285
|
+
// mock the items
|
|
286
|
+
items={items}
|
|
287
|
+
role="listbox"
|
|
288
|
+
open={true}
|
|
289
|
+
// mock the opener elements
|
|
290
|
+
opener={<button />}
|
|
291
|
+
openerElement={null}
|
|
292
|
+
onOpenChanged={jest.fn()}
|
|
293
|
+
/>,
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// Assert
|
|
297
|
+
expect(screen.queryByTestId("item-0")).toHaveFocus();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("selects correct item when starting off at an undefined index and a searchbox", () => {
|
|
301
|
+
// Arrange
|
|
302
|
+
render(
|
|
303
|
+
<DropdownCore
|
|
304
|
+
initialFocusedIndex={undefined}
|
|
305
|
+
searchText=""
|
|
306
|
+
// mock the items
|
|
307
|
+
items={[
|
|
308
|
+
{
|
|
309
|
+
component: (
|
|
310
|
+
<SearchTextInput
|
|
311
|
+
testId="item-0"
|
|
312
|
+
key="search-text-input"
|
|
313
|
+
onChange={jest.fn()}
|
|
314
|
+
searchText={""}
|
|
315
|
+
/>
|
|
316
|
+
),
|
|
317
|
+
focusable: true,
|
|
318
|
+
populatedProps: {},
|
|
319
|
+
},
|
|
320
|
+
]}
|
|
321
|
+
role="listbox"
|
|
322
|
+
open={true}
|
|
323
|
+
// mock the opener elements
|
|
324
|
+
opener={<button />}
|
|
325
|
+
openerElement={null}
|
|
326
|
+
onOpenChanged={jest.fn()}
|
|
327
|
+
/>,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Act
|
|
331
|
+
const firstItem = screen.queryByTestId("item-0");
|
|
332
|
+
|
|
333
|
+
// Assert
|
|
334
|
+
expect(firstItem).toHaveFocus();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("selects correct item when starting off at a different index and a searchbox", () => {
|
|
338
|
+
// Arrange
|
|
339
|
+
render(
|
|
340
|
+
<DropdownCore
|
|
341
|
+
initialFocusedIndex={1}
|
|
342
|
+
searchText=""
|
|
343
|
+
// mock the items
|
|
344
|
+
items={[
|
|
345
|
+
{
|
|
346
|
+
component: (
|
|
347
|
+
<SearchTextInput
|
|
348
|
+
testId="search"
|
|
349
|
+
key="search-text-input"
|
|
350
|
+
onChange={jest.fn()}
|
|
351
|
+
searchText={""}
|
|
352
|
+
/>
|
|
353
|
+
),
|
|
354
|
+
focusable: true,
|
|
355
|
+
populatedProps: {},
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
component: (
|
|
359
|
+
<OptionItem
|
|
360
|
+
testId="item-0"
|
|
361
|
+
label="item 1"
|
|
362
|
+
value="1"
|
|
363
|
+
key="1"
|
|
364
|
+
/>
|
|
365
|
+
),
|
|
366
|
+
focusable: true,
|
|
367
|
+
populatedProps: {},
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
component: (
|
|
371
|
+
<OptionItem
|
|
372
|
+
testId="item-1"
|
|
373
|
+
label="item 2"
|
|
374
|
+
value="2"
|
|
375
|
+
key="2"
|
|
376
|
+
/>
|
|
377
|
+
),
|
|
378
|
+
focusable: true,
|
|
379
|
+
populatedProps: {},
|
|
380
|
+
},
|
|
381
|
+
]}
|
|
382
|
+
role="listbox"
|
|
383
|
+
open={true}
|
|
384
|
+
// mock the opener elements
|
|
385
|
+
opener={<button />}
|
|
386
|
+
openerElement={null}
|
|
387
|
+
onOpenChanged={jest.fn()}
|
|
388
|
+
/>,
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
// Act
|
|
392
|
+
const firstItem = screen.queryByTestId("item-0");
|
|
393
|
+
|
|
394
|
+
// Assert
|
|
395
|
+
expect(firstItem).toHaveFocus();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("selects correct item when starting off at a different index", () => {
|
|
399
|
+
// Arrange
|
|
400
|
+
render(
|
|
401
|
+
<DropdownCore
|
|
402
|
+
initialFocusedIndex={2}
|
|
403
|
+
// mock the items
|
|
404
|
+
items={items}
|
|
405
|
+
role="listbox"
|
|
406
|
+
open={true}
|
|
407
|
+
// mock the opener elements
|
|
408
|
+
opener={<button />}
|
|
409
|
+
openerElement={null}
|
|
410
|
+
onOpenChanged={jest.fn()}
|
|
411
|
+
/>,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
// Act
|
|
415
|
+
// navigate down
|
|
416
|
+
userEvent.keyboard("{arrowdown}"); // 2 -> 0
|
|
417
|
+
|
|
418
|
+
// Assert
|
|
419
|
+
expect(screen.getByTestId("item-0")).toHaveFocus();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("focuses correct item with clicking/pressing with initial focused of not 0", () => {
|
|
423
|
+
// Arrange
|
|
424
|
+
render(
|
|
425
|
+
<DropdownCore
|
|
426
|
+
initialFocusedIndex={2}
|
|
427
|
+
// mock the items
|
|
428
|
+
items={items}
|
|
429
|
+
role="listbox"
|
|
430
|
+
open={true}
|
|
431
|
+
// mock the opener elements
|
|
432
|
+
opener={<button />}
|
|
433
|
+
openerElement={null}
|
|
434
|
+
onOpenChanged={jest.fn()}
|
|
435
|
+
/>,
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// Act
|
|
439
|
+
userEvent.click(screen.getByTestId("item-1"));
|
|
440
|
+
// navigate down
|
|
441
|
+
userEvent.keyboard("{arrowdown}"); // 1 -> 2
|
|
442
|
+
|
|
443
|
+
// Assert
|
|
444
|
+
expect(screen.getByTestId("item-2")).toHaveFocus();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("focuses correct item with a disabled item", () => {
|
|
448
|
+
// Arrange
|
|
449
|
+
render(
|
|
450
|
+
<DropdownCore
|
|
451
|
+
initialFocusedIndex={0}
|
|
452
|
+
// mock the items
|
|
453
|
+
items={[
|
|
454
|
+
{
|
|
455
|
+
component: (
|
|
456
|
+
<OptionItem
|
|
457
|
+
testId="item-0"
|
|
458
|
+
label="item 0"
|
|
459
|
+
value="0"
|
|
460
|
+
key="0"
|
|
461
|
+
/>
|
|
462
|
+
),
|
|
463
|
+
focusable: true,
|
|
464
|
+
populatedProps: {},
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
component: (
|
|
468
|
+
<OptionItem
|
|
469
|
+
testId="item-1"
|
|
470
|
+
label="item 1"
|
|
471
|
+
value="1"
|
|
472
|
+
key="1"
|
|
473
|
+
disabled={true}
|
|
474
|
+
/>
|
|
475
|
+
),
|
|
476
|
+
focusable: false,
|
|
477
|
+
populatedProps: {},
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
component: (
|
|
481
|
+
<OptionItem
|
|
482
|
+
testId="item-2"
|
|
483
|
+
label="item 2"
|
|
484
|
+
value="2"
|
|
485
|
+
key="2"
|
|
486
|
+
/>
|
|
487
|
+
),
|
|
488
|
+
focusable: true,
|
|
489
|
+
populatedProps: {},
|
|
490
|
+
},
|
|
491
|
+
]}
|
|
492
|
+
role="listbox"
|
|
493
|
+
open={true}
|
|
494
|
+
// mock the opener elements
|
|
495
|
+
opener={<button data-test-id="opener" />}
|
|
496
|
+
openerElement={null}
|
|
497
|
+
onOpenChanged={jest.fn()}
|
|
498
|
+
/>,
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
// Act
|
|
502
|
+
// navigate down
|
|
503
|
+
userEvent.keyboard("{arrowdown}"); // 0 -> 2 (1 is disabled)
|
|
504
|
+
|
|
505
|
+
// Assert
|
|
506
|
+
expect(screen.getByTestId("item-2")).toHaveFocus();
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it("calls correct onclick for an option item", () => {
|
|
510
|
+
// Arrange
|
|
511
|
+
const onClick1 = jest.fn();
|
|
512
|
+
render(
|
|
40
513
|
<DropdownCore
|
|
41
514
|
initialFocusedIndex={0}
|
|
42
515
|
// mock the items
|
|
@@ -44,10 +517,10 @@ describe("DropdownCore", () => {
|
|
|
44
517
|
{
|
|
45
518
|
component: (
|
|
46
519
|
<OptionItem
|
|
47
|
-
testId="item-0"
|
|
48
520
|
label="item 0"
|
|
49
521
|
value="0"
|
|
50
522
|
key="0"
|
|
523
|
+
testId="item-0"
|
|
51
524
|
/>
|
|
52
525
|
),
|
|
53
526
|
focusable: true,
|
|
@@ -56,10 +529,11 @@ describe("DropdownCore", () => {
|
|
|
56
529
|
{
|
|
57
530
|
component: (
|
|
58
531
|
<OptionItem
|
|
59
|
-
testId="item-1"
|
|
60
532
|
label="item 1"
|
|
61
533
|
value="1"
|
|
62
534
|
key="1"
|
|
535
|
+
testId="item-1"
|
|
536
|
+
onClick={onClick1}
|
|
63
537
|
/>
|
|
64
538
|
),
|
|
65
539
|
focusable: true,
|
|
@@ -68,8 +542,8 @@ describe("DropdownCore", () => {
|
|
|
68
542
|
{
|
|
69
543
|
component: (
|
|
70
544
|
<OptionItem
|
|
71
|
-
testId="item-2"
|
|
72
545
|
label="item 2"
|
|
546
|
+
testId="item-2"
|
|
73
547
|
value="2"
|
|
74
548
|
key="2"
|
|
75
549
|
/>
|
|
@@ -79,682 +553,280 @@ describe("DropdownCore", () => {
|
|
|
79
553
|
},
|
|
80
554
|
]}
|
|
81
555
|
role="listbox"
|
|
82
|
-
|
|
83
|
-
open={false}
|
|
556
|
+
open={true}
|
|
84
557
|
// mock the opener elements
|
|
85
|
-
opener={
|
|
558
|
+
opener={<button />}
|
|
86
559
|
openerElement={null}
|
|
87
|
-
onOpenChanged={
|
|
560
|
+
onOpenChanged={jest.fn()}
|
|
88
561
|
/>,
|
|
89
562
|
);
|
|
90
|
-
});
|
|
91
563
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
// have cleared the timeout we actually set!)
|
|
98
|
-
unmountAll();
|
|
99
|
-
jest.restoreAllMocks();
|
|
564
|
+
// Act
|
|
565
|
+
userEvent.click(screen.getByTestId("item-1"));
|
|
566
|
+
|
|
567
|
+
// Assert
|
|
568
|
+
expect(onClick1).toHaveBeenCalledTimes(1);
|
|
100
569
|
});
|
|
101
570
|
|
|
102
|
-
it("
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
initialFocusedIndex: 0,
|
|
106
|
-
onOpenChanged: (open) => handleOpen(open),
|
|
107
|
-
open: true,
|
|
108
|
-
});
|
|
571
|
+
it("Displays no results when no items are left with filter", () => {
|
|
572
|
+
// Arrange
|
|
573
|
+
const handleSearchTextChanged = jest.fn();
|
|
109
574
|
|
|
110
|
-
|
|
111
|
-
|
|
575
|
+
// Act
|
|
576
|
+
render(
|
|
577
|
+
<DropdownCore
|
|
578
|
+
onSearchTextChanged={handleSearchTextChanged}
|
|
579
|
+
searchText="ab"
|
|
580
|
+
items={[
|
|
581
|
+
{
|
|
582
|
+
component: (
|
|
583
|
+
<SearchTextInput
|
|
584
|
+
key="search-text-input"
|
|
585
|
+
onChange={handleSearchTextChanged}
|
|
586
|
+
searchText={""}
|
|
587
|
+
/>
|
|
588
|
+
),
|
|
589
|
+
focusable: true,
|
|
590
|
+
populatedProps: {},
|
|
591
|
+
},
|
|
592
|
+
]}
|
|
593
|
+
role="listbox"
|
|
594
|
+
open={true}
|
|
595
|
+
// mock the opener elements
|
|
596
|
+
opener={<button />}
|
|
597
|
+
openerElement={null}
|
|
598
|
+
onOpenChanged={jest.fn()}
|
|
599
|
+
/>,
|
|
600
|
+
);
|
|
112
601
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
602
|
+
// Assert
|
|
603
|
+
expect(
|
|
604
|
+
screen.getByTestId("dropdown-core-no-results"),
|
|
605
|
+
).toBeInTheDocument();
|
|
606
|
+
});
|
|
118
607
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
jest.
|
|
122
|
-
|
|
608
|
+
it("SearchTextInput should be focused when opened", () => {
|
|
609
|
+
// Arrange
|
|
610
|
+
const handleSearchTextChanged = jest.fn();
|
|
611
|
+
const handleOpen = jest.fn();
|
|
123
612
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
613
|
+
// Act
|
|
614
|
+
render(
|
|
615
|
+
<DropdownCore
|
|
616
|
+
initialFocusedIndex={0}
|
|
617
|
+
onOpenChanged={handleOpen}
|
|
618
|
+
onSearchTextChanged={handleSearchTextChanged}
|
|
619
|
+
searchText="ab"
|
|
620
|
+
items={[
|
|
621
|
+
{
|
|
622
|
+
component: (
|
|
623
|
+
<SearchTextInput
|
|
624
|
+
testId="item-0"
|
|
625
|
+
key="search-text-input"
|
|
626
|
+
onChange={handleSearchTextChanged}
|
|
627
|
+
searchText={""}
|
|
628
|
+
/>
|
|
629
|
+
),
|
|
630
|
+
focusable: true,
|
|
631
|
+
populatedProps: {},
|
|
632
|
+
},
|
|
633
|
+
]}
|
|
634
|
+
role="listbox"
|
|
635
|
+
open={true}
|
|
636
|
+
// mock the opener elements
|
|
637
|
+
opener={<button />}
|
|
638
|
+
openerElement={null}
|
|
639
|
+
/>,
|
|
640
|
+
);
|
|
128
641
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
dropdown.simulate("keydown", {keyCode: keyCodes.up});
|
|
132
|
-
dropdown.simulate("keyup", {keyCode: keyCodes.up});
|
|
133
|
-
jest.runAllTimers();
|
|
134
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 2));
|
|
135
|
-
// to the previous one
|
|
136
|
-
dropdown.simulate("keydown", {keyCode: keyCodes.up});
|
|
137
|
-
dropdown.simulate("keyup", {keyCode: keyCodes.up});
|
|
138
|
-
jest.runAllTimers();
|
|
139
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 1));
|
|
642
|
+
// Assert
|
|
643
|
+
expect(screen.getByPlaceholderText("Filter")).toHaveFocus();
|
|
140
644
|
});
|
|
141
645
|
|
|
142
|
-
it("
|
|
646
|
+
it("When SearchTextInput has input and focused, tab key should not close the select", () => {
|
|
647
|
+
// Arrange
|
|
648
|
+
const handleSearchTextChanged = jest.fn();
|
|
143
649
|
const handleOpen = jest.fn();
|
|
144
|
-
dropdown.setProps({
|
|
145
|
-
onOpenChanged: (open) => handleOpen(open),
|
|
146
|
-
open: true,
|
|
147
|
-
});
|
|
148
650
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
651
|
+
render(
|
|
652
|
+
<DropdownCore
|
|
653
|
+
onOpenChanged={handleOpen}
|
|
654
|
+
onSearchTextChanged={handleSearchTextChanged}
|
|
655
|
+
searchText="ab"
|
|
656
|
+
items={[
|
|
657
|
+
{
|
|
658
|
+
component: (
|
|
659
|
+
<SearchTextInput
|
|
660
|
+
testId="item-0"
|
|
661
|
+
key="search-text-input"
|
|
662
|
+
onChange={handleSearchTextChanged}
|
|
663
|
+
searchText={""}
|
|
664
|
+
/>
|
|
665
|
+
),
|
|
666
|
+
focusable: true,
|
|
667
|
+
populatedProps: {},
|
|
668
|
+
},
|
|
669
|
+
]}
|
|
670
|
+
role="listbox"
|
|
671
|
+
open={true}
|
|
672
|
+
// mock the opener elements
|
|
673
|
+
opener={<button />}
|
|
674
|
+
openerElement={null}
|
|
675
|
+
/>,
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
// Act
|
|
679
|
+
userEvent.keyboard("{tab}");
|
|
159
680
|
|
|
681
|
+
// Assert
|
|
160
682
|
expect(handleOpen).toHaveBeenCalledTimes(0);
|
|
683
|
+
expect(screen.getByTestId("item-0")).toHaveFocus();
|
|
161
684
|
});
|
|
162
685
|
|
|
163
|
-
it("
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
open: true,
|
|
168
|
-
});
|
|
686
|
+
it("When SearchTextInput exists and focused, space key pressing should be allowed", () => {
|
|
687
|
+
// Arrange
|
|
688
|
+
const handleSearchTextChanged = jest.fn();
|
|
689
|
+
const preventDefaultMock = jest.fn();
|
|
169
690
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
691
|
+
render(
|
|
692
|
+
<DropdownCore
|
|
693
|
+
onSearchTextChanged={jest.fn()}
|
|
694
|
+
searchText="ab"
|
|
695
|
+
items={[
|
|
696
|
+
{
|
|
697
|
+
component: (
|
|
698
|
+
<SearchTextInput
|
|
699
|
+
testId="item-0"
|
|
700
|
+
key="search-text-input"
|
|
701
|
+
onChange={handleSearchTextChanged}
|
|
702
|
+
searchText={"ab"}
|
|
703
|
+
/>
|
|
704
|
+
),
|
|
705
|
+
focusable: true,
|
|
706
|
+
populatedProps: {},
|
|
707
|
+
},
|
|
708
|
+
]}
|
|
709
|
+
role="listbox"
|
|
710
|
+
open={true}
|
|
711
|
+
// mock the opener elements
|
|
712
|
+
opener={<button />}
|
|
713
|
+
openerElement={null}
|
|
714
|
+
onOpenChanged={jest.fn()}
|
|
715
|
+
/>,
|
|
716
|
+
);
|
|
180
717
|
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
718
|
+
// Act
|
|
719
|
+
const searchInput = screen.getByTestId("item-0");
|
|
720
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
721
|
+
fireEvent.keyDown(searchInput, {
|
|
722
|
+
keyCode: 32,
|
|
723
|
+
preventDefault: preventDefaultMock,
|
|
724
|
+
});
|
|
725
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
726
|
+
fireEvent.keyUp(searchInput, {
|
|
727
|
+
keyCode: 32,
|
|
728
|
+
preventDefault: preventDefaultMock,
|
|
186
729
|
});
|
|
187
730
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
expect(handleOpen).toHaveBeenCalledTimes(1);
|
|
192
|
-
expect(handleOpen.mock.calls[0][0]).toBe(false);
|
|
731
|
+
// Assert
|
|
732
|
+
expect(preventDefaultMock).toHaveBeenCalledTimes(0);
|
|
193
733
|
});
|
|
194
734
|
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
ReactDOM.render(
|
|
213
|
-
<div>
|
|
214
|
-
<h1 id="foo">DropdownCore test</h1>
|
|
735
|
+
describe("VirtualizedList", () => {
|
|
736
|
+
const optionItems = new Array(200).fill(null).map((_, i) => ({
|
|
737
|
+
component: (
|
|
738
|
+
<OptionItem
|
|
739
|
+
key={i}
|
|
740
|
+
value={(i + 1).toString()}
|
|
741
|
+
label={`Fruit # ${i + 1}`}
|
|
742
|
+
testId={`item-${i}`}
|
|
743
|
+
/>
|
|
744
|
+
),
|
|
745
|
+
focusable: true,
|
|
746
|
+
populatedProps: {},
|
|
747
|
+
}));
|
|
748
|
+
|
|
749
|
+
it("should render a virtualized list of options and focus on the search field", async () => {
|
|
750
|
+
// Arrange
|
|
751
|
+
render(
|
|
215
752
|
<DropdownCore
|
|
216
|
-
initialFocusedIndex={
|
|
753
|
+
initialFocusedIndex={undefined}
|
|
754
|
+
onSearchTextChanged={jest.fn()}
|
|
755
|
+
searchText=""
|
|
217
756
|
// mock the items
|
|
218
757
|
items={[
|
|
219
758
|
{
|
|
220
759
|
component: (
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
component: (
|
|
228
|
-
<OptionItem label="item 1" value="1" key="1" />
|
|
760
|
+
<SearchTextInput
|
|
761
|
+
testId="search-text-input"
|
|
762
|
+
key="search-text-input"
|
|
763
|
+
onChange={jest.fn()}
|
|
764
|
+
searchText={""}
|
|
765
|
+
/>
|
|
229
766
|
),
|
|
230
767
|
focusable: true,
|
|
231
768
|
populatedProps: {},
|
|
232
769
|
},
|
|
770
|
+
...optionItems,
|
|
771
|
+
]}
|
|
772
|
+
role="listbox"
|
|
773
|
+
open={true}
|
|
774
|
+
// mock the opener elements
|
|
775
|
+
opener={<button />}
|
|
776
|
+
openerElement={null}
|
|
777
|
+
onOpenChanged={jest.fn()}
|
|
778
|
+
/>,
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
await screen.findByRole("listbox");
|
|
782
|
+
|
|
783
|
+
// Act
|
|
784
|
+
const searchField = await screen.findByPlaceholderText("Filter");
|
|
785
|
+
|
|
786
|
+
// Assert
|
|
787
|
+
expect(searchField).toHaveFocus();
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
it("should focus on the item after clicking on it", async () => {
|
|
791
|
+
// Arrange
|
|
792
|
+
render(
|
|
793
|
+
<DropdownCore
|
|
794
|
+
initialFocusedIndex={undefined}
|
|
795
|
+
onSearchTextChanged={jest.fn()}
|
|
796
|
+
searchText=""
|
|
797
|
+
// mock the items
|
|
798
|
+
items={[
|
|
233
799
|
{
|
|
234
800
|
component: (
|
|
235
|
-
<
|
|
801
|
+
<SearchTextInput
|
|
802
|
+
testId="search-text-input"
|
|
803
|
+
key="search-text-input"
|
|
804
|
+
onChange={jest.fn()}
|
|
805
|
+
searchText={""}
|
|
806
|
+
/>
|
|
236
807
|
),
|
|
237
808
|
focusable: true,
|
|
238
809
|
populatedProps: {},
|
|
239
810
|
},
|
|
811
|
+
...optionItems,
|
|
240
812
|
]}
|
|
241
813
|
role="listbox"
|
|
242
|
-
light={false}
|
|
243
814
|
open={true}
|
|
244
815
|
// mock the opener elements
|
|
245
816
|
opener={<button />}
|
|
246
817
|
openerElement={null}
|
|
247
|
-
onOpenChanged={(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
container,
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
const title = document.querySelector("#foo");
|
|
254
|
-
if (!title) {
|
|
255
|
-
throw new Error("Couldn't find title");
|
|
256
|
-
}
|
|
257
|
-
const event = new MouseEvent("mouseup", {bubbles: true});
|
|
258
|
-
title.dispatchEvent(event);
|
|
259
|
-
|
|
260
|
-
expect(handleOpen).toHaveBeenCalledTimes(1);
|
|
261
|
-
expect(handleOpen.mock.calls[0][0]).toBe(false);
|
|
262
|
-
|
|
263
|
-
// cleanup
|
|
264
|
-
ReactDOM.unmountComponentAtNode(container);
|
|
265
|
-
if (!document.body) {
|
|
266
|
-
throw new Error("No document.body");
|
|
267
|
-
}
|
|
268
|
-
document.body.removeChild(container);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("doesn't close on external mouse click if already closed", () => {
|
|
272
|
-
const handleOpen = jest.fn();
|
|
273
|
-
dropdown.setProps({
|
|
274
|
-
onOpenChanged: (open) => handleOpen(open),
|
|
275
|
-
open: false,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const event = new MouseEvent("mouseup");
|
|
279
|
-
document.dispatchEvent(event);
|
|
280
|
-
|
|
281
|
-
expect(handleOpen).toHaveBeenCalledTimes(0);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
it("opens on down key as expected", () => {
|
|
285
|
-
const handleOpen = jest.fn();
|
|
286
|
-
dropdown.setProps({
|
|
287
|
-
onOpenChanged: (open) => handleOpen(open),
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
dropdown.simulate("keydown", {keyCode: keyCodes.down});
|
|
291
|
-
dropdown.simulate("keyup", {keyCode: keyCodes.down});
|
|
292
|
-
|
|
293
|
-
expect(handleOpen).toHaveBeenCalledTimes(1);
|
|
294
|
-
expect(handleOpen.mock.calls[0][0]).toBe(true);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it("selects correct item when starting off at an undefined index", () => {
|
|
298
|
-
const handleOpen = jest.fn();
|
|
299
|
-
dropdown.setProps({
|
|
300
|
-
initialFocusedIndex: undefined,
|
|
301
|
-
onOpenChanged: (open) => handleOpen(open),
|
|
302
|
-
open: true,
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
jest.runAllTimers();
|
|
306
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it("selects correct item when starting off at an undefined index and a searchbox", () => {
|
|
310
|
-
const handleOpen = jest.fn();
|
|
311
|
-
const handleSearchTextChanged = jest.fn();
|
|
312
|
-
dropdown.setProps({
|
|
313
|
-
initialFocusedIndex: undefined,
|
|
314
|
-
onOpenChanged: (open) => handleOpen(open),
|
|
315
|
-
searchText: "",
|
|
316
|
-
items: [
|
|
317
|
-
{
|
|
318
|
-
component: (
|
|
319
|
-
<SearchTextInput
|
|
320
|
-
testId="item-0"
|
|
321
|
-
key="search-text-input"
|
|
322
|
-
onChange={handleSearchTextChanged}
|
|
323
|
-
searchText={""}
|
|
324
|
-
/>
|
|
325
|
-
),
|
|
326
|
-
focusable: true,
|
|
327
|
-
populatedProps: {},
|
|
328
|
-
},
|
|
329
|
-
],
|
|
330
|
-
open: true,
|
|
331
|
-
});
|
|
332
|
-
jest.runAllTimers();
|
|
333
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it("selects correct item when starting off at a different index and a searchbox", () => {
|
|
337
|
-
const handleOpen = jest.fn();
|
|
338
|
-
const handleSearchTextChanged = jest.fn();
|
|
339
|
-
dropdown.setProps({
|
|
340
|
-
initialFocusedIndex: 1,
|
|
341
|
-
onOpenChanged: (open) => handleOpen(open),
|
|
342
|
-
searchText: "",
|
|
343
|
-
items: [
|
|
344
|
-
{
|
|
345
|
-
component: (
|
|
346
|
-
<SearchTextInput
|
|
347
|
-
testId="search"
|
|
348
|
-
key="search-text-input"
|
|
349
|
-
onChange={handleSearchTextChanged}
|
|
350
|
-
searchText={""}
|
|
351
|
-
/>
|
|
352
|
-
),
|
|
353
|
-
focusable: true,
|
|
354
|
-
populatedProps: {},
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
component: (
|
|
358
|
-
<OptionItem
|
|
359
|
-
testId="item-0"
|
|
360
|
-
label="item 1"
|
|
361
|
-
value="1"
|
|
362
|
-
key="1"
|
|
363
|
-
/>
|
|
364
|
-
),
|
|
365
|
-
focusable: false,
|
|
366
|
-
populatedProps: {},
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
component: (
|
|
370
|
-
<OptionItem
|
|
371
|
-
testId="item-1"
|
|
372
|
-
label="item 2"
|
|
373
|
-
value="2"
|
|
374
|
-
key="2"
|
|
375
|
-
/>
|
|
376
|
-
),
|
|
377
|
-
focusable: true,
|
|
378
|
-
populatedProps: {},
|
|
379
|
-
},
|
|
380
|
-
],
|
|
381
|
-
open: true,
|
|
382
|
-
});
|
|
383
|
-
jest.runAllTimers();
|
|
384
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 1));
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
it("selects correct item when starting off at a different index", () => {
|
|
388
|
-
const handleOpen = jest.fn();
|
|
389
|
-
dropdown.setProps({
|
|
390
|
-
initialFocusedIndex: 2,
|
|
391
|
-
onOpenChanged: (open) => handleOpen(open),
|
|
392
|
-
open: true,
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
jest.runAllTimers();
|
|
396
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 2));
|
|
397
|
-
|
|
398
|
-
// navigate down
|
|
399
|
-
dropdown.simulate("keydown", {keyCode: keyCodes.down});
|
|
400
|
-
dropdown.simulate("keyup", {keyCode: keyCodes.down});
|
|
401
|
-
jest.runAllTimers();
|
|
402
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
it("focuses correct item with clicking/pressing with initial focused of not 0", () => {
|
|
406
|
-
// Same as the previous test, expect initialFocusedIndex is 2 now
|
|
407
|
-
dropdown.setProps({
|
|
408
|
-
initialFocusedIndex: 2,
|
|
409
|
-
open: true,
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
const option1 = dropdown.find("OptionItem").at(1);
|
|
413
|
-
|
|
414
|
-
// Click on item at index 1
|
|
415
|
-
option1.simulate("click");
|
|
416
|
-
|
|
417
|
-
// should move to next item
|
|
418
|
-
dropdown.simulate("keydown", {keyCode: keyCodes.down});
|
|
419
|
-
dropdown.simulate("keyup", {keyCode: keyCodes.down});
|
|
420
|
-
jest.runAllTimers();
|
|
421
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 2));
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it("focuses correct item with a disabled item", () => {
|
|
425
|
-
dropdown.setProps({
|
|
426
|
-
initialFocusedIndex: 0,
|
|
427
|
-
items: [
|
|
428
|
-
{
|
|
429
|
-
component: (
|
|
430
|
-
<OptionItem
|
|
431
|
-
testId="item-0"
|
|
432
|
-
label="item 0"
|
|
433
|
-
value="0"
|
|
434
|
-
key="0"
|
|
435
|
-
/>
|
|
436
|
-
),
|
|
437
|
-
focusable: true,
|
|
438
|
-
populatedProps: {},
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
component: (
|
|
442
|
-
<OptionItem
|
|
443
|
-
testId="item-1"
|
|
444
|
-
label="item 1"
|
|
445
|
-
value="1"
|
|
446
|
-
key="1"
|
|
447
|
-
/>
|
|
448
|
-
),
|
|
449
|
-
focusable: false,
|
|
450
|
-
populatedProps: {},
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
component: (
|
|
454
|
-
<OptionItem
|
|
455
|
-
testId="item-2"
|
|
456
|
-
label="item 2"
|
|
457
|
-
value="2"
|
|
458
|
-
key="2"
|
|
459
|
-
/>
|
|
460
|
-
),
|
|
461
|
-
focusable: true,
|
|
462
|
-
populatedProps: {},
|
|
463
|
-
},
|
|
464
|
-
],
|
|
465
|
-
open: true,
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
jest.runAllTimers();
|
|
469
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
|
|
470
|
-
|
|
471
|
-
// Should select option2
|
|
472
|
-
dropdown.simulate("keydown", {keyCode: keyCodes.down});
|
|
473
|
-
dropdown.simulate("keyup", {keyCode: keyCodes.down});
|
|
474
|
-
jest.runAllTimers();
|
|
475
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 2));
|
|
476
|
-
|
|
477
|
-
// Should select option0
|
|
478
|
-
dropdown.simulate("keydown", {keyCode: keyCodes.down});
|
|
479
|
-
dropdown.simulate("keyup", {keyCode: keyCodes.down});
|
|
480
|
-
jest.runAllTimers();
|
|
481
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
it("focuses correct item after different items become focusable", () => {
|
|
485
|
-
dropdown.setProps({
|
|
486
|
-
initialFocusedIndex: 0,
|
|
487
|
-
open: true,
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
jest.runAllTimers();
|
|
491
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
|
|
492
|
-
|
|
493
|
-
// Should select option1
|
|
494
|
-
dropdown.simulate("keydown", {keyCode: keyCodes.down});
|
|
495
|
-
dropdown.simulate("keyup", {keyCode: keyCodes.down});
|
|
496
|
-
jest.runAllTimers();
|
|
497
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 1));
|
|
498
|
-
|
|
499
|
-
dropdown.setProps({
|
|
500
|
-
items: [
|
|
501
|
-
{
|
|
502
|
-
component: (
|
|
503
|
-
<OptionItem
|
|
504
|
-
testId="item-0"
|
|
505
|
-
label="item 0"
|
|
506
|
-
value="0"
|
|
507
|
-
key="0"
|
|
508
|
-
/>
|
|
509
|
-
),
|
|
510
|
-
focusable: false,
|
|
511
|
-
populatedProps: {},
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
component: (
|
|
515
|
-
<OptionItem
|
|
516
|
-
testId="item-1"
|
|
517
|
-
label="item 1"
|
|
518
|
-
value="1"
|
|
519
|
-
key="1"
|
|
520
|
-
/>
|
|
521
|
-
),
|
|
522
|
-
focusable: true,
|
|
523
|
-
populatedProps: {},
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
component: (
|
|
527
|
-
<OptionItem
|
|
528
|
-
testId="item-2"
|
|
529
|
-
label="item 2"
|
|
530
|
-
value="2"
|
|
531
|
-
key="2"
|
|
532
|
-
/>
|
|
533
|
-
),
|
|
534
|
-
focusable: true,
|
|
535
|
-
populatedProps: {},
|
|
536
|
-
},
|
|
537
|
-
],
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
// Should figure out that option1, which is now at index 0, is selected
|
|
541
|
-
jest.runAllTimers();
|
|
542
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 1));
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
it("focuses first item after currently focused item is no longer focusable", () => {
|
|
546
|
-
dropdown.setProps({
|
|
547
|
-
initialFocusedIndex: 0,
|
|
548
|
-
open: true,
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
jest.runAllTimers();
|
|
552
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
|
|
553
|
-
|
|
554
|
-
const option0 = dropdown.find("OptionItem").at(0);
|
|
555
|
-
option0.simulate("click");
|
|
556
|
-
jest.runAllTimers();
|
|
557
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
|
|
558
|
-
|
|
559
|
-
dropdown.setProps({
|
|
560
|
-
items: [
|
|
561
|
-
{
|
|
562
|
-
component: (
|
|
563
|
-
<OptionItem
|
|
564
|
-
testId="item-0"
|
|
565
|
-
label="item 0"
|
|
566
|
-
value="0"
|
|
567
|
-
key="0"
|
|
568
|
-
/>
|
|
569
|
-
),
|
|
570
|
-
focusable: false,
|
|
571
|
-
populatedProps: {},
|
|
572
|
-
},
|
|
573
|
-
{
|
|
574
|
-
component: (
|
|
575
|
-
<OptionItem
|
|
576
|
-
testId="item-1"
|
|
577
|
-
label="item 1"
|
|
578
|
-
value="1"
|
|
579
|
-
key="1"
|
|
580
|
-
/>
|
|
581
|
-
),
|
|
582
|
-
focusable: true,
|
|
583
|
-
populatedProps: {},
|
|
584
|
-
},
|
|
585
|
-
{
|
|
586
|
-
component: (
|
|
587
|
-
<OptionItem
|
|
588
|
-
testId="item-2"
|
|
589
|
-
label="item 2"
|
|
590
|
-
value="2"
|
|
591
|
-
key="2"
|
|
592
|
-
/>
|
|
593
|
-
),
|
|
594
|
-
focusable: true,
|
|
595
|
-
populatedProps: {},
|
|
596
|
-
},
|
|
597
|
-
],
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
// Should figure out that option1 is now selected
|
|
601
|
-
jest.runAllTimers();
|
|
602
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 1));
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
it("calls correct onclick for an option item", () => {
|
|
606
|
-
const onClick0 = jest.fn();
|
|
607
|
-
const onClick1 = jest.fn();
|
|
608
|
-
dropdown.setProps({
|
|
609
|
-
items: [
|
|
610
|
-
{
|
|
611
|
-
component: (
|
|
612
|
-
<OptionItem
|
|
613
|
-
label="item 0"
|
|
614
|
-
value="0"
|
|
615
|
-
key="0"
|
|
616
|
-
onClick={onClick0}
|
|
617
|
-
/>
|
|
618
|
-
),
|
|
619
|
-
focusable: true,
|
|
620
|
-
populatedProps: {},
|
|
621
|
-
},
|
|
622
|
-
{
|
|
623
|
-
component: (
|
|
624
|
-
<OptionItem
|
|
625
|
-
label="item 1"
|
|
626
|
-
value="1"
|
|
627
|
-
key="1"
|
|
628
|
-
onClick={onClick1}
|
|
629
|
-
/>
|
|
630
|
-
),
|
|
631
|
-
focusable: true,
|
|
632
|
-
populatedProps: {},
|
|
633
|
-
},
|
|
634
|
-
{
|
|
635
|
-
component: <OptionItem label="item 2" value="2" key="2" />,
|
|
636
|
-
focusable: true,
|
|
637
|
-
populatedProps: {},
|
|
638
|
-
},
|
|
639
|
-
],
|
|
640
|
-
open: true,
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
const option1 = dropdown.find("OptionItem").at(1);
|
|
644
|
-
|
|
645
|
-
option1.simulate("click");
|
|
646
|
-
expect(onClick1).toHaveBeenCalledTimes(1);
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
it("Displays no results when no items are left with filter", () => {
|
|
650
|
-
// Arrange
|
|
651
|
-
const handleSearchTextChanged = jest.fn();
|
|
818
|
+
onOpenChanged={jest.fn()}
|
|
819
|
+
/>,
|
|
820
|
+
);
|
|
652
821
|
|
|
653
|
-
|
|
654
|
-
dropdown.setProps({
|
|
655
|
-
onSearchTextChanged: (text) => handleSearchTextChanged(text),
|
|
656
|
-
searchText: "ab",
|
|
657
|
-
items: [
|
|
658
|
-
{
|
|
659
|
-
component: (
|
|
660
|
-
<SearchTextInput
|
|
661
|
-
key="search-text-input"
|
|
662
|
-
onChange={handleSearchTextChanged}
|
|
663
|
-
searchText={""}
|
|
664
|
-
/>
|
|
665
|
-
),
|
|
666
|
-
focusable: true,
|
|
667
|
-
populatedProps: {},
|
|
668
|
-
},
|
|
669
|
-
],
|
|
670
|
-
open: true,
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
// Assert
|
|
674
|
-
expect(dropdown).toContainMatchingElement(
|
|
675
|
-
`[data-test-id="dropdown-core-no-results"]`,
|
|
676
|
-
);
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
it("When SearchTextInput has input and focused, tab key should not close the select", () => {
|
|
680
|
-
// Arrange
|
|
681
|
-
const handleSearchTextChanged = jest.fn();
|
|
682
|
-
const handleOpen = jest.fn();
|
|
683
|
-
|
|
684
|
-
dropdown.setProps({
|
|
685
|
-
onOpenChanged: (open) => handleOpen(open),
|
|
686
|
-
onSearchTextChanged: (text) => handleSearchTextChanged(text),
|
|
687
|
-
searchText: "ab",
|
|
688
|
-
open: true,
|
|
689
|
-
items: [
|
|
690
|
-
{
|
|
691
|
-
component: (
|
|
692
|
-
<SearchTextInput
|
|
693
|
-
testId="item-0"
|
|
694
|
-
key="search-text-input"
|
|
695
|
-
onChange={handleSearchTextChanged}
|
|
696
|
-
searchText={""}
|
|
697
|
-
/>
|
|
698
|
-
),
|
|
699
|
-
focusable: true,
|
|
700
|
-
populatedProps: {},
|
|
701
|
-
},
|
|
702
|
-
],
|
|
703
|
-
});
|
|
704
|
-
// SearchTextInput should be focused
|
|
705
|
-
const searchInput = dropdown.find(SearchTextInput);
|
|
706
|
-
jest.runAllTimers();
|
|
707
|
-
|
|
708
|
-
expect(searchInput.state("focused")).toBe(true);
|
|
709
|
-
|
|
710
|
-
// Act
|
|
711
|
-
dropdown.simulate("keydown", {keyCode: keyCodes.tab});
|
|
712
|
-
jest.runAllTimers();
|
|
822
|
+
await screen.findByRole("listbox");
|
|
713
823
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
it("When SearchTextInput exists and focused, space key pressing should be allowed", () => {
|
|
720
|
-
// Arrange
|
|
721
|
-
const handleSearchTextChanged = jest.fn();
|
|
722
|
-
const preventDefaultMock = jest.fn();
|
|
723
|
-
dropdown.setProps({
|
|
724
|
-
onSearchTextChanged: (text) => handleSearchTextChanged(text),
|
|
725
|
-
searchText: "",
|
|
726
|
-
items: [
|
|
727
|
-
{
|
|
728
|
-
component: (
|
|
729
|
-
<SearchTextInput
|
|
730
|
-
testId="item-0"
|
|
731
|
-
key="search-text-input"
|
|
732
|
-
onChange={handleSearchTextChanged}
|
|
733
|
-
searchText={""}
|
|
734
|
-
/>
|
|
735
|
-
),
|
|
736
|
-
focusable: true,
|
|
737
|
-
populatedProps: {},
|
|
738
|
-
},
|
|
739
|
-
],
|
|
740
|
-
open: true,
|
|
741
|
-
});
|
|
742
|
-
// SearchTextInput should be focused
|
|
743
|
-
const searchInput = dropdown.find(SearchTextInput).find("input");
|
|
744
|
-
jest.runAllTimers();
|
|
745
|
-
expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
|
|
824
|
+
// Act
|
|
825
|
+
const item = await screen.findByTestId("item-1");
|
|
826
|
+
userEvent.click(item);
|
|
746
827
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
keyCode: keyCodes.space,
|
|
750
|
-
preventDefault: preventDefaultMock,
|
|
751
|
-
});
|
|
752
|
-
searchInput.simulate("keyup", {
|
|
753
|
-
keyCode: keyCodes.space,
|
|
754
|
-
preventDefault: preventDefaultMock,
|
|
828
|
+
// Assert
|
|
829
|
+
expect(item).toHaveFocus();
|
|
755
830
|
});
|
|
756
|
-
|
|
757
|
-
// Assert
|
|
758
|
-
expect(preventDefaultMock).toHaveBeenCalledTimes(0);
|
|
759
831
|
});
|
|
760
832
|
});
|