Skip to content

Commit 36c64a1

Browse files
mpeyperLei Chen
authored and
Lei Chen
committed
test(fake-timers): add more tests to test suite for fake timers
fix the code after code review and clean up fix the test timeout is false clean up
1 parent 035b437 commit 36c64a1

File tree

3 files changed

+300
-25
lines changed

3 files changed

+300
-25
lines changed

src/__tests__/asyncHook.fakeTimers.test.ts

+258-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,27 @@
1+
import { useState, useRef, useEffect } from 'react'
2+
13
describe('async hook (fake timers) tests', () => {
4+
const useSequence = (values: string[], intervalMs = 50) => {
5+
const [first, ...otherValues] = values
6+
const [value, setValue] = useState(() => first)
7+
const index = useRef(0)
8+
9+
useEffect(() => {
10+
const interval = setInterval(() => {
11+
setValue(otherValues[index.current++])
12+
if (index.current >= otherValues.length) {
13+
clearInterval(interval)
14+
}
15+
}, intervalMs)
16+
return () => {
17+
clearInterval(interval)
18+
}
19+
// eslint-disable-next-line react-hooks/exhaustive-deps
20+
}, otherValues)
21+
22+
return value
23+
}
24+
225
beforeEach(() => {
326
jest.useFakeTimers()
427
})
@@ -70,7 +93,7 @@ describe('async hook (fake timers) tests', () => {
7093

7194
setTimeout(() => {
7295
actual = expected
73-
}, 101)
96+
}, 30)
7497

7598
let complete = false
7699

@@ -80,14 +103,243 @@ describe('async hook (fake timers) tests', () => {
80103
expect(actual).toBe(expected)
81104
complete = true
82105
},
83-
{ timeout: 100, interval: 50 }
106+
{ timeout: 29, interval: 10 }
84107
)
85-
).rejects.toThrow(Error('Timed out in waitFor after 100ms.'))
108+
).rejects.toThrow(Error('Timed out in waitFor after 29ms.'))
86109

87110
expect(complete).toBe(false)
88111
})
112+
113+
test('should wait for next update', async () => {
114+
const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second']))
115+
116+
expect(result.current).toBe('first')
117+
118+
await waitForNextUpdate()
119+
120+
expect(result.current).toBe('second')
121+
})
122+
123+
test('should wait for multiple updates', async () => {
124+
const { result, waitForNextUpdate } = renderHook(() =>
125+
useSequence(['first', 'second', 'third'])
126+
)
127+
128+
expect(result.current).toBe('first')
129+
130+
await waitForNextUpdate()
131+
132+
expect(result.current).toBe('second')
133+
134+
await waitForNextUpdate()
135+
136+
expect(result.current).toBe('third')
137+
})
138+
139+
test('should reject if timeout exceeded when waiting for next update', async () => {
140+
const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second']))
141+
142+
expect(result.current).toBe('first')
143+
144+
await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow(
145+
Error('Timed out in waitForNextUpdate after 10ms.')
146+
)
147+
})
148+
149+
test('should not reject when waiting for next update if timeout has been disabled', async () => {
150+
const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'], 1100))
151+
152+
expect(result.current).toBe('first')
153+
154+
await waitForNextUpdate({ timeout: false })
155+
156+
expect(result.current).toBe('second')
157+
})
158+
159+
test('should wait for expectation to pass', async () => {
160+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third']))
161+
162+
expect(result.current).toBe('first')
163+
164+
let complete = false
165+
await waitFor(() => {
166+
expect(result.current).toBe('third')
167+
complete = true
168+
})
169+
expect(complete).toBe(true)
170+
})
171+
172+
test('should wait for arbitrary expectation to pass', async () => {
173+
const { waitFor } = renderHook(() => null)
174+
175+
let actual = 0
176+
const expected = 1
177+
178+
setTimeout(() => {
179+
actual = expected
180+
}, 200)
181+
182+
let complete = false
183+
await waitFor(() => {
184+
expect(actual).toBe(expected)
185+
complete = true
186+
})
187+
188+
expect(complete).toBe(true)
189+
})
190+
191+
test('should not hang if expectation is already passing', async () => {
192+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second']))
193+
194+
expect(result.current).toBe('first')
195+
196+
let complete = false
197+
await waitFor(() => {
198+
expect(result.current).toBe('first')
199+
complete = true
200+
})
201+
expect(complete).toBe(true)
202+
})
203+
204+
test('should wait for truthy value', async () => {
205+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third']))
206+
207+
expect(result.current).toBe('first')
208+
209+
await waitFor(() => result.current === 'third')
210+
211+
expect(result.current).toBe('third')
212+
})
213+
214+
test('should wait for arbitrary truthy value', async () => {
215+
const { waitFor } = renderHook(() => null)
216+
217+
let actual = 0
218+
const expected = 1
219+
220+
setTimeout(() => {
221+
actual = expected
222+
}, 200)
223+
224+
await waitFor(() => actual === 1)
225+
226+
expect(actual).toBe(expected)
227+
})
228+
229+
test('should reject if timeout exceeded when waiting for expectation to pass', async () => {
230+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third']))
231+
232+
expect(result.current).toBe('first')
233+
234+
await expect(
235+
waitFor(
236+
() => {
237+
expect(result.current).toBe('third')
238+
},
239+
{ timeout: 75 }
240+
)
241+
).rejects.toThrow(Error('Timed out in waitFor after 75ms.'))
242+
})
243+
244+
test('should not reject when waiting for expectation to pass if timeout has been disabled', async () => {
245+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'], 550))
246+
247+
expect(result.current).toBe('first')
248+
249+
await waitFor(
250+
() => {
251+
expect(result.current).toBe('third')
252+
},
253+
{ timeout: false }
254+
)
255+
256+
expect(result.current).toBe('third')
257+
})
258+
259+
test('should check on interval when waiting for expectation to pass', async () => {
260+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third']))
261+
262+
let checks = 0
263+
264+
await waitFor(
265+
() => {
266+
checks++
267+
return result.current === 'third'
268+
},
269+
{ interval: 100 }
270+
)
271+
272+
expect(checks).toBe(3)
273+
})
274+
275+
test('should wait for value to change', async () => {
276+
const { result, waitForValueToChange } = renderHook(() =>
277+
useSequence(['first', 'second', 'third'])
278+
)
279+
280+
expect(result.current).toBe('first')
281+
282+
await waitForValueToChange(() => result.current === 'third')
283+
284+
expect(result.current).toBe('third')
285+
})
286+
287+
test('should wait for arbitrary value to change', async () => {
288+
const { waitForValueToChange } = renderHook(() => null)
289+
290+
let actual = 0
291+
const expected = 1
292+
293+
setTimeout(() => {
294+
actual = expected
295+
}, 200)
296+
297+
await waitForValueToChange(() => actual)
298+
299+
expect(actual).toBe(expected)
300+
})
301+
302+
test('should reject if timeout exceeded when waiting for value to change', async () => {
303+
const { result, waitForValueToChange } = renderHook(() =>
304+
useSequence(['first', 'second', 'third'])
305+
)
306+
307+
expect(result.current).toBe('first')
308+
309+
await expect(
310+
waitForValueToChange(() => result.current === 'third', {
311+
timeout: 75
312+
})
313+
).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.'))
314+
})
315+
316+
test('should not reject when waiting for value to change if timeout is disabled', async () => {
317+
const { result, waitForValueToChange } = renderHook(() =>
318+
useSequence(['first', 'second', 'third'], 550)
319+
)
320+
321+
expect(result.current).toBe('first')
322+
323+
await waitForValueToChange(() => result.current === 'third', {
324+
timeout: false
325+
})
326+
327+
expect(result.current).toBe('third')
328+
})
329+
330+
test('should reject if selector throws error', async () => {
331+
const { result, waitForValueToChange } = renderHook(() => useSequence(['first', 'second']))
332+
333+
expect(result.current).toBe('first')
334+
335+
await expect(
336+
waitForValueToChange(() => {
337+
if (result.current === 'second') {
338+
throw new Error('Something Unexpected')
339+
}
340+
return result.current
341+
})
342+
).rejects.toThrow(Error('Something Unexpected'))
343+
})
89344
})
90345
})
91-
92-
// eslint-disable-next-line jest/no-export
93-
export {}

