From 20f23ad0810654ab046e7056560653c72274d50a Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Mon, 19 Sep 2022 07:49:12 -0700 Subject: [PATCH 01/20] feat(prefer-wait-for): remove rule (#648) BREAKING CHANGE: `prefer-wait-for` is now removed --- .github/ISSUE_TEMPLATE/propose_new_rule.yml | 2 +- README.md | 1 - docs/rules/prefer-wait-for.md | 74 - lib/rules/prefer-wait-for.ts | 214 -- tests/index.test.ts | 2 +- tests/lib/rules/prefer-wait-for.test.ts | 2258 ------------------- 6 files changed, 2 insertions(+), 2549 deletions(-) delete mode 100644 docs/rules/prefer-wait-for.md delete mode 100644 lib/rules/prefer-wait-for.ts delete mode 100644 tests/lib/rules/prefer-wait-for.test.ts diff --git a/.github/ISSUE_TEMPLATE/propose_new_rule.yml b/.github/ISSUE_TEMPLATE/propose_new_rule.yml index 8205d7d4..6af708cd 100644 --- a/.github/ISSUE_TEMPLATE/propose_new_rule.yml +++ b/.github/ISSUE_TEMPLATE/propose_new_rule.yml @@ -7,7 +7,7 @@ body: attributes: label: Name for new rule description: Suggest a name for the new rule that follows the [rule naming conventions](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/CONTRIBUTING.md#rule-naming-conventions). - placeholder: prefer-wait-for + placeholder: prefer-find-by validations: required: true diff --git a/README.md b/README.md index f8186ca4..c7ea121d 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,6 @@ To enable this configuration use the `extends` property in your | [`prefer-query-by-disappearance`](./docs/rules/prefer-query-by-disappearance.md) | Suggest using `queryBy*` queries when waiting for disappearance | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`prefer-screen-queries`](./docs/rules/prefer-screen-queries.md) | Suggest using `screen` while querying | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`prefer-user-event`](./docs/rules/prefer-user-event.md) | Suggest using `userEvent` over `fireEvent` for simulating user interactions | | | -| [`prefer-wait-for`](./docs/rules/prefer-wait-for.md) | Use `waitFor` instead of deprecated wait methods | 🔧 | | | [`render-result-naming-convention`](./docs/rules/render-result-naming-convention.md) | Enforce a valid naming for return value from `render` | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | diff --git a/docs/rules/prefer-wait-for.md b/docs/rules/prefer-wait-for.md deleted file mode 100644 index 449548fd..00000000 --- a/docs/rules/prefer-wait-for.md +++ /dev/null @@ -1,74 +0,0 @@ -# Use `waitFor` instead of deprecated wait methods (`testing-library/prefer-wait-for`) - -`dom-testing-library` v7 released a new async util called `waitFor` which satisfies the use cases of `wait`, `waitForElement`, and `waitForDomChange` making them deprecated. - -## Rule Details - -This rule aims to use `waitFor` async util rather than previous deprecated ones. - -Deprecated `wait` async utils are: - -- `wait` -- `waitForElement` -- `waitForDomChange` - -> 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(() => {});` - -Examples of **incorrect** code for this rule: - -```js -import { wait, waitForElement, waitForDomChange } from '@testing-library/dom'; -// this also works for const { wait, waitForElement, waitForDomChange } = require ('@testing-library/dom') - -const foo = async () => { - await wait(); - await wait(() => {}); - await waitForElement(() => {}); - await waitForDomChange(); - await waitForDomChange(mutationObserverOptions); - await waitForDomChange({ timeout: 100 }); -}; - -import * as tl from '@testing-library/dom'; -// this also works for const tl = require('@testing-library/dom') -const foo = async () => { - await tl.wait(); - await tl.wait(() => {}); - await tl.waitForElement(() => {}); - await tl.waitForDomChange(); - await tl.waitForDomChange(mutationObserverOptions); - await tl.waitForDomChange({ timeout: 100 }); -}; -``` - -Examples of **correct** code for this rule: - -```js -import { waitFor, waitForElementToBeRemoved } from '@testing-library/dom'; -// this also works for const { waitFor, waitForElementToBeRemoved } = require('@testing-library/dom') -const foo = async () => { - // new waitFor method - await waitFor(() => {}); - - // previous waitForElementToBeRemoved is not deprecated - await waitForElementToBeRemoved(() => {}); -}; - -import * as tl from '@testing-library/dom'; -// this also works for const tl = require('@testing-library/dom') -const foo = async () => { - // new waitFor method - await tl.waitFor(() => {}); - - // previous waitForElementToBeRemoved is not deprecated - await tl.waitForElementToBeRemoved(() => {}); -}; -``` - -## When Not To Use It - -When using dom-testing-library (or any other Testing Library relying on dom-testing-library) prior to v7. - -## Further Reading - -- [dom-testing-library v7 release](https://github.com/testing-library/dom-testing-library/releases/tag/v7.0.0) diff --git a/lib/rules/prefer-wait-for.ts b/lib/rules/prefer-wait-for.ts deleted file mode 100644 index 30cb93b7..00000000 --- a/lib/rules/prefer-wait-for.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { TSESTree, ASTUtils } from '@typescript-eslint/utils'; - -import { createTestingLibraryRule } from '../create-testing-library-rule'; -import { - isImportSpecifier, - isMemberExpression, - findClosestCallExpressionNode, - isCallExpression, - isImportNamespaceSpecifier, - isObjectPattern, - isProperty, -} from '../node-utils'; - -export const RULE_NAME = 'prefer-wait-for'; -export type MessageIds = - | 'preferWaitForImport' - | 'preferWaitForMethod' - | 'preferWaitForRequire'; -type Options = []; - -const DEPRECATED_METHODS = ['wait', 'waitForElement', 'waitForDomChange']; - -export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: 'Use `waitFor` instead of deprecated wait methods', - recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: false, - marko: false, - }, - }, - messages: { - preferWaitForMethod: - '`{{ methodName }}` is deprecated in favour of `waitFor`', - preferWaitForImport: 'import `waitFor` instead of deprecated async utils', - preferWaitForRequire: - 'require `waitFor` instead of deprecated async utils', - }, - - fixable: 'code', - schema: [], - }, - defaultOptions: [], - - create(context, _, helpers) { - let addWaitFor = false; - - const reportRequire = (node: TSESTree.ObjectPattern) => { - context.report({ - node, - messageId: 'preferWaitForRequire', - fix(fixer) { - const excludedImports = [...DEPRECATED_METHODS, 'waitFor']; - - const newAllRequired = node.properties - .filter( - (s) => - isProperty(s) && - ASTUtils.isIdentifier(s.key) && - !excludedImports.includes(s.key.name) - ) - .map( - (s) => ((s as TSESTree.Property).key as TSESTree.Identifier).name - ); - - newAllRequired.push('waitFor'); - - return fixer.replaceText(node, `{ ${newAllRequired.join(',')} }`); - }, - }); - }; - - const reportImport = (node: TSESTree.ImportDeclaration) => { - context.report({ - node, - messageId: 'preferWaitForImport', - fix(fixer) { - const excludedImports = [...DEPRECATED_METHODS, 'waitFor']; - - // get all import names excluding all testing library `wait*` utils... - const newImports = node.specifiers - .map( - (specifier) => - isImportSpecifier(specifier) && - !excludedImports.includes(specifier.imported.name) && - specifier.imported.name - ) - .filter(Boolean) as string[]; - - // ... and append `waitFor` - newImports.push('waitFor'); - - // build new node with new imports and previous source value - const newNode = `import { ${newImports.join(',')} } from '${ - node.source.value - }';`; - - return fixer.replaceText(node, newNode); - }, - }); - }; - - const reportWait = (node: TSESTree.Identifier | TSESTree.JSXIdentifier) => { - context.report({ - node, - messageId: 'preferWaitForMethod', - data: { - methodName: node.name, - }, - fix(fixer) { - const callExpressionNode = findClosestCallExpressionNode(node); - if (!callExpressionNode) { - return null; - } - const [arg] = callExpressionNode.arguments; - const fixers = []; - - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (arg) { - // if method been fixed already had a callback - // then we just replace the method name. - fixers.push(fixer.replaceText(node, 'waitFor')); - - if (node.name === 'waitForDomChange') { - // if method been fixed is `waitForDomChange` - // then the arg received was options object so we need to insert - // empty callback before. - fixers.push(fixer.insertTextBefore(arg, '() => {}, ')); - } - } else { - // if wait method been fixed didn't have any callback - // then we replace the method name and include an empty callback. - let methodReplacement = 'waitFor(() => {})'; - - // if wait method used like `foo.wait()` then we need to keep the - // member expression to get `foo.waitFor(() => {})` - if ( - isMemberExpression(node.parent) && - ASTUtils.isIdentifier(node.parent.object) - ) { - methodReplacement = `${node.parent.object.name}.${methodReplacement}`; - } - const newText = methodReplacement; - - fixers.push(fixer.replaceText(callExpressionNode, newText)); - } - - return fixers; - }, - }); - }; - - return { - 'CallExpression > MemberExpression'(node: TSESTree.MemberExpression) { - const isDeprecatedMethod = - ASTUtils.isIdentifier(node.property) && - DEPRECATED_METHODS.includes(node.property.name); - if (!isDeprecatedMethod) { - // the method does not match a deprecated method - return; - } - if (!helpers.isNodeComingFromTestingLibrary(node)) { - // the method does not match from the imported elements from TL (even from custom) - return; - } - addWaitFor = true; - reportWait(node.property as TSESTree.Identifier); // compiler is not picking up correctly, it should have inferred it is an identifier - }, - 'CallExpression > Identifier'(node: TSESTree.Identifier) { - if (!DEPRECATED_METHODS.includes(node.name)) { - return; - } - - if (!helpers.isNodeComingFromTestingLibrary(node)) { - return; - } - addWaitFor = true; - reportWait(node); - }, - 'Program:exit'() { - if (!addWaitFor) { - return; - } - // now that all usages of deprecated methods were replaced, remove the extra imports - const testingLibraryNode = - helpers.getCustomModuleImportNode() ?? - helpers.getTestingLibraryImportNode(); - if (isCallExpression(testingLibraryNode)) { - const parent = - testingLibraryNode.parent as TSESTree.VariableDeclarator; - if (!isObjectPattern(parent.id)) { - // if there is no destructuring, there is nothing to replace - return; - } - reportRequire(parent.id); - } else if (testingLibraryNode) { - if ( - testingLibraryNode.specifiers.length === 1 && - isImportNamespaceSpecifier(testingLibraryNode.specifiers[0]) - ) { - // if we import everything, there is nothing to replace - return; - } - reportImport(testingLibraryNode); - } - }, - }; - }, -}); diff --git a/tests/index.test.ts b/tests/index.test.ts index 6e490f2b..49cf042f 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -8,7 +8,7 @@ import plugin from '../lib'; const execAsync = util.promisify(exec); const generateConfigs = () => execAsync(`npm run generate:configs`); -const numberOfRules = 27; +const numberOfRules = 26; const ruleNames = Object.keys(plugin.rules); // eslint-disable-next-line jest/expect-expect diff --git a/tests/lib/rules/prefer-wait-for.test.ts b/tests/lib/rules/prefer-wait-for.test.ts deleted file mode 100644 index d9dec019..00000000 --- a/tests/lib/rules/prefer-wait-for.test.ts +++ /dev/null @@ -1,2258 +0,0 @@ -import rule, { RULE_NAME } from '../../../lib/rules/prefer-wait-for'; -import { LIBRARY_MODULES } from '../../../lib/utils'; -import { createRuleTester } from '../test-utils'; - -const ruleTester = createRuleTester(); - -ruleTester.run(RULE_NAME, rule, { - valid: [ - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `import { waitFor, render } from '${libraryModule}'; - - async () => { - await waitFor(() => {}); - }`, - })), - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `const { waitFor, render } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}); - }`, - })), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { waitFor, render } from 'test-utils'; - - async () => { - await waitFor(() => {}); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { waitFor, render } = require('test-utils'); - - async () => { - await waitFor(() => {}); - }`, - }, - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `import { waitForElementToBeRemoved, render } from '${libraryModule}'; - - async () => { - await waitForElementToBeRemoved(() => {}); - }`, - })), - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `const { waitForElementToBeRemoved, render } = require('${libraryModule}'); - - async () => { - await waitForElementToBeRemoved(() => {}); - }`, - })), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { waitForElementToBeRemoved, render } from 'test-utils'; - - async () => { - await waitForElementToBeRemoved(() => {}); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { waitForElementToBeRemoved, render } = require('test-utils'); - - async () => { - await waitForElementToBeRemoved(() => {}); - }`, - }, - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `import * as testingLibrary from '${libraryModule}'; - - async () => { - await testingLibrary.waitForElementToBeRemoved(() => {}); - }`, - })), - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `const testingLibrary = require('${libraryModule}'); - - async () => { - await testingLibrary.waitForElementToBeRemoved(() => {}); - }`, - })), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import * as testingLibrary from 'test-utils'; - - async () => { - await testingLibrary.waitForElementToBeRemoved(() => {}); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const testingLibrary = require('test-utils'); - - async () => { - await testingLibrary.waitForElementToBeRemoved(() => {}); - }`, - }, - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `import { render } from '${libraryModule}'; - import { waitForSomethingElse } from 'other-module'; - - async () => { - await waitForSomethingElse(() => {}); - }`, - })), - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `const { render } = require('${libraryModule}'); - const { waitForSomethingElse } = require('other-module'); - - async () => { - await waitForSomethingElse(() => {}); - }`, - })), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { render } from 'test-utils'; - import { waitForSomethingElse } from 'other-module'; - - async () => { - await waitForSomethingElse(() => {}); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { render } = require('test-utils'); - const { waitForSomethingElse } = require('other-module'); - - async () => { - await waitForSomethingElse(() => {}); - }`, - }, - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `import * as testingLibrary from '${libraryModule}'; - - async () => { - await testingLibrary.waitFor(() => {}, { timeout: 500 }); - }`, - })), - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `const testingLibrary = require('${libraryModule}'); - - async () => { - await testingLibrary.waitFor(() => {}, { timeout: 500 }); - }`, - })), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import * as testingLibrary from 'test-utils'; - - async () => { - await testingLibrary.waitFor(() => {}, { timeout: 500 }); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const testingLibrary = require('test-utils'); - - async () => { - await testingLibrary.waitFor(() => {}, { timeout: 500 }); - }`, - }, - { - code: `import { wait } from 'imNoTestingLibrary'; - - async () => { - await wait(); - }`, - }, - { - code: `const { wait } = require('imNoTestingLibrary'); - - async () => { - await wait(); - }`, - }, - { - code: `import * as foo from 'imNoTestingLibrary'; - - async () => { - await foo.wait(); - }`, - }, - { - code: `const foo = require('imNoTestingLibrary'); - - async () => { - await foo.wait(); - }`, - }, - { - code: `import * as foo from 'imNoTestingLibrary'; - cy.wait(); - `, - }, - { - code: `const foo = require('imNoTestingLibrary'); - cy.wait(); - `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - // case: aggressive reporting disabled - method named same as invalid method - // but not coming from Testing Library is valid - import { wait as testingLibraryWait } from 'test-utils' - import { wait } from 'somewhere-else' - - async () => { - await wait(); - } - `, - }, - { - // https://github.com/testing-library/eslint-plugin-testing-library/issues/145 - code: `import * as foo from 'imNoTestingLibrary'; - async function wait(): Promise { - // doesn't matter - } - - function callsWait(): void { - await wait(); - } - `, - }, - { - // https://github.com/testing-library/eslint-plugin-testing-library/issues/145 - code: `const foo = require('imNoTestingLibrary'); - async function wait(): Promise { - // doesn't matter - } - - function callsWait(): void { - await wait(); - } - `, - }, - ], - - invalid: [ - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { wait, render } from '${libraryModule}'; - - async () => { - await wait(); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,waitFor } from '${libraryModule}'; - - async () => { - await waitFor(() => {}); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { wait, render } = require('${libraryModule}'); - - async () => { - await wait(); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { render,waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { wait, render } from 'test-utils'; - - async () => { - await wait(); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,waitFor } from 'test-utils'; - - async () => { - await waitFor(() => {}); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { wait, render } = require('test-utils'); - - async () => { - await wait(); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { render,waitFor } = require('test-utils'); - - async () => { - await waitFor(() => {}); - }`, - }, - // namespaced wait should be fixed but not its import - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import * as testingLibrary from '${libraryModule}'; - - async () => { - await testingLibrary.wait(); - }`, - errors: [ - { - messageId: 'preferWaitForMethod', - line: 4, - column: 30, - }, - ], - output: `import * as testingLibrary from '${libraryModule}'; - - async () => { - await testingLibrary.waitFor(() => {}); - }`, - } as const) - ), - // namespaced wait should be fixed but not its import - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const testingLibrary = require('${libraryModule}'); - - async () => { - await testingLibrary.wait(); - }`, - errors: [ - { - messageId: 'preferWaitForMethod', - line: 4, - column: 30, - }, - ], - output: `const testingLibrary = require('${libraryModule}'); - - async () => { - await testingLibrary.waitFor(() => {}); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import * as testingLibrary from 'test-utils'; - - async () => { - await testingLibrary.wait(); - }`, - errors: [ - { - messageId: 'preferWaitForMethod', - line: 4, - column: 30, - }, - ], - output: `import * as testingLibrary from 'test-utils'; - - async () => { - await testingLibrary.waitFor(() => {}); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const testingLibrary = require('test-utils'); - - async () => { - await testingLibrary.wait(); - }`, - errors: [ - { - messageId: 'preferWaitForMethod', - line: 4, - column: 30, - }, - ], - output: `const testingLibrary = require('test-utils'); - - async () => { - await testingLibrary.waitFor(() => {}); - }`, - }, - // namespaced waitForDomChange should be fixed but not its import - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import * as testingLibrary from '${libraryModule}'; - - async () => { - await testingLibrary.waitForDomChange({ timeout: 500 }); - }`, - errors: [ - { - messageId: 'preferWaitForMethod', - line: 4, - column: 30, - }, - ], - output: `import * as testingLibrary from '${libraryModule}'; - - async () => { - await testingLibrary.waitFor(() => {}, { timeout: 500 }); - }`, - } as const) - ), - // namespaced waitForDomChange should be fixed but not its import - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const testingLibrary = require('${libraryModule}'); - - async () => { - await testingLibrary.waitForDomChange({ timeout: 500 }); - }`, - errors: [ - { - messageId: 'preferWaitForMethod', - line: 4, - column: 30, - }, - ], - output: `const testingLibrary = require('${libraryModule}'); - - async () => { - await testingLibrary.waitFor(() => {}, { timeout: 500 }); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import * as testingLibrary from 'test-utils'; - - async () => { - await testingLibrary.waitForDomChange({ timeout: 500 }); - }`, - errors: [ - { - messageId: 'preferWaitForMethod', - line: 4, - column: 30, - }, - ], - output: `import * as testingLibrary from 'test-utils'; - - async () => { - await testingLibrary.waitFor(() => {}, { timeout: 500 }); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const testingLibrary = require('test-utils'); - - async () => { - await testingLibrary.waitForDomChange({ timeout: 500 }); - }`, - errors: [ - { - messageId: 'preferWaitForMethod', - line: 4, - column: 30, - }, - ], - output: `const testingLibrary = require('test-utils'); - - async () => { - await testingLibrary.waitFor(() => {}, { timeout: 500 }); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { render, wait } from '${libraryModule}' - - async () => { - await wait(() => {}); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,waitFor } from '${libraryModule}'; - - async () => { - await waitFor(() => {}); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { render, wait } = require('${libraryModule}'); - - async () => { - await wait(() => {}); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { render,waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { render, wait } from 'test-utils' - - async () => { - await wait(() => {}); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,waitFor } from 'test-utils'; - - async () => { - await waitFor(() => {}); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { render, wait } = require('test-utils'); - - async () => { - await wait(() => {}); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { render,waitFor } = require('test-utils'); - - async () => { - await waitFor(() => {}); - }`, - }, - // this import doesn't have trailing semicolon but fixer adds it - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { render, wait, screen } from "${libraryModule}"; - - async () => { - await wait(function cb() { - doSomething(); - }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,screen,waitFor } from '${libraryModule}'; - - async () => { - await waitFor(function cb() { - doSomething(); - }); - }`, - } as const) - ), - // this import doesn't have trailing semicolon but fixer adds it - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { render, wait, screen } from "${libraryModule}"; - - async () => { - await wait(function cb() { - doSomething(); - }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,screen,waitFor } from '${libraryModule}'; - - async () => { - await waitFor(function cb() { - doSomething(); - }); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { render, wait, screen } from "test-utils"; - - async () => { - await wait(function cb() { - doSomething(); - }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,screen,waitFor } from 'test-utils'; - - async () => { - await waitFor(function cb() { - doSomething(); - }); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { render, wait, screen } = require('test-utils'); - - async () => { - await wait(function cb() { - doSomething(); - }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { render,screen,waitFor } = require('test-utils'); - - async () => { - await waitFor(function cb() { - doSomething(); - }); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { render, waitForElement, screen } from '${libraryModule}' - - async () => { - await waitForElement(() => {}); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,screen,waitFor } from '${libraryModule}'; - - async () => { - await waitFor(() => {}); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { render, waitForElement, screen } = require('${libraryModule}'); - - async () => { - await waitForElement(() => {}); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { render,screen,waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { render, waitForElement, screen } from 'test-utils' - - async () => { - await waitForElement(() => {}); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,screen,waitFor } from 'test-utils'; - - async () => { - await waitFor(() => {}); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { render, waitForElement, screen } = require('test-utils'); - - async () => { - await waitForElement(() => {}); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { render,screen,waitFor } = require('test-utils'); - - async () => { - await waitFor(() => {}); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { waitForElement } from '${libraryModule}'; - - async () => { - await waitForElement(function cb() { - doSomething(); - }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { waitFor } from '${libraryModule}'; - - async () => { - await waitFor(function cb() { - doSomething(); - }); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { waitForElement } = require('${libraryModule}'); - - async () => { - await waitForElement(function cb() { - doSomething(); - }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(function cb() { - doSomething(); - }); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { waitForElement } from 'test-utils'; - - async () => { - await waitForElement(function cb() { - doSomething(); - }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { waitFor } from 'test-utils'; - - async () => { - await waitFor(function cb() { - doSomething(); - }); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { waitForElement } = require('test-utils'); - - async () => { - await waitForElement(function cb() { - doSomething(); - }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { waitFor } = require('test-utils'); - - async () => { - await waitFor(function cb() { - doSomething(); - }); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { waitForDomChange } from '${libraryModule}'; - - async () => { - await waitForDomChange(); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { waitFor } from '${libraryModule}'; - - async () => { - await waitFor(() => {}); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { waitForDomChange } = require('${libraryModule}'); - - async () => { - await waitForDomChange(); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { waitForDomChange } from 'test-utils'; - - async () => { - await waitForDomChange(); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { waitFor } from 'test-utils'; - - async () => { - await waitFor(() => {}); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { waitForDomChange } = require('test-utils'); - - async () => { - await waitForDomChange(); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { waitFor } = require('test-utils'); - - async () => { - await waitFor(() => {}); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { waitForDomChange } from '${libraryModule}'; - - async () => { - await waitForDomChange(mutationObserverOptions); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { waitFor } from '${libraryModule}'; - - async () => { - await waitFor(() => {}, mutationObserverOptions); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { waitForDomChange } = require('${libraryModule}'); - - async () => { - await waitForDomChange(mutationObserverOptions); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}, mutationObserverOptions); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { waitForDomChange } from 'test-utils'; - - async () => { - await waitForDomChange(mutationObserverOptions); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { waitFor } from 'test-utils'; - - async () => { - await waitFor(() => {}, mutationObserverOptions); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { waitForDomChange } = require('test-utils'); - - async () => { - await waitForDomChange(mutationObserverOptions); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { waitFor } = require('test-utils'); - - async () => { - await waitFor(() => {}, mutationObserverOptions); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { waitForDomChange } from '${libraryModule}'; - - async () => { - await waitForDomChange({ timeout: 5000 }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { waitFor } from '${libraryModule}'; - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { waitForDomChange } = require('${libraryModule}'); - - async () => { - await waitForDomChange({ timeout: 5000 }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { waitForDomChange } from 'test-utils'; - - async () => { - await waitForDomChange({ timeout: 5000 }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { waitFor } from 'test-utils'; - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { waitForDomChange } = require('test-utils'); - - async () => { - await waitForDomChange({ timeout: 5000 }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { waitFor } = require('test-utils'); - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { waitForDomChange, wait, waitForElement } from '${libraryModule}'; - import userEvent from '@testing-library/user-event'; - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 8, - column: 15, - }, - ], - output: `import { waitFor } from '${libraryModule}'; - import userEvent from '@testing-library/user-event'; - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { waitForDomChange, wait, waitForElement } = require('${libraryModule}'); - const userEvent = require('@testing-library/user-event'); - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 8, - column: 15, - }, - ], - output: `const { waitFor } = require('${libraryModule}'); - const userEvent = require('@testing-library/user-event'); - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { waitForDomChange, wait, waitForElement } from 'test-utils'; - import userEvent from '@testing-library/user-event'; - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 8, - column: 15, - }, - ], - output: `import { waitFor } from 'test-utils'; - import userEvent from '@testing-library/user-event'; - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { waitForDomChange, wait, waitForElement } = require('test-utils'); - const userEvent = require('@testing-library/user-event'); - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 8, - column: 15, - }, - ], - output: `const { waitFor } = require('test-utils'); - const userEvent = require('@testing-library/user-event'); - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { render, waitForDomChange, wait, waitForElement } from '${libraryModule}'; - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - ], - output: `import { render,waitFor } from '${libraryModule}'; - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { render, waitForDomChange, wait, waitForElement } = require('${libraryModule}'); - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - ], - output: `const { render,waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { render, waitForDomChange, wait, waitForElement } from 'test-utils'; - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - ], - output: `import { render,waitFor } from 'test-utils'; - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { render, waitForDomChange, wait, waitForElement } = require('test-utils'); - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - ], - output: `const { render,waitFor } = require('test-utils'); - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { waitForDomChange, wait, render, waitForElement } from '${libraryModule}'; - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - ], - output: `import { render,waitFor } from '${libraryModule}'; - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { waitForDomChange, wait, render, waitForElement } = require('${libraryModule}'); - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - ], - output: `const { render,waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { waitForDomChange, wait, render, waitForElement } from 'test-utils'; - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - ], - output: `import { render,waitFor } from 'test-utils'; - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { waitForDomChange, wait, render, waitForElement } = require('test-utils'); - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 5, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 6, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 7, - column: 15, - }, - ], - output: `const { render,waitFor } = require('test-utils'); - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { - waitForDomChange, - wait, - render, - waitForElement, - } from '${libraryModule}'; - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 9, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 10, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 11, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 12, - column: 15, - }, - ], - output: `import { render,waitFor } from '${libraryModule}'; - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { - waitForDomChange, - wait, - render, - waitForElement, - } = require('${libraryModule}'); - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 9, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 10, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 11, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 12, - column: 15, - }, - ], - output: `const { render,waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { - waitForDomChange, - wait, - render, - waitForElement, - } from 'test-utils'; - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 9, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 10, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 11, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 12, - column: 15, - }, - ], - output: `import { render,waitFor } from 'test-utils'; - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { - waitForDomChange, - wait, - render, - waitForElement, - } = require('test-utils'); - - async () => { - await waitForDomChange({ timeout: 5000 }); - await waitForElement(); - await wait(); - await wait(() => { doSomething() }); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 9, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 10, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 11, - column: 15, - }, - { - messageId: 'preferWaitForMethod', - line: 12, - column: 15, - }, - ], - output: `const { render,waitFor } = require('test-utils'); - - async () => { - await waitFor(() => {}, { timeout: 5000 }); - await waitFor(() => {}); - await waitFor(() => {}); - await waitFor(() => { doSomething() }); - }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - // if already importing waitFor then it's not imported twice - code: `import { wait, waitFor, render } from '${libraryModule}'; - - async () => { - await wait(); - await waitFor(someCallback); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,waitFor } from '${libraryModule}'; - - async () => { - await waitFor(() => {}); - await waitFor(someCallback); - }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - // if already importing waitFor then it's not imported twice - code: `const { wait, waitFor, render } = require('${libraryModule}'); - - async () => { - await wait(); - await waitFor(someCallback); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { render,waitFor } = require('${libraryModule}'); - - async () => { - await waitFor(() => {}); - await waitFor(someCallback); - }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - // if already importing waitFor then it's not imported twice - code: `import { wait, waitFor, render } from 'test-utils'; - - async () => { - await wait(); - await waitFor(someCallback); - }`, - errors: [ - { - messageId: 'preferWaitForImport', - line: 1, - column: 1, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `import { render,waitFor } from 'test-utils'; - - async () => { - await waitFor(() => {}); - await waitFor(someCallback); - }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - // if already importing waitFor then it's not imported twice - code: `const { wait, waitFor, render } = require('test-utils'); - - async () => { - await wait(); - await waitFor(someCallback); - }`, - errors: [ - { - messageId: 'preferWaitForRequire', - line: 1, - column: 7, - }, - { - messageId: 'preferWaitForMethod', - line: 4, - column: 15, - }, - ], - output: `const { render,waitFor } = require('test-utils'); - - async () => { - await waitFor(() => {}); - await waitFor(someCallback); - }`, - }, - ], -}); From 9acba430da3729bf3e4c781d1b9d2f3ff3ed6962 Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Mon, 19 Sep 2022 23:42:53 -0700 Subject: [PATCH 02/20] feat(no-render-in-setup): rename to `no-render-in-lifecycle` (#649) BREAKING CHANGE: `no-render-in-setup` is now called `no-render-in-lifecycle` --- README.md | 2 +- .../{no-render-in-setup.md => no-render-in-lifecycle.md} | 4 ++-- lib/configs/angular.ts | 2 +- lib/configs/marko.ts | 2 +- lib/configs/react.ts | 2 +- lib/configs/vue.ts | 2 +- .../{no-render-in-setup.ts => no-render-in-lifecycle.ts} | 2 +- tests/__snapshots__/index.test.ts.snap | 8 ++++---- ...er-in-setup.test.ts => no-render-in-lifecycle.test.ts} | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) rename docs/rules/{no-render-in-setup.md => no-render-in-lifecycle.md} (93%) rename lib/rules/{no-render-in-setup.ts => no-render-in-lifecycle.ts} (98%) rename tests/lib/rules/{no-render-in-setup.test.ts => no-render-in-lifecycle.test.ts} (99%) diff --git a/README.md b/README.md index c7ea121d..ab27d091 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ To enable this configuration use the `extends` property in your | [`no-manual-cleanup`](./docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | | | [`no-node-access`](./docs/rules/no-node-access.md) | Disallow direct Node access | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-promise-in-fire-event`](./docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | -| [`no-render-in-setup`](./docs/rules/no-render-in-setup.md) | Disallow the use of `render` in testing frameworks setup functions | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | +| [`no-render-in-lifecycle`](./docs/rules/no-render-in-lifecycle.md) | Disallow the use of `render` in testing frameworks setup functions | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-unnecessary-act`](./docs/rules/no-unnecessary-act.md) | Disallow wrapping Testing Library utils or empty callbacks in `act` | | ![react-badge][] ![marko-badge][] | | [`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][] ![marko-badge][] | | [`no-wait-for-multiple-assertions`](./docs/rules/no-wait-for-multiple-assertions.md) | Disallow the use of multiple `expect` calls inside `waitFor` | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | diff --git a/docs/rules/no-render-in-setup.md b/docs/rules/no-render-in-lifecycle.md similarity index 93% rename from docs/rules/no-render-in-setup.md rename to docs/rules/no-render-in-lifecycle.md index cd27e366..f195243f 100644 --- a/docs/rules/no-render-in-setup.md +++ b/docs/rules/no-render-in-lifecycle.md @@ -1,4 +1,4 @@ -# Disallow the use of `render` in setup functions (`testing-library/no-render-in-setup`) +# Disallow the use of `render` in setup functions (`testing-library/no-render-in-lifecycle`) ## Rule Details @@ -77,5 +77,5 @@ it('Should have foo and bar', () => { If you would like to allow the use of `render` (or a custom render function) in _either_ `beforeAll` or `beforeEach`, this can be configured using the option `allowTestingFrameworkSetupHook`. This may be useful if you have configured your tests to [skip auto cleanup](https://testing-library.com/docs/react-testing-library/setup#skipping-auto-cleanup). `allowTestingFrameworkSetupHook` is an enum that accepts either `"beforeAll"` or `"beforeEach"`. ``` - "testing-library/no-render-in-setup": ["error", {"allowTestingFrameworkSetupHook": "beforeAll"}], + "testing-library/no-render-in-lifecycle": ["error", {"allowTestingFrameworkSetupHook": "beforeAll"}], ``` diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index 6f4388b8..aab442ad 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -13,7 +13,7 @@ export = { 'testing-library/no-dom-import': ['error', 'angular'], 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', - 'testing-library/no-render-in-setup': 'error', + 'testing-library/no-render-in-lifecycle': 'error', 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', 'testing-library/no-wait-for-side-effects': 'error', diff --git a/lib/configs/marko.ts b/lib/configs/marko.ts index d5dc1311..51d95924 100644 --- a/lib/configs/marko.ts +++ b/lib/configs/marko.ts @@ -14,7 +14,7 @@ export = { 'testing-library/no-dom-import': ['error', 'marko'], 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', - 'testing-library/no-render-in-setup': 'error', + 'testing-library/no-render-in-lifecycle': 'error', 'testing-library/no-unnecessary-act': 'error', 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', diff --git a/lib/configs/react.ts b/lib/configs/react.ts index 066d9fd7..9876f2c7 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -13,7 +13,7 @@ export = { 'testing-library/no-dom-import': ['error', 'react'], 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', - 'testing-library/no-render-in-setup': 'error', + 'testing-library/no-render-in-lifecycle': 'error', 'testing-library/no-unnecessary-act': 'error', 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index 2a3b2d2b..97b6c14b 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -14,7 +14,7 @@ export = { 'testing-library/no-dom-import': ['error', 'vue'], 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', - 'testing-library/no-render-in-setup': 'error', + 'testing-library/no-render-in-lifecycle': 'error', 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', 'testing-library/no-wait-for-side-effects': 'error', diff --git a/lib/rules/no-render-in-setup.ts b/lib/rules/no-render-in-lifecycle.ts similarity index 98% rename from lib/rules/no-render-in-setup.ts rename to lib/rules/no-render-in-lifecycle.ts index f40c7d1e..4496522c 100644 --- a/lib/rules/no-render-in-setup.ts +++ b/lib/rules/no-render-in-lifecycle.ts @@ -9,7 +9,7 @@ import { } from '../node-utils'; import { TESTING_FRAMEWORK_SETUP_HOOKS } from '../utils'; -export const RULE_NAME = 'no-render-in-setup'; +export const RULE_NAME = 'no-render-in-lifecycle'; export type MessageIds = 'noRenderInSetup'; type Options = [ { diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index 3a191824..32ceabeb 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -18,7 +18,7 @@ Object { ], "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", - "testing-library/no-render-in-setup": "error", + "testing-library/no-render-in-lifecycle": "error", "testing-library/no-wait-for-empty-callback": "error", "testing-library/no-wait-for-multiple-assertions": "error", "testing-library/no-wait-for-side-effects": "error", @@ -66,7 +66,7 @@ Object { ], "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", - "testing-library/no-render-in-setup": "error", + "testing-library/no-render-in-lifecycle": "error", "testing-library/no-unnecessary-act": "error", "testing-library/no-wait-for-empty-callback": "error", "testing-library/no-wait-for-multiple-assertions": "error", @@ -95,7 +95,7 @@ Object { ], "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", - "testing-library/no-render-in-setup": "error", + "testing-library/no-render-in-lifecycle": "error", "testing-library/no-unnecessary-act": "error", "testing-library/no-wait-for-empty-callback": "error", "testing-library/no-wait-for-multiple-assertions": "error", @@ -125,7 +125,7 @@ Object { ], "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", - "testing-library/no-render-in-setup": "error", + "testing-library/no-render-in-lifecycle": "error", "testing-library/no-wait-for-empty-callback": "error", "testing-library/no-wait-for-multiple-assertions": "error", "testing-library/no-wait-for-side-effects": "error", diff --git a/tests/lib/rules/no-render-in-setup.test.ts b/tests/lib/rules/no-render-in-lifecycle.test.ts similarity index 99% rename from tests/lib/rules/no-render-in-setup.test.ts rename to tests/lib/rules/no-render-in-lifecycle.test.ts index 0e66802e..9665afcc 100644 --- a/tests/lib/rules/no-render-in-setup.test.ts +++ b/tests/lib/rules/no-render-in-lifecycle.test.ts @@ -1,4 +1,4 @@ -import rule, { RULE_NAME } from '../../../lib/rules/no-render-in-setup'; +import rule, { RULE_NAME } from '../../../lib/rules/no-render-in-lifecycle'; import { TESTING_FRAMEWORK_SETUP_HOOKS } from '../../../lib/utils'; import { createRuleTester } from '../test-utils'; From 8f6ebf567b0e3bd136db3f917ef1ac6def5f14cb Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Sun, 2 Oct 2022 09:55:14 -0700 Subject: [PATCH 03/20] feat(no-manual-cleanup): add to React & Vue configs by default (#659) BREAKING CHANGE: `no-manual-cleanup` is now enabled by default in the React & Vue configs --- README.md | 2 +- lib/configs/react.ts | 1 + lib/configs/vue.ts | 1 + lib/rules/no-manual-cleanup.ts | 4 ++-- tests/__snapshots__/index.test.ts.snap | 2 ++ 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ab27d091..f014076d 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ To enable this configuration use the `extends` property in your | [`no-debugging-utils`](./docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-dom-import`](./docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | 🔧 | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-global-regexp-flag-in-query`](./docs/rules/no-global-regexp-flag-in-query.md) | Disallow the use of the global RegExp flag (/g) in queries | 🔧 | | -| [`no-manual-cleanup`](./docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | | +| [`no-manual-cleanup`](./docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | ![react-badge][] ![vue-badge][] | | [`no-node-access`](./docs/rules/no-node-access.md) | Disallow direct Node access | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-promise-in-fire-event`](./docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-render-in-lifecycle`](./docs/rules/no-render-in-lifecycle.md) | Disallow the use of `render` in testing frameworks setup functions | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | diff --git a/lib/configs/react.ts b/lib/configs/react.ts index 9876f2c7..52f5bc8e 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -11,6 +11,7 @@ export = { 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'error', 'testing-library/no-dom-import': ['error', 'react'], + 'testing-library/no-manual-cleanup': 'error', 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', 'testing-library/no-render-in-lifecycle': 'error', diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index 97b6c14b..31883b0d 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -12,6 +12,7 @@ export = { 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'error', 'testing-library/no-dom-import': ['error', 'vue'], + 'testing-library/no-manual-cleanup': 'error', 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', 'testing-library/no-render-in-lifecycle': 'error', diff --git a/lib/rules/no-manual-cleanup.ts b/lib/rules/no-manual-cleanup.ts index 4f8cb4c2..833fa147 100644 --- a/lib/rules/no-manual-cleanup.ts +++ b/lib/rules/no-manual-cleanup.ts @@ -28,8 +28,8 @@ export default createTestingLibraryRule({ recommendedConfig: { dom: false, angular: false, - react: false, - vue: false, + react: 'error', + vue: 'error', marko: false, }, }, diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index 32ceabeb..cebd4135 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -93,6 +93,7 @@ Object { "error", "react", ], + "testing-library/no-manual-cleanup": "error", "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", "testing-library/no-render-in-lifecycle": "error", @@ -123,6 +124,7 @@ Object { "error", "vue", ], + "testing-library/no-manual-cleanup": "error", "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", "testing-library/no-render-in-lifecycle": "error", From c76a7bfa153d8f206bd88cd3928e3f8bd107be18 Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:50:24 -0700 Subject: [PATCH 04/20] feat(no-global-regexp-flag-in-query): add to all configs by default (#660) BREAKING CHANGE: `no-global-regexp-flag-in-query` is now enabled by default in all configs --- README.md | 2 +- lib/configs/angular.ts | 1 + lib/configs/dom.ts | 1 + lib/configs/marko.ts | 1 + lib/configs/react.ts | 1 + lib/configs/vue.ts | 1 + lib/rules/no-global-regexp-flag-in-query.ts | 10 +++++----- tests/__snapshots__/index.test.ts.snap | 5 +++++ 8 files changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f014076d..a7da8c41 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ To enable this configuration use the `extends` property in your | [`no-container`](./docs/rules/no-container.md) | Disallow the use of `container` methods | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-debugging-utils`](./docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-dom-import`](./docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | 🔧 | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | -| [`no-global-regexp-flag-in-query`](./docs/rules/no-global-regexp-flag-in-query.md) | Disallow the use of the global RegExp flag (/g) in queries | 🔧 | | +| [`no-global-regexp-flag-in-query`](./docs/rules/no-global-regexp-flag-in-query.md) | Disallow the use of the global RegExp flag (/g) in queries | 🔧 | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-manual-cleanup`](./docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | ![react-badge][] ![vue-badge][] | | [`no-node-access`](./docs/rules/no-node-access.md) | Disallow direct Node access | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-promise-in-fire-event`](./docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index aab442ad..b19f7397 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -11,6 +11,7 @@ export = { 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'error', 'testing-library/no-dom-import': ['error', 'angular'], + 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', 'testing-library/no-render-in-lifecycle': 'error', diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index 83add602..cad0f7f4 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -8,6 +8,7 @@ export = { 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', + 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-promise-in-fire-event': 'error', 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', diff --git a/lib/configs/marko.ts b/lib/configs/marko.ts index 51d95924..d4f5f427 100644 --- a/lib/configs/marko.ts +++ b/lib/configs/marko.ts @@ -12,6 +12,7 @@ export = { 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'error', 'testing-library/no-dom-import': ['error', 'marko'], + 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', 'testing-library/no-render-in-lifecycle': 'error', diff --git a/lib/configs/react.ts b/lib/configs/react.ts index 52f5bc8e..e9beeb6f 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -11,6 +11,7 @@ export = { 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'error', 'testing-library/no-dom-import': ['error', 'react'], + 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-manual-cleanup': 'error', 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index 31883b0d..e2ff94a6 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -12,6 +12,7 @@ export = { 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'error', 'testing-library/no-dom-import': ['error', 'vue'], + 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-manual-cleanup': 'error', 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', diff --git a/lib/rules/no-global-regexp-flag-in-query.ts b/lib/rules/no-global-regexp-flag-in-query.ts index 9bf9a2ef..15a47617 100644 --- a/lib/rules/no-global-regexp-flag-in-query.ts +++ b/lib/rules/no-global-regexp-flag-in-query.ts @@ -21,11 +21,11 @@ export default createTestingLibraryRule({ docs: { description: 'Disallow the use of the global RegExp flag (/g) in queries', recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: false, - marko: false, + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', }, }, messages: { diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index cebd4135..f608f04d 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -16,6 +16,7 @@ Object { "error", "angular", ], + "testing-library/no-global-regexp-flag-in-query": "error", "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", "testing-library/no-render-in-lifecycle": "error", @@ -38,6 +39,7 @@ Object { "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", + "testing-library/no-global-regexp-flag-in-query": "error", "testing-library/no-promise-in-fire-event": "error", "testing-library/no-wait-for-empty-callback": "error", "testing-library/no-wait-for-multiple-assertions": "error", @@ -64,6 +66,7 @@ Object { "error", "marko", ], + "testing-library/no-global-regexp-flag-in-query": "error", "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", "testing-library/no-render-in-lifecycle": "error", @@ -93,6 +96,7 @@ Object { "error", "react", ], + "testing-library/no-global-regexp-flag-in-query": "error", "testing-library/no-manual-cleanup": "error", "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", @@ -124,6 +128,7 @@ Object { "error", "vue", ], + "testing-library/no-global-regexp-flag-in-query": "error", "testing-library/no-manual-cleanup": "error", "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", From b4ce9bb2ddec61f1790aa422412b3557f2baaf90 Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:56:07 -0700 Subject: [PATCH 05/20] feat(await-fire-event): rename to `await-async-event` + add support for `user-event` (#652) BREAKING CHANGE: `await-fire-event` is now called `await-async-event` --- README.md | 2 +- docs/rules/await-async-event.md | 143 +++++ docs/rules/await-fire-event.md | 66 -- docs/rules/no-await-sync-events.md | 3 +- lib/configs/angular.ts | 4 + lib/configs/dom.ts | 4 + lib/configs/marko.ts | 5 +- lib/configs/react.ts | 4 + lib/configs/vue.ts | 5 +- lib/node-utils/index.ts | 2 +- lib/rules/await-async-event.ts | 163 +++++ lib/rules/await-fire-event.ts | 113 ---- tests/__snapshots__/index.test.ts.snap | 38 +- tests/lib/rules/await-async-event.test.ts | 717 ++++++++++++++++++++++ tests/lib/rules/await-fire-event.test.ts | 353 ----------- 15 files changed, 1082 insertions(+), 540 deletions(-) create mode 100644 docs/rules/await-async-event.md delete mode 100644 docs/rules/await-fire-event.md create mode 100644 lib/rules/await-async-event.ts delete mode 100644 lib/rules/await-fire-event.ts create mode 100644 tests/lib/rules/await-async-event.test.ts delete mode 100644 tests/lib/rules/await-fire-event.test.ts diff --git a/README.md b/README.md index a7da8c41..ff2ae87a 100644 --- a/README.md +++ b/README.md @@ -206,9 +206,9 @@ To enable this configuration use the `extends` property in your | Name | Description | 🔧 | Included in configurations | | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | --- | ---------------------------------------------------------------------------------- | +| [`await-async-event`](./docs/rules/await-async-event.md) | Enforce promises from async event methods are handled | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`await-async-query`](./docs/rules/await-async-query.md) | Enforce promises from async queries to be handled | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`await-async-utils`](./docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | -| [`await-fire-event`](./docs/rules/await-fire-event.md) | Enforce promises from `fireEvent` methods to be handled | | ![vue-badge][] ![marko-badge][] | | [`consistent-data-testid`](./docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | | [`no-await-sync-events`](./docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | | | | [`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][] ![marko-badge][] | diff --git a/docs/rules/await-async-event.md b/docs/rules/await-async-event.md new file mode 100644 index 00000000..ef555409 --- /dev/null +++ b/docs/rules/await-async-event.md @@ -0,0 +1,143 @@ +# Enforce promises from async event methods are handled (`testing-library/await-async-event`) + +Ensure that promises returned by `userEvent` (v14+) async methods or `fireEvent` (only Vue and Marko) async methods are handled properly. + +## Rule Details + +This rule aims to prevent users from forgetting to handle promise returned from async event +methods. + +> ⚠️ `fireEvent` methods are async only on following Testing Library packages: +> +> - `@testing-library/vue` (supported by this plugin) +> - `@testing-library/svelte` (not supported yet by this plugin) +> - `@marko/testing-library` (supported by this plugin) + +Examples of **incorrect** code for this rule: + +```js +fireEvent.click(getByText('Click me')); + +fireEvent.focus(getByLabelText('username')); +fireEvent.blur(getByLabelText('username')); + +// wrap a fireEvent method within a function... +function triggerEvent() { + return fireEvent.click(button); +} +triggerEvent(); // ...but not handling promise from it is incorrect too +``` + +```js +userEvent.click(getByText('Click me')); +userEvent.tripleClick(getByText('Click me')); +userEvent.keyboard('foo'); + +// wrap a userEvent method within a function... +function triggerEvent() { + return userEvent.click(button); +} +triggerEvent(); // ...but not handling promise from it is incorrect too +``` + +Examples of **correct** code for this rule: + +```js +// `await` operator is correct +await fireEvent.focus(getByLabelText('username')); +await fireEvent.blur(getByLabelText('username')); + +// `then` method is correct +fireEvent.click(getByText('Click me')).then(() => { + // ... +}); + +// return the promise within a function is correct too! +const clickMeArrowFn = () => fireEvent.click(getByText('Click me')); + +// wrap a fireEvent method within a function... +function triggerEvent() { + return fireEvent.click(button); +} +await triggerEvent(); // ...and handling promise from it is correct also + +// using `Promise.all` or `Promise.allSettled` with an array of promises is valid +await Promise.all([ + fireEvent.focus(getByLabelText('username')), + fireEvent.blur(getByLabelText('username')), +]); +``` + +```js +// `await` operator is correct +await userEvent.click(getByText('Click me')); +await userEvent.tripleClick(getByText('Click me')); + +// `then` method is correct +userEvent.keyboard('foo').then(() => { + // ... +}); + +// return the promise within a function is correct too! +const clickMeArrowFn = () => userEvent.click(getByText('Click me')); + +// wrap a userEvent method within a function... +function triggerEvent() { + return userEvent.click(button); +} +await triggerEvent(); // ...and handling promise from it is correct also + +// using `Promise.all` or `Promise.allSettled` with an array of promises is valid +await Promise.all([ + userEvent.click(getByText('Click me')); + userEvent.tripleClick(getByText('Click me')); +]); +``` + +## Options + +- `eventModule`: `string` or `string[]`. Which event module should be linted for async event methods. Defaults to `userEvent` which should be used after v14. `fireEvent` should only be used with frameworks that have async fire event methods. + +## Example + +```json +{ + "testing-library/await-async-event": [ + 2, + { + "eventModule": "userEvent" + } + ] +} +``` + +```json +{ + "testing-library/await-async-event": [ + 2, + { + "eventModule": "fireEvent" + } + ] +} +``` + +```json +{ + "testing-library/await-async-event": [ + 2, + { + "eventModule": ["fireEvent", "userEvent"] + } + ] +} +``` + +## When Not To Use It + +- `userEvent` is below v14, before all event methods are async +- `fireEvent` methods are sync for most Testing Library packages. If you are not using Testing Library package with async events, you shouldn't use this rule. + +## Further Reading + +- [Vue Testing Library fireEvent](https://testing-library.com/docs/vue-testing-library/api#fireevent) diff --git a/docs/rules/await-fire-event.md b/docs/rules/await-fire-event.md deleted file mode 100644 index 54643c60..00000000 --- a/docs/rules/await-fire-event.md +++ /dev/null @@ -1,66 +0,0 @@ -# Enforce promises from fire event methods to be handled (`testing-library/await-fire-event`) - -Ensure that promises returned by `fireEvent` methods are handled -properly. - -## Rule Details - -This rule aims to prevent users from forgetting to handle promise returned from `fireEvent` -methods. - -> ⚠️ `fireEvent` methods are async only on following Testing Library packages: -> -> - `@testing-library/vue` (supported by this plugin) -> - `@testing-library/svelte` (not supported yet by this plugin) -> - `@marko/testing-library` (supported by this plugin) - -Examples of **incorrect** code for this rule: - -```js -fireEvent.click(getByText('Click me')); - -fireEvent.focus(getByLabelText('username')); -fireEvent.blur(getByLabelText('username')); - -// wrap a fireEvent method within a function... -function triggerEvent() { - return fireEvent.click(button); -} -triggerEvent(); // ...but not handling promise from it is incorrect too -``` - -Examples of **correct** code for this rule: - -```js -// `await` operator is correct -await fireEvent.focus(getByLabelText('username')); -await fireEvent.blur(getByLabelText('username')); - -// `then` method is correct -fireEvent.click(getByText('Click me')).then(() => { - // ... -}); - -// return the promise within a function is correct too! -const clickMeArrowFn = () => fireEvent.click(getByText('Click me')); - -// wrap a fireEvent method within a function... -function triggerEvent() { - return fireEvent.click(button); -} -await triggerEvent(); // ...and handling promise from it is correct also - -// using `Promise.all` or `Promise.allSettled` with an array of promises is valid -await Promise.all([ - fireEvent.focus(getByLabelText('username')), - fireEvent.blur(getByLabelText('username')), -]); -``` - -## When Not To Use It - -`fireEvent` methods are not async on all Testing Library packages. If you are not using Testing Library package with async fire event, you shouldn't use this rule. - -## Further Reading - -- [Vue Testing Library fireEvent](https://testing-library.com/docs/vue-testing-library/api#fireevent) diff --git a/docs/rules/no-await-sync-events.md b/docs/rules/no-await-sync-events.md index ae891577..39cfdf45 100644 --- a/docs/rules/no-await-sync-events.md +++ b/docs/rules/no-await-sync-events.md @@ -105,5 +105,4 @@ Example: ## Notes - Since `user-event` v14 all its methods are async, so you should disable reporting them by setting the `eventModules` to just `"fire-event"` so `user-event` methods are not reported. -- There is another rule `await-fire-event`, which is only in Vue Testing - Library. Please do not confuse with this rule. +- There is another rule `await-async-event`, which is for awaiting async events for `user-event` v14 or `fire-event` only in Vue Testing Library. Please do not confuse with this rule. diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index b19f7397..e0a14a8b 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -5,6 +5,10 @@ export = { plugins: ['testing-library'], rules: { + 'testing-library/await-async-event': [ + 'error', + { eventModule: 'userEvent' }, + ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index cad0f7f4..13fbc8d7 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -5,6 +5,10 @@ export = { plugins: ['testing-library'], rules: { + 'testing-library/await-async-event': [ + 'error', + { eventModule: 'userEvent' }, + ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', diff --git a/lib/configs/marko.ts b/lib/configs/marko.ts index d4f5f427..fbbc67a4 100644 --- a/lib/configs/marko.ts +++ b/lib/configs/marko.ts @@ -5,9 +5,12 @@ export = { plugins: ['testing-library'], rules: { + 'testing-library/await-async-event': [ + 'error', + { eventModule: ['fireEvent', 'userEvent'] }, + ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/await-fire-event': 'error', 'testing-library/no-await-sync-query': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'error', diff --git a/lib/configs/react.ts b/lib/configs/react.ts index e9beeb6f..e52476fc 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -5,6 +5,10 @@ export = { plugins: ['testing-library'], rules: { + 'testing-library/await-async-event': [ + 'error', + { eventModule: 'userEvent' }, + ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index e2ff94a6..bb4b3a21 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -5,9 +5,12 @@ export = { plugins: ['testing-library'], rules: { + 'testing-library/await-async-event': [ + 'error', + { eventModule: ['fireEvent', 'userEvent'] }, + ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/await-fire-event': 'error', 'testing-library/no-await-sync-query': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'error', diff --git a/lib/node-utils/index.ts b/lib/node-utils/index.ts index 33b8cce4..2e805ab0 100644 --- a/lib/node-utils/index.ts +++ b/lib/node-utils/index.ts @@ -234,7 +234,7 @@ export function isPromiseHandled(nodeIdentifier: TSESTree.Identifier): boolean { } export function getVariableReferences( - context: TSESLint.RuleContext, + context: TSESLint.RuleContext, node: TSESTree.Node ): TSESLint.Scope.Reference[] { if (ASTUtils.isVariableDeclarator(node)) { diff --git a/lib/rules/await-async-event.ts b/lib/rules/await-async-event.ts new file mode 100644 index 00000000..0ad2e433 --- /dev/null +++ b/lib/rules/await-async-event.ts @@ -0,0 +1,163 @@ +import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; + +import { createTestingLibraryRule } from '../create-testing-library-rule'; +import { + findClosestCallExpressionNode, + getFunctionName, + getInnermostReturningFunction, + getVariableReferences, + isPromiseHandled, +} from '../node-utils'; +import { EVENTS_SIMULATORS } from '../utils'; + +export const RULE_NAME = 'await-async-event'; +export type MessageIds = 'awaitAsyncEvent' | 'awaitAsyncEventWrapper'; +const FIRE_EVENT_NAME = 'fireEvent'; +const USER_EVENT_NAME = 'userEvent'; +type EventModules = typeof EVENTS_SIMULATORS[number]; +export type Options = [ + { + eventModule: EventModules | EventModules[]; + } +]; + +export default createTestingLibraryRule({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Enforce promises from async event methods are handled', + recommendedConfig: { + dom: ['error', { eventModule: 'userEvent' }], + angular: ['error', { eventModule: 'userEvent' }], + react: ['error', { eventModule: 'userEvent' }], + vue: ['error', { eventModule: ['fireEvent', 'userEvent'] }], + marko: ['error', { eventModule: ['fireEvent', 'userEvent'] }], + }, + }, + messages: { + awaitAsyncEvent: + 'Promise returned from async event method `{{ name }}` must be handled', + awaitAsyncEventWrapper: + 'Promise returned from `{{ name }}` wrapper over async event method must be handled', + }, + schema: [ + { + type: 'object', + default: {}, + additionalProperties: false, + properties: { + eventModule: { + default: USER_EVENT_NAME, + oneOf: [ + { + type: 'string', + enum: EVENTS_SIMULATORS, + }, + { + type: 'array', + items: { + type: 'string', + enum: EVENTS_SIMULATORS, + }, + }, + ], + }, + }, + }, + ], + }, + defaultOptions: [ + { + eventModule: USER_EVENT_NAME, + }, + ], + + create(context, [options], helpers) { + const functionWrappersNames: string[] = []; + + function reportUnhandledNode( + node: TSESTree.Identifier, + closestCallExpressionNode: TSESTree.CallExpression, + messageId: MessageIds = 'awaitAsyncEvent' + ): void { + if (!isPromiseHandled(node)) { + context.report({ + node: closestCallExpressionNode.callee, + messageId, + data: { name: node.name }, + }); + } + } + + function detectEventMethodWrapper(node: TSESTree.Identifier): void { + const innerFunction = getInnermostReturningFunction(context, node); + + if (innerFunction) { + functionWrappersNames.push(getFunctionName(innerFunction)); + } + } + + const eventModules = + typeof options.eventModule === 'string' + ? [options.eventModule] + : options.eventModule; + const isFireEventEnabled = eventModules.includes(FIRE_EVENT_NAME); + const isUserEventEnabled = eventModules.includes(USER_EVENT_NAME); + + return { + 'CallExpression Identifier'(node: TSESTree.Identifier) { + if ( + (isFireEventEnabled && helpers.isFireEventMethod(node)) || + (isUserEventEnabled && helpers.isUserEventMethod(node)) + ) { + detectEventMethodWrapper(node); + + const closestCallExpression = findClosestCallExpressionNode( + node, + true + ); + + if (!closestCallExpression || !closestCallExpression.parent) { + return; + } + + const references = getVariableReferences( + context, + closestCallExpression.parent + ); + + if (references.length === 0) { + reportUnhandledNode(node, closestCallExpression); + } else { + for (const reference of references) { + if (ASTUtils.isIdentifier(reference.identifier)) { + reportUnhandledNode( + reference.identifier, + closestCallExpression + ); + } + } + } + } else if (functionWrappersNames.includes(node.name)) { + // report promise returned from function wrapping fire event method + // previously detected + const closestCallExpression = findClosestCallExpressionNode( + node, + true + ); + + if (!closestCallExpression) { + return; + } + + reportUnhandledNode( + node, + closestCallExpression, + 'awaitAsyncEventWrapper' + ); + } + }, + }; + }, +}); diff --git a/lib/rules/await-fire-event.ts b/lib/rules/await-fire-event.ts deleted file mode 100644 index bfc298e0..00000000 --- a/lib/rules/await-fire-event.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; - -import { createTestingLibraryRule } from '../create-testing-library-rule'; -import { - findClosestCallExpressionNode, - getFunctionName, - getInnermostReturningFunction, - getVariableReferences, - isPromiseHandled, -} from '../node-utils'; - -export const RULE_NAME = 'await-fire-event'; -export type MessageIds = 'awaitFireEvent' | 'fireEventWrapper'; -type Options = []; - -export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Enforce promises from `fireEvent` methods to be handled', - recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: 'error', - marko: 'error', - }, - }, - messages: { - awaitFireEvent: - 'Promise returned from `fireEvent.{{ name }}` must be handled', - fireEventWrapper: - 'Promise returned from `{{ name }}` wrapper over fire event method must be handled', - }, - schema: [], - }, - defaultOptions: [], - - create(context, _, helpers) { - const functionWrappersNames: string[] = []; - - function reportUnhandledNode( - node: TSESTree.Identifier, - closestCallExpressionNode: TSESTree.CallExpression, - messageId: MessageIds = 'awaitFireEvent' - ): void { - if (!isPromiseHandled(node)) { - context.report({ - node: closestCallExpressionNode.callee, - messageId, - data: { name: node.name }, - }); - } - } - - function detectFireEventMethodWrapper(node: TSESTree.Identifier): void { - const innerFunction = getInnermostReturningFunction(context, node); - - if (innerFunction) { - functionWrappersNames.push(getFunctionName(innerFunction)); - } - } - - return { - 'CallExpression Identifier'(node: TSESTree.Identifier) { - if (helpers.isFireEventMethod(node)) { - detectFireEventMethodWrapper(node); - - const closestCallExpression = findClosestCallExpressionNode( - node, - true - ); - - if (!closestCallExpression || !closestCallExpression.parent) { - return; - } - - const references = getVariableReferences( - context, - closestCallExpression.parent - ); - - if (references.length === 0) { - reportUnhandledNode(node, closestCallExpression); - } else { - for (const reference of references) { - if (ASTUtils.isIdentifier(reference.identifier)) { - reportUnhandledNode( - reference.identifier, - closestCallExpression - ); - } - } - } - } else if (functionWrappersNames.includes(node.name)) { - // report promise returned from function wrapping fire event method - // previously detected - const closestCallExpression = findClosestCallExpressionNode( - node, - true - ); - - if (!closestCallExpression) { - return; - } - - reportUnhandledNode(node, closestCallExpression, 'fireEventWrapper'); - } - }, - }; - }, -}); diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index f608f04d..4dc40ef7 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -7,6 +7,12 @@ Object { "testing-library", ], "rules": Object { + "testing-library/await-async-event": Array [ + "error", + Object { + "eventModule": "userEvent", + }, + ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", @@ -36,6 +42,12 @@ Object { "testing-library", ], "rules": Object { + "testing-library/await-async-event": Array [ + "error", + Object { + "eventModule": "userEvent", + }, + ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", @@ -56,9 +68,17 @@ Object { "testing-library", ], "rules": Object { + "testing-library/await-async-event": Array [ + "error", + Object { + "eventModule": Array [ + "fireEvent", + "userEvent", + ], + }, + ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", - "testing-library/await-fire-event": "error", "testing-library/no-await-sync-query": "error", "testing-library/no-container": "error", "testing-library/no-debugging-utils": "error", @@ -87,6 +107,12 @@ Object { "testing-library", ], "rules": Object { + "testing-library/await-async-event": Array [ + "error", + Object { + "eventModule": "userEvent", + }, + ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", @@ -118,9 +144,17 @@ Object { "testing-library", ], "rules": Object { + "testing-library/await-async-event": Array [ + "error", + Object { + "eventModule": Array [ + "fireEvent", + "userEvent", + ], + }, + ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", - "testing-library/await-fire-event": "error", "testing-library/no-await-sync-query": "error", "testing-library/no-container": "error", "testing-library/no-debugging-utils": "error", diff --git a/tests/lib/rules/await-async-event.test.ts b/tests/lib/rules/await-async-event.test.ts new file mode 100644 index 00000000..54718d24 --- /dev/null +++ b/tests/lib/rules/await-async-event.test.ts @@ -0,0 +1,717 @@ +import rule, { Options, RULE_NAME } from '../../../lib/rules/await-async-event'; +import { createRuleTester } from '../test-utils'; + +const ruleTester = createRuleTester(); + +const FIRE_EVENT_ASYNC_FUNCTIONS = [ + 'click', + 'change', + 'focus', + 'blur', + 'keyDown', +] as const; +const USER_EVENT_ASYNC_FUNCTIONS = [ + 'click', + 'dblClick', + 'tripleClick', + 'hover', + 'unhover', + 'tab', + 'keyboard', + 'copy', + 'cut', + 'paste', + 'pointer', + 'clear', + 'deselectOptions', + 'selectOptions', + 'type', + 'upload', +] as const; +const FIRE_EVENT_ASYNC_FRAMEWORKS = [ + '@testing-library/vue', + '@marko/testing-library', +] as const; +const USER_EVENT_ASYNC_FRAMEWORKS = ['@testing-library/user-event'] as const; + +ruleTester.run(RULE_NAME, rule, { + valid: [ + ...FIRE_EVENT_ASYNC_FRAMEWORKS.flatMap((testingFramework) => [ + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('event method not called is valid', () => { + fireEvent.${eventMethod} + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('await promise from event method is valid', async () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('await several promises from event methods is valid', async () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('await promise kept in a var from event method is valid', async () => { + const promise = fireEvent.${eventMethod}(getByLabelText('username')) + await promise + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('chain then method to promise from event method is valid', async (done) => { + fireEvent.${eventMethod}(getByLabelText('username')) + .then(() => { done() }) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('chain then method to several promises from event methods is valid', async (done) => { + fireEvent.${eventMethod}(getByLabelText('username')).then(() => { + fireEvent.${eventMethod}(getByLabelText('username')).then(() => { done() }) + }) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + { + code: ` + import { fireEvent } from '${testingFramework}' + test('event methods wrapped with Promise.all are valid', async () => { + await Promise.all([ + fireEvent.${FIRE_EVENT_ASYNC_FUNCTIONS[0]}(getByText('Click me')), + fireEvent.${FIRE_EVENT_ASYNC_FUNCTIONS[1]}(getByText('Click me')), + ]) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + }, + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('return promise from event methods is valid', () => { + function triggerEvent() { + doSomething() + return fireEvent.${eventMethod}(getByLabelText('username')) + } + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('await promise returned from function wrapping event method is valid', () => { + function triggerEvent() { + doSomething() + return fireEvent.${eventMethod}(getByLabelText('username')) + } + + await triggerEvent() + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import { fireEvent } from 'somewhere-else' + test('unhandled promise from event not related to TL is valid', async () => { + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import { fireEvent } from 'test-utils' + test('await promise from event method imported from custom module is valid', async () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + { + // edge case for coverage: + // valid use case without call expression + // so there is no innermost function scope found + code: ` + import { fireEvent } from 'test-utils' + test('edge case for innermost function without call expression', async () => { + function triggerEvent() { + doSomething() + return fireEvent.focus(getByLabelText('username')) + } + + const reassignedFunction = triggerEvent + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + }, + ]), + + ...USER_EVENT_ASYNC_FRAMEWORKS.flatMap((testingFramework) => [ + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('event method not called is valid', () => { + userEvent.${eventMethod} + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('await promise from event method is valid', async () => { + await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('await several promises from event methods is valid', async () => { + await userEvent.${eventMethod}(getByLabelText('username')) + await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('await promise kept in a var from event method is valid', async () => { + const promise = userEvent.${eventMethod}(getByLabelText('username')) + await promise + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('chain then method to promise from event method is valid', async (done) => { + userEvent.${eventMethod}(getByLabelText('username')) + .then(() => { done() }) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('chain then method to several promises from event methods is valid', async (done) => { + userEvent.${eventMethod}(getByLabelText('username')).then(() => { + userEvent.${eventMethod}(getByLabelText('username')).then(() => { done() }) + }) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + { + code: ` + import userEvent from '${testingFramework}' + test('event methods wrapped with Promise.all are valid', async () => { + await Promise.all([ + userEvent.${USER_EVENT_ASYNC_FUNCTIONS[0]}(getByText('Click me')), + userEvent.${USER_EVENT_ASYNC_FUNCTIONS[1]}(getByText('Click me')), + ]) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + }, + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('return promise from event methods is valid', () => { + function triggerEvent() { + doSomething() + return userEvent.${eventMethod}(getByLabelText('username')) + } + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('await promise returned from function wrapping event method is valid', () => { + function triggerEvent() { + doSomething() + return userEvent.${eventMethod}(getByLabelText('username')) + } + + await triggerEvent() + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import userEvent from 'somewhere-else' + test('unhandled promise from event not related to TL is valid', async () => { + userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import userEvent from 'test-utils' + test('await promise from event method imported from custom module is valid', async () => { + await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('await promise from userEvent relying on default options', async () => { + await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + })), + { + // edge case for coverage: + // valid use case without call expression + // so there is no innermost function scope found + code: ` + import userEvent from 'test-utils' + test('edge case for innermost function without call expression', async () => { + function triggerEvent() { + doSomething() + return userEvent.focus(getByLabelText('username')) + } + + const reassignedFunction = triggerEvent + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + }, + { + code: ` + import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}' + import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}' + test('await promises from multiple event modules', async () => { + await fireEvent.click(getByLabelText('username')) + await userEvent.click(getByLabelText('username')) + }) + `, + options: [{ eventModule: ['userEvent', 'fireEvent'] }] as Options, + }, + ]), + ], + + invalid: [ + ...FIRE_EVENT_ASYNC_FRAMEWORKS.flatMap((testingFramework) => [ + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('unhandled promise from event method is invalid', async () => { + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + endColumn: 19 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent as testingLibraryFireEvent } from '${testingFramework}' + test('unhandled promise from aliased event method is invalid', async () => { + testingLibraryFireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + endColumn: 33 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import * as testingLibrary from '${testingFramework}' + test('unhandled promise from wildcard imported event method is invalid', async () => { + testingLibrary.fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + endColumn: 34 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('several unhandled promises from event methods is invalid', async () => { + fireEvent.${eventMethod}(getByLabelText('username')) + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + { + line: 5, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import { fireEvent } from '${testingFramework}' + test('unhandled promise from event method with aggressive reporting opted-out is invalid', async () => { + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import { fireEvent } from 'test-utils' + test( + 'unhandled promise from event method imported from custom module with aggressive reporting opted-out is invalid', + () => { + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 6, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import { fireEvent } from '${testingFramework}' + test( + 'unhandled promise from event method imported from default module with aggressive reporting opted-out is invalid', + () => { + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 6, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + test( + 'unhandled promise from event method kept in a var is invalid', + () => { + const promise = fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 6, + column: 25, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('unhandled promise returned from function wrapping event method is invalid', () => { + function triggerEvent() { + doSomething() + return fireEvent.${eventMethod}(getByLabelText('username')) + } + + triggerEvent() + }) + `, + errors: [ + { + line: 9, + column: 9, + messageId: 'awaitAsyncEventWrapper', + data: { name: 'triggerEvent' }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ]), + ...USER_EVENT_ASYNC_FRAMEWORKS.flatMap((testingFramework) => [ + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import userEvent from '${testingFramework}' + test('unhandled promise from event method is invalid', async () => { + userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + endColumn: 19 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'userEvent' }], + } as const) + ), + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import testingLibraryUserEvent from '${testingFramework}' + test('unhandled promise imported from alternate name event method is invalid', async () => { + testingLibraryUserEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + endColumn: 33 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'userEvent' }], + } as const) + ), + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import userEvent from '${testingFramework}' + test('several unhandled promises from event methods is invalid', async () => { + userEvent.${eventMethod}(getByLabelText('username')) + userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + { + line: 5, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'userEvent' }], + } as const) + ), + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import userEvent from '${testingFramework}' + test( + 'unhandled promise from event method kept in a var is invalid', + () => { + const promise = userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 6, + column: 25, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'userEvent' }], + } as const) + ), + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import userEvent from '${testingFramework}' + test('unhandled promise returned from function wrapping event method is invalid', () => { + function triggerEvent() { + doSomething() + return userEvent.${eventMethod}(getByLabelText('username')) + } + + triggerEvent() + }) + `, + errors: [ + { + line: 9, + column: 9, + messageId: 'awaitAsyncEventWrapper', + data: { name: 'triggerEvent' }, + }, + ], + options: [{ eventModule: 'userEvent' }], + } as const) + ), + ]), + { + code: ` + import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}' + import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}' + test('unhandled promises from multiple event modules', async () => { + fireEvent.click(getByLabelText('username')) + userEvent.click(getByLabelText('username')) + }) + `, + errors: [ + { + line: 5, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: 'click' }, + }, + { + line: 6, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: 'click' }, + }, + ], + options: [{ eventModule: ['userEvent', 'fireEvent'] }] as Options, + }, + { + code: ` + import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}' + import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}' + test('unhandled promise from userEvent relying on default options', async () => { + fireEvent.click(getByLabelText('username')) + userEvent.click(getByLabelText('username')) + }) + `, + errors: [ + { + line: 6, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: 'click' }, + }, + ], + }, + ], +}); diff --git a/tests/lib/rules/await-fire-event.test.ts b/tests/lib/rules/await-fire-event.test.ts deleted file mode 100644 index 62b860e2..00000000 --- a/tests/lib/rules/await-fire-event.test.ts +++ /dev/null @@ -1,353 +0,0 @@ -import rule, { RULE_NAME } from '../../../lib/rules/await-fire-event'; -import { createRuleTester } from '../test-utils'; - -const ruleTester = createRuleTester(); - -const COMMON_FIRE_EVENT_METHODS: string[] = [ - 'click', - 'change', - 'focus', - 'blur', - 'keyDown', -]; -const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/vue', - '@marko/testing-library', -]; - -ruleTester.run(RULE_NAME, rule, { - valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('fire event method not called is valid', () => { - fireEvent.${fireEventMethod} - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('await promise from fire event method is valid', async () => { - await fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('await several promises from fire event methods is valid', async () => { - await fireEvent.${fireEventMethod}(getByLabelText('username')) - await fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('await promise kept in a var from fire event method is valid', async () => { - const promise = fireEvent.${fireEventMethod}(getByLabelText('username')) - await promise - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('chain then method to promise from fire event method is valid', async (done) => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - .then(() => { done() }) - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('chain then method to several promises from fire event methods is valid', async (done) => { - fireEvent.${fireEventMethod}(getByLabelText('username')).then(() => { - fireEvent.${fireEventMethod}(getByLabelText('username')).then(() => { done() }) - }) - }) - `, - })), - { - code: ` - import { fireEvent } from '${testingFramework}' - test('fireEvent methods wrapped with Promise.all are valid', async () => { - await Promise.all([ - fireEvent.blur(getByText('Click me')), - fireEvent.click(getByText('Click me')), - ]) - }) - `, - }, - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('return promise from fire event methods is valid', () => { - function triggerEvent() { - doSomething() - return fireEvent.${fireEventMethod}(getByLabelText('username')) - } - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('await promise returned from function wrapping fire event method is valid', () => { - function triggerEvent() { - doSomething() - return fireEvent.${fireEventMethod}(getByLabelText('username')) - } - - await triggerEvent() - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - import { fireEvent } from 'somewhere-else' - test('unhandled promise from fire event not related to TL is valid', async () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - import { fireEvent } from 'test-utils' - test('await promise from fire event method imported from custom module is valid', async () => { - await fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - })), - - { - // edge case for coverage: - // valid use case without call expression - // so there is no innermost function scope found - code: ` - import { fireEvent } from 'test-utils' - test('edge case for innermost function without call expression', async () => { - function triggerEvent() { - doSomething() - return fireEvent.focus(getByLabelText('username')) - } - - const reassignedFunction = triggerEvent - }) - `, - }, - ]), - - invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('unhandled promise from fire event method is invalid', async () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 4, - column: 9, - endColumn: 19 + fireEventMethod.length, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import { fireEvent as testingLibraryFireEvent } from '${testingFramework}' - test('unhandled promise from aliased fire event method is invalid', async () => { - testingLibraryFireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 4, - column: 9, - endColumn: 33 + fireEventMethod.length, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import * as testingLibrary from '${testingFramework}' - test('unhandled promise from wildcard imported fire event method is invalid', async () => { - testingLibrary.fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 4, - column: 9, - endColumn: 34 + fireEventMethod.length, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('several unhandled promises from fire event methods is invalid', async () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 4, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - { - line: 5, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - import { fireEvent } from '${testingFramework}' - test('unhandled promise from fire event method with aggressive reporting opted-out is invalid', async () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 4, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - import { fireEvent } from 'test-utils' - test( - 'unhandled promise from fire event method imported from custom module with aggressive reporting opted-out is invalid', - () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 6, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - import { fireEvent } from '${testingFramework}' - test( - 'unhandled promise from fire event method imported from default module with aggressive reporting opted-out is invalid', - () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 6, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import { fireEvent } from '${testingFramework}' - test( - 'unhandled promise from fire event method kept in a var is invalid', - () => { - const promise = fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 6, - column: 25, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('unhandled promise returned from function wrapping fire event method is invalid', () => { - function triggerEvent() { - doSomething() - return fireEvent.${fireEventMethod}(getByLabelText('username')) - } - - triggerEvent() - }) - `, - errors: [ - { - line: 9, - column: 9, - messageId: 'fireEventWrapper', - data: { name: 'triggerEvent' }, - }, - ], - } as const) - ), - ]), -}); From b4b5394b18979e7fde175d36e0cbe9cec5fb2f85 Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Sun, 2 Oct 2022 12:59:17 -0700 Subject: [PATCH 06/20] feat(await-async-event): add basic fixer (#656) --- README.md | 2 +- lib/rules/await-async-event.ts | 52 ++++++--- tests/lib/rules/await-async-event.test.ts | 122 +++++++++++++++++++++- 3 files changed, 159 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index ff2ae87a..856c593c 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,7 @@ To enable this configuration use the `extends` property in your | Name | Description | 🔧 | Included in configurations | | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | --- | ---------------------------------------------------------------------------------- | -| [`await-async-event`](./docs/rules/await-async-event.md) | Enforce promises from async event methods are handled | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | +| [`await-async-event`](./docs/rules/await-async-event.md) | Enforce promises from async event methods are handled | 🔧 | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`await-async-query`](./docs/rules/await-async-query.md) | Enforce promises from async queries to be handled | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`await-async-utils`](./docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`consistent-data-testid`](./docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | diff --git a/lib/rules/await-async-event.ts b/lib/rules/await-async-event.ts index 0ad2e433..c5aebff6 100644 --- a/lib/rules/await-async-event.ts +++ b/lib/rules/await-async-event.ts @@ -1,4 +1,4 @@ -import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; +import { ASTUtils, TSESLint, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { @@ -6,6 +6,7 @@ import { getFunctionName, getInnermostReturningFunction, getVariableReferences, + isMemberExpression, isPromiseHandled, } from '../node-utils'; import { EVENTS_SIMULATORS } from '../utils'; @@ -41,6 +42,7 @@ export default createTestingLibraryRule({ awaitAsyncEventWrapper: 'Promise returned from `{{ name }}` wrapper over async event method must be handled', }, + fixable: 'code', schema: [ { type: 'object', @@ -76,16 +78,23 @@ export default createTestingLibraryRule({ create(context, [options], helpers) { const functionWrappersNames: string[] = []; - function reportUnhandledNode( - node: TSESTree.Identifier, - closestCallExpressionNode: TSESTree.CallExpression, - messageId: MessageIds = 'awaitAsyncEvent' - ): void { + function reportUnhandledNode({ + node, + closestCallExpression, + messageId = 'awaitAsyncEvent', + fix, + }: { + node: TSESTree.Identifier; + closestCallExpression: TSESTree.CallExpression; + messageId?: MessageIds; + fix?: TSESLint.ReportFixFunction; + }): void { if (!isPromiseHandled(node)) { context.report({ - node: closestCallExpressionNode.callee, + node: closestCallExpression.callee, messageId, data: { name: node.name }, + fix, }); } } @@ -128,14 +137,24 @@ export default createTestingLibraryRule({ ); if (references.length === 0) { - reportUnhandledNode(node, closestCallExpression); + reportUnhandledNode({ + node, + closestCallExpression, + fix: (fixer) => { + if (isMemberExpression(node.parent)) { + return fixer.insertTextBefore(node.parent, 'await '); + } + + return null; + }, + }); } else { for (const reference of references) { if (ASTUtils.isIdentifier(reference.identifier)) { - reportUnhandledNode( - reference.identifier, - closestCallExpression - ); + reportUnhandledNode({ + node: reference.identifier, + closestCallExpression, + }); } } } @@ -151,11 +170,14 @@ export default createTestingLibraryRule({ return; } - reportUnhandledNode( + reportUnhandledNode({ node, closestCallExpression, - 'awaitAsyncEventWrapper' - ); + messageId: 'awaitAsyncEventWrapper', + fix: (fixer) => { + return fixer.insertTextBefore(node, 'await '); + }, + }); } }, }; diff --git a/tests/lib/rules/await-async-event.test.ts b/tests/lib/rules/await-async-event.test.ts index 54718d24..5b245908 100644 --- a/tests/lib/rules/await-async-event.test.ts +++ b/tests/lib/rules/await-async-event.test.ts @@ -359,6 +359,12 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent } from '${testingFramework}' + test('unhandled promise from event method is invalid', async () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...FIRE_EVENT_ASYNC_FUNCTIONS.map( @@ -380,6 +386,12 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent as testingLibraryFireEvent } from '${testingFramework}' + test('unhandled promise from aliased event method is invalid', async () => { + await testingLibraryFireEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...FIRE_EVENT_ASYNC_FUNCTIONS.map( @@ -401,6 +413,12 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'fireEvent' }], + output: ` + import * as testingLibrary from '${testingFramework}' + test('unhandled promise from wildcard imported event method is invalid', async () => { + await testingLibrary.fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...FIRE_EVENT_ASYNC_FUNCTIONS.map( @@ -428,6 +446,13 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent } from '${testingFramework}' + test('several unhandled promises from event methods is invalid', async () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...FIRE_EVENT_ASYNC_FUNCTIONS.map( @@ -451,6 +476,12 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent } from '${testingFramework}' + test('unhandled promise from event method with aggressive reporting opted-out is invalid', async () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...FIRE_EVENT_ASYNC_FUNCTIONS.map( @@ -476,6 +507,14 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent } from 'test-utils' + test( + 'unhandled promise from event method imported from custom module with aggressive reporting opted-out is invalid', + () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...FIRE_EVENT_ASYNC_FUNCTIONS.map( @@ -501,6 +540,14 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent } from '${testingFramework}' + test( + 'unhandled promise from event method imported from default module with aggressive reporting opted-out is invalid', + () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), @@ -524,6 +571,14 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent } from '${testingFramework}' + test( + 'unhandled promise from event method kept in a var is invalid', + () => { + const promise = await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...FIRE_EVENT_ASYNC_FUNCTIONS.map( @@ -549,6 +604,17 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent } from '${testingFramework}' + test('unhandled promise returned from function wrapping event method is invalid', () => { + function triggerEvent() { + doSomething() + return fireEvent.${eventMethod}(getByLabelText('username')) + } + + await triggerEvent() + }) + `, } as const) ), ]), @@ -572,6 +638,12 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'userEvent' }], + output: ` + import userEvent from '${testingFramework}' + test('unhandled promise from event method is invalid', async () => { + await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...USER_EVENT_ASYNC_FUNCTIONS.map( @@ -593,6 +665,12 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'userEvent' }], + output: ` + import testingLibraryUserEvent from '${testingFramework}' + test('unhandled promise imported from alternate name event method is invalid', async () => { + await testingLibraryUserEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...USER_EVENT_ASYNC_FUNCTIONS.map( @@ -620,6 +698,13 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'userEvent' }], + output: ` + import userEvent from '${testingFramework}' + test('several unhandled promises from event methods is invalid', async () => { + await userEvent.${eventMethod}(getByLabelText('username')) + await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...USER_EVENT_ASYNC_FUNCTIONS.map( @@ -642,6 +727,14 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'userEvent' }], + output: ` + import userEvent from '${testingFramework}' + test( + 'unhandled promise from event method kept in a var is invalid', + () => { + const promise = await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, } as const) ), ...USER_EVENT_ASYNC_FUNCTIONS.map( @@ -667,6 +760,17 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: 'userEvent' }], + output: ` + import userEvent from '${testingFramework}' + test('unhandled promise returned from function wrapping event method is invalid', () => { + function triggerEvent() { + doSomething() + return userEvent.${eventMethod}(getByLabelText('username')) + } + + await triggerEvent() + }) + `, } as const) ), ]), @@ -674,7 +778,7 @@ ruleTester.run(RULE_NAME, rule, { code: ` import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}' import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}' - test('unhandled promises from multiple event modules', async () => { + test('unhandled promises from multiple event modules', async () => { fireEvent.click(getByLabelText('username')) userEvent.click(getByLabelText('username')) }) @@ -694,6 +798,14 @@ ruleTester.run(RULE_NAME, rule, { }, ], options: [{ eventModule: ['userEvent', 'fireEvent'] }] as Options, + output: ` + import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}' + import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}' + test('unhandled promises from multiple event modules', async () => { + await fireEvent.click(getByLabelText('username')) + await userEvent.click(getByLabelText('username')) + }) + `, }, { code: ` @@ -712,6 +824,14 @@ ruleTester.run(RULE_NAME, rule, { data: { name: 'click' }, }, ], + output: ` + import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}' + import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}' + test('unhandled promise from userEvent relying on default options', async () => { + fireEvent.click(getByLabelText('username')) + await userEvent.click(getByLabelText('username')) + }) + `, }, ], }); From 8dfe155f5658c14004c80ddef274171e1d2950b1 Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Sun, 2 Oct 2022 13:40:04 -0700 Subject: [PATCH 07/20] feat(no-node-access): add to DOM config by default (#661) BREAKING CHANGE: `no-node-access` is now enabled by default in the DOM config --- README.md | 2 +- lib/configs/dom.ts | 1 + lib/rules/no-node-access.ts | 2 +- tests/__snapshots__/index.test.ts.snap | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 856c593c..8ece9fd0 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ To enable this configuration use the `extends` property in your | [`no-dom-import`](./docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | 🔧 | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-global-regexp-flag-in-query`](./docs/rules/no-global-regexp-flag-in-query.md) | Disallow the use of the global RegExp flag (/g) in queries | 🔧 | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-manual-cleanup`](./docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | ![react-badge][] ![vue-badge][] | -| [`no-node-access`](./docs/rules/no-node-access.md) | Disallow direct Node access | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | +| [`no-node-access`](./docs/rules/no-node-access.md) | Disallow direct Node access | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-promise-in-fire-event`](./docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-render-in-lifecycle`](./docs/rules/no-render-in-lifecycle.md) | Disallow the use of `render` in testing frameworks setup functions | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-unnecessary-act`](./docs/rules/no-unnecessary-act.md) | Disallow wrapping Testing Library utils or empty callbacks in `act` | | ![react-badge][] ![marko-badge][] | diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index 13fbc8d7..b0fd6d33 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -13,6 +13,7 @@ export = { 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', 'testing-library/no-global-regexp-flag-in-query': 'error', + 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', diff --git a/lib/rules/no-node-access.ts b/lib/rules/no-node-access.ts index 1beb3f2c..78ffdc31 100644 --- a/lib/rules/no-node-access.ts +++ b/lib/rules/no-node-access.ts @@ -14,7 +14,7 @@ export default createTestingLibraryRule({ docs: { description: 'Disallow direct Node access', recommendedConfig: { - dom: false, + dom: 'error', angular: 'error', react: 'error', vue: 'error', diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index 4dc40ef7..33902d1e 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -52,6 +52,7 @@ Object { "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", "testing-library/no-global-regexp-flag-in-query": "error", + "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", "testing-library/no-wait-for-empty-callback": "error", "testing-library/no-wait-for-multiple-assertions": "error", From 8063f8f6a8e1ed0304c6eefc3b00493997952cd1 Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Sun, 2 Oct 2022 18:01:19 -0700 Subject: [PATCH 08/20] feat(no-debugging-utils): warn instead of error in all configs (#662) --- lib/configs/angular.ts | 2 +- lib/configs/marko.ts | 2 +- lib/configs/react.ts | 2 +- lib/configs/vue.ts | 2 +- lib/rules/no-debugging-utils.ts | 8 ++++---- tests/__snapshots__/index.test.ts.snap | 8 ++++---- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index e0a14a8b..b24f0bf0 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -13,7 +13,7 @@ export = { 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', 'testing-library/no-container': 'error', - 'testing-library/no-debugging-utils': 'error', + 'testing-library/no-debugging-utils': 'warn', 'testing-library/no-dom-import': ['error', 'angular'], 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-node-access': 'error', diff --git a/lib/configs/marko.ts b/lib/configs/marko.ts index fbbc67a4..6d59ebec 100644 --- a/lib/configs/marko.ts +++ b/lib/configs/marko.ts @@ -13,7 +13,7 @@ export = { 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', 'testing-library/no-container': 'error', - 'testing-library/no-debugging-utils': 'error', + 'testing-library/no-debugging-utils': 'warn', 'testing-library/no-dom-import': ['error', 'marko'], 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-node-access': 'error', diff --git a/lib/configs/react.ts b/lib/configs/react.ts index e52476fc..6f8acde9 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -13,7 +13,7 @@ export = { 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', 'testing-library/no-container': 'error', - 'testing-library/no-debugging-utils': 'error', + 'testing-library/no-debugging-utils': 'warn', 'testing-library/no-dom-import': ['error', 'react'], 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-manual-cleanup': 'error', diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index bb4b3a21..fe1e62e0 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -13,7 +13,7 @@ export = { 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', 'testing-library/no-container': 'error', - 'testing-library/no-debugging-utils': 'error', + 'testing-library/no-debugging-utils': 'warn', 'testing-library/no-dom-import': ['error', 'vue'], 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-manual-cleanup': 'error', diff --git a/lib/rules/no-debugging-utils.ts b/lib/rules/no-debugging-utils.ts index 83e97f0a..05e95c06 100644 --- a/lib/rules/no-debugging-utils.ts +++ b/lib/rules/no-debugging-utils.ts @@ -29,10 +29,10 @@ export default createTestingLibraryRule({ description: 'Disallow the use of debugging utilities like `debug`', recommendedConfig: { dom: false, - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', + angular: 'warn', + react: 'warn', + vue: 'warn', + marko: 'warn', }, }, messages: { diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index 33902d1e..1dba0cd0 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -17,7 +17,7 @@ Object { "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", "testing-library/no-container": "error", - "testing-library/no-debugging-utils": "error", + "testing-library/no-debugging-utils": "warn", "testing-library/no-dom-import": Array [ "error", "angular", @@ -82,7 +82,7 @@ Object { "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", "testing-library/no-container": "error", - "testing-library/no-debugging-utils": "error", + "testing-library/no-debugging-utils": "warn", "testing-library/no-dom-import": Array [ "error", "marko", @@ -118,7 +118,7 @@ Object { "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", "testing-library/no-container": "error", - "testing-library/no-debugging-utils": "error", + "testing-library/no-debugging-utils": "warn", "testing-library/no-dom-import": Array [ "error", "react", @@ -158,7 +158,7 @@ Object { "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", "testing-library/no-container": "error", - "testing-library/no-debugging-utils": "error", + "testing-library/no-debugging-utils": "warn", "testing-library/no-dom-import": Array [ "error", "vue", From e394ce0962616e79d45b9abed8afead7e00f5d13 Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Mon, 3 Oct 2022 03:15:28 -0700 Subject: [PATCH 09/20] feat(no-debugging-utils): enable all debug methods in all configs by default (#663) BREAKING CHANGE: `no-debugging-utils` now enables all debug methods in all configs by default --- docs/rules/no-debugging-utils.md | 2 +- lib/rules/no-debugging-utils.ts | 18 ++++++++++++------ tests/lib/rules/no-debugging-utils.test.ts | 4 ---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/rules/no-debugging-utils.md b/docs/rules/no-debugging-utils.md index 7628e04a..e8b0922a 100644 --- a/docs/rules/no-debugging-utils.md +++ b/docs/rules/no-debugging-utils.md @@ -13,7 +13,7 @@ This rule supports disallowing the following debugging utilities: - `logDOM` - `prettyFormat` -By default, only `debug` and `logTestingPlaygroundURL` are disallowed. +By default, all are disallowed. Examples of **incorrect** code for this rule: diff --git a/lib/rules/no-debugging-utils.ts b/lib/rules/no-debugging-utils.ts index 05e95c06..5d18499b 100644 --- a/lib/rules/no-debugging-utils.ts +++ b/lib/rules/no-debugging-utils.ts @@ -13,14 +13,22 @@ import { } from '../node-utils'; import { DEBUG_UTILS } from '../utils'; -type DebugUtilsToCheckFor = Partial< - Record ->; +type DebugUtilsToCheckForConfig = Record; +type DebugUtilsToCheckFor = Partial; export const RULE_NAME = 'no-debugging-utils'; export type MessageIds = 'noDebug'; type Options = [{ utilsToCheckFor?: DebugUtilsToCheckFor }]; +const defaultUtilsToCheckFor: DebugUtilsToCheckForConfig = { + debug: true, + logTestingPlaygroundURL: true, + prettyDOM: true, + logRoles: true, + logDOM: true, + prettyFormat: true, +}; + export default createTestingLibraryRule({ name: RULE_NAME, meta: { @@ -60,9 +68,7 @@ export default createTestingLibraryRule({ }, ], }, - defaultOptions: [ - { utilsToCheckFor: { debug: true, logTestingPlaygroundURL: true } }, - ], + defaultOptions: [{ utilsToCheckFor: defaultUtilsToCheckFor }], create(context, [{ utilsToCheckFor = {} }], helpers) { const suspiciousDebugVariableNames: string[] = []; diff --git a/tests/lib/rules/no-debugging-utils.test.ts b/tests/lib/rules/no-debugging-utils.test.ts index 3879d9e2..b31861a7 100644 --- a/tests/lib/rules/no-debugging-utils.test.ts +++ b/tests/lib/rules/no-debugging-utils.test.ts @@ -448,7 +448,6 @@ ruleTester.run(RULE_NAME, rule, { import { screen } from '@testing-library/dom' screen.logTestingPlaygroundURL() `, - options: [{ utilsToCheckFor: { logTestingPlaygroundURL: true } }], errors: [ { line: 3, @@ -462,7 +461,6 @@ ruleTester.run(RULE_NAME, rule, { import { logRoles } from '@testing-library/dom' logRoles(document.createElement('nav')) `, - options: [{ utilsToCheckFor: { logRoles: true } }], errors: [ { line: 3, @@ -476,7 +474,6 @@ ruleTester.run(RULE_NAME, rule, { import { screen } from '@testing-library/dom' screen.logTestingPlaygroundURL() `, - options: [{ utilsToCheckFor: { logRoles: true } }], errors: [ { line: 3, @@ -490,7 +487,6 @@ ruleTester.run(RULE_NAME, rule, { import { screen } from '@testing-library/dom' screen.logTestingPlaygroundURL() `, - options: [{ utilsToCheckFor: { debug: false } }], errors: [ { line: 3, From 7ed3da8ef5b454baea76f8f14465644311366270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Wed, 5 Oct 2022 09:59:06 +0200 Subject: [PATCH 10/20] feat(no-await-sync-query): rename to `no-await-sync-queries` (#666) BREAKING CHANGE: `no-await-sync-query` is now called `no-await-sync-queries` --- README.md | 4 ++-- ...no-await-sync-query.md => no-await-sync-queries.md} | 2 +- lib/configs/angular.ts | 2 +- lib/configs/dom.ts | 2 +- lib/configs/marko.ts | 2 +- lib/configs/react.ts | 2 +- lib/configs/vue.ts | 2 +- ...no-await-sync-query.ts => no-await-sync-queries.ts} | 2 +- tests/__snapshots__/index.test.ts.snap | 10 +++++----- ...ync-query.test.ts => no-await-sync-queries.test.ts} | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) rename docs/rules/{no-await-sync-query.md => no-await-sync-queries.md} (97%) rename lib/rules/{no-await-sync-query.ts => no-await-sync-queries.ts} (95%) rename tests/lib/rules/{no-await-sync-query.test.ts => no-await-sync-queries.test.ts} (99%) diff --git a/README.md b/README.md index 8ece9fd0..392dfa9e 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Then configure the rules you want to use within `rules` property of your `.eslin { "rules": { "testing-library/await-async-query": "error", - "testing-library/no-await-sync-query": "error", + "testing-library/no-await-sync-queries": "error", "testing-library/no-debugging-utils": "warn", "testing-library/no-dom-import": "off" } @@ -211,7 +211,7 @@ To enable this configuration use the `extends` property in your | [`await-async-utils`](./docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`consistent-data-testid`](./docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | | [`no-await-sync-events`](./docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | | | -| [`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][] ![marko-badge][] | +| [`no-await-sync-queries`](./docs/rules/no-await-sync-queries.md) | Disallow unnecessary `await` for sync queries | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-container`](./docs/rules/no-container.md) | Disallow the use of `container` methods | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-debugging-utils`](./docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-dom-import`](./docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | 🔧 | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | diff --git a/docs/rules/no-await-sync-query.md b/docs/rules/no-await-sync-queries.md similarity index 97% rename from docs/rules/no-await-sync-query.md rename to docs/rules/no-await-sync-queries.md index c86baf47..2ae7357a 100644 --- a/docs/rules/no-await-sync-query.md +++ b/docs/rules/no-await-sync-queries.md @@ -1,4 +1,4 @@ -# Disallow unnecessary `await` for sync queries (`testing-library/no-await-sync-query`) +# Disallow unnecessary `await` for sync queries (`testing-library/no-await-sync-queries`) Ensure that sync queries are not awaited unnecessarily. diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index b24f0bf0..3b2594ba 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -11,7 +11,7 @@ export = { ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/no-await-sync-query': 'error', + 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'warn', 'testing-library/no-dom-import': ['error', 'angular'], diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index b0fd6d33..80aaf58e 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -11,7 +11,7 @@ export = { ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/no-await-sync-query': 'error', + 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', diff --git a/lib/configs/marko.ts b/lib/configs/marko.ts index 6d59ebec..735a76a0 100644 --- a/lib/configs/marko.ts +++ b/lib/configs/marko.ts @@ -11,7 +11,7 @@ export = { ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/no-await-sync-query': 'error', + 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'warn', 'testing-library/no-dom-import': ['error', 'marko'], diff --git a/lib/configs/react.ts b/lib/configs/react.ts index 6f8acde9..ab3b12d4 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -11,7 +11,7 @@ export = { ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/no-await-sync-query': 'error', + 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'warn', 'testing-library/no-dom-import': ['error', 'react'], diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index fe1e62e0..78ce9cf4 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -11,7 +11,7 @@ export = { ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/no-await-sync-query': 'error', + 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'warn', 'testing-library/no-dom-import': ['error', 'vue'], diff --git a/lib/rules/no-await-sync-query.ts b/lib/rules/no-await-sync-queries.ts similarity index 95% rename from lib/rules/no-await-sync-query.ts rename to lib/rules/no-await-sync-queries.ts index 70c86e8b..c6f4a70c 100644 --- a/lib/rules/no-await-sync-query.ts +++ b/lib/rules/no-await-sync-queries.ts @@ -3,7 +3,7 @@ import { TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { getDeepestIdentifierNode } from '../node-utils'; -export const RULE_NAME = 'no-await-sync-query'; +export const RULE_NAME = 'no-await-sync-queries'; export type MessageIds = 'noAwaitSyncQuery'; type Options = []; diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index 1dba0cd0..bfece0f2 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -15,7 +15,7 @@ Object { ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", - "testing-library/no-await-sync-query": "error", + "testing-library/no-await-sync-queries": "error", "testing-library/no-container": "error", "testing-library/no-debugging-utils": "warn", "testing-library/no-dom-import": Array [ @@ -50,7 +50,7 @@ Object { ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", - "testing-library/no-await-sync-query": "error", + "testing-library/no-await-sync-queries": "error", "testing-library/no-global-regexp-flag-in-query": "error", "testing-library/no-node-access": "error", "testing-library/no-promise-in-fire-event": "error", @@ -80,7 +80,7 @@ Object { ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", - "testing-library/no-await-sync-query": "error", + "testing-library/no-await-sync-queries": "error", "testing-library/no-container": "error", "testing-library/no-debugging-utils": "warn", "testing-library/no-dom-import": Array [ @@ -116,7 +116,7 @@ Object { ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", - "testing-library/no-await-sync-query": "error", + "testing-library/no-await-sync-queries": "error", "testing-library/no-container": "error", "testing-library/no-debugging-utils": "warn", "testing-library/no-dom-import": Array [ @@ -156,7 +156,7 @@ Object { ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", - "testing-library/no-await-sync-query": "error", + "testing-library/no-await-sync-queries": "error", "testing-library/no-container": "error", "testing-library/no-debugging-utils": "warn", "testing-library/no-dom-import": Array [ diff --git a/tests/lib/rules/no-await-sync-query.test.ts b/tests/lib/rules/no-await-sync-queries.test.ts similarity index 99% rename from tests/lib/rules/no-await-sync-query.test.ts rename to tests/lib/rules/no-await-sync-queries.test.ts index 9538a979..3b088ad0 100644 --- a/tests/lib/rules/no-await-sync-query.test.ts +++ b/tests/lib/rules/no-await-sync-queries.test.ts @@ -1,4 +1,4 @@ -import rule, { RULE_NAME } from '../../../lib/rules/no-await-sync-query'; +import rule, { RULE_NAME } from '../../../lib/rules/no-await-sync-queries'; import { SYNC_QUERIES_COMBINATIONS, ASYNC_QUERIES_COMBINATIONS, From afce5ea7888db3070711c39baf470e46a01f99a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Wed, 5 Oct 2022 12:30:03 +0200 Subject: [PATCH 11/20] feat(no-await-sync-events): add to DOM, Angular & React configs by default (#667) BREAKING CHANGE: `no-await-sync-events` is now enabled by default in the DOM, Angular & React configs --- README.md | 2 +- lib/configs/angular.ts | 1 + lib/configs/dom.ts | 1 + lib/configs/react.ts | 1 + lib/rules/no-await-sync-events.ts | 6 +++--- tests/__snapshots__/index.test.ts.snap | 3 +++ 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 392dfa9e..e37f89e4 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ To enable this configuration use the `extends` property in your | [`await-async-query`](./docs/rules/await-async-query.md) | Enforce promises from async queries to be handled | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`await-async-utils`](./docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`consistent-data-testid`](./docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | -| [`no-await-sync-events`](./docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | | | +| [`no-await-sync-events`](./docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | | ![dom-badge][] ![angular-badge][] ![react-badge][] | | [`no-await-sync-queries`](./docs/rules/no-await-sync-queries.md) | Disallow unnecessary `await` for sync queries | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-container`](./docs/rules/no-container.md) | Disallow the use of `container` methods | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`no-debugging-utils`](./docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | | ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index 3b2594ba..de23cedc 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -11,6 +11,7 @@ export = { ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', + 'testing-library/no-await-sync-events': 'error', 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'warn', diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index 80aaf58e..4360414a 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -11,6 +11,7 @@ export = { ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', + 'testing-library/no-await-sync-events': 'error', 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-node-access': 'error', diff --git a/lib/configs/react.ts b/lib/configs/react.ts index ab3b12d4..8d766247 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -11,6 +11,7 @@ export = { ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', + 'testing-library/no-await-sync-events': 'error', 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'warn', diff --git a/lib/rules/no-await-sync-events.ts b/lib/rules/no-await-sync-events.ts index ebb0b5bc..8c7b0244 100644 --- a/lib/rules/no-await-sync-events.ts +++ b/lib/rules/no-await-sync-events.ts @@ -25,9 +25,9 @@ export default createTestingLibraryRule({ docs: { description: 'Disallow unnecessary `await` for sync events', recommendedConfig: { - dom: false, - angular: false, - react: false, + dom: 'error', + angular: 'error', + react: 'error', vue: false, marko: false, }, diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index bfece0f2..ccaa271a 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -15,6 +15,7 @@ Object { ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", + "testing-library/no-await-sync-events": "error", "testing-library/no-await-sync-queries": "error", "testing-library/no-container": "error", "testing-library/no-debugging-utils": "warn", @@ -50,6 +51,7 @@ Object { ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", + "testing-library/no-await-sync-events": "error", "testing-library/no-await-sync-queries": "error", "testing-library/no-global-regexp-flag-in-query": "error", "testing-library/no-node-access": "error", @@ -116,6 +118,7 @@ Object { ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", + "testing-library/no-await-sync-events": "error", "testing-library/no-await-sync-queries": "error", "testing-library/no-container": "error", "testing-library/no-debugging-utils": "warn", From c1803df5e35188ed5fcaeaf45ebcea379fa36838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Wed, 5 Oct 2022 12:52:10 +0200 Subject: [PATCH 12/20] feat(await-async-query): rename to `await-async-queries` (#665) BREAKING CHANGE: `await-async-query` is now called `await-async-queries` --- README.md | 4 ++-- .../{await-async-query.md => await-async-queries.md} | 2 +- lib/configs/angular.ts | 2 +- lib/configs/dom.ts | 2 +- lib/configs/marko.ts | 2 +- lib/configs/react.ts | 2 +- lib/configs/vue.ts | 2 +- .../{await-async-query.ts => await-async-queries.ts} | 2 +- tests/__snapshots__/index.test.ts.snap | 10 +++++----- ...async-query.test.ts => await-async-queries.test.ts} | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) rename docs/rules/{await-async-query.md => await-async-queries.md} (98%) rename lib/rules/{await-async-query.ts => await-async-queries.ts} (98%) rename tests/lib/rules/{await-async-query.test.ts => await-async-queries.test.ts} (99%) diff --git a/README.md b/README.md index e37f89e4..a0ca5920 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Then configure the rules you want to use within `rules` property of your `.eslin ```json { "rules": { - "testing-library/await-async-query": "error", + "testing-library/await-async-queries": "error", "testing-library/no-await-sync-queries": "error", "testing-library/no-debugging-utils": "warn", "testing-library/no-dom-import": "off" @@ -207,7 +207,7 @@ To enable this configuration use the `extends` property in your | Name | Description | 🔧 | Included in configurations | | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | --- | ---------------------------------------------------------------------------------- | | [`await-async-event`](./docs/rules/await-async-event.md) | Enforce promises from async event methods are handled | 🔧 | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | -| [`await-async-query`](./docs/rules/await-async-query.md) | Enforce promises from async queries to be handled | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | +| [`await-async-queries`](./docs/rules/await-async-queries.md) | Enforce promises from async queries to be handled | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`await-async-utils`](./docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`consistent-data-testid`](./docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | | [`no-await-sync-events`](./docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | | ![dom-badge][] ![angular-badge][] ![react-badge][] | diff --git a/docs/rules/await-async-query.md b/docs/rules/await-async-queries.md similarity index 98% rename from docs/rules/await-async-query.md rename to docs/rules/await-async-queries.md index 70e83aaf..8819ed7c 100644 --- a/docs/rules/await-async-query.md +++ b/docs/rules/await-async-queries.md @@ -1,4 +1,4 @@ -# Enforce promises from async queries to be handled (`testing-library/await-async-query`) +# Enforce promises from async queries to be handled (`testing-library/await-async-queries`) Ensure that promises returned by async queries are handled properly. diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index de23cedc..0e6f9774 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -9,7 +9,7 @@ export = { 'error', { eventModule: 'userEvent' }, ], - 'testing-library/await-async-query': 'error', + 'testing-library/await-async-queries': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-events': 'error', 'testing-library/no-await-sync-queries': 'error', diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index 4360414a..e46afbe0 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -9,7 +9,7 @@ export = { 'error', { eventModule: 'userEvent' }, ], - 'testing-library/await-async-query': 'error', + 'testing-library/await-async-queries': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-events': 'error', 'testing-library/no-await-sync-queries': 'error', diff --git a/lib/configs/marko.ts b/lib/configs/marko.ts index 735a76a0..7a7abdd2 100644 --- a/lib/configs/marko.ts +++ b/lib/configs/marko.ts @@ -9,7 +9,7 @@ export = { 'error', { eventModule: ['fireEvent', 'userEvent'] }, ], - 'testing-library/await-async-query': 'error', + 'testing-library/await-async-queries': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-container': 'error', diff --git a/lib/configs/react.ts b/lib/configs/react.ts index 8d766247..4d483ad7 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -9,7 +9,7 @@ export = { 'error', { eventModule: 'userEvent' }, ], - 'testing-library/await-async-query': 'error', + 'testing-library/await-async-queries': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-events': 'error', 'testing-library/no-await-sync-queries': 'error', diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index 78ce9cf4..faa0f61c 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -9,7 +9,7 @@ export = { 'error', { eventModule: ['fireEvent', 'userEvent'] }, ], - 'testing-library/await-async-query': 'error', + 'testing-library/await-async-queries': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-container': 'error', diff --git a/lib/rules/await-async-query.ts b/lib/rules/await-async-queries.ts similarity index 98% rename from lib/rules/await-async-query.ts rename to lib/rules/await-async-queries.ts index 0af105a5..f719362d 100644 --- a/lib/rules/await-async-query.ts +++ b/lib/rules/await-async-queries.ts @@ -10,7 +10,7 @@ import { isPromiseHandled, } from '../node-utils'; -export const RULE_NAME = 'await-async-query'; +export const RULE_NAME = 'await-async-queries'; export type MessageIds = 'asyncQueryWrapper' | 'awaitAsyncQuery'; type Options = []; diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index ccaa271a..5304da46 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -13,7 +13,7 @@ Object { "eventModule": "userEvent", }, ], - "testing-library/await-async-query": "error", + "testing-library/await-async-queries": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-events": "error", "testing-library/no-await-sync-queries": "error", @@ -49,7 +49,7 @@ Object { "eventModule": "userEvent", }, ], - "testing-library/await-async-query": "error", + "testing-library/await-async-queries": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-events": "error", "testing-library/no-await-sync-queries": "error", @@ -80,7 +80,7 @@ Object { ], }, ], - "testing-library/await-async-query": "error", + "testing-library/await-async-queries": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-queries": "error", "testing-library/no-container": "error", @@ -116,7 +116,7 @@ Object { "eventModule": "userEvent", }, ], - "testing-library/await-async-query": "error", + "testing-library/await-async-queries": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-events": "error", "testing-library/no-await-sync-queries": "error", @@ -157,7 +157,7 @@ Object { ], }, ], - "testing-library/await-async-query": "error", + "testing-library/await-async-queries": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-queries": "error", "testing-library/no-container": "error", diff --git a/tests/lib/rules/await-async-query.test.ts b/tests/lib/rules/await-async-queries.test.ts similarity index 99% rename from tests/lib/rules/await-async-query.test.ts rename to tests/lib/rules/await-async-queries.test.ts index 8054035a..8cf3819c 100644 --- a/tests/lib/rules/await-async-query.test.ts +++ b/tests/lib/rules/await-async-queries.test.ts @@ -1,6 +1,6 @@ import { TSESLint } from '@typescript-eslint/utils'; -import rule, { RULE_NAME } from '../../../lib/rules/await-async-query'; +import rule, { RULE_NAME } from '../../../lib/rules/await-async-queries'; import { ASYNC_QUERIES_COMBINATIONS, ASYNC_QUERIES_VARIANTS, From d0a5d35c99be8fe87f7bbcc788a68e6745fa7999 Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Wed, 5 Oct 2022 08:28:16 -0700 Subject: [PATCH 13/20] docs: initial v6 migration guide (#668) --- README.md | 5 +++-- docs/migration-guides/v6.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 docs/migration-guides/v6.md diff --git a/README.md b/README.md index a0ca5920..5f75cf08 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,9 @@ $ yarn add --dev eslint-plugin-testing-library You can find detailed guides for migrating `eslint-plugin-testing-library` in the [migration guide docs](docs/migration-guides): -- [Migrate guide for v4](docs/migration-guides/v4.md) -- [Migrate guide for v5](docs/migration-guides/v5.md) +- [Migration guide for v4](docs/migration-guides/v4.md) +- [Migration guide for v5](docs/migration-guides/v5.md) +- [Migration guide for v6](docs/migration-guides/v6.md) ## Usage diff --git a/docs/migration-guides/v6.md b/docs/migration-guides/v6.md new file mode 100644 index 00000000..574020f4 --- /dev/null +++ b/docs/migration-guides/v6.md @@ -0,0 +1,28 @@ +# Guide: migrating to v6 + +If you are not on v5 yet, we recommend first following the [v5 migration guide](docs/migration-guides/v5.md). + +## Overview + +- `prefer-wait-for` was removed +- `await-fire-event` is now called `await-async-events` with support for an `eventModule` option with `userEvent` and/or `fireEvent` +- `await-async-events` is now enabled by default for `fireEvent` in Vue and Marko shared configs +- `await-async-events` is now enabled by default for `userEvent` in all shared configs +- `await-async-query` is now called `await-async-queries` +- `no-await-async-query` is now called `no-await-async-queries` +- `no-render-in-setup` is now called `no-render-in-lifecycle` +- `no-await-sync-events` is now enabled by default in React, Angular, and DOM shared configs +- `no-manual-cleanup` is now enabled by default in React and Vue shared configs +- `no-global-regexp-flag-in-query` is now enabled by default in all shared configs +- `no-node-access` is now enabled by default in DOM shared config +- `no-debugging-utils` now reports all debugging utility methods by default +- `no-debugging-utils` now defaults to `warn` instead of `error` in all shared configs + +## Steps to upgrade + +- Removing `testing-library/prefer-wait-for` if you were referencing it manually somewhere +- Renaming `testing-library/await-fire-event` to `testing-library/await-async-events` if you were referencing it manually somewhere +- Renaming `testing-library/await-async-query` to `testing-library/await-async-queries` if you were referencing it manually somewhere +- Renaming `testing-library/no-await-async-query` to `testing-library/no-await-async-queries` if you were referencing it manually somewhere +- Renaming `testing-library/no-render-in-setup` to `testing-library/no-render-in-lifecycle` if you were referencing it manually somewhere +- Being aware of new rules enabled or changed above in shared configs which can lead to newly reported errors From 7238f76c44d026798c8ad7c41700a717a065aef6 Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Wed, 5 Oct 2022 14:43:50 -0700 Subject: [PATCH 14/20] fix(await-async-event): pluralize to await-async-events (#670) --- README.md | 2 +- .../{await-async-event.md => await-async-events.md} | 8 ++++---- docs/rules/no-await-sync-events.md | 2 +- lib/configs/angular.ts | 2 +- lib/configs/dom.ts | 2 +- lib/configs/marko.ts | 2 +- lib/configs/react.ts | 2 +- lib/configs/vue.ts | 2 +- .../{await-async-event.ts => await-async-events.ts} | 2 +- tests/__snapshots__/index.test.ts.snap | 10 +++++----- ...-async-event.test.ts => await-async-events.test.ts} | 5 ++++- 11 files changed, 21 insertions(+), 18 deletions(-) rename docs/rules/{await-async-event.md => await-async-events.md} (95%) rename lib/rules/{await-async-event.ts => await-async-events.ts} (98%) rename tests/lib/rules/{await-async-event.test.ts => await-async-events.test.ts} (99%) diff --git a/README.md b/README.md index 5f75cf08..35b8d645 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ To enable this configuration use the `extends` property in your | Name | Description | 🔧 | Included in configurations | | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | --- | ---------------------------------------------------------------------------------- | -| [`await-async-event`](./docs/rules/await-async-event.md) | Enforce promises from async event methods are handled | 🔧 | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | +| [`await-async-events`](./docs/rules/await-async-events.md) | Enforce promises from async event methods are handled | 🔧 | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`await-async-queries`](./docs/rules/await-async-queries.md) | Enforce promises from async queries to be handled | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`await-async-utils`](./docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | | [`consistent-data-testid`](./docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | diff --git a/docs/rules/await-async-event.md b/docs/rules/await-async-events.md similarity index 95% rename from docs/rules/await-async-event.md rename to docs/rules/await-async-events.md index ef555409..d9b5f00f 100644 --- a/docs/rules/await-async-event.md +++ b/docs/rules/await-async-events.md @@ -1,4 +1,4 @@ -# Enforce promises from async event methods are handled (`testing-library/await-async-event`) +# Enforce promises from async event methods are handled (`testing-library/await-async-events`) Ensure that promises returned by `userEvent` (v14+) async methods or `fireEvent` (only Vue and Marko) async methods are handled properly. @@ -102,7 +102,7 @@ await Promise.all([ ```json { - "testing-library/await-async-event": [ + "testing-library/await-async-events": [ 2, { "eventModule": "userEvent" @@ -113,7 +113,7 @@ await Promise.all([ ```json { - "testing-library/await-async-event": [ + "testing-library/await-async-events": [ 2, { "eventModule": "fireEvent" @@ -124,7 +124,7 @@ await Promise.all([ ```json { - "testing-library/await-async-event": [ + "testing-library/await-async-events": [ 2, { "eventModule": ["fireEvent", "userEvent"] diff --git a/docs/rules/no-await-sync-events.md b/docs/rules/no-await-sync-events.md index 39cfdf45..1392006b 100644 --- a/docs/rules/no-await-sync-events.md +++ b/docs/rules/no-await-sync-events.md @@ -105,4 +105,4 @@ Example: ## Notes - Since `user-event` v14 all its methods are async, so you should disable reporting them by setting the `eventModules` to just `"fire-event"` so `user-event` methods are not reported. -- There is another rule `await-async-event`, which is for awaiting async events for `user-event` v14 or `fire-event` only in Vue Testing Library. Please do not confuse with this rule. +- There is another rule `await-async-events`, which is for awaiting async events for `user-event` v14 or `fire-event` only in Vue Testing Library. Please do not confuse with this rule. diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index 0e6f9774..77921fcb 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -5,7 +5,7 @@ export = { plugins: ['testing-library'], rules: { - 'testing-library/await-async-event': [ + 'testing-library/await-async-events': [ 'error', { eventModule: 'userEvent' }, ], diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index e46afbe0..d0317251 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -5,7 +5,7 @@ export = { plugins: ['testing-library'], rules: { - 'testing-library/await-async-event': [ + 'testing-library/await-async-events': [ 'error', { eventModule: 'userEvent' }, ], diff --git a/lib/configs/marko.ts b/lib/configs/marko.ts index 7a7abdd2..ca496765 100644 --- a/lib/configs/marko.ts +++ b/lib/configs/marko.ts @@ -5,7 +5,7 @@ export = { plugins: ['testing-library'], rules: { - 'testing-library/await-async-event': [ + 'testing-library/await-async-events': [ 'error', { eventModule: ['fireEvent', 'userEvent'] }, ], diff --git a/lib/configs/react.ts b/lib/configs/react.ts index 4d483ad7..a89ee93e 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -5,7 +5,7 @@ export = { plugins: ['testing-library'], rules: { - 'testing-library/await-async-event': [ + 'testing-library/await-async-events': [ 'error', { eventModule: 'userEvent' }, ], diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index faa0f61c..449f2936 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -5,7 +5,7 @@ export = { plugins: ['testing-library'], rules: { - 'testing-library/await-async-event': [ + 'testing-library/await-async-events': [ 'error', { eventModule: ['fireEvent', 'userEvent'] }, ], diff --git a/lib/rules/await-async-event.ts b/lib/rules/await-async-events.ts similarity index 98% rename from lib/rules/await-async-event.ts rename to lib/rules/await-async-events.ts index c5aebff6..57ce0694 100644 --- a/lib/rules/await-async-event.ts +++ b/lib/rules/await-async-events.ts @@ -11,7 +11,7 @@ import { } from '../node-utils'; import { EVENTS_SIMULATORS } from '../utils'; -export const RULE_NAME = 'await-async-event'; +export const RULE_NAME = 'await-async-events'; export type MessageIds = 'awaitAsyncEvent' | 'awaitAsyncEventWrapper'; const FIRE_EVENT_NAME = 'fireEvent'; const USER_EVENT_NAME = 'userEvent'; diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index 5304da46..0084e4a0 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -7,7 +7,7 @@ Object { "testing-library", ], "rules": Object { - "testing-library/await-async-event": Array [ + "testing-library/await-async-events": Array [ "error", Object { "eventModule": "userEvent", @@ -43,7 +43,7 @@ Object { "testing-library", ], "rules": Object { - "testing-library/await-async-event": Array [ + "testing-library/await-async-events": Array [ "error", Object { "eventModule": "userEvent", @@ -71,7 +71,7 @@ Object { "testing-library", ], "rules": Object { - "testing-library/await-async-event": Array [ + "testing-library/await-async-events": Array [ "error", Object { "eventModule": Array [ @@ -110,7 +110,7 @@ Object { "testing-library", ], "rules": Object { - "testing-library/await-async-event": Array [ + "testing-library/await-async-events": Array [ "error", Object { "eventModule": "userEvent", @@ -148,7 +148,7 @@ Object { "testing-library", ], "rules": Object { - "testing-library/await-async-event": Array [ + "testing-library/await-async-events": Array [ "error", Object { "eventModule": Array [ diff --git a/tests/lib/rules/await-async-event.test.ts b/tests/lib/rules/await-async-events.test.ts similarity index 99% rename from tests/lib/rules/await-async-event.test.ts rename to tests/lib/rules/await-async-events.test.ts index 5b245908..e0acd43c 100644 --- a/tests/lib/rules/await-async-event.test.ts +++ b/tests/lib/rules/await-async-events.test.ts @@ -1,4 +1,7 @@ -import rule, { Options, RULE_NAME } from '../../../lib/rules/await-async-event'; +import rule, { + Options, + RULE_NAME, +} from '../../../lib/rules/await-async-events'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); From 9d5554cb6bd44f3dbb9e370d67b684b54010c704 Mon Sep 17 00:00:00 2001 From: Spencer Miskoviak <5247455+skovy@users.noreply.github.com> Date: Fri, 14 Oct 2022 04:53:39 -0700 Subject: [PATCH 15/20] fix(await-async-events): improve fixer (#675) --- lib/node-utils/index.ts | 24 +++ lib/node-utils/is-node-of-type.ts | 3 + lib/rules/await-async-events.ts | 46 ++++- tests/lib/rules/await-async-events.test.ts | 187 +++++++++++++++++++-- 4 files changed, 240 insertions(+), 20 deletions(-) diff --git a/lib/node-utils/index.ts b/lib/node-utils/index.ts index 2e805ab0..497029d2 100644 --- a/lib/node-utils/index.ts +++ b/lib/node-utils/index.ts @@ -13,6 +13,8 @@ import { isBlockStatement, isCallExpression, isExpressionStatement, + isFunctionExpression, + isFunctionDeclaration, isImportDeclaration, isImportNamespaceSpecifier, isImportSpecifier, @@ -95,6 +97,28 @@ export function findClosestVariableDeclaratorNode( return findClosestVariableDeclaratorNode(node.parent); } +export function findClosestFunctionExpressionNode( + node: TSESTree.Node | undefined +): + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionExpression + | TSESTree.FunctionDeclaration + | null { + if (!node) { + return null; + } + + if ( + isArrowFunctionExpression(node) || + isFunctionExpression(node) || + isFunctionDeclaration(node) + ) { + return node; + } + + return findClosestFunctionExpressionNode(node.parent); +} + /** * TODO: remove this one in favor of {@link findClosestCallExpressionNode} */ diff --git a/lib/node-utils/is-node-of-type.ts b/lib/node-utils/is-node-of-type.ts index 8f74a9a5..afa2b3fc 100644 --- a/lib/node-utils/is-node-of-type.ts +++ b/lib/node-utils/is-node-of-type.ts @@ -59,3 +59,6 @@ export const isReturnStatement = ASTUtils.isNodeOfType( export const isFunctionExpression = ASTUtils.isNodeOfType( AST_NODE_TYPES.FunctionExpression ); +export const isFunctionDeclaration = ASTUtils.isNodeOfType( + AST_NODE_TYPES.FunctionDeclaration +); diff --git a/lib/rules/await-async-events.ts b/lib/rules/await-async-events.ts index 57ce0694..e1dbfcf5 100644 --- a/lib/rules/await-async-events.ts +++ b/lib/rules/await-async-events.ts @@ -3,6 +3,7 @@ import { ASTUtils, TSESLint, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { findClosestCallExpressionNode, + findClosestFunctionExpressionNode, getFunctionName, getInnermostReturningFunction, getVariableReferences, @@ -142,7 +143,28 @@ export default createTestingLibraryRule({ closestCallExpression, fix: (fixer) => { if (isMemberExpression(node.parent)) { - return fixer.insertTextBefore(node.parent, 'await '); + const functionExpression = + findClosestFunctionExpressionNode(node); + + if (functionExpression) { + const memberExpressionFixer = fixer.insertTextBefore( + node.parent, + 'await ' + ); + + if (functionExpression.async) { + return memberExpressionFixer; + } else { + // Mutate the actual node so if other nodes exist in this + // function expression body they don't also try to fix it. + functionExpression.async = true; + + return [ + memberExpressionFixer, + fixer.insertTextBefore(functionExpression, 'async '), + ]; + } + } } return null; @@ -175,7 +197,27 @@ export default createTestingLibraryRule({ closestCallExpression, messageId: 'awaitAsyncEventWrapper', fix: (fixer) => { - return fixer.insertTextBefore(node, 'await '); + const functionExpression = + findClosestFunctionExpressionNode(node); + + if (functionExpression) { + const nodeFixer = fixer.insertTextBefore(node, 'await '); + + if (functionExpression.async) { + return nodeFixer; + } else { + // Mutate the actual node so if other nodes exist in this + // function expression body they don't also try to fix it. + functionExpression.async = true; + + return [ + nodeFixer, + fixer.insertTextBefore(functionExpression, 'async '), + ]; + } + } + + return null; }, }); } diff --git a/tests/lib/rules/await-async-events.test.ts b/tests/lib/rules/await-async-events.test.ts index e0acd43c..fe133c0c 100644 --- a/tests/lib/rules/await-async-events.test.ts +++ b/tests/lib/rules/await-async-events.test.ts @@ -348,7 +348,7 @@ ruleTester.run(RULE_NAME, rule, { ({ code: ` import { fireEvent } from '${testingFramework}' - test('unhandled promise from event method is invalid', async () => { + test('unhandled promise from event method is invalid', () => { fireEvent.${eventMethod}(getByLabelText('username')) }) `, @@ -367,6 +367,64 @@ ruleTester.run(RULE_NAME, rule, { test('unhandled promise from event method is invalid', async () => { await fireEvent.${eventMethod}(getByLabelText('username')) }) + `, + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + + fireEvent.${eventMethod}(getByLabelText('username')) + `, + errors: [ + { + line: 4, + column: 7, + endColumn: 17 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent } from '${testingFramework}' + + fireEvent.${eventMethod}(getByLabelText('username')) + `, + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + + function run() { + fireEvent.${eventMethod}(getByLabelText('username')) + } + + test('should handle external function', run) + `, + errors: [ + { + line: 5, + column: 9, + endColumn: 19 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent } from '${testingFramework}' + + async function run() { + await fireEvent.${eventMethod}(getByLabelText('username')) + } + + test('should handle external function', run) `, } as const) ), @@ -429,7 +487,7 @@ ruleTester.run(RULE_NAME, rule, { ({ code: ` import { fireEvent } from '${testingFramework}' - test('several unhandled promises from event methods is invalid', async () => { + test('several unhandled promises from event methods is invalid', async function() { fireEvent.${eventMethod}(getByLabelText('username')) fireEvent.${eventMethod}(getByLabelText('username')) }) @@ -451,7 +509,7 @@ ruleTester.run(RULE_NAME, rule, { options: [{ eventModule: 'fireEvent' }], output: ` import { fireEvent } from '${testingFramework}' - test('several unhandled promises from event methods is invalid', async () => { + test('several unhandled promises from event methods is invalid', async function() { await fireEvent.${eventMethod}(getByLabelText('username')) await fireEvent.${eventMethod}(getByLabelText('username')) }) @@ -466,7 +524,7 @@ ruleTester.run(RULE_NAME, rule, { }, code: ` import { fireEvent } from '${testingFramework}' - test('unhandled promise from event method with aggressive reporting opted-out is invalid', async () => { + test('unhandled promise from event method with aggressive reporting opted-out is invalid', function() { fireEvent.${eventMethod}(getByLabelText('username')) }) `, @@ -481,7 +539,7 @@ ruleTester.run(RULE_NAME, rule, { options: [{ eventModule: 'fireEvent' }], output: ` import { fireEvent } from '${testingFramework}' - test('unhandled promise from event method with aggressive reporting opted-out is invalid', async () => { + test('unhandled promise from event method with aggressive reporting opted-out is invalid', async function() { await fireEvent.${eventMethod}(getByLabelText('username')) }) `, @@ -514,7 +572,7 @@ ruleTester.run(RULE_NAME, rule, { import { fireEvent } from 'test-utils' test( 'unhandled promise from event method imported from custom module with aggressive reporting opted-out is invalid', - () => { + async () => { await fireEvent.${eventMethod}(getByLabelText('username')) }) `, @@ -547,7 +605,7 @@ ruleTester.run(RULE_NAME, rule, { import { fireEvent } from '${testingFramework}' test( 'unhandled promise from event method imported from default module with aggressive reporting opted-out is invalid', - () => { + async () => { await fireEvent.${eventMethod}(getByLabelText('username')) }) `, @@ -578,7 +636,7 @@ ruleTester.run(RULE_NAME, rule, { import { fireEvent } from '${testingFramework}' test( 'unhandled promise from event method kept in a var is invalid', - () => { + async () => { const promise = await fireEvent.${eventMethod}(getByLabelText('username')) }) `, @@ -609,7 +667,7 @@ ruleTester.run(RULE_NAME, rule, { options: [{ eventModule: 'fireEvent' }], output: ` import { fireEvent } from '${testingFramework}' - test('unhandled promise returned from function wrapping event method is invalid', () => { + test('unhandled promise returned from function wrapping event method is invalid', async () => { function triggerEvent() { doSomething() return fireEvent.${eventMethod}(getByLabelText('username')) @@ -617,6 +675,40 @@ ruleTester.run(RULE_NAME, rule, { await triggerEvent() }) + `, + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + + function triggerEvent() { + doSomething() + return fireEvent.${eventMethod}(getByLabelText('username')) + } + + triggerEvent() + `, + errors: [ + { + line: 9, + column: 7, + messageId: 'awaitAsyncEventWrapper', + data: { name: 'triggerEvent' }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + output: ` + import { fireEvent } from '${testingFramework}' + + function triggerEvent() { + doSomething() + return fireEvent.${eventMethod}(getByLabelText('username')) + } + + triggerEvent() `, } as const) ), @@ -627,7 +719,7 @@ ruleTester.run(RULE_NAME, rule, { ({ code: ` import userEvent from '${testingFramework}' - test('unhandled promise from event method is invalid', async () => { + test('unhandled promise from event method is invalid', () => { userEvent.${eventMethod}(getByLabelText('username')) }) `, @@ -646,6 +738,31 @@ ruleTester.run(RULE_NAME, rule, { test('unhandled promise from event method is invalid', async () => { await userEvent.${eventMethod}(getByLabelText('username')) }) + `, + } as const) + ), + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import userEvent from '${testingFramework}' + + userEvent.${eventMethod}(getByLabelText('username')) + `, + errors: [ + { + line: 4, + column: 7, + endColumn: 17 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'userEvent' }], + output: ` + import userEvent from '${testingFramework}' + + userEvent.${eventMethod}(getByLabelText('username')) `, } as const) ), @@ -654,7 +771,7 @@ ruleTester.run(RULE_NAME, rule, { ({ code: ` import testingLibraryUserEvent from '${testingFramework}' - test('unhandled promise imported from alternate name event method is invalid', async () => { + test('unhandled promise imported from alternate name event method is invalid', () => { testingLibraryUserEvent.${eventMethod}(getByLabelText('username')) }) `, @@ -681,7 +798,7 @@ ruleTester.run(RULE_NAME, rule, { ({ code: ` import userEvent from '${testingFramework}' - test('several unhandled promises from event methods is invalid', async () => { + test('several unhandled promises from event methods is invalid', () => { userEvent.${eventMethod}(getByLabelText('username')) userEvent.${eventMethod}(getByLabelText('username')) }) @@ -734,7 +851,7 @@ ruleTester.run(RULE_NAME, rule, { import userEvent from '${testingFramework}' test( 'unhandled promise from event method kept in a var is invalid', - () => { + async () => { const promise = await userEvent.${eventMethod}(getByLabelText('username')) }) `, @@ -745,7 +862,7 @@ ruleTester.run(RULE_NAME, rule, { ({ code: ` import userEvent from '${testingFramework}' - test('unhandled promise returned from function wrapping event method is invalid', () => { + test('unhandled promise returned from function wrapping event method is invalid', function() { function triggerEvent() { doSomething() return userEvent.${eventMethod}(getByLabelText('username')) @@ -765,7 +882,7 @@ ruleTester.run(RULE_NAME, rule, { options: [{ eventModule: 'userEvent' }], output: ` import userEvent from '${testingFramework}' - test('unhandled promise returned from function wrapping event method is invalid', () => { + test('unhandled promise returned from function wrapping event method is invalid', async function() { function triggerEvent() { doSomething() return userEvent.${eventMethod}(getByLabelText('username')) @@ -773,6 +890,40 @@ ruleTester.run(RULE_NAME, rule, { await triggerEvent() }) + `, + } as const) + ), + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import userEvent from '${testingFramework}' + + function triggerEvent() { + doSomething() + return userEvent.${eventMethod}(getByLabelText('username')) + } + + triggerEvent() + `, + errors: [ + { + line: 9, + column: 7, + messageId: 'awaitAsyncEventWrapper', + data: { name: 'triggerEvent' }, + }, + ], + options: [{ eventModule: 'userEvent' }], + output: ` + import userEvent from '${testingFramework}' + + function triggerEvent() { + doSomething() + return userEvent.${eventMethod}(getByLabelText('username')) + } + + triggerEvent() `, } as const) ), @@ -781,7 +932,7 @@ ruleTester.run(RULE_NAME, rule, { code: ` import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}' import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}' - test('unhandled promises from multiple event modules', async () => { + test('unhandled promises from multiple event modules', () => { fireEvent.click(getByLabelText('username')) userEvent.click(getByLabelText('username')) }) @@ -814,7 +965,7 @@ ruleTester.run(RULE_NAME, rule, { code: ` import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}' import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}' - test('unhandled promise from userEvent relying on default options', async () => { + test('unhandled promise from userEvent relying on default options', async function() { fireEvent.click(getByLabelText('username')) userEvent.click(getByLabelText('username')) }) @@ -830,7 +981,7 @@ ruleTester.run(RULE_NAME, rule, { output: ` import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}' import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}' - test('unhandled promise from userEvent relying on default options', async () => { + test('unhandled promise from userEvent relying on default options', async function() { fireEvent.click(getByLabelText('username')) await userEvent.click(getByLabelText('username')) }) From e838a48fa3bf7c39439d9ec34145f42083a284ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Tue, 20 Dec 2022 11:34:50 +0000 Subject: [PATCH 16/20] docs: regenerate after merge --- README.md | 58 ++++++++++---------- docs/rules/await-async-events.md | 6 ++ docs/rules/no-await-sync-events.md | 2 + docs/rules/no-debugging-utils.md | 2 +- docs/rules/no-global-regexp-flag-in-query.md | 2 + docs/rules/no-manual-cleanup.md | 2 + docs/rules/no-node-access.md | 2 +- 7 files changed, 43 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index a768eb91..b6360f25 100644 --- a/README.md +++ b/README.md @@ -202,37 +202,37 @@ To enable this configuration use the `extends` property in your 💼 Configurations enabled in.\ +⚠️ Configurations set to warn in.\ 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). -| Name                            | Description | 💼 | 🔧 | -| :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | :-- | -| [await-async-query](docs/rules/await-async-query.md) | Enforce promises from async queries to be handled | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [await-async-utils](docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [await-fire-event](docs/rules/await-fire-event.md) | Enforce promises from `fireEvent` methods to be handled | ![badge-marko][] ![badge-vue][] | | -| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | -| [no-await-sync-events](docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | | | -| [no-await-sync-query](docs/rules/no-await-sync-query.md) | Disallow unnecessary `await` for sync queries | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [no-debugging-utils](docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | 🔧 | -| [no-global-regexp-flag-in-query](docs/rules/no-global-regexp-flag-in-query.md) | Disallow the use of the global RegExp flag (/g) in queries | | 🔧 | -| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | | -| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [no-render-in-setup](docs/rules/no-render-in-setup.md) | Disallow the use of `render` in testing frameworks setup functions | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [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-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [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-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-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-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-vue][] | 🔧 | -| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Ensure appropriate `get*`/`query*` queries are used with their respective matchers | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [prefer-query-by-disappearance](docs/rules/prefer-query-by-disappearance.md) | Suggest using `queryBy*` queries when waiting for disappearance | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using `screen` while querying | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [prefer-user-event](docs/rules/prefer-user-event.md) | Suggest using `userEvent` over `fireEvent` for simulating user interactions | | | -| [prefer-wait-for](docs/rules/prefer-wait-for.md) | Use `waitFor` instead of deprecated wait methods | | 🔧 | -| [render-result-naming-convention](docs/rules/render-result-naming-convention.md) | Enforce a valid naming for return value from `render` | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | +| Name                            | Description | 💼 | ⚠️ | 🔧 | +| :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | :------------------------------------------------------------------ | :-- | +| [await-async-events](docs/rules/await-async-events.md) | Enforce promises from async event methods are handled | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | 🔧 | +| [await-async-queries](docs/rules/await-async-queries.md) | Enforce promises from async queries to be handled | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [await-async-utils](docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | | +| [no-await-sync-events](docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | ![badge-angular][] ![badge-dom][] ![badge-react][] | | | +| [no-await-sync-queries](docs/rules/no-await-sync-queries.md) | Disallow unnecessary `await` for sync queries | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [no-debugging-utils](docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | +| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | 🔧 | +| [no-global-regexp-flag-in-query](docs/rules/no-global-regexp-flag-in-query.md) | Disallow the use of the global RegExp flag (/g) in queries | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | 🔧 | +| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | ![badge-react][] ![badge-vue][] | | | +| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [no-render-in-lifecycle](docs/rules/no-render-in-lifecycle.md) | Disallow the use of `render` in testing frameworks setup functions | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [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-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [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-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-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-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-vue][] | | 🔧 | +| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Ensure appropriate `get*`/`query*` queries are used with their respective matchers | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [prefer-query-by-disappearance](docs/rules/prefer-query-by-disappearance.md) | Suggest using `queryBy*` queries when waiting for disappearance | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using `screen` while querying | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [prefer-user-event](docs/rules/prefer-user-event.md) | Suggest using `userEvent` over `fireEvent` for simulating user interactions | | | | +| [render-result-naming-convention](docs/rules/render-result-naming-convention.md) | Enforce a valid naming for return value from `render` | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | diff --git a/docs/rules/await-async-events.md b/docs/rules/await-async-events.md index d9b5f00f..8f21ce33 100644 --- a/docs/rules/await-async-events.md +++ b/docs/rules/await-async-events.md @@ -1,5 +1,11 @@ # Enforce promises from async event methods are handled (`testing-library/await-async-events`) +💼 This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `vue`. + +🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + Ensure that promises returned by `userEvent` (v14+) async methods or `fireEvent` (only Vue and Marko) async methods are handled properly. ## Rule Details diff --git a/docs/rules/no-await-sync-events.md b/docs/rules/no-await-sync-events.md index 1fa10ea4..1a0c7369 100644 --- a/docs/rules/no-await-sync-events.md +++ b/docs/rules/no-await-sync-events.md @@ -1,5 +1,7 @@ # Disallow unnecessary `await` for sync events (`testing-library/no-await-sync-events`) +💼 This rule is enabled in the following configs: `angular`, `dom`, `react`. + Ensure that sync simulated events are not awaited unnecessarily. diff --git a/docs/rules/no-debugging-utils.md b/docs/rules/no-debugging-utils.md index 45b12097..6b62702e 100644 --- a/docs/rules/no-debugging-utils.md +++ b/docs/rules/no-debugging-utils.md @@ -1,6 +1,6 @@ # Disallow the use of debugging utilities like `debug` (`testing-library/no-debugging-utils`) -💼 This rule is enabled in the following configs: `angular`, `marko`, `react`, `vue`. +⚠️ This rule _warns_ in the following configs: `angular`, `marko`, `react`, `vue`. diff --git a/docs/rules/no-global-regexp-flag-in-query.md b/docs/rules/no-global-regexp-flag-in-query.md index 41469460..75e5a18a 100644 --- a/docs/rules/no-global-regexp-flag-in-query.md +++ b/docs/rules/no-global-regexp-flag-in-query.md @@ -1,5 +1,7 @@ # Disallow the use of the global RegExp flag (/g) in queries (`testing-library/no-global-regexp-flag-in-query`) +💼 This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `vue`. + 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-manual-cleanup.md b/docs/rules/no-manual-cleanup.md index ebd8d427..7c0b0115 100644 --- a/docs/rules/no-manual-cleanup.md +++ b/docs/rules/no-manual-cleanup.md @@ -1,5 +1,7 @@ # Disallow the use of `cleanup` (`testing-library/no-manual-cleanup`) +💼 This rule is enabled in the following configs: `react`, `vue`. + `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. diff --git a/docs/rules/no-node-access.md b/docs/rules/no-node-access.md index 4f90c681..91c0727a 100644 --- a/docs/rules/no-node-access.md +++ b/docs/rules/no-node-access.md @@ -1,6 +1,6 @@ # Disallow direct Node access (`testing-library/no-node-access`) -💼 This rule is enabled in the following configs: `angular`, `marko`, `react`, `vue`. +💼 This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `vue`. From 9316c3a9bf6185e63b029d45eb9d8d3d4949b96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Tue, 20 Dec 2022 12:23:02 +0000 Subject: [PATCH 17/20] refactor: fix lint error --- lib/rules/await-async-events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/await-async-events.ts b/lib/rules/await-async-events.ts index e1dbfcf5..c8797062 100644 --- a/lib/rules/await-async-events.ts +++ b/lib/rules/await-async-events.ts @@ -128,7 +128,7 @@ export default createTestingLibraryRule({ true ); - if (!closestCallExpression || !closestCallExpression.parent) { + if (!closestCallExpression?.parent) { return; } From c0b01b04210e11ebebed80863c637d09e24e051b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n?= Date: Thu, 5 Jan 2023 16:46:07 +0100 Subject: [PATCH 18/20] refactor: remove remaining support for old async utils (#706) --- README.md | 2 +- docs/migration-guides/v6.md | 2 + docs/rules/await-async-utils.md | 5 +- docs/rules/no-wait-for-empty-callback.md | 45 ---- docs/rules/no-wait-for-snapshot.md | 8 - docs/rules/prefer-find-by.md | 2 +- lib/configs/angular.ts | 1 - lib/configs/dom.ts | 1 - lib/configs/marko.ts | 1 - lib/configs/react.ts | 1 - lib/configs/vue.ts | 1 - lib/rules/no-wait-for-empty-callback.ts | 100 -------- lib/rules/prefer-find-by.ts | 18 +- lib/utils/index.ts | 8 +- tests/index.test.ts | 2 +- .../rules/no-wait-for-empty-callback.test.ts | 242 ------------------ tests/lib/rules/prefer-find-by.test.ts | 168 ++++++------ 17 files changed, 83 insertions(+), 524 deletions(-) delete mode 100644 docs/rules/no-wait-for-empty-callback.md delete mode 100644 lib/rules/no-wait-for-empty-callback.ts delete mode 100644 tests/lib/rules/no-wait-for-empty-callback.test.ts diff --git a/README.md b/README.md index b6360f25..2b834b9b 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ +

eslint-plugin-testing-library

ESLint plugin to follow best practices and anticipate common mistakes when writing tests with Testing Library

@@ -222,7 +223,6 @@ To enable this configuration use the `extends` property in your | [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | | [no-render-in-lifecycle](docs/rules/no-render-in-lifecycle.md) | Disallow the use of `render` in testing frameworks setup functions | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | | [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-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | | [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-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-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-vue][] | | | diff --git a/docs/migration-guides/v6.md b/docs/migration-guides/v6.md index 574020f4..05557e2b 100644 --- a/docs/migration-guides/v6.md +++ b/docs/migration-guides/v6.md @@ -5,6 +5,7 @@ If you are not on v5 yet, we recommend first following the [v5 migration guide]( ## Overview - `prefer-wait-for` was removed +- `no-wait-for-empty-callback` was removed - `await-fire-event` is now called `await-async-events` with support for an `eventModule` option with `userEvent` and/or `fireEvent` - `await-async-events` is now enabled by default for `fireEvent` in Vue and Marko shared configs - `await-async-events` is now enabled by default for `userEvent` in all shared configs @@ -21,6 +22,7 @@ If you are not on v5 yet, we recommend first following the [v5 migration guide]( ## Steps to upgrade - Removing `testing-library/prefer-wait-for` if you were referencing it manually somewhere +- Removing `testing-library/no-wait-for-empty-callback` if you were referencing it manually somewhere - Renaming `testing-library/await-fire-event` to `testing-library/await-async-events` if you were referencing it manually somewhere - Renaming `testing-library/await-async-query` to `testing-library/await-async-queries` if you were referencing it manually somewhere - Renaming `testing-library/no-await-async-query` to `testing-library/no-await-async-queries` if you were referencing it manually somewhere diff --git a/docs/rules/await-async-utils.md b/docs/rules/await-async-utils.md index 86119f64..b5433d83 100644 --- a/docs/rules/await-async-utils.md +++ b/docs/rules/await-async-utils.md @@ -10,11 +10,8 @@ Ensure that promises returned by async utils are handled properly. Testing library provides several utilities for dealing with asynchronous code. These are useful to wait for an element until certain criteria or situation happens. The available async utils are: -- `waitFor` _(introduced since dom-testing-library v7)_ +- `waitFor` - `waitForElementToBeRemoved` -- `wait` _(**deprecated** since dom-testing-library v7)_ -- `waitForElement` _(**deprecated** since dom-testing-library v7)_ -- `waitForDomChange` _(**deprecated** since dom-testing-library v7)_ This rule aims to prevent users from forgetting to handle the returned promise from async utils, which could lead to diff --git a/docs/rules/no-wait-for-empty-callback.md b/docs/rules/no-wait-for-empty-callback.md deleted file mode 100644 index b92fc3a2..00000000 --- a/docs/rules/no-wait-for-empty-callback.md +++ /dev/null @@ -1,45 +0,0 @@ -# Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` (`testing-library/no-wait-for-empty-callback`) - -💼 This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `vue`. - - - -## Rule Details - -This rule aims to ensure the correct usage of `waitFor` and `waitForElementToBeRemoved`, in the way that they're intended to be used. -If an empty callback is passed, these methods will just wait next tick of the event loop before proceeding, and that's not consistent with the philosophy of the library. -**Instead, insert an assertion in that callback function.** - -Examples of **incorrect** code for this rule: - -```js -const foo = async () => { - await waitFor(() => {}); - await waitFor(function () {}); - await waitFor(noop); - - await waitForElementToBeRemoved(() => {}); - await waitForElementToBeRemoved(function () {}); - await waitForElementToBeRemoved(noop); -}; -``` - -Examples of **correct** code for this rule: - -```js -const foo = async () => { - await waitFor(() => { - screen.getByText(/submit/i); - }); - - const submit = screen.getByText(/submit/i); - await waitForElementToBeRemoved(() => submit); - // or - await waitForElementToBeRemoved(submit); -}; -``` - -## Further Reading - -- [dom-testing-library v7 release](https://github.com/testing-library/dom-testing-library/releases/tag/v7.0.0) -- [inspiration for this rule](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#passing-an-empty-callback-to-waitfor) diff --git a/docs/rules/no-wait-for-snapshot.md b/docs/rules/no-wait-for-snapshot.md index 470d07bc..e29fb7fa 100644 --- a/docs/rules/no-wait-for-snapshot.md +++ b/docs/rules/no-wait-for-snapshot.md @@ -29,14 +29,6 @@ const bar = async () => { await waitFor(() => expect(container).toMatchInlineSnapshot()); // ... }; - -const baz = async () => { - // ... - await wait(() => { - expect(container).toMatchSnapshot(); - }); - // ... -}; ``` Examples of **correct** code for this rule: diff --git a/docs/rules/prefer-find-by.md b/docs/rules/prefer-find-by.md index 257af7a2..1cc5b217 100644 --- a/docs/rules/prefer-find-by.md +++ b/docs/rules/prefer-find-by.md @@ -10,7 +10,7 @@ ## Rule details -This rule aims to use `findBy*` or `findAllBy*` queries to wait for elements, rather than using `waitFor`, or the deprecated methods `waitForElement` and `wait`. +This rule aims to use `findBy*` or `findAllBy*` queries to wait for elements, rather than using `waitFor`. This rule analyzes those cases where `waitFor` is used with just one query method, in the form of an arrow function with only one statement (that is, without a block of statements). Given the callback could be more complex, this rule does not consider function callbacks or arrow functions with blocks of code. Examples of **incorrect** code for this rule diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index 77921fcb..8defb66b 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -20,7 +20,6 @@ export = { 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', 'testing-library/no-render-in-lifecycle': 'error', - 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', 'testing-library/no-wait-for-side-effects': 'error', 'testing-library/no-wait-for-snapshot': 'error', diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index d0317251..3e5830ac 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -16,7 +16,6 @@ export = { 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', - 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', 'testing-library/no-wait-for-side-effects': 'error', 'testing-library/no-wait-for-snapshot': 'error', diff --git a/lib/configs/marko.ts b/lib/configs/marko.ts index ca496765..066c3498 100644 --- a/lib/configs/marko.ts +++ b/lib/configs/marko.ts @@ -20,7 +20,6 @@ export = { 'testing-library/no-promise-in-fire-event': 'error', 'testing-library/no-render-in-lifecycle': 'error', 'testing-library/no-unnecessary-act': 'error', - 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', 'testing-library/no-wait-for-side-effects': 'error', 'testing-library/no-wait-for-snapshot': 'error', diff --git a/lib/configs/react.ts b/lib/configs/react.ts index a89ee93e..538b4fc9 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -22,7 +22,6 @@ export = { 'testing-library/no-promise-in-fire-event': 'error', 'testing-library/no-render-in-lifecycle': 'error', 'testing-library/no-unnecessary-act': 'error', - 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', 'testing-library/no-wait-for-side-effects': 'error', 'testing-library/no-wait-for-snapshot': 'error', diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index 449f2936..fdf8bfb7 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -20,7 +20,6 @@ export = { 'testing-library/no-node-access': 'error', 'testing-library/no-promise-in-fire-event': 'error', 'testing-library/no-render-in-lifecycle': 'error', - 'testing-library/no-wait-for-empty-callback': 'error', 'testing-library/no-wait-for-multiple-assertions': 'error', 'testing-library/no-wait-for-side-effects': 'error', 'testing-library/no-wait-for-snapshot': 'error', diff --git a/lib/rules/no-wait-for-empty-callback.ts b/lib/rules/no-wait-for-empty-callback.ts deleted file mode 100644 index 4ef3f851..00000000 --- a/lib/rules/no-wait-for-empty-callback.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; - -import { createTestingLibraryRule } from '../create-testing-library-rule'; -import { - getPropertyIdentifierNode, - isCallExpression, - isEmptyFunction, -} from '../node-utils'; - -export const RULE_NAME = 'no-wait-for-empty-callback'; -export type MessageIds = 'noWaitForEmptyCallback'; -type Options = []; - -export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: - 'Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved`', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - noWaitForEmptyCallback: - 'Avoid passing empty callback to `{{ methodName }}`. Insert an assertion instead.', - }, - schema: [], - }, - defaultOptions: [], - - // trimmed down implementation of https://github.com/eslint/eslint/blob/master/lib/rules/no-empty-function.js - create(context, _, helpers) { - function isValidWaitFor(node: TSESTree.Node): boolean { - const parentCallExpression = node.parent as TSESTree.CallExpression; - const parentIdentifier = getPropertyIdentifierNode(parentCallExpression); - - if (!parentIdentifier) { - return false; - } - - return helpers.isAsyncUtil(parentIdentifier, [ - 'waitFor', - 'waitForElementToBeRemoved', - ]); - } - - function reportIfEmpty( - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression - ) { - if (!isValidWaitFor(node)) { - return; - } - - if ( - isEmptyFunction(node) && - isCallExpression(node.parent) && - ASTUtils.isIdentifier(node.parent.callee) - ) { - context.report({ - node, - loc: node.body.loc.start, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: node.parent.callee.name, - }, - }); - } - } - - function reportNoop(node: TSESTree.Identifier) { - if (!isValidWaitFor(node)) { - return; - } - - context.report({ - node, - loc: node.loc.start, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: - isCallExpression(node.parent) && - ASTUtils.isIdentifier(node.parent.callee) && - node.parent.callee.name, - }, - }); - } - - return { - 'CallExpression > ArrowFunctionExpression': reportIfEmpty, - 'CallExpression > FunctionExpression': reportIfEmpty, - 'CallExpression > Identifier[name="noop"]': reportNoop, - }; - }, -}); diff --git a/lib/rules/prefer-find-by.ts b/lib/rules/prefer-find-by.ts index 6c096cdb..f9d951e0 100644 --- a/lib/rules/prefer-find-by.ts +++ b/lib/rules/prefer-find-by.ts @@ -14,8 +14,6 @@ export const RULE_NAME = 'prefer-find-by'; export type MessageIds = 'preferFindBy'; type Options = []; -export const WAIT_METHODS = ['waitFor', 'waitForElement', 'wait'] as const; - export function getFindByQueryVariant( queryMethod: string ): 'findAllBy' | 'findBy' { @@ -63,7 +61,7 @@ export default createTestingLibraryRule({ }, messages: { preferFindBy: - 'Prefer `{{queryVariant}}{{queryMethod}}` query over using `{{waitForMethodName}}` + `{{prevQuery}}`', + 'Prefer `{{queryVariant}}{{queryMethod}}` query over using `waitFor` + `{{prevQuery}}`', }, fixable: 'code', schema: [], @@ -75,12 +73,11 @@ export default createTestingLibraryRule({ /** * Reports the invalid usage of wait* plus getBy/QueryBy methods and automatically fixes the scenario - * @param node - The CallExpresion node that contains the wait* method + * @param node - The CallExpression node that contains the waitFor method * @param replacementParams - Object with info for error message and autofix: * @param replacementParams.queryVariant - The variant method used to query: findBy/findAllBy. * @param replacementParams.prevQuery - The query originally used inside `waitFor` * @param replacementParams.queryMethod - Suffix string to build the query method (the query-part that comes after the "By"): LabelText, Placeholder, Text, Role, Title, etc. - * @param replacementParams.waitForMethodName - wait for method used: waitFor/wait/waitForElement * @param replacementParams.fix - Function that applies the fix to correct the code */ function reportInvalidUsage( @@ -89,12 +86,10 @@ export default createTestingLibraryRule({ queryVariant: 'findAllBy' | 'findBy'; queryMethod: string; prevQuery: string; - waitForMethodName: string; fix: TSESLint.ReportFixFunction; } ) { - const { queryMethod, queryVariant, prevQuery, waitForMethodName, fix } = - replacementParams; + const { queryMethod, queryVariant, prevQuery, fix } = replacementParams; context.report({ node, messageId: 'preferFindBy', @@ -102,7 +97,6 @@ export default createTestingLibraryRule({ queryVariant, queryMethod, prevQuery, - waitForMethodName, }, fix, }); @@ -336,7 +330,7 @@ export default createTestingLibraryRule({ 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { if ( !ASTUtils.isIdentifier(node.callee) || - !helpers.isAsyncUtil(node.callee, WAIT_METHODS) + !helpers.isAsyncUtil(node.callee, ['waitFor']) ) { return; } @@ -350,8 +344,6 @@ export default createTestingLibraryRule({ return; } - const waitForMethodName = node.callee.name; - // ensure here it's one of the sync methods that we are calling if (isScreenSyncQuery(argument)) { const caller = getCaller(argument); @@ -386,7 +378,6 @@ export default createTestingLibraryRule({ queryMethod, queryVariant, prevQuery: fullQueryMethod, - waitForMethodName, fix(fixer) { const property = ( (argument.body as TSESTree.CallExpression) @@ -423,7 +414,6 @@ export default createTestingLibraryRule({ queryMethod, queryVariant, prevQuery: fullQueryMethod, - waitForMethodName, fix(fixer) { // we know from above callee is an Identifier if ( diff --git a/lib/utils/index.ts b/lib/utils/index.ts index adaef168..7ed659f7 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -59,13 +59,7 @@ const ALL_QUERIES_COMBINATIONS = [ ...ASYNC_QUERIES_COMBINATIONS, ]; -const ASYNC_UTILS = [ - 'waitFor', - 'waitForElementToBeRemoved', - 'wait', - 'waitForElement', - 'waitForDomChange', -] as const; +const ASYNC_UTILS = ['waitFor', 'waitForElementToBeRemoved'] as const; const DEBUG_UTILS = [ 'debug', diff --git a/tests/index.test.ts b/tests/index.test.ts index 78e09cc7..5c8f68d4 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -3,7 +3,7 @@ import { resolve } from 'path'; import plugin from '../lib'; -const numberOfRules = 26; +const numberOfRules = 25; const ruleNames = Object.keys(plugin.rules); // eslint-disable-next-line jest/expect-expect diff --git a/tests/lib/rules/no-wait-for-empty-callback.test.ts b/tests/lib/rules/no-wait-for-empty-callback.test.ts deleted file mode 100644 index ed65bad6..00000000 --- a/tests/lib/rules/no-wait-for-empty-callback.test.ts +++ /dev/null @@ -1,242 +0,0 @@ -import rule, { RULE_NAME } from '../../../lib/rules/no-wait-for-empty-callback'; -import { createRuleTester } from '../test-utils'; - -const ruleTester = createRuleTester(); - -const ALL_WAIT_METHODS = ['waitFor', 'waitForElementToBeRemoved']; -const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', -]; - -ruleTester.run(RULE_NAME, rule, { - valid: [ - ...ALL_WAIT_METHODS.map((m) => ({ - code: `${m}(() => { - screen.getByText(/submit/i) - })`, - })), - ...ALL_WAIT_METHODS.map((m) => ({ - code: `${m}(function() { - screen.getByText(/submit/i) - })`, - })), - { - code: `waitForElementToBeRemoved(someNode)`, - }, - { - code: `waitForElementToBeRemoved(() => someNode)`, - }, - { - code: `waitSomethingElse(() => {})`, - }, - { - code: `wait(() => {})`, - }, - { - code: `wait(noop)`, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` - import { waitFor } from 'somewhere-else' - waitFor(() => {}) - `, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` - import { waitFor as renamedWaitFor } from '${testingFramework}' - import { waitFor } from 'somewhere-else' - waitFor(() => {}) - `, - })), - ], - - invalid: [ - ...ALL_WAIT_METHODS.map( - (m) => - ({ - code: `${m}(() => {})`, - errors: [ - { - line: 1, - column: 8 + m.length, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: m, - }, - }, - ], - } as const) - ), - ...ALL_WAIT_METHODS.map( - (m) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` - import { ${m} } from 'test-utils'; - ${m}(() => {}); - `, - errors: [ - { - line: 3, - column: 16 + m.length, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: m, - }, - }, - ], - } as const) - ), - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => - ALL_WAIT_METHODS.map( - (m) => - ({ - code: ` - import { ${m} } from '${testingFramework}'; - ${m}(() => {}); - `, - errors: [ - { - line: 3, - column: 16 + m.length, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: m, - }, - }, - ], - } as const) - ) - ), - ...ALL_WAIT_METHODS.map( - (m) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` - import { ${m} as renamedAsyncUtil } from 'test-utils'; - renamedAsyncUtil(() => {}); - `, - errors: [ - { - line: 3, - column: 32, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: 'renamedAsyncUtil', - }, - }, - ], - } as const) - ), - ...ALL_WAIT_METHODS.map( - (m) => - ({ - code: `${m}((a, b) => {})`, - errors: [ - { - line: 1, - column: 12 + m.length, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: m, - }, - }, - ], - } as const) - ), - ...ALL_WAIT_METHODS.map( - (m) => - ({ - code: `${m}(() => { /* I'm empty anyway */ })`, - errors: [ - { - line: 1, - column: 8 + m.length, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: m, - }, - }, - ], - } as const) - ), - - ...ALL_WAIT_METHODS.map( - (m) => - ({ - code: `${m}(function() { - - })`, - errors: [ - { - line: 1, - column: 13 + m.length, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: m, - }, - }, - ], - } as const) - ), - ...ALL_WAIT_METHODS.map( - (m) => - ({ - code: `${m}(function(a) { - - })`, - errors: [ - { - line: 1, - column: 14 + m.length, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: m, - }, - }, - ], - } as const) - ), - ...ALL_WAIT_METHODS.map( - (m) => - ({ - code: `${m}(function() { - // another empty callback - })`, - errors: [ - { - line: 1, - column: 13 + m.length, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: m, - }, - }, - ], - } as const) - ), - - ...ALL_WAIT_METHODS.map( - (m) => - ({ - code: `${m}(noop)`, - errors: [ - { - line: 1, - column: 2 + m.length, - messageId: 'noWaitForEmptyCallback', - data: { - methodName: m, - }, - }, - ], - } as const) - ), - ], -}); diff --git a/tests/lib/rules/prefer-find-by.test.ts b/tests/lib/rules/prefer-find-by.test.ts index 97fec6d5..22a2eb4e 100644 --- a/tests/lib/rules/prefer-find-by.test.ts +++ b/tests/lib/rules/prefer-find-by.test.ts @@ -1,7 +1,6 @@ import { TSESLint } from '@typescript-eslint/utils'; import rule, { - WAIT_METHODS, RULE_NAME, getFindByQueryVariant, MessageIds, @@ -31,14 +30,8 @@ function createScenario< | TSESLint.InvalidTestCase | TSESLint.ValidTestCase<[]> >(callback: (waitMethod: string, queryMethod: string) => T) { - return WAIT_METHODS.reduce( - (acc: T[], waitMethod) => - acc.concat( - SYNC_QUERIES_COMBINATIONS.map((queryMethod) => - callback(waitMethod, queryMethod) - ) - ), - [] + return SYNC_QUERIES_COMBINATIONS.map((queryMethod) => + callback('waitFor', queryMethod) ); } @@ -153,7 +146,6 @@ ruleTester.run(RULE_NAME, rule, { import {waitFor} from '${testingFramework}'; it('tests', async () => { await waitFor(); - await wait(); }) `, }, @@ -204,67 +196,59 @@ ruleTester.run(RULE_NAME, rule, { `, })), // // this scenario verifies it works when the render function is defined in another scope - ...WAIT_METHODS.map( - (waitMethod: string) => - ({ - code: ` - import {${waitMethod}} from '${testingFramework}'; + { + code: ` + import { waitFor } from '${testingFramework}'; const { getByText, queryByLabelText, findAllByRole } = customRender() it('tests', async () => { - const submitButton = await ${waitMethod}(() => getByText('baz', { name: 'button' })) + const submitButton = await waitFor(() => getByText('baz', { name: 'button' })) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: 'findBy', - queryMethod: 'Text', - prevQuery: 'getByText', - waitForMethodName: waitMethod, - }, - }, - ], - output: ` - import {${waitMethod}} from '${testingFramework}'; + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: 'findBy', + queryMethod: 'Text', + prevQuery: 'getByText', + }, + }, + ], + output: ` + import { waitFor } from '${testingFramework}'; const { getByText, queryByLabelText, findAllByRole, findByText } = customRender() it('tests', async () => { const submitButton = await findByText('baz', { name: 'button' }) }) `, - } as const) - ), + }, // // this scenario verifies when findBy* were already defined (because it was used elsewhere) - ...WAIT_METHODS.map( - (waitMethod: string) => - ({ - code: ` - import {${waitMethod}} from '${testingFramework}'; + { + code: ` + import { waitFor } from '${testingFramework}'; const { getAllByRole, findAllByRole } = customRender() it('tests', async () => { - const submitButton = await ${waitMethod}(() => getAllByRole('baz', { name: 'button' })) + const submitButton = await waitFor(() => getAllByRole('baz', { name: 'button' })) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: 'findAllBy', - queryMethod: 'Role', - prevQuery: 'getAllByRole', - waitForMethodName: waitMethod, - }, - }, - ], - output: ` - import {${waitMethod}} from '${testingFramework}'; + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: 'findAllBy', + queryMethod: 'Role', + prevQuery: 'getAllByRole', + }, + }, + ], + output: ` + import { waitFor } from '${testingFramework}'; const { getAllByRole, findAllByRole } = customRender() it('tests', async () => { const submitButton = await findAllByRole('baz', { name: 'button' }) }) `, - } as const) - ), + }, // invalid code, as we need findBy* to be defined somewhere, but required for getting 100% coverage { code: `const submitButton = await waitFor(() => getByText('baz', { name: 'button' }))`, @@ -304,67 +288,59 @@ ruleTester.run(RULE_NAME, rule, { `, }, // custom query triggers the error but there is no fix - so output is the same - ...WAIT_METHODS.map( - (waitMethod: string) => - ({ - code: ` - import {${waitMethod},render} from '${testingFramework}'; + { + code: ` + import { waitFor, render} from '${testingFramework}'; it('tests', async () => { const { getByCustomQuery } = render() - const submitButton = await ${waitMethod}(() => getByCustomQuery('baz')) + const submitButton = await waitFor(() => getByCustomQuery('baz')) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: 'findBy', - queryMethod: 'CustomQuery', - prevQuery: 'getByCustomQuery', - waitForMethodName: waitMethod, - }, - }, - ], - output: ` - import {${waitMethod},render} from '${testingFramework}'; + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: 'findBy', + queryMethod: 'CustomQuery', + prevQuery: 'getByCustomQuery', + }, + }, + ], + output: ` + import { waitFor, render} from '${testingFramework}'; it('tests', async () => { const { getByCustomQuery } = render() - const submitButton = await ${waitMethod}(() => getByCustomQuery('baz')) + const submitButton = await waitFor(() => getByCustomQuery('baz')) }) `, - } as const) - ), + }, // custom query triggers the error but there is no fix - so output is the same - ...WAIT_METHODS.map( - (waitMethod: string) => - ({ - code: ` - import {${waitMethod},render,screen} from '${testingFramework}'; + { + code: ` + import {waitFor,render,screen} from '${testingFramework}'; it('tests', async () => { const { getByCustomQuery } = render() - const submitButton = await ${waitMethod}(() => screen.getByCustomQuery('baz')) + const submitButton = await waitFor(() => screen.getByCustomQuery('baz')) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: 'findBy', - queryMethod: 'CustomQuery', - prevQuery: 'getByCustomQuery', - waitForMethodName: waitMethod, - }, - }, - ], - output: ` - import {${waitMethod},render,screen} from '${testingFramework}'; + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: 'findBy', + queryMethod: 'CustomQuery', + prevQuery: 'getByCustomQuery', + }, + }, + ], + output: ` + import {waitFor,render,screen} from '${testingFramework}'; it('tests', async () => { const { getByCustomQuery } = render() - const submitButton = await ${waitMethod}(() => screen.getByCustomQuery('baz')) + const submitButton = await waitFor(() => screen.getByCustomQuery('baz')) }) `, - } as const) - ), + }, // presence matchers ...createScenario((waitMethod: string, queryMethod: string) => ({ code: ` From 8516c6c93c4d4cff0956c836e168ead9879ccd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Sat, 5 Aug 2023 13:47:26 +0200 Subject: [PATCH 19/20] refactor: adjust changes after merge --- README.md | 58 ++++++++++++++++++++++----------------------- tests/index.test.ts | 2 +- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 8063abee..5b02e35d 100644 --- a/README.md +++ b/README.md @@ -206,36 +206,34 @@ module.exports = { ⚠️ Configurations set to warn in.\ 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). -| Name                            | Description | 💼 | 🔧 | -| :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | :-- | -| [await-async-query](docs/rules/await-async-query.md) | Enforce promises from async queries to be handled | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [await-async-utils](docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [await-fire-event](docs/rules/await-fire-event.md) | Enforce promises from `fireEvent` methods to be handled | ![badge-marko][] ![badge-vue][] | | -| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | -| [no-await-sync-events](docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | | | -| [no-await-sync-query](docs/rules/no-await-sync-query.md) | Disallow unnecessary `await` for sync queries | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [no-debugging-utils](docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | 🔧 | -| [no-global-regexp-flag-in-query](docs/rules/no-global-regexp-flag-in-query.md) | Disallow the use of the global RegExp flag (/g) in queries | | 🔧 | -| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | | -| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [no-render-in-setup](docs/rules/no-render-in-setup.md) | Disallow the use of `render` in testing frameworks setup functions | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [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-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [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-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-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-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-vue][] | 🔧 | -| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Ensure appropriate `get*`/`query*` queries are used with their respective matchers | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [prefer-query-by-disappearance](docs/rules/prefer-query-by-disappearance.md) | Suggest using `queryBy*` queries when waiting for disappearance | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [prefer-query-matchers](docs/rules/prefer-query-matchers.md) | Ensure the configured `get*`/`query*` query is used with the corresponding matchers | | | -| [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using `screen` while querying | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | -| [prefer-user-event](docs/rules/prefer-user-event.md) | Suggest using `userEvent` over `fireEvent` for simulating user interactions | | | -| [prefer-wait-for](docs/rules/prefer-wait-for.md) | Use `waitFor` instead of deprecated wait methods | | 🔧 | -| [render-result-naming-convention](docs/rules/render-result-naming-convention.md) | Enforce a valid naming for return value from `render` | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | +| Name                            | Description | 💼 | ⚠️ | 🔧 | +| :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | :------------------------------------------------------------------ | :-- | +| [await-async-events](docs/rules/await-async-events.md) | Enforce promises from async event methods are handled | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | 🔧 | +| [await-async-queries](docs/rules/await-async-queries.md) | Enforce promises from async queries to be handled | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [await-async-utils](docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | | +| [no-await-sync-events](docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | ![badge-angular][] ![badge-dom][] ![badge-react][] | | | +| [no-await-sync-queries](docs/rules/no-await-sync-queries.md) | Disallow unnecessary `await` for sync queries | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [no-debugging-utils](docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | +| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | 🔧 | +| [no-global-regexp-flag-in-query](docs/rules/no-global-regexp-flag-in-query.md) | Disallow the use of the global RegExp flag (/g) in queries | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | 🔧 | +| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | ![badge-react][] ![badge-vue][] | | | +| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [no-render-in-lifecycle](docs/rules/no-render-in-lifecycle.md) | Disallow the use of `render` in testing frameworks setup functions | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [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-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-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-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-vue][] | | 🔧 | +| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Ensure appropriate `get*`/`query*` queries are used with their respective matchers | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [prefer-query-by-disappearance](docs/rules/prefer-query-by-disappearance.md) | Suggest using `queryBy*` queries when waiting for disappearance | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [prefer-query-matchers](docs/rules/prefer-query-matchers.md) | Ensure the configured `get*`/`query*` query is used with the corresponding matchers | | | | +| [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using `screen` while querying | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | +| [prefer-user-event](docs/rules/prefer-user-event.md) | Suggest using `userEvent` over `fireEvent` for simulating user interactions | | | | +| [render-result-naming-convention](docs/rules/render-result-naming-convention.md) | Enforce a valid naming for return value from `render` | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-vue][] | | | diff --git a/tests/index.test.ts b/tests/index.test.ts index b3b535b2..c69e0c6a 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -3,7 +3,7 @@ import { resolve } from 'path'; import plugin from '../lib'; -const numberOfRules = 28; +const numberOfRules = 26; const ruleNames = Object.keys(plugin.rules); // eslint-disable-next-line jest/expect-expect From acc2865c9dc9de024f9aa4257d8551f2d77ffd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Sat, 5 Aug 2023 13:55:18 +0200 Subject: [PATCH 20/20] style: format files --- lib/rules/await-async-events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/await-async-events.ts b/lib/rules/await-async-events.ts index c8797062..53c62659 100644 --- a/lib/rules/await-async-events.ts +++ b/lib/rules/await-async-events.ts @@ -16,7 +16,7 @@ export const RULE_NAME = 'await-async-events'; export type MessageIds = 'awaitAsyncEvent' | 'awaitAsyncEventWrapper'; const FIRE_EVENT_NAME = 'fireEvent'; const USER_EVENT_NAME = 'userEvent'; -type EventModules = typeof EVENTS_SIMULATORS[number]; +type EventModules = (typeof EVENTS_SIMULATORS)[number]; export type Options = [ { eventModule: EventModules | EventModules[];