Skip to content

docs: update async utility #394

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

Merged
merged 2 commits into from
Mar 13, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 84 additions & 117 deletions docs/dom-testing-library/api-async.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,116 +7,103 @@ Several utilities are provided for dealing with asynchronous code. These can be
useful to wait for an element to appear or disappear in response to an action.
(See the [guide to testing disappearance](guide-disappearance.md).)

All the async utils are built on top of `wait`.

## `wait`

```typescript
function wait(
callback?: () => void,
function wait<T>(
callback: () => void,
options?: {
container?: HTMLElement
timeout?: number
interval?: number
mutationObserverOptions?: MutationObserverInit
}
): Promise<void>
): Promise<T>
```

When in need to wait for non-deterministic periods of time you can use `wait`,
to wait for your expectations to pass. The `wait` function is a small wrapper
around the
[`wait-for-expect`](https://github.com/TheBrainFamily/wait-for-expect) module.
Here's a simple example:
When in need to wait for any period of time you can use `wait`, to wait for your
expectations to pass. Here's a simple example:

```javascript
// ...
// Wait until the callback does not throw an error. In this case, that means
// it'll wait until we can get a form control with a label that matches "username".
await wait(() => getByLabelText(container, 'username'))
getByLabelText(container, 'username').value = 'chucknorris'
await wait(() => expect(mockAPI).toHaveBeenCalledTimes(1))
// ...
```

This can be useful if you have a unit test that mocks API calls and you need to
wait for your mock promises to all resolve.

The default `callback` is a no-op function (used like `await wait()`). This can
be helpful if you only need to wait for one tick of the event loop (in the case
of mocked API calls with promises that resolve immediately).

The default `timeout` is `4500ms` which will keep you under
[Jest's default timeout of `5000ms`](https://facebook.github.io/jest/docs/en/jest-object.html#jestsettimeouttimeout).
The default `container` is the global `document`. Make sure the elements you
wait for are descendants of `container`.

The default `interval` is `50ms`. However it will run your callback immediately
on the next tick of the event loop (in a `setTimeout`) before starting the
intervals.
before starting the intervals.

The default `timeout` is `1000ms` which will keep you under
[Jest's default timeout of `5000ms`](https://jestjs.io/docs/en/jest-object.html#jestsettimeouttimeout).

<a name="mutationobserveroptions"></a>The default `mutationObserverOptions` is
`{subtree: true, childList: true, attributes: true, characterData: true}` which
will detect additions and removals of child elements (including text nodes) in
the `container` and any of its descendants. It will also detect attribute
changes. When any of those changes occur, it will re-run the callback.

## `waitForElement`
## `waitForElementToBeRemoved`

```typescript
function waitForElement<T>(
callback: () => T,
function waitForElementToBeRemoved<T>(
callback: (() => T) | T,
options?: {
container?: HTMLElement
timeout?: number
interval?: number
mutationObserverOptions?: MutationObserverInit
}
): Promise<T>
```

When in need to wait for DOM elements to appear, disappear, or change you can
use `waitForElement`. The `waitForElement` function is a small wrapper around
the
[`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
To wait for the removal of element(s) from the DOM you can use
`waitForElementToBeRemoved`. The `waitForElementToBeRemoved` function is a small
wrapper around the `wait` utility.

Here's a simple example:
The first argument must be an element, array of elements, or a callback which returns
an element or array of elements.

Here is an example where the promise resolves with `true` because the element is
removed:

```javascript
// ...
// Wait until the callback does not throw an error and returns a truthy value. In this case, that means
// it'll wait until we can get a form control with a label that matches "username".
// The difference from `wait` is that rather than running your callback on
// an interval, it's run as soon as there are DOM changes in the container
// and returns the value returned by the callback.
const usernameElement = await waitForElement(
() => getByLabelText(container, 'username'),
{ container }
)
usernameElement.value = 'chucknorris'
// ...
```
const el = document.querySelector('div.getOuttaHere')

You can also wait for multiple elements at once:
waitForElementToBeRemoved(document.querySelector('div.getOuttaHere'))
.then(() => console.log('Element no longer in DOM'))

```javascript
const [usernameElement, passwordElement] = await waitForElement(
() => [
getByLabelText(container, 'username'),
getByLabelText(container, 'password'),
],
{ container }
)
el.setAttribute('data-neat', true)
// other mutations are ignored...

el.parentElement.removeChild(el)
// logs 'Element no longer in DOM'
```

Using
[`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
is more efficient than polling the DOM at regular intervals with `wait`. This
library sets up a
[`'mutationobserver-shim'`](https://github.com/megawac/MutationObserver.js) on
the global `window` object for cross-platform compatibility with older browsers
and the [`jsdom`](https://github.com/jsdom/jsdom/issues/639) that is usually
used in Node-based tests.
`waitForElementToBeRemoved` will throw an error if the first argument is `null`
or an empty array:

The default `container` is the global `document`. Make sure the elements you
wait for will be attached to it, or set a different `container`.
```javascript
waitForElementToBeRemoved(null).catch(err => console.log(err))
waitForElementToBeRemoved(queryByText(/not here/i)).catch(err => console.log(err))
waitForElementToBeRemoved(queryAllByText(/not here/i)).catch(err => console.log(err))
waitForElementToBeRemoved(() => getByText(/not here/i)).catch(err => console.log(err))

The default `timeout` is `4500ms` which will keep you under
[Jest's default timeout of `5000ms`](https://facebook.github.io/jest/docs/en/jest-object.html#jestsettimeouttimeout).
// Error: The element(s) given to waitForElementToBeRemoved are already removed. waitForElementToBeRemoved requires that the element(s) exist(s) before waiting for removal.
```

<a name="mutationobserveroptions"></a>The default `mutationObserverOptions` is
`{subtree: true, childList: true, attributes: true, characterData: true}` which
will detect additions and removals of child elements (including text nodes) in
the `container` and any of its descendants. It will also detect attribute
changes.
The options object is forwarded to `wait`.

## `waitForDomChange`
## `waitForDomChange` (DEPRECATED, use wait instead)

```typescript
function waitForDomChange<T>(options?: {
Expand Down Expand Up @@ -167,31 +154,23 @@ container.setAttribute('data-cool', 'false')
*/
```

Using
[`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
is more efficient than polling the DOM at regular intervals with `wait`. This
library sets up a
[`'mutationobserver-shim'`](https://github.com/megawac/MutationObserver.js) on
the global `window` object for cross-platform compatibility with older browsers
and the [`jsdom`](https://github.com/jsdom/jsdom/issues/639) that is usually
used in Node-based tests.

The default `container` is the global `document`. Make sure the elements you
wait for will be attached to it, or set a different `container`.
wait for are descendants of `container`.

The default `timeout` is `4500ms` which will keep you under
[Jest's default timeout of `5000ms`](https://facebook.github.io/jest/docs/en/jest-object.html#jestsettimeouttimeout).
The default `timeout` is `1000ms` which will keep you under
[Jest's default timeout of `5000ms`](https://jestjs.io/docs/en/jest-object.html#jestsettimeouttimeout).

<a name="mutationobserveroptions"></a>The default `mutationObserverOptions` is
`{subtree: true, childList: true, attributes: true, characterData: true}` which
will detect additions and removals of child elements (including text nodes) in
the `container` and any of its descendants. It will also detect attribute
changes.

## `waitForElementToBeRemoved`

## `waitForElement` (DEPRECATED, use `find*` queries or `wait`)

```typescript
function waitForElementToBeRemoved<T>(
function waitForElement<T>(
callback: () => T,
options?: {
container?: HTMLElement
Expand All @@ -201,54 +180,42 @@ function waitForElementToBeRemoved<T>(
): Promise<T>
```

To wait for the removal of element(s) from the DOM you can use
`waitForElementToBeRemoved`. The `waitForElementToBeRemoved` function is a small
wrapper around the
When in need to wait for DOM elements to appear, disappear, or change you can
use `waitForElement`. The `waitForElement` function is a small wrapper around
the
[`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).

The callback must return the pre-existing element or array of elements that are
expected to be removed.

Here is an example where the promise resolves with `true` because the element is
removed:
Here's a simple example:

```javascript
const el = document.querySelector('div.getOuttaHere')

waitForElementToBeRemoved(() =>
document.querySelector('div.getOuttaHere')
).then(() => console.log('Element no longer in DOM'))

el.setAttribute('data-neat', true)
// other mutations are ignored...

el.parentElement.removeChild(el)
// logs 'Element no longer in DOM'
// ...
// Wait until the callback does not throw an error and returns a truthy value. In this case, that means
// it'll wait until we can get a form control with a label that matches "username".
// The difference from `wait` is that rather than running your callback on
// an interval, it's run as soon as there are DOM changes in the container
// and returns the value returned by the callback.
const usernameElement = await waitForElement(
() => getByLabelText(container, 'username'),
{ container }
)
usernameElement.value = 'chucknorris'
// ...
```

`waitForElementToBeRemoved` will throw an error when the provided callback does
not return an element.
You can also wait for multiple elements at once:

```javascript
waitForElementToBeRemoved(() => null).catch(err => console.log(err))

// 'The callback function which was passed did not return an element
// or non-empty array of elements.
// waitForElementToBeRemoved requires that the element(s) exist
// before waiting for removal.'
const [usernameElement, passwordElement] = await waitForElement(
() => [
getByLabelText(container, 'username'),
getByLabelText(container, 'password'),
],
{ container }
)
```

Using
[`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
is more efficient than polling the DOM at regular intervals with `wait`. This
library sets up a
[`'mutationobserver-shim'`](https://github.com/megawac/MutationObserver.js) on
the global `window` object for cross-platform compatibility with older browsers
and the [`jsdom`](https://github.com/jsdom/jsdom/issues/639) that is usually
used in Node-based tests.

The default `container` is the global `document`. Make sure the elements you
wait for are descendants of `container`.
wait for will be attached to it, or set a different `container`.

The default `timeout` is `4500ms` which will keep you under
[Jest's default timeout of `5000ms`](https://facebook.github.io/jest/docs/en/jest-object.html#jestsettimeouttimeout).
Expand Down