-
Notifications
You must be signed in to change notification settings - Fork 723
docs: rework disappearance guide to provide more guidance #499
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,93 +3,136 @@ id: guide-disappearance | |||||||||||||
title: Appearance and Disappearance | ||||||||||||||
--- | ||||||||||||||
|
||||||||||||||
Sometimes you need to test that an element is present and then disappears or | ||||||||||||||
vice versa. | ||||||||||||||
Sometimes you need to test that an element is present and then disappears or the | ||||||||||||||
other way around. | ||||||||||||||
|
||||||||||||||
## Waiting for appearance | ||||||||||||||
|
||||||||||||||
If you need to wait for an element to appear, the [async wait | ||||||||||||||
utilities][async-api] allow you to wait for an assertion to be satisfied before | ||||||||||||||
proceeding. The wait utilities retry until the query passes or times out. | ||||||||||||||
If you need to wait for an element to appear, [`findBy`][find-by] and the [async | ||||||||||||||
wait utility][async-api] `waitFor` allow you to wait for an assertion to be | ||||||||||||||
satisfied before proceeding. The wait utilities retry until the query passes or | ||||||||||||||
times out. | ||||||||||||||
|
||||||||||||||
```jsx | ||||||||||||||
Wait for appearance and return the element using `findBy`: | ||||||||||||||
|
||||||||||||||
```javascript | ||||||||||||||
test('movie title appears', async () => { | ||||||||||||||
// element is initially not present... | ||||||||||||||
|
||||||||||||||
const movieTitle = await screen.findByText('the lion king') | ||||||||||||||
expect(movieTitle).toBeInTheDocument() | ||||||||||||||
}) | ||||||||||||||
``` | ||||||||||||||
|
||||||||||||||
Or wait for appearance using `waitFor` and `getBy`: | ||||||||||||||
|
||||||||||||||
```javascript | ||||||||||||||
test('movie title appears', async () => { | ||||||||||||||
// element is initially not present... | ||||||||||||||
kimroen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
// wait for appearance | ||||||||||||||
await waitFor(() => { | ||||||||||||||
expect(getByText('the lion king')).toBeInTheDocument() | ||||||||||||||
expect(screen.getByText('the lion king')).toBeInTheDocument() | ||||||||||||||
}) | ||||||||||||||
|
||||||||||||||
// wait for appearance and return the element | ||||||||||||||
const movie = await findByText('the lion king') | ||||||||||||||
}) | ||||||||||||||
``` | ||||||||||||||
|
||||||||||||||
These do more or less the same thing (`findBy` uses `waitFor` under the hood), | ||||||||||||||
but the `findBy` version results in simpler code and a better error message. | ||||||||||||||
Comment on lines
+39
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, this is interesting. I'm not sure I agree on the statement (I believe using If we agree on
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is the last bit that needs some attention, then we can merge!
Comment on lines
+39
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
_Note: `toBeInTheDocument()` comes from [`@testing-library/jest-dom`][jest-dom]_ | ||||||||||||||
kimroen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
## Waiting for disappearance | ||||||||||||||
|
||||||||||||||
The `waitForElementToBeRemoved` [async helper][async-api] function uses a | ||||||||||||||
callback to query for the element on each DOM mutation and resolves to `true` | ||||||||||||||
when the element is removed. | ||||||||||||||
When you want to wait until an element has disappeared, you can use | ||||||||||||||
`waitForElementToBeRemoved` or `waitFor`. | ||||||||||||||
|
||||||||||||||
The `waitForElementToBeRemoved` [async wait utility][async-api] takes an element | ||||||||||||||
as an argument and waits until that element has been removed from the document. | ||||||||||||||
|
||||||||||||||
```javascript | ||||||||||||||
test('movie title no longer present in document', async () => { | ||||||||||||||
// element is present | ||||||||||||||
const movie = screen.queryByText('the mummy') | ||||||||||||||
kimroen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
await waitForElementToBeRemoved(movie) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see @kentcdodds commented over here that this should receive a callback, not the element directly: testing-library/react-testing-library#673 (comment) I feel like I've seen that doing it this way works too though, so I'm not sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can either receive a callback, an element, or an array of elements: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. might be worth expanding on this section later with a note about working with React, because sometimes the node gets destroyed and replaced, making these types of tests either pass or fail when they shouldn't |
||||||||||||||
|
||||||||||||||
```jsx | ||||||||||||||
test('movie title no longer present in DOM', async () => { | ||||||||||||||
// element is removed | ||||||||||||||
await waitForElementToBeRemoved(() => queryByText('the mummy')) | ||||||||||||||
// element has been removed | ||||||||||||||
expect(movie).not.toBeInTheDocument() | ||||||||||||||
}) | ||||||||||||||
``` | ||||||||||||||
|
||||||||||||||
Using | ||||||||||||||
`waitForElementToBeRemoved` works by using a | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the |
||||||||||||||
[`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) | ||||||||||||||
is more efficient than polling the DOM at regular intervals with `waitFor`. | ||||||||||||||
internally, which means it responds to the element being removed in a very | ||||||||||||||
efficient way. | ||||||||||||||
|
||||||||||||||
You can also pass an array or a callback to `waitForElementToBeRemoved` - | ||||||||||||||
[see the documentation for more options](dom-testing-library/api-async.md#waitforelementtoberemoved). | ||||||||||||||
|
||||||||||||||
Another option is using the `waitFor` helper. | ||||||||||||||
|
||||||||||||||
The `waitFor` [async helper][async-api] function retries until the wrapped function | ||||||||||||||
stops throwing an error. This can be used to assert that an element disappears | ||||||||||||||
from the page. | ||||||||||||||
The `waitFor` [async wait utility][async-api] retries until the wrapped function | ||||||||||||||
stops throwing an error. Because expectations throw errors when they fail, | ||||||||||||||
putting one in the wrapped function can be used to wait for the element to be | ||||||||||||||
removed. | ||||||||||||||
|
||||||||||||||
```jsx | ||||||||||||||
```javascript | ||||||||||||||
test('movie title goes away', async () => { | ||||||||||||||
// element is initially present... | ||||||||||||||
kimroen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
// note use of queryBy instead of getBy to return null | ||||||||||||||
// instead of throwing in the query itself | ||||||||||||||
await waitFor(() => { | ||||||||||||||
expect(queryByText('i, robot')).not.toBeInTheDocument() | ||||||||||||||
expect(screen.queryByText('i, robot')).not.toBeInTheDocument() | ||||||||||||||
}) | ||||||||||||||
}) | ||||||||||||||
``` | ||||||||||||||
|
||||||||||||||
Polling like this is not as efficient as observing for mutations using | ||||||||||||||
`waitForElementToBeRemoved`, but sometimes it's the best option. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there any good examples of when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree 👍 If not, I wouldn't even mention a "negated" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, I've removed the |
||||||||||||||
|
||||||||||||||
## Asserting elements are not present | ||||||||||||||
|
||||||||||||||
The standard `getBy` methods throw an error when they can't find an element, so | ||||||||||||||
if you want to make an assertion that an element is _not_ present in the DOM, | ||||||||||||||
you can use `queryBy` APIs instead: | ||||||||||||||
As opposed to waiting for removal, this is for when you are at a point in your | ||||||||||||||
test where you know an element shouldn't be present. | ||||||||||||||
Comment on lines
+73
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section can be tightened up. It's unclear what "this" refers to here. Also, we shouldn't assume jest-dom is installed and should mention the
Suggested change
|
||||||||||||||
|
||||||||||||||
You might reach for `getBy` to check that something is not present, but `getBy` | ||||||||||||||
queries throw an error when they can't find an element, which means you don't | ||||||||||||||
get the chance to use the result in an expectation. If you want to make an | ||||||||||||||
assertion that an element is _not_ present in the document, you can use | ||||||||||||||
`queryBy` queries instead: | ||||||||||||||
kimroen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
```javascript | ||||||||||||||
const submitButton = screen.queryByText('submit') | ||||||||||||||
expect(submitButton).toBeNull() // it doesn't exist | ||||||||||||||
expect(submitButton).not.toBeInTheDocument() // it doesn't exist in the document | ||||||||||||||
``` | ||||||||||||||
|
||||||||||||||
The `queryAll` APIs version return an array of matching nodes. The length of the | ||||||||||||||
array can be useful for assertions after elements are added or removed from the | ||||||||||||||
DOM. | ||||||||||||||
The `queryAll` query methods return an array of matching elements. The length of | ||||||||||||||
the array can be useful for assertions after elements are added or removed from | ||||||||||||||
the document. | ||||||||||||||
|
||||||||||||||
```javascript | ||||||||||||||
const submitButtons = screen.queryAllByText('submit') | ||||||||||||||
expect(submitButtons).toHaveLength(2) // expect 2 elements | ||||||||||||||
``` | ||||||||||||||
|
||||||||||||||
### `not.toBeInTheDocument` | ||||||||||||||
_Note: `not.toBeInTheDocument()` comes from | ||||||||||||||
[`@testing-library/jest-dom`][jest-dom]_ | ||||||||||||||
|
||||||||||||||
The [`jest-dom`](ecosystem-jest-dom.md) utility library provides the | ||||||||||||||
## The `toBeInTheDocument()` matcher | ||||||||||||||
|
||||||||||||||
The [`@testing-library/jest-dom`][jest-dom] utility library provides the | ||||||||||||||
`.toBeInTheDocument()` matcher, which can be used to assert that an element is | ||||||||||||||
in the body of the document, or not. This can be more meaningful than asserting | ||||||||||||||
a query result is `null`. | ||||||||||||||
a query result is `null`, and it also provides more helpful error messages when | ||||||||||||||
your tests fail. | ||||||||||||||
|
||||||||||||||
```javascript | ||||||||||||||
import '@testing-library/jest-dom/extend-expect' | ||||||||||||||
// use `queryBy` to avoid throwing an error with `getBy` | ||||||||||||||
const submitButton = screen.queryByText('submit') | ||||||||||||||
expect(submitButton).not.toBeInTheDocument() | ||||||||||||||
``` | ||||||||||||||
|
||||||||||||||
Read about how to set this up [in the documentation](jest-dom). | ||||||||||||||
kimroen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
[async-api]: dom-testing-library/api-async.md | ||||||||||||||
[find-by]: dom-testing-library/api-queries.md#findby | ||||||||||||||
[jest-dom]: ecosystem-jest-dom.md |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add an extra assertion to check that it's not present initially?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, but I feel that isn't entirely relevant and detracts a little from what we're trying to show in this example. Also, asserting that things don't exist is covered further down on the page.
If we did do that though, there should also be code here to do something that would make the title appear after asserting it isn't here, and I'm not sure it makes sense to get into that here.
We could add a comment saying as much after the assertion, like this:
But I think it might be better to alter the comment in the test to something that makes it clearer that this would be part of a longer test:
Do you think the last suggestion would help, or is there maybe something else we could do to alleviate the confusion or lack of clarity we are hoping to fix by adding this assertion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the first suggestion is much clearer instead of changing the comment tbh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair! In your mind, what is it that is unclear that adding this assertion solves? Or is there something else that makes it so you want it to be added?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the other hand, you're right with not wanting to have things in here that you'll explain later, so maybe the changed comment is indeed a better option to go with here...
Maybe we can make a full example (like the second suggestion) later on the page? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's value in keeping the first examples minimal, without having to decipher fake application code. Expanding on the comment and making clear this is a snippet of a larger test should be good.