1
1
import userEvent from '@testing-library/user-event' ;
2
2
3
3
import { AutocompleteState } from '..' ;
4
- import { createSource , defer } from '../../../../test/utils' ;
4
+ import { createPlayground , createSource , defer } from '../../../../test/utils' ;
5
5
import { createAutocomplete } from '../createAutocomplete' ;
6
6
7
7
type Item = {
8
8
label : string ;
9
9
} ;
10
10
11
+ beforeEach ( ( ) => {
12
+ document . body . innerHTML = '' ;
13
+ } ) ;
14
+
11
15
describe ( 'concurrency' , ( ) => {
12
16
test ( 'resolves the responses in order from getSources' , async ( ) => {
13
- // These delays make the second query come back after the third one.
14
- const sourcesDelays = [ 100 , 150 , 200 ] ;
15
- const itemsDelays = [ 0 , 150 , 0 ] ;
16
- let deferSourcesCount = - 1 ;
17
- let deferItemsCount = - 1 ;
18
-
19
- const getSources = ( { query } ) => {
20
- deferSourcesCount ++ ;
21
-
22
- return defer ( ( ) => {
23
- return [
24
- createSource ( {
25
- getItems ( ) {
26
- deferItemsCount ++ ;
27
-
28
- return defer (
29
- ( ) => [ { label : query } ] ,
30
- itemsDelays [ deferItemsCount ]
31
- ) ;
32
- } ,
33
- } ) ,
34
- ] ;
35
- } , sourcesDelays [ deferSourcesCount ] ) ;
36
- } ;
17
+ const { timeout, delayedGetSources : getSources } = createDelayedGetSources ( {
18
+ // These delays make the second query come back after the third one.
19
+ sources : [ 100 , 150 , 200 ] ,
20
+ items : [ 0 , 150 , 0 ] ,
21
+ } ) ;
22
+
37
23
const onStateChange = jest . fn ( ) ;
38
24
const autocomplete = createAutocomplete ( { getSources, onStateChange } ) ;
39
25
const { onChange } = autocomplete . getInputProps ( { inputElement : null } ) ;
@@ -45,10 +31,6 @@ describe('concurrency', () => {
45
31
userEvent . type ( input , 'b' ) ;
46
32
userEvent . type ( input , 'c' ) ;
47
33
48
- const timeout = Math . max (
49
- ...sourcesDelays . map ( ( delay , index ) => delay + itemsDelays [ index ] )
50
- ) ;
51
-
52
34
await defer ( ( ) => { } , timeout ) ;
53
35
54
36
let stateHistory : Array <
@@ -91,4 +73,258 @@ describe('concurrency', () => {
91
73
92
74
document . body . removeChild ( input ) ;
93
75
} ) ;
76
+
77
+ describe ( 'closing the panel with pending requests' , ( ) => {
78
+ describe ( 'without debug mode' , ( ) => {
79
+ test ( 'keeps the panel closed on Escape' , async ( ) => {
80
+ const onStateChange = jest . fn ( ) ;
81
+ const { timeout, delayedGetSources } = createDelayedGetSources ( {
82
+ sources : [ 100 , 200 ] ,
83
+ } ) ;
84
+ const getSources = jest . fn ( delayedGetSources ) ;
85
+
86
+ const { inputElement } = createPlayground ( createAutocomplete , {
87
+ onStateChange,
88
+ getSources,
89
+ } ) ;
90
+
91
+ userEvent . type ( inputElement , 'ab{esc}' ) ;
92
+
93
+ await defer ( ( ) => { } , timeout ) ;
94
+
95
+ expect ( onStateChange ) . toHaveBeenLastCalledWith (
96
+ expect . objectContaining ( {
97
+ state : expect . objectContaining ( {
98
+ isOpen : false ,
99
+ status : 'idle' ,
100
+ } ) ,
101
+ } )
102
+ ) ;
103
+ expect ( getSources ) . toHaveBeenCalledTimes ( 2 ) ;
104
+ } ) ;
105
+
106
+ test ( 'keeps the panel closed on blur' , async ( ) => {
107
+ const onStateChange = jest . fn ( ) ;
108
+ const { timeout, delayedGetSources } = createDelayedGetSources ( {
109
+ sources : [ 100 , 200 ] ,
110
+ } ) ;
111
+ const getSources = jest . fn ( delayedGetSources ) ;
112
+
113
+ const { inputElement } = createPlayground ( createAutocomplete , {
114
+ onStateChange,
115
+ getSources,
116
+ } ) ;
117
+
118
+ userEvent . type ( inputElement , 'a{enter}' ) ;
119
+
120
+ await defer ( ( ) => { } , timeout ) ;
121
+
122
+ expect ( onStateChange ) . toHaveBeenLastCalledWith (
123
+ expect . objectContaining ( {
124
+ state : expect . objectContaining ( {
125
+ isOpen : false ,
126
+ status : 'idle' ,
127
+ } ) ,
128
+ } )
129
+ ) ;
130
+ expect ( getSources ) . toHaveBeenCalledTimes ( 1 ) ;
131
+ } ) ;
132
+
133
+ test ( 'keeps the panel closed on touchstart blur' , async ( ) => {
134
+ const onStateChange = jest . fn ( ) ;
135
+ const { timeout, delayedGetSources } = createDelayedGetSources ( {
136
+ sources : [ 100 , 200 ] ,
137
+ } ) ;
138
+ const getSources = jest . fn ( delayedGetSources ) ;
139
+
140
+ const {
141
+ getEnvironmentProps,
142
+ inputElement,
143
+ formElement,
144
+ } = createPlayground ( createAutocomplete , {
145
+ onStateChange,
146
+ getSources,
147
+ } ) ;
148
+
149
+ const panelElement = document . createElement ( 'div' ) ;
150
+
151
+ const { onTouchStart } = getEnvironmentProps ( {
152
+ inputElement,
153
+ formElement,
154
+ panelElement,
155
+ } ) ;
156
+ window . addEventListener ( 'touchstart' , onTouchStart ) ;
157
+
158
+ userEvent . type ( inputElement , 'a' ) ;
159
+ const customEvent = new CustomEvent ( 'touchstart' , { bubbles : true } ) ;
160
+ window . document . dispatchEvent ( customEvent ) ;
161
+
162
+ await defer ( ( ) => { } , timeout ) ;
163
+
164
+ expect ( onStateChange ) . toHaveBeenLastCalledWith (
165
+ expect . objectContaining ( {
166
+ state : expect . objectContaining ( {
167
+ isOpen : false ,
168
+ status : 'idle' ,
169
+ } ) ,
170
+ } )
171
+ ) ;
172
+ expect ( getSources ) . toHaveBeenCalledTimes ( 1 ) ;
173
+
174
+ window . removeEventListener ( 'touchstart' , onTouchStart ) ;
175
+ } ) ;
176
+ } ) ;
177
+
178
+ describe ( 'with debug mode' , ( ) => {
179
+ const delay = 300 ;
180
+
181
+ test ( 'keeps the panel closed on Escape' , async ( ) => {
182
+ const onStateChange = jest . fn ( ) ;
183
+ const getSources = jest . fn ( ( ) => {
184
+ return defer ( ( ) => {
185
+ return [
186
+ createSource ( {
187
+ getItems : ( ) => [ { label : '1' } , { label : '2' } ] ,
188
+ } ) ,
189
+ ] ;
190
+ } , delay ) ;
191
+ } ) ;
192
+ const { inputElement } = createPlayground ( createAutocomplete , {
193
+ debug : true ,
194
+ onStateChange,
195
+ getSources,
196
+ } ) ;
197
+
198
+ userEvent . type ( inputElement , 'a{esc}' ) ;
199
+
200
+ await defer ( ( ) => { } , delay ) ;
201
+
202
+ expect ( onStateChange ) . toHaveBeenLastCalledWith (
203
+ expect . objectContaining ( {
204
+ state : expect . objectContaining ( {
205
+ isOpen : false ,
206
+ status : 'idle' ,
207
+ } ) ,
208
+ } )
209
+ ) ;
210
+ expect ( getSources ) . toHaveBeenCalledTimes ( 1 ) ;
211
+ } ) ;
212
+
213
+ test ( 'keeps the panel open on blur' , async ( ) => {
214
+ const onStateChange = jest . fn ( ) ;
215
+ const getSources = jest . fn ( ( ) => {
216
+ return defer ( ( ) => {
217
+ return [
218
+ createSource ( {
219
+ getItems : ( ) => [ { label : '1' } , { label : '2' } ] ,
220
+ } ) ,
221
+ ] ;
222
+ } , delay ) ;
223
+ } ) ;
224
+ const { inputElement } = createPlayground ( createAutocomplete , {
225
+ debug : true ,
226
+ onStateChange,
227
+ getSources,
228
+ } ) ;
229
+
230
+ userEvent . type ( inputElement , 'a{enter}' ) ;
231
+
232
+ await defer ( ( ) => { } , delay ) ;
233
+
234
+ expect ( onStateChange ) . toHaveBeenLastCalledWith (
235
+ expect . objectContaining ( {
236
+ state : expect . objectContaining ( {
237
+ isOpen : true ,
238
+ status : 'idle' ,
239
+ } ) ,
240
+ } )
241
+ ) ;
242
+ expect ( getSources ) . toHaveBeenCalledTimes ( 1 ) ;
243
+ } ) ;
244
+
245
+ test ( 'keeps the panel open on touchstart blur' , async ( ) => {
246
+ const onStateChange = jest . fn ( ) ;
247
+ const getSources = jest . fn ( ( ) => {
248
+ return defer ( ( ) => {
249
+ return [
250
+ createSource ( {
251
+ getItems : ( ) => [ { label : '1' } , { label : '2' } ] ,
252
+ } ) ,
253
+ ] ;
254
+ } , delay ) ;
255
+ } ) ;
256
+ const {
257
+ getEnvironmentProps,
258
+ inputElement,
259
+ formElement,
260
+ } = createPlayground ( createAutocomplete , {
261
+ debug : true ,
262
+ onStateChange,
263
+ getSources,
264
+ } ) ;
265
+
266
+ const panelElement = document . createElement ( 'div' ) ;
267
+
268
+ const { onTouchStart } = getEnvironmentProps ( {
269
+ inputElement,
270
+ formElement,
271
+ panelElement,
272
+ } ) ;
273
+ window . addEventListener ( 'touchstart' , onTouchStart ) ;
274
+
275
+ userEvent . type ( inputElement , 'a' ) ;
276
+ const customEvent = new CustomEvent ( 'touchstart' , { bubbles : true } ) ;
277
+ window . document . dispatchEvent ( customEvent ) ;
278
+
279
+ await defer ( ( ) => { } , delay ) ;
280
+
281
+ expect ( onStateChange ) . toHaveBeenLastCalledWith (
282
+ expect . objectContaining ( {
283
+ state : expect . objectContaining ( {
284
+ isOpen : true ,
285
+ status : 'idle' ,
286
+ } ) ,
287
+ } )
288
+ ) ;
289
+ expect ( getSources ) . toHaveBeenCalledTimes ( 1 ) ;
290
+
291
+ window . removeEventListener ( 'touchstart' , onTouchStart ) ;
292
+ } ) ;
293
+ } ) ;
294
+ } ) ;
94
295
} ) ;
296
+
297
+ function createDelayedGetSources ( delays : {
298
+ sources : number [ ] ;
299
+ items ?: number [ ] ;
300
+ } ) {
301
+ let deferSourcesCount = - 1 ;
302
+ let deferItemsCount = - 1 ;
303
+
304
+ const itemsDelays = delays . items || delays . sources . map ( ( ) => 0 ) ;
305
+
306
+ const timeout = Math . max (
307
+ ...delays . sources . map ( ( delay , index ) => delay + itemsDelays [ index ] )
308
+ ) ;
309
+
310
+ function delayedGetSources ( { query } ) {
311
+ deferSourcesCount ++ ;
312
+
313
+ return defer ( ( ) => {
314
+ return [
315
+ createSource ( {
316
+ getItems ( ) {
317
+ deferItemsCount ++ ;
318
+
319
+ return defer (
320
+ ( ) => [ { label : query } ] ,
321
+ itemsDelays [ deferItemsCount ]
322
+ ) ;
323
+ } ,
324
+ } ) ,
325
+ ] ;
326
+ } , delays . sources [ deferSourcesCount ] ) ;
327
+ }
328
+
329
+ return { timeout, delayedGetSources } ;
330
+ }
0 commit comments