Skip to content

Commit 050cd4f

Browse files
feat: add no-manual-cleanup rule (#72)
* feat: start no-manual-cleanup rule * feat: no-manual-cleanup-rule * docs: remove badges no-manual-cleanup * docs: add skip auto cleanup
1 parent cddf204 commit 050cd4f

File tree

5 files changed

+334
-0
lines changed

5 files changed

+334
-0
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
[![Tweet][tweet-badge]][tweet-url]
2424

2525
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
26+
2627
[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-)
28+
2729
<!-- ALL-CONTRIBUTORS-BADGE:END -->
2830

2931
## Installation
@@ -142,6 +144,7 @@ To enable this configuration use the `extends` property in your
142144
| [no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
143145
| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
144146
| [no-get-by-for-checking-element-not-present](docs/rules/no-get-by-for-checking-element-not-present) | Disallow the use of `getBy*` queries when checking elements are not present | | |
147+
| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | |
145148
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | |
146149

147150
[build-badge]: https://img.shields.io/travis/testing-library/eslint-plugin-testing-library?style=flat-square
@@ -190,6 +193,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
190193

191194
<!-- markdownlint-enable -->
192195
<!-- prettier-ignore-end -->
196+
193197
<!-- ALL-CONTRIBUTORS-LIST:END -->
194198

195199
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

docs/rules/no-manual-cleanup.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Disallow the use of `cleanup` (no-manual-cleanup)
2+
3+
`cleanup` is performed automatically if the testing framework you're using supports the `afterEach` global (like mocha, Jest, and Jasmine). In this case, it's unnecessary to do manual cleanups after each test unless you skip the auto-cleanup with environment variables such as `RTL_SKIP_AUTO_CLEANUP` for React.
4+
5+
## Rule Details
6+
7+
This rule disallows the import/use of `cleanup` in your test files. It fires if you import `cleanup` from one of these libraries:
8+
9+
- [Marko Testing Library](https://testing-library.com/docs/marko-testing-library/api#cleanup)
10+
- [Preact Testing Library](https://testing-library.com/docs/preact-testing-library/api#cleanup)
11+
- [React Testing Library](https://testing-library.com/docs/react-testing-library/api#cleanup)
12+
- [Svelte Testing Library](https://testing-library.com/docs/svelte-testing-library/api#cleanup)
13+
- [Vue Testing Library](https://testing-library.com/docs/vue-testing-library/api#cleanup)
14+
15+
Examples of **incorrect** code for this rule:
16+
17+
```js
18+
import { cleanup } from '@testing-library/react';
19+
20+
const { cleanup } = require('@testing-library/react');
21+
22+
import utils from '@testing-library/react';
23+
afterEach(() => utils.cleanup());
24+
25+
const utils = require('@testing-library/react');
26+
afterEach(utils.cleanup);
27+
```
28+
29+
Examples of **correct** code for this rule:
30+
31+
```js
32+
import { cleanup } from 'any-other-library';
33+
34+
const utils = require('any-other-library');
35+
utils.cleanup();
36+
```
37+
38+
## Further Reading
39+
40+
- [cleanup API in React Testing Library](https://testing-library.com/docs/react-testing-library/api#cleanup)
41+
- [Skipping Auto Cleanup](https://testing-library.com/docs/react-testing-library/setup#skipping-auto-cleanup)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const rules = {
99
'no-debug': require('./rules/no-debug'),
1010
'no-dom-import': require('./rules/no-dom-import'),
1111
'no-get-by-for-checking-element-not-present': require('./rules/no-get-by-for-checking-element-not-present'),
12+
'no-manual-cleanup': require('./rules/no-manual-cleanup'),
1213
'prefer-explicit-assert': require('./rules/prefer-explicit-assert'),
1314
};
1415

lib/rules/no-manual-cleanup.js

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
'use strict';
2+
3+
const CLEANUP_LIBRARY_REGEX = /(@testing-library\/(preact|react|svelte|vue))|@marko\/testing-library/;
4+
5+
module.exports = {
6+
meta: {
7+
type: 'problem',
8+
docs: {
9+
description: ' Disallow the use of `cleanup`',
10+
category: 'Best Practices',
11+
recommended: false,
12+
},
13+
messages: {
14+
noManualCleanup:
15+
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
16+
},
17+
fixable: null,
18+
schema: [],
19+
},
20+
21+
create: function(context) {
22+
let defaultImportFromTestingLibrary;
23+
let defaultRequireFromTestingLibrary;
24+
25+
return {
26+
ImportDeclaration(node) {
27+
const testingLibraryWithCleanup = node.source.value.match(
28+
CLEANUP_LIBRARY_REGEX
29+
);
30+
31+
// Early return if the library doesn't support `cleanup`
32+
if (!testingLibraryWithCleanup) {
33+
return;
34+
}
35+
36+
if (node.specifiers[0].type === 'ImportDefaultSpecifier') {
37+
defaultImportFromTestingLibrary = node;
38+
}
39+
40+
const cleanupSpecifier = node.specifiers.find(
41+
specifier =>
42+
specifier.imported && specifier.imported.name === 'cleanup'
43+
);
44+
45+
if (cleanupSpecifier) {
46+
context.report({
47+
node: cleanupSpecifier,
48+
messageId: 'noManualCleanup',
49+
});
50+
}
51+
},
52+
VariableDeclarator(node) {
53+
if (
54+
node.init &&
55+
node.init.callee &&
56+
node.init.callee.name === 'require'
57+
) {
58+
const requiredModule = node.init.arguments[0];
59+
const testingLibraryWithCleanup = requiredModule.value.match(
60+
CLEANUP_LIBRARY_REGEX
61+
);
62+
63+
// Early return if the library doesn't support `cleanup`
64+
if (!testingLibraryWithCleanup) {
65+
return;
66+
}
67+
68+
if (node.id.type === 'ObjectPattern') {
69+
const cleanupProperty = node.id.properties.find(
70+
property => property.key.name === 'cleanup'
71+
);
72+
if (cleanupProperty) {
73+
context.report({
74+
node: cleanupProperty,
75+
messageId: 'noManualCleanup',
76+
});
77+
}
78+
} else {
79+
defaultRequireFromTestingLibrary = node.id;
80+
}
81+
}
82+
},
83+
'Program:exit'() {
84+
if (defaultImportFromTestingLibrary) {
85+
const references = context.getDeclaredVariables(
86+
defaultImportFromTestingLibrary
87+
)[0].references;
88+
89+
reportImportReferences(context, references);
90+
}
91+
92+
if (defaultRequireFromTestingLibrary) {
93+
const references = context
94+
.getDeclaredVariables(defaultRequireFromTestingLibrary.parent)[0]
95+
.references.slice(1);
96+
97+
reportImportReferences(context, references);
98+
}
99+
},
100+
};
101+
},
102+
};
103+
104+
function reportImportReferences(context, references) {
105+
if (references && references.length > 0) {
106+
references.forEach(reference => {
107+
const utilsUsage = reference.identifier.parent;
108+
if (
109+
utilsUsage &&
110+
utilsUsage.property &&
111+
utilsUsage.property.name === 'cleanup'
112+
) {
113+
context.report({
114+
node: utilsUsage.property,
115+
messageId: 'noManualCleanup',
116+
});
117+
}
118+
});
119+
}
120+
}

tests/lib/rules/no-manual-cleanup.js

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
'use strict';
2+
3+
// ------------------------------------------------------------------------------
4+
// Requirements
5+
// ------------------------------------------------------------------------------
6+
7+
const rule = require('../../../lib/rules/no-manual-cleanup');
8+
const RuleTester = require('eslint').RuleTester;
9+
10+
// ------------------------------------------------------------------------------
11+
// Tests
12+
// ------------------------------------------------------------------------------
13+
14+
const ruleTester = new RuleTester({
15+
parserOptions: {
16+
ecmaVersion: 2018,
17+
sourceType: 'module',
18+
ecmaFeatures: {
19+
jsx: true,
20+
},
21+
},
22+
});
23+
24+
const ALL_TESTING_LIBRARIES_WITH_CLEANUP = [
25+
'@testing-library/preact',
26+
'@testing-library/react',
27+
'@testing-library/svelte',
28+
'@testing-library/vue',
29+
'@marko/testing-library',
30+
];
31+
32+
ruleTester.run('no-manual-cleanup', rule, {
33+
valid: [
34+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
35+
code: `import { render } from "${lib}"`,
36+
})),
37+
{
38+
code: `import { cleanup } from "any-other-library"`,
39+
},
40+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
41+
code: `import utils from "${lib}"`,
42+
})),
43+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
44+
code: `
45+
import utils from "${lib}"
46+
utils.render()
47+
`,
48+
})),
49+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
50+
code: `const { render, within } = require("${lib}")`,
51+
})),
52+
{
53+
code: `const { cleanup } = require("any-other-library")`,
54+
},
55+
{
56+
code: `
57+
const utils = require("any-other-library")
58+
utils.cleanup()
59+
`,
60+
},
61+
{
62+
// For test coverage
63+
code: `const utils = render("something")`,
64+
},
65+
],
66+
invalid: [
67+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
68+
code: `import { render, cleanup } from "${lib}"`,
69+
errors: [
70+
{
71+
line: 1,
72+
column: 18, // error points to `cleanup`
73+
message:
74+
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
75+
},
76+
],
77+
})),
78+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
79+
code: `import { cleanup as myCustomCleanup } from "${lib}"`,
80+
errors: [
81+
{
82+
line: 1,
83+
column: 10, // error points to `cleanup`
84+
message:
85+
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
86+
},
87+
],
88+
})),
89+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
90+
code: `import utils, { cleanup } from "${lib}"`,
91+
errors: [
92+
{
93+
line: 1,
94+
column: 17, // error points to `cleanup`
95+
message:
96+
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
97+
},
98+
],
99+
})),
100+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
101+
code: `
102+
import utils from "${lib}"
103+
afterEach(() => utils.cleanup())
104+
`,
105+
errors: [
106+
{
107+
line: 3,
108+
column: 31,
109+
message:
110+
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
111+
},
112+
],
113+
})),
114+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
115+
code: `
116+
import utils from "${lib}"
117+
afterEach(utils.cleanup)
118+
`,
119+
errors: [
120+
{
121+
line: 3,
122+
column: 25,
123+
message:
124+
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
125+
},
126+
],
127+
})),
128+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
129+
code: `const { cleanup } = require("${lib}")`,
130+
errors: [
131+
{
132+
line: 1,
133+
column: 9, // error points to `cleanup`
134+
message:
135+
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
136+
},
137+
],
138+
})),
139+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
140+
code: `
141+
const utils = require("${lib}")
142+
afterEach(() => utils.cleanup())
143+
`,
144+
errors: [
145+
{
146+
line: 3,
147+
column: 31,
148+
message:
149+
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
150+
},
151+
],
152+
})),
153+
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
154+
code: `
155+
const utils = require("${lib}")
156+
afterEach(utils.cleanup)
157+
`,
158+
errors: [
159+
{
160+
line: 3,
161+
column: 25,
162+
message:
163+
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
164+
},
165+
],
166+
})),
167+
],
168+
});

0 commit comments

Comments
 (0)