1
1
import React from 'react'
2
2
import { render , fireEvent , cleanup } from '@testing-library/react'
3
- import { useTypeaheadFocus } from '../../hooks'
4
- import { TYPEAHEAD_TIMEOUT } from '../../hooks/useTypeaheadFocus'
3
+ import { useMnemonics } from '../../hooks'
5
4
6
- const Component = ( {
5
+ const Fixture = ( {
7
6
onSelect = ( ) => null ,
8
7
hasInput = false ,
9
8
refNotAttached = false
@@ -13,7 +12,7 @@ const Component = ({
13
12
refNotAttached ?: boolean
14
13
} ) => {
15
14
const containerRef = React . createRef < HTMLDivElement > ( )
16
- useTypeaheadFocus ( true , containerRef ) // hard coding open=true for test
15
+ useMnemonics ( true , containerRef ) // hard coding open=true for test
17
16
18
17
return (
19
18
< >
@@ -22,9 +21,12 @@ const Component = ({
22
21
{ hasInput && < input autoFocus type = "text" placeholder = "Filter options" /> }
23
22
< button onKeyDown = { onSelect } > button 1</ button >
24
23
< button onKeyDown = { onSelect } > Button 2</ button >
25
- < button disabled > button 3 is disabled</ button >
26
- < button onKeyDown = { onSelect } > button 4</ button >
27
24
< button onKeyDown = { onSelect } > third button</ button >
25
+ < button disabled > fourth button is disabled</ button >
26
+ < button onKeyDown = { onSelect } > button 5</ button >
27
+ < button onKeyDown = { onSelect } aria-keyshortcuts = "6 E" >
28
+ button 6
29
+ </ button >
28
30
< span > not focusable</ span >
29
31
</ div >
30
32
</ >
@@ -35,31 +37,31 @@ describe('useTypeaheadFocus', () => {
35
37
afterEach ( cleanup )
36
38
37
39
it ( 'First element: when b is pressed, it should move focus the "b"utton 1' , ( ) => {
38
- const { getByTestId, getByText} = render ( < Component /> )
40
+ const { getByTestId, getByText} = render ( < Fixture /> )
39
41
const container = getByTestId ( 'container' )
40
42
41
43
fireEvent . keyDown ( container , { key : 'b' , code : 'b' } )
42
44
expect ( getByText ( 'button 1' ) ) . toEqual ( document . activeElement )
43
45
} )
44
46
45
47
it ( 'Not first element: when t is pressed, it should move focus the "t"hird button' , ( ) => {
46
- const { getByTestId, getByText} = render ( < Component /> )
48
+ const { getByTestId, getByText} = render ( < Fixture /> )
47
49
const container = getByTestId ( 'container' )
48
50
49
51
fireEvent . keyDown ( container , { key : 't' , code : 't' } )
50
52
expect ( getByText ( 'third button' ) ) . toEqual ( document . activeElement )
51
53
} )
52
54
53
55
it ( 'Case insensitive: when B is pressed, it should move focus the "b"utton 1' , ( ) => {
54
- const { getByTestId, getByText} = render ( < Component /> )
56
+ const { getByTestId, getByText} = render ( < Fixture /> )
55
57
const container = getByTestId ( 'container' )
56
58
57
59
fireEvent . keyDown ( container , { key : 'B' , code : 'B' } )
58
60
expect ( getByText ( 'button 1' ) ) . toEqual ( document . activeElement )
59
61
} )
60
62
61
63
it ( 'Repeating letter: when b is pressed repeatedly, it should wrap through the buttons starting with "b", skipping the disabled button' , ( ) => {
62
- const { getByTestId, getByText} = render ( < Component /> )
64
+ const { getByTestId, getByText} = render ( < Fixture /> )
63
65
const container = getByTestId ( 'container' )
64
66
65
67
fireEvent . keyDown ( container , { key : 'b' , code : 'b' } )
@@ -69,65 +71,61 @@ describe('useTypeaheadFocus', () => {
69
71
expect ( getByText ( 'Button 2' ) ) . toEqual ( document . activeElement )
70
72
71
73
fireEvent . keyDown ( container , { key : 'b' , code : 'b' } )
72
- expect ( getByText ( 'button 4 ' ) ) . toEqual ( document . activeElement )
74
+ expect ( getByText ( 'button 5 ' ) ) . toEqual ( document . activeElement )
73
75
76
+ // should cycle back to start of the list
74
77
fireEvent . keyDown ( container , { key : 'b' , code : 'b' } )
75
78
expect ( getByText ( 'button 1' ) ) . toEqual ( document . activeElement )
76
79
} )
77
80
78
- it ( 'Timeout: when presses b, waits and presses t, it should move focus the "t"hird button' , async ( ) => {
79
- jest . useFakeTimers ( )
80
- const { getByTestId, getByText} = render ( < Component /> )
81
+ it ( 'User defined aria-keyshortcuts: Should catch all shortcuts defined by user' , ( ) => {
82
+ const { getByTestId, getByText} = render ( < Fixture /> )
81
83
const container = getByTestId ( 'container' )
82
84
83
- fireEvent . keyDown ( container , { key : 'b' , code : 'b' } )
84
- expect ( getByText ( 'button 1' ) ) . toEqual ( document . activeElement )
85
-
86
- // if we press t now, the query would be "bt",
87
- // and focus will stay wherever it is
88
- fireEvent . keyDown ( container , { key : 't' , code : 't' } )
89
- expect ( getByText ( 'button 1' ) ) . toEqual ( document . activeElement )
85
+ fireEvent . keyDown ( container , { key : '6' , code : '6' } )
86
+ expect ( getByText ( 'button 6' ) ) . toEqual ( document . activeElement )
90
87
91
- // but, if we simulate typeahead timeout, then type t
92
- // it should jump to the third button
93
- jest . advanceTimersByTime ( TYPEAHEAD_TIMEOUT )
88
+ // send focus elsewhere
94
89
fireEvent . keyDown ( container , { key : 't' , code : 't' } )
95
90
expect ( getByText ( 'third button' ) ) . toEqual ( document . activeElement )
91
+
92
+ fireEvent . keyDown ( container , { key : 'e' , code : 'e' } )
93
+ expect ( getByText ( 'button 6' ) ) . toEqual ( document . activeElement )
96
94
} )
97
95
98
- it ( 'Space: when user is typing and presses Space, it should not select the option' , ( ) => {
99
- const mockFunction = jest . fn ( )
100
- const { getByTestId, getByText} = render ( < Component onSelect = { mockFunction } /> )
96
+ it ( 'aria-keyshortcuts: it should add aria-keyshortcuts to focusable items' , ( ) => {
97
+ const { getByText} = render ( < Fixture /> )
101
98
102
- const container = getByTestId ( 'container' )
103
- fireEvent . keyDown ( container , { key : 't' , code : 't' } )
99
+ expect ( getByText ( 'button 1' ) ) . toHaveAttribute ( 'aria-keyshortcuts' , 'b' )
100
+ expect ( getByText ( 'Button 2' ) ) . toHaveAttribute ( 'aria-keyshortcuts' , 'b' )
101
+ expect ( getByText ( 'third button' ) ) . toHaveAttribute ( 'aria-keyshortcuts' , 't' )
102
+ expect ( getByText ( 'button 5' ) ) . toHaveAttribute ( 'aria-keyshortcuts' , 'b' )
104
103
105
- const thirdButton = getByText ( 'third button' )
106
- expect ( thirdButton ) . toEqual ( document . activeElement )
107
- fireEvent . keyDown ( thirdButton , { key : ' ' , code : 'Space' } )
108
- expect ( mockFunction ) . not . toHaveBeenCalled ( )
104
+ // don't overwrite aria-keyshortcuts if it's already defined
105
+ expect ( getByText ( 'button 6' ) ) . toHaveAttribute ( 'aria-keyshortcuts' , '6 E' )
106
+
107
+ // not focusable items should not have aria-keyshortcuts
108
+ expect ( getByText ( 'fourth button is disabled' ) ) . not . toHaveAttribute ( 'aria-keyshortcuts' )
109
+ expect ( getByText ( 'not focusable' ) ) . not . toHaveAttribute ( 'aria-keyshortcuts' )
109
110
} )
110
111
111
- it ( 'Space after timeout: when user is presses Space after waiting, it should select the option' , ( ) => {
112
- jest . useFakeTimers ( )
112
+ it ( 'Space: when user presses Space, it should select the option' , ( ) => {
113
113
const mockFunction = jest . fn ( )
114
- const { getByTestId, getByText} = render ( < Component onSelect = { mockFunction } /> )
114
+ const { getByTestId, getByText} = render ( < Fixture onSelect = { mockFunction } /> )
115
115
116
116
const container = getByTestId ( 'container' )
117
117
fireEvent . keyDown ( container , { key : 't' , code : 't' } )
118
118
119
119
const thirdButton = getByText ( 'third button' )
120
120
expect ( thirdButton ) . toEqual ( document . activeElement )
121
-
122
- jest . advanceTimersByTime ( TYPEAHEAD_TIMEOUT )
123
121
fireEvent . keyDown ( thirdButton , { key : ' ' , code : 'Space' } )
124
122
expect ( mockFunction ) . toHaveBeenCalled ( )
125
123
} )
126
124
127
- it ( 'Enter: when user is presses Enter, it should instantly select the option' , ( ) => {
125
+ it ( 'Enter: when user is presses Enter, it should select the option' , ( ) => {
128
126
jest . useFakeTimers ( )
129
127
const mockFunction = jest . fn ( )
130
- const { getByTestId, getByText} = render ( < Component onSelect = { mockFunction } /> )
128
+ const { getByTestId, getByText} = render ( < Fixture onSelect = { mockFunction } /> )
131
129
132
130
const container = getByTestId ( 'container' )
133
131
fireEvent . keyDown ( container , { key : 't' , code : 't' } )
@@ -139,44 +137,16 @@ describe('useTypeaheadFocus', () => {
139
137
expect ( mockFunction ) . toHaveBeenCalled ( )
140
138
} )
141
139
142
- it ( 'Long string: when user starts typing a longer string "button 4", focus should jump to closest match' , async ( ) => {
143
- jest . useFakeTimers ( )
144
- const mockFunction = jest . fn ( )
145
- const { getByTestId, getByText} = render ( < Component onSelect = { mockFunction } /> )
146
- const container = getByTestId ( 'container' )
147
-
148
- fireEvent . keyDown ( container , { key : 'b' , code : 'b' } )
149
- expect ( getByText ( 'button 1' ) ) . toEqual ( document . activeElement )
150
-
151
- fireEvent . keyDown ( container , { key : 'u' , code : 'u' } )
152
- expect ( getByText ( 'button 1' ) ) . toEqual ( document . activeElement )
153
-
154
- fireEvent . keyDown ( container , { key : 't' , code : 't' } )
155
- fireEvent . keyDown ( container , { key : 't' , code : 't' } )
156
- fireEvent . keyDown ( container , { key : 'o' , code : 'o' } )
157
- fireEvent . keyDown ( container , { key : 'n' , code : 'n' } )
158
-
159
- // pressing Space should be treated as part of query
160
- // and shouldn't select the option
161
- fireEvent . keyDown ( container , { key : ' ' , code : 'Space' } )
162
- expect ( mockFunction ) . not . toHaveBeenCalled ( )
163
- expect ( getByText ( 'button 1' ) ) . toEqual ( document . activeElement )
164
-
165
- fireEvent . keyDown ( container , { key : '4' , code : '4' } )
166
- // the query is now "button 4" and should move focus
167
- expect ( getByText ( 'button 4' ) ) . toEqual ( document . activeElement )
168
- } )
169
-
170
140
it ( 'Shortcuts: when a modifier is used, typeahead should not do anything' , ( ) => {
171
- const { getByTestId, getByText} = render ( < Component /> )
141
+ const { getByTestId, getByText} = render ( < Fixture /> )
172
142
const container = getByTestId ( 'container' )
173
143
174
144
fireEvent . keyDown ( container , { metaKey : true , key : 'b' , code : 'b' } )
175
145
expect ( getByText ( 'button 1' ) ) . not . toEqual ( document . activeElement )
176
146
} )
177
147
178
148
it ( 'TextInput: when an textinput has focus, typeahead should not do anything' , async ( ) => {
179
- const { getByTestId, getByPlaceholderText} = render ( < Component hasInput = { true } /> )
149
+ const { getByTestId, getByPlaceholderText} = render ( < Fixture hasInput = { true } /> )
180
150
const container = getByTestId ( 'container' )
181
151
182
152
const input = getByPlaceholderText ( 'Filter options' )
@@ -187,7 +157,7 @@ describe('useTypeaheadFocus', () => {
187
157
} )
188
158
189
159
it ( 'Missing ref: when a ref is not attached, typeahead should break the component' , async ( ) => {
190
- const { getByTestId, getByText} = render ( < Component refNotAttached = { true } /> )
160
+ const { getByTestId, getByText} = render ( < Fixture refNotAttached = { true } /> )
191
161
const container = getByTestId ( 'container' )
192
162
193
163
fireEvent . keyDown ( container , { key : 'b' , code : 'b' } )
0 commit comments