testing-library / testing-library/eslint-plugin-testing-library
`no-wait-for-side-effects`: side effects not detected via `userEvent.setup()` instances and when wrapped in `void`
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 1.1k
- Forks
- 168
- Avg merge
- 15h 44m
- Merged PRs (30d)
- 14
Description
Have you read the Troubleshooting section?
Yes
Plugin version
v7.16.2
ESLint version
v9.39.2
Node.js version
v22.20.0
Bug description
no-wait-for-side-effects misses side effects inside waitFor in two
independent cases:
1. Aliased userEvent (userEvent.setup() instances). The rule only
recognizes side effects spelled userEvent.* (or the import alias). With the
user-event v14 recommended pattern — const user = userEvent.setup() and
calling methods on the instance — nothing is reported, so the rule is silent
for the most common modern usage. Cause: side effects are resolved by
identifier name via isUserEventUtil (node.name === userEventName), so the
identifier user never matches. The plugin already has an alias-aware helper
for exactly this case — isUserEventMethod with userEventSetupVars tracking,
used by await-async-events and no-await-sync-events — but this rule doesn't
use it. Same class of gap previously fixed for other rules in #812 and #758.
2. void-wrapped side effects. A statement like
void userEvent.click(el) is not reported even with the plain userEvent
name — this also hides void fireEvent.*(...) and void render(...). Cause:
getPropertyIdentifierNode unwraps member/call/chain/await expressions but has
no UnaryExpression case, so void x returns null and the statement is
skipped. await unwrapping was added for this rule in #1008; void needs the
same treatment.
The two combine: void user.click(el) is missed for both reasons.
Steps to reproduce
- Enable
testing-library/no-wait-for-side-effects(config below). - Lint this spec:
import { waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
const el = document.body;
test('direct userEvent — reported', async () => {
await waitFor(async () => {
await userEvent.click(el); // ✅ reported
});
});
test('fireEvent — reported', async () => {
await waitFor(() => {
fireEvent.click(el); // ✅ reported
});
});
test('bug 1: setup() instance — NOT reported', async () => {
const user = userEvent.setup();
await waitFor(async () => {
await user.click(el); // ❌ silent (alias not matched)
});
});
test('bug 2: void-wrapped, real userEvent name — NOT reported', async () => {
await waitFor(() => {
void userEvent.click(el); // ❌ silent (void not unwrapped)
});
});
test('bugs 1+2 combined — NOT reported', async () => {
const user = userEvent.setup();
await waitFor(() => {
void user.click(el); // ❌ silent
});
});
- Only the first two tests are flagged. The
user.clickand
void userEvent.clickstatements pass silently, even though they are the
same side effects.
Error output/screenshots
Actual output (fresh project, only eslint + this plugin installed):
repro.spec.js
8:5 error Avoid using side effects within `waitFor` callback testing-library/no-wait-for-side-effects
14:5 error Avoid using side effects within `waitFor` callback testing-library/no-wait-for-side-effects
✖ 2 problems (2 errors, 0 warnings)
Expected: the same noSideEffectsWaitFor error on the three unreported
statements (await user.click(el), void userEvent.click(el),
void user.click(el)).
| callback body | reported? |
|---|---|
await waitFor(async () => { await userEvent.click(el) }) |
✅ error |
await waitFor(() => { fireEvent.click(el) }) |
✅ error |
await waitFor(async () => { await user.click(el) }) |
❌ silent |
await waitFor(() => { void userEvent.click(el) }) |
❌ silent |
await waitFor(() => { void user.click(el) }) |
❌ silent |
### ESLint configuration
```js
// eslint.config.js
import testingLibrary from 'eslint-plugin-testing-library';
export default [
{
files: ['**/*.spec.js'],
plugins: { 'testing-library': testingLibrary },
rules: {
'testing-library/no-wait-for-side-effects': 'error',
},
},
];
Rule(s) affected
testing-library/no-wait-for-side-effects, there might be more
Anything else?
No response
Do you want to submit a pull request to fix this bug?
Yes
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at no-wait-for-side-effects and compare its side-effect resolution with isUserEventMethod, userEventSetupVars, and getPropertyIdentifierNode. Add regression coverage for setup instances and void-wrapped userEvent, fireEvent, and render calls, using the supplied reproductions. Done means the three previously silent statements are reported while the existing direct userEvent and fireEvent cases remain reported.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- eslint, javascript, typescript
- Domain
- testing-qa, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100