11import userEvent from '@testing-library/user-event' ;
22
33import { AutocompleteState } from '..' ;
4- import { createSource , defer } from '../../../../test/utils' ;
4+ import { createPlayground , createSource , defer } from '../../../../test/utils' ;
55import { createAutocomplete } from '../createAutocomplete' ;
66
77type Item = {
88 label : string ;
99} ;
1010
11+ beforeEach ( ( ) => {
12+ document . body . innerHTML = '' ;
13+ } ) ;
14+
1115describe ( 'concurrency' , ( ) => {
1216 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+
3723 const onStateChange = jest . fn ( ) ;
3824 const autocomplete = createAutocomplete ( { getSources, onStateChange } ) ;
3925 const { onChange } = autocomplete . getInputProps ( { inputElement : null } ) ;
@@ -45,10 +31,6 @@ describe('concurrency', () => {
4531 userEvent . type ( input , 'b' ) ;
4632 userEvent . type ( input , 'c' ) ;
4733
48- const timeout = Math . max (
49- ...sourcesDelays . map ( ( delay , index ) => delay + itemsDelays [ index ] )
50- ) ;
51-
5234 await defer ( ( ) => { } , timeout ) ;
5335
5436 let stateHistory : Array <
@@ -91,4 +73,258 @@ describe('concurrency', () => {
9173
9274 document . body . removeChild ( input ) ;
9375 } ) ;
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+ } ) ;
94295} ) ;
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