Skip to content

Commit 7fb956b

Browse files
authored
Merge pull request #272 from sparksuite/extra-keyboard-controls
Implement extra keyboard controls
2 parents 0ddd2f2 + 4667844 commit 7fb956b

File tree

4 files changed

+185
-70
lines changed

4 files changed

+185
-70
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-accessible-dropdown-menu-hook",
3-
"version": "2.2.2",
3+
"version": "2.3.0",
44
"description": "A simple Hook for creating fully accessible dropdown menus in React",
55
"main": "dist/use-dropdown-menu.js",
66
"types": "dist/use-dropdown-menu.d.ts",

src/use-dropdown-menu.test.tsx

+131-34
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import userEvent from '@testing-library/user-event';
66

77
// A mock component for testing the Hook
88
const TestComponent: React.FC = () => {
9-
const [itemCount, setItemCount] = useState(3);
9+
const [itemCount, setItemCount] = useState(4);
1010
const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(itemCount);
1111

12+
const clickHandlers: (() => void)[] = [(): void => console.log('Item one clicked'), (): void => setIsOpen(false)];
13+
1214
return (
1315
<React.Fragment>
1416
<button {...buttonProps} id='menu-button'>
@@ -21,10 +23,10 @@ const TestComponent: React.FC = () => {
2123
{...props}
2224
key={i}
2325
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}
2628
>
27-
Item {i + 1}
29+
{i + 1} Item
2830
</a>
2931
))}
3032
</div>
@@ -60,7 +62,7 @@ it('Moves the focus to the first menu item after pressing enter while focused on
6062
skipClick: true,
6163
});
6264

63-
expect(screen.getByText('Item 1')).toHaveFocus();
65+
expect(screen.getByText('1 Item')).toHaveFocus();
6466
});
6567

6668
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
7476
skipClick: true,
7577
});
7678

77-
expect(screen.getByText('Item 1')).toHaveFocus();
79+
expect(screen.getByText('1 Item')).toHaveFocus();
7880
});
7981

8082
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
8688

8789
userEvent.tab();
8890

89-
expect(screen.getByText('Item 1')).toHaveFocus();
91+
expect(screen.getByText('1 Item')).toHaveFocus();
9092
});
9193

9294
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
105107
})
106108
);
107109

108-
expect(screen.getByText('Item 1')).toHaveFocus();
110+
expect(screen.getByText('1 Item')).toHaveFocus();
109111
});
110112

111113
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
144146
render(<TestComponent />);
145147

146148
userEvent.click(screen.getByText('Primary'));
147-
userEvent.click(screen.getByText('Item 1'));
149+
userEvent.click(screen.getByText('2 Item'));
148150

149151
expect(screen.getByTestId('is-open-indicator')).toHaveTextContent('false');
150152
});
@@ -158,18 +160,18 @@ it('Moves the focus to the next element in the menu after pressing the down arro
158160
skipClick: true,
159161
});
160162

161-
expect(screen.getByText('Item 1')).toHaveFocus();
163+
expect(screen.getByText('1 Item')).toHaveFocus();
162164

163165
fireEvent(
164-
screen.getByText('Item 1'),
166+
screen.getByText('1 Item'),
165167
new KeyboardEvent('keydown', {
166168
key: 'ArrowDown',
167169
bubbles: true,
168170
cancelable: true,
169171
})
170172
);
171173

172-
expect(screen.getByText('Item 2')).toHaveFocus();
174+
expect(screen.getByText('2 Item')).toHaveFocus();
173175
});
174176

175177
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
181183
skipClick: true,
182184
});
183185

184-
expect(screen.getByText('Item 1')).toHaveFocus();
186+
expect(screen.getByText('1 Item')).toHaveFocus();
185187

186188
fireEvent(
187-
screen.getByText('Item 1'),
189+
screen.getByText('1 Item'),
188190
new KeyboardEvent('keydown', {
189191
key: 'ArrowDown',
190192
bubbles: true,
191193
cancelable: true,
192194
})
193195
);
194196

195-
expect(screen.getByText('Item 2')).toHaveFocus();
197+
expect(screen.getByText('2 Item')).toHaveFocus();
196198

197199
fireEvent(
198-
screen.getByText('Item 2'),
200+
screen.getByText('2 Item'),
199201
new KeyboardEvent('keydown', {
200202
key: 'ArrowUp',
201203
bubbles: true,
202204
cancelable: true,
203205
})
204206
);
205207

206-
expect(screen.getByText('Item 1')).toHaveFocus();
208+
expect(screen.getByText('1 Item')).toHaveFocus();
207209
});
208210

