Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interval to async utilities to supplement post render checks #408

Merged
merged 4 commits into from Jul 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 59 additions & 18 deletions docs/api-reference.md
Expand Up @@ -152,50 +152,92 @@ variable to `true` before importing `@testing-library/react-hooks` will also dis
### `waitForNextUpdate`

```js
function waitForNextUpdate(options?: WaitOptions): Promise<void>
function waitForNextUpdate(options?: {
timeout?: number
}): Promise<void>
```

Returns a `Promise` that resolves the next time the hook renders, commonly when state is updated as
the result of an asynchronous update.

See the [`wait` Options](/reference/api#wait-options) section for more details on the available
`options`.
#### `timeout`

### `wait`
The maximum amount of time in milliseconds (ms) to wait. By default, no timeout is applied.

### `waitFor`

```js
function wait(callback: function(): boolean|void, options?: WaitOptions): Promise<void>
function waitFor(callback: function(): boolean|void, options?: {
interval?: number,
timeout?: number,
suppressErrors?: boolean
}): Promise<void>
```

Returns a `Promise` that resolves if the provided callback executes without exception and returns a
truthy or `undefined` value. It is safe to use the [`result` of `renderHook`](/reference/api#result)
in the callback to perform assertion or to test values.

The callback is tested after each render of the hook. By default, errors raised from the callback
will be suppressed (`suppressErrors = true`).
#### `interval`

See the [`wait` Options](/reference/api#wait-options) section for more details on the available
`options`.
The amount of time in milliseconds (ms) to wait between checks of the callback if no renders occur.
Interval checking is disabled if `interval` is not provided in the options or provided as a `falsy`
value. By default, it is disabled.

#### `timeout`

The maximum amount of time in milliseconds (ms) to wait. By default, no timeout is applied.

#### `suppressErrors`

If this option is set to `true`, any errors that occur while waiting are treated as a failed check.
If this option is set to `false`, any errors that occur while waiting cause the promise to be
rejected. By default, errors are suppressed for this utility.

### `waitForValueToChange`

```js
function waitForValueToChange(selector: function(): any, options?: WaitOptions): Promise<void>
function waitForValueToChange(selector: function(): any, options?: {
interval?: number,
timeout?: number,
suppressErrors?: boolean
}): Promise<void>
```

Returns a `Promise` that resolves if the value returned from the provided selector changes. It
expected that the [`result` of `renderHook`](/reference/api#result) to select the value for
comparison.

The value is selected for comparison after each render of the hook. By default, errors raised from
selecting the value will not be suppressed (`suppressErrors = false`).
#### `interval`

The amount of time in milliseconds (ms) to wait between checks of the callback if no renders occur.
Interval checking is disabled if `interval` is not provided in the options or provided as a `falsy`
value. By default, it is disabled.

#### `timeout`

The maximum amount of time in milliseconds (ms) to wait. By default, no timeout is applied.

#### `suppressErrors`

See the [`wait` Options](/reference/api#wait-options) section for more details on the available
`options`.
If this option is set to `true`, any errors that occur while waiting are treated as a failed check.
If this option is set to `false`, any errors that occur while waiting cause the promise to be
rejected. By default, errors are not suppressed for this utility.

### `wait` Options
### `wait`

The async utilities accept the following options:
_(DEPRECATED, use [`waitFor`](/reference/api#waitfor) instead)_

```js
function wait(callback: function(): boolean|void, options?: {
timeout?: number,
suppressErrors?: boolean
}): Promise<void>
```

Returns a `Promise` that resolves if the provided callback executes without exception and returns a
truthy or `undefined` value. It is safe to use the [`result` of `renderHook`](/reference/api#result)
in the callback to perform assertion or to test values.

#### `timeout`

Expand All @@ -205,5 +247,4 @@ The maximum amount of time in milliseconds (ms) to wait. By default, no timeout

If this option is set to `true`, any errors that occur while waiting are treated as a failed check.
If this option is set to `false`, any errors that occur while waiting cause the promise to be
rejected. Please refer to the [utility descriptions](/reference/api#async-utilities) for the default
values of this option (if applicable).
rejected. By default, errors are suppressed for this utility.
39 changes: 35 additions & 4 deletions src/asyncUtils.js
Expand Up @@ -6,6 +6,14 @@ function createTimeoutError(utilName, { timeout }) {
return timeoutError
}

function resolveAfter(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}

let hasWarnedDeprecatedWait = false

function asyncUtils(addResolver) {
let nextUpdatePromise = null

Expand All @@ -30,7 +38,7 @@ function asyncUtils(addResolver) {
await nextUpdatePromise
}

const wait = async (callback, { timeout, suppressErrors = true } = {}) => {
const waitFor = async (callback, { interval, timeout, suppressErrors = true } = {}) => {
const checkResult = () => {
try {
const callbackResult = callback()
Expand All @@ -47,13 +55,18 @@ function asyncUtils(addResolver) {
while (true) {
const startTime = Date.now()
try {
await waitForNextUpdate({ timeout })
const nextCheck = interval
? Promise.race([waitForNextUpdate({ timeout }), resolveAfter(interval)])
: waitForNextUpdate({ timeout })

await nextCheck

if (checkResult()) {
return
}
} catch (e) {
if (e.timeout) {
throw createTimeoutError('wait', { timeout: initialTimeout })
throw createTimeoutError('waitFor', { timeout: initialTimeout })
}
throw e
}
Expand All @@ -69,7 +82,7 @@ function asyncUtils(addResolver) {
const waitForValueToChange = async (selector, options = {}) => {
const initialValue = selector()
try {
await wait(() => selector() !== initialValue, {
await waitFor(() => selector() !== initialValue, {
suppressErrors: false,
...options
})
Expand All @@ -81,8 +94,26 @@ function asyncUtils(addResolver) {
}
}

const wait = async (callback, { timeout, suppressErrors } = {}) => {
if (!hasWarnedDeprecatedWait) {
hasWarnedDeprecatedWait = true
console.warn(
'`wait` has been deprecated. Use `waitFor` instead: https://react-hooks-testing-library.com/reference/api#waitfor.'
)
}
try {
await waitFor(callback, { timeout, suppressErrors })
} catch (e) {
if (e.timeout) {
throw createTimeoutError('wait', { timeout })
}
throw e
}
}

return {
wait,
waitFor,
waitForNextUpdate,
waitForValueToChange
}
Expand Down