@@ -2,7 +2,7 @@ import * as React from 'react';
22import { Pressable , Text , TouchableOpacity , View } from 'react-native' ;
33
44import { configure , fireEvent , render , screen , waitFor } from '..' ;
5- import { useTimerType } from '../test-utils/timers' ;
5+ import { setupTimeType , TimerType } from '../test-utils/timers' ;
66
77beforeEach ( ( ) => {
88 jest . useRealTimers ( ) ;
@@ -24,26 +24,26 @@ test('waits for expect() assertion to pass', async () => {
2424 expect ( mockFunction ) . toHaveBeenCalledTimes ( 1 ) ;
2525} ) ;
2626
27- test . each ( [
28- { timerType : 'real' as const } ,
29- { timerType : 'fake' as const } ,
30- { timerType : 'fake-legacy' as const } ,
31- ] ) ( 'waits for query with $timerType timers' , async ( { timerType } ) => {
32- function AsyncComponent ( ) {
33- const [ text , setText ] = React . useState ( 'Loading...' ) ;
27+ test . each ( [ 'real' , 'fake' , 'fake-legacy' ] as const ) (
28+ 'waits for query with %s timers' ,
29+ async ( timerType ) => {
30+ setupTimeType ( timerType as TimerType ) ;
31+ function AsyncComponent ( ) {
32+ const [ text , setText ] = React . useState ( 'Loading...' ) ;
3433
35- React . useEffect ( ( ) => {
36- setTimeout ( ( ) => setText ( 'Loaded' ) , 100 ) ;
37- } , [ ] ) ;
34+ React . useEffect ( ( ) => {
35+ setTimeout ( ( ) => setText ( 'Loaded' ) , 100 ) ;
36+ } , [ ] ) ;
3837
39- return < Text > { text } </ Text > ;
40- }
38+ return < Text > { text } </ Text > ;
39+ }
4140
42- useTimerType ( timerType ) ;
43- await render ( < AsyncComponent /> ) ;
44- await waitFor ( ( ) => screen . getByText ( 'Loaded' ) ) ;
45- expect ( screen . getByText ( 'Loaded' ) ) . toBeOnTheScreen ( ) ;
46- } ) ;
41+ setupTimeType ( timerType ) ;
42+ await render ( < AsyncComponent /> ) ;
43+ await waitFor ( ( ) => screen . getByText ( 'Loaded' ) ) ;
44+ expect ( screen . getByText ( 'Loaded' ) ) . toBeOnTheScreen ( ) ;
45+ } ,
46+ ) ;
4747
4848test ( 'throws timeout error when condition never becomes true' , async ( ) => {
4949 function Component ( ) {
@@ -68,6 +68,17 @@ test('uses custom error from onTimeout callback when timeout occurs', async () =
6868 ) . rejects . toThrow ( customErrorMessage ) ;
6969} ) ;
7070
71+ test ( 'onTimeout callback returning falsy value keeps original error' , async ( ) => {
72+ await render ( < View /> ) ;
73+ // When onTimeout returns null/undefined/false, the original error should be kept (line 181 false branch)
74+ await expect (
75+ waitFor ( ( ) => screen . getByText ( 'Never appears' ) , {
76+ timeout : 100 ,
77+ onTimeout : ( ) => null as any ,
78+ } ) ,
79+ ) . rejects . toThrow ( 'Unable to find an element with text: Never appears' ) ;
80+ } ) ;
81+
7182test ( 'throws TypeError when expectation is not a function' , async ( ) => {
7283 await expect ( waitFor ( null as any ) ) . rejects . toThrow (
7384 'Received `expectation` arg must be a function' ,
@@ -152,7 +163,9 @@ test('throws timeout error with fake timers when condition never becomes true',
152163
153164 await jest . advanceTimersByTimeAsync ( 100 ) ;
154165
155- await expect ( waitForPromise ) . rejects . toThrow ( 'Unable to find an element with text: Never appears' ) ;
166+ await expect ( waitForPromise ) . rejects . toThrow (
167+ 'Unable to find an element with text: Never appears' ,
168+ ) ;
156169} ) ;
157170
158171test ( 'throws generic timeout error when promise rejects with falsy value until timeout' , async ( ) => {
@@ -178,73 +191,61 @@ test('waits for element with custom interval', async () => {
178191} ) ;
179192
180193test ( 'waitFor defaults to asyncUtilTimeout config option' , async ( ) => {
181- class BananaContainer extends React . Component < object , any > {
182- state = { fresh : false } ;
194+ function Component ( ) {
195+ const [ active , setActive ] = React . useState ( false ) ;
183196
184- onChangeFresh = async ( ) => {
185- await new Promise ( ( resolve ) => setTimeout ( resolve , 300 ) ) ;
186- this . setState ( { fresh : true } ) ;
197+ const handlePress = ( ) => {
198+ setTimeout ( ( ) => setActive ( true ) , 300 ) ;
187199 } ;
188200
189- render ( ) {
190- return (
191- < View >
192- { this . state . fresh && < Text > Fresh</ Text > }
193- < TouchableOpacity onPress = { this . onChangeFresh } >
194- < Text > Change freshness!</ Text >
195- </ TouchableOpacity >
196- </ View >
197- ) ;
198- }
201+ return (
202+ < View >
203+ { active && < Text > Active</ Text > }
204+ < Pressable onPress = { handlePress } >
205+ < Text > Activate</ Text >
206+ </ Pressable >
207+ </ View >
208+ ) ;
199209 }
200210
201211 configure ( { asyncUtilTimeout : 100 } ) ;
202- await render ( < BananaContainer /> ) ;
203-
204- fireEvent . press ( screen . getByText ( 'Change freshness!' ) ) ;
205-
206- expect ( screen . queryByText ( 'Fresh' ) ) . toBeNull ( ) ;
207-
208- await expect ( waitFor ( ( ) => screen . getByText ( 'Fresh' ) ) ) . rejects . toThrow ( ) ;
212+ await render ( < Component /> ) ;
213+ await fireEvent . press ( screen . getByText ( 'Activate' ) ) ;
214+ expect ( screen . queryByText ( 'Active' ) ) . toBeNull ( ) ;
215+ await expect ( waitFor ( ( ) => screen . getByText ( 'Active' ) ) ) . rejects . toThrow ( ) ;
209216
210217 // Async action ends after 300ms and we only waited 100ms, so we need to wait
211218 // for the remaining async actions to finish
212- await waitFor ( ( ) => screen . getByText ( 'Fresh ' ) , { timeout : 1000 } ) ;
219+ await waitFor ( ( ) => screen . getByText ( 'Active ' ) , { timeout : 1000 } ) ;
213220} ) ;
214221
215222test ( 'waitFor timeout option takes precedence over asyncUtilTimeout config option' , async ( ) => {
216- class BananaContainer extends React . Component < object , any > {
217- state = { fresh : false } ;
223+ function AsyncTextToggle ( ) {
224+ const [ active , setActive ] = React . useState ( false ) ;
218225
219- onChangeFresh = async ( ) => {
220- await new Promise ( ( resolve ) => setTimeout ( resolve , 300 ) ) ;
221- this . setState ( { fresh : true } ) ;
226+ const handlePress = ( ) => {
227+ setTimeout ( ( ) => setActive ( true ) , 300 ) ;
222228 } ;
223229
224- render ( ) {
225- return (
226- < View >
227- { this . state . fresh && < Text > Fresh</ Text > }
228- < TouchableOpacity onPress = { this . onChangeFresh } >
229- < Text > Change freshness!</ Text >
230- </ TouchableOpacity >
231- </ View >
232- ) ;
233- }
230+ return (
231+ < View >
232+ { active && < Text > Active</ Text > }
233+ < TouchableOpacity onPress = { handlePress } >
234+ < Text > Activate</ Text >
235+ </ TouchableOpacity >
236+ </ View >
237+ ) ;
234238 }
235239
236240 configure ( { asyncUtilTimeout : 2000 } ) ;
237- await render ( < BananaContainer /> ) ;
238-
239- fireEvent . press ( screen . getByText ( 'Change freshness!' ) ) ;
240-
241- expect ( screen . queryByText ( 'Fresh' ) ) . toBeNull ( ) ;
242-
243- await expect ( waitFor ( ( ) => screen . getByText ( 'Fresh' ) , { timeout : 100 } ) ) . rejects . toThrow ( ) ;
241+ await render ( < AsyncTextToggle /> ) ;
242+ await fireEvent . press ( screen . getByText ( 'Activate' ) ) ;
243+ expect ( screen . queryByText ( 'Active' ) ) . toBeNull ( ) ;
244+ await expect ( waitFor ( ( ) => screen . getByText ( 'Active' ) , { timeout : 100 } ) ) . rejects . toThrow ( ) ;
244245
245246 // Async action ends after 300ms and we only waited 100ms, so we need to wait
246247 // for the remaining async actions to finish
247- await waitFor ( ( ) => screen . getByText ( 'Fresh ' ) ) ;
248+ await waitFor ( ( ) => screen . getByText ( 'Active ' ) ) ;
248249} ) ;
249250
250251test ( 'waits for async event with fireEvent' , async ( ) => {
@@ -279,18 +280,12 @@ test('waits for async event with fireEvent', async () => {
279280 } ) ;
280281} ) ;
281282
282- test . each ( [
283- [ false , false ] ,
284- [ true , false ] ,
285- [ true , true ] ,
286- ] ) (
287- 'flushes scheduled updates before returning (fakeTimers = %s, legacyFakeTimers = %s)' ,
288- async ( fakeTimers , legacyFakeTimers ) => {
289- if ( fakeTimers ) {
290- jest . useFakeTimers ( { legacyFakeTimers } ) ;
291- }
283+ test . each ( [ 'real' , 'fake' , 'fake-legacy' ] as const ) (
284+ 'flushes scheduled updates before returning (timerType = %s)' ,
285+ async ( timerType ) => {
286+ setupTimeType ( timerType ) ;
292287
293- function Apple ( { onPress } : { onPress : ( color : string ) => void } ) {
288+ function Component ( { onPress } : { onPress : ( color : string ) => void } ) {
294289 const [ color , setColor ] = React . useState ( 'green' ) ;
295290 const [ syncedColor , setSyncedColor ] = React . useState ( color ) ;
296291
@@ -316,7 +311,7 @@ test.each([
316311 }
317312
318313 const onPress = jest . fn ( ) ;
319- await render ( < Apple onPress = { onPress } /> ) ;
314+ await render ( < Component onPress = { onPress } /> ) ;
320315
321316 // Required: this `waitFor` will succeed on first check, because the "root" view is there
322317 // since the initial mount.
@@ -332,27 +327,27 @@ test.each([
332327 } ,
333328) ;
334329
335- test . each ( [ true , false ] ) (
336- 'it should not depend on real time when using fake timers (legacyFakeTimers = %s) ' ,
337- async ( legacyFakeTimers ) => {
338- jest . useFakeTimers ( { legacyFakeTimers } ) ;
330+ test . each ( [ 'fake' , 'fake-legacy' ] as const ) (
331+ 'it should not depend on real time when using %s timers ' ,
332+ async ( timerType ) => {
333+ setupTimeType ( timerType ) ;
339334 const WAIT_FOR_INTERVAL = 20 ;
340335 const WAIT_FOR_TIMEOUT = WAIT_FOR_INTERVAL * 5 ;
341336
342- const blockThread = ( timeToBlockThread : number , legacyFakeTimers : boolean ) => {
337+ const blockThread = ( timeToBlockThread : number , timerType : TimerType ) : void => {
343338 jest . useRealTimers ( ) ;
344339 const end = Date . now ( ) + timeToBlockThread ;
345340
346341 while ( Date . now ( ) < end ) {
347342 // do nothing
348343 }
349344
350- jest . useFakeTimers ( { legacyFakeTimers } ) ;
345+ setupTimeType ( timerType ) ;
351346 } ;
352347
353348 const mockErrorFn = jest . fn ( ( ) => {
354349 // Wait 2 times interval so that check time is longer than interval
355- blockThread ( WAIT_FOR_INTERVAL * 2 , legacyFakeTimers ) ;
350+ blockThread ( WAIT_FOR_INTERVAL * 2 , timerType ) ;
356351 throw new Error ( 'test' ) ;
357352 } ) ;
358353
@@ -371,10 +366,10 @@ test.each([true, false])(
371366 } ,
372367) ;
373368
374- test . each ( [ false , true ] ) (
375- 'waits for assertion until timeout is met with fake timers and interval (legacyFakeTimers = %s) ' ,
376- async ( legacyFakeTimers ) => {
377- jest . useFakeTimers ( { legacyFakeTimers } ) ;
369+ test . each ( [ 'fake' , 'fake-legacy' ] as const ) (
370+ 'waits for assertion until timeout is met with %s timers and interval' ,
371+ async ( timerType ) => {
372+ setupTimeType ( timerType ) ;
378373
379374 const mockFn = jest . fn ( ( ) => {
380375 throw Error ( 'test' ) ;
@@ -390,10 +385,10 @@ test.each([false, true])(
390385 } ,
391386) ;
392387
393- test . each ( [ false , true ] ) (
394- 'waits for assertion until timeout is met with fake timers, interval, and onTimeout (legacyFakeTimers = %s) ' ,
395- async ( legacyFakeTimers ) => {
396- jest . useFakeTimers ( { legacyFakeTimers } ) ;
388+ test . each ( [ 'fake' , 'fake-legacy' ] as const ) (
389+ 'waits for assertion until timeout is met with %s timers, interval, and onTimeout' ,
390+ async ( timerType ) => {
391+ setupTimeType ( timerType ) ;
397392
398393 const mockErrorFn = jest . fn ( ( ) => {
399394 throw Error ( 'test' ) ;
0 commit comments