@khanacademy/wonder-blocks-dropdown 2.3.19

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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/dist/es/index.js +3403 -0
  3. package/dist/index.js +3966 -0
  4. package/dist/index.js.flow +2 -0
  5. package/docs.md +12 -0
  6. package/package.json +44 -0
  7. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +4054 -0
  8. package/src/__tests__/generated-snapshot.test.js +1612 -0
  9. package/src/__tests__/index.test.js +23 -0
  10. package/src/components/__mocks__/dropdown-core-virtualized.js +40 -0
  11. package/src/components/__tests__/__snapshots__/action-item.test.js.snap +63 -0
  12. package/src/components/__tests__/action-item.test.js +43 -0
  13. package/src/components/__tests__/action-menu.test.js +544 -0
  14. package/src/components/__tests__/dropdown-core-virtualized.test.js +119 -0
  15. package/src/components/__tests__/dropdown-core.test.js +659 -0
  16. package/src/components/__tests__/multi-select.test.js +982 -0
  17. package/src/components/__tests__/search-text-input.test.js +144 -0
  18. package/src/components/__tests__/single-select.test.js +588 -0
  19. package/src/components/action-item.js +270 -0
  20. package/src/components/action-menu-opener-core.js +203 -0
  21. package/src/components/action-menu.js +300 -0
  22. package/src/components/action-menu.md +338 -0
  23. package/src/components/check.js +59 -0
  24. package/src/components/checkbox.js +111 -0
  25. package/src/components/dropdown-core-virtualized-item.js +62 -0
  26. package/src/components/dropdown-core-virtualized.js +246 -0
  27. package/src/components/dropdown-core.js +770 -0
  28. package/src/components/dropdown-opener.js +101 -0
  29. package/src/components/multi-select.js +597 -0
  30. package/src/components/multi-select.md +718 -0
  31. package/src/components/multi-select.stories.js +111 -0
  32. package/src/components/option-item.js +239 -0
  33. package/src/components/search-text-input.js +227 -0
  34. package/src/components/select-opener.js +297 -0
  35. package/src/components/separator-item.js +50 -0
  36. package/src/components/single-select.js +418 -0
  37. package/src/components/single-select.md +520 -0
  38. package/src/components/single-select.stories.js +107 -0
  39. package/src/index.js +20 -0
  40. package/src/util/constants.js +50 -0
  41. package/src/util/types.js +32 -0
