Skip to content

Commit 9aca667

Browse files
committed
Added wait async util with documentation
1 parent 8f31599 commit 9aca667

File tree

3 files changed

+86
-6
lines changed

3 files changed

+86
-6
lines changed

docs/api-reference.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,23 +103,37 @@ This is the same [`act` function](https://reactjs.org/docs/test-utils.html#act)
103103

104104
## Async Utilities
105105

106+
### `wait`
107+
108+
```js
109+
function wait(callback: function(): boolean|void, options?: WaitOptions): Promise<void>
110+
```
111+
112+
Returns a `Promise` that resolves if the provided callback executes without exception and returns a
113+
truthy or `undefined` value. The callback is tested after each render of the hook.
114+
115+
It is safe to use the [`result` of `renderHook`](/reference/api#result) in the callback to perform
116+
assertion or to test values.
117+
118+
See the [`wait` Options](/reference/api#wait-options) section for more details on the optional
119+
`options` parameter.
120+
106121
### `waitForNextUpdate`
107122

108123
```js
109124
function waitForNextUpdate(options?: WaitOptions): Promise<void>
110125
```
111126

112-
`waitForNextUpdate` returns a `Promise` that resolves the next time the hook renders, commonly when
113-
state is updated as the result of an asynchronous update.
127+
Returns a `Promise` that resolves the next time the hook renders, commonly when state is updated as
128+
the result of an asynchronous update.
114129

115-
An options object is accepted as the first parameter to modify it's execution. See the
116-
[`wait` Options](/reference/api#wait-options) section for more details.
130+
See the [`wait` Options](/reference/api#wait-options) section for more details on the optional
131+
`options` parameter.
117132

118133
### `wait` Options
119134

120135
The async utilities accepts the following options:
121136

122137
#### `timeout`
123138

124-
The amount of time in milliseconds (ms) to wait. By default, the `wait` utilities will wait
125-
indefinitely.
139+
The maximum amount of time in milliseconds (ms) to wait. By default, no timeout is applied.

src/asyncUtils.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { act } from 'react-test-renderer'
22

3+
function actForResult(callback) {
4+
let value
5+
act(() => {
6+
value = callback()
7+
})
8+
return value
9+
}
10+
311
function createTimeoutError(utilName, timeout) {
412
const timeoutError = new Error(`Timed out in ${utilName} after ${timeout}ms.`)
513
timeoutError.timeout = true
@@ -32,7 +40,27 @@ function asyncUtils(addResolver) {
3240
return await nextUpdatePromise
3341
}
3442

43+
const wait = async (callback, options = {}) => {
44+
const initialTimeout = options.timeout
45+
while (true) {
46+
const startTime = Date.now()
47+
try {
48+
await waitForNextUpdate(options)
49+
const callbackResult = actForResult(callback)
50+
if (callbackResult || callbackResult === undefined) {
51+
break
52+
}
53+
} catch (e) {
54+
if (e.timeout) {
55+
throw createTimeoutError('wait', initialTimeout)
56+
}
57+
}
58+
options.timeout -= Date.now() - startTime
59+
}
60+
}
61+
3562
return {
63+
wait,
3664
waitForNextUpdate
3765
}
3866
}

test/asyncHook.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,42 @@ describe('async hook tests', () => {
6363
Error('Timed out in waitForNextUpdate after 10ms.')
6464
)
6565
})
66+
67+
test('should wait for expectation to pass', async () => {
68+
const { result, wait } = renderHook(() => useSequence('first', 'second', 'third'))
69+
70+
expect(result.current).toBe('first')
71+
72+
let complete = false
73+
await wait(() => {
74+
expect(result.current).toBe('third')
75+
complete = true
76+
})
77+
expect(complete).toBe(true)
78+
})
79+
80+
test('should wait for truthy value', async () => {
81+
const { result, wait } = renderHook(() => useSequence('first', 'second', 'third'))
82+
83+
expect(result.current).toBe('first')
84+
85+
await wait(() => result.current === 'third')
86+
87+
expect(result.current).toBe('third')
88+
})
89+
90+
test('should reject if timeout exceeded when waiting for expectation to pass', async () => {
91+
const { result, wait } = renderHook(() => useSequence('first', 'second', 'third'))
92+
93+
expect(result.current).toBe('first')
94+
95+
await expect(
96+
wait(
97+
() => {
98+
expect(result.current).toBe('third')
99+
},
100+
{ timeout: 75 }
101+
)
102+
).rejects.toThrow(Error('Timed out in wait after 75ms.'))
103+
})
66104
})

0 commit comments

Comments
 (0)