@@ -6,9 +6,11 @@ import userEvent from '@testing-library/user-event';
6
6
7
7
// A mock component for testing the Hook
8
8
const TestComponent : React . FC = ( ) => {
9
- const [ itemCount , setItemCount ] = useState ( 3 ) ;
9
+ const [ itemCount , setItemCount ] = useState ( 4 ) ;
10
10
const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu ( itemCount ) ;
11
11
12
+ const clickHandlers : ( ( ) => void ) [ ] = [ ( ) : void => console . log ( 'Item one clicked' ) , ( ) : void => setIsOpen ( false ) ] ;
13
+
12
14
return (
13
15
< React . Fragment >
14
16
< button { ...buttonProps } id = 'menu-button' >
@@ -21,10 +23,10 @@ const TestComponent: React.FC = () => {
21
23
{ ...props }
22
24
key = { i }
23
25
id = { `menu-item-${ i + 1 } ` }
24
- onClick = { i === 0 ? ( ) : void => setIsOpen ( false ) : undefined }
25
- href = { i !== 0 ? 'https://example.com' : undefined }
26
+ onClick = { clickHandlers [ i ] }
27
+ href = { i > 1 ? 'https://example.com' : undefined }
26
28
>
27
- Item { i + 1 }
29
+ { i + 1 } Item
28
30
</ a >
29
31
) ) }
30
32
</ div >
@@ -60,7 +62,7 @@ it('Moves the focus to the first menu item after pressing enter while focused on
60
62
skipClick : true ,
61
63
} ) ;
62
64
63
- expect ( screen . getByText ( 'Item 1 ' ) ) . toHaveFocus ( ) ;
65
+ expect ( screen . getByText ( '1 Item ' ) ) . toHaveFocus ( ) ;
64
66
} ) ;
65
67
66
68
it ( 'Moves the focus to the first menu item after pressing space while focused on the menu button' , ( ) => {
@@ -74,7 +76,7 @@ it('Moves the focus to the first menu item after pressing space while focused on
74
76
skipClick : true ,
75
77
} ) ;
76
78
77
- expect ( screen . getByText ( 'Item 1 ' ) ) . toHaveFocus ( ) ;
79
+ expect ( screen . getByText ( '1 Item ' ) ) . toHaveFocus ( ) ;
78
80
} ) ;
79
81
80
82
it ( 'Moves the focus to the first menu item after clicking the menu to open it, then pressing tab while focused on the menu button' , ( ) => {
@@ -86,7 +88,7 @@ it('Moves the focus to the first menu item after clicking the menu to open it, t
86
88
87
89
userEvent . tab ( ) ;
88
90
89
- expect ( screen . getByText ( 'Item 1 ' ) ) . toHaveFocus ( ) ;
91
+ expect ( screen . getByText ( '1 Item ' ) ) . toHaveFocus ( ) ;
90
92
} ) ;
91
93
92
94
it ( 'Moves the focus to the first menu item after clicking the menu to open it, then pressing arrow down while focused on the menu button' , ( ) => {
@@ -105,7 +107,7 @@ it('Moves the focus to the first menu item after clicking the menu to open it, t
105
107
} )
106
108
) ;
107
109
108
- expect ( screen . getByText ( 'Item 1 ' ) ) . toHaveFocus ( ) ;
110
+ expect ( screen . getByText ( '1 Item ' ) ) . toHaveFocus ( ) ;
109
111
} ) ;
110
112
111
113
it ( 'Sets isOpen to true after pressing enter while focused on the menu button' , ( ) => {
@@ -144,7 +146,7 @@ it('Sets isOpen to false after clicking a menu item that calls the state change
144
146
render ( < TestComponent /> ) ;
145
147
146
148
userEvent . click ( screen . getByText ( 'Primary' ) ) ;
147
- userEvent . click ( screen . getByText ( 'Item 1 ' ) ) ;
149
+ userEvent . click ( screen . getByText ( '2 Item ' ) ) ;
148
150
149
151
expect ( screen . getByTestId ( 'is-open-indicator' ) ) . toHaveTextContent ( 'false' ) ;
150
152
} ) ;
@@ -158,18 +160,18 @@ it('Moves the focus to the next element in the menu after pressing the down arro
158
160
skipClick : true ,
159
161
} ) ;
160
162
161
- expect ( screen . getByText ( 'Item 1 ' ) ) . toHaveFocus ( ) ;
163
+ expect ( screen . getByText ( '1 Item ' ) ) . toHaveFocus ( ) ;
162
164
163
165
fireEvent (
164
- screen . getByText ( 'Item 1 ' ) ,
166
+ screen . getByText ( '1 Item ' ) ,
165
167
new KeyboardEvent ( 'keydown' , {
166
168
key : 'ArrowDown' ,
167
169
bubbles : true ,
168
170
cancelable : true ,
169
171
} )
170
172
) ;
171
173
172
- expect ( screen . getByText ( 'Item 2 ' ) ) . toHaveFocus ( ) ;
174
+ expect ( screen . getByText ( '2 Item ' ) ) . toHaveFocus ( ) ;
173
175
} ) ;
174
176
175
177
it ( 'Moves the focus to the previous element in the menu after pressing the up arrow' , ( ) => {
@@ -181,29 +183,29 @@ it('Moves the focus to the previous element in the menu after pressing the up ar
181
183
skipClick : true ,
182
184
} ) ;
183
185
184
- expect ( screen . getByText ( 'Item 1 ' ) ) . toHaveFocus ( ) ;
186
+ expect ( screen . getByText ( '1 Item ' ) ) . toHaveFocus ( ) ;
185
187
186
188
fireEvent (
187
- screen . getByText ( 'Item 1 ' ) ,
189
+ screen . getByText ( '1 Item ' ) ,
188
190
new KeyboardEvent ( 'keydown' , {
189
191
key : 'ArrowDown' ,
190
192
bubbles : true ,
191
193
cancelable : true ,
192
194
} )
193
195
) ;
194
196
195
- expect ( screen . getByText ( 'Item 2 ' ) ) . toHaveFocus ( ) ;
197
+ expect ( screen . getByText ( '2 Item ' ) ) . toHaveFocus ( ) ;
196
198
197
199
fireEvent (
198
- screen . getByText ( 'Item 2 ' ) ,
200
+ screen . getByText ( '2 Item ' ) ,
199
201
new KeyboardEvent ( 'keydown' , {
200
202
key : 'ArrowUp' ,
201
203
bubbles : true ,
202
204
cancelable : true ,
203
205
} )
204
206
) ;
205
207
206
- expect ( screen . getByText ( 'Item 1 ' ) ) . toHaveFocus ( ) ;
208
+ expect ( screen . getByText ( '1 Item ' ) ) . toHaveFocus ( ) ;
207
209
} ) ;
208
210
209
211
it ( 'Wraps the focus to the last element when pressing the up arrow at the beginning of the menu' , ( ) => {
@@ -215,18 +217,18 @@ it('Wraps the focus to the last element when pressing the up arrow at the beginn
215
217
skipClick : true ,
216
218
} ) ;
217
219
218
- expect ( screen . getByText ( 'Item 1 ' ) ) . toHaveFocus ( ) ;
220
+ expect ( screen . getByText ( '1 Item ' ) ) . toHaveFocus ( ) ;
219
221
220
222
fireEvent (
221
- screen . getByText ( 'Item 1 ' ) ,
223
+ screen . getByText ( '1 Item ' ) ,
222
224
new KeyboardEvent ( 'keydown' , {
223
225
key : 'ArrowUp' ,
224
226
bubbles : true ,
225
227
cancelable : true ,
226
228
} )
227
229
) ;
228
230
229
- expect ( screen . getByText ( 'Item 3 ' ) ) . toHaveFocus ( ) ;
231
+ expect ( screen . getByText ( '4 Item ' ) ) . toHaveFocus ( ) ;
230
232
} ) ;
231
233
232
234
it ( 'Wraps the focus to the first element when pressing the down arrow at the end of the menu' , ( ) => {
@@ -238,29 +240,29 @@ it('Wraps the focus to the first element when pressing the down arrow at the end
238
240
skipClick : true ,
239
241
} ) ;
240
242
241
- expect ( screen . getByText ( 'Item 1 ' ) ) . toHaveFocus ( ) ;
243
+ expect ( screen . getByText ( '1 Item ' ) ) . toHaveFocus ( ) ;
242
244
243
245
fireEvent (
244
- screen . getByText ( 'Item 1 ' ) ,
246
+ screen . getByText ( '1 Item ' ) ,
245
247
new KeyboardEvent ( 'keydown' , {
246
248
key : 'ArrowUp' ,
247
249
bubbles : true ,
248
250
cancelable : true ,
249
251
} )
250
252
) ;
251
253
252
- expect ( screen . getByText ( 'Item 3 ' ) ) . toHaveFocus ( ) ;
254
+ expect ( screen . getByText ( '4 Item ' ) ) . toHaveFocus ( ) ;
253
255
254
256
fireEvent (
255
- screen . getByText ( 'Item 3 ' ) ,
257
+ screen . getByText ( '4 Item ' ) ,
256
258
new KeyboardEvent ( 'keydown' , {
257
259
key : 'ArrowDown' ,
258
260
bubbles : true ,
259
261
cancelable : true ,
260
262
} )
261
263
) ;
262
264
263
- expect ( screen . getByText ( 'Item 1 ' ) ) . toHaveFocus ( ) ;
265
+ expect ( screen . getByText ( '1 Item ' ) ) . toHaveFocus ( ) ;
264
266
} ) ;
265
267
266
268
it ( 'Sets isOpen to false after pressing escape while focused on a menu item' , ( ) => {
@@ -272,7 +274,7 @@ it('Sets isOpen to false after pressing escape while focused on a menu item', ()
272
274
skipClick : true ,
273
275
} ) ;
274
276
275
- userEvent . type ( screen . getByText ( 'Item 1 ' ) , '{esc}' , {
277
+ userEvent . type ( screen . getByText ( '1 Item ' ) , '{esc}' , {
276
278
skipClick : true ,
277
279
} ) ;
278
280
@@ -302,7 +304,7 @@ it('Moves the focus to the menu button after pressing escape while focused on a
302
304
skipClick : true ,
303
305
} ) ;
304
306
305
- userEvent . type ( screen . getByText ( 'Item 1 ' ) , '{esc}' , {
307
+ userEvent . type ( screen . getByText ( '1 Item ' ) , '{esc}' , {
306
308
skipClick : true ,
307
309
} ) ;
308
310
@@ -333,7 +335,7 @@ it('Adds properties to items added after mount', () => {
333
335
334
336
userEvent . click ( screen . getByText ( 'Add Item' ) ) ;
335
337
336
- expect ( screen . getByText ( 'Item 4 ' ) ) . toHaveAttribute ( 'role' , 'menuitem' ) ;
338
+ expect ( screen . getByText ( '4 Item ' ) ) . toHaveAttribute ( 'role' , 'menuitem' ) ;
337
339
} ) ;
338
340
339
341
it ( 'Can navigate to a dynamically-added item' , ( ) => {
@@ -353,7 +355,16 @@ it('Can navigate to a dynamically-added item', () => {
353
355
) ;
354
356
355
357
fireEvent (
356
- screen . getByText ( 'Item 1' ) ,
358
+ screen . getByText ( '1 Item' ) ,
359
+ new KeyboardEvent ( 'keydown' , {
360
+ key : 'ArrowDown' ,
361
+ bubbles : true ,
362
+ cancelable : true ,
363
+ } )
364
+ ) ;
365
+
366
+ fireEvent (
367
+ screen . getByText ( '2 Item' ) ,
357
368
new KeyboardEvent ( 'keydown' , {
358
369
key : 'ArrowDown' ,
359
370
bubbles : true ,
@@ -362,7 +373,7 @@ it('Can navigate to a dynamically-added item', () => {
362
373
) ;
363
374
364
375
fireEvent (
365
- screen . getByText ( 'Item 2 ' ) ,
376
+ screen . getByText ( '3 Item ' ) ,
366
377
new KeyboardEvent ( 'keydown' , {
367
378
key : 'ArrowDown' ,
368
379
bubbles : true ,
@@ -371,15 +382,15 @@ it('Can navigate to a dynamically-added item', () => {
371
382
) ;
372
383
373
384
fireEvent (
374
- screen . getByText ( 'Item 3 ' ) ,
385
+ screen . getByText ( '4 Item ' ) ,
375
386
new KeyboardEvent ( 'keydown' , {
376
387
key : 'ArrowDown' ,
377
388
bubbles : true ,
378
389
cancelable : true ,
379
390
} )
380
391
) ;
381
392
382
- expect ( screen . getByText ( 'Item 4 ' ) ) . toHaveFocus ( ) ;
393
+ expect ( screen . getByText ( '5 Item ' ) ) . toHaveFocus ( ) ;
383
394
} ) ;
384
395
385
396
it ( 'Ignores keys that buttons don’t need to handle' , ( ) => {
@@ -401,9 +412,11 @@ it('Ignores keys that items don’t need to handle', () => {
401
412
skipClick : true ,
402
413
} ) ;
403
414
404
- userEvent . type ( screen . getByText ( 'Item 1 ' ) , 'Z' , {
415
+ userEvent . type ( screen . getByText ( '1 Item ' ) , 'Z' , {
405
416
skipClick : true ,
406
417
} ) ;
418
+
419
+ expect ( screen . getByText ( '1 Item' ) ) . toHaveFocus ( ) ;
407
420
} ) ;
408
421
409
422
it ( 'Doesn’t crash when enter press occurs on a menu item' , ( ) => {
@@ -415,7 +428,91 @@ it('Doesn’t crash when enter press occurs on a menu item', () => {
415
428
skipClick : true ,
416
429
} ) ;
417
430
418
- userEvent . type ( screen . getByText ( 'Item 1' ) , '{enter}' , {
431
+ userEvent . type ( screen . getByText ( '1 Item' ) , '{enter}' , {
432
+ skipClick : true ,
433
+ } ) ;
434
+ } ) ;
435
+
436
+ it ( 'Closes the menu after pressing enter on a menu item with a click handler' , ( ) => {
437
+ render ( < TestComponent /> ) ;
438
+
439
+ userEvent . tab ( ) ;
440
+
441
+ userEvent . type ( screen . getByText ( 'Primary' ) , '{enter}' , {
442
+ skipClick : true ,
443
+ } ) ;
444
+
445
+ userEvent . type ( screen . getByText ( '1 Item' ) , '{enter}' , {
446
+ skipClick : true ,
447
+ } ) ;
448
+
449
+ expect ( screen . getByTestId ( 'is-open-indicator' ) ) . toHaveTextContent ( 'false' ) ;
450
+ } ) ;
451
+
452
+ it ( 'Activates the click handler of a menu item after pressing enter while focused on it' , ( ) => {
453
+ render ( < TestComponent /> ) ;
454
+
455
+ jest . spyOn ( console , 'log' ) ;
456
+
457
+ userEvent . tab ( ) ;
458
+
459
+ userEvent . type ( screen . getByText ( 'Primary' ) , '{enter}' , {
460
+ skipClick : true ,
461
+ } ) ;
462
+
463
+ userEvent . type ( screen . getByText ( '1 Item' ) , '{enter}' , {
419
464
skipClick : true ,
420
465
} ) ;
466
+
467
+ expect ( console . log ) . toHaveBeenCalledWith ( 'Item one clicked' ) ;
468
+ } ) ;
469
+
470
+ it ( 'Closes the menu after pressing space on a menu item with a click handler' , ( ) => {
471
+ render ( < TestComponent /> ) ;
472
+
473
+ userEvent . tab ( ) ;
474
+
475
+ userEvent . type ( screen . getByText ( 'Primary' ) , '{enter}' , {
476
+ skipClick : true ,
477
+ } ) ;
478
+
479
+ userEvent . type ( screen . getByText ( '1 Item' ) , '{space}' , {
480
+ skipClick : true ,
481
+ } ) ;
482
+
483
+ expect ( screen . getByTestId ( 'is-open-indicator' ) ) . toHaveTextContent ( 'false' ) ;
484
+ } ) ;
485
+
486
+ it ( 'Activates the click handler of a menu item after pressing space while focused on it' , ( ) => {
487
+ render ( < TestComponent /> ) ;
488
+
489
+ jest . spyOn ( console , 'log' ) ;
490
+
491
+ userEvent . tab ( ) ;
492
+
493
+ userEvent . type ( screen . getByText ( 'Primary' ) , '{enter}' , {
494
+ skipClick : true ,
495
+ } ) ;
496
+
497
+ userEvent . type ( screen . getByText ( '1 Item' ) , '{space}' , {
498
+ skipClick : true ,
499
+ } ) ;
500
+
501
+ expect ( console . log ) . toHaveBeenCalledWith ( 'Item one clicked' ) ;
502
+ } ) ;
503
+
504
+ it ( 'Moves the focus to the menu item with a label that starts with the corresponding character that was pressed' , ( ) => {
505
+ render ( < TestComponent /> ) ;
506
+
507
+ userEvent . tab ( ) ;
508
+
509
+ userEvent . type ( screen . getByText ( 'Primary' ) , '{enter}' , {
510
+ skipClick : true ,
511
+ } ) ;
512
+
513
+ userEvent . type ( screen . getByText ( '1 Item' ) , '3' , {
514
+ skipClick : true ,
515
+ } ) ;
516
+
517
+ expect ( screen . getByText ( '3 Item' ) ) . toHaveFocus ( ) ;
421
518
} ) ;
0 commit comments