diff --git a/README.md b/README.md index c5cd48bb..961c0bb3 100644 --- a/README.md +++ b/README.md @@ -341,7 +341,7 @@ module.exports = [ | [no-test-id-queries](docs/rules/no-test-id-queries.md) | Ensure no `data-testid` queries are used | | | | | [no-unnecessary-act](docs/rules/no-unnecessary-act.md) | Disallow wrapping Testing Library utils or empty callbacks in `act` | ![badge-marko][] ![badge-react][] | | | | [no-wait-for-multiple-assertions](docs/rules/no-wait-for-multiple-assertions.md) | Disallow the use of multiple `expect` calls inside `waitFor` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | | -| [no-wait-for-side-effects](docs/rules/no-wait-for-side-effects.md) | Disallow the use of side effects in `waitFor` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | | +| [no-wait-for-side-effects](docs/rules/no-wait-for-side-effects.md) | Disallow the use of side effects in `waitFor` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 | | [no-wait-for-snapshot](docs/rules/no-wait-for-snapshot.md) | Ensures no snapshot is generated inside of a `waitFor` call | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | | | [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than standalone queries | | | | | [prefer-find-by](docs/rules/prefer-find-by.md) | Suggest using `find(All)By*` query instead of `waitFor` + `get(All)By*` to wait for elements | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 | diff --git a/docs/rules/no-wait-for-side-effects.md b/docs/rules/no-wait-for-side-effects.md index 08cc31f1..79f62dee 100644 --- a/docs/rules/no-wait-for-side-effects.md +++ b/docs/rules/no-wait-for-side-effects.md @@ -2,6 +2,8 @@ 💼 This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `svelte`, `vue`. +🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + ## Rule Details diff --git a/lib/rules/no-wait-for-side-effects.ts b/lib/rules/no-wait-for-side-effects.ts index 571b98f6..aeea5640 100644 --- a/lib/rules/no-wait-for-side-effects.ts +++ b/lib/rules/no-wait-for-side-effects.ts @@ -1,3 +1,5 @@ +import { isAwaitExpression } from '@typescript-eslint/utils/ast-utils'; + import { createTestingLibraryRule } from '../create-testing-library-rule'; import { getPropertyIdentifierNode, @@ -8,6 +10,7 @@ import { isSequenceExpression, hasThenProperty, } from '../node-utils'; +import { getSourceCode } from '../utils'; import type { TSESTree } from '@typescript-eslint/utils'; @@ -35,6 +38,7 @@ export default createTestingLibraryRule({ 'Avoid using side effects within `waitFor` callback', }, schema: [], + fixable: 'code', }, defaultOptions: [], create(context, _, helpers) { @@ -209,10 +213,19 @@ export default createTestingLibraryRule({ } function reportImplicitReturnSideEffect( - node: + node: ( | TSESTree.AssignmentExpression | TSESTree.CallExpression | TSESTree.SequenceExpression + ) & { + parent: ( + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + ) & { + parent: TSESTree.CallExpression; + }; + } ) { if (!isCallerWaitFor(node)) { return; @@ -242,6 +255,14 @@ export default createTestingLibraryRule({ context.report({ node, messageId: 'noSideEffectsWaitFor', + fix: (fixer) => { + const { parent: callExpressionNode } = node.parent; + const targetNode = isAwaitExpression(callExpressionNode.parent) + ? callExpressionNode.parent + : callExpressionNode; + const sourceCode = getSourceCode(context); + return fixer.replaceText(targetNode, sourceCode.getText(node)); + }, }); } diff --git a/tests/lib/rules/no-wait-for-side-effects.test.ts b/tests/lib/rules/no-wait-for-side-effects.test.ts index 0f2f84c8..eaf9af2c 100644 --- a/tests/lib/rules/no-wait-for-side-effects.test.ts +++ b/tests/lib/rules/no-wait-for-side-effects.test.ts @@ -360,6 +360,10 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => render()) `, errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor } from '${testingFramework}'; + render() + `, }, { code: ` @@ -387,6 +391,11 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => renderHelper()) `, errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor } from '${testingFramework}'; + import { renderHelper } from 'somewhere-else'; + renderHelper() + `, }, { settings: { 'testing-library/custom-renders': ['renderHelper'] }, @@ -428,6 +437,10 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => result = render()) `, errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor } from '${testingFramework}'; + result = render() + `, }, { code: ` @@ -435,6 +448,10 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => (a = 5, result = render())) `, errors: [{ line: 3, column: 30, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor } from '${testingFramework}'; + a = 5, result = render() + `, }, { code: ` @@ -443,6 +460,11 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => rerender()) `, errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor } from '${testingFramework}'; + const { rerender } = render() + rerender() + `, }, { code: ` @@ -450,6 +472,10 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => render()) `, errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor, render } from '${testingFramework}'; + render() + `, }, { code: ` @@ -457,6 +483,10 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => renderHelper()) `, errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor } from '${testingFramework}'; + renderHelper() + `, }, { code: ` @@ -465,6 +495,11 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => render()) `, errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor } from '${testingFramework}'; + import { render } from 'somewhere-else'; + render() + `, }, ] ), @@ -475,6 +510,10 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => render()) `, errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor, render } from '~/test-utils'; + render() + `, }, ...SUPPORTED_TESTING_FRAMEWORKS.flatMap( (testingFramework) => [ @@ -486,6 +525,11 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => renderWrapper()) `, errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor } from '${testingFramework}'; + import { renderWrapper } from 'somewhere-else'; + renderWrapper() + `, }, { code: ` @@ -570,6 +614,10 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => fireEvent.keyDown(input, {key: 'ArrowDown'})) `, errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor } from '${testingFramework}'; + fireEvent.keyDown(input, {key: 'ArrowDown'}) + `, }) ), { @@ -579,6 +627,10 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => fireEvent.keyDown(input, {key: 'ArrowDown'})) `, errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor, fireEvent } from '~/test-utils'; + fireEvent.keyDown(input, {key: 'ArrowDown'}) + `, }, ...SUPPORTED_TESTING_FRAMEWORKS.flatMap( (testingFramework) => [ @@ -674,6 +726,10 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => userEvent.click(button)) `, errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + output: ` + import { waitFor } from '${testingFramework}'; + userEvent.click(button) + `, }, { code: `