Skip to content

Commit ba40a64

Browse files
2 parents 0f1cd36 + b2ef721 commit ba40a64

File tree

6 files changed

+266
-20
lines changed

6 files changed

+266
-20
lines changed

.all-contributorsrc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,18 @@
294294
"test",
295295
"doc"
296296
]
297+
},
298+
{
299+
"login": "renatoagds",
300+
"name": "Renato Augusto Gama dos Santos",
301+
"avatar_url": "https://avatars2.githubusercontent.com/u/1663717?v=4",
302+
"profile": "https://github.com/renatoagds",
303+
"contributions": [
304+
"ideas",
305+
"code",
306+
"doc",
307+
"test"
308+
]
297309
}
298310
],
299311
"contributorsPerLine": 7,

README.md

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
2626

27-
[![All Contributors](https://img.shields.io/badge/all_contributors-27-orange.svg?style=flat-square)](#contributors-)
27+
[![All Contributors](https://img.shields.io/badge/all_contributors-28-orange.svg?style=flat-square)](#contributors-)
2828

2929
<!-- ALL-CONTRIBUTORS-BADGE:END -->
3030

@@ -125,25 +125,26 @@ To enable this configuration use the `extends` property in your
125125

126126
## Supported Rules
127127

128-
| Rule | Description | Configurations | Fixable |
129-
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------ |
130-
| [await-async-query](docs/rules/await-async-query.md) | Enforce async queries to have proper `await` | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
131-
| [await-async-utils](docs/rules/await-async-utils.md) | Enforce async utils to be awaited properly | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
132-
| [await-fire-event](docs/rules/await-fire-event.md) | Enforce async fire event methods to be awaited | ![vue-badge][] | |
133-
| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensure `data-testid` values match a provided regex. | | |
134-
| [no-await-sync-query](docs/rules/no-await-sync-query.md) | Disallow unnecessary `await` for sync queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
135-
| [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
136-
| [no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
137-
| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
138-
| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | |
139-
| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![angular-badge][] ![react-badge][] ![vue-badge][] | | |
140-
| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | |
141-
| [no-wait-for-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
142-
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | |
143-
| [prefer-find-by](docs/rules/prefer-find-by.md) | Suggest using `findBy*` methods instead of the `waitFor` + `getBy` queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
144-
| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Enforce specific queries when checking element is present or not | | |
145-
| [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using screen while using queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
146-
| [prefer-wait-for](docs/rules/prefer-wait-for.md) | Use `waitFor` instead of deprecated wait methods | | ![fixable-badge][] |
128+
| Rule | Description | Configurations | Fixable |
129+
| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------ |
130+
| [await-async-query](docs/rules/await-async-query.md) | Enforce async queries to have proper `await` | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
131+
| [await-async-utils](docs/rules/await-async-utils.md) | Enforce async utils to be awaited properly | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
132+
| [await-fire-event](docs/rules/await-fire-event.md) | Enforce async fire event methods to be awaited | ![vue-badge][] | |
133+
| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensure `data-testid` values match a provided regex. | | |
134+
| [no-await-sync-query](docs/rules/no-await-sync-query.md) | Disallow unnecessary `await` for sync queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
135+
| [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
136+
| [no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
137+
| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
138+
| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | |
139+
| [no-multiple-assertions-wait-for](docs/rules/no-multiple-assertions-wait-for.md) | Disallow the use of multiple expect inside `waitFor` | | |
140+
| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![angular-badge][] ![react-badge][] ![vue-badge][] | | |
141+
| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | |
142+
| [no-wait-for-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
143+
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | |
144+
| [prefer-find-by](docs/rules/prefer-find-by.md) | Suggest using `findBy*` methods instead of the `waitFor` + `getBy` queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
145+
| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Enforce specific queries when checking element is present or not | | |
146+
| [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using screen while using queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
147+
| [prefer-wait-for](docs/rules/prefer-wait-for.md) | Use `waitFor` instead of deprecated wait methods | | ![fixable-badge][] |
147148

148149
[build-badge]: https://img.shields.io/travis/testing-library/eslint-plugin-testing-library?style=flat-square
149150
[build-url]: https://travis-ci.org/testing-library/eslint-plugin-testing-library
@@ -207,6 +208,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
207208
<td align="center"><a href="https://nickmccurdy.com/"><img src="https://avatars0.githubusercontent.com/u/927220?v=4" width="100px;" alt=""/><br /><sub><b>Nick McCurdy</b></sub></a><br /><a href="#ideas-nickmccurdy" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=nickmccurdy" title="Code">💻</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/pulls?q=is%3Apr+reviewed-by%3Anickmccurdy" title="Reviewed Pull Requests">👀</a></td>
208209
<td align="center"><a href="https://stefancameron.com/"><img src="https://avatars3.githubusercontent.com/u/2855350?v=4" width="100px;" alt=""/><br /><sub><b>Stefan Cameron</b></sub></a><br /><a href="https://github.com/testing-library/eslint-plugin-testing-library/issues?q=author%3Astefcameron" title="Bug reports">🐛</a></td>
209210
<td align="center"><a href="https://www.linkedin.com/in/mateusfelix/"><img src="https://avatars2.githubusercontent.com/u/4968788?v=4" width="100px;" alt=""/><br /><sub><b>Mateus Felix</b></sub></a><br /><a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=thebinaryfelix" title="Code">💻</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=thebinaryfelix" title="Tests">⚠️</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=thebinaryfelix" title="Documentation">📖</a></td>
211+
<td align="center"><a href="https://github.com/renatoagds"><img src="https://avatars2.githubusercontent.com/u/1663717?v=4" width="100px;" alt=""/><br /><sub><b>Renato Augusto Gama dos Santos</b></sub></a><br /><a href="#ideas-renatoagds" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=renatoagds" title="Code">💻</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=renatoagds" title="Documentation">📖</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=renatoagds" title="Tests">⚠️</a></td>
210212
</tr>
211213
</table>
212214

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Multiple assertions inside `waitFor` are not preferred (no-multiple-assertions-wait-for)
2+
3+
## Rule Details
4+
5+
This rule aims to ensure the correct usage of `expect` inside `waitFor`, in the way that they're intended to be used.
6+
When using multiples assertions inside `waitFor`, if one fails, you have to wait for a timeout before seeing it failing.
7+
Putting one assertion, you can both wait for the UI to settle to the state you want to assert on,
8+
and also fail faster if one of the assertions do end up failing
9+
10+
Example of **incorrect** code for this rule:
11+
12+
```js
13+
const foo = async () => {
14+
await waitFor(() => {
15+
expect(a).toEqual('a');
16+
expect(b).toEqual('b');
17+
});
18+
19+
// or
20+
await waitFor(function() {
21+
expect(a).toEqual('a');
22+
expect(b).toEqual('b');
23+
});
24+
};
25+
```
26+
27+
Examples of **correct** code for this rule:
28+
29+
```js
30+
const foo = async () => {
31+
await waitFor(() => expect(a).toEqual('a'));
32+
expect(b).toEqual('b');
33+
34+
// or
35+
await waitFor(function() {
36+
expect(a).toEqual('a');
37+
});
38+
expect(b).toEqual('b');
39+
40+
// it only detects expect
41+
// so this case doesn't generate warnings
42+
await waitFor(() => {
43+
fireEvent.keyDown(input, { key: 'ArrowDown' });
44+
expect(b).toEqual('b');
45+
});
46+
};
47+
```
48+
49+
## Further Reading
50+
51+
- [about `waitFor`](https://testing-library.com/docs/dom-testing-library/api-async#waitfor)
52+
- [inspiration for this rule](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#having-multiple-assertions-in-a-single-waitfor-callback)

lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import preferExplicitAssert from './rules/prefer-explicit-assert';
1414
import preferPresenceQueries from './rules/prefer-presence-queries';
1515
import preferScreenQueries from './rules/prefer-screen-queries';
1616
import preferWaitFor from './rules/prefer-wait-for';
17+
import noMultipleAssertionsWaitFor from './rules/no-multiple-assertions-wait-for'
1718
import preferFindBy from './rules/prefer-find-by';
1819

1920
const rules = {
@@ -26,6 +27,7 @@ const rules = {
2627
'no-debug': noDebug,
2728
'no-dom-import': noDomImport,
2829
'no-manual-cleanup': noManualCleanup,
30+
'no-multiple-assertions-wait-for': noMultipleAssertionsWaitFor,
2931
'no-node-access': noNodeAccess,
3032
'no-promise-in-fire-event': noPromiseInFireEvent,
3133
'no-wait-for-empty-callback': noWaitForEmptyCallback,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'
2+
import { getDocsUrl } from '../utils'
3+
import { isBlockStatement, findClosestCallNode, isMemberExpression, isCallExpression, isIdentifier } from '../node-utils'
4+
5+
export const RULE_NAME = 'no-multiple-assertions-wait-for';
6+
7+
const WAIT_EXPRESSION_QUERY =
8+
'CallExpression[callee.name=/^(waitFor)$/]';
9+
10+
export type MessageIds = 'noMultipleAssertionWaitFor';
11+
type Options = [];
12+
13+
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
14+
name: RULE_NAME,
15+
meta: {
16+
type: 'suggestion',
17+
docs: {
18+
description:
19+
"It's preferred to avoid multiple assertions in `waitFor`",
20+
category: 'Best Practices',
21+
recommended: false,
22+
},
23+
messages: {
24+
noMultipleAssertionWaitFor: 'Avoid using multiple assertions within `waitFor` callback',
25+
},
26+
fixable: null,
27+
schema: [],
28+
},
29+
defaultOptions: [],
30+
create: function(context) {
31+
function reportMultipleAssertion(
32+
node: TSESTree.BlockStatement
33+
) {
34+
const totalExpect = (body: Array<TSESTree.Node>): Array<TSESTree.Node> =>
35+
body.filter((node: TSESTree.ExpressionStatement) => {
36+
if (
37+
isCallExpression(node.expression) &&
38+
isMemberExpression(node.expression.callee) &&
39+
isCallExpression(node.expression.callee.object)
40+
) {
41+
const object: TSESTree.CallExpression = node.expression.callee.object
42+
const expressionName: string = isIdentifier(object.callee) && object.callee.name
43+
return expressionName === 'expect'
44+
} else {
45+
return false
46+
}
47+
})
48+
49+
if (isBlockStatement(node) && totalExpect(node.body).length > 1) {
50+
context.report({
51+
node,
52+
loc: node.loc.start,
53+
messageId: 'noMultipleAssertionWaitFor',
54+
});
55+
}
56+
}
57+
58+
return {
59+
[`${WAIT_EXPRESSION_QUERY} > ArrowFunctionExpression > BlockStatement`]: reportMultipleAssertion,
60+
[`${WAIT_EXPRESSION_QUERY} > FunctionExpression > BlockStatement`]: reportMultipleAssertion,
61+
};
62+
}
63+
})
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { createRuleTester } from '../test-utils';
2+
import rule, { RULE_NAME } from '../../../lib/rules/no-multiple-assertions-wait-for';
3+
4+
const ruleTester = createRuleTester({
5+
ecmaFeatures: {
6+
jsx: true,
7+
},
8+
});
9+
10+
ruleTester.run(RULE_NAME, rule, {
11+
valid: [
12+
{
13+
code: `
14+
await waitFor(() => expect(a).toEqual('a'))
15+
`,
16+
},
17+
{
18+
code: `
19+
await waitFor(function() {
20+
expect(a).toEqual('a')
21+
})
22+
`,
23+
},
24+
// this needs to be check by other rule
25+
{
26+
code: `
27+
await waitFor(() => {
28+
fireEvent.keyDown(input, {key: 'ArrowDown'})
29+
expect(b).toEqual('b')
30+
})
31+
`,
32+
},
33+
{
34+
code: `
35+
await waitFor(function() {
36+
fireEvent.keyDown(input, {key: 'ArrowDown'})
37+
expect(b).toEqual('b')
38+
})
39+
`,
40+
},
41+
{
42+
code: `
43+
await waitFor(() => {
44+
console.log('testing-library')
45+
expect(b).toEqual('b')
46+
})
47+
`,
48+
},
49+
{
50+
code: `
51+
await waitFor(function() {
52+
console.log('testing-library')
53+
expect(b).toEqual('b')
54+
})
55+
`,
56+
},
57+
{
58+
code: `
59+
await waitFor(() => {})
60+
`,
61+
},
62+
{
63+
code: `
64+
await waitFor(function() {})
65+
`,
66+
},
67+
{
68+
code: `
69+
await waitFor(() => {
70+
// testing
71+
})
72+
`,
73+
},
74+
],
75+
invalid: [
76+
{
77+
code: `
78+
await waitFor(() => {
79+
expect(a).toEqual('a')
80+
expect(b).toEqual('b')
81+
})
82+
`,
83+
errors: [{ messageId: 'noMultipleAssertionWaitFor' }]
84+
},
85+
{
86+
code: `
87+
await waitFor(() => {
88+
expect(a).toEqual('a')
89+
console.log('testing-library')
90+
expect(b).toEqual('b')
91+
})
92+
`,
93+
errors: [{ messageId: 'noMultipleAssertionWaitFor' }]
94+
},
95+
{
96+
code: `
97+
await waitFor(function() {
98+
expect(a).toEqual('a')
99+
expect(b).toEqual('b')
100+
})
101+
`,
102+
errors: [{ messageId: 'noMultipleAssertionWaitFor' }]
103+
},
104+
{
105+
code: `
106+
await waitFor(function() {
107+
expect(a).toEqual('a')
108+
console.log('testing-library')
109+
expect(b).toEqual('b')
110+
})
111+
`,
112+
errors: [{ messageId: 'noMultipleAssertionWaitFor' }]
113+
}
114+
]
115+
})

0 commit comments

Comments
 (0)