src/core/asyncUtils.ts

+12-10
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,41 @@ import {
77
AsyncUtils
88
} from '../types'
99

10-
import { createTimeoutController } from '../helpers/createTimeoutController'
10+
import { createTimeoutController, DEFAULT_TIMEOUT } from '../helpers/createTimeoutController'
1111
import { TimeoutError } from '../helpers/error'
1212

13-
const DEFAULT_TIMEOUT = 1000
1413
const DEFAULT_INTERVAL = 50
1514

1615
function asyncUtils(act: Act, addResolver: (callback: () => void) => void): AsyncUtils {
17-
const wait = async (callback: () => boolean | void, { interval, timeout }: WaitOptions) => {
16+
const wait = async (
17+
callback: () => boolean | void,
18+
{ interval, timeout }: Required<WaitOptions>
19+
) => {
1820
const checkResult = () => {
1921
const callbackResult = callback()
2022
return callbackResult ?? callbackResult === undefined
2123
}
2224

23-
const timeoutSignal = createTimeoutController(timeout as number | boolean, false)
25+
const timeoutController = createTimeoutController(timeout, { allowFakeTimers: !interval })
2426

2527
const waitForResult = async () => {
2628
while (true) {
27-
const intervalSignal = createTimeoutController(interval as number | boolean, true)
28-
timeoutSignal.onTimeout(() => intervalSignal.cancel())
29+
const intervalController = createTimeoutController(interval, { allowFakeTimers: true })
30+
timeoutController.onTimeout(() => intervalController.cancel())
2931

30-
await intervalSignal.wrap(new Promise<void>(addResolver))
32+
await intervalController.wrap(new Promise<void>(addResolver))
3133

32-
if (checkResult() || timeoutSignal.timedOut) {
34+
if (checkResult() || timeoutController.timedOut) {
3335
return
3436
}
3537
}
3638
}
3739

3840
if (!checkResult()) {
39-
await act(() => timeoutSignal.wrap(waitForResult()))
41+
await act(() => timeoutController.wrap(waitForResult()))
4042
}
4143

42-
return !timeoutSignal.timedOut
44+
return !timeoutController.timedOut
4345
}
4446

4547
const waitFor = async (

0 commit comments

Comments
 (0)