Skip to content

Commit

Permalink
feat: add no-manual-cleanup rule (#72)
Browse files Browse the repository at this point in the history
* feat: start no-manual-cleanup rule

* feat: no-manual-cleanup-rule

* docs: remove badges no-manual-cleanup

* docs: add skip auto cleanup
  • Loading branch information
Thomas Lombart committed Jan 30, 2020
1 parent cddf204 commit 050cd4f
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -23,7 +23,9 @@
[![Tweet][tweet-badge]][tweet-url]

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

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

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

## Installation
Expand Down Expand Up @@ -142,6 +144,7 @@ To enable this configuration use the `extends` property in your
| [no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
| [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 | | |
| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | |
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | |

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

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
41 changes: 41 additions & 0 deletions docs/rules/no-manual-cleanup.md
@@ -0,0 +1,41 @@
# Disallow the use of `cleanup` (no-manual-cleanup)

`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.

## Rule Details

This rule disallows the import/use of `cleanup` in your test files. It fires if you import `cleanup` from one of these libraries:

- [Marko Testing Library](https://testing-library.com/docs/marko-testing-library/api#cleanup)
- [Preact Testing Library](https://testing-library.com/docs/preact-testing-library/api#cleanup)
- [React Testing Library](https://testing-library.com/docs/react-testing-library/api#cleanup)
- [Svelte Testing Library](https://testing-library.com/docs/svelte-testing-library/api#cleanup)
- [Vue Testing Library](https://testing-library.com/docs/vue-testing-library/api#cleanup)

Examples of **incorrect** code for this rule:

```js
import { cleanup } from '@testing-library/react';

const { cleanup } = require('@testing-library/react');

import utils from '@testing-library/react';
afterEach(() => utils.cleanup());

const utils = require('@testing-library/react');
afterEach(utils.cleanup);
```

Examples of **correct** code for this rule:

```js
import { cleanup } from 'any-other-library';

const utils = require('any-other-library');
utils.cleanup();
```

## Further Reading

- [cleanup API in React Testing Library](https://testing-library.com/docs/react-testing-library/api#cleanup)
- [Skipping Auto Cleanup](https://testing-library.com/docs/react-testing-library/setup#skipping-auto-cleanup)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -9,6 +9,7 @@ const rules = {
'no-debug': require('./rules/no-debug'),
'no-dom-import': require('./rules/no-dom-import'),
'no-get-by-for-checking-element-not-present': require('./rules/no-get-by-for-checking-element-not-present'),
'no-manual-cleanup': require('./rules/no-manual-cleanup'),
'prefer-explicit-assert': require('./rules/prefer-explicit-assert'),
};

Expand Down
120 changes: 120 additions & 0 deletions lib/rules/no-manual-cleanup.js
@@ -0,0 +1,120 @@
'use strict';

const CLEANUP_LIBRARY_REGEX = /(@testing-library\/(preact|react|svelte|vue))|@marko\/testing-library/;

module.exports = {
meta: {
type: 'problem',
docs: {
description: ' Disallow the use of `cleanup`',
category: 'Best Practices',
recommended: false,
},
messages: {
noManualCleanup:
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
},
fixable: null,
schema: [],
},

create: function(context) {
let defaultImportFromTestingLibrary;
let defaultRequireFromTestingLibrary;

return {
ImportDeclaration(node) {
const testingLibraryWithCleanup = node.source.value.match(
CLEANUP_LIBRARY_REGEX
);

// Early return if the library doesn't support `cleanup`
if (!testingLibraryWithCleanup) {
return;
}

if (node.specifiers[0].type === 'ImportDefaultSpecifier') {
defaultImportFromTestingLibrary = node;
}

const cleanupSpecifier = node.specifiers.find(
specifier =>
specifier.imported && specifier.imported.name === 'cleanup'
);

if (cleanupSpecifier) {
context.report({
node: cleanupSpecifier,
messageId: 'noManualCleanup',
});
}
},
VariableDeclarator(node) {
if (
node.init &&
node.init.callee &&
node.init.callee.name === 'require'
) {
const requiredModule = node.init.arguments[0];
const testingLibraryWithCleanup = requiredModule.value.match(
CLEANUP_LIBRARY_REGEX
);

// Early return if the library doesn't support `cleanup`
if (!testingLibraryWithCleanup) {
return;
}

if (node.id.type === 'ObjectPattern') {
const cleanupProperty = node.id.properties.find(
property => property.key.name === 'cleanup'
);
if (cleanupProperty) {
context.report({
node: cleanupProperty,
messageId: 'noManualCleanup',
});
}
} else {
defaultRequireFromTestingLibrary = node.id;
}
}
},
'Program:exit'() {
if (defaultImportFromTestingLibrary) {
const references = context.getDeclaredVariables(
defaultImportFromTestingLibrary
)[0].references;

reportImportReferences(context, references);
}

if (defaultRequireFromTestingLibrary) {
const references = context
.getDeclaredVariables(defaultRequireFromTestingLibrary.parent)[0]
.references.slice(1);

reportImportReferences(context, references);
}
},
};
},
};

function reportImportReferences(context, references) {
if (references && references.length > 0) {
references.forEach(reference => {
const utilsUsage = reference.identifier.parent;
if (
utilsUsage &&
utilsUsage.property &&
utilsUsage.property.name === 'cleanup'
) {
context.report({
node: utilsUsage.property,
messageId: 'noManualCleanup',
});
}
});
}
}
168 changes: 168 additions & 0 deletions tests/lib/rules/no-manual-cleanup.js
@@ -0,0 +1,168 @@
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const rule = require('../../../lib/rules/no-manual-cleanup');
const RuleTester = require('eslint').RuleTester;

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
});

const ALL_TESTING_LIBRARIES_WITH_CLEANUP = [
'@testing-library/preact',
'@testing-library/react',
'@testing-library/svelte',
'@testing-library/vue',
'@marko/testing-library',
];

ruleTester.run('no-manual-cleanup', rule, {
valid: [
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `import { render } from "${lib}"`,
})),
{
code: `import { cleanup } from "any-other-library"`,
},
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `import utils from "${lib}"`,
})),
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `
import utils from "${lib}"
utils.render()
`,
})),
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `const { render, within } = require("${lib}")`,
})),
{
code: `const { cleanup } = require("any-other-library")`,
},
{
code: `
const utils = require("any-other-library")
utils.cleanup()
`,
},
{
// For test coverage
code: `const utils = render("something")`,
},
],
invalid: [
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `import { render, cleanup } from "${lib}"`,
errors: [
{
line: 1,
column: 18, // error points to `cleanup`
message:
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
},
],
})),
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `import { cleanup as myCustomCleanup } from "${lib}"`,
errors: [
{
line: 1,
column: 10, // error points to `cleanup`
message:
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
},
],
})),
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `import utils, { cleanup } from "${lib}"`,
errors: [
{
line: 1,
column: 17, // error points to `cleanup`
message:
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
},
],
})),
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `
import utils from "${lib}"
afterEach(() => utils.cleanup())
`,
errors: [
{
line: 3,
column: 31,
message:
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
},
],
})),
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `
import utils from "${lib}"
afterEach(utils.cleanup)
`,
errors: [
{
line: 3,
column: 25,
message:
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
},
],
})),
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `const { cleanup } = require("${lib}")`,
errors: [
{
line: 1,
column: 9, // error points to `cleanup`
message:
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
},
],
})),
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `
const utils = require("${lib}")
afterEach(() => utils.cleanup())
`,
errors: [
{
line: 3,
column: 31,
message:
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
},
],
})),
...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map(lib => ({
code: `
const utils = require("${lib}")
afterEach(utils.cleanup)
`,
errors: [
{
line: 3,
column: 25,
message:
"`cleanup` is performed automatically by your test runner, you don't need manual cleanups.",
},
],
})),
],
});

0 comments on commit 050cd4f

Please sign in to comment.