Skip to content

Commit ae32b64

Browse files
feat: new rule prefer-wait-for (#88)
* feat(prefer-wait-for): new rule prefer-wait-for * docs(prefer-wait-for): pr fixes * test(prefer-wait-for): increase coverage * refactor(prefer-wait-for): use ternary * docs(prefer-wait-for): PR small fixes Co-Authored-By: Tim Deschryver <[email protected]> * test(prefer-wait-for): include imports * fix(prefer-wait-for): fist attempt to report related imports * refactor(prefer-wait-for): merge wait reports under single func * fix(prefer-wait-for): fix single imports * fix(prefer-wait-for): fix several imports * fix(prefer-wait-for): guard against empty named imports * fix(prefer-wait-for): fix more imports cases Fix multiline imports and avoid duplicating waitFor import if already present * feat(prefer-wait-for): new rule prefer-wait-for * docs(prefer-wait-for): pr fixes * test(prefer-wait-for): increase coverage * refactor(prefer-wait-for): use ternary * docs(prefer-wait-for): PR small fixes Co-Authored-By: Tim Deschryver <[email protected]> * test(prefer-wait-for): include imports * fix(prefer-wait-for): fist attempt to report related imports * refactor(prefer-wait-for): merge wait reports under single func * fix(prefer-wait-for): fix single imports * fix(prefer-wait-for): fix several imports * fix(prefer-wait-for): guard against empty named imports * fix(prefer-wait-for): fix more imports cases Fix multiline imports and avoid duplicating waitFor import if already present Co-authored-by: Tim Deschryver <[email protected]>
1 parent e50546d commit ae32b64

File tree

5 files changed

+622
-0
lines changed

5 files changed

+622
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ To enable this configuration use the `extends` property in your
147147
| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | |
148148
| [no-wait-for-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | | |
149149
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | |
150+
| [prefer-wait-for](docs/rules/prefer-wait-for.md) | Use `waitFor` instead of deprecated wait methods | | ![fixable-badge][] |
150151

151152
[build-badge]: https://img.shields.io/travis/testing-library/eslint-plugin-testing-library?style=flat-square
152153
[build-url]: https://travis-ci.org/testing-library/eslint-plugin-testing-library

docs/rules/prefer-wait-for.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Use `waitFor` instead of deprecated wait methods (prefer-wait-for)
2+
3+
`dom-testing-library` v7 released a new async util called `waitFor` which satisfies the use cases of `wait`, `waitForElement`, and `waitForDomChange` making them deprecated.
4+
5+
## Rule Details
6+
7+
This rule aims to use `waitFor` async util rather than previous deprecated ones.
8+
9+
Deprecated `wait` async utils are:
10+
11+
- `wait`
12+
- `waitForElement`
13+
- `waitForDomChange`
14+
15+
> This rule will auto fix deprecated async utils for you, including the necessary empty callback for `waitFor`. This means `wait();` will be replaced with `waitFor(() => {});`
16+
17+
Examples of **incorrect** code for this rule:
18+
19+
```js
20+
const foo = async () => {
21+
await wait();
22+
await wait(() => {});
23+
await waitForElement(() => {});
24+
await waitForDomChange();
25+
await waitForDomChange(mutationObserverOptions);
26+
await waitForDomChange({ timeout: 100});
27+
};
28+
```
29+
30+
Examples of **correct** code for this rule:
31+
32+
```js
33+
const foo = async () => {
34+
// new waitFor method
35+
await waitFor(() => {});
36+
37+
// previous waitForElementToBeRemoved is not deprecated
38+
await waitForElementToBeRemoved(() => {});
39+
};
40+
```
41+
42+
## When Not To Use It
43+
44+
When using dom-testing-library (or any other Testing Library relying on dom-testing-library) prior to v7.
45+
46+
## Further Reading
47+
48+
- [dom-testing-library v7 release](https://github.com/testing-library/dom-testing-library/releases/tag/v7.0.0)

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const rules = {
1212
'no-manual-cleanup': require('./rules/no-manual-cleanup'),
1313
'no-wait-for-empty-callback': require('./rules/no-wait-for-empty-callback'),
1414
'prefer-explicit-assert': require('./rules/prefer-explicit-assert'),
15+
'prefer-wait-for': require('./rules/prefer-wait-for'),
1516
};
1617

1718
const recommendedRules = {

lib/rules/prefer-wait-for.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
'use strict';
2+
3+
const { getDocsUrl } = require('../utils');
4+
5+
const DEPRECATED_METHODS = ['wait', 'waitForElement', 'waitForDomChange'];
6+
7+
module.exports = {
8+
meta: {
9+
type: 'suggestion',
10+
docs: {
11+
description: 'Use `waitFor` instead of deprecated wait methods',
12+
category: 'Best Practices',
13+
recommended: false,
14+
url: getDocsUrl('prefer-wait-for'),
15+
},
16+
messages: {
17+
preferWaitForMethod:
18+
'`{{ methodName }}` is deprecated in favour of `waitFor`',
19+
preferWaitForImport: 'import `waitFor` instead of deprecated async utils',
20+
},
21+
fixable: 'code',
22+
schema: [],
23+
},
24+
25+
create: function(context) {
26+
const importNodes = [];
27+
const waitNodes = [];
28+
29+
const reportImport = node => {
30+
context.report({
31+
node: node,
32+
messageId: 'preferWaitForImport',
33+
fix(fixer) {
34+
const excludedImports = [...DEPRECATED_METHODS, 'waitFor'];
35+
36+
// get all import names excluding all testing library `wait*` utils...
37+
const newImports = node.specifiers
38+
.filter(
39+
specifier => !excludedImports.includes(specifier.imported.name)
40+
)
41+
.map(specifier => specifier.imported.name);
42+
43+
// ... and append `waitFor`
44+
newImports.push('waitFor');
45+
46+
// build new node with new imports and previous source value
47+
const newNode = `import { ${newImports.join(',')} } from '${
48+
node.source.value
49+
}';`;
50+
51+
return fixer.replaceText(node, newNode);
52+
},
53+
});
54+
};
55+
56+
const reportWait = node => {
57+
context.report({
58+
node: node,
59+
messageId: 'preferWaitForMethod',
60+
data: {
61+
methodName: node.name,
62+
},
63+
fix(fixer) {
64+
const { parent } = node;
65+
const [arg] = parent.arguments;
66+
const fixers = [];
67+
68+
if (arg) {
69+
// if method been fixed already had a callback
70+
// then we just replace the method name.
71+
fixers.push(fixer.replaceText(node, 'waitFor'));
72+
73+
if (node.name === 'waitForDomChange') {
74+
// if method been fixed is `waitForDomChange`
75+
// then the arg received was options object so we need to insert
76+
// empty callback before.
77+
fixers.push(fixer.insertTextBefore(arg, `() => {}, `));
78+
}
79+
} else {
80+
// if wait method been fixed didn't have any callback
81+
// then we replace the method name and include an empty callback.
82+
fixers.push(fixer.replaceText(parent, 'waitFor(() => {})'));
83+
}
84+
85+
return fixers;
86+
},
87+
});
88+
};
89+
90+
return {
91+
'ImportDeclaration[source.value=/testing-library/]'(node) {
92+
const importedNames = node.specifiers
93+
.map(specifier => specifier.imported && specifier.imported.name)
94+
.filter(Boolean);
95+
96+
if (
97+
importedNames.some(importedName =>
98+
DEPRECATED_METHODS.includes(importedName)
99+
)
100+
) {
101+
importNodes.push(node);
102+
}
103+
},
104+
'CallExpression > Identifier[name=/^(wait|waitForElement|waitForDomChange)$/]'(
105+
node
106+
) {
107+
waitNodes.push(node);
108+
},
109+
'Program:exit'() {
110+
waitNodes.forEach(waitNode => {
111+
reportWait(waitNode);
112+
});
113+
114+
importNodes.forEach(importNode => {
115+
reportImport(importNode);
116+
});
117+
},
118+
};
119+
},
120+
};

0 commit comments

Comments
 (0)