-
Notifications
You must be signed in to change notification settings - Fork 162
feat: add no-unsettled-absence-query rule
#1278
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
Open
JMiltier
wants to merge
10
commits into
testing-library:main
Choose a base branch
from
JMiltier:pr/no-unsettled-absence-query
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
c5ddd2c
feat: introduce new rule to allow eslint to pick up on potential ghos…
JMiltier 8454897
test: add missing test coverage
JMiltier 314ef58
test: add line and column values (req)
JMiltier 1d678b2
fix: malformed HTML comment in the docs md file
JMiltier b6f8e0c
chore: refactor tree-walking to respect function scope boundaries
JMiltier e11dfcf
test: add testing for dom and marko (post support)
JMiltier 8b383b6
Merge branch 'main' into pr/no-unsettled-absence-query
JMiltier 200d442
Merge branch 'main' into pr/no-unsettled-absence-query
JMiltier e966f4e
Merge branch 'main' into pr/no-unsettled-absence-query
JMiltier e3f0c3b
Merge branch 'main' into pr/no-unsettled-absence-query
JMiltier File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| # testing-library/no-unsettled-absence-query | ||
|
|
||
| 📝 Disallow absence assertions on `queryBy*` before the component has settled. | ||
|
|
||
| <!-- end auto-generated rule header --> | ||
|
|
||
| Asserting absence with `queryBy*` + `.not.toBeInTheDocument()` (or `.not.toBeVisible()`) immediately after `render()`, before the component has settled, can produce a false positive. The element isn't there _yet_, not because it _won't_ be there. This is commonly referred to as **Testing Ghosts**. | ||
|
|
||
| ## Rule details | ||
|
|
||
| This rule fires when an absence assertion on a `queryBy*` / `queryAllBy*` query appears before any **settling expression** in the test body. | ||
|
|
||
| **What counts as "settled":** | ||
|
|
||
| 1. Any `await` expression on a preceding statement - covers `findBy*`, `waitFor`, `act`, or custom async helpers. | ||
| 2. A `getBy*` / `getAllBy*` call on a preceding statement - proves the synchronous render produced expected output. | ||
|
|
||
| **Additionally**, absence assertions inside a `waitFor` callback are always flagged, because `waitFor` retries until the assertion passes - an absence check passes on the first invocation before the component has settled. | ||
|
|
||
| Examples of **incorrect** code for this rule: | ||
|
|
||
| ```js | ||
| // Absence assertion before component has settled | ||
| test('shows no error', () => { | ||
| render(<AsyncComponent />); | ||
| expect(screen.queryByText('error')).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| // Absence assertion BEFORE the await - order matters | ||
| test('shows no error', async () => { | ||
| render(<AsyncComponent />); | ||
| expect(screen.queryByText('error')).not.toBeInTheDocument(); | ||
| await screen.findByText('loaded'); | ||
| }); | ||
|
|
||
| // queryAllBy variant | ||
| test('shows no alerts', () => { | ||
| render(<AsyncComponent />); | ||
| expect(screen.queryAllByRole('alert')).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| // Absence assertion inside waitFor - passes on first retry, still a ghost | ||
| test('shows no error', async () => { | ||
| render(<AsyncComponent />); | ||
| await waitFor(() => { | ||
| expect(screen.queryByText('error')).not.toBeInTheDocument(); | ||
| }); | ||
| }); | ||
| ``` | ||
|
|
||
| Examples of **correct** code for this rule: | ||
|
|
||
| ```js | ||
| // findBy* settles the component first | ||
| test('shows no error', async () => { | ||
| render(<AsyncComponent />); | ||
| await screen.findByText('loaded'); | ||
| expect(screen.queryByText('error')).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| // waitFor settles the component first | ||
| test('shows no error', async () => { | ||
| render(<AsyncComponent />); | ||
| await waitFor(() => expect(something).toBe(true)); | ||
| expect(screen.queryByRole('alert')).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| // act settles the component first | ||
| test('shows no error', async () => { | ||
| await act(() => render(<AsyncComponent />)); | ||
| expect(screen.queryByText('error')).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| // getBy* proves sync render completed | ||
| test('shows no error', () => { | ||
| render(<Component />); | ||
| screen.getByText('visible heading'); | ||
| expect(screen.queryByText('error')).not.toBeInTheDocument(); | ||
| }); | ||
| ``` | ||
|
|
||
| ## Further Reading | ||
|
|
||
| - [Testing Library: Appearance and Disappearance guide](https://testing-library.com/docs/guide-disappearance/) | ||
| - [Kent C. Dodds: Common mistakes with React Testing Library](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library) | ||
| - [Gerardo Perrucci: Stop Testing Ghosts](https://www.gperrucci.com/blog/react/assert-non-existence-react-testing-library) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
We need a "Limitations" section to mention that some untracked helpers (custom setup, fake timers, etc) might produce false positives.