209211
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
215217
skipClick: true,
216218
});
217219

218-
expect(screen.getByText('Item 1')).toHaveFocus();
220+
expect(screen.getByText('1 Item')).toHaveFocus();
219221

220222
fireEvent(
221-
screen.getByText('Item 1'),
223+
screen.getByText('1 Item'),
222224
new KeyboardEvent('keydown', {
223225
key: 'ArrowUp',
224226
bubbles: true,
225227
cancelable: true,
226228
})
227229
);
228230

229-
expect(screen.getByText('Item 3')).toHaveFocus();
231+
expect(screen.getByText('4 Item')).toHaveFocus();
230232
});
231233

232234
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
238240
skipClick: true,
239241
});
240242

241-
expect(screen.getByText('Item 1')).toHaveFocus();
243+
expect(screen.getByText('1 Item')).toHaveFocus();
242244

243245
fireEvent(
244-
screen.getByText('Item 1'),
246+
screen.getByText('1 Item'),
245247
new KeyboardEvent('keydown', {
246248
key: 'ArrowUp',
247249
bubbles: true,
248250
cancelable: true,
249251
})
250252
);
251253

252-
expect(screen.getByText('Item 3')).toHaveFocus();
254+
expect(screen.getByText('4 Item')).toHaveFocus();
253255

254256
fireEvent(
255-
screen.getByText('Item 3'),
257+
screen.getByText('4 Item'),
256258
new KeyboardEvent('keydown', {
257259
key: 'ArrowDown',
258260
bubbles: true,
259261
cancelable: true,
260262
})
261263
);
262264

263-
expect(screen.getByText('Item 1')).toHaveFocus();
265+
expect(screen.getByText('1 Item')).toHaveFocus();
264266
});
265267

266268
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', ()
272274
skipClick: true,
273275
});
274276

275-
userEvent.type(screen.getByText('Item 1'), '{esc}', {
277+
userEvent.type(screen.getByText('1 Item'), '{esc}', {
276278
skipClick: true,
277279
});
278280

@@ -302,7 +304,7 @@ it('Moves the focus to the menu button after pressing escape while focused on a
302304
skipClick: true,
303305
});
304306

305-
userEvent.type(screen.getByText('Item 1'), '{esc}', {
307+
userEvent.type(screen.getByText('1 Item'), '{esc}', {
306308
skipClick: true,
307309
});
308310

@@ -333,7 +335,7 @@ it('Adds properties to items added after mount', () => {
333335

334336
userEvent.click(screen.getByText('Add Item'));
335337

336-
expect(screen.getByText('Item 4')).toHaveAttribute('role', 'menuitem');
338+
expect(screen.getByText('4 Item')).toHaveAttribute('role', 'menuitem');
337339
});
338340

339341
it('Can navigate to a dynamically-added item', () => {
@@ -353,7 +355,16 @@ it('Can navigate to a dynamically-added item', () => {
353355
);
354356

355357
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'),
357368
new KeyboardEvent('keydown', {
358369
key: 'ArrowDown',
359370
bubbles: true,
@@ -362,7 +373,7 @@ it('Can navigate to a dynamically-added item', () => {
362373
);
363374

364375
fireEvent(
365-
screen.getByText('Item 2'),
376+
screen.getByText('3 Item'),
366377
new KeyboardEvent('keydown', {
367378
key: 'ArrowDown',
368379
bubbles: true,
@@ -371,15 +382,15 @@ it('Can navigate to a dynamically-added item', () => {
371382
);
372383

373384
fireEvent(
374-
screen.getByText('Item 3'),
385+
screen.getByText('4 Item'),
375386
new KeyboardEvent('keydown', {
376387
key: 'ArrowDown',
377388
bubbles: true,
378389
cancelable: true,
379390
})
380391
);
381392

382-
expect(screen.getByText('Item 4')).toHaveFocus();
393+
expect(screen.getByText('5 Item')).toHaveFocus();
383394
});
384395

385396
it('Ignores keys that buttons don’t need to handle', () => {
@@ -401,9 +412,11 @@ it('Ignores keys that items don’t need to handle', () => {
401412
skipClick: true,
402413
});
403414

404-
userEvent.type(screen.getByText('Item 1'), 'Z', {
415+
userEvent.type(screen.getByText('1 Item'), 'Z', {
405416
skipClick: true,
406417
});
418+
419+
expect(screen.getByText('1 Item')).toHaveFocus();
407420
});
408421

409422
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', () => {
415428
skipClick: true,
416429
});
417430

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}', {
419464
skipClick: true,
420465
});
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();
421518
});

0 commit comments

Comments
 (0)