From b6189371d3a10f674f920f4a8851b9c2875e8015 Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Wed, 4 Mar 2020 13:17:08 -0700 Subject: [PATCH 1/2] update async utility documentation for next major --- docs/dom-testing-library/api-async.md | 201 +++++++++++--------------- 1 file changed, 84 insertions(+), 117 deletions(-) diff --git a/docs/dom-testing-library/api-async.md b/docs/dom-testing-library/api-async.md index 24008c9db..8e49bc40d 100644 --- a/docs/dom-testing-library/api-async.md +++ b/docs/dom-testing-library/api-async.md @@ -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( + callback: () => void, options?: { + container?: HTMLElement timeout?: number interval?: number + mutationObserverOptions?: MutationObserverInit } -): Promise +): Promise ``` -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). + +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( - callback: () => T, +function waitForElementToBeRemoved( + callback: () => T | T, options?: { container?: HTMLElement timeout?: number + interval?: number mutationObserverOptions?: MutationObserverInit } ): Promise ``` -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. +``` -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(options?: { @@ -167,20 +154,11 @@ 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). The default `mutationObserverOptions` is `{subtree: true, childList: true, attributes: true, characterData: true}` which @@ -188,10 +166,11 @@ 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( +function waitForElement( callback: () => T, options?: { container?: HTMLElement @@ -201,54 +180,42 @@ function waitForElementToBeRemoved( ): Promise ``` -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). From 3f198642a3fcbe5e36765b668fd84b22137f2a7f Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 13 Mar 2020 14:04:16 +0100 Subject: [PATCH 2/2] Update docs/dom-testing-library/api-async.md --- docs/dom-testing-library/api-async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dom-testing-library/api-async.md b/docs/dom-testing-library/api-async.md index 8e49bc40d..017aa8e3b 100644 --- a/docs/dom-testing-library/api-async.md +++ b/docs/dom-testing-library/api-async.md @@ -56,7 +56,7 @@ changes. When any of those changes occur, it will re-run the callback. ```typescript function waitForElementToBeRemoved( - callback: () => T | T, + callback: (() => T) | T, options?: { container?: HTMLElement timeout?: number