@@ -0,0 +1,119 @@
1
+ //@flow
2
+ import * as React from "react";
3
+ import {VariableSizeList as List} from "react-window";
4
+ import {mount} from "enzyme";
5
+
6
+ import OptionItem from "../option-item.js";
7
+ import SeparatorItem from "../separator-item.js";
8
+ import DropdownCoreVirtualized from "../dropdown-core-virtualized.js";
9
+ import SearchTextInput from "../search-text-input.js";
10
+
11
+ describe("DropdownCoreVirtualized", () => {
12
+ beforeEach(() => {
13
+ jest.useFakeTimers();
14
+
15
+ // Jest doesn't fake out the animation frame API, so we're going to do
16
+ // it here and map it to timeouts, that way we can use the fake timer
17
+ // API to test our animation frame things.
18
+ jest.spyOn(global, "requestAnimationFrame").mockImplementation((fn) =>
19
+ setTimeout(fn, 0),
20
+ );
21
+ jest.spyOn(global, "cancelAnimationFrame").mockImplementation((id) =>
22
+ clearTimeout(id),
23
+ );
24
+ });
25
+
26
+ afterEach(() => {
27
+ jest.restoreAllMocks();
28
+ });
29
+
30
+ it("should sort the items on first load", () => {
31
+ // Arrange
32
+ const optionItems = ["a", "bb", "ccc"].map((item, i) => ({
33
+ component: <OptionItem key={i} value={item} label={item} />,
34
+ focusable: true,
35
+ onClick: jest.fn(),
36
+ role: "option",
37
+ populatedProps: {
38
+ selected: false,
39
+ variant: "checkbox",
40
+ },
41
+ }));
42
+
43
+ const initialItems = [
44
+ {
45
+ component: (
46
+ <SearchTextInput onChange={jest.fn()} searchText="" />
47
+ ),
48
+ focusable: true,
49
+ populatedProps: {},
50
+ },
51
+ {
52
+ component: <SeparatorItem />,
53
+ focusable: false,
54
+ populatedProps: {},
55
+ },
56
+ ];
57
+
58
+ const wrapper = mount(
59
+ <DropdownCoreVirtualized
60
+ data={[...initialItems, ...optionItems]}
61
+ />,
62
+ );
63
+
64
+ jest.runAllTimers();
65
+
66
+ // Act
67
+ const firstLabel = wrapper.find(OptionItem).first().text();
68
+
69
+ // Assert
70
+ // make sure we are rendering the longest item first
71
+ expect(firstLabel).toEqual("ccc");
72
+ });
73
+
74
+ it("should render a virtualized list", () => {
75
+ // Arrange
76
+ const optionItems = new Array(10).fill(null).map((item, i) => ({
77
+ component: (
78
+ <OptionItem
79
+ key={i}
80
+ value={(i + 1).toString()}
81
+ label={`School ${i + 1} in Wizarding World`}
82
+ />
83
+ ),
84
+ focusable: true,
85
+ onClick: jest.fn(),
86
+ role: "option",
87
+ populatedProps: {
88
+ selected: false,
89
+ variant: "checkbox",
90
+ },
91
+ }));
92
+
93
+ const initialItems = [
94
+ {
95
+ component: (
96
+ <SearchTextInput onChange={jest.fn()} searchText="" />
97
+ ),
98
+ focusable: true,
99
+ populatedProps: {},
100
+ },
101
+ {
102
+ component: <SeparatorItem />,
103
+ focusable: false,
104
+ populatedProps: {},
105
+ },
106
+ ];
107
+
108
+ const wrapper = mount(
109
+ <DropdownCoreVirtualized data={initialItems} width={300} />,
110
+ );
111
+
112
+ // Act
113
+ // append items to update container height
114
+ wrapper.setProps({data: [...initialItems, ...optionItems]});
115
+
116
+ // Assert
117
+ expect(wrapper.find(List)).toExist();
118
+ });
119
+ });
@@ -0,0 +1,659 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import * as ReactDOM from "react-dom";
4
+ import {mount} from "enzyme";
5
+
6
+ import OptionItem from "../option-item.js";
7
+ import SearchTextInput from "../search-text-input.js";
8
+ import DropdownCore from "../dropdown-core.js";
9
+ import {keyCodes} from "../../util/constants.js";
10
+
11
+ jest.mock("../dropdown-core-virtualized.js");
12
+
13
+ const elementAtIndex = (wrapper, index) =>
14
+ wrapper.find(`[data-test-id="item-${index}"]`).first().getDOMNode();
15
+
16
+ describe("DropdownCore", () => {
17
+ window.getComputedStyle = jest.fn();
18
+
19
+ let dropdown;
20
+
21
+ beforeEach(() => {
22
+ jest.useFakeTimers();
23
+
24
+ // Jest doesn't fake out the animation frame API, so we're going to do
25
+ // it here and map it to timeouts, that way we can use the fake timer
26
+ // API to test our animation frame things.
27
+ jest.spyOn(global, "requestAnimationFrame").mockImplementation((fn) =>
28
+ setTimeout(fn, 0),
29
+ );
30
+ jest.spyOn(global, "cancelAnimationFrame").mockImplementation((id) =>
31
+ clearTimeout(id),
32
+ );
33
+
34
+ const dummyOpener = <button />;
35
+ const openChanged = jest.fn();
36
+ dropdown = mount(
37
+ <DropdownCore
38
+ initialFocusedIndex={0}
39
+ // mock the items
40
+ items={[
41
+ {
42
+ component: (
43
+ <OptionItem
44
+ testId="item-0"
45
+ label="item 0"
46
+ value="0"
47
+ key="0"
48
+ />
49
+ ),
50
+ focusable: true,
51
+ populatedProps: {},
52
+ },
53
+ {
54
+ component: (
55
+ <OptionItem
56
+ testId="item-1"
57
+ label="item 1"
58
+ value="1"
59
+ key="1"
60
+ />
61
+ ),
62
+ focusable: true,
63
+ populatedProps: {},
64
+ },
65
+ {
66
+ component: (
67
+ <OptionItem
68
+ testId="item-2"
69
+ label="item 2"
70
+ value="2"
71
+ key="2"
72
+ />
73
+ ),
74
+ focusable: true,
75
+ populatedProps: {},
76
+ },
77
+ ]}
78
+ role="listbox"
79
+ light={false}
80
+ open={false}
81
+ // mock the opener elements
82
+ opener={dummyOpener}
83
+ openerElement={null}
84
+ onOpenChanged={openChanged}
85
+ />,
86
+ );
87
+ });
88
+
89
+ afterEach(() => {
90
+ jest.restoreAllMocks();
91
+ });
92
+
93
+ it("handles basic keyboard navigation as expected", () => {
94
+ const handleOpen = jest.fn();
95
+ dropdown.setProps({
96
+ initialFocusedIndex: 0,
97
+ onOpenChanged: (open) => handleOpen(open),
98
+ open: true,
99
+ });
100
+
101
+ jest.runAllTimers();
102
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
103
+
104
+ // navigate down three times
105
+ dropdown.simulate("keydown", {keyCode: keyCodes.down});
106
+ dropdown.simulate("keyup", {keyCode: keyCodes.down});
107
+ jest.runAllTimers();
108
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 1));
109
+
110
+ dropdown.simulate("keydown", {keyCode: keyCodes.down});
111
+ dropdown.simulate("keyup", {keyCode: keyCodes.down});
112
+ jest.runAllTimers();
113
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 2));
114
+
115
+ dropdown.simulate("keydown", {keyCode: keyCodes.down});
116
+ dropdown.simulate("keyup", {keyCode: keyCodes.down});
117
+ jest.runAllTimers();
118
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
119
+
120
+ // navigate up back two times
121
+ // to last item
122
+ dropdown.simulate("keydown", {keyCode: keyCodes.up});
123
+ dropdown.simulate("keyup", {keyCode: keyCodes.up});
124
+ jest.runAllTimers();
125
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 2));
126
+ // to the previous one
127
+ dropdown.simulate("keydown", {keyCode: keyCodes.up});
128
+ dropdown.simulate("keyup", {keyCode: keyCodes.up});
129
+ jest.runAllTimers();
130
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 1));
131
+ });
132
+
133
+ it("doesn't close on touch interaction with option", () => {
134
+ const handleOpen = jest.fn();
135
+ dropdown.setProps({
136
+ onOpenChanged: (open) => handleOpen(open),
137
+ open: true,
138
+ });
139
+
140
+ const option0 = dropdown.find("OptionItem").at(0);
141
+ // This is the full order of events fired when tapping an element
142
+ // on mobile
143
+ // https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent#Event_order
144
+ option0.simulate("touchstart");
145
+ option0.simulate("touchend");
146
+ option0.simulate("mousemove");
147
+ option0.simulate("mousedown");
148
+ option0.simulate("mouseup");
149
+ option0.simulate("click");
150
+
151
+ expect(handleOpen).toHaveBeenCalledTimes(0);
152
+ });
153
+
154
+ it("closes on tab and escape as expected", () => {
155
+ const handleOpen = jest.fn();
156
+ dropdown.setProps({
157
+ onOpenChanged: (open) => handleOpen(open),
158
+ open: true,
159
+ });
160
+
161
+ // "close" some menus
162
+ dropdown.simulate("keydown", {keyCode: keyCodes.tab});
163
+ dropdown.simulate("keyup", {keyCode: keyCodes.tab});
164
+ dropdown.simulate("keydown", {keyCode: keyCodes.escape});
165
+ dropdown.simulate("keyup", {keyCode: keyCodes.escape});
166
+ expect(handleOpen).toHaveBeenCalledTimes(2);
167
+ // Test that we pass "false" to handleOpenChanged both times
168
+ expect(handleOpen.mock.calls[0][0]).toBe(false);
169
+ expect(handleOpen.mock.calls[1][0]).toBe(false);
170
+ });
171
+
172
+ it("closes on external mouse click", () => {
173
+ const handleOpen = jest.fn();
174
+ dropdown.setProps({
175
+ onOpenChanged: (open) => handleOpen(open),
176
+ open: true,
177
+ });
178
+
179
+ const event = new MouseEvent("mouseup");
180
+ document.dispatchEvent(event);
181
+
182
+ expect(handleOpen).toHaveBeenCalledTimes(1);
183
+ expect(handleOpen.mock.calls[0][0]).toBe(false);
184
+ });
185
+
186
+ it("closes on external mouse click on an element inside document.body", () => {
187
+ const handleOpen = jest.fn();
188
+ const container = document.createElement("container");
189
+ if (!document.body) {
190
+ throw new Error("No document.body");
191
+ }
192
+ document.body.appendChild(container);
193
+
194
+ /**
195
+ * According to https://stackoverflow.com/questions/36803733/jsdom-dispatchevent-addeventlistener-doesnt-seem-to-work
196
+ * Enzyme uses renderIntoDocument from React.TestUtils which doesn't actually
197
+ * render the component into document.body so testing behavior that relies
198
+ * on bubbling won't work. This test works around this limitation by using
199
+ * ReactDOM.render() to render our test component into a container that lives
200
+ * in document.body.
201
+ */
202
+
203
+ ReactDOM.render(
204
+ <div>
205
+ <h1 id="foo">DropdownCore test</h1>
206
+ <DropdownCore
207
+ initialFocusedIndex={0}
208
+ // mock the items
209
+ items={[
210
+ {
211
+ component: (
212
+ <OptionItem label="item 0" value="0" key="0" />
213
+ ),
214
+ focusable: true,
215
+ populatedProps: {},
216
+ },
217
+ {
218
+ component: (
219
+ <OptionItem label="item 1" value="1" key="1" />
220
+ ),
221
+ focusable: true,
222
+ populatedProps: {},
223
+ },
224
+ {
225
+ component: (
226
+ <OptionItem label="item 2" value="2" key="2" />
227
+ ),
228
+ focusable: true,
229
+ populatedProps: {},
230
+ },
231
+ ]}
232
+ role="listbox"
233
+ light={false}
234
+ open={true}
235
+ // mock the opener elements
236
+ opener={<button />}
237
+ openerElement={null}
238
+ onOpenChanged={(open) => handleOpen(open)}
239
+ />
240
+ </div>,
241
+ container,
242
+ );
243
+
244
+ const title = document.querySelector("#foo");
245
+ if (!title) {
246
+ throw new Error("Couldn't find title");
247
+ }
248
+ const event = new MouseEvent("mouseup", {bubbles: true});
249
+ title.dispatchEvent(event);
250
+
251
+ expect(handleOpen).toHaveBeenCalledTimes(1);
252
+ expect(handleOpen.mock.calls[0][0]).toBe(false);
253
+
254
+ // cleanup
255
+ ReactDOM.unmountComponentAtNode(container);
256
+ if (!document.body) {
257
+ throw new Error("No document.body");
258
+ }
259
+ document.body.removeChild(container);
260
+ });
261
+
262
+ it("doesn't close on external mouse click if already closed", () => {
263
+ const handleOpen = jest.fn();
264
+ dropdown.setProps({
265
+ onOpenChanged: (open) => handleOpen(open),
266
+ open: false,
267
+ });
268
+
269
+ const event = new MouseEvent("mouseup");
270
+ document.dispatchEvent(event);
271
+
272
+ expect(handleOpen).toHaveBeenCalledTimes(0);
273
+ });
274
+
275
+ it("opens on down key as expected", () => {
276
+ const handleOpen = jest.fn();
277
+ dropdown.setProps({
278
+ onOpenChanged: (open) => handleOpen(open),
279
+ });
280
+
281
+ dropdown.simulate("keydown", {keyCode: keyCodes.down});
282
+ dropdown.simulate("keyup", {keyCode: keyCodes.down});
283
+
284
+ expect(handleOpen).toHaveBeenCalledTimes(1);
285
+ expect(handleOpen.mock.calls[0][0]).toBe(true);
286
+ });
287
+
288
+ it("selects correct item when starting off at a different index", () => {
289
+ const handleOpen = jest.fn();
290
+ dropdown.setProps({
291
+ initialFocusedIndex: 2,
292
+ onOpenChanged: (open) => handleOpen(open),
293
+ open: true,
294
+ });
295
+
296
+ jest.runAllTimers();
297
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 2));
298
+
299
+ // navigate down
300
+ dropdown.simulate("keydown", {keyCode: keyCodes.down});
301
+ dropdown.simulate("keyup", {keyCode: keyCodes.down});
302
+ jest.runAllTimers();
303
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
304
+ });
305
+
306
+ it("focuses correct item with clicking/pressing with initial focused of not 0", () => {
307
+ // Same as the previous test, expect initialFocusedIndex is 2 now
308
+ dropdown.setProps({
309
+ initialFocusedIndex: 2,
310
+ open: true,
311
+ });
312
+
313
+ const option1 = dropdown.find("OptionItem").at(1);
314
+
315
+ // Click on item at index 1
316
+ option1.simulate("click");
317
+
318
+ // should move to next item
319
+ dropdown.simulate("keydown", {keyCode: keyCodes.down});
320
+ dropdown.simulate("keyup", {keyCode: keyCodes.down});
321
+ jest.runAllTimers();
322
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 2));
323
+ });
324
+
325
+ it("focuses correct item with a disabled item", () => {
326
+ dropdown.setProps({
327
+ initialFocusedIndex: 0,
328
+ items: [
329
+ {
330
+ component: (
331
+ <OptionItem
332
+ testId="item-0"
333
+ label="item 0"
334
+ value="0"
335
+ key="0"
336
+ />
337
+ ),
338
+ focusable: true,
339
+ populatedProps: {},
340
+ },
341
+ {
342
+ component: (
343
+ <OptionItem
344
+ testId="item-1"
345
+ label="item 1"
346
+ value="1"
347
+ key="1"
348
+ />
349
+ ),
350
+ focusable: false,
351
+ populatedProps: {},
352
+ },
353
+ {
354
+ component: (
355
+ <OptionItem
356
+ testId="item-2"
357
+ label="item 2"
358
+ value="2"
359
+ key="2"
360
+ />
361
+ ),
362
+ focusable: true,
363
+ populatedProps: {},
364
+ },
365
+ ],
366
+ open: true,
367
+ });
368
+
369
+ jest.runAllTimers();
370
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
371
+
372
+ // Should select option2
373
+ dropdown.simulate("keydown", {keyCode: keyCodes.down});
374
+ dropdown.simulate("keyup", {keyCode: keyCodes.down});
375
+ jest.runAllTimers();
376
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 2));
377
+
378
+ // Should select option0
379
+ dropdown.simulate("keydown", {keyCode: keyCodes.down});
380
+ dropdown.simulate("keyup", {keyCode: keyCodes.down});
381
+ jest.runAllTimers();
382
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
383
+ });
384
+
385
+ it("focuses correct item after different items become focusable", () => {
386
+ dropdown.setProps({
387
+ initialFocusedIndex: 0,
388
+ open: true,
389
+ });
390
+
391
+ jest.runAllTimers();
392
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
393
+
394
+ // Should select option1
395
+ dropdown.simulate("keydown", {keyCode: keyCodes.down});
396
+ dropdown.simulate("keyup", {keyCode: keyCodes.down});
397
+ jest.runAllTimers();
398
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 1));
399
+
400
+ dropdown.setProps({
401
+ items: [
402
+ {
403
+ component: (
404
+ <OptionItem
405
+ testId="item-0"
406
+ label="item 0"
407
+ value="0"
408
+ key="0"
409
+ />
410
+ ),
411
+ focusable: false,
412
+ populatedProps: {},
413
+ },
414
+ {
415
+ component: (
416
+ <OptionItem
417
+ testId="item-1"
418
+ label="item 1"
419
+ value="1"
420
+ key="1"
421
+ />
422
+ ),
423
+ focusable: true,
424
+ populatedProps: {},
425
+ },
426
+ {
427
+ component: (
428
+ <OptionItem
429
+ testId="item-2"
430
+ label="item 2"
431
+ value="2"
432
+ key="2"
433
+ />
434
+ ),
435
+ focusable: true,
436
+ populatedProps: {},
437
+ },
438
+ ],
439
+ });
440
+
441
+ // Should figure out that option1, which is now at index 0, is selected
442
+ jest.runAllTimers();
443
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 1));
444
+ });
445
+
446
+ it("focuses first item after currently focused item is no longer focusable", () => {
447
+ dropdown.setProps({
448
+ initialFocusedIndex: 0,
449
+ open: true,
450
+ });
451
+
452
+ jest.runAllTimers();
453
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
454
+
455
+ const option0 = dropdown.find("OptionItem").at(0);
456
+ option0.simulate("click");
457
+ jest.runAllTimers();
458
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
459
+
460
+ dropdown.setProps({
461
+ items: [
462
+ {
463
+ component: (
464
+ <OptionItem
465
+ testId="item-0"
466
+ label="item 0"
467
+ value="0"
468
+ key="0"
469
+ />
470
+ ),
471
+ focusable: false,
472
+ populatedProps: {},
473
+ },
474
+ {
475
+ component: (
476
+ <OptionItem
477
+ testId="item-1"
478
+ label="item 1"
479
+ value="1"
480
+ key="1"
481
+ />
482
+ ),
483
+ focusable: true,
484
+ populatedProps: {},
485
+ },
486
+ {
487
+ component: (
488
+ <OptionItem
489
+ testId="item-2"
490
+ label="item 2"
491
+ value="2"
492
+ key="2"
493
+ />
494
+ ),
495
+ focusable: true,
496
+ populatedProps: {},
497
+ },
498
+ ],
499
+ });
500
+
501
+ // Should figure out that option1 is now selected
502
+ jest.runAllTimers();
503
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 1));
504
+ });
505
+
506
+ it("calls correct onclick for an option item", () => {
507
+ const onClick0 = jest.fn();
508
+ const onClick1 = jest.fn();
509
+ dropdown.setProps({
510
+ items: [
511
+ {
512
+ component: (
513
+ <OptionItem
514
+ label="item 0"
515
+ value="0"
516
+ key="0"
517
+ onClick={onClick0}
518
+ />
519
+ ),
520
+ focusable: true,
521
+ populatedProps: {},
522
+ },
523
+ {
524
+ component: (
525
+ <OptionItem
526
+ label="item 1"
527
+ value="1"
528
+ key="1"
529
+ onClick={onClick1}
530
+ />
531
+ ),
532
+ focusable: true,
533
+ populatedProps: {},
534
+ },
535
+ {
536
+ component: <OptionItem label="item 2" value="2" key="2" />,
537
+ focusable: true,
538
+ populatedProps: {},
539
+ },
540
+ ],
541
+ open: true,
542
+ });
543
+
544
+ const option1 = dropdown.find("OptionItem").at(1);
545
+
546
+ option1.simulate("click");
547
+ expect(onClick1).toHaveBeenCalledTimes(1);
548
+ });
549
+
550
+ it("Displays no results when no items are left with filter", () => {
551
+ // Arrange
552
+ const handleSearchTextChanged = jest.fn();
553
+
554
+ // Act
555
+ dropdown.setProps({
556
+ onSearchTextChanged: (text) => handleSearchTextChanged(text),
557
+ searchText: "ab",
558
+ items: [
559
+ {
560
+ component: (
561
+ <SearchTextInput
562
+ key="search-text-input"
563
+ onChange={handleSearchTextChanged}
564
+ searchText={""}
565
+ />
566
+ ),
567
+ focusable: true,
568
+ populatedProps: {},
569
+ },
570
+ ],
571
+ open: true,
572
+ });
573
+
574
+ // Assert
575
+ expect(dropdown.find("InnerPopper").text()).toContain("No results");
576
+ });
577
+
578
+ it("When SearchTextInput has input and focused, tab key should not close the select", () => {
579
+ // Arrange
580
+ const handleSearchTextChanged = jest.fn();
581
+ const handleOpen = jest.fn();
582
+
583
+ dropdown.setProps({
584
+ onOpenChanged: (open) => handleOpen(open),
585
+ onSearchTextChanged: (text) => handleSearchTextChanged(text),
586
+ searchText: "ab",
587
+ open: true,
588
+ items: [
589
+ {
590
+ component: (
591
+ <SearchTextInput
592
+ testId="item-0"
593
+ key="search-text-input"
594
+ onChange={handleSearchTextChanged}
595
+ searchText={""}
596
+ />
597
+ ),
598
+ focusable: true,
599
+ populatedProps: {},
600
+ },
601
+ ],
602
+ });
603
+ // SearchTextInput should be focused
604
+ const searchInput = dropdown.find(SearchTextInput);
605
+ jest.runAllTimers();
606
+
607
+ expect(searchInput.state("focused")).toBe(true);
608
+
609
+ // Act
610
+ dropdown.simulate("keydown", {keyCode: keyCodes.tab});
611
+ jest.runAllTimers();
612
+
613
+ // Assert
614
+ expect(handleOpen).toHaveBeenCalledTimes(0);
615
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
616
+ });
617
+
618
+ it("When SearchTextInput exists and focused, space key pressing should be allowed", () => {
619
+ // Arrange
620
+ const handleSearchTextChanged = jest.fn();
621
+ const preventDefaultMock = jest.fn();
622
+ dropdown.setProps({
623
+ onSearchTextChanged: (text) => handleSearchTextChanged(text),
624
+ searchText: "",
625
+ items: [
626
+ {
627
+ component: (
628
+ <SearchTextInput
629
+ testId="item-0"
630
+ key="search-text-input"
631
+ onChange={handleSearchTextChanged}
632
+ searchText={""}
633
+ />
634
+ ),
635
+ focusable: true,
636
+ populatedProps: {},
637
+ },
638
+ ],
639
+ open: true,
640
+ });
641
+ // SearchTextInput should be focused
642
+ const searchInput = dropdown.find(SearchTextInput).find("input");
643
+ jest.runAllTimers();
644
+ expect(document.activeElement).toBe(elementAtIndex(dropdown, 0));
645
+
646
+ // Act
647
+ searchInput.simulate("keydown", {
648
+ keyCode: keyCodes.space,
649
+ preventDefault: preventDefaultMock,
650
+ });
651
+ searchInput.simulate("keyup", {
652
+ keyCode: keyCodes.space,
653
+ preventDefault: preventDefaultMock,
654
+ });
655
+
656
+ // Assert
657
+ expect(preventDefaultMock).toHaveBeenCalledTimes(0);
658
+ });
659
+ });