From 2fd2ee47740dc4dbd0f7d161656a12a3656fbc17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Thu, 22 Dec 2022 11:58:41 +0000 Subject: [PATCH 1/9] refactor(no-wait-for-empty-callback): remove rule BREAKING CHANGE: `waitFor` callback param is no longer optional so this rule is useless --- README.md | 14 +- docs/rules/no-wait-for-empty-callback.md | 45 ---- 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 -------- tests/index.test.ts | 2 +- .../rules/no-wait-for-empty-callback.test.ts | 242 ------------------ 10 files changed, 8 insertions(+), 400 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..b7b3fc22 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@
- - - - - - + + + + + + +

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/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/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/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) - ), - ], -}); From d3f86a2b803963f10be3640758be99cff0dfe85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Thu, 22 Dec 2022 12:17:22 +0000 Subject: [PATCH 2/9] refactor: remove references to unexisting async utils --- README.md | 2 +- docs/rules/await-async-utils.md | 5 +- docs/rules/prefer-find-by.md | 2 +- lib/rules/prefer-find-by.ts | 16 +-- lib/utils/index.ts | 8 +- tests/lib/rules/prefer-find-by.test.ts | 167 +++++++++++-------------- 6 files changed, 79 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index b7b3fc22..e93da689 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -
+align="center"> 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/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/rules/prefer-find-by.ts b/lib/rules/prefer-find-by.ts index 6c096cdb..70e6c7ef 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: [], @@ -80,7 +78,6 @@ export default createTestingLibraryRule({ * @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) ) { 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/lib/rules/prefer-find-by.test.ts b/tests/lib/rules/prefer-find-by.test.ts index 97fec6d5..6e7059c0 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) ); } @@ -204,67 +197,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 +289,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 e0d5e00852fac08f061fd94e80219b90738cba27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Thu, 22 Dec 2022 19:03:14 +0100 Subject: [PATCH 3/9] chore: fix README div block --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e93da689..b7b3fc22 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -align="center"> +
From 7a6818be65ba3464e917bb16e24ea31c94f5bf8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Thu, 22 Dec 2022 19:08:28 +0100 Subject: [PATCH 4/9] fix(prefer-find-by): report waitFor only --- lib/rules/prefer-find-by.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/prefer-find-by.ts b/lib/rules/prefer-find-by.ts index 70e6c7ef..da47b9a4 100644 --- a/lib/rules/prefer-find-by.ts +++ b/lib/rules/prefer-find-by.ts @@ -330,7 +330,7 @@ export default createTestingLibraryRule({ 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { if ( !ASTUtils.isIdentifier(node.callee) || - !helpers.isAsyncUtil(node.callee) + !helpers.isAsyncUtil(node.callee, ['waitFor']) ) { return; } From 9f07de58474b25e12bb3ad31df209e1df2bf2177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Thu, 22 Dec 2022 19:14:16 +0100 Subject: [PATCH 5/9] refactor: remove more old wait references --- docs/rules/no-wait-for-snapshot.md | 8 -------- lib/rules/prefer-find-by.ts | 2 +- tests/lib/rules/prefer-find-by.test.ts | 1 - 3 files changed, 1 insertion(+), 10 deletions(-) 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/lib/rules/prefer-find-by.ts b/lib/rules/prefer-find-by.ts index da47b9a4..f9d951e0 100644 --- a/lib/rules/prefer-find-by.ts +++ b/lib/rules/prefer-find-by.ts @@ -73,7 +73,7 @@ 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` diff --git a/tests/lib/rules/prefer-find-by.test.ts b/tests/lib/rules/prefer-find-by.test.ts index 6e7059c0..22a2eb4e 100644 --- a/tests/lib/rules/prefer-find-by.test.ts +++ b/tests/lib/rules/prefer-find-by.test.ts @@ -146,7 +146,6 @@ ruleTester.run(RULE_NAME, rule, { import {waitFor} from '${testingFramework}'; it('tests', async () => { await waitFor(); - await wait(); }) `, }, From 59f7220b009d43f398fdd4d85aa52cdc5fdf09c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Fri, 23 Dec 2022 17:58:15 +0100 Subject: [PATCH 6/9] docs: update v6 migration guide --- docs/migration-guides/v6.md | 2 ++ 1 file changed, 2 insertions(+) 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 From f8e8c4cf37d599e40ef9023ca91426224b84a22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Tue, 27 Dec 2022 16:56:09 +0100 Subject: [PATCH 7/9] style: revert README changes --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b7b3fc22..5bac1727 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@
- - - - + + + + From 043f37b640f105fa83a30fd940de9bc70702d0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Tue, 27 Dec 2022 17:02:33 +0100 Subject: [PATCH 8/9] style: revert more README changes --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5bac1727..3d61161f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@
- + - - + +

eslint-plugin-testing-library

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

From 78b2fb8d43a7e7ea91f1d22726d39fb076dada50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Tue, 27 Dec 2022 17:03:01 +0100 Subject: [PATCH 9/9] style: indent img correctly --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d61161f..2b834b9b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ - +

eslint-plugin-testing-library