From 7644e3b99fd4890ea7d90d055dbcc8987e9c3b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Thu, 18 Aug 2022 17:12:12 +0200 Subject: [PATCH 1/2] refactor: move lint-staged config to a js file --- .lintstagedrc | 8 -------- lint-staged.config.js | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) delete mode 100644 .lintstagedrc create mode 100644 lint-staged.config.js diff --git a/.lintstagedrc b/.lintstagedrc deleted file mode 100644 index eff1e342..00000000 --- a/.lintstagedrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "*.{js,ts}": [ - "eslint --max-warnings 0 --fix", - "prettier --write", - "jest --findRelatedTests" - ], - "*.{json,md,yml}": ["prettier --write"] -} diff --git a/lint-staged.config.js b/lint-staged.config.js new file mode 100644 index 00000000..bbbe515d --- /dev/null +++ b/lint-staged.config.js @@ -0,0 +1,19 @@ +//eslint-disable-next-line @typescript-eslint/no-var-requires +const { ESLint } = require('eslint'); + +const removeIgnoredFiles = async (files) => { + const eslint = new ESLint(); + const ignoredFiles = await Promise.all( + files.map((file) => eslint.isPathIgnored(file)) + ); + const filteredFiles = files.filter((_, i) => !ignoredFiles[i]); + return filteredFiles.join(' '); +}; + +module.exports = { + '*.{js,ts}': async (files) => { + const filesToLint = await removeIgnoredFiles(files); + return [`eslint --max-warnings=0 ${filesToLint}`]; + }, + '*': 'prettier --write --ignore-unknown', +}; From 380848233840033e0d985d5600a5b64fb201bb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Thu, 18 Aug 2022 17:12:26 +0200 Subject: [PATCH 2/2] style: indent lines with tabs instead of spaces --- .eslintrc.json | 106 +- .prettierrc.js | 3 +- .releaserc.json | 30 +- CONTRIBUTING.md | 14 +- README.md | 82 +- commitlint.config.js | 2 +- docs/migration-guides/v4.md | 90 +- docs/rules/await-async-query.md | 4 +- docs/rules/await-async-utils.md | 104 +- docs/rules/await-fire-event.md | 10 +- docs/rules/consistent-data-testid.md | 24 +- docs/rules/no-await-sync-events.md | 76 +- docs/rules/no-await-sync-query.md | 16 +- docs/rules/no-debugging-utils.md | 20 +- docs/rules/no-dom-import.md | 2 +- docs/rules/no-render-in-setup.md | 32 +- docs/rules/no-unnecessary-act.md | 26 +- docs/rules/no-wait-for-empty-callback.md | 28 +- docs/rules/no-wait-for-multiple-assertions.md | 50 +- docs/rules/no-wait-for-snapshot.md | 34 +- docs/rules/prefer-find-by.md | 24 +- docs/rules/prefer-presence-queries.md | 80 +- docs/rules/prefer-user-event.md | 16 +- docs/rules/prefer-wait-for.md | 40 +- jest.config.js | 24 +- lib/configs/angular.ts | 42 +- lib/configs/dom.ts | 30 +- lib/configs/index.ts | 18 +- lib/configs/marko.ts | 46 +- lib/configs/react.ts | 44 +- lib/configs/vue.ts | 44 +- .../detect-testing-library-utils.ts | 2084 ++++++------ lib/create-testing-library-rule/index.ts | 64 +- lib/index.ts | 4 +- lib/node-utils/index.ts | 882 ++--- lib/node-utils/is-node-of-type.ts | 38 +- lib/rules/await-async-query.ts | 196 +- lib/rules/await-async-utils.ts | 176 +- lib/rules/await-fire-event.ts | 180 +- lib/rules/consistent-data-testid.ts | 212 +- lib/rules/index.ts | 24 +- lib/rules/no-await-sync-events.ts | 200 +- lib/rules/no-await-sync-query.ts | 78 +- lib/rules/no-container.ts | 308 +- lib/rules/no-debugging-utils.ts | 354 +- lib/rules/no-dom-import.ts | 160 +- lib/rules/no-global-regexp-flag-in-query.ts | 178 +- lib/rules/no-manual-cleanup.ts | 212 +- lib/rules/no-node-access.ts | 120 +- lib/rules/no-promise-in-fire-event.ts | 180 +- lib/rules/no-render-in-setup.ts | 242 +- lib/rules/no-unnecessary-act.ts | 384 +-- lib/rules/no-wait-for-empty-callback.ts | 160 +- lib/rules/no-wait-for-multiple-assertions.ts | 142 +- lib/rules/no-wait-for-side-effects.ts | 370 +-- lib/rules/no-wait-for-snapshot.ts | 132 +- lib/rules/prefer-explicit-assert.ts | 362 +- lib/rules/prefer-find-by.ts | 916 ++--- lib/rules/prefer-presence-queries.ts | 144 +- lib/rules/prefer-query-by-disappearance.ts | 342 +- lib/rules/prefer-screen-queries.ts | 346 +- lib/rules/prefer-user-event.ts | 336 +- lib/rules/prefer-wait-for.ts | 400 +-- lib/rules/render-result-naming-convention.ts | 190 +- lib/utils/file-import.ts | 8 +- lib/utils/index.ts | 178 +- lib/utils/types.ts | 44 +- package.json | 166 +- tests/create-testing-library-rule.test.ts | 1248 +++---- tests/eslint-remote-tester.config.js | 74 +- tests/fake-rule.ts | 238 +- tests/index.test.ts | 104 +- tests/lib/rules/await-async-query.test.ts | 510 +-- tests/lib/rules/await-async-utils.test.ts | 420 +-- tests/lib/rules/await-fire-event.test.ts | 400 +-- .../lib/rules/consistent-data-testid.test.ts | 384 +-- tests/lib/rules/no-await-sync-events.test.ts | 540 +-- tests/lib/rules/no-await-sync-query.test.ts | 294 +- tests/lib/rules/no-container.test.ts | 304 +- tests/lib/rules/no-debugging-utils.test.ts | 858 ++--- tests/lib/rules/no-dom-import.test.ts | 362 +- .../no-global-regexp-flag-in-query.test.ts | 202 +- tests/lib/rules/no-manual-cleanup.test.ts | 400 +-- tests/lib/rules/no-node-access.test.ts | 332 +- .../rules/no-promise-in-fire-event.test.ts | 252 +- tests/lib/rules/no-render-in-setup.test.ts | 350 +- tests/lib/rules/no-unnecessary-act.test.ts | 882 ++--- .../rules/no-wait-for-empty-callback.test.ts | 404 +-- .../no-wait-for-multiple-assertions.test.ts | 214 +- .../rules/no-wait-for-side-effects.test.ts | 694 ++-- tests/lib/rules/no-wait-for-snapshot.test.ts | 366 +- .../lib/rules/prefer-explicit-assert.test.ts | 644 ++-- tests/lib/rules/prefer-find-by.test.ts | 806 ++--- .../lib/rules/prefer-presence-queries.test.ts | 2268 ++++++------- .../prefer-query-by-disappearance.test.ts | 852 ++--- tests/lib/rules/prefer-screen-queries.test.ts | 696 ++-- tests/lib/rules/prefer-user-event.test.ts | 878 ++--- tests/lib/rules/prefer-wait-for.test.ts | 2954 ++++++++--------- .../render-result-naming-convention.test.ts | 542 +-- tests/lib/test-utils.ts | 68 +- tools/generate-configs/index.ts | 50 +- tools/generate-configs/utils.ts | 30 +- tools/generate-rules-list/index.ts | 26 +- tools/generate-rules-list/utils.ts | 106 +- tsconfig.eslint.json | 4 +- tsconfig.json | 32 +- 106 files changed, 15731 insertions(+), 15730 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 5761661c..6b6c57a8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,57 +1,57 @@ { - "env": { - "commonjs": true, - "es6": true, - "node": true, - "jest/globals": true - }, - "extends": [ - "kentcdodds", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "prettier", - "plugin:jest/recommended", - "plugin:jest-formatting/recommended" - ], - "plugins": ["@typescript-eslint", "jest", "jest-formatting"], - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.eslint.json" - }, - "rules": { - // TS - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-unused-vars": [ - "warn", - { "argsIgnorePattern": "^_" } - ], - "@typescript-eslint/no-use-before-define": "off", + "env": { + "commonjs": true, + "es6": true, + "node": true, + "jest/globals": true + }, + "extends": [ + "kentcdodds", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + "plugin:jest/recommended", + "plugin:jest-formatting/recommended" + ], + "plugins": ["@typescript-eslint", "jest", "jest-formatting"], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.eslint.json" + }, + "rules": { + // TS + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { "argsIgnorePattern": "^_" } + ], + "@typescript-eslint/no-use-before-define": "off", - // ESLint - "max-lines-per-function": "off", - "no-restricted-imports": [ - "error", - { - "patterns": ["@typescript-eslint/utils/dist/*"] - } - ], + // ESLint + "max-lines-per-function": "off", + "no-restricted-imports": [ + "error", + { + "patterns": ["@typescript-eslint/utils/dist/*"] + } + ], - // Import - "import/no-import-module-exports": "off", - "import/order": [ - "warn", - { - "groups": ["builtin", "external", "parent", "sibling", "index"], - "newlines-between": "always", - "alphabetize": { - "order": "asc", - "caseInsensitive": false - } - } - ] - } + // Import + "import/no-import-module-exports": "off", + "import/order": [ + "warn", + { + "groups": ["builtin", "external", "parent", "sibling", "index"], + "newlines-between": "always", + "alphabetize": { + "order": "asc", + "caseInsensitive": false + } + } + ] + } } diff --git a/.prettierrc.js b/.prettierrc.js index e340799c..4e57e1cf 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,3 +1,4 @@ module.exports = { - singleQuote: true, + singleQuote: true, + useTabs: true, }; diff --git a/.releaserc.json b/.releaserc.json index dd5a8466..dd066de3 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -1,17 +1,17 @@ { - "pkgRoot": "dist", - "branches": [ - "+([0-9])?(.{+([0-9]),x}).x", - "main", - "next", - "next-major", - { - "name": "beta", - "prerelease": true - }, - { - "name": "alpha", - "prerelease": true - } - ] + "pkgRoot": "dist", + "branches": [ + "+([0-9])?(.{+([0-9]),x}).x", + "main", + "next", + "next-major", + { + "name": "beta", + "prerelease": true + }, + { + "name": "alpha", + "prerelease": true + } + ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d92df51..e2527f68 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -118,10 +118,10 @@ If you wish to run a single test while developing locally, add `only: true` to t ```javascript valid: [ - { - only: true, - code: `...`, - }, + { + only: true, + code: `...`, + }, ]; ``` @@ -137,9 +137,9 @@ Since the plugin will report differently depending on which Testing Library pack import { render } from '@testing-library/react'; test('should report invalid render usage', () => { - // the following line is the actual code you needed to test your rule, - // but everything else helps finding edge cases and makes it more robust. - const wrapper = render(); + // the following line is the actual code you needed to test your rule, + // but everything else helps finding edge cases and makes it more robust. + const wrapper = render(); }); ``` diff --git a/README.md b/README.md index ca258987..98d44a48 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Add `testing-library` to the plugins section of your `.eslintrc` configuration f ```json { - "plugins": ["testing-library"] + "plugins": ["testing-library"] } ``` @@ -66,12 +66,12 @@ Then configure the rules you want to use within `rules` property of your `.eslin ```json { - "rules": { - "testing-library/await-async-query": "error", - "testing-library/no-await-sync-query": "error", - "testing-library/no-debugging-utils": "warn", - "testing-library/no-dom-import": "off" - } + "rules": { + "testing-library/await-async-query": "error", + "testing-library/no-await-sync-query": "error", + "testing-library/no-debugging-utils": "warn", + "testing-library/no-dom-import": "off" + } } ``` @@ -88,19 +88,19 @@ Assuming you are using the same pattern for your test files as [Jest by default] ```json5 // .eslintrc { - // 1) Here we have our usual config which applies to the whole project, so we don't put testing-library preset here. - extends: ['airbnb', 'plugin:prettier/recommended'], - - // 2) We load other plugins than eslint-plugin-testing-library globally if we want to. - plugins: ['react-hooks'], - - overrides: [ - { - // 3) Now we enable eslint-plugin-testing-library rules or preset only for matching testing files! - files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], - extends: ['plugin:testing-library/react'], - }, - ], + // 1) Here we have our usual config which applies to the whole project, so we don't put testing-library preset here. + extends: ['airbnb', 'plugin:prettier/recommended'], + + // 2) We load other plugins than eslint-plugin-testing-library globally if we want to. + plugins: ['react-hooks'], + + overrides: [ + { + // 3) Now we enable eslint-plugin-testing-library rules or preset only for matching testing files! + files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], + extends: ['plugin:testing-library/react'], + }, + ], } ``` @@ -118,14 +118,14 @@ Since each one of these configurations is aimed at a particular Testing Library ```json5 // ❌ Don't do this { - extends: ['plugin:testing-library/dom', 'plugin:testing-library/react'], + extends: ['plugin:testing-library/dom', 'plugin:testing-library/react'], } ``` ```json5 // ✅ Just do this instead { - extends: ['plugin:testing-library/react'], + extends: ['plugin:testing-library/react'], } ``` @@ -138,7 +138,7 @@ To enable this configuration use the `extends` property in your ```json { - "extends": ["plugin:testing-library/dom"] + "extends": ["plugin:testing-library/dom"] } ``` @@ -151,7 +151,7 @@ To enable this configuration use the `extends` property in your ```json { - "extends": ["plugin:testing-library/angular"] + "extends": ["plugin:testing-library/angular"] } ``` @@ -164,7 +164,7 @@ To enable this configuration use the `extends` property in your ```json { - "extends": ["plugin:testing-library/react"] + "extends": ["plugin:testing-library/react"] } ``` @@ -177,7 +177,7 @@ To enable this configuration use the `extends` property in your ```json { - "extends": ["plugin:testing-library/vue"] + "extends": ["plugin:testing-library/vue"] } ``` @@ -190,7 +190,7 @@ To enable this configuration use the `extends` property in your ```json { - "extends": ["plugin:testing-library/marko"] + "extends": ["plugin:testing-library/marko"] } ``` @@ -255,9 +255,9 @@ The name of your custom utility file from where you re-export everything from th ```json5 // .eslintrc { - settings: { - 'testing-library/utils-module': 'my-custom-test-utility-file', - }, + settings: { + 'testing-library/utils-module': 'my-custom-test-utility-file', + }, } ``` @@ -270,9 +270,9 @@ A list of function names that are valid as Testing Library custom renders, or `" ```json5 // .eslintrc { - settings: { - 'testing-library/custom-renders': ['display', 'renderWithProviders'], - }, + settings: { + 'testing-library/custom-renders': ['display', 'renderWithProviders'], + }, } ``` @@ -285,9 +285,9 @@ A list of query names/patterns that are valid as Testing Library custom queries, ```json5 // .eslintrc { - settings: { - 'testing-library/custom-queries': ['ByIcon', 'getByComplexText'], - }, + settings: { + 'testing-library/custom-queries': ['ByIcon', 'getByComplexText'], + }, } ``` @@ -300,11 +300,11 @@ Since each Shared Setting is related to one Aggressive Reporting mechanism, and ```json5 // .eslintrc { - settings: { - 'testing-library/utils-module': 'off', - 'testing-library/custom-renders': 'off', - 'testing-library/custom-queries': 'off', - }, + settings: { + 'testing-library/utils-module': 'off', + 'testing-library/custom-renders': 'off', + 'testing-library/custom-queries': 'off', + }, } ``` diff --git a/commitlint.config.js b/commitlint.config.js index 84dcb122..4c73b71e 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,3 +1,3 @@ module.exports = { - extends: ['@commitlint/config-conventional'], + extends: ['@commitlint/config-conventional'], }; diff --git a/docs/migration-guides/v4.md b/docs/migration-guides/v4.md index 4d89d6c8..69c4de6b 100644 --- a/docs/migration-guides/v4.md +++ b/docs/migration-guides/v4.md @@ -185,9 +185,9 @@ If you pass a string other than `"off"` to this option, it will represent your c ```json // .eslintrc { - "settings": { - "testing-library/utils-module": "my-custom-test-utility-file" - } + "settings": { + "testing-library/utils-module": "my-custom-test-utility-file" + } } ``` @@ -197,9 +197,9 @@ Configuring this setting like that, you'll restrict the errors reported by the p import { waitFor } from '@testing-library/react'; test('testing-library/utils-module setting example', () => { - // ✅ this would be reported since this invalid usage of an util - // is imported from `@testing-library/*` package - waitFor(/* some invalid usage to be reported */); + // ✅ this would be reported since this invalid usage of an util + // is imported from `@testing-library/*` package + waitFor(/* some invalid usage to be reported */); }); ``` @@ -207,9 +207,9 @@ test('testing-library/utils-module setting example', () => { import { waitFor } from '../my-custom-test-utility-file'; test('testing-library/utils-module setting example', () => { - // ✅ this would be reported since this invalid usage of an util - // is imported from specified custom utility file. - waitFor(/* some invalid usage to be reported */); + // ✅ this would be reported since this invalid usage of an util + // is imported from specified custom utility file. + waitFor(/* some invalid usage to be reported */); }); ``` @@ -217,9 +217,9 @@ test('testing-library/utils-module setting example', () => { import { waitFor } from '../somewhere-else'; test('testing-library/utils-module setting example', () => { - // ❌ this would NOT be reported since this invalid usage of an util - // is NOT imported from either `@testing-library/*` package or specified custom utility file. - waitFor(/* some invalid usage to be reported */); + // ❌ this would NOT be reported since this invalid usage of an util + // is NOT imported from either `@testing-library/*` package or specified custom utility file. + waitFor(/* some invalid usage to be reported */); }); ``` @@ -228,9 +228,9 @@ You can also set this setting to `"off"` to entirely opt-out Aggressive Imports ```json // .eslintrc { - "settings": { - "testing-library/utils-module": "off" - } + "settings": { + "testing-library/utils-module": "off" + } } ``` @@ -243,9 +243,9 @@ If you pass an array of strings to this option, it will represent a list of func ```json // .eslintrc { - "settings": { - "testing-library/custom-renders": ["display", "renderWithProviders"] - } + "settings": { + "testing-library/custom-renders": ["display", "renderWithProviders"] + } } ``` @@ -253,10 +253,10 @@ Configuring this setting like that, you'll restrict the errors reported by the p ```javascript import { - render, - display, - renderWithProviders, - renderWithRedux, + render, + display, + renderWithProviders, + renderWithRedux, } from 'test-utils'; import Component from 'somewhere'; @@ -264,25 +264,25 @@ const setupA = () => renderWithProviders(); const setupB = () => renderWithRedux(); test('testing-library/custom-renders setting example', () => { - // ✅ this would be reported since `render` is a built-in Testing Library util - const invalidUsage = render(); + // ✅ this would be reported since `render` is a built-in Testing Library util + const invalidUsage = render(); - // ✅ this would be reported since `display` has been set as `custom-render` - const invalidUsage = display(); + // ✅ this would be reported since `display` has been set as `custom-render` + const invalidUsage = display(); - // ✅ this would be reported since `renderWithProviders` has been set as `custom-render` - const invalidUsage = renderWithProviders(); + // ✅ this would be reported since `renderWithProviders` has been set as `custom-render` + const invalidUsage = renderWithProviders(); - // ❌ this would NOT be reported since `renderWithRedux` isn't a `custom-render` or built-in one - const invalidUsage = renderWithRedux(); + // ❌ this would NOT be reported since `renderWithRedux` isn't a `custom-render` or built-in one + const invalidUsage = renderWithRedux(); - // ✅ this would be reported since it wraps `renderWithProviders`, - // which has been set as `custom-render` - const invalidUsage = setupA(); + // ✅ this would be reported since it wraps `renderWithProviders`, + // which has been set as `custom-render` + const invalidUsage = setupA(); - // ❌ this would NOT be reported since it wraps `renderWithRedux`, - // which isn't a `custom-render` or built-in one - const invalidUsage = setupB(); + // ❌ this would NOT be reported since it wraps `renderWithRedux`, + // which isn't a `custom-render` or built-in one + const invalidUsage = setupB(); }); ``` @@ -291,9 +291,9 @@ You can also set this setting to `"off"` to entirely opt-out Aggressive Renders ```json // .eslintrc { - "settings": { - "testing-library/custom-renders": "off" - } + "settings": { + "testing-library/custom-renders": "off" + } } ``` @@ -311,9 +311,9 @@ Each string passed to this list of custom queries can be: ```json // .eslintrc { - "settings": { - "testing-library/custom-queries": ["ByIcon", "getByComplexText"] - } + "settings": { + "testing-library/custom-queries": ["ByIcon", "getByComplexText"] + } } ``` @@ -347,8 +347,8 @@ You can also set this setting to `"off"` to entirely opt-out Aggressive Queries ```json // .eslintrc { - "settings": { - "testing-library/custom-queries": "off" - } + "settings": { + "testing-library/custom-queries": "off" + } } ``` diff --git a/docs/rules/await-async-query.md b/docs/rules/await-async-query.md index c0f8081f..70e83aaf 100644 --- a/docs/rules/await-async-query.md +++ b/docs/rules/await-async-query.md @@ -79,8 +79,8 @@ await Promise.all([findByText('my button'), findByText('something else')]); ```js // several promises handled `Promise.allSettled` is correct await Promise.allSettled([ - findByText('my button'), - findByText('something else'), + findByText('my button'), + findByText('something else'), ]); ``` diff --git a/docs/rules/await-async-utils.md b/docs/rules/await-async-utils.md index 9d23ab41..c17a4cd8 100644 --- a/docs/rules/await-async-utils.md +++ b/docs/rules/await-async-utils.md @@ -26,28 +26,28 @@ Examples of **incorrect** code for this rule: ```js test('something incorrectly', async () => { - // ... - waitFor(() => {}); - - const [usernameElement, passwordElement] = waitFor( - () => [ - getByLabelText(container, 'username'), - getByLabelText(container, 'password'), - ], - { container } - ); - - waitFor(() => {}, { timeout: 100 }); - - waitForElementToBeRemoved(() => document.querySelector('div.getOuttaHere')); - - // wrap an async util within a function... - const makeCustomWait = () => { - return waitForElementToBeRemoved(() => - document.querySelector('div.getOuttaHere') - ); - }; - makeCustomWait(); // ...but not handling promise from it is incorrect + // ... + waitFor(() => {}); + + const [usernameElement, passwordElement] = waitFor( + () => [ + getByLabelText(container, 'username'), + getByLabelText(container, 'password'), + ], + { container } + ); + + waitFor(() => {}, { timeout: 100 }); + + waitForElementToBeRemoved(() => document.querySelector('div.getOuttaHere')); + + // wrap an async util within a function... + const makeCustomWait = () => { + return waitForElementToBeRemoved(() => + document.querySelector('div.getOuttaHere') + ); + }; + makeCustomWait(); // ...but not handling promise from it is incorrect }); ``` @@ -55,36 +55,36 @@ Examples of **correct** code for this rule: ```js test('something correctly', async () => { - // ... - // `await` operator is correct - await waitFor(() => getByLabelText('email')); - - const [usernameElement, passwordElement] = await waitFor( - () => [ - getByLabelText(container, 'username'), - getByLabelText(container, 'password'), - ], - { container } - ); - - // `then` chained method is correct - waitFor(() => {}, { timeout: 100 }) - .then(() => console.log('DOM changed!')) - .catch((err) => console.log(`Error you need to deal with: ${err}`)); - - // wrap an async util within a function... - const makeCustomWait = () => { - return waitForElementToBeRemoved(() => - document.querySelector('div.getOuttaHere') - ); - }; - await makeCustomWait(); // ...and handling promise from it is correct - - // using Promise.all combining the methods - await Promise.all([ - waitFor(() => getByLabelText('email')), - waitForElementToBeRemoved(() => document.querySelector('div.getOuttaHere')), - ]); + // ... + // `await` operator is correct + await waitFor(() => getByLabelText('email')); + + const [usernameElement, passwordElement] = await waitFor( + () => [ + getByLabelText(container, 'username'), + getByLabelText(container, 'password'), + ], + { container } + ); + + // `then` chained method is correct + waitFor(() => {}, { timeout: 100 }) + .then(() => console.log('DOM changed!')) + .catch((err) => console.log(`Error you need to deal with: ${err}`)); + + // wrap an async util within a function... + const makeCustomWait = () => { + return waitForElementToBeRemoved(() => + document.querySelector('div.getOuttaHere') + ); + }; + await makeCustomWait(); // ...and handling promise from it is correct + + // using Promise.all combining the methods + await Promise.all([ + waitFor(() => getByLabelText('email')), + waitForElementToBeRemoved(() => document.querySelector('div.getOuttaHere')), + ]); }); ``` diff --git a/docs/rules/await-fire-event.md b/docs/rules/await-fire-event.md index f88ca68f..54643c60 100644 --- a/docs/rules/await-fire-event.md +++ b/docs/rules/await-fire-event.md @@ -24,7 +24,7 @@ fireEvent.blur(getByLabelText('username')); // wrap a fireEvent method within a function... function triggerEvent() { - return fireEvent.click(button); + return fireEvent.click(button); } triggerEvent(); // ...but not handling promise from it is incorrect too ``` @@ -38,7 +38,7 @@ await fireEvent.blur(getByLabelText('username')); // `then` method is correct fireEvent.click(getByText('Click me')).then(() => { - // ... + // ... }); // return the promise within a function is correct too! @@ -46,14 +46,14 @@ const clickMeArrowFn = () => fireEvent.click(getByText('Click me')); // wrap a fireEvent method within a function... function triggerEvent() { - return fireEvent.click(button); + 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')), + fireEvent.focus(getByLabelText('username')), + fireEvent.blur(getByLabelText('username')), ]); ``` diff --git a/docs/rules/consistent-data-testid.md b/docs/rules/consistent-data-testid.md index 107f28f4..50a4beed 100644 --- a/docs/rules/consistent-data-testid.md +++ b/docs/rules/consistent-data-testid.md @@ -37,22 +37,22 @@ const baz = (props) =>
...
; ```json { - "testing-library/consistent-data-testid": [ - 2, - { - "testIdPattern": "^TestId(__[A-Z]*)?$" - } - ] + "testing-library/consistent-data-testid": [ + 2, + { + "testIdPattern": "^TestId(__[A-Z]*)?$" + } + ] } ``` ```json { - "testing-library/consistent-data-testid": [ - 2, - { - "testIdAttribute": ["data-testid", "testId"] - } - ] + "testing-library/consistent-data-testid": [ + 2, + { + "testIdAttribute": ["data-testid", "testId"] + } + ] } ``` diff --git a/docs/rules/no-await-sync-events.md b/docs/rules/no-await-sync-events.md index 141c3632..ae891577 100644 --- a/docs/rules/no-await-sync-events.md +++ b/docs/rules/no-await-sync-events.md @@ -22,24 +22,24 @@ Examples of **incorrect** code for this rule: ```js const foo = async () => { - // ... - await fireEvent.click(button); - // ... + // ... + await fireEvent.click(button); + // ... }; const bar = async () => { - // ... - // userEvent prior to v14 - await userEvent.tab(); - // ... + // ... + // userEvent prior to v14 + await userEvent.tab(); + // ... }; const baz = async () => { - // ... - // userEvent prior to v14 - await userEvent.type(textInput, 'abc'); - await userEvent.keyboard('abc'); - // ... + // ... + // userEvent prior to v14 + await userEvent.type(textInput, 'abc'); + await userEvent.keyboard('abc'); + // ... }; ``` @@ -47,35 +47,35 @@ Examples of **correct** code for this rule: ```js const foo = () => { - // ... - fireEvent.click(button); - // ... + // ... + fireEvent.click(button); + // ... }; const bar = () => { - // ... - userEvent.tab(); - // ... + // ... + userEvent.tab(); + // ... }; const baz = async () => { - // await userEvent.type only with delay option - await userEvent.type(textInput, 'abc', { delay: 1000 }); - userEvent.type(textInput, '123'); - - // same for userEvent.keyboard - await userEvent.keyboard(textInput, 'abc', { delay: 1000 }); - userEvent.keyboard('123'); - // ... + // await userEvent.type only with delay option + await userEvent.type(textInput, 'abc', { delay: 1000 }); + userEvent.type(textInput, '123'); + + // same for userEvent.keyboard + await userEvent.keyboard(textInput, 'abc', { delay: 1000 }); + userEvent.keyboard('123'); + // ... }; const qux = async () => { - // userEvent v14 - await userEvent.tab(); - await userEvent.click(button); - await userEvent.type(textInput, 'abc'); - await userEvent.keyboard('abc'); - // ... + // userEvent v14 + await userEvent.tab(); + await userEvent.click(button); + await userEvent.type(textInput, 'abc'); + await userEvent.keyboard('abc'); + // ... }; ``` @@ -93,12 +93,12 @@ Example: ```json { - "testing-library/no-await-sync-events": [ - "error", - { - "eventModules": ["fire-event", "user-event"] - } - ] + "testing-library/no-await-sync-events": [ + "error", + { + "eventModules": ["fire-event", "user-event"] + } + ] } ``` diff --git a/docs/rules/no-await-sync-query.md b/docs/rules/no-await-sync-query.md index 09424258..c86baf47 100644 --- a/docs/rules/no-await-sync-query.md +++ b/docs/rules/no-await-sync-query.md @@ -40,20 +40,20 @@ Examples of **correct** code for this rule: ```js const foo = () => { - // ... - const rows = queryAllByRole('row'); - // ... + // ... + const rows = queryAllByRole('row'); + // ... }; const bar = () => { - // ... - const button = getByText('submit'); - // ... + // ... + const button = getByText('submit'); + // ... }; const baz = () => { - // ... - const button = screen.getByText('submit'); + // ... + const button = screen.getByText('submit'); }; ``` diff --git a/docs/rules/no-debugging-utils.md b/docs/rules/no-debugging-utils.md index 7737eb9f..7628e04a 100644 --- a/docs/rules/no-debugging-utils.md +++ b/docs/rules/no-debugging-utils.md @@ -41,16 +41,16 @@ You can control which debugging utils are checked for with the `utilsToCheckFor` ```json { - "testing-library/no-debugging-utils": [ - "error", - { - "utilsToCheckFor": { - "debug": false, - "logRoles": true, - "logDOM": true - } - } - ] + "testing-library/no-debugging-utils": [ + "error", + { + "utilsToCheckFor": { + "debug": false, + "logRoles": true, + "logDOM": true + } + } + ] } ``` diff --git a/docs/rules/no-dom-import.md b/docs/rules/no-dom-import.md index fb453b25..2e84b17a 100644 --- a/docs/rules/no-dom-import.md +++ b/docs/rules/no-dom-import.md @@ -65,7 +65,7 @@ This rule has an option in case you want to tell the user which framework to use ```json { - "testing-library/no-dom-import": ["error", "react"] + "testing-library/no-dom-import": ["error", "react"] } ``` diff --git a/docs/rules/no-render-in-setup.md b/docs/rules/no-render-in-setup.md index 91ec364f..cd27e366 100644 --- a/docs/rules/no-render-in-setup.md +++ b/docs/rules/no-render-in-setup.md @@ -8,15 +8,15 @@ Examples of **incorrect** code for this rule: ```js beforeEach(() => { - render(); + render(); }); it('Should have foo', () => { - expect(screen.getByText('foo')).toBeInTheDocument(); + expect(screen.getByText('foo')).toBeInTheDocument(); }); it('Should have bar', () => { - expect(screen.getByText('bar')).toBeInTheDocument(); + expect(screen.getByText('bar')).toBeInTheDocument(); }); ``` @@ -24,29 +24,29 @@ it('Should have bar', () => { const setup = () => render(); beforeEach(() => { - setup(); + setup(); }); it('Should have foo', () => { - expect(screen.getByText('foo')).toBeInTheDocument(); + expect(screen.getByText('foo')).toBeInTheDocument(); }); it('Should have bar', () => { - expect(screen.getByText('bar')).toBeInTheDocument(); + expect(screen.getByText('bar')).toBeInTheDocument(); }); ``` ```js beforeAll(() => { - render(); + render(); }); it('Should have foo', () => { - expect(screen.getByText('foo')).toBeInTheDocument(); + expect(screen.getByText('foo')).toBeInTheDocument(); }); it('Should have bar', () => { - expect(screen.getByText('bar')).toBeInTheDocument(); + expect(screen.getByText('bar')).toBeInTheDocument(); }); ``` @@ -54,9 +54,9 @@ Examples of **correct** code for this rule: ```js it('Should have foo and bar', () => { - render(); - expect(screen.getByText('foo')).toBeInTheDocument(); - expect(screen.getByText('bar')).toBeInTheDocument(); + render(); + expect(screen.getByText('foo')).toBeInTheDocument(); + expect(screen.getByText('bar')).toBeInTheDocument(); }); ``` @@ -64,13 +64,13 @@ it('Should have foo and bar', () => { const setup = () => render(); beforeEach(() => { - // other stuff... + // other stuff... }); it('Should have foo and bar', () => { - setup(); - expect(screen.getByText('foo')).toBeInTheDocument(); - expect(screen.getByText('bar')).toBeInTheDocument(); + setup(); + expect(screen.getByText('foo')).toBeInTheDocument(); + expect(screen.getByText('bar')).toBeInTheDocument(); }); ``` diff --git a/docs/rules/no-unnecessary-act.md b/docs/rules/no-unnecessary-act.md index c5919e92..019f5661 100644 --- a/docs/rules/no-unnecessary-act.md +++ b/docs/rules/no-unnecessary-act.md @@ -22,11 +22,11 @@ Example of **incorrect** code for this rule: ```js // ❌ wrapping things related to Testing Library in `act` is incorrect import { - act, - render, - screen, - waitFor, - fireEvent, + act, + render, + screen, + waitFor, + fireEvent, } from '@testing-library/react'; // ^ act imported from 'react-dom/test-utils' will be reported too import userEvent from '@testing-library/user-event'; @@ -34,7 +34,7 @@ import userEvent from '@testing-library/user-event'; // ... act(() => { - render(); + render(); }); await act(async () => waitFor(() => {})); @@ -42,11 +42,11 @@ await act(async () => waitFor(() => {})); act(() => screen.getByRole('button')); act(() => { - fireEvent.click(element); + fireEvent.click(element); }); act(() => { - userEvent.click(element); + userEvent.click(element); }); ``` @@ -73,7 +73,7 @@ import { stuffThatDoesNotUseRTL } from 'somwhere-else'; // ... act(() => { - stuffThatDoesNotUseRTL(); + stuffThatDoesNotUseRTL(); }); ``` @@ -83,8 +83,8 @@ import { act, screen } from '@testing-library/react'; import { stuffThatDoesNotUseRTL } from 'somwhere-else'; await act(async () => { - await screen.findByRole('button'); - stuffThatDoesNotUseRTL(); + await screen.findByRole('button'); + stuffThatDoesNotUseRTL(); }); ``` @@ -107,8 +107,8 @@ import { act, screen } from '@testing-library/react'; import { stuffThatDoesNotUseRTL } from 'somwhere-else'; await act(async () => { - await screen.findByRole('button'); - stuffThatDoesNotUseRTL(); + await screen.findByRole('button'); + stuffThatDoesNotUseRTL(); }); ``` diff --git a/docs/rules/no-wait-for-empty-callback.md b/docs/rules/no-wait-for-empty-callback.md index 612af0f9..2bcf48d5 100644 --- a/docs/rules/no-wait-for-empty-callback.md +++ b/docs/rules/no-wait-for-empty-callback.md @@ -10,13 +10,13 @@ Examples of **incorrect** code for this rule: ```js const foo = async () => { - await waitFor(() => {}); - await waitFor(function () {}); - await waitFor(noop); + await waitFor(() => {}); + await waitFor(function () {}); + await waitFor(noop); - await waitForElementToBeRemoved(() => {}); - await waitForElementToBeRemoved(function () {}); - await waitForElementToBeRemoved(noop); + await waitForElementToBeRemoved(() => {}); + await waitForElementToBeRemoved(function () {}); + await waitForElementToBeRemoved(noop); }; ``` @@ -24,14 +24,14 @@ 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); + await waitFor(() => { + screen.getByText(/submit/i); + }); + + const submit = screen.getByText(/submit/i); + await waitForElementToBeRemoved(() => submit); + // or + await waitForElementToBeRemoved(submit); }; ``` diff --git a/docs/rules/no-wait-for-multiple-assertions.md b/docs/rules/no-wait-for-multiple-assertions.md index efe376ca..82a33204 100644 --- a/docs/rules/no-wait-for-multiple-assertions.md +++ b/docs/rules/no-wait-for-multiple-assertions.md @@ -11,16 +11,16 @@ Example of **incorrect** code for this rule: ```js const foo = async () => { - await waitFor(() => { - expect(a).toEqual('a'); - expect(b).toEqual('b'); - }); - - // or - await waitFor(function () { - expect(a).toEqual('a'); - expect(b).toEqual('b'); - }); + await waitFor(() => { + expect(a).toEqual('a'); + expect(b).toEqual('b'); + }); + + // or + await waitFor(function () { + expect(a).toEqual('a'); + expect(b).toEqual('b'); + }); }; ``` @@ -28,21 +28,21 @@ Examples of **correct** code for this rule: ```js const foo = async () => { - await waitFor(() => expect(a).toEqual('a')); - expect(b).toEqual('b'); - - // or - await waitFor(function () { - expect(a).toEqual('a'); - }); - expect(b).toEqual('b'); - - // it only detects expect - // so this case doesn't generate warnings - await waitFor(() => { - fireEvent.keyDown(input, { key: 'ArrowDown' }); - expect(b).toEqual('b'); - }); + await waitFor(() => expect(a).toEqual('a')); + expect(b).toEqual('b'); + + // or + await waitFor(function () { + expect(a).toEqual('a'); + }); + expect(b).toEqual('b'); + + // it only detects expect + // so this case doesn't generate warnings + await waitFor(() => { + fireEvent.keyDown(input, { key: 'ArrowDown' }); + expect(b).toEqual('b'); + }); }; ``` diff --git a/docs/rules/no-wait-for-snapshot.md b/docs/rules/no-wait-for-snapshot.md index 65b3683f..1af66404 100644 --- a/docs/rules/no-wait-for-snapshot.md +++ b/docs/rules/no-wait-for-snapshot.md @@ -15,23 +15,23 @@ Examples of **incorrect** code for this rule: ```js const foo = async () => { - // ... - await waitFor(() => expect(container).toMatchSnapshot()); - // ... + // ... + await waitFor(() => expect(container).toMatchSnapshot()); + // ... }; const bar = async () => { - // ... - await waitFor(() => expect(container).toMatchInlineSnapshot()); - // ... + // ... + await waitFor(() => expect(container).toMatchInlineSnapshot()); + // ... }; const baz = async () => { - // ... - await wait(() => { - expect(container).toMatchSnapshot(); - }); - // ... + // ... + await wait(() => { + expect(container).toMatchSnapshot(); + }); + // ... }; ``` @@ -39,15 +39,15 @@ Examples of **correct** code for this rule: ```js const foo = () => { - // ... - expect(container).toMatchSnapshot(); - // ... + // ... + expect(container).toMatchSnapshot(); + // ... }; const bar = () => { - // ... - expect(container).toMatchInlineSnapshot(); - // ... + // ... + expect(container).toMatchInlineSnapshot(); + // ... }; ``` diff --git a/docs/rules/prefer-find-by.md b/docs/rules/prefer-find-by.md index 210e4c8c..7187dc37 100644 --- a/docs/rules/prefer-find-by.md +++ b/docs/rules/prefer-find-by.md @@ -12,28 +12,28 @@ Examples of **incorrect** code for this rule ```js // arrow functions with one statement, using screen and any sync query method const submitButton = await waitFor(() => - screen.getByRole('button', { name: /submit/i }) + screen.getByRole('button', { name: /submit/i }) ); const submitButton = await waitFor(() => - screen.getAllByTestId('button', { name: /submit/i }) + screen.getAllByTestId('button', { name: /submit/i }) ); // arrow functions with one statement, calling any sync query method const submitButton = await waitFor(() => - queryByLabel('button', { name: /submit/i }) + queryByLabel('button', { name: /submit/i }) ); const submitButton = await waitFor(() => - queryAllByText('button', { name: /submit/i }) + queryAllByText('button', { name: /submit/i }) ); // arrow functions with one statement, calling any sync query method with presence assertion const submitButton = await waitFor(() => - expect(queryByLabel('button', { name: /submit/i })).toBeInTheDocument() + expect(queryByLabel('button', { name: /submit/i })).toBeInTheDocument() ); const submitButton = await waitFor(() => - expect(queryByLabel('button', { name: /submit/i })).not.toBeFalsy() + expect(queryByLabel('button', { name: /submit/i })).not.toBeFalsy() ); ``` @@ -51,21 +51,21 @@ await waitForElementToBeRemoved(document.querySelector('foo')); // using waitFor with a function await waitFor(function () { - foo(); - return getByText('name'); + foo(); + return getByText('name'); }); // passing a reference of a function function myCustomFunction() { - foo(); - return getByText('name'); + foo(); + return getByText('name'); } await waitFor(myCustomFunction); // using waitFor with an arrow function with a code block await waitFor(() => { - baz(); - return queryAllByText('foo'); + baz(); + return queryAllByText('foo'); }); // using a custom arrow function diff --git a/docs/rules/prefer-presence-queries.md b/docs/rules/prefer-presence-queries.md index 78ae6463..93e81f0d 100644 --- a/docs/rules/prefer-presence-queries.md +++ b/docs/rules/prefer-presence-queries.md @@ -16,21 +16,21 @@ Examples of **incorrect** code for this rule: ```js test('some test', () => { - render(); - - // check element is present with `queryBy*` - expect(screen.queryByText('button')).toBeInTheDocument(); - expect(screen.queryAllByText('button')[0]).toBeTruthy(); - expect(screen.queryByText('button')).not.toBeNull(); - expect(screen.queryAllByText('button')[2]).not.toBeNull(); - expect(screen.queryByText('button')).not.toBeFalsy(); - - // check element is NOT present with `getBy*` - expect(screen.getByText('loading')).not.toBeInTheDocument(); - expect(screen.getAllByText('loading')[1]).not.toBeTruthy(); - expect(screen.getByText('loading')).toBeNull(); - expect(screen.getAllByText('loading')[3]).toBeNull(); - expect(screen.getByText('loading')).toBeFalsy(); + render(); + + // check element is present with `queryBy*` + expect(screen.queryByText('button')).toBeInTheDocument(); + expect(screen.queryAllByText('button')[0]).toBeTruthy(); + expect(screen.queryByText('button')).not.toBeNull(); + expect(screen.queryAllByText('button')[2]).not.toBeNull(); + expect(screen.queryByText('button')).not.toBeFalsy(); + + // check element is NOT present with `getBy*` + expect(screen.getByText('loading')).not.toBeInTheDocument(); + expect(screen.getAllByText('loading')[1]).not.toBeTruthy(); + expect(screen.getByText('loading')).toBeNull(); + expect(screen.getAllByText('loading')[3]).toBeNull(); + expect(screen.getByText('loading')).toBeFalsy(); }); ``` @@ -38,24 +38,24 @@ Examples of **correct** code for this rule: ```js test('some test', async () => { - render(); - // check element is present with `getBy*` - expect(screen.getByText('button')).toBeInTheDocument(); - expect(screen.getAllByText('button')[9]).toBeTruthy(); - expect(screen.getByText('button')).not.toBeNull(); - expect(screen.getAllByText('button')[7]).not.toBeNull(); - expect(screen.getByText('button')).not.toBeFalsy(); - - // check element is NOT present with `queryBy*` - expect(screen.queryByText('loading')).not.toBeInTheDocument(); - expect(screen.queryAllByText('loading')[8]).not.toBeTruthy(); - expect(screen.queryByText('loading')).toBeNull(); - expect(screen.queryAllByText('loading')[6]).toBeNull(); - expect(screen.queryByText('loading')).toBeFalsy(); - - // `findBy*` queries are out of the scope for this rule - const button = await screen.findByText('submit'); - expect(button).toBeInTheDocument(); + render(); + // check element is present with `getBy*` + expect(screen.getByText('button')).toBeInTheDocument(); + expect(screen.getAllByText('button')[9]).toBeTruthy(); + expect(screen.getByText('button')).not.toBeNull(); + expect(screen.getAllByText('button')[7]).not.toBeNull(); + expect(screen.getByText('button')).not.toBeFalsy(); + + // check element is NOT present with `queryBy*` + expect(screen.queryByText('loading')).not.toBeInTheDocument(); + expect(screen.queryAllByText('loading')[8]).not.toBeTruthy(); + expect(screen.queryByText('loading')).toBeNull(); + expect(screen.queryAllByText('loading')[6]).toBeNull(); + expect(screen.queryByText('loading')).toBeFalsy(); + + // `findBy*` queries are out of the scope for this rule + const button = await screen.findByText('submit'); + expect(button).toBeInTheDocument(); }); ``` @@ -70,13 +70,13 @@ test('some test', async () => { ```json { - "testing-library/prefer-presence-queries": [ - 2, - { - "presence": true, - "absence": false - } - ] + "testing-library/prefer-presence-queries": [ + 2, + { + "presence": true, + "absence": false + } + ] } ``` diff --git a/docs/rules/prefer-user-event.md b/docs/rules/prefer-user-event.md index f2e0210e..8508b5b6 100644 --- a/docs/rules/prefer-user-event.md +++ b/docs/rules/prefer-user-event.md @@ -70,14 +70,14 @@ An example looks like this ```json { - "rules": { - "prefer-user-event": [ - "error", - { - "allowedMethods": ["click", "change"] - } - ] - } + "rules": { + "prefer-user-event": [ + "error", + { + "allowedMethods": ["click", "change"] + } + ] + } } ``` diff --git a/docs/rules/prefer-wait-for.md b/docs/rules/prefer-wait-for.md index ee82769c..449548fd 100644 --- a/docs/rules/prefer-wait-for.md +++ b/docs/rules/prefer-wait-for.md @@ -21,23 +21,23 @@ 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 }); + 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 }); + await tl.wait(); + await tl.wait(() => {}); + await tl.waitForElement(() => {}); + await tl.waitForDomChange(); + await tl.waitForDomChange(mutationObserverOptions); + await tl.waitForDomChange({ timeout: 100 }); }; ``` @@ -47,21 +47,21 @@ Examples of **correct** code for this rule: 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(() => {}); + // new waitFor method + await waitFor(() => {}); - // previous waitForElementToBeRemoved is not deprecated - await waitForElementToBeRemoved(() => {}); + // 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(() => {}); + // new waitFor method + await tl.waitFor(() => {}); - // previous waitForElementToBeRemoved is not deprecated - await tl.waitForElementToBeRemoved(() => {}); + // previous waitForElementToBeRemoved is not deprecated + await tl.waitForElementToBeRemoved(() => {}); }; ``` diff --git a/jest.config.js b/jest.config.js index a595d520..150a0de5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,14 +1,14 @@ module.exports = { - testMatch: ['**/tests/**/*.test.ts'], - transform: { - '^.+\\.tsx?$': 'ts-jest', - }, - coverageThreshold: { - global: { - branches: 90, - functions: 90, - lines: 90, - statements: 90, - }, - }, + testMatch: ['**/tests/**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + coverageThreshold: { + global: { + branches: 90, + functions: 90, + lines: 90, + statements: 90, + }, + }, }; diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index af9b277c..6f4388b8 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -3,25 +3,25 @@ // YOU CAN REGENERATE IT USING npm run generate:configs export = { - plugins: ['testing-library'], - rules: { - 'testing-library/await-async-query': 'error', - '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-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-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', - 'testing-library/prefer-find-by': 'error', - 'testing-library/prefer-presence-queries': 'error', - 'testing-library/prefer-query-by-disappearance': 'error', - 'testing-library/prefer-screen-queries': 'error', - 'testing-library/render-result-naming-convention': 'error', - }, + plugins: ['testing-library'], + rules: { + 'testing-library/await-async-query': 'error', + '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-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-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', + 'testing-library/prefer-find-by': 'error', + 'testing-library/prefer-presence-queries': 'error', + 'testing-library/prefer-query-by-disappearance': 'error', + 'testing-library/prefer-screen-queries': 'error', + 'testing-library/render-result-naming-convention': 'error', + }, }; diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index ce153733..83add602 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -3,19 +3,19 @@ // YOU CAN REGENERATE IT USING npm run generate:configs export = { - plugins: ['testing-library'], - rules: { - 'testing-library/await-async-query': 'error', - 'testing-library/await-async-utils': 'error', - 'testing-library/no-await-sync-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', - 'testing-library/no-wait-for-side-effects': 'error', - 'testing-library/no-wait-for-snapshot': 'error', - 'testing-library/prefer-find-by': 'error', - 'testing-library/prefer-presence-queries': 'error', - 'testing-library/prefer-query-by-disappearance': 'error', - 'testing-library/prefer-screen-queries': 'error', - }, + plugins: ['testing-library'], + rules: { + 'testing-library/await-async-query': 'error', + 'testing-library/await-async-utils': 'error', + 'testing-library/no-await-sync-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', + 'testing-library/no-wait-for-side-effects': 'error', + 'testing-library/no-wait-for-snapshot': 'error', + 'testing-library/prefer-find-by': 'error', + 'testing-library/prefer-presence-queries': 'error', + 'testing-library/prefer-query-by-disappearance': 'error', + 'testing-library/prefer-screen-queries': 'error', + }, }; diff --git a/lib/configs/index.ts b/lib/configs/index.ts index c9edb204..d312381b 100644 --- a/lib/configs/index.ts +++ b/lib/configs/index.ts @@ -3,9 +3,9 @@ import { join } from 'path'; import type { TSESLint } from '@typescript-eslint/utils'; import { - importDefault, - SUPPORTED_TESTING_FRAMEWORKS, - SupportedTestingFramework, + importDefault, + SUPPORTED_TESTING_FRAMEWORKS, + SupportedTestingFramework, } from '../utils'; export type LinterConfigRules = Record; @@ -13,12 +13,12 @@ export type LinterConfigRules = Record; const configsDir = __dirname; const getConfigForFramework = (framework: SupportedTestingFramework) => - importDefault(join(configsDir, framework)); + importDefault(join(configsDir, framework)); export default SUPPORTED_TESTING_FRAMEWORKS.reduce( - (allConfigs, framework) => ({ - ...allConfigs, - [framework]: getConfigForFramework(framework), - }), - {} + (allConfigs, framework) => ({ + ...allConfigs, + [framework]: getConfigForFramework(framework), + }), + {} ) as Record; diff --git a/lib/configs/marko.ts b/lib/configs/marko.ts index 3df1ce9c..d5dc1311 100644 --- a/lib/configs/marko.ts +++ b/lib/configs/marko.ts @@ -3,27 +3,27 @@ // YOU CAN REGENERATE IT USING npm run generate:configs export = { - plugins: ['testing-library'], - rules: { - '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', - '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-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', - 'testing-library/prefer-find-by': 'error', - 'testing-library/prefer-presence-queries': 'error', - 'testing-library/prefer-query-by-disappearance': 'error', - 'testing-library/prefer-screen-queries': 'error', - 'testing-library/render-result-naming-convention': 'error', - }, + plugins: ['testing-library'], + rules: { + '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', + '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-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', + 'testing-library/prefer-find-by': 'error', + 'testing-library/prefer-presence-queries': 'error', + 'testing-library/prefer-query-by-disappearance': 'error', + 'testing-library/prefer-screen-queries': 'error', + 'testing-library/render-result-naming-convention': 'error', + }, }; diff --git a/lib/configs/react.ts b/lib/configs/react.ts index 5e2bc966..066d9fd7 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -3,26 +3,26 @@ // YOU CAN REGENERATE IT USING npm run generate:configs export = { - plugins: ['testing-library'], - rules: { - 'testing-library/await-async-query': 'error', - '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-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-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', - 'testing-library/prefer-find-by': 'error', - 'testing-library/prefer-presence-queries': 'error', - 'testing-library/prefer-query-by-disappearance': 'error', - 'testing-library/prefer-screen-queries': 'error', - 'testing-library/render-result-naming-convention': 'error', - }, + plugins: ['testing-library'], + rules: { + 'testing-library/await-async-query': 'error', + '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-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-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', + 'testing-library/prefer-find-by': 'error', + 'testing-library/prefer-presence-queries': 'error', + 'testing-library/prefer-query-by-disappearance': 'error', + 'testing-library/prefer-screen-queries': 'error', + 'testing-library/render-result-naming-convention': 'error', + }, }; diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index cf9e42bb..2a3b2d2b 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -3,26 +3,26 @@ // YOU CAN REGENERATE IT USING npm run generate:configs export = { - plugins: ['testing-library'], - rules: { - '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', - '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-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', - 'testing-library/prefer-find-by': 'error', - 'testing-library/prefer-presence-queries': 'error', - 'testing-library/prefer-query-by-disappearance': 'error', - 'testing-library/prefer-screen-queries': 'error', - 'testing-library/render-result-naming-convention': 'error', - }, + plugins: ['testing-library'], + rules: { + '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', + '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-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', + 'testing-library/prefer-find-by': 'error', + 'testing-library/prefer-presence-queries': 'error', + 'testing-library/prefer-query-by-disappearance': 'error', + 'testing-library/prefer-screen-queries': 'error', + 'testing-library/render-result-naming-convention': 'error', + }, }; diff --git a/lib/create-testing-library-rule/detect-testing-library-utils.ts b/lib/create-testing-library-rule/detect-testing-library-utils.ts index a962ceed..93922a06 100644 --- a/lib/create-testing-library-rule/detect-testing-library-utils.ts +++ b/lib/create-testing-library-rule/detect-testing-library-utils.ts @@ -1,55 +1,55 @@ import { ASTUtils, TSESLint, TSESTree } from '@typescript-eslint/utils'; import { - findClosestVariableDeclaratorNode, - findImportSpecifier, - getAssertNodeInfo, - getDeepestIdentifierNode, - getImportModuleName, - getPropertyIdentifierNode, - getReferenceNode, - hasImportMatch, - ImportModuleNode, - isCallExpression, - isImportDeclaration, - isImportDefaultSpecifier, - isImportSpecifier, - isLiteral, - isMemberExpression, + findClosestVariableDeclaratorNode, + findImportSpecifier, + getAssertNodeInfo, + getDeepestIdentifierNode, + getImportModuleName, + getPropertyIdentifierNode, + getReferenceNode, + hasImportMatch, + ImportModuleNode, + isCallExpression, + isImportDeclaration, + isImportDefaultSpecifier, + isImportSpecifier, + isLiteral, + isMemberExpression, } from '../node-utils'; import { - ABSENCE_MATCHERS, - ALL_QUERIES_COMBINATIONS, - ASYNC_UTILS, - DEBUG_UTILS, - PRESENCE_MATCHERS, + ABSENCE_MATCHERS, + ALL_QUERIES_COMBINATIONS, + ASYNC_UTILS, + DEBUG_UTILS, + PRESENCE_MATCHERS, } from '../utils'; const SETTING_OPTION_OFF = 'off' as const; export type TestingLibrarySettings = { - 'testing-library/utils-module'?: string | typeof SETTING_OPTION_OFF; - 'testing-library/custom-renders'?: string[] | typeof SETTING_OPTION_OFF; - 'testing-library/custom-queries'?: string[] | typeof SETTING_OPTION_OFF; + 'testing-library/utils-module'?: string | typeof SETTING_OPTION_OFF; + 'testing-library/custom-renders'?: string[] | typeof SETTING_OPTION_OFF; + 'testing-library/custom-queries'?: string[] | typeof SETTING_OPTION_OFF; }; export type TestingLibraryContext< - TOptions extends readonly unknown[], - TMessageIds extends string + TOptions extends readonly unknown[], + TMessageIds extends string > = Readonly< - TSESLint.RuleContext & { - settings: TestingLibrarySettings; - } + TSESLint.RuleContext & { + settings: TestingLibrarySettings; + } >; export type EnhancedRuleCreate< - TOptions extends readonly unknown[], - TMessageIds extends string, - TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener + TOptions extends readonly unknown[], + TMessageIds extends string, + TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener > = ( - context: TestingLibraryContext, - optionsWithDefault: Readonly, - detectionHelpers: Readonly + context: TestingLibraryContext, + optionsWithDefault: Readonly, + detectionHelpers: Readonly ) => TRuleListener; // Helpers methods @@ -67,62 +67,62 @@ type IsQueryFn = (node: TSESTree.Identifier) => boolean; type IsCustomQueryFn = (node: TSESTree.Identifier) => boolean; type IsBuiltInQueryFn = (node: TSESTree.Identifier) => boolean; type IsAsyncUtilFn = ( - node: TSESTree.Identifier, - validNames?: readonly typeof ASYNC_UTILS[number][] + node: TSESTree.Identifier, + validNames?: readonly typeof ASYNC_UTILS[number][] ) => boolean; type IsFireEventMethodFn = (node: TSESTree.Identifier) => boolean; type IsUserEventMethodFn = (node: TSESTree.Identifier) => boolean; type IsRenderUtilFn = (node: TSESTree.Identifier) => boolean; type IsCreateEventUtil = ( - node: TSESTree.CallExpression | TSESTree.Identifier + node: TSESTree.CallExpression | TSESTree.Identifier ) => boolean; type IsRenderVariableDeclaratorFn = ( - node: TSESTree.VariableDeclarator + node: TSESTree.VariableDeclarator ) => boolean; type IsDebugUtilFn = ( - identifierNode: TSESTree.Identifier, - validNames?: ReadonlyArray + identifierNode: TSESTree.Identifier, + validNames?: ReadonlyArray ) => boolean; type IsPresenceAssertFn = (node: TSESTree.MemberExpression) => boolean; type IsAbsenceAssertFn = (node: TSESTree.MemberExpression) => boolean; type CanReportErrorsFn = () => boolean; type FindImportedTestingLibraryUtilSpecifierFn = ( - specifierName: string + specifierName: string ) => TSESTree.Identifier | TSESTree.ImportClause | undefined; type IsNodeComingFromTestingLibraryFn = ( - node: TSESTree.Identifier | TSESTree.MemberExpression + node: TSESTree.Identifier | TSESTree.MemberExpression ) => boolean; export interface DetectionHelpers { - getTestingLibraryImportNode: GetTestingLibraryImportNodeFn; - getCustomModuleImportNode: GetCustomModuleImportNodeFn; - getTestingLibraryImportName: GetTestingLibraryImportNameFn; - getCustomModuleImportName: GetCustomModuleImportNameFn; - isTestingLibraryImported: IsTestingLibraryImportedFn; - isTestingLibraryUtil: (node: TSESTree.Identifier) => boolean; - isGetQueryVariant: IsGetQueryVariantFn; - isQueryQueryVariant: IsQueryQueryVariantFn; - isFindQueryVariant: IsFindQueryVariantFn; - isSyncQuery: IsSyncQueryFn; - isAsyncQuery: IsAsyncQueryFn; - isQuery: IsQueryFn; - isCustomQuery: IsCustomQueryFn; - isBuiltInQuery: IsBuiltInQueryFn; - isAsyncUtil: IsAsyncUtilFn; - isFireEventUtil: (node: TSESTree.Identifier) => boolean; - isUserEventUtil: (node: TSESTree.Identifier) => boolean; - isFireEventMethod: IsFireEventMethodFn; - isUserEventMethod: IsUserEventMethodFn; - isRenderUtil: IsRenderUtilFn; - isCreateEventUtil: IsCreateEventUtil; - isRenderVariableDeclarator: IsRenderVariableDeclaratorFn; - isDebugUtil: IsDebugUtilFn; - isActUtil: (node: TSESTree.Identifier) => boolean; - isPresenceAssert: IsPresenceAssertFn; - isAbsenceAssert: IsAbsenceAssertFn; - canReportErrors: CanReportErrorsFn; - findImportedTestingLibraryUtilSpecifier: FindImportedTestingLibraryUtilSpecifierFn; - isNodeComingFromTestingLibrary: IsNodeComingFromTestingLibraryFn; + getTestingLibraryImportNode: GetTestingLibraryImportNodeFn; + getCustomModuleImportNode: GetCustomModuleImportNodeFn; + getTestingLibraryImportName: GetTestingLibraryImportNameFn; + getCustomModuleImportName: GetCustomModuleImportNameFn; + isTestingLibraryImported: IsTestingLibraryImportedFn; + isTestingLibraryUtil: (node: TSESTree.Identifier) => boolean; + isGetQueryVariant: IsGetQueryVariantFn; + isQueryQueryVariant: IsQueryQueryVariantFn; + isFindQueryVariant: IsFindQueryVariantFn; + isSyncQuery: IsSyncQueryFn; + isAsyncQuery: IsAsyncQueryFn; + isQuery: IsQueryFn; + isCustomQuery: IsCustomQueryFn; + isBuiltInQuery: IsBuiltInQueryFn; + isAsyncUtil: IsAsyncUtilFn; + isFireEventUtil: (node: TSESTree.Identifier) => boolean; + isUserEventUtil: (node: TSESTree.Identifier) => boolean; + isFireEventMethod: IsFireEventMethodFn; + isUserEventMethod: IsUserEventMethodFn; + isRenderUtil: IsRenderUtilFn; + isCreateEventUtil: IsCreateEventUtil; + isRenderVariableDeclarator: IsRenderVariableDeclaratorFn; + isDebugUtil: IsDebugUtilFn; + isActUtil: (node: TSESTree.Identifier) => boolean; + isPresenceAssert: IsPresenceAssertFn; + isAbsenceAssert: IsAbsenceAssertFn; + canReportErrors: CanReportErrorsFn; + findImportedTestingLibraryUtilSpecifier: FindImportedTestingLibraryUtilSpecifierFn; + isNodeComingFromTestingLibrary: IsNodeComingFromTestingLibraryFn; } const USER_EVENT_PACKAGE = '@testing-library/user-event'; @@ -133,983 +133,983 @@ const USER_EVENT_NAME = 'userEvent'; const RENDER_NAME = 'render'; export type DetectionOptions = { - /** - * If true, force `detectTestingLibraryUtils` to skip `canReportErrors` - * so it doesn't opt-out rule listener. - * - * Useful when some rule apply to files other than testing ones - * (e.g. `consistent-data-testid`) - */ - skipRuleReportingCheck: boolean; + /** + * If true, force `detectTestingLibraryUtils` to skip `canReportErrors` + * so it doesn't opt-out rule listener. + * + * Useful when some rule apply to files other than testing ones + * (e.g. `consistent-data-testid`) + */ + skipRuleReportingCheck: boolean; }; /** * Enhances a given rule `create` with helpers to detect Testing Library utils. */ export function detectTestingLibraryUtils< - TOptions extends readonly unknown[], - TMessageIds extends string, - TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener + TOptions extends readonly unknown[], + TMessageIds extends string, + TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener >( - ruleCreate: EnhancedRuleCreate, - { skipRuleReportingCheck = false }: Partial = {} + ruleCreate: EnhancedRuleCreate, + { skipRuleReportingCheck = false }: Partial = {} ) { - return ( - context: TestingLibraryContext, - optionsWithDefault: Readonly - ): TSESLint.RuleListener => { - let importedTestingLibraryNode: ImportModuleNode | null = null; - let importedCustomModuleNode: ImportModuleNode | null = null; - let importedUserEventLibraryNode: ImportModuleNode | null = null; - let importedReactDomTestUtilsNode: ImportModuleNode | null = null; - - // Init options based on shared ESLint settings - const customModuleSetting = - context.settings['testing-library/utils-module']; - const customRendersSetting = - context.settings['testing-library/custom-renders']; - const customQueriesSetting = - context.settings['testing-library/custom-queries']; - - /** - * Small method to extract common checks to determine whether a node is - * related to Testing Library or not. - * - * To determine whether a node is a valid Testing Library util, there are - * two conditions to match: - * - it's named in a particular way (decided by given callback) - * - it's imported from valid Testing Library module (depends on aggressive - * reporting) - */ - function isPotentialTestingLibraryFunction( - node: TSESTree.Identifier | null | undefined, - isPotentialFunctionCallback: ( - identifierNodeName: string, - originalNodeName?: string - ) => boolean - ): boolean { - if (!node) { - return false; - } - - const referenceNode = getReferenceNode(node); - const referenceNodeIdentifier = getPropertyIdentifierNode(referenceNode); - - if (!referenceNodeIdentifier) { - return false; - } - - const importedUtilSpecifier = getTestingLibraryImportedUtilSpecifier( - referenceNodeIdentifier - ); - - const originalNodeName = - isImportSpecifier(importedUtilSpecifier) && - importedUtilSpecifier.local.name !== importedUtilSpecifier.imported.name - ? importedUtilSpecifier.imported.name - : undefined; - - if (!isPotentialFunctionCallback(node.name, originalNodeName)) { - return false; - } - - if (isAggressiveModuleReportingEnabled()) { - return true; - } - - return isNodeComingFromTestingLibrary(referenceNodeIdentifier); - } - - /** - * Determines whether aggressive module reporting is enabled or not. - * - * This aggressive reporting mechanism is considered as enabled when custom - * module is not set, so we need to assume everything matching Testing - * Library utils is related to Testing Library no matter from where module - * they are coming from. Otherwise, this aggressive reporting mechanism is - * opted-out in favour to report only those utils coming from Testing - * Library package or custom module set up on settings. - */ - const isAggressiveModuleReportingEnabled = () => !customModuleSetting; - - /** - * Determines whether aggressive render reporting is enabled or not. - * - * This aggressive reporting mechanism is considered as enabled when custom - * renders are not set, so we need to assume every method containing - * "render" is a valid Testing Library `render`. Otherwise, this aggressive - * reporting mechanism is opted-out in favour to report only `render` or - * names set up on custom renders setting. - */ - const isAggressiveRenderReportingEnabled = (): boolean => { - const isSwitchedOff = customRendersSetting === SETTING_OPTION_OFF; - const hasCustomOptions = - Array.isArray(customRendersSetting) && customRendersSetting.length > 0; - - return !isSwitchedOff && !hasCustomOptions; - }; - - /** - * Determines whether Aggressive Reporting for queries is enabled or not. - * - * This Aggressive Reporting mechanism is considered as enabled when custom-queries setting is not set, - * so the plugin needs to report both built-in and custom queries. - * Otherwise, this Aggressive Reporting mechanism is opted-out in favour of reporting only built-in queries + those - * indicated in custom-queries setting. - */ - const isAggressiveQueryReportingEnabled = (): boolean => { - const isSwitchedOff = customQueriesSetting === SETTING_OPTION_OFF; - const hasCustomOptions = - Array.isArray(customQueriesSetting) && customQueriesSetting.length > 0; - - return !isSwitchedOff && !hasCustomOptions; - }; - - const getCustomModule = (): string | undefined => { - if ( - !isAggressiveModuleReportingEnabled() && - customModuleSetting !== SETTING_OPTION_OFF - ) { - return customModuleSetting; - } - return undefined; - }; - - const getCustomRenders = (): string[] => { - if ( - !isAggressiveRenderReportingEnabled() && - customRendersSetting !== SETTING_OPTION_OFF - ) { - return customRendersSetting as string[]; - } - - return []; - }; - - const getCustomQueries = (): string[] => { - if ( - !isAggressiveQueryReportingEnabled() && - customQueriesSetting !== SETTING_OPTION_OFF - ) { - return customQueriesSetting as string[]; - } - - return []; - }; - - // Helpers for Testing Library detection. - const getTestingLibraryImportNode: GetTestingLibraryImportNodeFn = () => { - return importedTestingLibraryNode; - }; - - const getCustomModuleImportNode: GetCustomModuleImportNodeFn = () => { - return importedCustomModuleNode; - }; - - const getTestingLibraryImportName: GetTestingLibraryImportNameFn = () => { - return getImportModuleName(importedTestingLibraryNode); - }; - - const getCustomModuleImportName: GetCustomModuleImportNameFn = () => { - return getImportModuleName(importedCustomModuleNode); - }; - - /** - * Determines whether Testing Library utils are imported or not for - * current file being analyzed. - * - * By default, it is ALWAYS considered as imported. This is what we call - * "aggressive reporting" so we don't miss TL utils reexported from - * custom modules. - * - * However, there is a setting to customize the module where TL utils can - * be imported from: "testing-library/utils-module". If this setting is enabled, - * then this method will return `true` ONLY IF a testing-library package - * or custom module are imported. - */ - const isTestingLibraryImported: IsTestingLibraryImportedFn = ( - isStrict = false - ) => { - const isSomeModuleImported = - !!importedTestingLibraryNode || !!importedCustomModuleNode; - - return ( - (!isStrict && isAggressiveModuleReportingEnabled()) || - isSomeModuleImported - ); - }; - - /** - * Determines whether a given node is a reportable query, - * either a built-in or a custom one. - * - * Depending on Aggressive Query Reporting setting, custom queries will be - * reportable or not. - */ - const isQuery: IsQueryFn = (node) => { - const hasQueryPattern = /^(get|query|find)(All)?By.+$/.test(node.name); - if (!hasQueryPattern) { - return false; - } - - if (isAggressiveQueryReportingEnabled()) { - return true; - } - - const customQueries = getCustomQueries(); - const isBuiltInQuery = ALL_QUERIES_COMBINATIONS.includes(node.name); - const isReportableCustomQuery = customQueries.some((pattern) => - new RegExp(pattern).test(node.name) - ); - return isBuiltInQuery || isReportableCustomQuery; - }; - - /** - * Determines whether a given node is `get*` query variant or not. - */ - const isGetQueryVariant: IsGetQueryVariantFn = (node) => { - return isQuery(node) && node.name.startsWith('get'); - }; - - /** - * Determines whether a given node is `query*` query variant or not. - */ - const isQueryQueryVariant: IsQueryQueryVariantFn = (node) => { - return isQuery(node) && node.name.startsWith('query'); - }; - - /** - * Determines whether a given node is `find*` query variant or not. - */ - const isFindQueryVariant: IsFindQueryVariantFn = (node) => { - return isQuery(node) && node.name.startsWith('find'); - }; - - /** - * Determines whether a given node is sync query or not. - */ - const isSyncQuery: IsSyncQueryFn = (node) => { - return isGetQueryVariant(node) || isQueryQueryVariant(node); - }; - - /** - * Determines whether a given node is async query or not. - */ - const isAsyncQuery: IsAsyncQueryFn = (node) => { - return isFindQueryVariant(node); - }; - - const isCustomQuery: IsCustomQueryFn = (node) => { - return isQuery(node) && !ALL_QUERIES_COMBINATIONS.includes(node.name); - }; - - const isBuiltInQuery = (node: TSESTree.Identifier): boolean => { - return isQuery(node) && ALL_QUERIES_COMBINATIONS.includes(node.name); - }; - - /** - * Determines whether a given node is a valid async util or not. - * - * A node will be interpreted as a valid async util based on two conditions: - * the name matches with some Testing Library async util, and the node is - * coming from Testing Library module. - * - * The latter depends on Aggressive module reporting: - * if enabled, then it doesn't matter from where the given node was imported - * from as it will be considered part of Testing Library. - * Otherwise, it means `custom-module` has been set up, so only those nodes - * coming from Testing Library will be considered as valid. - */ - const isAsyncUtil: IsAsyncUtilFn = (node, validNames = ASYNC_UTILS) => { - return isPotentialTestingLibraryFunction( - node, - (identifierNodeName, originalNodeName) => { - return ( - (validNames as string[]).includes(identifierNodeName) || - (!!originalNodeName && - (validNames as string[]).includes(originalNodeName)) - ); - } - ); - }; - - /** - * Determines whether a given node is fireEvent util itself or not. - * - * Not to be confused with {@link isFireEventMethod} - */ - const isFireEventUtil = (node: TSESTree.Identifier): boolean => { - return isPotentialTestingLibraryFunction( - node, - (identifierNodeName, originalNodeName) => { - return [identifierNodeName, originalNodeName].includes('fireEvent'); - } - ); - }; - - /** - * Determines whether a given node is userEvent util itself or not. - * - * Not to be confused with {@link isUserEventMethod} - */ - const isUserEventUtil = (node: TSESTree.Identifier): boolean => { - const userEvent = findImportedUserEventSpecifier(); - let userEventName: string | undefined; - - if (userEvent) { - userEventName = userEvent.name; - } else if (isAggressiveModuleReportingEnabled()) { - userEventName = USER_EVENT_NAME; - } - - if (!userEventName) { - return false; - } - - return node.name === userEventName; - }; - - /** - * Determines whether a given node is fireEvent method or not - */ - // eslint-disable-next-line complexity - const isFireEventMethod: IsFireEventMethodFn = (node) => { - const fireEventUtil = - findImportedTestingLibraryUtilSpecifier(FIRE_EVENT_NAME); - let fireEventUtilName: string | undefined; - - if (fireEventUtil) { - fireEventUtilName = ASTUtils.isIdentifier(fireEventUtil) - ? fireEventUtil.name - : fireEventUtil.local.name; - } else if (isAggressiveModuleReportingEnabled()) { - fireEventUtilName = FIRE_EVENT_NAME; - } - - if (!fireEventUtilName) { - return false; - } - - const parentMemberExpression: TSESTree.MemberExpression | undefined = - node.parent && isMemberExpression(node.parent) - ? node.parent - : undefined; - - const parentCallExpression: TSESTree.CallExpression | undefined = - node.parent && isCallExpression(node.parent) ? node.parent : undefined; - - if (!parentMemberExpression && !parentCallExpression) { - return false; - } - - // check fireEvent('method', node) usage - if (parentCallExpression) { - return [fireEventUtilName, FIRE_EVENT_NAME].includes(node.name); - } - - // we know it's defined at this point, but TS seems to think it is not - // so here I'm enforcing it once in order to avoid using "!" operator every time - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const definedParentMemberExpression = parentMemberExpression!; - - // check fireEvent.click() usage - const regularCall = - ASTUtils.isIdentifier(definedParentMemberExpression.object) && - isCallExpression(definedParentMemberExpression.parent) && - definedParentMemberExpression.object.name === fireEventUtilName && - node.name !== FIRE_EVENT_NAME && - node.name !== fireEventUtilName; - - // check testingLibraryUtils.fireEvent.click() usage - const wildcardCall = - isMemberExpression(definedParentMemberExpression.object) && - ASTUtils.isIdentifier(definedParentMemberExpression.object.object) && - definedParentMemberExpression.object.object.name === - fireEventUtilName && - ASTUtils.isIdentifier(definedParentMemberExpression.object.property) && - definedParentMemberExpression.object.property.name === - FIRE_EVENT_NAME && - node.name !== FIRE_EVENT_NAME && - node.name !== fireEventUtilName; - - // check testingLibraryUtils.fireEvent('click') - const wildcardCallWithCallExpression = - ASTUtils.isIdentifier(definedParentMemberExpression.object) && - definedParentMemberExpression.object.name === fireEventUtilName && - ASTUtils.isIdentifier(definedParentMemberExpression.property) && - definedParentMemberExpression.property.name === FIRE_EVENT_NAME && - !isMemberExpression(definedParentMemberExpression.parent) && - node.name === FIRE_EVENT_NAME && - node.name !== fireEventUtilName; - - return regularCall || wildcardCall || wildcardCallWithCallExpression; - }; - - const isUserEventMethod: IsUserEventMethodFn = (node) => { - const userEvent = findImportedUserEventSpecifier(); - let userEventName: string | undefined; - - if (userEvent) { - userEventName = userEvent.name; - } else if (isAggressiveModuleReportingEnabled()) { - userEventName = USER_EVENT_NAME; - } - - if (!userEventName) { - return false; - } - - const parentMemberExpression: TSESTree.MemberExpression | undefined = - node.parent && isMemberExpression(node.parent) - ? node.parent - : undefined; - - if (!parentMemberExpression) { - return false; - } - - // make sure that given node it's not userEvent object itself - if ( - [userEventName, USER_EVENT_NAME].includes(node.name) || - (ASTUtils.isIdentifier(parentMemberExpression.object) && - parentMemberExpression.object.name === node.name) - ) { - return false; - } - - // check userEvent.click() usage - return ( - ASTUtils.isIdentifier(parentMemberExpression.object) && - parentMemberExpression.object.name === userEventName - ); - }; - - /** - * Determines whether a given node is a valid render util or not. - * - * A node will be interpreted as a valid render based on two conditions: - * the name matches with a valid "render" option, and the node is coming - * from Testing Library module. This depends on: - * - * - Aggressive render reporting: if enabled, then every node name - * containing "render" will be assumed as Testing Library render util. - * Otherwise, it means `custom-modules` has been set up, so only those nodes - * named as "render" or some of the `custom-modules` options will be - * considered as Testing Library render util. - * - Aggressive module reporting: if enabled, then it doesn't matter from - * where the given node was imported from as it will be considered part of - * Testing Library. Otherwise, it means `custom-module` has been set up, so - * only those nodes coming from Testing Library will be considered as valid. - */ - const isRenderUtil: IsRenderUtilFn = (node) => - isPotentialTestingLibraryFunction( - node, - (identifierNodeName, originalNodeName) => { - if (isAggressiveRenderReportingEnabled()) { - return identifierNodeName.toLowerCase().includes(RENDER_NAME); - } - - return [RENDER_NAME, ...getCustomRenders()].some( - (validRenderName) => - validRenderName === identifierNodeName || - (Boolean(originalNodeName) && - validRenderName === originalNodeName) - ); - } - ); - - const isCreateEventUtil: IsCreateEventUtil = (node) => { - const isCreateEventCallback = ( - identifierNodeName: string, - originalNodeName?: string - ) => [identifierNodeName, originalNodeName].includes(CREATE_EVENT_NAME); - if ( - isCallExpression(node) && - isMemberExpression(node.callee) && - ASTUtils.isIdentifier(node.callee.object) - ) { - return isPotentialTestingLibraryFunction( - node.callee.object, - isCreateEventCallback - ); - } - - if ( - isCallExpression(node) && - isMemberExpression(node.callee) && - isMemberExpression(node.callee.object) && - ASTUtils.isIdentifier(node.callee.object.property) - ) { - return isPotentialTestingLibraryFunction( - node.callee.object.property, - isCreateEventCallback - ); - } - const identifier = getDeepestIdentifierNode(node); - return isPotentialTestingLibraryFunction( - identifier, - isCreateEventCallback - ); - }; - - const isRenderVariableDeclarator: IsRenderVariableDeclaratorFn = (node) => { - if (!node.init) { - return false; - } - const initIdentifierNode = getDeepestIdentifierNode(node.init); - - if (!initIdentifierNode) { - return false; - } - - return isRenderUtil(initIdentifierNode); - }; - - const isDebugUtil: IsDebugUtilFn = ( - identifierNode, - validNames = DEBUG_UTILS - ) => { - const isBuiltInConsole = - isMemberExpression(identifierNode.parent) && - ASTUtils.isIdentifier(identifierNode.parent.object) && - identifierNode.parent.object.name === 'console'; - - return ( - !isBuiltInConsole && - isPotentialTestingLibraryFunction( - identifierNode, - (identifierNodeName, originalNodeName) => { - return ( - (validNames as string[]).includes(identifierNodeName) || - (!!originalNodeName && - (validNames as string[]).includes(originalNodeName)) - ); - } - ) - ); - }; - - /** - * Determines whether a given node is some reportable `act` util. - * - * An `act` is reportable if some of these conditions is met: - * - it's related to Testing Library module (this depends on Aggressive Reporting) - * - it's related to React DOM Test Utils - */ - const isActUtil = (node: TSESTree.Identifier): boolean => { - const isTestingLibraryAct = isPotentialTestingLibraryFunction( - node, - (identifierNodeName, originalNodeName) => { - return [identifierNodeName, originalNodeName] - .filter(Boolean) - .includes('act'); - } - ); - - const isReactDomTestUtilsAct = (() => { - if (!importedReactDomTestUtilsNode) { - return false; - } - const referenceNode = getReferenceNode(node); - const referenceNodeIdentifier = - getPropertyIdentifierNode(referenceNode); - if (!referenceNodeIdentifier) { - return false; - } - - const importedUtilSpecifier = findImportSpecifier( - node.name, - importedReactDomTestUtilsNode - ); - if (!importedUtilSpecifier) { - return false; - } - - const importDeclaration = (() => { - if (isImportDeclaration(importedUtilSpecifier.parent)) { - return importedUtilSpecifier.parent; - } - - const variableDeclarator = findClosestVariableDeclaratorNode( - importedUtilSpecifier - ); - - if (isCallExpression(variableDeclarator?.init)) { - return variableDeclarator?.init; - } - - return undefined; - })(); - if (!importDeclaration) { - return false; - } - - const importDeclarationName = getImportModuleName(importDeclaration); - if (!importDeclarationName) { - return false; - } - - if (importDeclarationName !== REACT_DOM_TEST_UTILS_PACKAGE) { - return false; - } - - return hasImportMatch( - importedUtilSpecifier, - referenceNodeIdentifier.name - ); - })(); - - return isTestingLibraryAct || isReactDomTestUtilsAct; - }; - - const isTestingLibraryUtil = (node: TSESTree.Identifier): boolean => { - return ( - isAsyncUtil(node) || - isQuery(node) || - isRenderUtil(node) || - isFireEventMethod(node) || - isUserEventMethod(node) || - isActUtil(node) || - isCreateEventUtil(node) - ); - }; - - /** - * Determines whether a given MemberExpression node is a presence assert - * - * Presence asserts could have shape of: - * - expect(element).toBeInTheDocument() - * - expect(element).not.toBeNull() - */ - const isPresenceAssert: IsPresenceAssertFn = (node) => { - const { matcher, isNegated } = getAssertNodeInfo(node); - - if (!matcher) { - return false; - } - - return isNegated - ? ABSENCE_MATCHERS.includes(matcher) - : PRESENCE_MATCHERS.includes(matcher); - }; - - /** - * Determines whether a given MemberExpression node is an absence assert - * - * Absence asserts could have shape of: - * - expect(element).toBeNull() - * - expect(element).not.toBeInTheDocument() - */ - const isAbsenceAssert: IsAbsenceAssertFn = (node) => { - const { matcher, isNegated } = getAssertNodeInfo(node); - - if (!matcher) { - return false; - } - - return isNegated - ? PRESENCE_MATCHERS.includes(matcher) - : ABSENCE_MATCHERS.includes(matcher); - }; - - /** - * Finds the import util specifier related to Testing Library for a given name. - */ - const findImportedTestingLibraryUtilSpecifier: FindImportedTestingLibraryUtilSpecifierFn = - ( - specifierName - ): TSESTree.Identifier | TSESTree.ImportClause | undefined => { - const node = - getCustomModuleImportNode() ?? getTestingLibraryImportNode(); - - if (!node) { - return undefined; - } - - return findImportSpecifier(specifierName, node); - }; - - const findImportedUserEventSpecifier: () => TSESTree.Identifier | null = - () => { - if (!importedUserEventLibraryNode) { - return null; - } - - if (isImportDeclaration(importedUserEventLibraryNode)) { - const userEventIdentifier = - importedUserEventLibraryNode.specifiers.find((specifier) => - isImportDefaultSpecifier(specifier) - ); - - if (userEventIdentifier) { - return userEventIdentifier.local; - } - } else { - if ( - !ASTUtils.isVariableDeclarator(importedUserEventLibraryNode.parent) - ) { - return null; - } - - const requireNode = importedUserEventLibraryNode.parent; - if (!ASTUtils.isIdentifier(requireNode.id)) { - return null; - } - - return requireNode.id; - } - - return null; - }; - - const getTestingLibraryImportedUtilSpecifier = ( - node: TSESTree.Identifier | TSESTree.MemberExpression - ): TSESTree.Identifier | TSESTree.ImportClause | undefined => { - const identifierName: string | undefined = - getPropertyIdentifierNode(node)?.name; - - if (!identifierName) { - return undefined; - } - - return findImportedTestingLibraryUtilSpecifier(identifierName); - }; - - /** - * Determines if file inspected meets all conditions to be reported by rules or not. - */ - const canReportErrors: CanReportErrorsFn = () => { - return skipRuleReportingCheck || isTestingLibraryImported(); - }; - - /** - * Determines whether a node is imported from a valid Testing Library module - * - * This method will try to find any import matching the given node name, - * and also make sure the name is a valid match in case it's been renamed. - */ - const isNodeComingFromTestingLibrary: IsNodeComingFromTestingLibraryFn = ( - node - ) => { - const importNode = getTestingLibraryImportedUtilSpecifier(node); - - if (!importNode) { - return false; - } - - const referenceNode = getReferenceNode(node); - const referenceNodeIdentifier = getPropertyIdentifierNode(referenceNode); - if (!referenceNodeIdentifier) { - return false; - } - - const importDeclaration = (() => { - if (isImportDeclaration(importNode.parent)) { - return importNode.parent; - } - - const variableDeclarator = - findClosestVariableDeclaratorNode(importNode); - - if (isCallExpression(variableDeclarator?.init)) { - return variableDeclarator?.init; - } - - return undefined; - })(); - - if (!importDeclaration) { - return false; - } - - const importDeclarationName = getImportModuleName(importDeclaration); - if (!importDeclarationName) { - return false; - } - - const identifierName: string | undefined = - getPropertyIdentifierNode(node)?.name; - - if (!identifierName) { - return false; - } - - const hasImportElementMatch = hasImportMatch(importNode, identifierName); - const hasImportModuleMatch = - /testing-library/g.test(importDeclarationName) || - (typeof customModuleSetting === 'string' && - importDeclarationName.endsWith(customModuleSetting)); - - return hasImportElementMatch && hasImportModuleMatch; - }; - - const helpers: DetectionHelpers = { - getTestingLibraryImportNode, - getCustomModuleImportNode, - getTestingLibraryImportName, - getCustomModuleImportName, - isTestingLibraryImported, - isTestingLibraryUtil, - isGetQueryVariant, - isQueryQueryVariant, - isFindQueryVariant, - isSyncQuery, - isAsyncQuery, - isQuery, - isCustomQuery, - isBuiltInQuery, - isAsyncUtil, - isFireEventUtil, - isUserEventUtil, - isFireEventMethod, - isUserEventMethod, - isRenderUtil, - isCreateEventUtil, - isRenderVariableDeclarator, - isDebugUtil, - isActUtil, - isPresenceAssert, - isAbsenceAssert, - canReportErrors, - findImportedTestingLibraryUtilSpecifier, - isNodeComingFromTestingLibrary, - }; - - // Instructions for Testing Library detection. - const detectionInstructions: TSESLint.RuleListener = { - /** - * This ImportDeclaration rule listener will check if Testing Library related - * modules are imported. Since imports happen first thing in a file, it's - * safe to use `isImportingTestingLibraryModule` and `isImportingCustomModule` - * since they will have corresponding value already updated when reporting other - * parts of the file. - */ - ImportDeclaration(node: TSESTree.ImportDeclaration) { - if (typeof node.source.value !== 'string') { - return; - } - // check only if testing library import not found yet so we avoid - // to override importedTestingLibraryNode after it's found - if ( - !importedTestingLibraryNode && - /testing-library/g.test(node.source.value) - ) { - importedTestingLibraryNode = node; - } - - // check only if custom module import not found yet so we avoid - // to override importedCustomModuleNode after it's found - const customModule = getCustomModule(); - if ( - customModule && - !importedCustomModuleNode && - node.source.value.endsWith(customModule) - ) { - importedCustomModuleNode = node; - } - - // check only if user-event import not found yet so we avoid - // to override importedUserEventLibraryNode after it's found - if ( - !importedUserEventLibraryNode && - node.source.value === USER_EVENT_PACKAGE - ) { - importedUserEventLibraryNode = node; - } - - // check only if react-dom/test-utils import not found yet so we avoid - // to override importedReactDomTestUtilsNode after it's found - if ( - !importedUserEventLibraryNode && - node.source.value === REACT_DOM_TEST_UTILS_PACKAGE - ) { - importedReactDomTestUtilsNode = node; - } - }, - - // Check if Testing Library related modules are loaded with required. - [`CallExpression > Identifier[name="require"]`]( - node: TSESTree.Identifier - ) { - const callExpression = node.parent as TSESTree.CallExpression; - const { arguments: args } = callExpression; - - if ( - !importedTestingLibraryNode && - args.some( - (arg) => - isLiteral(arg) && - typeof arg.value === 'string' && - /testing-library/g.test(arg.value) - ) - ) { - importedTestingLibraryNode = callExpression; - } - - const customModule = getCustomModule(); - if ( - !importedCustomModuleNode && - args.some( - (arg) => - customModule && - isLiteral(arg) && - typeof arg.value === 'string' && - arg.value.endsWith(customModule) - ) - ) { - importedCustomModuleNode = callExpression; - } - - if ( - !importedCustomModuleNode && - args.some( - (arg) => - isLiteral(arg) && - typeof arg.value === 'string' && - arg.value === USER_EVENT_PACKAGE - ) - ) { - importedUserEventLibraryNode = callExpression; - } - - if ( - !importedReactDomTestUtilsNode && - args.some( - (arg) => - isLiteral(arg) && - typeof arg.value === 'string' && - arg.value === REACT_DOM_TEST_UTILS_PACKAGE - ) - ) { - importedReactDomTestUtilsNode = callExpression; - } - }, - }; - - // update given rule to inject Testing Library detection - const ruleInstructions = ruleCreate(context, optionsWithDefault, helpers); - const enhancedRuleInstructions: TSESLint.RuleListener = {}; - - const allKeys = new Set( - Object.keys(detectionInstructions).concat(Object.keys(ruleInstructions)) - ); - - // Iterate over ALL instructions keys so we can override original rule instructions - // to prevent their execution if conditions to report errors are not met. - allKeys.forEach((instruction) => { - enhancedRuleInstructions[instruction] = (node) => { - if (instruction in detectionInstructions) { - detectionInstructions[instruction]?.(node); - } - - if (canReportErrors() && ruleInstructions[instruction]) { - return ruleInstructions[instruction]?.(node); - } - - return undefined; - }; - }); - - return enhancedRuleInstructions; - }; + return ( + context: TestingLibraryContext, + optionsWithDefault: Readonly + ): TSESLint.RuleListener => { + let importedTestingLibraryNode: ImportModuleNode | null = null; + let importedCustomModuleNode: ImportModuleNode | null = null; + let importedUserEventLibraryNode: ImportModuleNode | null = null; + let importedReactDomTestUtilsNode: ImportModuleNode | null = null; + + // Init options based on shared ESLint settings + const customModuleSetting = + context.settings['testing-library/utils-module']; + const customRendersSetting = + context.settings['testing-library/custom-renders']; + const customQueriesSetting = + context.settings['testing-library/custom-queries']; + + /** + * Small method to extract common checks to determine whether a node is + * related to Testing Library or not. + * + * To determine whether a node is a valid Testing Library util, there are + * two conditions to match: + * - it's named in a particular way (decided by given callback) + * - it's imported from valid Testing Library module (depends on aggressive + * reporting) + */ + function isPotentialTestingLibraryFunction( + node: TSESTree.Identifier | null | undefined, + isPotentialFunctionCallback: ( + identifierNodeName: string, + originalNodeName?: string + ) => boolean + ): boolean { + if (!node) { + return false; + } + + const referenceNode = getReferenceNode(node); + const referenceNodeIdentifier = getPropertyIdentifierNode(referenceNode); + + if (!referenceNodeIdentifier) { + return false; + } + + const importedUtilSpecifier = getTestingLibraryImportedUtilSpecifier( + referenceNodeIdentifier + ); + + const originalNodeName = + isImportSpecifier(importedUtilSpecifier) && + importedUtilSpecifier.local.name !== importedUtilSpecifier.imported.name + ? importedUtilSpecifier.imported.name + : undefined; + + if (!isPotentialFunctionCallback(node.name, originalNodeName)) { + return false; + } + + if (isAggressiveModuleReportingEnabled()) { + return true; + } + + return isNodeComingFromTestingLibrary(referenceNodeIdentifier); + } + + /** + * Determines whether aggressive module reporting is enabled or not. + * + * This aggressive reporting mechanism is considered as enabled when custom + * module is not set, so we need to assume everything matching Testing + * Library utils is related to Testing Library no matter from where module + * they are coming from. Otherwise, this aggressive reporting mechanism is + * opted-out in favour to report only those utils coming from Testing + * Library package or custom module set up on settings. + */ + const isAggressiveModuleReportingEnabled = () => !customModuleSetting; + + /** + * Determines whether aggressive render reporting is enabled or not. + * + * This aggressive reporting mechanism is considered as enabled when custom + * renders are not set, so we need to assume every method containing + * "render" is a valid Testing Library `render`. Otherwise, this aggressive + * reporting mechanism is opted-out in favour to report only `render` or + * names set up on custom renders setting. + */ + const isAggressiveRenderReportingEnabled = (): boolean => { + const isSwitchedOff = customRendersSetting === SETTING_OPTION_OFF; + const hasCustomOptions = + Array.isArray(customRendersSetting) && customRendersSetting.length > 0; + + return !isSwitchedOff && !hasCustomOptions; + }; + + /** + * Determines whether Aggressive Reporting for queries is enabled or not. + * + * This Aggressive Reporting mechanism is considered as enabled when custom-queries setting is not set, + * so the plugin needs to report both built-in and custom queries. + * Otherwise, this Aggressive Reporting mechanism is opted-out in favour of reporting only built-in queries + those + * indicated in custom-queries setting. + */ + const isAggressiveQueryReportingEnabled = (): boolean => { + const isSwitchedOff = customQueriesSetting === SETTING_OPTION_OFF; + const hasCustomOptions = + Array.isArray(customQueriesSetting) && customQueriesSetting.length > 0; + + return !isSwitchedOff && !hasCustomOptions; + }; + + const getCustomModule = (): string | undefined => { + if ( + !isAggressiveModuleReportingEnabled() && + customModuleSetting !== SETTING_OPTION_OFF + ) { + return customModuleSetting; + } + return undefined; + }; + + const getCustomRenders = (): string[] => { + if ( + !isAggressiveRenderReportingEnabled() && + customRendersSetting !== SETTING_OPTION_OFF + ) { + return customRendersSetting as string[]; + } + + return []; + }; + + const getCustomQueries = (): string[] => { + if ( + !isAggressiveQueryReportingEnabled() && + customQueriesSetting !== SETTING_OPTION_OFF + ) { + return customQueriesSetting as string[]; + } + + return []; + }; + + // Helpers for Testing Library detection. + const getTestingLibraryImportNode: GetTestingLibraryImportNodeFn = () => { + return importedTestingLibraryNode; + }; + + const getCustomModuleImportNode: GetCustomModuleImportNodeFn = () => { + return importedCustomModuleNode; + }; + + const getTestingLibraryImportName: GetTestingLibraryImportNameFn = () => { + return getImportModuleName(importedTestingLibraryNode); + }; + + const getCustomModuleImportName: GetCustomModuleImportNameFn = () => { + return getImportModuleName(importedCustomModuleNode); + }; + + /** + * Determines whether Testing Library utils are imported or not for + * current file being analyzed. + * + * By default, it is ALWAYS considered as imported. This is what we call + * "aggressive reporting" so we don't miss TL utils reexported from + * custom modules. + * + * However, there is a setting to customize the module where TL utils can + * be imported from: "testing-library/utils-module". If this setting is enabled, + * then this method will return `true` ONLY IF a testing-library package + * or custom module are imported. + */ + const isTestingLibraryImported: IsTestingLibraryImportedFn = ( + isStrict = false + ) => { + const isSomeModuleImported = + !!importedTestingLibraryNode || !!importedCustomModuleNode; + + return ( + (!isStrict && isAggressiveModuleReportingEnabled()) || + isSomeModuleImported + ); + }; + + /** + * Determines whether a given node is a reportable query, + * either a built-in or a custom one. + * + * Depending on Aggressive Query Reporting setting, custom queries will be + * reportable or not. + */ + const isQuery: IsQueryFn = (node) => { + const hasQueryPattern = /^(get|query|find)(All)?By.+$/.test(node.name); + if (!hasQueryPattern) { + return false; + } + + if (isAggressiveQueryReportingEnabled()) { + return true; + } + + const customQueries = getCustomQueries(); + const isBuiltInQuery = ALL_QUERIES_COMBINATIONS.includes(node.name); + const isReportableCustomQuery = customQueries.some((pattern) => + new RegExp(pattern).test(node.name) + ); + return isBuiltInQuery || isReportableCustomQuery; + }; + + /** + * Determines whether a given node is `get*` query variant or not. + */ + const isGetQueryVariant: IsGetQueryVariantFn = (node) => { + return isQuery(node) && node.name.startsWith('get'); + }; + + /** + * Determines whether a given node is `query*` query variant or not. + */ + const isQueryQueryVariant: IsQueryQueryVariantFn = (node) => { + return isQuery(node) && node.name.startsWith('query'); + }; + + /** + * Determines whether a given node is `find*` query variant or not. + */ + const isFindQueryVariant: IsFindQueryVariantFn = (node) => { + return isQuery(node) && node.name.startsWith('find'); + }; + + /** + * Determines whether a given node is sync query or not. + */ + const isSyncQuery: IsSyncQueryFn = (node) => { + return isGetQueryVariant(node) || isQueryQueryVariant(node); + }; + + /** + * Determines whether a given node is async query or not. + */ + const isAsyncQuery: IsAsyncQueryFn = (node) => { + return isFindQueryVariant(node); + }; + + const isCustomQuery: IsCustomQueryFn = (node) => { + return isQuery(node) && !ALL_QUERIES_COMBINATIONS.includes(node.name); + }; + + const isBuiltInQuery = (node: TSESTree.Identifier): boolean => { + return isQuery(node) && ALL_QUERIES_COMBINATIONS.includes(node.name); + }; + + /** + * Determines whether a given node is a valid async util or not. + * + * A node will be interpreted as a valid async util based on two conditions: + * the name matches with some Testing Library async util, and the node is + * coming from Testing Library module. + * + * The latter depends on Aggressive module reporting: + * if enabled, then it doesn't matter from where the given node was imported + * from as it will be considered part of Testing Library. + * Otherwise, it means `custom-module` has been set up, so only those nodes + * coming from Testing Library will be considered as valid. + */ + const isAsyncUtil: IsAsyncUtilFn = (node, validNames = ASYNC_UTILS) => { + return isPotentialTestingLibraryFunction( + node, + (identifierNodeName, originalNodeName) => { + return ( + (validNames as string[]).includes(identifierNodeName) || + (!!originalNodeName && + (validNames as string[]).includes(originalNodeName)) + ); + } + ); + }; + + /** + * Determines whether a given node is fireEvent util itself or not. + * + * Not to be confused with {@link isFireEventMethod} + */ + const isFireEventUtil = (node: TSESTree.Identifier): boolean => { + return isPotentialTestingLibraryFunction( + node, + (identifierNodeName, originalNodeName) => { + return [identifierNodeName, originalNodeName].includes('fireEvent'); + } + ); + }; + + /** + * Determines whether a given node is userEvent util itself or not. + * + * Not to be confused with {@link isUserEventMethod} + */ + const isUserEventUtil = (node: TSESTree.Identifier): boolean => { + const userEvent = findImportedUserEventSpecifier(); + let userEventName: string | undefined; + + if (userEvent) { + userEventName = userEvent.name; + } else if (isAggressiveModuleReportingEnabled()) { + userEventName = USER_EVENT_NAME; + } + + if (!userEventName) { + return false; + } + + return node.name === userEventName; + }; + + /** + * Determines whether a given node is fireEvent method or not + */ + // eslint-disable-next-line complexity + const isFireEventMethod: IsFireEventMethodFn = (node) => { + const fireEventUtil = + findImportedTestingLibraryUtilSpecifier(FIRE_EVENT_NAME); + let fireEventUtilName: string | undefined; + + if (fireEventUtil) { + fireEventUtilName = ASTUtils.isIdentifier(fireEventUtil) + ? fireEventUtil.name + : fireEventUtil.local.name; + } else if (isAggressiveModuleReportingEnabled()) { + fireEventUtilName = FIRE_EVENT_NAME; + } + + if (!fireEventUtilName) { + return false; + } + + const parentMemberExpression: TSESTree.MemberExpression | undefined = + node.parent && isMemberExpression(node.parent) + ? node.parent + : undefined; + + const parentCallExpression: TSESTree.CallExpression | undefined = + node.parent && isCallExpression(node.parent) ? node.parent : undefined; + + if (!parentMemberExpression && !parentCallExpression) { + return false; + } + + // check fireEvent('method', node) usage + if (parentCallExpression) { + return [fireEventUtilName, FIRE_EVENT_NAME].includes(node.name); + } + + // we know it's defined at this point, but TS seems to think it is not + // so here I'm enforcing it once in order to avoid using "!" operator every time + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const definedParentMemberExpression = parentMemberExpression!; + + // check fireEvent.click() usage + const regularCall = + ASTUtils.isIdentifier(definedParentMemberExpression.object) && + isCallExpression(definedParentMemberExpression.parent) && + definedParentMemberExpression.object.name === fireEventUtilName && + node.name !== FIRE_EVENT_NAME && + node.name !== fireEventUtilName; + + // check testingLibraryUtils.fireEvent.click() usage + const wildcardCall = + isMemberExpression(definedParentMemberExpression.object) && + ASTUtils.isIdentifier(definedParentMemberExpression.object.object) && + definedParentMemberExpression.object.object.name === + fireEventUtilName && + ASTUtils.isIdentifier(definedParentMemberExpression.object.property) && + definedParentMemberExpression.object.property.name === + FIRE_EVENT_NAME && + node.name !== FIRE_EVENT_NAME && + node.name !== fireEventUtilName; + + // check testingLibraryUtils.fireEvent('click') + const wildcardCallWithCallExpression = + ASTUtils.isIdentifier(definedParentMemberExpression.object) && + definedParentMemberExpression.object.name === fireEventUtilName && + ASTUtils.isIdentifier(definedParentMemberExpression.property) && + definedParentMemberExpression.property.name === FIRE_EVENT_NAME && + !isMemberExpression(definedParentMemberExpression.parent) && + node.name === FIRE_EVENT_NAME && + node.name !== fireEventUtilName; + + return regularCall || wildcardCall || wildcardCallWithCallExpression; + }; + + const isUserEventMethod: IsUserEventMethodFn = (node) => { + const userEvent = findImportedUserEventSpecifier(); + let userEventName: string | undefined; + + if (userEvent) { + userEventName = userEvent.name; + } else if (isAggressiveModuleReportingEnabled()) { + userEventName = USER_EVENT_NAME; + } + + if (!userEventName) { + return false; + } + + const parentMemberExpression: TSESTree.MemberExpression | undefined = + node.parent && isMemberExpression(node.parent) + ? node.parent + : undefined; + + if (!parentMemberExpression) { + return false; + } + + // make sure that given node it's not userEvent object itself + if ( + [userEventName, USER_EVENT_NAME].includes(node.name) || + (ASTUtils.isIdentifier(parentMemberExpression.object) && + parentMemberExpression.object.name === node.name) + ) { + return false; + } + + // check userEvent.click() usage + return ( + ASTUtils.isIdentifier(parentMemberExpression.object) && + parentMemberExpression.object.name === userEventName + ); + }; + + /** + * Determines whether a given node is a valid render util or not. + * + * A node will be interpreted as a valid render based on two conditions: + * the name matches with a valid "render" option, and the node is coming + * from Testing Library module. This depends on: + * + * - Aggressive render reporting: if enabled, then every node name + * containing "render" will be assumed as Testing Library render util. + * Otherwise, it means `custom-modules` has been set up, so only those nodes + * named as "render" or some of the `custom-modules` options will be + * considered as Testing Library render util. + * - Aggressive module reporting: if enabled, then it doesn't matter from + * where the given node was imported from as it will be considered part of + * Testing Library. Otherwise, it means `custom-module` has been set up, so + * only those nodes coming from Testing Library will be considered as valid. + */ + const isRenderUtil: IsRenderUtilFn = (node) => + isPotentialTestingLibraryFunction( + node, + (identifierNodeName, originalNodeName) => { + if (isAggressiveRenderReportingEnabled()) { + return identifierNodeName.toLowerCase().includes(RENDER_NAME); + } + + return [RENDER_NAME, ...getCustomRenders()].some( + (validRenderName) => + validRenderName === identifierNodeName || + (Boolean(originalNodeName) && + validRenderName === originalNodeName) + ); + } + ); + + const isCreateEventUtil: IsCreateEventUtil = (node) => { + const isCreateEventCallback = ( + identifierNodeName: string, + originalNodeName?: string + ) => [identifierNodeName, originalNodeName].includes(CREATE_EVENT_NAME); + if ( + isCallExpression(node) && + isMemberExpression(node.callee) && + ASTUtils.isIdentifier(node.callee.object) + ) { + return isPotentialTestingLibraryFunction( + node.callee.object, + isCreateEventCallback + ); + } + + if ( + isCallExpression(node) && + isMemberExpression(node.callee) && + isMemberExpression(node.callee.object) && + ASTUtils.isIdentifier(node.callee.object.property) + ) { + return isPotentialTestingLibraryFunction( + node.callee.object.property, + isCreateEventCallback + ); + } + const identifier = getDeepestIdentifierNode(node); + return isPotentialTestingLibraryFunction( + identifier, + isCreateEventCallback + ); + }; + + const isRenderVariableDeclarator: IsRenderVariableDeclaratorFn = (node) => { + if (!node.init) { + return false; + } + const initIdentifierNode = getDeepestIdentifierNode(node.init); + + if (!initIdentifierNode) { + return false; + } + + return isRenderUtil(initIdentifierNode); + }; + + const isDebugUtil: IsDebugUtilFn = ( + identifierNode, + validNames = DEBUG_UTILS + ) => { + const isBuiltInConsole = + isMemberExpression(identifierNode.parent) && + ASTUtils.isIdentifier(identifierNode.parent.object) && + identifierNode.parent.object.name === 'console'; + + return ( + !isBuiltInConsole && + isPotentialTestingLibraryFunction( + identifierNode, + (identifierNodeName, originalNodeName) => { + return ( + (validNames as string[]).includes(identifierNodeName) || + (!!originalNodeName && + (validNames as string[]).includes(originalNodeName)) + ); + } + ) + ); + }; + + /** + * Determines whether a given node is some reportable `act` util. + * + * An `act` is reportable if some of these conditions is met: + * - it's related to Testing Library module (this depends on Aggressive Reporting) + * - it's related to React DOM Test Utils + */ + const isActUtil = (node: TSESTree.Identifier): boolean => { + const isTestingLibraryAct = isPotentialTestingLibraryFunction( + node, + (identifierNodeName, originalNodeName) => { + return [identifierNodeName, originalNodeName] + .filter(Boolean) + .includes('act'); + } + ); + + const isReactDomTestUtilsAct = (() => { + if (!importedReactDomTestUtilsNode) { + return false; + } + const referenceNode = getReferenceNode(node); + const referenceNodeIdentifier = + getPropertyIdentifierNode(referenceNode); + if (!referenceNodeIdentifier) { + return false; + } + + const importedUtilSpecifier = findImportSpecifier( + node.name, + importedReactDomTestUtilsNode + ); + if (!importedUtilSpecifier) { + return false; + } + + const importDeclaration = (() => { + if (isImportDeclaration(importedUtilSpecifier.parent)) { + return importedUtilSpecifier.parent; + } + + const variableDeclarator = findClosestVariableDeclaratorNode( + importedUtilSpecifier + ); + + if (isCallExpression(variableDeclarator?.init)) { + return variableDeclarator?.init; + } + + return undefined; + })(); + if (!importDeclaration) { + return false; + } + + const importDeclarationName = getImportModuleName(importDeclaration); + if (!importDeclarationName) { + return false; + } + + if (importDeclarationName !== REACT_DOM_TEST_UTILS_PACKAGE) { + return false; + } + + return hasImportMatch( + importedUtilSpecifier, + referenceNodeIdentifier.name + ); + })(); + + return isTestingLibraryAct || isReactDomTestUtilsAct; + }; + + const isTestingLibraryUtil = (node: TSESTree.Identifier): boolean => { + return ( + isAsyncUtil(node) || + isQuery(node) || + isRenderUtil(node) || + isFireEventMethod(node) || + isUserEventMethod(node) || + isActUtil(node) || + isCreateEventUtil(node) + ); + }; + + /** + * Determines whether a given MemberExpression node is a presence assert + * + * Presence asserts could have shape of: + * - expect(element).toBeInTheDocument() + * - expect(element).not.toBeNull() + */ + const isPresenceAssert: IsPresenceAssertFn = (node) => { + const { matcher, isNegated } = getAssertNodeInfo(node); + + if (!matcher) { + return false; + } + + return isNegated + ? ABSENCE_MATCHERS.includes(matcher) + : PRESENCE_MATCHERS.includes(matcher); + }; + + /** + * Determines whether a given MemberExpression node is an absence assert + * + * Absence asserts could have shape of: + * - expect(element).toBeNull() + * - expect(element).not.toBeInTheDocument() + */ + const isAbsenceAssert: IsAbsenceAssertFn = (node) => { + const { matcher, isNegated } = getAssertNodeInfo(node); + + if (!matcher) { + return false; + } + + return isNegated + ? PRESENCE_MATCHERS.includes(matcher) + : ABSENCE_MATCHERS.includes(matcher); + }; + + /** + * Finds the import util specifier related to Testing Library for a given name. + */ + const findImportedTestingLibraryUtilSpecifier: FindImportedTestingLibraryUtilSpecifierFn = + ( + specifierName + ): TSESTree.Identifier | TSESTree.ImportClause | undefined => { + const node = + getCustomModuleImportNode() ?? getTestingLibraryImportNode(); + + if (!node) { + return undefined; + } + + return findImportSpecifier(specifierName, node); + }; + + const findImportedUserEventSpecifier: () => TSESTree.Identifier | null = + () => { + if (!importedUserEventLibraryNode) { + return null; + } + + if (isImportDeclaration(importedUserEventLibraryNode)) { + const userEventIdentifier = + importedUserEventLibraryNode.specifiers.find((specifier) => + isImportDefaultSpecifier(specifier) + ); + + if (userEventIdentifier) { + return userEventIdentifier.local; + } + } else { + if ( + !ASTUtils.isVariableDeclarator(importedUserEventLibraryNode.parent) + ) { + return null; + } + + const requireNode = importedUserEventLibraryNode.parent; + if (!ASTUtils.isIdentifier(requireNode.id)) { + return null; + } + + return requireNode.id; + } + + return null; + }; + + const getTestingLibraryImportedUtilSpecifier = ( + node: TSESTree.Identifier | TSESTree.MemberExpression + ): TSESTree.Identifier | TSESTree.ImportClause | undefined => { + const identifierName: string | undefined = + getPropertyIdentifierNode(node)?.name; + + if (!identifierName) { + return undefined; + } + + return findImportedTestingLibraryUtilSpecifier(identifierName); + }; + + /** + * Determines if file inspected meets all conditions to be reported by rules or not. + */ + const canReportErrors: CanReportErrorsFn = () => { + return skipRuleReportingCheck || isTestingLibraryImported(); + }; + + /** + * Determines whether a node is imported from a valid Testing Library module + * + * This method will try to find any import matching the given node name, + * and also make sure the name is a valid match in case it's been renamed. + */ + const isNodeComingFromTestingLibrary: IsNodeComingFromTestingLibraryFn = ( + node + ) => { + const importNode = getTestingLibraryImportedUtilSpecifier(node); + + if (!importNode) { + return false; + } + + const referenceNode = getReferenceNode(node); + const referenceNodeIdentifier = getPropertyIdentifierNode(referenceNode); + if (!referenceNodeIdentifier) { + return false; + } + + const importDeclaration = (() => { + if (isImportDeclaration(importNode.parent)) { + return importNode.parent; + } + + const variableDeclarator = + findClosestVariableDeclaratorNode(importNode); + + if (isCallExpression(variableDeclarator?.init)) { + return variableDeclarator?.init; + } + + return undefined; + })(); + + if (!importDeclaration) { + return false; + } + + const importDeclarationName = getImportModuleName(importDeclaration); + if (!importDeclarationName) { + return false; + } + + const identifierName: string | undefined = + getPropertyIdentifierNode(node)?.name; + + if (!identifierName) { + return false; + } + + const hasImportElementMatch = hasImportMatch(importNode, identifierName); + const hasImportModuleMatch = + /testing-library/g.test(importDeclarationName) || + (typeof customModuleSetting === 'string' && + importDeclarationName.endsWith(customModuleSetting)); + + return hasImportElementMatch && hasImportModuleMatch; + }; + + const helpers: DetectionHelpers = { + getTestingLibraryImportNode, + getCustomModuleImportNode, + getTestingLibraryImportName, + getCustomModuleImportName, + isTestingLibraryImported, + isTestingLibraryUtil, + isGetQueryVariant, + isQueryQueryVariant, + isFindQueryVariant, + isSyncQuery, + isAsyncQuery, + isQuery, + isCustomQuery, + isBuiltInQuery, + isAsyncUtil, + isFireEventUtil, + isUserEventUtil, + isFireEventMethod, + isUserEventMethod, + isRenderUtil, + isCreateEventUtil, + isRenderVariableDeclarator, + isDebugUtil, + isActUtil, + isPresenceAssert, + isAbsenceAssert, + canReportErrors, + findImportedTestingLibraryUtilSpecifier, + isNodeComingFromTestingLibrary, + }; + + // Instructions for Testing Library detection. + const detectionInstructions: TSESLint.RuleListener = { + /** + * This ImportDeclaration rule listener will check if Testing Library related + * modules are imported. Since imports happen first thing in a file, it's + * safe to use `isImportingTestingLibraryModule` and `isImportingCustomModule` + * since they will have corresponding value already updated when reporting other + * parts of the file. + */ + ImportDeclaration(node: TSESTree.ImportDeclaration) { + if (typeof node.source.value !== 'string') { + return; + } + // check only if testing library import not found yet so we avoid + // to override importedTestingLibraryNode after it's found + if ( + !importedTestingLibraryNode && + /testing-library/g.test(node.source.value) + ) { + importedTestingLibraryNode = node; + } + + // check only if custom module import not found yet so we avoid + // to override importedCustomModuleNode after it's found + const customModule = getCustomModule(); + if ( + customModule && + !importedCustomModuleNode && + node.source.value.endsWith(customModule) + ) { + importedCustomModuleNode = node; + } + + // check only if user-event import not found yet so we avoid + // to override importedUserEventLibraryNode after it's found + if ( + !importedUserEventLibraryNode && + node.source.value === USER_EVENT_PACKAGE + ) { + importedUserEventLibraryNode = node; + } + + // check only if react-dom/test-utils import not found yet so we avoid + // to override importedReactDomTestUtilsNode after it's found + if ( + !importedUserEventLibraryNode && + node.source.value === REACT_DOM_TEST_UTILS_PACKAGE + ) { + importedReactDomTestUtilsNode = node; + } + }, + + // Check if Testing Library related modules are loaded with required. + [`CallExpression > Identifier[name="require"]`]( + node: TSESTree.Identifier + ) { + const callExpression = node.parent as TSESTree.CallExpression; + const { arguments: args } = callExpression; + + if ( + !importedTestingLibraryNode && + args.some( + (arg) => + isLiteral(arg) && + typeof arg.value === 'string' && + /testing-library/g.test(arg.value) + ) + ) { + importedTestingLibraryNode = callExpression; + } + + const customModule = getCustomModule(); + if ( + !importedCustomModuleNode && + args.some( + (arg) => + customModule && + isLiteral(arg) && + typeof arg.value === 'string' && + arg.value.endsWith(customModule) + ) + ) { + importedCustomModuleNode = callExpression; + } + + if ( + !importedCustomModuleNode && + args.some( + (arg) => + isLiteral(arg) && + typeof arg.value === 'string' && + arg.value === USER_EVENT_PACKAGE + ) + ) { + importedUserEventLibraryNode = callExpression; + } + + if ( + !importedReactDomTestUtilsNode && + args.some( + (arg) => + isLiteral(arg) && + typeof arg.value === 'string' && + arg.value === REACT_DOM_TEST_UTILS_PACKAGE + ) + ) { + importedReactDomTestUtilsNode = callExpression; + } + }, + }; + + // update given rule to inject Testing Library detection + const ruleInstructions = ruleCreate(context, optionsWithDefault, helpers); + const enhancedRuleInstructions: TSESLint.RuleListener = {}; + + const allKeys = new Set( + Object.keys(detectionInstructions).concat(Object.keys(ruleInstructions)) + ); + + // Iterate over ALL instructions keys so we can override original rule instructions + // to prevent their execution if conditions to report errors are not met. + allKeys.forEach((instruction) => { + enhancedRuleInstructions[instruction] = (node) => { + if (instruction in detectionInstructions) { + detectionInstructions[instruction]?.(node); + } + + if (canReportErrors() && ruleInstructions[instruction]) { + return ruleInstructions[instruction]?.(node); + } + + return undefined; + }; + }); + + return enhancedRuleInstructions; + }; } diff --git a/lib/create-testing-library-rule/index.ts b/lib/create-testing-library-rule/index.ts index 2edf6ee8..7a919447 100644 --- a/lib/create-testing-library-rule/index.ts +++ b/lib/create-testing-library-rule/index.ts @@ -3,42 +3,42 @@ import { ESLintUtils, TSESLint } from '@typescript-eslint/utils'; import { getDocsUrl, TestingLibraryRuleMeta } from '../utils'; import { - DetectionOptions, - detectTestingLibraryUtils, - EnhancedRuleCreate, + DetectionOptions, + detectTestingLibraryUtils, + EnhancedRuleCreate, } from './detect-testing-library-utils'; export function createTestingLibraryRule< - TOptions extends readonly unknown[], - TMessageIds extends string, - TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener + TOptions extends readonly unknown[], + TMessageIds extends string, + TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener >({ - create, - detectionOptions = {}, - meta, - ...remainingConfig + create, + detectionOptions = {}, + meta, + ...remainingConfig }: Readonly<{ - name: string; - meta: TestingLibraryRuleMeta; - defaultOptions: Readonly; - detectionOptions?: Partial; - create: EnhancedRuleCreate; + name: string; + meta: TestingLibraryRuleMeta; + defaultOptions: Readonly; + detectionOptions?: Partial; + create: EnhancedRuleCreate; }>): TSESLint.RuleModule { - // eslint-disable-next-line new-cap - return ESLintUtils.RuleCreator(getDocsUrl)({ - ...remainingConfig, - create: detectTestingLibraryUtils( - create, - detectionOptions - ), - meta: { - ...meta, - docs: { - ...meta.docs, - // We're using our own recommendedConfig meta to tell our build tools - // if the rule is recommended on a config basis - recommended: false, - }, - }, - }); + // eslint-disable-next-line new-cap + return ESLintUtils.RuleCreator(getDocsUrl)({ + ...remainingConfig, + create: detectTestingLibraryUtils( + create, + detectionOptions + ), + meta: { + ...meta, + docs: { + ...meta.docs, + // We're using our own recommendedConfig meta to tell our build tools + // if the rule is recommended on a config basis + recommended: false, + }, + }, + }); } diff --git a/lib/index.ts b/lib/index.ts index e288411a..c270f91d 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -2,6 +2,6 @@ import configs from './configs'; import rules from './rules'; export = { - configs, - rules, + configs, + rules, }; diff --git a/lib/node-utils/index.ts b/lib/node-utils/index.ts index 138ca956..33b8cce4 100644 --- a/lib/node-utils/index.ts +++ b/lib/node-utils/index.ts @@ -1,57 +1,57 @@ import { - AST_NODE_TYPES, - ASTUtils, - TSESLint, - TSESLintScope, - TSESTree, + AST_NODE_TYPES, + ASTUtils, + TSESLint, + TSESLintScope, + TSESTree, } from '@typescript-eslint/utils'; import { - isArrayExpression, - isArrowFunctionExpression, - isAssignmentExpression, - isBlockStatement, - isCallExpression, - isExpressionStatement, - isImportDeclaration, - isImportNamespaceSpecifier, - isImportSpecifier, - isLiteral, - isMemberExpression, - isObjectPattern, - isProperty, - isReturnStatement, - isVariableDeclaration, + isArrayExpression, + isArrowFunctionExpression, + isAssignmentExpression, + isBlockStatement, + isCallExpression, + isExpressionStatement, + isImportDeclaration, + isImportNamespaceSpecifier, + isImportSpecifier, + isLiteral, + isMemberExpression, + isObjectPattern, + isProperty, + isReturnStatement, + isVariableDeclaration, } from './is-node-of-type'; export * from './is-node-of-type'; const ValidLeftHandSideExpressions = [ - AST_NODE_TYPES.CallExpression, - AST_NODE_TYPES.ClassExpression, - AST_NODE_TYPES.ClassDeclaration, - AST_NODE_TYPES.FunctionExpression, - AST_NODE_TYPES.Literal, - AST_NODE_TYPES.TemplateLiteral, - AST_NODE_TYPES.MemberExpression, - AST_NODE_TYPES.ArrayExpression, - AST_NODE_TYPES.ArrayPattern, - AST_NODE_TYPES.ClassExpression, - AST_NODE_TYPES.FunctionExpression, - AST_NODE_TYPES.Identifier, - AST_NODE_TYPES.JSXElement, - AST_NODE_TYPES.JSXFragment, - AST_NODE_TYPES.JSXOpeningElement, - AST_NODE_TYPES.MetaProperty, - AST_NODE_TYPES.ObjectExpression, - AST_NODE_TYPES.ObjectPattern, - AST_NODE_TYPES.Super, - AST_NODE_TYPES.ThisExpression, - AST_NODE_TYPES.TSNullKeyword, - AST_NODE_TYPES.TaggedTemplateExpression, - AST_NODE_TYPES.TSNonNullExpression, - AST_NODE_TYPES.TSAsExpression, - AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.CallExpression, + AST_NODE_TYPES.ClassExpression, + AST_NODE_TYPES.ClassDeclaration, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.Literal, + AST_NODE_TYPES.TemplateLiteral, + AST_NODE_TYPES.MemberExpression, + AST_NODE_TYPES.ArrayExpression, + AST_NODE_TYPES.ArrayPattern, + AST_NODE_TYPES.ClassExpression, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.Identifier, + AST_NODE_TYPES.JSXElement, + AST_NODE_TYPES.JSXFragment, + AST_NODE_TYPES.JSXOpeningElement, + AST_NODE_TYPES.MetaProperty, + AST_NODE_TYPES.ObjectExpression, + AST_NODE_TYPES.ObjectPattern, + AST_NODE_TYPES.Super, + AST_NODE_TYPES.ThisExpression, + AST_NODE_TYPES.TSNullKeyword, + AST_NODE_TYPES.TaggedTemplateExpression, + AST_NODE_TYPES.TSNonNullExpression, + AST_NODE_TYPES.TSAsExpression, + AST_NODE_TYPES.ArrowFunctionExpression, ]; /** @@ -60,105 +60,105 @@ const ValidLeftHandSideExpressions = [ * @param shouldRestrictInnerScope - If true, CallExpression must belong to innermost scope of given node */ export function findClosestCallExpressionNode( - node: TSESTree.Node | null | undefined, - shouldRestrictInnerScope = false + node: TSESTree.Node | null | undefined, + shouldRestrictInnerScope = false ): TSESTree.CallExpression | null { - if (isCallExpression(node)) { - return node; - } - - if (!node || !node.parent) { - return null; - } - - if ( - shouldRestrictInnerScope && - !ValidLeftHandSideExpressions.includes(node.parent.type) - ) { - return null; - } - - return findClosestCallExpressionNode(node.parent, shouldRestrictInnerScope); + if (isCallExpression(node)) { + return node; + } + + if (!node || !node.parent) { + return null; + } + + if ( + shouldRestrictInnerScope && + !ValidLeftHandSideExpressions.includes(node.parent.type) + ) { + return null; + } + + return findClosestCallExpressionNode(node.parent, shouldRestrictInnerScope); } export function findClosestVariableDeclaratorNode( - node: TSESTree.Node | undefined + node: TSESTree.Node | undefined ): TSESTree.VariableDeclarator | null { - if (!node) { - return null; - } + if (!node) { + return null; + } - if (ASTUtils.isVariableDeclarator(node)) { - return node; - } + if (ASTUtils.isVariableDeclarator(node)) { + return node; + } - return findClosestVariableDeclaratorNode(node.parent); + return findClosestVariableDeclaratorNode(node.parent); } /** * TODO: remove this one in favor of {@link findClosestCallExpressionNode} */ export function findClosestCallNode( - node: TSESTree.Node, - name: string + node: TSESTree.Node, + name: string ): TSESTree.CallExpression | null { - if (!node.parent) { - return null; - } - - if ( - isCallExpression(node) && - ASTUtils.isIdentifier(node.callee) && - node.callee.name === name - ) { - return node; - } else { - return findClosestCallNode(node.parent, name); - } + if (!node.parent) { + return null; + } + + if ( + isCallExpression(node) && + ASTUtils.isIdentifier(node.callee) && + node.callee.name === name + ) { + return node; + } else { + return findClosestCallNode(node.parent, name); + } } export function hasThenProperty(node: TSESTree.Node): boolean { - return ( - isMemberExpression(node) && - ASTUtils.isIdentifier(node.property) && - node.property.name === 'then' - ); + return ( + isMemberExpression(node) && + ASTUtils.isIdentifier(node.property) && + node.property.name === 'then' + ); } export function hasChainedThen(node: TSESTree.Node): boolean { - const parent = node.parent; + const parent = node.parent; - // wait(...).then(...) - if (isCallExpression(parent) && parent.parent) { - return hasThenProperty(parent.parent); - } + // wait(...).then(...) + if (isCallExpression(parent) && parent.parent) { + return hasThenProperty(parent.parent); + } - // promise.then(...) - return !!parent && hasThenProperty(parent); + // promise.then(...) + return !!parent && hasThenProperty(parent); } export function isPromiseIdentifier( - node: TSESTree.Node + node: TSESTree.Node ): node is TSESTree.Identifier & { name: 'Promise' } { - return ASTUtils.isIdentifier(node) && node.name === 'Promise'; + return ASTUtils.isIdentifier(node) && node.name === 'Promise'; } export function isPromiseAll(node: TSESTree.CallExpression): boolean { - return ( - isMemberExpression(node.callee) && - isPromiseIdentifier(node.callee.object) && - ASTUtils.isIdentifier(node.callee.property) && - node.callee.property.name === 'all' - ); + return ( + isMemberExpression(node.callee) && + isPromiseIdentifier(node.callee.object) && + ASTUtils.isIdentifier(node.callee.property) && + node.callee.property.name === 'all' + ); } export function isPromiseAllSettled(node: TSESTree.CallExpression): boolean { - return ( - isMemberExpression(node.callee) && - isPromiseIdentifier(node.callee.object) && - ASTUtils.isIdentifier(node.callee.property) && - node.callee.property.name === 'allSettled' - ); + return ( + isMemberExpression(node.callee) && + isPromiseIdentifier(node.callee.object) && + ASTUtils.isIdentifier(node.callee.property) && + node.callee.property.name === 'allSettled' + ); } /** @@ -166,19 +166,19 @@ export function isPromiseAllSettled(node: TSESTree.CallExpression): boolean { * array expression. */ export function isPromisesArrayResolved(node: TSESTree.Node): boolean { - const closestCallExpression = findClosestCallExpressionNode(node, true); - - if (!closestCallExpression) { - return false; - } - - return ( - !!closestCallExpression.parent && - isArrayExpression(closestCallExpression.parent) && - isCallExpression(closestCallExpression.parent.parent) && - (isPromiseAll(closestCallExpression.parent.parent) || - isPromiseAllSettled(closestCallExpression.parent.parent)) - ); + const closestCallExpression = findClosestCallExpressionNode(node, true); + + if (!closestCallExpression) { + return false; + } + + return ( + !!closestCallExpression.parent && + isArrayExpression(closestCallExpression.parent) && + isCallExpression(closestCallExpression.parent.parent) && + (isPromiseAll(closestCallExpression.parent.parent) || + isPromiseAllSettled(closestCallExpression.parent.parent)) + ); } /** @@ -193,106 +193,106 @@ export function isPromisesArrayResolved(node: TSESTree.Node): boolean { * - has `resolves` or `rejects` jest methods */ export function isPromiseHandled(nodeIdentifier: TSESTree.Identifier): boolean { - const closestCallExpressionNode = findClosestCallExpressionNode( - nodeIdentifier, - true - ); - - const suspiciousNodes = [nodeIdentifier, closestCallExpressionNode].filter( - Boolean - ); - - for (const node of suspiciousNodes) { - if (!node || !node.parent) { - continue; - } - if (ASTUtils.isAwaitExpression(node.parent)) { - return true; - } - - if ( - isArrowFunctionExpression(node.parent) || - isReturnStatement(node.parent) - ) { - return true; - } - - if (hasClosestExpectResolvesRejects(node.parent)) { - return true; - } - - if (hasChainedThen(node)) { - return true; - } - - if (isPromisesArrayResolved(node)) { - return true; - } - } - - return false; + const closestCallExpressionNode = findClosestCallExpressionNode( + nodeIdentifier, + true + ); + + const suspiciousNodes = [nodeIdentifier, closestCallExpressionNode].filter( + Boolean + ); + + for (const node of suspiciousNodes) { + if (!node || !node.parent) { + continue; + } + if (ASTUtils.isAwaitExpression(node.parent)) { + return true; + } + + if ( + isArrowFunctionExpression(node.parent) || + isReturnStatement(node.parent) + ) { + return true; + } + + if (hasClosestExpectResolvesRejects(node.parent)) { + return true; + } + + if (hasChainedThen(node)) { + return true; + } + + if (isPromisesArrayResolved(node)) { + return true; + } + } + + return false; } export function getVariableReferences( - context: TSESLint.RuleContext, - node: TSESTree.Node + context: TSESLint.RuleContext, + node: TSESTree.Node ): TSESLint.Scope.Reference[] { - if (ASTUtils.isVariableDeclarator(node)) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - return context.getDeclaredVariables(node)[0]?.references?.slice(1) ?? []; - } + if (ASTUtils.isVariableDeclarator(node)) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return context.getDeclaredVariables(node)[0]?.references?.slice(1) ?? []; + } - return []; + return []; } interface InnermostFunctionScope extends TSESLintScope.FunctionScope { - block: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression; + block: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression; } export function getInnermostFunctionScope( - context: TSESLint.RuleContext, - asyncQueryNode: TSESTree.Identifier + context: TSESLint.RuleContext, + asyncQueryNode: TSESTree.Identifier ): InnermostFunctionScope | null { - const innermostScope = ASTUtils.getInnermostScope( - context.getScope(), - asyncQueryNode - ); - - if ( - innermostScope.type === 'function' && - ASTUtils.isFunction(innermostScope.block) - ) { - return innermostScope as unknown as InnermostFunctionScope; - } - - return null; + const innermostScope = ASTUtils.getInnermostScope( + context.getScope(), + asyncQueryNode + ); + + if ( + innermostScope.type === 'function' && + ASTUtils.isFunction(innermostScope.block) + ) { + return innermostScope as unknown as InnermostFunctionScope; + } + + return null; } export function getFunctionReturnStatementNode( - functionNode: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression + functionNode: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression ): TSESTree.Node | null { - if (isBlockStatement(functionNode.body)) { - // regular function or arrow function with block - const returnStatementNode = functionNode.body.body.find((statement) => - isReturnStatement(statement) - ) as TSESTree.ReturnStatement | undefined; - - if (!returnStatementNode) { - return null; - } - return returnStatementNode.argument; - } else if (functionNode.expression) { - // arrow function with implicit return - return functionNode.body; - } - - return null; + if (isBlockStatement(functionNode.body)) { + // regular function or arrow function with block + const returnStatementNode = functionNode.body.body.find((statement) => + isReturnStatement(statement) + ) as TSESTree.ReturnStatement | undefined; + + if (!returnStatementNode) { + return null; + } + return returnStatementNode.argument; + } else if (functionNode.expression) { + // arrow function with implicit return + return functionNode.body; + } + + return null; } /** @@ -306,25 +306,25 @@ export function getFunctionReturnStatementNode( * it will return `rtl` identifier node */ export function getPropertyIdentifierNode( - node: TSESTree.Node + node: TSESTree.Node ): TSESTree.Identifier | null { - if (ASTUtils.isIdentifier(node)) { - return node; - } + if (ASTUtils.isIdentifier(node)) { + return node; + } - if (isMemberExpression(node)) { - return getPropertyIdentifierNode(node.object); - } + if (isMemberExpression(node)) { + return getPropertyIdentifierNode(node.object); + } - if (isCallExpression(node)) { - return getPropertyIdentifierNode(node.callee); - } + if (isCallExpression(node)) { + return getPropertyIdentifierNode(node.callee); + } - if (isExpressionStatement(node)) { - return getPropertyIdentifierNode(node.expression); - } + if (isExpressionStatement(node)) { + return getPropertyIdentifierNode(node.expression); + } - return null; + return null; } /** @@ -338,25 +338,25 @@ export function getPropertyIdentifierNode( * it will return `getByRole` identifier */ export function getDeepestIdentifierNode( - node: TSESTree.Node + node: TSESTree.Node ): TSESTree.Identifier | null { - if (ASTUtils.isIdentifier(node)) { - return node; - } + if (ASTUtils.isIdentifier(node)) { + return node; + } - if (isMemberExpression(node) && ASTUtils.isIdentifier(node.property)) { - return node.property; - } + if (isMemberExpression(node) && ASTUtils.isIdentifier(node.property)) { + return node.property; + } - if (isCallExpression(node)) { - return getDeepestIdentifierNode(node.callee); - } + if (isCallExpression(node)) { + return getDeepestIdentifierNode(node.callee); + } - if (ASTUtils.isAwaitExpression(node)) { - return getDeepestIdentifierNode(node.argument); - } + if (ASTUtils.isAwaitExpression(node)) { + return getDeepestIdentifierNode(node.argument); + } - return null; + return null; } /** @@ -370,96 +370,96 @@ export function getDeepestIdentifierNode( * it will return `rtl` node */ export function getReferenceNode( - node: - | TSESTree.CallExpression - | TSESTree.Identifier - | TSESTree.MemberExpression + node: + | TSESTree.CallExpression + | TSESTree.Identifier + | TSESTree.MemberExpression ): TSESTree.CallExpression | TSESTree.Identifier | TSESTree.MemberExpression { - if ( - node.parent && - (isMemberExpression(node.parent) || isCallExpression(node.parent)) - ) { - return getReferenceNode(node.parent); - } - - return node; + if ( + node.parent && + (isMemberExpression(node.parent) || isCallExpression(node.parent)) + ) { + return getReferenceNode(node.parent); + } + + return node; } export function getFunctionName( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression + node: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression ): string { - return ( - ASTUtils.getFunctionNameWithKind(node) - .match(/('\w+')/g)?.[0] - .replace(/'/g, '') ?? '' - ); + return ( + ASTUtils.getFunctionNameWithKind(node) + .match(/('\w+')/g)?.[0] + .replace(/'/g, '') ?? '' + ); } // TODO: extract into types file? export type ImportModuleNode = - | TSESTree.CallExpression - | TSESTree.ImportDeclaration; + | TSESTree.CallExpression + | TSESTree.ImportDeclaration; export function getImportModuleName( - node: ImportModuleNode | null | undefined + node: ImportModuleNode | null | undefined ): string | undefined { - // import node of shape: import { foo } from 'bar' - if (isImportDeclaration(node) && typeof node.source.value === 'string') { - return node.source.value; - } - - // import node of shape: const { foo } = require('bar') - if ( - isCallExpression(node) && - isLiteral(node.arguments[0]) && - typeof node.arguments[0].value === 'string' - ) { - return node.arguments[0].value; - } - - return undefined; + // import node of shape: import { foo } from 'bar' + if (isImportDeclaration(node) && typeof node.source.value === 'string') { + return node.source.value; + } + + // import node of shape: const { foo } = require('bar') + if ( + isCallExpression(node) && + isLiteral(node.arguments[0]) && + typeof node.arguments[0].value === 'string' + ) { + return node.arguments[0].value; + } + + return undefined; } type AssertNodeInfo = { - matcher: string | null; - isNegated: boolean; + matcher: string | null; + isNegated: boolean; }; /** * Extracts matcher info from MemberExpression node representing an assert. */ export function getAssertNodeInfo( - node: TSESTree.MemberExpression + node: TSESTree.MemberExpression ): AssertNodeInfo { - const emptyInfo = { matcher: null, isNegated: false } as AssertNodeInfo; - - if ( - !isCallExpression(node.object) || - !ASTUtils.isIdentifier(node.object.callee) - ) { - return emptyInfo; - } - - if (node.object.callee.name !== 'expect') { - return emptyInfo; - } - - let matcher = ASTUtils.getPropertyName(node); - const isNegated = matcher === 'not'; - if (isNegated) { - matcher = - node.parent && isMemberExpression(node.parent) - ? ASTUtils.getPropertyName(node.parent) - : null; - } - - if (!matcher) { - return emptyInfo; - } - - return { matcher, isNegated }; + const emptyInfo = { matcher: null, isNegated: false } as AssertNodeInfo; + + if ( + !isCallExpression(node.object) || + !ASTUtils.isIdentifier(node.object.callee) + ) { + return emptyInfo; + } + + if (node.object.callee.name !== 'expect') { + return emptyInfo; + } + + let matcher = ASTUtils.getPropertyName(node); + const isNegated = matcher === 'not'; + if (isNegated) { + matcher = + node.parent && isMemberExpression(node.parent) + ? ASTUtils.getPropertyName(node.parent) + : null; + } + + if (!matcher) { + return emptyInfo; + } + + return { matcher, isNegated }; } /** @@ -468,115 +468,115 @@ export function getAssertNodeInfo( * */ export function hasClosestExpectResolvesRejects(node: TSESTree.Node): boolean { - if ( - isCallExpression(node) && - ASTUtils.isIdentifier(node.callee) && - node.parent && - isMemberExpression(node.parent) && - node.callee.name === 'expect' - ) { - const expectMatcher = node.parent.property; - return ( - ASTUtils.isIdentifier(expectMatcher) && - (expectMatcher.name === 'resolves' || expectMatcher.name === 'rejects') - ); - } - - if (!node.parent) { - return false; - } - - return hasClosestExpectResolvesRejects(node.parent); + if ( + isCallExpression(node) && + ASTUtils.isIdentifier(node.callee) && + node.parent && + isMemberExpression(node.parent) && + node.callee.name === 'expect' + ) { + const expectMatcher = node.parent.property; + return ( + ASTUtils.isIdentifier(expectMatcher) && + (expectMatcher.name === 'resolves' || expectMatcher.name === 'rejects') + ); + } + + if (!node.parent) { + return false; + } + + return hasClosestExpectResolvesRejects(node.parent); } /** * Gets the Function node which returns the given Identifier. */ export function getInnermostReturningFunction( - context: TSESLint.RuleContext, - node: TSESTree.Identifier + context: TSESLint.RuleContext, + node: TSESTree.Identifier ): - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | undefined { - const functionScope = getInnermostFunctionScope(context, node); + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | undefined { + const functionScope = getInnermostFunctionScope(context, node); - if (!functionScope) { - return undefined; - } + if (!functionScope) { + return undefined; + } - const returnStatementNode = getFunctionReturnStatementNode( - functionScope.block - ); + const returnStatementNode = getFunctionReturnStatementNode( + functionScope.block + ); - if (!returnStatementNode) { - return undefined; - } + if (!returnStatementNode) { + return undefined; + } - const returnStatementIdentifier = - getDeepestIdentifierNode(returnStatementNode); + const returnStatementIdentifier = + getDeepestIdentifierNode(returnStatementNode); - if (returnStatementIdentifier?.name !== node.name) { - return undefined; - } + if (returnStatementIdentifier?.name !== node.name) { + return undefined; + } - return functionScope.block; + return functionScope.block; } export function hasImportMatch( - importNode: TSESTree.Identifier | TSESTree.ImportClause, - identifierName: string + importNode: TSESTree.Identifier | TSESTree.ImportClause, + identifierName: string ): boolean { - if (ASTUtils.isIdentifier(importNode)) { - return importNode.name === identifierName; - } + if (ASTUtils.isIdentifier(importNode)) { + return importNode.name === identifierName; + } - return importNode.local.name === identifierName; + return importNode.local.name === identifierName; } export function getStatementCallExpression( - statement: TSESTree.Statement + statement: TSESTree.Statement ): TSESTree.CallExpression | undefined { - if (isExpressionStatement(statement)) { - const { expression } = statement; - if (isCallExpression(expression)) { - return expression; - } - - if ( - ASTUtils.isAwaitExpression(expression) && - isCallExpression(expression.argument) - ) { - return expression.argument; - } - - if (isAssignmentExpression(expression)) { - if (isCallExpression(expression.right)) { - return expression.right; - } - - if ( - ASTUtils.isAwaitExpression(expression.right) && - isCallExpression(expression.right.argument) - ) { - return expression.right.argument; - } - } - } - - if (isReturnStatement(statement) && isCallExpression(statement.argument)) { - return statement.argument; - } - - if (isVariableDeclaration(statement)) { - for (const declaration of statement.declarations) { - if (isCallExpression(declaration.init)) { - return declaration.init; - } - } - } - return undefined; + if (isExpressionStatement(statement)) { + const { expression } = statement; + if (isCallExpression(expression)) { + return expression; + } + + if ( + ASTUtils.isAwaitExpression(expression) && + isCallExpression(expression.argument) + ) { + return expression.argument; + } + + if (isAssignmentExpression(expression)) { + if (isCallExpression(expression.right)) { + return expression.right; + } + + if ( + ASTUtils.isAwaitExpression(expression.right) && + isCallExpression(expression.right.argument) + ) { + return expression.right.argument; + } + } + } + + if (isReturnStatement(statement) && isCallExpression(statement.argument)) { + return statement.argument; + } + + if (isVariableDeclaration(statement)) { + for (const declaration of statement.declarations) { + if (isCallExpression(declaration.init)) { + return declaration.init; + } + } + } + return undefined; } /** @@ -589,60 +589,60 @@ export function getStatementCallExpression( * If node given is not a function, `false` will be returned. */ export function isEmptyFunction(node: TSESTree.Node): boolean | undefined { - if (ASTUtils.isFunction(node) && isBlockStatement(node.body)) { - return node.body.body.length === 0; - } + if (ASTUtils.isFunction(node) && isBlockStatement(node.body)) { + return node.body.body.length === 0; + } - return false; + return false; } /** * Finds the import specifier matching a given name for a given import module node. */ export function findImportSpecifier( - specifierName: string, - node: ImportModuleNode + specifierName: string, + node: ImportModuleNode ): TSESTree.Identifier | TSESTree.ImportClause | undefined { - if (isImportDeclaration(node)) { - const namedExport = node.specifiers.find((n) => { - return ( - isImportSpecifier(n) && - [n.imported.name, n.local.name].includes(specifierName) - ); - }); - - // it is "import { foo [as alias] } from 'baz'" - if (namedExport) { - return namedExport; - } - - // it could be "import * as rtl from 'baz'" - return node.specifiers.find((n) => isImportNamespaceSpecifier(n)); - } else { - if (!ASTUtils.isVariableDeclarator(node.parent)) { - return undefined; - } - const requireNode = node.parent; - - if (ASTUtils.isIdentifier(requireNode.id)) { - // this is const rtl = require('foo') - return requireNode.id; - } - - // this should be const { something } = require('foo') - if (!isObjectPattern(requireNode.id)) { - return undefined; - } - - const property = requireNode.id.properties.find( - (n) => - isProperty(n) && - ASTUtils.isIdentifier(n.key) && - n.key.name === specifierName - ); - if (!property) { - return undefined; - } - return (property as TSESTree.Property).key as TSESTree.Identifier; - } + if (isImportDeclaration(node)) { + const namedExport = node.specifiers.find((n) => { + return ( + isImportSpecifier(n) && + [n.imported.name, n.local.name].includes(specifierName) + ); + }); + + // it is "import { foo [as alias] } from 'baz'" + if (namedExport) { + return namedExport; + } + + // it could be "import * as rtl from 'baz'" + return node.specifiers.find((n) => isImportNamespaceSpecifier(n)); + } else { + if (!ASTUtils.isVariableDeclarator(node.parent)) { + return undefined; + } + const requireNode = node.parent; + + if (ASTUtils.isIdentifier(requireNode.id)) { + // this is const rtl = require('foo') + return requireNode.id; + } + + // this should be const { something } = require('foo') + if (!isObjectPattern(requireNode.id)) { + return undefined; + } + + const property = requireNode.id.properties.find( + (n) => + isProperty(n) && + ASTUtils.isIdentifier(n.key) && + n.key.name === specifierName + ); + if (!property) { + return undefined; + } + return (property as TSESTree.Property).key as TSESTree.Identifier; + } } diff --git a/lib/node-utils/is-node-of-type.ts b/lib/node-utils/is-node-of-type.ts index a7f7597c..8f74a9a5 100644 --- a/lib/node-utils/is-node-of-type.ts +++ b/lib/node-utils/is-node-of-type.ts @@ -1,61 +1,61 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; export const isArrayExpression = ASTUtils.isNodeOfType( - AST_NODE_TYPES.ArrayExpression + AST_NODE_TYPES.ArrayExpression ); export const isArrowFunctionExpression = ASTUtils.isNodeOfType( - AST_NODE_TYPES.ArrowFunctionExpression + AST_NODE_TYPES.ArrowFunctionExpression ); export const isBlockStatement = ASTUtils.isNodeOfType( - AST_NODE_TYPES.BlockStatement + AST_NODE_TYPES.BlockStatement ); export const isCallExpression = ASTUtils.isNodeOfType( - AST_NODE_TYPES.CallExpression + AST_NODE_TYPES.CallExpression ); export const isExpressionStatement = ASTUtils.isNodeOfType( - AST_NODE_TYPES.ExpressionStatement + AST_NODE_TYPES.ExpressionStatement ); export const isVariableDeclaration = ASTUtils.isNodeOfType( - AST_NODE_TYPES.VariableDeclaration + AST_NODE_TYPES.VariableDeclaration ); export const isAssignmentExpression = ASTUtils.isNodeOfType( - AST_NODE_TYPES.AssignmentExpression + AST_NODE_TYPES.AssignmentExpression ); export const isSequenceExpression = ASTUtils.isNodeOfType( - AST_NODE_TYPES.SequenceExpression + AST_NODE_TYPES.SequenceExpression ); export const isImportDeclaration = ASTUtils.isNodeOfType( - AST_NODE_TYPES.ImportDeclaration + AST_NODE_TYPES.ImportDeclaration ); export const isImportDefaultSpecifier = ASTUtils.isNodeOfType( - AST_NODE_TYPES.ImportDefaultSpecifier + AST_NODE_TYPES.ImportDefaultSpecifier ); export const isImportNamespaceSpecifier = ASTUtils.isNodeOfType( - AST_NODE_TYPES.ImportNamespaceSpecifier + AST_NODE_TYPES.ImportNamespaceSpecifier ); export const isImportSpecifier = ASTUtils.isNodeOfType( - AST_NODE_TYPES.ImportSpecifier + AST_NODE_TYPES.ImportSpecifier ); export const isJSXAttribute = ASTUtils.isNodeOfType( - AST_NODE_TYPES.JSXAttribute + AST_NODE_TYPES.JSXAttribute ); export const isLiteral = ASTUtils.isNodeOfType(AST_NODE_TYPES.Literal); export const isMemberExpression = ASTUtils.isNodeOfType( - AST_NODE_TYPES.MemberExpression + AST_NODE_TYPES.MemberExpression ); export const isNewExpression = ASTUtils.isNodeOfType( - AST_NODE_TYPES.NewExpression + AST_NODE_TYPES.NewExpression ); export const isObjectExpression = ASTUtils.isNodeOfType( - AST_NODE_TYPES.ObjectExpression + AST_NODE_TYPES.ObjectExpression ); export const isObjectPattern = ASTUtils.isNodeOfType( - AST_NODE_TYPES.ObjectPattern + AST_NODE_TYPES.ObjectPattern ); export const isProperty = ASTUtils.isNodeOfType(AST_NODE_TYPES.Property); export const isReturnStatement = ASTUtils.isNodeOfType( - AST_NODE_TYPES.ReturnStatement + AST_NODE_TYPES.ReturnStatement ); export const isFunctionExpression = ASTUtils.isNodeOfType( - AST_NODE_TYPES.FunctionExpression + AST_NODE_TYPES.FunctionExpression ); diff --git a/lib/rules/await-async-query.ts b/lib/rules/await-async-query.ts index 31cb1b30..0af105a5 100644 --- a/lib/rules/await-async-query.ts +++ b/lib/rules/await-async-query.ts @@ -2,12 +2,12 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - findClosestCallExpressionNode, - getDeepestIdentifierNode, - getFunctionName, - getInnermostReturningFunction, - getVariableReferences, - isPromiseHandled, + findClosestCallExpressionNode, + getDeepestIdentifierNode, + getFunctionName, + getInnermostReturningFunction, + getVariableReferences, + isPromiseHandled, } from '../node-utils'; export const RULE_NAME = 'await-async-query'; @@ -15,106 +15,106 @@ export type MessageIds = 'asyncQueryWrapper' | 'awaitAsyncQuery'; type Options = []; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Enforce promises from async queries to be handled', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - awaitAsyncQuery: - 'promise returned from `{{ name }}` query must be handled', - asyncQueryWrapper: - 'promise returned from `{{ name }}` wrapper over async query must be handled', - }, - schema: [], - }, - defaultOptions: [], + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Enforce promises from async queries to be handled', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + awaitAsyncQuery: + 'promise returned from `{{ name }}` query must be handled', + asyncQueryWrapper: + 'promise returned from `{{ name }}` wrapper over async query must be handled', + }, + schema: [], + }, + defaultOptions: [], - create(context, _, helpers) { - const functionWrappersNames: string[] = []; + create(context, _, helpers) { + const functionWrappersNames: string[] = []; - function detectAsyncQueryWrapper(node: TSESTree.Identifier) { - const innerFunction = getInnermostReturningFunction(context, node); - if (innerFunction) { - functionWrappersNames.push(getFunctionName(innerFunction)); - } - } + function detectAsyncQueryWrapper(node: TSESTree.Identifier) { + const innerFunction = getInnermostReturningFunction(context, node); + if (innerFunction) { + functionWrappersNames.push(getFunctionName(innerFunction)); + } + } - return { - CallExpression(node) { - const identifierNode = getDeepestIdentifierNode(node); + return { + CallExpression(node) { + const identifierNode = getDeepestIdentifierNode(node); - if (!identifierNode) { - return; - } + if (!identifierNode) { + return; + } - if (helpers.isAsyncQuery(identifierNode)) { - // detect async query used within wrapper function for later analysis - detectAsyncQueryWrapper(identifierNode); + if (helpers.isAsyncQuery(identifierNode)) { + // detect async query used within wrapper function for later analysis + detectAsyncQueryWrapper(identifierNode); - const closestCallExpressionNode = findClosestCallExpressionNode( - node, - true - ); + const closestCallExpressionNode = findClosestCallExpressionNode( + node, + true + ); - if (!closestCallExpressionNode || !closestCallExpressionNode.parent) { - return; - } + if (!closestCallExpressionNode || !closestCallExpressionNode.parent) { + return; + } - const references = getVariableReferences( - context, - closestCallExpressionNode.parent - ); + const references = getVariableReferences( + context, + closestCallExpressionNode.parent + ); - // check direct usage of async query: - // const element = await findByRole('button') - if (references.length === 0) { - if (!isPromiseHandled(identifierNode)) { - context.report({ - node: identifierNode, - messageId: 'awaitAsyncQuery', - data: { name: identifierNode.name }, - }); - return; - } - } + // check direct usage of async query: + // const element = await findByRole('button') + if (references.length === 0) { + if (!isPromiseHandled(identifierNode)) { + context.report({ + node: identifierNode, + messageId: 'awaitAsyncQuery', + data: { name: identifierNode.name }, + }); + return; + } + } - // check references usages of async query: - // const promise = findByRole('button') - // const element = await promise - for (const reference of references) { - if ( - ASTUtils.isIdentifier(reference.identifier) && - !isPromiseHandled(reference.identifier) - ) { - context.report({ - node: identifierNode, - messageId: 'awaitAsyncQuery', - data: { name: identifierNode.name }, - }); - return; - } - } - } else if ( - functionWrappersNames.includes(identifierNode.name) && - !isPromiseHandled(identifierNode) - ) { - // check async queries used within a wrapper previously detected - context.report({ - node: identifierNode, - messageId: 'asyncQueryWrapper', - data: { name: identifierNode.name }, - }); - } - }, - }; - }, + // check references usages of async query: + // const promise = findByRole('button') + // const element = await promise + for (const reference of references) { + if ( + ASTUtils.isIdentifier(reference.identifier) && + !isPromiseHandled(reference.identifier) + ) { + context.report({ + node: identifierNode, + messageId: 'awaitAsyncQuery', + data: { name: identifierNode.name }, + }); + return; + } + } + } else if ( + functionWrappersNames.includes(identifierNode.name) && + !isPromiseHandled(identifierNode) + ) { + // check async queries used within a wrapper previously detected + context.report({ + node: identifierNode, + messageId: 'asyncQueryWrapper', + data: { name: identifierNode.name }, + }); + } + }, + }; + }, }); diff --git a/lib/rules/await-async-utils.ts b/lib/rules/await-async-utils.ts index 71d22ab5..dbbcd9a8 100644 --- a/lib/rules/await-async-utils.ts +++ b/lib/rules/await-async-utils.ts @@ -2,11 +2,11 @@ import { TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - findClosestCallExpressionNode, - getFunctionName, - getInnermostReturningFunction, - getVariableReferences, - isPromiseHandled, + findClosestCallExpressionNode, + getFunctionName, + getInnermostReturningFunction, + getVariableReferences, + isPromiseHandled, } from '../node-utils'; export const RULE_NAME = 'await-async-utils'; @@ -14,95 +14,95 @@ export type MessageIds = 'asyncUtilWrapper' | 'awaitAsyncUtil'; type Options = []; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Enforce promises from async utils to be awaited properly', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - awaitAsyncUtil: 'Promise returned from `{{ name }}` must be handled', - asyncUtilWrapper: - 'Promise returned from {{ name }} wrapper over async util must be handled', - }, - schema: [], - }, - defaultOptions: [], + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Enforce promises from async utils to be awaited properly', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + awaitAsyncUtil: 'Promise returned from `{{ name }}` must be handled', + asyncUtilWrapper: + 'Promise returned from {{ name }} wrapper over async util must be handled', + }, + schema: [], + }, + defaultOptions: [], - create(context, _, helpers) { - const functionWrappersNames: string[] = []; + create(context, _, helpers) { + const functionWrappersNames: string[] = []; - function detectAsyncUtilWrapper(node: TSESTree.Identifier) { - const innerFunction = getInnermostReturningFunction(context, node); + function detectAsyncUtilWrapper(node: TSESTree.Identifier) { + const innerFunction = getInnermostReturningFunction(context, node); - if (innerFunction) { - functionWrappersNames.push(getFunctionName(innerFunction)); - } - } + if (innerFunction) { + functionWrappersNames.push(getFunctionName(innerFunction)); + } + } - return { - 'CallExpression Identifier'(node: TSESTree.Identifier) { - if (helpers.isAsyncUtil(node)) { - // detect async query used within wrapper function for later analysis - detectAsyncUtilWrapper(node); + return { + 'CallExpression Identifier'(node: TSESTree.Identifier) { + if (helpers.isAsyncUtil(node)) { + // detect async query used within wrapper function for later analysis + detectAsyncUtilWrapper(node); - const closestCallExpression = findClosestCallExpressionNode( - node, - true - ); + const closestCallExpression = findClosestCallExpressionNode( + node, + true + ); - if (!closestCallExpression || !closestCallExpression.parent) { - return; - } + if (!closestCallExpression || !closestCallExpression.parent) { + return; + } - const references = getVariableReferences( - context, - closestCallExpression.parent - ); + const references = getVariableReferences( + context, + closestCallExpression.parent + ); - if (references.length === 0) { - if (!isPromiseHandled(node)) { - context.report({ - node, - messageId: 'awaitAsyncUtil', - data: { - name: node.name, - }, - }); - } - } else { - for (const reference of references) { - const referenceNode = reference.identifier as TSESTree.Identifier; - if (!isPromiseHandled(referenceNode)) { - context.report({ - node, - messageId: 'awaitAsyncUtil', - data: { - name: node.name, - }, - }); - return; - } - } - } - } else if (functionWrappersNames.includes(node.name)) { - // check async queries used within a wrapper previously detected - if (!isPromiseHandled(node)) { - context.report({ - node, - messageId: 'asyncUtilWrapper', - data: { name: node.name }, - }); - } - } - }, - }; - }, + if (references.length === 0) { + if (!isPromiseHandled(node)) { + context.report({ + node, + messageId: 'awaitAsyncUtil', + data: { + name: node.name, + }, + }); + } + } else { + for (const reference of references) { + const referenceNode = reference.identifier as TSESTree.Identifier; + if (!isPromiseHandled(referenceNode)) { + context.report({ + node, + messageId: 'awaitAsyncUtil', + data: { + name: node.name, + }, + }); + return; + } + } + } + } else if (functionWrappersNames.includes(node.name)) { + // check async queries used within a wrapper previously detected + if (!isPromiseHandled(node)) { + context.report({ + node, + messageId: 'asyncUtilWrapper', + data: { name: node.name }, + }); + } + } + }, + }; + }, }); diff --git a/lib/rules/await-fire-event.ts b/lib/rules/await-fire-event.ts index 3dfdc6cf..bfc298e0 100644 --- a/lib/rules/await-fire-event.ts +++ b/lib/rules/await-fire-event.ts @@ -2,11 +2,11 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - findClosestCallExpressionNode, - getFunctionName, - getInnermostReturningFunction, - getVariableReferences, - isPromiseHandled, + findClosestCallExpressionNode, + getFunctionName, + getInnermostReturningFunction, + getVariableReferences, + isPromiseHandled, } from '../node-utils'; export const RULE_NAME = 'await-fire-event'; @@ -14,100 +14,100 @@ 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: [], + 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[] = []; + 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 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); + function detectFireEventMethodWrapper(node: TSESTree.Identifier): void { + const innerFunction = getInnermostReturningFunction(context, node); - if (innerFunction) { - functionWrappersNames.push(getFunctionName(innerFunction)); - } - } + if (innerFunction) { + functionWrappersNames.push(getFunctionName(innerFunction)); + } + } - return { - 'CallExpression Identifier'(node: TSESTree.Identifier) { - if (helpers.isFireEventMethod(node)) { - detectFireEventMethodWrapper(node); + return { + 'CallExpression Identifier'(node: TSESTree.Identifier) { + if (helpers.isFireEventMethod(node)) { + detectFireEventMethodWrapper(node); - const closestCallExpression = findClosestCallExpressionNode( - node, - true - ); + const closestCallExpression = findClosestCallExpressionNode( + node, + true + ); - if (!closestCallExpression || !closestCallExpression.parent) { - return; - } + if (!closestCallExpression || !closestCallExpression.parent) { + return; + } - const references = getVariableReferences( - context, - closestCallExpression.parent - ); + 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 (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; - } + if (!closestCallExpression) { + return; + } - reportUnhandledNode(node, closestCallExpression, 'fireEventWrapper'); - } - }, - }; - }, + reportUnhandledNode(node, closestCallExpression, 'fireEventWrapper'); + } + }, + }; + }, }); diff --git a/lib/rules/consistent-data-testid.ts b/lib/rules/consistent-data-testid.ts index 42333fe8..54d7d23b 100644 --- a/lib/rules/consistent-data-testid.ts +++ b/lib/rules/consistent-data-testid.ts @@ -4,123 +4,123 @@ import { isJSXAttribute, isLiteral } from '../node-utils'; export const RULE_NAME = 'consistent-data-testid'; export type MessageIds = 'consistentDataTestId'; export type Options = [ - { - testIdAttribute?: string[] | string; - testIdPattern: string; - } + { + testIdAttribute?: string[] | string; + testIdPattern: string; + } ]; const FILENAME_PLACEHOLDER = '{fileName}'; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: 'Ensures consistent usage of `data-testid`', - recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: false, - marko: false, - }, - }, - messages: { - consistentDataTestId: '`{{attr}}` "{{value}}" should match `{{regex}}`', - }, - schema: [ - { - type: 'object', - default: {}, - additionalProperties: false, - required: ['testIdPattern'], - properties: { - testIdPattern: { - type: 'string', - }, - testIdAttribute: { - default: 'data-testid', - oneOf: [ - { - type: 'string', - }, - { - type: 'array', - items: { - type: 'string', - }, - }, - ], - }, - }, - }, - ], - }, - defaultOptions: [ - { - testIdPattern: '', - testIdAttribute: 'data-testid', - }, - ], - detectionOptions: { - skipRuleReportingCheck: true, - }, + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: 'Ensures consistent usage of `data-testid`', + recommendedConfig: { + dom: false, + angular: false, + react: false, + vue: false, + marko: false, + }, + }, + messages: { + consistentDataTestId: '`{{attr}}` "{{value}}" should match `{{regex}}`', + }, + schema: [ + { + type: 'object', + default: {}, + additionalProperties: false, + required: ['testIdPattern'], + properties: { + testIdPattern: { + type: 'string', + }, + testIdAttribute: { + default: 'data-testid', + oneOf: [ + { + type: 'string', + }, + { + type: 'array', + items: { + type: 'string', + }, + }, + ], + }, + }, + }, + ], + }, + defaultOptions: [ + { + testIdPattern: '', + testIdAttribute: 'data-testid', + }, + ], + detectionOptions: { + skipRuleReportingCheck: true, + }, - create: (context, [options]) => { - const { getFilename } = context; - const { testIdPattern, testIdAttribute: attr } = options; + create: (context, [options]) => { + const { getFilename } = context; + const { testIdPattern, testIdAttribute: attr } = options; - function getFileNameData() { - const splitPath = getFilename().split('/'); - const fileNameWithExtension = splitPath.pop() ?? ''; - const parent = splitPath.pop(); - const fileName = fileNameWithExtension.split('.').shift(); + function getFileNameData() { + const splitPath = getFilename().split('/'); + const fileNameWithExtension = splitPath.pop() ?? ''; + const parent = splitPath.pop(); + const fileName = fileNameWithExtension.split('.').shift(); - return { - fileName: fileName === 'index' ? parent : fileName, - }; - } + return { + fileName: fileName === 'index' ? parent : fileName, + }; + } - function getTestIdValidator(fileName: string) { - return new RegExp(testIdPattern.replace(FILENAME_PLACEHOLDER, fileName)); - } + function getTestIdValidator(fileName: string) { + return new RegExp(testIdPattern.replace(FILENAME_PLACEHOLDER, fileName)); + } - function isTestIdAttribute(name: string): boolean { - if (typeof attr === 'string') { - return attr === name; - } else { - return attr?.includes(name) ?? false; - } - } + function isTestIdAttribute(name: string): boolean { + if (typeof attr === 'string') { + return attr === name; + } else { + return attr?.includes(name) ?? false; + } + } - return { - JSXIdentifier: (node) => { - if ( - !node.parent || - !isJSXAttribute(node.parent) || - !isLiteral(node.parent.value) || - !isTestIdAttribute(node.name) - ) { - return; - } + return { + JSXIdentifier: (node) => { + if ( + !node.parent || + !isJSXAttribute(node.parent) || + !isLiteral(node.parent.value) || + !isTestIdAttribute(node.name) + ) { + return; + } - const value = node.parent.value.value; - const { fileName } = getFileNameData(); - const regex = getTestIdValidator(fileName ?? ''); + const value = node.parent.value.value; + const { fileName } = getFileNameData(); + const regex = getTestIdValidator(fileName ?? ''); - if (value && typeof value === 'string' && !regex.test(value)) { - context.report({ - node, - messageId: 'consistentDataTestId', - data: { - attr: node.name, - value, - regex, - }, - }); - } - }, - }; - }, + if (value && typeof value === 'string' && !regex.test(value)) { + context.report({ + node, + messageId: 'consistentDataTestId', + data: { + attr: node.name, + value, + regex, + }, + }); + } + }, + }; + }, }); diff --git a/lib/rules/index.ts b/lib/rules/index.ts index 7b20c3ce..7a340ac0 100644 --- a/lib/rules/index.ts +++ b/lib/rules/index.ts @@ -6,21 +6,21 @@ import { TSESLint } from '@typescript-eslint/utils'; import { importDefault, TestingLibraryRuleMeta } from '../utils'; type RuleModule = TSESLint.RuleModule & { - meta: TestingLibraryRuleMeta & { - recommended: false; - }; + meta: TestingLibraryRuleMeta & { + recommended: false; + }; }; const rulesDir = __dirname; const excludedFiles = ['index']; export default readdirSync(rulesDir) - .map((rule) => parse(rule).name) - .filter((ruleName) => !excludedFiles.includes(ruleName)) - .reduce>( - (allRules, ruleName) => ({ - ...allRules, - [ruleName]: importDefault(join(rulesDir, ruleName)), - }), - {} - ); + .map((rule) => parse(rule).name) + .filter((ruleName) => !excludedFiles.includes(ruleName)) + .reduce>( + (allRules, ruleName) => ({ + ...allRules, + [ruleName]: importDefault(join(rulesDir, ruleName)), + }), + {} + ); diff --git a/lib/rules/no-await-sync-events.ts b/lib/rules/no-await-sync-events.ts index 4202cd91..8c09b05f 100644 --- a/lib/rules/no-await-sync-events.ts +++ b/lib/rules/no-await-sync-events.ts @@ -2,11 +2,11 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getDeepestIdentifierNode, - getPropertyIdentifierNode, - isLiteral, - isObjectExpression, - isProperty, + getDeepestIdentifierNode, + getPropertyIdentifierNode, + isLiteral, + isObjectExpression, + isProperty, } from '../node-utils'; const USER_EVENT_ASYNC_EXCEPTIONS: string[] = ['type', 'keyboard']; @@ -15,113 +15,113 @@ const VALID_EVENT_MODULES = ['fire-event', 'user-event'] as const; export const RULE_NAME = 'no-await-sync-events'; export type MessageIds = 'noAwaitSyncEvents'; type Options = [ - { eventModules?: readonly typeof VALID_EVENT_MODULES[number][] } + { eventModules?: readonly typeof VALID_EVENT_MODULES[number][] } ]; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Disallow unnecessary `await` for sync events', - recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: false, - marko: false, - }, - }, - messages: { - noAwaitSyncEvents: - '`{{ name }}` is sync and does not need `await` operator', - }, - schema: [ - { - type: 'object', - properties: { - eventModules: { - type: 'array', - minItems: 1, - items: { - enum: VALID_EVENT_MODULES, - }, - }, - }, - additionalProperties: false, - }, - ], - }, - defaultOptions: [{ eventModules: VALID_EVENT_MODULES }], + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Disallow unnecessary `await` for sync events', + recommendedConfig: { + dom: false, + angular: false, + react: false, + vue: false, + marko: false, + }, + }, + messages: { + noAwaitSyncEvents: + '`{{ name }}` is sync and does not need `await` operator', + }, + schema: [ + { + type: 'object', + properties: { + eventModules: { + type: 'array', + minItems: 1, + items: { + enum: VALID_EVENT_MODULES, + }, + }, + }, + additionalProperties: false, + }, + ], + }, + defaultOptions: [{ eventModules: VALID_EVENT_MODULES }], - create(context, [options], helpers) { - const { eventModules = VALID_EVENT_MODULES } = options; + create(context, [options], helpers) { + const { eventModules = VALID_EVENT_MODULES } = options; - // userEvent.type() and userEvent.keyboard() are exceptions, which returns a - // Promise. But it is only necessary to wait when delay option other than 0 - // is specified. So this rule has a special exception for the case await: - // - userEvent.type(element, 'abc', {delay: 1234}) - // - userEvent.keyboard('abc', {delay: 1234}) - return { - 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { - const simulateEventFunctionIdentifier = getDeepestIdentifierNode(node); + // userEvent.type() and userEvent.keyboard() are exceptions, which returns a + // Promise. But it is only necessary to wait when delay option other than 0 + // is specified. So this rule has a special exception for the case await: + // - userEvent.type(element, 'abc', {delay: 1234}) + // - userEvent.keyboard('abc', {delay: 1234}) + return { + 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { + const simulateEventFunctionIdentifier = getDeepestIdentifierNode(node); - if (!simulateEventFunctionIdentifier) { - return; - } + if (!simulateEventFunctionIdentifier) { + return; + } - const isUserEventMethod = helpers.isUserEventMethod( - simulateEventFunctionIdentifier - ); - const isFireEventMethod = helpers.isFireEventMethod( - simulateEventFunctionIdentifier - ); - const isSimulateEventMethod = isUserEventMethod || isFireEventMethod; + const isUserEventMethod = helpers.isUserEventMethod( + simulateEventFunctionIdentifier + ); + const isFireEventMethod = helpers.isFireEventMethod( + simulateEventFunctionIdentifier + ); + const isSimulateEventMethod = isUserEventMethod || isFireEventMethod; - if (!isSimulateEventMethod) { - return; - } + if (!isSimulateEventMethod) { + return; + } - if (isFireEventMethod && !eventModules.includes('fire-event')) { - return; - } - if (isUserEventMethod && !eventModules.includes('user-event')) { - return; - } + if (isFireEventMethod && !eventModules.includes('fire-event')) { + return; + } + if (isUserEventMethod && !eventModules.includes('user-event')) { + return; + } - const lastArg = node.arguments[node.arguments.length - 1]; + const lastArg = node.arguments[node.arguments.length - 1]; - const hasDelay = - isObjectExpression(lastArg) && - lastArg.properties.some( - (property) => - isProperty(property) && - ASTUtils.isIdentifier(property.key) && - property.key.name === 'delay' && - isLiteral(property.value) && - !!property.value.value && - property.value.value > 0 - ); + const hasDelay = + isObjectExpression(lastArg) && + lastArg.properties.some( + (property) => + isProperty(property) && + ASTUtils.isIdentifier(property.key) && + property.key.name === 'delay' && + isLiteral(property.value) && + !!property.value.value && + property.value.value > 0 + ); - const simulateEventFunctionName = simulateEventFunctionIdentifier.name; + const simulateEventFunctionName = simulateEventFunctionIdentifier.name; - if ( - USER_EVENT_ASYNC_EXCEPTIONS.includes(simulateEventFunctionName) && - hasDelay - ) { - return; - } + if ( + USER_EVENT_ASYNC_EXCEPTIONS.includes(simulateEventFunctionName) && + hasDelay + ) { + return; + } - context.report({ - node, - messageId: 'noAwaitSyncEvents', - data: { - name: `${ - getPropertyIdentifierNode(node)?.name - }.${simulateEventFunctionName}`, - }, - }); - }, - }; - }, + context.report({ + node, + messageId: 'noAwaitSyncEvents', + data: { + name: `${ + getPropertyIdentifierNode(node)?.name + }.${simulateEventFunctionName}`, + }, + }); + }, + }; + }, }); diff --git a/lib/rules/no-await-sync-query.ts b/lib/rules/no-await-sync-query.ts index 25daffa5..70c86e8b 100644 --- a/lib/rules/no-await-sync-query.ts +++ b/lib/rules/no-await-sync-query.ts @@ -8,46 +8,46 @@ export type MessageIds = 'noAwaitSyncQuery'; type Options = []; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Disallow unnecessary `await` for sync queries', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - noAwaitSyncQuery: - '`{{ name }}` query is sync so it does not need to be awaited', - }, - schema: [], - }, - defaultOptions: [], + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Disallow unnecessary `await` for sync queries', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + noAwaitSyncQuery: + '`{{ name }}` query is sync so it does not need to be awaited', + }, + schema: [], + }, + defaultOptions: [], - create(context, _, helpers) { - return { - 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { - const deepestIdentifierNode = getDeepestIdentifierNode(node); + create(context, _, helpers) { + return { + 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { + const deepestIdentifierNode = getDeepestIdentifierNode(node); - if (!deepestIdentifierNode) { - return; - } + if (!deepestIdentifierNode) { + return; + } - if (helpers.isSyncQuery(deepestIdentifierNode)) { - context.report({ - node: deepestIdentifierNode, - messageId: 'noAwaitSyncQuery', - data: { - name: deepestIdentifierNode.name, - }, - }); - } - }, - }; - }, + if (helpers.isSyncQuery(deepestIdentifierNode)) { + context.report({ + node: deepestIdentifierNode, + messageId: 'noAwaitSyncQuery', + data: { + name: deepestIdentifierNode.name, + }, + }); + } + }, + }; + }, }); diff --git a/lib/rules/no-container.ts b/lib/rules/no-container.ts index 56400f2e..54423db5 100644 --- a/lib/rules/no-container.ts +++ b/lib/rules/no-container.ts @@ -2,12 +2,12 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getDeepestIdentifierNode, - getFunctionName, - getInnermostReturningFunction, - isMemberExpression, - isObjectPattern, - isProperty, + getDeepestIdentifierNode, + getFunctionName, + getInnermostReturningFunction, + isMemberExpression, + isObjectPattern, + isProperty, } from '../node-utils'; export const RULE_NAME = 'no-container'; @@ -15,152 +15,152 @@ export type MessageIds = 'noContainer'; type Options = []; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Disallow the use of `container` methods', - recommendedConfig: { - dom: false, - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - noContainer: - 'Avoid using container methods. Prefer using the methods from Testing Library, such as "getByRole()"', - }, - schema: [], - }, - defaultOptions: [], - - create(context, _, helpers) { - const destructuredContainerPropNames: string[] = []; - const renderWrapperNames: string[] = []; - let renderResultVarName: string | null = null; - let containerName: string | null = null; - let containerCallsMethod = false; - - function detectRenderWrapper(node: TSESTree.Identifier): void { - const innerFunction = getInnermostReturningFunction(context, node); - - if (innerFunction) { - renderWrapperNames.push(getFunctionName(innerFunction)); - } - } - - function showErrorIfChainedContainerMethod( - innerNode: TSESTree.MemberExpression - ) { - if (isMemberExpression(innerNode)) { - if (ASTUtils.isIdentifier(innerNode.object)) { - const isContainerName = innerNode.object.name === containerName; - - if (isContainerName) { - context.report({ - node: innerNode, - messageId: 'noContainer', - }); - return; - } - - const isRenderWrapper = innerNode.object.name === renderResultVarName; - containerCallsMethod = - ASTUtils.isIdentifier(innerNode.property) && - innerNode.property.name === 'container' && - isRenderWrapper; - - if (containerCallsMethod) { - context.report({ - node: innerNode.property, - messageId: 'noContainer', - }); - return; - } - } - showErrorIfChainedContainerMethod( - innerNode.object as TSESTree.MemberExpression - ); - } - } - - return { - CallExpression(node) { - const callExpressionIdentifier = getDeepestIdentifierNode(node); - - if (!callExpressionIdentifier) { - return; - } - - if (helpers.isRenderUtil(callExpressionIdentifier)) { - detectRenderWrapper(callExpressionIdentifier); - } - - if (isMemberExpression(node.callee)) { - showErrorIfChainedContainerMethod(node.callee); - } else if ( - ASTUtils.isIdentifier(node.callee) && - destructuredContainerPropNames.includes(node.callee.name) - ) { - context.report({ - node, - messageId: 'noContainer', - }); - } - }, - - VariableDeclarator(node) { - if (!node.init) { - return; - } - const initIdentifierNode = getDeepestIdentifierNode(node.init); - - if (!initIdentifierNode) { - return; - } - - const isRenderWrapperVariableDeclarator = renderWrapperNames.includes( - initIdentifierNode.name - ); - - if ( - !helpers.isRenderVariableDeclarator(node) && - !isRenderWrapperVariableDeclarator - ) { - return; - } - - if (isObjectPattern(node.id)) { - const containerIndex = node.id.properties.findIndex( - (property) => - isProperty(property) && - ASTUtils.isIdentifier(property.key) && - property.key.name === 'container' - ); - - const nodeValue = - containerIndex !== -1 && node.id.properties[containerIndex].value; - - if (!nodeValue) { - return; - } - - if (ASTUtils.isIdentifier(nodeValue)) { - containerName = nodeValue.name; - } else if (isObjectPattern(nodeValue)) { - nodeValue.properties.forEach( - (property) => - isProperty(property) && - ASTUtils.isIdentifier(property.key) && - destructuredContainerPropNames.push(property.key.name) - ); - } - } else if (ASTUtils.isIdentifier(node.id)) { - renderResultVarName = node.id.name; - } - }, - }; - }, + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Disallow the use of `container` methods', + recommendedConfig: { + dom: false, + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + noContainer: + 'Avoid using container methods. Prefer using the methods from Testing Library, such as "getByRole()"', + }, + schema: [], + }, + defaultOptions: [], + + create(context, _, helpers) { + const destructuredContainerPropNames: string[] = []; + const renderWrapperNames: string[] = []; + let renderResultVarName: string | null = null; + let containerName: string | null = null; + let containerCallsMethod = false; + + function detectRenderWrapper(node: TSESTree.Identifier): void { + const innerFunction = getInnermostReturningFunction(context, node); + + if (innerFunction) { + renderWrapperNames.push(getFunctionName(innerFunction)); + } + } + + function showErrorIfChainedContainerMethod( + innerNode: TSESTree.MemberExpression + ) { + if (isMemberExpression(innerNode)) { + if (ASTUtils.isIdentifier(innerNode.object)) { + const isContainerName = innerNode.object.name === containerName; + + if (isContainerName) { + context.report({ + node: innerNode, + messageId: 'noContainer', + }); + return; + } + + const isRenderWrapper = innerNode.object.name === renderResultVarName; + containerCallsMethod = + ASTUtils.isIdentifier(innerNode.property) && + innerNode.property.name === 'container' && + isRenderWrapper; + + if (containerCallsMethod) { + context.report({ + node: innerNode.property, + messageId: 'noContainer', + }); + return; + } + } + showErrorIfChainedContainerMethod( + innerNode.object as TSESTree.MemberExpression + ); + } + } + + return { + CallExpression(node) { + const callExpressionIdentifier = getDeepestIdentifierNode(node); + + if (!callExpressionIdentifier) { + return; + } + + if (helpers.isRenderUtil(callExpressionIdentifier)) { + detectRenderWrapper(callExpressionIdentifier); + } + + if (isMemberExpression(node.callee)) { + showErrorIfChainedContainerMethod(node.callee); + } else if ( + ASTUtils.isIdentifier(node.callee) && + destructuredContainerPropNames.includes(node.callee.name) + ) { + context.report({ + node, + messageId: 'noContainer', + }); + } + }, + + VariableDeclarator(node) { + if (!node.init) { + return; + } + const initIdentifierNode = getDeepestIdentifierNode(node.init); + + if (!initIdentifierNode) { + return; + } + + const isRenderWrapperVariableDeclarator = renderWrapperNames.includes( + initIdentifierNode.name + ); + + if ( + !helpers.isRenderVariableDeclarator(node) && + !isRenderWrapperVariableDeclarator + ) { + return; + } + + if (isObjectPattern(node.id)) { + const containerIndex = node.id.properties.findIndex( + (property) => + isProperty(property) && + ASTUtils.isIdentifier(property.key) && + property.key.name === 'container' + ); + + const nodeValue = + containerIndex !== -1 && node.id.properties[containerIndex].value; + + if (!nodeValue) { + return; + } + + if (ASTUtils.isIdentifier(nodeValue)) { + containerName = nodeValue.name; + } else if (isObjectPattern(nodeValue)) { + nodeValue.properties.forEach( + (property) => + isProperty(property) && + ASTUtils.isIdentifier(property.key) && + destructuredContainerPropNames.push(property.key.name) + ); + } + } else if (ASTUtils.isIdentifier(node.id)) { + renderResultVarName = node.id.name; + } + }, + }; + }, }); diff --git a/lib/rules/no-debugging-utils.ts b/lib/rules/no-debugging-utils.ts index ce39e6c0..83e97f0a 100644 --- a/lib/rules/no-debugging-utils.ts +++ b/lib/rules/no-debugging-utils.ts @@ -2,19 +2,19 @@ import { ASTUtils, TSESTree, JSONSchema } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getDeepestIdentifierNode, - getFunctionName, - getInnermostReturningFunction, - getPropertyIdentifierNode, - getReferenceNode, - isCallExpression, - isObjectPattern, - isProperty, + getDeepestIdentifierNode, + getFunctionName, + getInnermostReturningFunction, + getPropertyIdentifierNode, + getReferenceNode, + isCallExpression, + isObjectPattern, + isProperty, } from '../node-utils'; import { DEBUG_UTILS } from '../utils'; type DebugUtilsToCheckFor = Partial< - Record + Record >; export const RULE_NAME = 'no-debugging-utils'; @@ -22,172 +22,172 @@ export type MessageIds = 'noDebug'; type Options = [{ utilsToCheckFor?: DebugUtilsToCheckFor }]; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Disallow the use of debugging utilities like `debug`', - recommendedConfig: { - dom: false, - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - noDebug: 'Unexpected debug statement', - }, - schema: [ - { - type: 'object', - properties: { - utilsToCheckFor: { - type: 'object', - properties: DEBUG_UTILS.reduce< - Record - >( - (obj, name) => ({ - [name]: { type: 'boolean' }, - ...obj, - }), - {} - ), - additionalProperties: false, - }, - }, - additionalProperties: false, - }, - ], - }, - defaultOptions: [ - { utilsToCheckFor: { debug: true, logTestingPlaygroundURL: true } }, - ], - - create(context, [{ utilsToCheckFor = {} }], helpers) { - const suspiciousDebugVariableNames: string[] = []; - const suspiciousReferenceNodes: TSESTree.Identifier[] = []; - const renderWrapperNames: string[] = []; - const builtInConsoleNodes: TSESTree.VariableDeclarator[] = []; - - const utilsToReport = Object.entries(utilsToCheckFor) - .filter(([, shouldCheckFor]) => shouldCheckFor) - .map(([name]) => name); - - function detectRenderWrapper(node: TSESTree.Identifier): void { - const innerFunction = getInnermostReturningFunction(context, node); - - if (innerFunction) { - renderWrapperNames.push(getFunctionName(innerFunction)); - } - } - - return { - VariableDeclarator(node) { - if (!node.init) { - return; - } - const initIdentifierNode = getDeepestIdentifierNode(node.init); - - if (!initIdentifierNode) { - return; - } - - if (initIdentifierNode.name === 'console') { - builtInConsoleNodes.push(node); - return; - } - - const isRenderWrapperVariableDeclarator = renderWrapperNames.includes( - initIdentifierNode.name - ); - - if ( - !helpers.isRenderVariableDeclarator(node) && - !isRenderWrapperVariableDeclarator - ) { - return; - } - - // find debug obtained from render and save their name, like: - // const { debug } = render(); - if (isObjectPattern(node.id)) { - for (const property of node.id.properties) { - if ( - isProperty(property) && - ASTUtils.isIdentifier(property.key) && - utilsToReport.includes(property.key.name) - ) { - const identifierNode = getDeepestIdentifierNode(property.value); - - if (identifierNode) { - suspiciousDebugVariableNames.push(identifierNode.name); - } - } - } - } - - // find utils kept from render and save their node, like: - // const utils = render(); - if (ASTUtils.isIdentifier(node.id)) { - suspiciousReferenceNodes.push(node.id); - } - }, - CallExpression(node) { - const callExpressionIdentifier = getDeepestIdentifierNode(node); - - if (!callExpressionIdentifier) { - return; - } - - if (helpers.isRenderUtil(callExpressionIdentifier)) { - detectRenderWrapper(callExpressionIdentifier); - } - - const referenceNode = getReferenceNode(node); - const referenceIdentifier = getPropertyIdentifierNode(referenceNode); - - if (!referenceIdentifier) { - return; - } - - const isDebugUtil = helpers.isDebugUtil( - callExpressionIdentifier, - utilsToReport as Array - ); - const isDeclaredDebugVariable = suspiciousDebugVariableNames.includes( - callExpressionIdentifier.name - ); - const isChainedReferenceDebug = suspiciousReferenceNodes.some( - (suspiciousReferenceIdentifier) => { - return ( - utilsToReport.includes(callExpressionIdentifier.name) && - suspiciousReferenceIdentifier.name === referenceIdentifier.name - ); - } - ); - - const isVariableFromBuiltInConsole = builtInConsoleNodes.some( - (variableDeclarator) => { - const variables = context.getDeclaredVariables(variableDeclarator); - return variables.some( - ({ name }) => - name === callExpressionIdentifier.name && - isCallExpression(callExpressionIdentifier.parent) - ); - } - ); - - if ( - !isVariableFromBuiltInConsole && - (isDebugUtil || isDeclaredDebugVariable || isChainedReferenceDebug) - ) { - context.report({ - node: callExpressionIdentifier, - messageId: 'noDebug', - }); - } - }, - }; - }, + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Disallow the use of debugging utilities like `debug`', + recommendedConfig: { + dom: false, + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + noDebug: 'Unexpected debug statement', + }, + schema: [ + { + type: 'object', + properties: { + utilsToCheckFor: { + type: 'object', + properties: DEBUG_UTILS.reduce< + Record + >( + (obj, name) => ({ + [name]: { type: 'boolean' }, + ...obj, + }), + {} + ), + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + ], + }, + defaultOptions: [ + { utilsToCheckFor: { debug: true, logTestingPlaygroundURL: true } }, + ], + + create(context, [{ utilsToCheckFor = {} }], helpers) { + const suspiciousDebugVariableNames: string[] = []; + const suspiciousReferenceNodes: TSESTree.Identifier[] = []; + const renderWrapperNames: string[] = []; + const builtInConsoleNodes: TSESTree.VariableDeclarator[] = []; + + const utilsToReport = Object.entries(utilsToCheckFor) + .filter(([, shouldCheckFor]) => shouldCheckFor) + .map(([name]) => name); + + function detectRenderWrapper(node: TSESTree.Identifier): void { + const innerFunction = getInnermostReturningFunction(context, node); + + if (innerFunction) { + renderWrapperNames.push(getFunctionName(innerFunction)); + } + } + + return { + VariableDeclarator(node) { + if (!node.init) { + return; + } + const initIdentifierNode = getDeepestIdentifierNode(node.init); + + if (!initIdentifierNode) { + return; + } + + if (initIdentifierNode.name === 'console') { + builtInConsoleNodes.push(node); + return; + } + + const isRenderWrapperVariableDeclarator = renderWrapperNames.includes( + initIdentifierNode.name + ); + + if ( + !helpers.isRenderVariableDeclarator(node) && + !isRenderWrapperVariableDeclarator + ) { + return; + } + + // find debug obtained from render and save their name, like: + // const { debug } = render(); + if (isObjectPattern(node.id)) { + for (const property of node.id.properties) { + if ( + isProperty(property) && + ASTUtils.isIdentifier(property.key) && + utilsToReport.includes(property.key.name) + ) { + const identifierNode = getDeepestIdentifierNode(property.value); + + if (identifierNode) { + suspiciousDebugVariableNames.push(identifierNode.name); + } + } + } + } + + // find utils kept from render and save their node, like: + // const utils = render(); + if (ASTUtils.isIdentifier(node.id)) { + suspiciousReferenceNodes.push(node.id); + } + }, + CallExpression(node) { + const callExpressionIdentifier = getDeepestIdentifierNode(node); + + if (!callExpressionIdentifier) { + return; + } + + if (helpers.isRenderUtil(callExpressionIdentifier)) { + detectRenderWrapper(callExpressionIdentifier); + } + + const referenceNode = getReferenceNode(node); + const referenceIdentifier = getPropertyIdentifierNode(referenceNode); + + if (!referenceIdentifier) { + return; + } + + const isDebugUtil = helpers.isDebugUtil( + callExpressionIdentifier, + utilsToReport as Array + ); + const isDeclaredDebugVariable = suspiciousDebugVariableNames.includes( + callExpressionIdentifier.name + ); + const isChainedReferenceDebug = suspiciousReferenceNodes.some( + (suspiciousReferenceIdentifier) => { + return ( + utilsToReport.includes(callExpressionIdentifier.name) && + suspiciousReferenceIdentifier.name === referenceIdentifier.name + ); + } + ); + + const isVariableFromBuiltInConsole = builtInConsoleNodes.some( + (variableDeclarator) => { + const variables = context.getDeclaredVariables(variableDeclarator); + return variables.some( + ({ name }) => + name === callExpressionIdentifier.name && + isCallExpression(callExpressionIdentifier.parent) + ); + } + ); + + if ( + !isVariableFromBuiltInConsole && + (isDebugUtil || isDeclaredDebugVariable || isChainedReferenceDebug) + ) { + context.report({ + node: callExpressionIdentifier, + messageId: 'noDebug', + }); + } + }, + }; + }, }); diff --git a/lib/rules/no-dom-import.ts b/lib/rules/no-dom-import.ts index 1b180ec0..887075f1 100644 --- a/lib/rules/no-dom-import.ts +++ b/lib/rules/no-dom-import.ts @@ -8,99 +8,99 @@ export type MessageIds = 'noDomImport' | 'noDomImportFramework'; type Options = [string]; const DOM_TESTING_LIBRARY_MODULES = [ - 'dom-testing-library', - '@testing-library/dom', + 'dom-testing-library', + '@testing-library/dom', ]; const correctModuleNameByFramework = { - angular: '@testing-library/angular', // ATL is *always* called `@testing-library/angular` - marko: '@marko/testing-library', // Marko TL is called `@marko/testing-library` + angular: '@testing-library/angular', // ATL is *always* called `@testing-library/angular` + marko: '@marko/testing-library', // Marko TL is called `@marko/testing-library` }; const getCorrectModuleName = (moduleName: string, framework: string): string => - correctModuleNameByFramework[framework] || - moduleName.replace('dom', framework); + correctModuleNameByFramework[framework] || + moduleName.replace('dom', framework); export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Disallow importing from DOM Testing Library', - recommendedConfig: { - dom: false, - angular: ['error', 'angular'], - react: ['error', 'react'], - vue: ['error', 'vue'], - marko: ['error', 'marko'], - }, - }, - messages: { - noDomImport: - 'import from DOM Testing Library is restricted, import from corresponding Testing Library framework instead', - noDomImportFramework: - 'import from DOM Testing Library is restricted, import from {{module}} instead', - }, - fixable: 'code', - schema: [{ type: 'string' }], - }, - defaultOptions: [''], + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Disallow importing from DOM Testing Library', + recommendedConfig: { + dom: false, + angular: ['error', 'angular'], + react: ['error', 'react'], + vue: ['error', 'vue'], + marko: ['error', 'marko'], + }, + }, + messages: { + noDomImport: + 'import from DOM Testing Library is restricted, import from corresponding Testing Library framework instead', + noDomImportFramework: + 'import from DOM Testing Library is restricted, import from {{module}} instead', + }, + fixable: 'code', + schema: [{ type: 'string' }], + }, + defaultOptions: [''], - create(context, [framework], helpers) { - function report( - node: TSESTree.CallExpression | TSESTree.ImportDeclaration, - moduleName: string - ) { - if (!framework) { - return context.report({ - node, - messageId: 'noDomImport', - }); - } + create(context, [framework], helpers) { + function report( + node: TSESTree.CallExpression | TSESTree.ImportDeclaration, + moduleName: string + ) { + if (!framework) { + return context.report({ + node, + messageId: 'noDomImport', + }); + } - const correctModuleName = getCorrectModuleName(moduleName, framework); - context.report({ - data: { module: correctModuleName }, - fix(fixer) { - if (isCallExpression(node)) { - const name = node.arguments[0] as TSESTree.Literal; + const correctModuleName = getCorrectModuleName(moduleName, framework); + context.report({ + data: { module: correctModuleName }, + fix(fixer) { + if (isCallExpression(node)) { + const name = node.arguments[0] as TSESTree.Literal; - // Replace the module name with the raw module name as we can't predict which punctuation the user is going to use - return fixer.replaceText( - name, - name.raw.replace(moduleName, correctModuleName) - ); - } else { - const name = node.source; - return fixer.replaceText( - name, - name.raw.replace(moduleName, correctModuleName) - ); - } - }, - messageId: 'noDomImportFramework', - node, - }); - } + // Replace the module name with the raw module name as we can't predict which punctuation the user is going to use + return fixer.replaceText( + name, + name.raw.replace(moduleName, correctModuleName) + ); + } else { + const name = node.source; + return fixer.replaceText( + name, + name.raw.replace(moduleName, correctModuleName) + ); + } + }, + messageId: 'noDomImportFramework', + node, + }); + } - return { - 'Program:exit'() { - const importName = helpers.getTestingLibraryImportName(); - const importNode = helpers.getTestingLibraryImportNode(); + return { + 'Program:exit'() { + const importName = helpers.getTestingLibraryImportName(); + const importNode = helpers.getTestingLibraryImportNode(); - if (!importNode) { - return; - } + if (!importNode) { + return; + } - const domModuleName = DOM_TESTING_LIBRARY_MODULES.find( - (module) => module === importName - ); + const domModuleName = DOM_TESTING_LIBRARY_MODULES.find( + (module) => module === importName + ); - if (!domModuleName) { - return; - } + if (!domModuleName) { + return; + } - report(importNode, domModuleName); - }, - }; - }, + report(importNode, domModuleName); + }, + }; + }, }); diff --git a/lib/rules/no-global-regexp-flag-in-query.ts b/lib/rules/no-global-regexp-flag-in-query.ts index ed2400e6..9bf9a2ef 100644 --- a/lib/rules/no-global-regexp-flag-in-query.ts +++ b/lib/rules/no-global-regexp-flag-in-query.ts @@ -2,12 +2,12 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - isMemberExpression, - isCallExpression, - isProperty, - isObjectExpression, - getDeepestIdentifierNode, - isLiteral, + isMemberExpression, + isCallExpression, + isProperty, + isObjectExpression, + getDeepestIdentifierNode, + isLiteral, } from '../node-utils'; export const RULE_NAME = 'no-global-regexp-flag-in-query'; @@ -15,95 +15,95 @@ export type MessageIds = 'noGlobalRegExpFlagInQuery'; type Options = []; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: 'Disallow the use of the global RegExp flag (/g) in queries', - recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: false, - marko: false, - }, - }, - messages: { - noGlobalRegExpFlagInQuery: - 'Avoid using the global RegExp flag (/g) in queries', - }, - fixable: 'code', - schema: [], - }, - defaultOptions: [], - create(context, _, helpers) { - function report(literalNode: TSESTree.Node) { - if ( - isLiteral(literalNode) && - 'regex' in literalNode && - literalNode.regex.flags.includes('g') - ) { - context.report({ - node: literalNode, - messageId: 'noGlobalRegExpFlagInQuery', - fix(fixer) { - const splitter = literalNode.raw.lastIndexOf('/'); - const raw = literalNode.raw.substring(0, splitter); - const flags = literalNode.raw.substring(splitter + 1); - const flagsWithoutGlobal = flags.replace('g', ''); + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: 'Disallow the use of the global RegExp flag (/g) in queries', + recommendedConfig: { + dom: false, + angular: false, + react: false, + vue: false, + marko: false, + }, + }, + messages: { + noGlobalRegExpFlagInQuery: + 'Avoid using the global RegExp flag (/g) in queries', + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + create(context, _, helpers) { + function report(literalNode: TSESTree.Node) { + if ( + isLiteral(literalNode) && + 'regex' in literalNode && + literalNode.regex.flags.includes('g') + ) { + context.report({ + node: literalNode, + messageId: 'noGlobalRegExpFlagInQuery', + fix(fixer) { + const splitter = literalNode.raw.lastIndexOf('/'); + const raw = literalNode.raw.substring(0, splitter); + const flags = literalNode.raw.substring(splitter + 1); + const flagsWithoutGlobal = flags.replace('g', ''); - return fixer.replaceText( - literalNode, - `${raw}/${flagsWithoutGlobal}` - ); - }, - }); - return true; - } - return false; - } + return fixer.replaceText( + literalNode, + `${raw}/${flagsWithoutGlobal}` + ); + }, + }); + return true; + } + return false; + } - function getArguments(identifierNode: TSESTree.Identifier) { - if (isCallExpression(identifierNode.parent)) { - return identifierNode.parent.arguments; - } else if ( - isMemberExpression(identifierNode.parent) && - isCallExpression(identifierNode.parent.parent) - ) { - return identifierNode.parent.parent.arguments; - } + function getArguments(identifierNode: TSESTree.Identifier) { + if (isCallExpression(identifierNode.parent)) { + return identifierNode.parent.arguments; + } else if ( + isMemberExpression(identifierNode.parent) && + isCallExpression(identifierNode.parent.parent) + ) { + return identifierNode.parent.parent.arguments; + } - return []; - } + return []; + } - return { - CallExpression(node) { - const identifierNode = getDeepestIdentifierNode(node); - if (!identifierNode || !helpers.isQuery(identifierNode)) { - return; - } + return { + CallExpression(node) { + const identifierNode = getDeepestIdentifierNode(node); + if (!identifierNode || !helpers.isQuery(identifierNode)) { + return; + } - const [firstArg, secondArg] = getArguments(identifierNode); + const [firstArg, secondArg] = getArguments(identifierNode); - const firstArgumentHasError = report(firstArg); - if (firstArgumentHasError) { - return; - } + const firstArgumentHasError = report(firstArg); + if (firstArgumentHasError) { + return; + } - if (isObjectExpression(secondArg)) { - const namePropertyNode = secondArg.properties.find( - (p) => - isProperty(p) && - ASTUtils.isIdentifier(p.key) && - p.key.name === 'name' && - isLiteral(p.value) - ) as TSESTree.Property | undefined; + if (isObjectExpression(secondArg)) { + const namePropertyNode = secondArg.properties.find( + (p) => + isProperty(p) && + ASTUtils.isIdentifier(p.key) && + p.key.name === 'name' && + isLiteral(p.value) + ) as TSESTree.Property | undefined; - if (namePropertyNode) { - report(namePropertyNode.value); - } - } - }, - }; - }, + if (namePropertyNode) { + report(namePropertyNode.value); + } + } + }, + }; + }, }); diff --git a/lib/rules/no-manual-cleanup.ts b/lib/rules/no-manual-cleanup.ts index 8c915bd1..4f8cb4c2 100644 --- a/lib/rules/no-manual-cleanup.ts +++ b/lib/rules/no-manual-cleanup.ts @@ -2,14 +2,14 @@ import { ASTUtils, TSESTree, TSESLint } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getVariableReferences, - isImportDefaultSpecifier, - isImportSpecifier, - isMemberExpression, - isObjectPattern, - isProperty, - ImportModuleNode, - isImportDeclaration, + getVariableReferences, + isImportDefaultSpecifier, + isImportSpecifier, + isMemberExpression, + isObjectPattern, + isProperty, + ImportModuleNode, + isImportDeclaration, } from '../node-utils'; export const RULE_NAME = 'no-manual-cleanup'; @@ -17,115 +17,115 @@ export type MessageIds = 'noManualCleanup'; type Options = []; const CLEANUP_LIBRARY_REGEXP = - /(@testing-library\/(preact|react|svelte|vue))|@marko\/testing-library/; + /(@testing-library\/(preact|react|svelte|vue))|@marko\/testing-library/; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Disallow the use of `cleanup`', - recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: false, - marko: false, - }, - }, - messages: { - noManualCleanup: - "`cleanup` is performed automatically by your test runner, you don't need manual cleanups.", - }, - schema: [], - }, - defaultOptions: [], + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Disallow the use of `cleanup`', + recommendedConfig: { + dom: false, + angular: false, + react: false, + vue: false, + marko: false, + }, + }, + messages: { + noManualCleanup: + "`cleanup` is performed automatically by your test runner, you don't need manual cleanups.", + }, + schema: [], + }, + defaultOptions: [], - create(context, _, helpers) { - function reportImportReferences(references: TSESLint.Scope.Reference[]) { - for (const reference of references) { - const utilsUsage = reference.identifier.parent; + create(context, _, helpers) { + function reportImportReferences(references: TSESLint.Scope.Reference[]) { + for (const reference of references) { + const utilsUsage = reference.identifier.parent; - if ( - utilsUsage && - isMemberExpression(utilsUsage) && - ASTUtils.isIdentifier(utilsUsage.property) && - utilsUsage.property.name === 'cleanup' - ) { - context.report({ - node: utilsUsage.property, - messageId: 'noManualCleanup', - }); - } - } - } + if ( + utilsUsage && + isMemberExpression(utilsUsage) && + ASTUtils.isIdentifier(utilsUsage.property) && + utilsUsage.property.name === 'cleanup' + ) { + context.report({ + node: utilsUsage.property, + messageId: 'noManualCleanup', + }); + } + } + } - function reportCandidateModule(moduleNode: ImportModuleNode) { - if (isImportDeclaration(moduleNode)) { - // case: import utils from 'testing-library-module' - if (isImportDefaultSpecifier(moduleNode.specifiers[0])) { - const { references } = context.getDeclaredVariables(moduleNode)[0]; + function reportCandidateModule(moduleNode: ImportModuleNode) { + if (isImportDeclaration(moduleNode)) { + // case: import utils from 'testing-library-module' + if (isImportDefaultSpecifier(moduleNode.specifiers[0])) { + const { references } = context.getDeclaredVariables(moduleNode)[0]; - reportImportReferences(references); - } + reportImportReferences(references); + } - // case: import { cleanup } from 'testing-library-module' - const cleanupSpecifier = moduleNode.specifiers.find( - (specifier) => - isImportSpecifier(specifier) && - specifier.imported.name === 'cleanup' - ); + // case: import { cleanup } from 'testing-library-module' + const cleanupSpecifier = moduleNode.specifiers.find( + (specifier) => + isImportSpecifier(specifier) && + specifier.imported.name === 'cleanup' + ); - if (cleanupSpecifier) { - context.report({ - node: cleanupSpecifier, - messageId: 'noManualCleanup', - }); - } - } else { - const declaratorNode = moduleNode.parent as TSESTree.VariableDeclarator; + if (cleanupSpecifier) { + context.report({ + node: cleanupSpecifier, + messageId: 'noManualCleanup', + }); + } + } else { + const declaratorNode = moduleNode.parent as TSESTree.VariableDeclarator; - if (isObjectPattern(declaratorNode.id)) { - // case: const { cleanup } = require('testing-library-module') - const cleanupProperty = declaratorNode.id.properties.find( - (property) => - isProperty(property) && - ASTUtils.isIdentifier(property.key) && - property.key.name === 'cleanup' - ); + if (isObjectPattern(declaratorNode.id)) { + // case: const { cleanup } = require('testing-library-module') + const cleanupProperty = declaratorNode.id.properties.find( + (property) => + isProperty(property) && + ASTUtils.isIdentifier(property.key) && + property.key.name === 'cleanup' + ); - if (cleanupProperty) { - context.report({ - node: cleanupProperty, - messageId: 'noManualCleanup', - }); - } - } else { - // case: const utils = require('testing-library-module') - const references = getVariableReferences(context, declaratorNode); - reportImportReferences(references); - } - } - } + if (cleanupProperty) { + context.report({ + node: cleanupProperty, + messageId: 'noManualCleanup', + }); + } + } else { + // case: const utils = require('testing-library-module') + const references = getVariableReferences(context, declaratorNode); + reportImportReferences(references); + } + } + } - return { - 'Program:exit'() { - const testingLibraryImportName = helpers.getTestingLibraryImportName(); - const testingLibraryImportNode = helpers.getTestingLibraryImportNode(); - const customModuleImportNode = helpers.getCustomModuleImportNode(); + return { + 'Program:exit'() { + const testingLibraryImportName = helpers.getTestingLibraryImportName(); + const testingLibraryImportNode = helpers.getTestingLibraryImportNode(); + const customModuleImportNode = helpers.getCustomModuleImportNode(); - if ( - testingLibraryImportName && - testingLibraryImportNode && - testingLibraryImportName.match(CLEANUP_LIBRARY_REGEXP) - ) { - reportCandidateModule(testingLibraryImportNode); - } + if ( + testingLibraryImportName && + testingLibraryImportNode && + testingLibraryImportName.match(CLEANUP_LIBRARY_REGEXP) + ) { + reportCandidateModule(testingLibraryImportNode); + } - if (customModuleImportNode) { - reportCandidateModule(customModuleImportNode); - } - }, - }; - }, + if (customModuleImportNode) { + reportCandidateModule(customModuleImportNode); + } + }, + }; + }, }); diff --git a/lib/rules/no-node-access.ts b/lib/rules/no-node-access.ts index cd5b7ea8..8d0b5302 100644 --- a/lib/rules/no-node-access.ts +++ b/lib/rules/no-node-access.ts @@ -8,68 +8,68 @@ export type MessageIds = 'noNodeAccess'; export type Options = [{ allowContainerFirstChild: boolean }]; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Disallow direct Node access', - recommendedConfig: { - dom: false, - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - noNodeAccess: - 'Avoid direct Node access. Prefer using the methods from Testing Library.', - }, - schema: [ - { - type: 'object', - properties: { - allowContainerFirstChild: { - type: 'boolean', - }, - }, - }, - ], - }, - defaultOptions: [ - { - allowContainerFirstChild: false, - }, - ], + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Disallow direct Node access', + recommendedConfig: { + dom: false, + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + noNodeAccess: + 'Avoid direct Node access. Prefer using the methods from Testing Library.', + }, + schema: [ + { + type: 'object', + properties: { + allowContainerFirstChild: { + type: 'boolean', + }, + }, + }, + ], + }, + defaultOptions: [ + { + allowContainerFirstChild: false, + }, + ], - create(context, [{ allowContainerFirstChild = false }], helpers) { - function showErrorForNodeAccess(node: TSESTree.MemberExpression) { - // This rule is so aggressive that can cause tons of false positives outside test files when Aggressive Reporting - // is enabled. Because of that, this rule will skip this mechanism and report only if some Testing Library package - // or custom one (set in utils-module Shared Setting) is found. - if (!helpers.isTestingLibraryImported(true)) { - return; - } + create(context, [{ allowContainerFirstChild = false }], helpers) { + function showErrorForNodeAccess(node: TSESTree.MemberExpression) { + // This rule is so aggressive that can cause tons of false positives outside test files when Aggressive Reporting + // is enabled. Because of that, this rule will skip this mechanism and report only if some Testing Library package + // or custom one (set in utils-module Shared Setting) is found. + if (!helpers.isTestingLibraryImported(true)) { + return; + } - if ( - ASTUtils.isIdentifier(node.property) && - ALL_RETURNING_NODES.includes(node.property.name) - ) { - if (allowContainerFirstChild && node.property.name === 'firstChild') { - return; - } + if ( + ASTUtils.isIdentifier(node.property) && + ALL_RETURNING_NODES.includes(node.property.name) + ) { + if (allowContainerFirstChild && node.property.name === 'firstChild') { + return; + } - context.report({ - node, - loc: node.property.loc.start, - messageId: 'noNodeAccess', - }); - } - } + context.report({ + node, + loc: node.property.loc.start, + messageId: 'noNodeAccess', + }); + } + } - return { - 'ExpressionStatement MemberExpression': showErrorForNodeAccess, - 'VariableDeclarator MemberExpression': showErrorForNodeAccess, - }; - }, + return { + 'ExpressionStatement MemberExpression': showErrorForNodeAccess, + 'VariableDeclarator MemberExpression': showErrorForNodeAccess, + }; + }, }); diff --git a/lib/rules/no-promise-in-fire-event.ts b/lib/rules/no-promise-in-fire-event.ts index bbaf1d65..c3c7eb4d 100644 --- a/lib/rules/no-promise-in-fire-event.ts +++ b/lib/rules/no-promise-in-fire-event.ts @@ -2,11 +2,11 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - findClosestCallExpressionNode, - getDeepestIdentifierNode, - isCallExpression, - isNewExpression, - isPromiseIdentifier, + findClosestCallExpressionNode, + getDeepestIdentifierNode, + isCallExpression, + isNewExpression, + isPromiseIdentifier, } from '../node-utils'; export const RULE_NAME = 'no-promise-in-fire-event'; @@ -14,101 +14,101 @@ export type MessageIds = 'noPromiseInFireEvent'; type Options = []; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: - 'Disallow the use of promises passed to a `fireEvent` method', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - noPromiseInFireEvent: - "A promise shouldn't be passed to a `fireEvent` method, instead pass the DOM element", - }, - schema: [], - }, - defaultOptions: [], + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: + 'Disallow the use of promises passed to a `fireEvent` method', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + noPromiseInFireEvent: + "A promise shouldn't be passed to a `fireEvent` method, instead pass the DOM element", + }, + schema: [], + }, + defaultOptions: [], - create(context, _, helpers) { - function checkSuspiciousNode( - node: TSESTree.Node, - originalNode?: TSESTree.Node - ): void { - if (ASTUtils.isAwaitExpression(node)) { - return; - } + create(context, _, helpers) { + function checkSuspiciousNode( + node: TSESTree.Node, + originalNode?: TSESTree.Node + ): void { + if (ASTUtils.isAwaitExpression(node)) { + return; + } - if (isNewExpression(node)) { - if (isPromiseIdentifier(node.callee)) { - context.report({ - node: originalNode ?? node, - messageId: 'noPromiseInFireEvent', - }); - return; - } - } + if (isNewExpression(node)) { + if (isPromiseIdentifier(node.callee)) { + context.report({ + node: originalNode ?? node, + messageId: 'noPromiseInFireEvent', + }); + return; + } + } - if (isCallExpression(node)) { - const domElementIdentifier = getDeepestIdentifierNode(node); + if (isCallExpression(node)) { + const domElementIdentifier = getDeepestIdentifierNode(node); - if (!domElementIdentifier) { - return; - } + if (!domElementIdentifier) { + return; + } - if ( - helpers.isAsyncQuery(domElementIdentifier) || - isPromiseIdentifier(domElementIdentifier) - ) { - context.report({ - node: originalNode ?? node, - messageId: 'noPromiseInFireEvent', - }); - return; - } - } + if ( + helpers.isAsyncQuery(domElementIdentifier) || + isPromiseIdentifier(domElementIdentifier) + ) { + context.report({ + node: originalNode ?? node, + messageId: 'noPromiseInFireEvent', + }); + return; + } + } - if (ASTUtils.isIdentifier(node)) { - const nodeVariable = ASTUtils.findVariable( - context.getScope(), - node.name - ); - if (!nodeVariable) { - return; - } + if (ASTUtils.isIdentifier(node)) { + const nodeVariable = ASTUtils.findVariable( + context.getScope(), + node.name + ); + if (!nodeVariable) { + return; + } - for (const definition of nodeVariable.defs) { - const variableDeclarator = - definition.node as TSESTree.VariableDeclarator; - if (variableDeclarator.init) { - checkSuspiciousNode(variableDeclarator.init, node); - } - } - } - } + for (const definition of nodeVariable.defs) { + const variableDeclarator = + definition.node as TSESTree.VariableDeclarator; + if (variableDeclarator.init) { + checkSuspiciousNode(variableDeclarator.init, node); + } + } + } + } - return { - 'CallExpression Identifier'(node: TSESTree.Identifier) { - if (!helpers.isFireEventMethod(node)) { - return; - } + return { + 'CallExpression Identifier'(node: TSESTree.Identifier) { + if (!helpers.isFireEventMethod(node)) { + return; + } - const closestCallExpression = findClosestCallExpressionNode(node, true); + const closestCallExpression = findClosestCallExpressionNode(node, true); - if (!closestCallExpression) { - return; - } + if (!closestCallExpression) { + return; + } - const domElementArgument = closestCallExpression.arguments[0]; + const domElementArgument = closestCallExpression.arguments[0]; - checkSuspiciousNode(domElementArgument); - }, - }; - }, + checkSuspiciousNode(domElementArgument); + }, + }; + }, }); diff --git a/lib/rules/no-render-in-setup.ts b/lib/rules/no-render-in-setup.ts index 7ea5f4be..f40c7d1e 100644 --- a/lib/rules/no-render-in-setup.ts +++ b/lib/rules/no-render-in-setup.ts @@ -2,138 +2,138 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getDeepestIdentifierNode, - getFunctionName, - getInnermostReturningFunction, - isCallExpression, + getDeepestIdentifierNode, + getFunctionName, + getInnermostReturningFunction, + isCallExpression, } from '../node-utils'; import { TESTING_FRAMEWORK_SETUP_HOOKS } from '../utils'; export const RULE_NAME = 'no-render-in-setup'; export type MessageIds = 'noRenderInSetup'; type Options = [ - { - allowTestingFrameworkSetupHook?: string; - } + { + allowTestingFrameworkSetupHook?: string; + } ]; export function findClosestBeforeHook( - node: TSESTree.Node | null, - testingFrameworkSetupHooksToFilter: string[] + node: TSESTree.Node | null, + testingFrameworkSetupHooksToFilter: string[] ): TSESTree.Identifier | null { - if (node === null) { - return null; - } - - if ( - isCallExpression(node) && - ASTUtils.isIdentifier(node.callee) && - testingFrameworkSetupHooksToFilter.includes(node.callee.name) - ) { - return node.callee; - } - - if (node.parent) { - return findClosestBeforeHook( - node.parent, - testingFrameworkSetupHooksToFilter - ); - } - - return null; + if (node === null) { + return null; + } + + if ( + isCallExpression(node) && + ASTUtils.isIdentifier(node.callee) && + testingFrameworkSetupHooksToFilter.includes(node.callee.name) + ) { + return node.callee; + } + + if (node.parent) { + return findClosestBeforeHook( + node.parent, + testingFrameworkSetupHooksToFilter + ); + } + + return null; } export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: - 'Disallow the use of `render` in testing frameworks setup functions', - recommendedConfig: { - dom: false, - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - noRenderInSetup: - 'Forbidden usage of `render` within testing framework `{{ name }}` setup', - }, - schema: [ - { - type: 'object', - properties: { - allowTestingFrameworkSetupHook: { - enum: TESTING_FRAMEWORK_SETUP_HOOKS, - }, - }, - }, - ], - }, - defaultOptions: [ - { - allowTestingFrameworkSetupHook: '', - }, - ], - - create(context, [{ allowTestingFrameworkSetupHook }], helpers) { - const renderWrapperNames: string[] = []; - - function detectRenderWrapper(node: TSESTree.Identifier): void { - const innerFunction = getInnermostReturningFunction(context, node); - - if (innerFunction) { - renderWrapperNames.push(getFunctionName(innerFunction)); - } - } - - return { - CallExpression(node) { - const testingFrameworkSetupHooksToFilter = - TESTING_FRAMEWORK_SETUP_HOOKS.filter( - (hook) => hook !== allowTestingFrameworkSetupHook - ); - const callExpressionIdentifier = getDeepestIdentifierNode(node); - - if (!callExpressionIdentifier) { - return; - } - - const isRenderIdentifier = helpers.isRenderUtil( - callExpressionIdentifier - ); - - if (isRenderIdentifier) { - detectRenderWrapper(callExpressionIdentifier); - } - - if ( - !isRenderIdentifier && - !renderWrapperNames.includes(callExpressionIdentifier.name) - ) { - return; - } - - const beforeHook = findClosestBeforeHook( - node, - testingFrameworkSetupHooksToFilter - ); - - if (!beforeHook) { - return; - } - - context.report({ - node: callExpressionIdentifier, - messageId: 'noRenderInSetup', - data: { - name: beforeHook.name, - }, - }); - }, - }; - }, + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: + 'Disallow the use of `render` in testing frameworks setup functions', + recommendedConfig: { + dom: false, + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + noRenderInSetup: + 'Forbidden usage of `render` within testing framework `{{ name }}` setup', + }, + schema: [ + { + type: 'object', + properties: { + allowTestingFrameworkSetupHook: { + enum: TESTING_FRAMEWORK_SETUP_HOOKS, + }, + }, + }, + ], + }, + defaultOptions: [ + { + allowTestingFrameworkSetupHook: '', + }, + ], + + create(context, [{ allowTestingFrameworkSetupHook }], helpers) { + const renderWrapperNames: string[] = []; + + function detectRenderWrapper(node: TSESTree.Identifier): void { + const innerFunction = getInnermostReturningFunction(context, node); + + if (innerFunction) { + renderWrapperNames.push(getFunctionName(innerFunction)); + } + } + + return { + CallExpression(node) { + const testingFrameworkSetupHooksToFilter = + TESTING_FRAMEWORK_SETUP_HOOKS.filter( + (hook) => hook !== allowTestingFrameworkSetupHook + ); + const callExpressionIdentifier = getDeepestIdentifierNode(node); + + if (!callExpressionIdentifier) { + return; + } + + const isRenderIdentifier = helpers.isRenderUtil( + callExpressionIdentifier + ); + + if (isRenderIdentifier) { + detectRenderWrapper(callExpressionIdentifier); + } + + if ( + !isRenderIdentifier && + !renderWrapperNames.includes(callExpressionIdentifier.name) + ) { + return; + } + + const beforeHook = findClosestBeforeHook( + node, + testingFrameworkSetupHooksToFilter + ); + + if (!beforeHook) { + return; + } + + context.report({ + node: callExpressionIdentifier, + messageId: 'noRenderInSetup', + data: { + name: beforeHook.name, + }, + }); + }, + }; + }, }); diff --git a/lib/rules/no-unnecessary-act.ts b/lib/rules/no-unnecessary-act.ts index 8c3fc10a..3d4317b5 100644 --- a/lib/rules/no-unnecessary-act.ts +++ b/lib/rules/no-unnecessary-act.ts @@ -2,203 +2,203 @@ import { TSESTree, ASTUtils } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getDeepestIdentifierNode, - getPropertyIdentifierNode, - getStatementCallExpression, - isEmptyFunction, - isExpressionStatement, - isReturnStatement, + getDeepestIdentifierNode, + getPropertyIdentifierNode, + getStatementCallExpression, + isEmptyFunction, + isExpressionStatement, + isReturnStatement, } from '../node-utils'; export const RULE_NAME = 'no-unnecessary-act'; export type MessageIds = - | 'noUnnecessaryActEmptyFunction' - | 'noUnnecessaryActTestingLibraryUtil'; + | 'noUnnecessaryActEmptyFunction' + | 'noUnnecessaryActTestingLibraryUtil'; export type Options = [{ isStrict: boolean }]; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: - 'Disallow wrapping Testing Library utils or empty callbacks in `act`', - recommendedConfig: { - dom: false, - angular: false, - react: 'error', - vue: false, - marko: 'error', - }, - }, - messages: { - noUnnecessaryActTestingLibraryUtil: - 'Avoid wrapping Testing Library util calls in `act`', - noUnnecessaryActEmptyFunction: 'Avoid wrapping empty function in `act`', - }, - schema: [ - { - type: 'object', - properties: { - isStrict: { - type: 'boolean', - }, - }, - }, - ], - }, - defaultOptions: [ - { - isStrict: true, - }, - ], - - create(context, [{ isStrict = true }], helpers) { - function getStatementIdentifier(statement: TSESTree.Statement) { - const callExpression = getStatementCallExpression(statement); - - if ( - !callExpression && - !isExpressionStatement(statement) && - !isReturnStatement(statement) - ) { - return null; - } - - if (callExpression) { - return getDeepestIdentifierNode(callExpression); - } - - if ( - isExpressionStatement(statement) && - ASTUtils.isAwaitExpression(statement.expression) - ) { - return getPropertyIdentifierNode(statement.expression.argument); - } - - if (isReturnStatement(statement) && statement.argument) { - return getPropertyIdentifierNode(statement.argument); - } - - return null; - } - - /** - * Determines whether some call is non Testing Library related for a given list of statements. - */ - function hasSomeNonTestingLibraryCall( - statements: TSESTree.Statement[] - ): boolean { - return statements.some((statement) => { - const identifier = getStatementIdentifier(statement); - - if (!identifier) { - return false; - } - - return !helpers.isTestingLibraryUtil(identifier); - }); - } - - function hasTestingLibraryCall(statements: TSESTree.Statement[]) { - return statements.some((statement) => { - const identifier = getStatementIdentifier(statement); - - if (!identifier) { - return false; - } - - return helpers.isTestingLibraryUtil(identifier); - }); - } - - function checkNoUnnecessaryActFromBlockStatement( - blockStatementNode: TSESTree.BlockStatement - ) { - const functionNode = blockStatementNode.parent as - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionExpression - | undefined; - const callExpressionNode = functionNode?.parent as - | TSESTree.CallExpression - | undefined; - - if (!callExpressionNode || !functionNode) { - return; - } - - const identifierNode = getDeepestIdentifierNode(callExpressionNode); - if (!identifierNode) { - return; - } - - if (!helpers.isActUtil(identifierNode)) { - return; - } - - if (isEmptyFunction(functionNode)) { - context.report({ - node: identifierNode, - messageId: 'noUnnecessaryActEmptyFunction', - }); - return; - } - - const shouldBeReported = isStrict - ? hasTestingLibraryCall(blockStatementNode.body) - : !hasSomeNonTestingLibraryCall(blockStatementNode.body); - - if (shouldBeReported) { - context.report({ - node: identifierNode, - messageId: 'noUnnecessaryActTestingLibraryUtil', - }); - } - } - - function checkNoUnnecessaryActFromImplicitReturn( - node: TSESTree.CallExpression - ) { - const nodeIdentifier = getDeepestIdentifierNode(node); - - if (!nodeIdentifier) { - return; - } - - const parentCallExpression = node.parent?.parent as - | TSESTree.CallExpression - | undefined; - - if (!parentCallExpression) { - return; - } - - const identifierNode = getDeepestIdentifierNode(parentCallExpression); - if (!identifierNode) { - return; - } - - if (!helpers.isActUtil(identifierNode)) { - return; - } - - if (!helpers.isTestingLibraryUtil(nodeIdentifier)) { - return; - } - - context.report({ - node: identifierNode, - messageId: 'noUnnecessaryActTestingLibraryUtil', - }); - } - - return { - 'CallExpression > ArrowFunctionExpression > BlockStatement': - checkNoUnnecessaryActFromBlockStatement, - 'CallExpression > FunctionExpression > BlockStatement': - checkNoUnnecessaryActFromBlockStatement, - 'CallExpression > ArrowFunctionExpression > CallExpression': - checkNoUnnecessaryActFromImplicitReturn, - }; - }, + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: + 'Disallow wrapping Testing Library utils or empty callbacks in `act`', + recommendedConfig: { + dom: false, + angular: false, + react: 'error', + vue: false, + marko: 'error', + }, + }, + messages: { + noUnnecessaryActTestingLibraryUtil: + 'Avoid wrapping Testing Library util calls in `act`', + noUnnecessaryActEmptyFunction: 'Avoid wrapping empty function in `act`', + }, + schema: [ + { + type: 'object', + properties: { + isStrict: { + type: 'boolean', + }, + }, + }, + ], + }, + defaultOptions: [ + { + isStrict: true, + }, + ], + + create(context, [{ isStrict = true }], helpers) { + function getStatementIdentifier(statement: TSESTree.Statement) { + const callExpression = getStatementCallExpression(statement); + + if ( + !callExpression && + !isExpressionStatement(statement) && + !isReturnStatement(statement) + ) { + return null; + } + + if (callExpression) { + return getDeepestIdentifierNode(callExpression); + } + + if ( + isExpressionStatement(statement) && + ASTUtils.isAwaitExpression(statement.expression) + ) { + return getPropertyIdentifierNode(statement.expression.argument); + } + + if (isReturnStatement(statement) && statement.argument) { + return getPropertyIdentifierNode(statement.argument); + } + + return null; + } + + /** + * Determines whether some call is non Testing Library related for a given list of statements. + */ + function hasSomeNonTestingLibraryCall( + statements: TSESTree.Statement[] + ): boolean { + return statements.some((statement) => { + const identifier = getStatementIdentifier(statement); + + if (!identifier) { + return false; + } + + return !helpers.isTestingLibraryUtil(identifier); + }); + } + + function hasTestingLibraryCall(statements: TSESTree.Statement[]) { + return statements.some((statement) => { + const identifier = getStatementIdentifier(statement); + + if (!identifier) { + return false; + } + + return helpers.isTestingLibraryUtil(identifier); + }); + } + + function checkNoUnnecessaryActFromBlockStatement( + blockStatementNode: TSESTree.BlockStatement + ) { + const functionNode = blockStatementNode.parent as + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionExpression + | undefined; + const callExpressionNode = functionNode?.parent as + | TSESTree.CallExpression + | undefined; + + if (!callExpressionNode || !functionNode) { + return; + } + + const identifierNode = getDeepestIdentifierNode(callExpressionNode); + if (!identifierNode) { + return; + } + + if (!helpers.isActUtil(identifierNode)) { + return; + } + + if (isEmptyFunction(functionNode)) { + context.report({ + node: identifierNode, + messageId: 'noUnnecessaryActEmptyFunction', + }); + return; + } + + const shouldBeReported = isStrict + ? hasTestingLibraryCall(blockStatementNode.body) + : !hasSomeNonTestingLibraryCall(blockStatementNode.body); + + if (shouldBeReported) { + context.report({ + node: identifierNode, + messageId: 'noUnnecessaryActTestingLibraryUtil', + }); + } + } + + function checkNoUnnecessaryActFromImplicitReturn( + node: TSESTree.CallExpression + ) { + const nodeIdentifier = getDeepestIdentifierNode(node); + + if (!nodeIdentifier) { + return; + } + + const parentCallExpression = node.parent?.parent as + | TSESTree.CallExpression + | undefined; + + if (!parentCallExpression) { + return; + } + + const identifierNode = getDeepestIdentifierNode(parentCallExpression); + if (!identifierNode) { + return; + } + + if (!helpers.isActUtil(identifierNode)) { + return; + } + + if (!helpers.isTestingLibraryUtil(nodeIdentifier)) { + return; + } + + context.report({ + node: identifierNode, + messageId: 'noUnnecessaryActTestingLibraryUtil', + }); + } + + return { + 'CallExpression > ArrowFunctionExpression > BlockStatement': + checkNoUnnecessaryActFromBlockStatement, + 'CallExpression > FunctionExpression > BlockStatement': + checkNoUnnecessaryActFromBlockStatement, + 'CallExpression > ArrowFunctionExpression > CallExpression': + checkNoUnnecessaryActFromImplicitReturn, + }; + }, }); diff --git a/lib/rules/no-wait-for-empty-callback.ts b/lib/rules/no-wait-for-empty-callback.ts index 1932cc8d..4ef3f851 100644 --- a/lib/rules/no-wait-for-empty-callback.ts +++ b/lib/rules/no-wait-for-empty-callback.ts @@ -2,9 +2,9 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getPropertyIdentifierNode, - isCallExpression, - isEmptyFunction, + getPropertyIdentifierNode, + isCallExpression, + isEmptyFunction, } from '../node-utils'; export const RULE_NAME = 'no-wait-for-empty-callback'; @@ -12,89 +12,89 @@ 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: [], + 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); + // 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; - } + if (!parentIdentifier) { + return false; + } - return helpers.isAsyncUtil(parentIdentifier, [ - 'waitFor', - 'waitForElementToBeRemoved', - ]); - } + return helpers.isAsyncUtil(parentIdentifier, [ + 'waitFor', + 'waitForElementToBeRemoved', + ]); + } - function reportIfEmpty( - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression - ) { - if (!isValidWaitFor(node)) { - return; - } + 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, - }, - }); - } - } + 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; - } + 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, - }, - }); - } + 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, - }; - }, + return { + 'CallExpression > ArrowFunctionExpression': reportIfEmpty, + 'CallExpression > FunctionExpression': reportIfEmpty, + 'CallExpression > Identifier[name="noop"]': reportNoop, + }; + }, }); diff --git a/lib/rules/no-wait-for-multiple-assertions.ts b/lib/rules/no-wait-for-multiple-assertions.ts index 614ad2e1..df9c1f52 100644 --- a/lib/rules/no-wait-for-multiple-assertions.ts +++ b/lib/rules/no-wait-for-multiple-assertions.ts @@ -2,8 +2,8 @@ import { TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getPropertyIdentifierNode, - isExpressionStatement, + getPropertyIdentifierNode, + isExpressionStatement, } from '../node-utils'; export const RULE_NAME = 'no-wait-for-multiple-assertions'; @@ -11,82 +11,82 @@ export type MessageIds = 'noWaitForMultipleAssertion'; type Options = []; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: - 'Disallow the use of multiple `expect` calls inside `waitFor`', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - noWaitForMultipleAssertion: - 'Avoid using multiple assertions within `waitFor` callback', - }, - schema: [], - }, - defaultOptions: [], - create(context, _, helpers) { - function getExpectNodes( - body: Array - ): Array { - return body.filter((node) => { - if (!isExpressionStatement(node)) { - return false; - } + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: + 'Disallow the use of multiple `expect` calls inside `waitFor`', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + noWaitForMultipleAssertion: + 'Avoid using multiple assertions within `waitFor` callback', + }, + schema: [], + }, + defaultOptions: [], + create(context, _, helpers) { + function getExpectNodes( + body: Array + ): Array { + return body.filter((node) => { + if (!isExpressionStatement(node)) { + return false; + } - const expressionIdentifier = getPropertyIdentifierNode(node); - if (!expressionIdentifier) { - return false; - } + const expressionIdentifier = getPropertyIdentifierNode(node); + if (!expressionIdentifier) { + return false; + } - return expressionIdentifier.name === 'expect'; - }) as Array; - } + return expressionIdentifier.name === 'expect'; + }) as Array; + } - function reportMultipleAssertion(node: TSESTree.BlockStatement) { - if (!node.parent) { - return; - } - const callExpressionNode = node.parent.parent as TSESTree.CallExpression; - const callExpressionIdentifier = - getPropertyIdentifierNode(callExpressionNode); + function reportMultipleAssertion(node: TSESTree.BlockStatement) { + if (!node.parent) { + return; + } + const callExpressionNode = node.parent.parent as TSESTree.CallExpression; + const callExpressionIdentifier = + getPropertyIdentifierNode(callExpressionNode); - if (!callExpressionIdentifier) { - return; - } + if (!callExpressionIdentifier) { + return; + } - if (!helpers.isAsyncUtil(callExpressionIdentifier, ['waitFor'])) { - return; - } + if (!helpers.isAsyncUtil(callExpressionIdentifier, ['waitFor'])) { + return; + } - const expectNodes = getExpectNodes(node.body); + const expectNodes = getExpectNodes(node.body); - if (expectNodes.length <= 1) { - return; - } + if (expectNodes.length <= 1) { + return; + } - for (let i = 0; i < expectNodes.length; i++) { - if (i !== 0) { - context.report({ - node: expectNodes[i], - messageId: 'noWaitForMultipleAssertion', - }); - } - } - } + for (let i = 0; i < expectNodes.length; i++) { + if (i !== 0) { + context.report({ + node: expectNodes[i], + messageId: 'noWaitForMultipleAssertion', + }); + } + } + } - return { - 'CallExpression > ArrowFunctionExpression > BlockStatement': - reportMultipleAssertion, - 'CallExpression > FunctionExpression > BlockStatement': - reportMultipleAssertion, - }; - }, + return { + 'CallExpression > ArrowFunctionExpression > BlockStatement': + reportMultipleAssertion, + 'CallExpression > FunctionExpression > BlockStatement': + reportMultipleAssertion, + }; + }, }); diff --git a/lib/rules/no-wait-for-side-effects.ts b/lib/rules/no-wait-for-side-effects.ts index 82f17895..a0fd85fe 100644 --- a/lib/rules/no-wait-for-side-effects.ts +++ b/lib/rules/no-wait-for-side-effects.ts @@ -2,12 +2,12 @@ import { TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getPropertyIdentifierNode, - isExpressionStatement, - isVariableDeclaration, - isAssignmentExpression, - isCallExpression, - isSequenceExpression, + getPropertyIdentifierNode, + isExpressionStatement, + isVariableDeclaration, + isAssignmentExpression, + isCallExpression, + isSequenceExpression, } from '../node-utils'; export const RULE_NAME = 'no-wait-for-side-effects'; @@ -15,183 +15,183 @@ export type MessageIds = 'noSideEffectsWaitFor'; type Options = []; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: 'Disallow the use of side effects in `waitFor`', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - noSideEffectsWaitFor: - 'Avoid using side effects within `waitFor` callback', - }, - schema: [], - }, - defaultOptions: [], - create(context, _, helpers) { - function isCallerWaitFor( - node: - | TSESTree.AssignmentExpression - | TSESTree.BlockStatement - | TSESTree.CallExpression - | TSESTree.SequenceExpression - ): boolean { - if (!node.parent) { - return false; - } - const callExpressionNode = node.parent.parent as TSESTree.CallExpression; - const callExpressionIdentifier = - getPropertyIdentifierNode(callExpressionNode); - - return ( - !!callExpressionIdentifier && - helpers.isAsyncUtil(callExpressionIdentifier, ['waitFor']) - ); - } - - function isRenderInVariableDeclaration(node: TSESTree.Node) { - return ( - isVariableDeclaration(node) && - node.declarations.some(helpers.isRenderVariableDeclarator) - ); - } - - function isRenderInExpressionStatement(node: TSESTree.Node) { - if ( - !isExpressionStatement(node) || - !isAssignmentExpression(node.expression) - ) { - return false; - } - - const expressionIdentifier = getPropertyIdentifierNode( - node.expression.right - ); - - if (!expressionIdentifier) { - return false; - } - - return helpers.isRenderUtil(expressionIdentifier); - } - - function isRenderInAssignmentExpression(node: TSESTree.Node) { - if (!isAssignmentExpression(node)) { - return false; - } - - const expressionIdentifier = getPropertyIdentifierNode(node.right); - if (!expressionIdentifier) { - return false; - } - - return helpers.isRenderUtil(expressionIdentifier); - } - - function isRenderInSequenceAssignment(node: TSESTree.Node) { - if (!isSequenceExpression(node)) { - return false; - } - - return node.expressions.some(isRenderInAssignmentExpression); - } - - function getSideEffectNodes( - body: TSESTree.Node[] - ): TSESTree.ExpressionStatement[] { - return body.filter((node) => { - if (!isExpressionStatement(node) && !isVariableDeclaration(node)) { - return false; - } - - if ( - isRenderInVariableDeclaration(node) || - isRenderInExpressionStatement(node) - ) { - return true; - } - - const expressionIdentifier = getPropertyIdentifierNode(node); - - if (!expressionIdentifier) { - return false; - } - - return ( - helpers.isFireEventUtil(expressionIdentifier) || - helpers.isUserEventUtil(expressionIdentifier) || - helpers.isRenderUtil(expressionIdentifier) - ); - }) as TSESTree.ExpressionStatement[]; - } - - function reportSideEffects(node: TSESTree.BlockStatement) { - if (!isCallerWaitFor(node)) { - return; - } - - getSideEffectNodes(node.body).forEach((sideEffectNode) => - context.report({ - node: sideEffectNode, - messageId: 'noSideEffectsWaitFor', - }) - ); - } - - function reportImplicitReturnSideEffect( - node: - | TSESTree.AssignmentExpression - | TSESTree.CallExpression - | TSESTree.SequenceExpression - ) { - if (!isCallerWaitFor(node)) { - return; - } - - const expressionIdentifier = isCallExpression(node) - ? getPropertyIdentifierNode(node.callee) - : null; - - if ( - !expressionIdentifier && - !isRenderInAssignmentExpression(node) && - !isRenderInSequenceAssignment(node) - ) { - return; - } - - if ( - expressionIdentifier && - !helpers.isFireEventUtil(expressionIdentifier) && - !helpers.isUserEventUtil(expressionIdentifier) && - !helpers.isRenderUtil(expressionIdentifier) - ) { - return; - } - - context.report({ - node, - messageId: 'noSideEffectsWaitFor', - }); - } - - return { - 'CallExpression > ArrowFunctionExpression > BlockStatement': - reportSideEffects, - 'CallExpression > ArrowFunctionExpression > CallExpression': - reportImplicitReturnSideEffect, - 'CallExpression > ArrowFunctionExpression > AssignmentExpression': - reportImplicitReturnSideEffect, - 'CallExpression > ArrowFunctionExpression > SequenceExpression': - reportImplicitReturnSideEffect, - 'CallExpression > FunctionExpression > BlockStatement': reportSideEffects, - }; - }, + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: 'Disallow the use of side effects in `waitFor`', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + noSideEffectsWaitFor: + 'Avoid using side effects within `waitFor` callback', + }, + schema: [], + }, + defaultOptions: [], + create(context, _, helpers) { + function isCallerWaitFor( + node: + | TSESTree.AssignmentExpression + | TSESTree.BlockStatement + | TSESTree.CallExpression + | TSESTree.SequenceExpression + ): boolean { + if (!node.parent) { + return false; + } + const callExpressionNode = node.parent.parent as TSESTree.CallExpression; + const callExpressionIdentifier = + getPropertyIdentifierNode(callExpressionNode); + + return ( + !!callExpressionIdentifier && + helpers.isAsyncUtil(callExpressionIdentifier, ['waitFor']) + ); + } + + function isRenderInVariableDeclaration(node: TSESTree.Node) { + return ( + isVariableDeclaration(node) && + node.declarations.some(helpers.isRenderVariableDeclarator) + ); + } + + function isRenderInExpressionStatement(node: TSESTree.Node) { + if ( + !isExpressionStatement(node) || + !isAssignmentExpression(node.expression) + ) { + return false; + } + + const expressionIdentifier = getPropertyIdentifierNode( + node.expression.right + ); + + if (!expressionIdentifier) { + return false; + } + + return helpers.isRenderUtil(expressionIdentifier); + } + + function isRenderInAssignmentExpression(node: TSESTree.Node) { + if (!isAssignmentExpression(node)) { + return false; + } + + const expressionIdentifier = getPropertyIdentifierNode(node.right); + if (!expressionIdentifier) { + return false; + } + + return helpers.isRenderUtil(expressionIdentifier); + } + + function isRenderInSequenceAssignment(node: TSESTree.Node) { + if (!isSequenceExpression(node)) { + return false; + } + + return node.expressions.some(isRenderInAssignmentExpression); + } + + function getSideEffectNodes( + body: TSESTree.Node[] + ): TSESTree.ExpressionStatement[] { + return body.filter((node) => { + if (!isExpressionStatement(node) && !isVariableDeclaration(node)) { + return false; + } + + if ( + isRenderInVariableDeclaration(node) || + isRenderInExpressionStatement(node) + ) { + return true; + } + + const expressionIdentifier = getPropertyIdentifierNode(node); + + if (!expressionIdentifier) { + return false; + } + + return ( + helpers.isFireEventUtil(expressionIdentifier) || + helpers.isUserEventUtil(expressionIdentifier) || + helpers.isRenderUtil(expressionIdentifier) + ); + }) as TSESTree.ExpressionStatement[]; + } + + function reportSideEffects(node: TSESTree.BlockStatement) { + if (!isCallerWaitFor(node)) { + return; + } + + getSideEffectNodes(node.body).forEach((sideEffectNode) => + context.report({ + node: sideEffectNode, + messageId: 'noSideEffectsWaitFor', + }) + ); + } + + function reportImplicitReturnSideEffect( + node: + | TSESTree.AssignmentExpression + | TSESTree.CallExpression + | TSESTree.SequenceExpression + ) { + if (!isCallerWaitFor(node)) { + return; + } + + const expressionIdentifier = isCallExpression(node) + ? getPropertyIdentifierNode(node.callee) + : null; + + if ( + !expressionIdentifier && + !isRenderInAssignmentExpression(node) && + !isRenderInSequenceAssignment(node) + ) { + return; + } + + if ( + expressionIdentifier && + !helpers.isFireEventUtil(expressionIdentifier) && + !helpers.isUserEventUtil(expressionIdentifier) && + !helpers.isRenderUtil(expressionIdentifier) + ) { + return; + } + + context.report({ + node, + messageId: 'noSideEffectsWaitFor', + }); + } + + return { + 'CallExpression > ArrowFunctionExpression > BlockStatement': + reportSideEffects, + 'CallExpression > ArrowFunctionExpression > CallExpression': + reportImplicitReturnSideEffect, + 'CallExpression > ArrowFunctionExpression > AssignmentExpression': + reportImplicitReturnSideEffect, + 'CallExpression > ArrowFunctionExpression > SequenceExpression': + reportImplicitReturnSideEffect, + 'CallExpression > FunctionExpression > BlockStatement': reportSideEffects, + }; + }, }); diff --git a/lib/rules/no-wait-for-snapshot.ts b/lib/rules/no-wait-for-snapshot.ts index 2818b4f4..3f0cc462 100644 --- a/lib/rules/no-wait-for-snapshot.ts +++ b/lib/rules/no-wait-for-snapshot.ts @@ -2,8 +2,8 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - findClosestCallExpressionNode, - isMemberExpression, + findClosestCallExpressionNode, + isMemberExpression, } from '../node-utils'; export const RULE_NAME = 'no-wait-for-snapshot'; @@ -13,72 +13,72 @@ type Options = []; const SNAPSHOT_REGEXP = /^(toMatchSnapshot|toMatchInlineSnapshot)$/; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: - 'Ensures no snapshot is generated inside of a `waitFor` call', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - noWaitForSnapshot: - "A snapshot can't be generated inside of a `{{ name }}` call", - }, - schema: [], - }, - defaultOptions: [], + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: + 'Ensures no snapshot is generated inside of a `waitFor` call', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + noWaitForSnapshot: + "A snapshot can't be generated inside of a `{{ name }}` call", + }, + schema: [], + }, + defaultOptions: [], - create(context, _, helpers) { - function getClosestAsyncUtil( - node: TSESTree.Node - ): TSESTree.Identifier | null { - let n: TSESTree.Node | null = node; - do { - const callExpression = findClosestCallExpressionNode(n); + create(context, _, helpers) { + function getClosestAsyncUtil( + node: TSESTree.Node + ): TSESTree.Identifier | null { + let n: TSESTree.Node | null = node; + do { + const callExpression = findClosestCallExpressionNode(n); - if (!callExpression) { - return null; - } + if (!callExpression) { + return null; + } - if ( - ASTUtils.isIdentifier(callExpression.callee) && - helpers.isAsyncUtil(callExpression.callee) - ) { - return callExpression.callee; - } - if ( - isMemberExpression(callExpression.callee) && - ASTUtils.isIdentifier(callExpression.callee.property) && - helpers.isAsyncUtil(callExpression.callee.property) - ) { - return callExpression.callee.property; - } - if (callExpression.parent) { - n = findClosestCallExpressionNode(callExpression.parent); - } - } while (n !== null); - return null; - } + if ( + ASTUtils.isIdentifier(callExpression.callee) && + helpers.isAsyncUtil(callExpression.callee) + ) { + return callExpression.callee; + } + if ( + isMemberExpression(callExpression.callee) && + ASTUtils.isIdentifier(callExpression.callee.property) && + helpers.isAsyncUtil(callExpression.callee.property) + ) { + return callExpression.callee.property; + } + if (callExpression.parent) { + n = findClosestCallExpressionNode(callExpression.parent); + } + } while (n !== null); + return null; + } - return { - [`Identifier[name=${SNAPSHOT_REGEXP}]`](node: TSESTree.Identifier) { - const closestAsyncUtil = getClosestAsyncUtil(node); - if (closestAsyncUtil === null) { - return; - } - context.report({ - node, - messageId: 'noWaitForSnapshot', - data: { name: closestAsyncUtil.name }, - }); - }, - }; - }, + return { + [`Identifier[name=${SNAPSHOT_REGEXP}]`](node: TSESTree.Identifier) { + const closestAsyncUtil = getClosestAsyncUtil(node); + if (closestAsyncUtil === null) { + return; + } + context.report({ + node, + messageId: 'noWaitForSnapshot', + data: { name: closestAsyncUtil.name }, + }); + }, + }; + }, }); diff --git a/lib/rules/prefer-explicit-assert.ts b/lib/rules/prefer-explicit-assert.ts index 155ab0c5..7a80e059 100644 --- a/lib/rules/prefer-explicit-assert.ts +++ b/lib/rules/prefer-explicit-assert.ts @@ -2,199 +2,199 @@ import { TSESTree, ASTUtils } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - findClosestCallNode, - isCallExpression, - isMemberExpression, + findClosestCallNode, + isCallExpression, + isMemberExpression, } from '../node-utils'; import { PRESENCE_MATCHERS, ABSENCE_MATCHERS } from '../utils'; export const RULE_NAME = 'prefer-explicit-assert'; export type MessageIds = - | 'preferExplicitAssert' - | 'preferExplicitAssertAssertion'; + | 'preferExplicitAssert' + | 'preferExplicitAssertAssertion'; type Options = [ - { - assertion?: string; - includeFindQueries?: boolean; - } + { + assertion?: string; + includeFindQueries?: boolean; + } ]; const isAtTopLevel = (node: TSESTree.Node) => - (!!node.parent?.parent && - node.parent.parent.type === 'ExpressionStatement') || - (node.parent?.parent?.type === 'AwaitExpression' && - !!node.parent.parent.parent && - node.parent.parent.parent.type === 'ExpressionStatement'); + (!!node.parent?.parent && + node.parent.parent.type === 'ExpressionStatement') || + (node.parent?.parent?.type === 'AwaitExpression' && + !!node.parent.parent.parent && + node.parent.parent.parent.type === 'ExpressionStatement'); const isVariableDeclaration = (node: TSESTree.Node) => { - if ( - isCallExpression(node.parent) && - ASTUtils.isAwaitExpression(node.parent.parent) && - ASTUtils.isVariableDeclarator(node.parent.parent.parent) - ) { - return true; // const quxElement = await findByLabelText('qux') - } - - if ( - isCallExpression(node.parent) && - ASTUtils.isVariableDeclarator(node.parent.parent) - ) { - return true; // const quxElement = findByLabelText('qux') - } - - if ( - isMemberExpression(node.parent) && - isCallExpression(node.parent.parent) && - ASTUtils.isAwaitExpression(node.parent.parent.parent) && - ASTUtils.isVariableDeclarator(node.parent.parent.parent.parent) - ) { - return true; // const quxElement = await screen.findByLabelText('qux') - } - - if ( - isMemberExpression(node.parent) && - isCallExpression(node.parent.parent) && - ASTUtils.isVariableDeclarator(node.parent.parent.parent) - ) { - return true; // const quxElement = screen.findByLabelText('qux') - } - - return false; + if ( + isCallExpression(node.parent) && + ASTUtils.isAwaitExpression(node.parent.parent) && + ASTUtils.isVariableDeclarator(node.parent.parent.parent) + ) { + return true; // const quxElement = await findByLabelText('qux') + } + + if ( + isCallExpression(node.parent) && + ASTUtils.isVariableDeclarator(node.parent.parent) + ) { + return true; // const quxElement = findByLabelText('qux') + } + + if ( + isMemberExpression(node.parent) && + isCallExpression(node.parent.parent) && + ASTUtils.isAwaitExpression(node.parent.parent.parent) && + ASTUtils.isVariableDeclarator(node.parent.parent.parent.parent) + ) { + return true; // const quxElement = await screen.findByLabelText('qux') + } + + if ( + isMemberExpression(node.parent) && + isCallExpression(node.parent.parent) && + ASTUtils.isVariableDeclarator(node.parent.parent.parent) + ) { + return true; // const quxElement = screen.findByLabelText('qux') + } + + return false; }; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: - 'Suggest using explicit assertions rather than standalone queries', - recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: false, - marko: false, - }, - }, - messages: { - preferExplicitAssert: - 'Wrap stand-alone `{{queryType}}` query with `expect` function for better explicit assertion', - preferExplicitAssertAssertion: - '`getBy*` queries must be asserted with `{{assertion}}`', // TODO: support findBy* queries as well - }, - schema: [ - { - type: 'object', - additionalProperties: false, - properties: { - assertion: { - type: 'string', - enum: PRESENCE_MATCHERS, - }, - includeFindQueries: { type: 'boolean' }, - }, - }, - ], - }, - defaultOptions: [{ includeFindQueries: true }], - create(context, [options], helpers) { - const { assertion, includeFindQueries } = options; - const getQueryCalls: TSESTree.Identifier[] = []; - const findQueryCalls: TSESTree.Identifier[] = []; - - return { - 'CallExpression Identifier'(node: TSESTree.Identifier) { - if (helpers.isGetQueryVariant(node)) { - getQueryCalls.push(node); - } - - if (helpers.isFindQueryVariant(node)) { - findQueryCalls.push(node); - } - }, - 'Program:exit'() { - if (includeFindQueries) { - findQueryCalls.forEach((queryCall) => { - const memberExpression = isMemberExpression(queryCall.parent) - ? queryCall.parent - : queryCall; - - if ( - isVariableDeclaration(queryCall) || - !isAtTopLevel(memberExpression) - ) { - return; - } - - context.report({ - node: queryCall, - messageId: 'preferExplicitAssert', - data: { - queryType: 'findBy*', - }, - }); - }); - } - - getQueryCalls.forEach((queryCall) => { - const node = isMemberExpression(queryCall.parent) - ? queryCall.parent - : queryCall; - - if (isAtTopLevel(node)) { - context.report({ - node: queryCall, - messageId: 'preferExplicitAssert', - data: { - queryType: 'getBy*', - }, - }); - } - - if (assertion) { - const expectCallNode = findClosestCallNode(node, 'expect'); - if (!expectCallNode) return; - - const expectStatement = expectCallNode.parent; - if (!isMemberExpression(expectStatement)) { - return; - } - - const property = expectStatement.property; - - if (!ASTUtils.isIdentifier(property)) { - return; - } - - let matcher = property.name; - let isNegatedMatcher = false; - - if ( - matcher === 'not' && - isMemberExpression(expectStatement.parent) && - ASTUtils.isIdentifier(expectStatement.parent.property) - ) { - isNegatedMatcher = true; - matcher = expectStatement.parent.property.name; - } - - const shouldEnforceAssertion = - (!isNegatedMatcher && PRESENCE_MATCHERS.includes(matcher)) || - (isNegatedMatcher && ABSENCE_MATCHERS.includes(matcher)); - - if (shouldEnforceAssertion && matcher !== assertion) { - context.report({ - node: property, - messageId: 'preferExplicitAssertAssertion', - data: { - assertion, - }, - }); - } - } - }); - }, - }; - }, + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: + 'Suggest using explicit assertions rather than standalone queries', + recommendedConfig: { + dom: false, + angular: false, + react: false, + vue: false, + marko: false, + }, + }, + messages: { + preferExplicitAssert: + 'Wrap stand-alone `{{queryType}}` query with `expect` function for better explicit assertion', + preferExplicitAssertAssertion: + '`getBy*` queries must be asserted with `{{assertion}}`', // TODO: support findBy* queries as well + }, + schema: [ + { + type: 'object', + additionalProperties: false, + properties: { + assertion: { + type: 'string', + enum: PRESENCE_MATCHERS, + }, + includeFindQueries: { type: 'boolean' }, + }, + }, + ], + }, + defaultOptions: [{ includeFindQueries: true }], + create(context, [options], helpers) { + const { assertion, includeFindQueries } = options; + const getQueryCalls: TSESTree.Identifier[] = []; + const findQueryCalls: TSESTree.Identifier[] = []; + + return { + 'CallExpression Identifier'(node: TSESTree.Identifier) { + if (helpers.isGetQueryVariant(node)) { + getQueryCalls.push(node); + } + + if (helpers.isFindQueryVariant(node)) { + findQueryCalls.push(node); + } + }, + 'Program:exit'() { + if (includeFindQueries) { + findQueryCalls.forEach((queryCall) => { + const memberExpression = isMemberExpression(queryCall.parent) + ? queryCall.parent + : queryCall; + + if ( + isVariableDeclaration(queryCall) || + !isAtTopLevel(memberExpression) + ) { + return; + } + + context.report({ + node: queryCall, + messageId: 'preferExplicitAssert', + data: { + queryType: 'findBy*', + }, + }); + }); + } + + getQueryCalls.forEach((queryCall) => { + const node = isMemberExpression(queryCall.parent) + ? queryCall.parent + : queryCall; + + if (isAtTopLevel(node)) { + context.report({ + node: queryCall, + messageId: 'preferExplicitAssert', + data: { + queryType: 'getBy*', + }, + }); + } + + if (assertion) { + const expectCallNode = findClosestCallNode(node, 'expect'); + if (!expectCallNode) return; + + const expectStatement = expectCallNode.parent; + if (!isMemberExpression(expectStatement)) { + return; + } + + const property = expectStatement.property; + + if (!ASTUtils.isIdentifier(property)) { + return; + } + + let matcher = property.name; + let isNegatedMatcher = false; + + if ( + matcher === 'not' && + isMemberExpression(expectStatement.parent) && + ASTUtils.isIdentifier(expectStatement.parent.property) + ) { + isNegatedMatcher = true; + matcher = expectStatement.parent.property.name; + } + + const shouldEnforceAssertion = + (!isNegatedMatcher && PRESENCE_MATCHERS.includes(matcher)) || + (isNegatedMatcher && ABSENCE_MATCHERS.includes(matcher)); + + if (shouldEnforceAssertion && matcher !== assertion) { + context.report({ + node: property, + messageId: 'preferExplicitAssertAssertion', + data: { + assertion, + }, + }); + } + } + }); + }, + }; + }, }); diff --git a/lib/rules/prefer-find-by.ts b/lib/rules/prefer-find-by.ts index 8e7bb296..084afef0 100644 --- a/lib/rules/prefer-find-by.ts +++ b/lib/rules/prefer-find-by.ts @@ -2,11 +2,11 @@ import { TSESTree, ASTUtils, TSESLint } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - isArrowFunctionExpression, - isCallExpression, - isMemberExpression, - isObjectPattern, - isProperty, + isArrowFunctionExpression, + isCallExpression, + isMemberExpression, + isObjectPattern, + isProperty, } from '../node-utils'; export const RULE_NAME = 'prefer-find-by'; @@ -16,465 +16,465 @@ type Options = []; export const WAIT_METHODS = ['waitFor', 'waitForElement', 'wait'] as const; export function getFindByQueryVariant( - queryMethod: string + queryMethod: string ): 'findAllBy' | 'findBy' { - return queryMethod.includes('All') ? 'findAllBy' : 'findBy'; + return queryMethod.includes('All') ? 'findAllBy' : 'findBy'; } function findRenderDefinitionDeclaration( - scope: TSESLint.Scope.Scope | null, - query: string + scope: TSESLint.Scope.Scope | null, + query: string ): TSESTree.Identifier | null { - if (!scope) { - return null; - } - - const variable = scope.variables.find( - (v: TSESLint.Scope.Variable) => v.name === query - ); - - if (variable) { - return ( - variable.defs - .map(({ name }) => name) - .filter(ASTUtils.isIdentifier) - .find(({ name }) => name === query) ?? null - ); - } - - return findRenderDefinitionDeclaration(scope.upper, query); + if (!scope) { + return null; + } + + const variable = scope.variables.find( + (v: TSESLint.Scope.Variable) => v.name === query + ); + + if (variable) { + return ( + variable.defs + .map(({ name }) => name) + .filter(ASTUtils.isIdentifier) + .find(({ name }) => name === query) ?? null + ); + } + + return findRenderDefinitionDeclaration(scope.upper, query); } export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: - 'Suggest using `find(All)By*` query instead of `waitFor` + `get(All)By*` to wait for elements', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - preferFindBy: - 'Prefer `{{queryVariant}}{{queryMethod}}` query over using `{{waitForMethodName}}` + `{{prevQuery}}`', - }, - fixable: 'code', - schema: [], - }, - defaultOptions: [], - - create(context, _, helpers) { - const sourceCode = context.getSourceCode(); - - /** - * 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 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( - node: TSESTree.CallExpression, - replacementParams: { - queryVariant: 'findAllBy' | 'findBy'; - queryMethod: string; - prevQuery: string; - waitForMethodName: string; - fix: TSESLint.ReportFixFunction; - } - ) { - const { queryMethod, queryVariant, prevQuery, waitForMethodName, fix } = - replacementParams; - context.report({ - node, - messageId: 'preferFindBy', - data: { - queryVariant, - queryMethod, - prevQuery, - waitForMethodName, - }, - fix, - }); - } - - function getWrongQueryNameInAssertion( - node: TSESTree.ArrowFunctionExpression - ) { - if ( - !isCallExpression(node.body) || - !isMemberExpression(node.body.callee) - ) { - return null; - } - - // expect(getByText).toBeInTheDocument() shape - if ( - isCallExpression(node.body.callee.object) && - isCallExpression(node.body.callee.object.arguments[0]) && - ASTUtils.isIdentifier(node.body.callee.object.arguments[0].callee) - ) { - return node.body.callee.object.arguments[0].callee.name; - } - - if (!ASTUtils.isIdentifier(node.body.callee.property)) { - return null; - } - - // expect(screen.getByText).toBeInTheDocument() shape - if ( - isCallExpression(node.body.callee.object) && - isCallExpression(node.body.callee.object.arguments[0]) && - isMemberExpression(node.body.callee.object.arguments[0].callee) && - ASTUtils.isIdentifier( - node.body.callee.object.arguments[0].callee.property - ) - ) { - return node.body.callee.object.arguments[0].callee.property.name; - } - - // expect(screen.getByText).not shape - if ( - isMemberExpression(node.body.callee.object) && - isCallExpression(node.body.callee.object.object) && - isCallExpression(node.body.callee.object.object.arguments[0]) && - isMemberExpression( - node.body.callee.object.object.arguments[0].callee - ) && - ASTUtils.isIdentifier( - node.body.callee.object.object.arguments[0].callee.property - ) - ) { - return node.body.callee.object.object.arguments[0].callee.property.name; - } - - // expect(getByText).not shape - if ( - isMemberExpression(node.body.callee.object) && - isCallExpression(node.body.callee.object.object) && - isCallExpression(node.body.callee.object.object.arguments[0]) && - ASTUtils.isIdentifier( - node.body.callee.object.object.arguments[0].callee - ) - ) { - return node.body.callee.object.object.arguments[0].callee.name; - } - - return node.body.callee.property.name; - } - - function getWrongQueryName(node: TSESTree.ArrowFunctionExpression) { - if (!isCallExpression(node.body)) { - return null; - } - - // expect(() => getByText) and expect(() => screen.getByText) shape - if ( - ASTUtils.isIdentifier(node.body.callee) && - helpers.isSyncQuery(node.body.callee) - ) { - return node.body.callee.name; - } - - return getWrongQueryNameInAssertion(node); - } - - function getCaller(node: TSESTree.ArrowFunctionExpression) { - if ( - !isCallExpression(node.body) || - !isMemberExpression(node.body.callee) - ) { - return null; - } - - if (ASTUtils.isIdentifier(node.body.callee.object)) { - // () => screen.getByText - return node.body.callee.object.name; - } - - if ( - // expect() - isCallExpression(node.body.callee.object) && - ASTUtils.isIdentifier(node.body.callee.object.callee) && - isCallExpression(node.body.callee.object.arguments[0]) && - isMemberExpression(node.body.callee.object.arguments[0].callee) && - ASTUtils.isIdentifier( - node.body.callee.object.arguments[0].callee.object - ) - ) { - return node.body.callee.object.arguments[0].callee.object.name; - } - - if ( - // expect().not - isMemberExpression(node.body.callee.object) && - isCallExpression(node.body.callee.object.object) && - isCallExpression(node.body.callee.object.object.arguments[0]) && - isMemberExpression( - node.body.callee.object.object.arguments[0].callee - ) && - ASTUtils.isIdentifier( - node.body.callee.object.object.arguments[0].callee.object - ) - ) { - return node.body.callee.object.object.arguments[0].callee.object.name; - } - - return null; - } - - function isSyncQuery(node: TSESTree.ArrowFunctionExpression) { - if (!isCallExpression(node.body)) { - return false; - } - - const isQuery = - ASTUtils.isIdentifier(node.body.callee) && - helpers.isSyncQuery(node.body.callee); - - const isWrappedInPresenceAssert = - isMemberExpression(node.body.callee) && - isCallExpression(node.body.callee.object) && - isCallExpression(node.body.callee.object.arguments[0]) && - ASTUtils.isIdentifier(node.body.callee.object.arguments[0].callee) && - helpers.isSyncQuery(node.body.callee.object.arguments[0].callee) && - helpers.isPresenceAssert(node.body.callee); - - const isWrappedInNegatedPresenceAssert = - isMemberExpression(node.body.callee) && - isMemberExpression(node.body.callee.object) && - isCallExpression(node.body.callee.object.object) && - isCallExpression(node.body.callee.object.object.arguments[0]) && - ASTUtils.isIdentifier( - node.body.callee.object.object.arguments[0].callee - ) && - helpers.isSyncQuery( - node.body.callee.object.object.arguments[0].callee - ) && - helpers.isPresenceAssert(node.body.callee.object); - - return ( - isQuery || isWrappedInPresenceAssert || isWrappedInNegatedPresenceAssert - ); - } - - function isScreenSyncQuery(node: TSESTree.ArrowFunctionExpression) { - if (!isArrowFunctionExpression(node) || !isCallExpression(node.body)) { - return false; - } - - if ( - !isMemberExpression(node.body.callee) || - !ASTUtils.isIdentifier(node.body.callee.property) - ) { - return false; - } - - if ( - !ASTUtils.isIdentifier(node.body.callee.object) && - !isCallExpression(node.body.callee.object) && - !isMemberExpression(node.body.callee.object) - ) { - return false; - } - - const isWrappedInPresenceAssert = - helpers.isPresenceAssert(node.body.callee) && - isCallExpression(node.body.callee.object) && - isCallExpression(node.body.callee.object.arguments[0]) && - isMemberExpression(node.body.callee.object.arguments[0].callee) && - ASTUtils.isIdentifier( - node.body.callee.object.arguments[0].callee.object - ); - - const isWrappedInNegatedPresenceAssert = - isMemberExpression(node.body.callee.object) && - helpers.isPresenceAssert(node.body.callee.object) && - isCallExpression(node.body.callee.object.object) && - isCallExpression(node.body.callee.object.object.arguments[0]) && - isMemberExpression(node.body.callee.object.object.arguments[0].callee); - - return ( - helpers.isSyncQuery(node.body.callee.property) || - isWrappedInPresenceAssert || - isWrappedInNegatedPresenceAssert - ); - } - - function getQueryArguments(node: TSESTree.CallExpression) { - if ( - isMemberExpression(node.callee) && - isCallExpression(node.callee.object) && - isCallExpression(node.callee.object.arguments[0]) - ) { - return node.callee.object.arguments[0].arguments; - } - - if ( - isMemberExpression(node.callee) && - isMemberExpression(node.callee.object) && - isCallExpression(node.callee.object.object) && - isCallExpression(node.callee.object.object.arguments[0]) - ) { - return node.callee.object.object.arguments[0].arguments; - } - - return node.arguments; - } - - return { - 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { - if ( - !ASTUtils.isIdentifier(node.callee) || - !helpers.isAsyncUtil(node.callee, WAIT_METHODS) - ) { - return; - } - // ensure the only argument is an arrow function expression - if the arrow function is a block - // we skip it - const argument = node.arguments[0]; - if ( - !isArrowFunctionExpression(argument) || - !isCallExpression(argument.body) - ) { - 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); - - if (!caller) { - return; - } - - // shape of () => screen.getByText - const fullQueryMethod = getWrongQueryName(argument); - - if (!fullQueryMethod) { - return; - } - - const queryVariant = getFindByQueryVariant(fullQueryMethod); - const callArguments = getQueryArguments(argument.body); - const queryMethod = fullQueryMethod.split('By')[1]; - - if (!queryMethod) { - return; - } - - reportInvalidUsage(node, { - queryMethod, - queryVariant, - prevQuery: fullQueryMethod, - waitForMethodName, - fix(fixer) { - const property = ( - (argument.body as TSESTree.CallExpression) - .callee as TSESTree.MemberExpression - ).property; - if (helpers.isCustomQuery(property as TSESTree.Identifier)) { - return null; - } - const newCode = `${caller}.${queryVariant}${queryMethod}(${callArguments - .map((callArgNode) => sourceCode.getText(callArgNode)) - .join(', ')})`; - return fixer.replaceText(node, newCode); - }, - }); - return; - } - - if (!isSyncQuery(argument)) { - return; - } - - // shape of () => getByText - const fullQueryMethod = getWrongQueryName(argument); - - if (!fullQueryMethod) { - return; - } - - const queryMethod = fullQueryMethod.split('By')[1]; - const queryVariant = getFindByQueryVariant(fullQueryMethod); - const callArguments = getQueryArguments(argument.body); - - reportInvalidUsage(node, { - queryMethod, - queryVariant, - prevQuery: fullQueryMethod, - waitForMethodName, - fix(fixer) { - // we know from above callee is an Identifier - if ( - helpers.isCustomQuery( - (argument.body as TSESTree.CallExpression) - .callee as TSESTree.Identifier - ) - ) { - return null; - } - const findByMethod = `${queryVariant}${queryMethod}`; - const allFixes: TSESLint.RuleFix[] = []; - // this updates waitFor with findBy* - const newCode = `${findByMethod}(${callArguments - .map((callArgNode) => sourceCode.getText(callArgNode)) - .join(', ')})`; - allFixes.push(fixer.replaceText(node, newCode)); - - // this adds the findBy* declaration - adding it to the list of destructured variables { findBy* } = render() - const definition = findRenderDefinitionDeclaration( - context.getScope(), - fullQueryMethod - ); - // I think it should always find it, otherwise code should not be valid (it'd be using undeclared variables) - if (!definition) { - return allFixes; - } - // check the declaration is part of a destructuring - if ( - definition.parent && - isObjectPattern(definition.parent.parent) - ) { - const allVariableDeclarations = definition.parent.parent; - // verify if the findBy* method was already declared - if ( - allVariableDeclarations.properties.some( - (p) => - isProperty(p) && - ASTUtils.isIdentifier(p.key) && - p.key.name === findByMethod - ) - ) { - return allFixes; - } - // the last character of a destructuring is always a "}", so we should replace it with the findBy* declaration - const textDestructuring = sourceCode.getText( - allVariableDeclarations - ); - const text = textDestructuring.replace( - /(\s*})$/, - `, ${findByMethod}$1` - ); - allFixes.push(fixer.replaceText(allVariableDeclarations, text)); - } - - return allFixes; - }, - }); - }, - }; - }, + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: + 'Suggest using `find(All)By*` query instead of `waitFor` + `get(All)By*` to wait for elements', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + preferFindBy: + 'Prefer `{{queryVariant}}{{queryMethod}}` query over using `{{waitForMethodName}}` + `{{prevQuery}}`', + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + + create(context, _, helpers) { + const sourceCode = context.getSourceCode(); + + /** + * 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 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( + node: TSESTree.CallExpression, + replacementParams: { + queryVariant: 'findAllBy' | 'findBy'; + queryMethod: string; + prevQuery: string; + waitForMethodName: string; + fix: TSESLint.ReportFixFunction; + } + ) { + const { queryMethod, queryVariant, prevQuery, waitForMethodName, fix } = + replacementParams; + context.report({ + node, + messageId: 'preferFindBy', + data: { + queryVariant, + queryMethod, + prevQuery, + waitForMethodName, + }, + fix, + }); + } + + function getWrongQueryNameInAssertion( + node: TSESTree.ArrowFunctionExpression + ) { + if ( + !isCallExpression(node.body) || + !isMemberExpression(node.body.callee) + ) { + return null; + } + + // expect(getByText).toBeInTheDocument() shape + if ( + isCallExpression(node.body.callee.object) && + isCallExpression(node.body.callee.object.arguments[0]) && + ASTUtils.isIdentifier(node.body.callee.object.arguments[0].callee) + ) { + return node.body.callee.object.arguments[0].callee.name; + } + + if (!ASTUtils.isIdentifier(node.body.callee.property)) { + return null; + } + + // expect(screen.getByText).toBeInTheDocument() shape + if ( + isCallExpression(node.body.callee.object) && + isCallExpression(node.body.callee.object.arguments[0]) && + isMemberExpression(node.body.callee.object.arguments[0].callee) && + ASTUtils.isIdentifier( + node.body.callee.object.arguments[0].callee.property + ) + ) { + return node.body.callee.object.arguments[0].callee.property.name; + } + + // expect(screen.getByText).not shape + if ( + isMemberExpression(node.body.callee.object) && + isCallExpression(node.body.callee.object.object) && + isCallExpression(node.body.callee.object.object.arguments[0]) && + isMemberExpression( + node.body.callee.object.object.arguments[0].callee + ) && + ASTUtils.isIdentifier( + node.body.callee.object.object.arguments[0].callee.property + ) + ) { + return node.body.callee.object.object.arguments[0].callee.property.name; + } + + // expect(getByText).not shape + if ( + isMemberExpression(node.body.callee.object) && + isCallExpression(node.body.callee.object.object) && + isCallExpression(node.body.callee.object.object.arguments[0]) && + ASTUtils.isIdentifier( + node.body.callee.object.object.arguments[0].callee + ) + ) { + return node.body.callee.object.object.arguments[0].callee.name; + } + + return node.body.callee.property.name; + } + + function getWrongQueryName(node: TSESTree.ArrowFunctionExpression) { + if (!isCallExpression(node.body)) { + return null; + } + + // expect(() => getByText) and expect(() => screen.getByText) shape + if ( + ASTUtils.isIdentifier(node.body.callee) && + helpers.isSyncQuery(node.body.callee) + ) { + return node.body.callee.name; + } + + return getWrongQueryNameInAssertion(node); + } + + function getCaller(node: TSESTree.ArrowFunctionExpression) { + if ( + !isCallExpression(node.body) || + !isMemberExpression(node.body.callee) + ) { + return null; + } + + if (ASTUtils.isIdentifier(node.body.callee.object)) { + // () => screen.getByText + return node.body.callee.object.name; + } + + if ( + // expect() + isCallExpression(node.body.callee.object) && + ASTUtils.isIdentifier(node.body.callee.object.callee) && + isCallExpression(node.body.callee.object.arguments[0]) && + isMemberExpression(node.body.callee.object.arguments[0].callee) && + ASTUtils.isIdentifier( + node.body.callee.object.arguments[0].callee.object + ) + ) { + return node.body.callee.object.arguments[0].callee.object.name; + } + + if ( + // expect().not + isMemberExpression(node.body.callee.object) && + isCallExpression(node.body.callee.object.object) && + isCallExpression(node.body.callee.object.object.arguments[0]) && + isMemberExpression( + node.body.callee.object.object.arguments[0].callee + ) && + ASTUtils.isIdentifier( + node.body.callee.object.object.arguments[0].callee.object + ) + ) { + return node.body.callee.object.object.arguments[0].callee.object.name; + } + + return null; + } + + function isSyncQuery(node: TSESTree.ArrowFunctionExpression) { + if (!isCallExpression(node.body)) { + return false; + } + + const isQuery = + ASTUtils.isIdentifier(node.body.callee) && + helpers.isSyncQuery(node.body.callee); + + const isWrappedInPresenceAssert = + isMemberExpression(node.body.callee) && + isCallExpression(node.body.callee.object) && + isCallExpression(node.body.callee.object.arguments[0]) && + ASTUtils.isIdentifier(node.body.callee.object.arguments[0].callee) && + helpers.isSyncQuery(node.body.callee.object.arguments[0].callee) && + helpers.isPresenceAssert(node.body.callee); + + const isWrappedInNegatedPresenceAssert = + isMemberExpression(node.body.callee) && + isMemberExpression(node.body.callee.object) && + isCallExpression(node.body.callee.object.object) && + isCallExpression(node.body.callee.object.object.arguments[0]) && + ASTUtils.isIdentifier( + node.body.callee.object.object.arguments[0].callee + ) && + helpers.isSyncQuery( + node.body.callee.object.object.arguments[0].callee + ) && + helpers.isPresenceAssert(node.body.callee.object); + + return ( + isQuery || isWrappedInPresenceAssert || isWrappedInNegatedPresenceAssert + ); + } + + function isScreenSyncQuery(node: TSESTree.ArrowFunctionExpression) { + if (!isArrowFunctionExpression(node) || !isCallExpression(node.body)) { + return false; + } + + if ( + !isMemberExpression(node.body.callee) || + !ASTUtils.isIdentifier(node.body.callee.property) + ) { + return false; + } + + if ( + !ASTUtils.isIdentifier(node.body.callee.object) && + !isCallExpression(node.body.callee.object) && + !isMemberExpression(node.body.callee.object) + ) { + return false; + } + + const isWrappedInPresenceAssert = + helpers.isPresenceAssert(node.body.callee) && + isCallExpression(node.body.callee.object) && + isCallExpression(node.body.callee.object.arguments[0]) && + isMemberExpression(node.body.callee.object.arguments[0].callee) && + ASTUtils.isIdentifier( + node.body.callee.object.arguments[0].callee.object + ); + + const isWrappedInNegatedPresenceAssert = + isMemberExpression(node.body.callee.object) && + helpers.isPresenceAssert(node.body.callee.object) && + isCallExpression(node.body.callee.object.object) && + isCallExpression(node.body.callee.object.object.arguments[0]) && + isMemberExpression(node.body.callee.object.object.arguments[0].callee); + + return ( + helpers.isSyncQuery(node.body.callee.property) || + isWrappedInPresenceAssert || + isWrappedInNegatedPresenceAssert + ); + } + + function getQueryArguments(node: TSESTree.CallExpression) { + if ( + isMemberExpression(node.callee) && + isCallExpression(node.callee.object) && + isCallExpression(node.callee.object.arguments[0]) + ) { + return node.callee.object.arguments[0].arguments; + } + + if ( + isMemberExpression(node.callee) && + isMemberExpression(node.callee.object) && + isCallExpression(node.callee.object.object) && + isCallExpression(node.callee.object.object.arguments[0]) + ) { + return node.callee.object.object.arguments[0].arguments; + } + + return node.arguments; + } + + return { + 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { + if ( + !ASTUtils.isIdentifier(node.callee) || + !helpers.isAsyncUtil(node.callee, WAIT_METHODS) + ) { + return; + } + // ensure the only argument is an arrow function expression - if the arrow function is a block + // we skip it + const argument = node.arguments[0]; + if ( + !isArrowFunctionExpression(argument) || + !isCallExpression(argument.body) + ) { + 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); + + if (!caller) { + return; + } + + // shape of () => screen.getByText + const fullQueryMethod = getWrongQueryName(argument); + + if (!fullQueryMethod) { + return; + } + + const queryVariant = getFindByQueryVariant(fullQueryMethod); + const callArguments = getQueryArguments(argument.body); + const queryMethod = fullQueryMethod.split('By')[1]; + + if (!queryMethod) { + return; + } + + reportInvalidUsage(node, { + queryMethod, + queryVariant, + prevQuery: fullQueryMethod, + waitForMethodName, + fix(fixer) { + const property = ( + (argument.body as TSESTree.CallExpression) + .callee as TSESTree.MemberExpression + ).property; + if (helpers.isCustomQuery(property as TSESTree.Identifier)) { + return null; + } + const newCode = `${caller}.${queryVariant}${queryMethod}(${callArguments + .map((callArgNode) => sourceCode.getText(callArgNode)) + .join(', ')})`; + return fixer.replaceText(node, newCode); + }, + }); + return; + } + + if (!isSyncQuery(argument)) { + return; + } + + // shape of () => getByText + const fullQueryMethod = getWrongQueryName(argument); + + if (!fullQueryMethod) { + return; + } + + const queryMethod = fullQueryMethod.split('By')[1]; + const queryVariant = getFindByQueryVariant(fullQueryMethod); + const callArguments = getQueryArguments(argument.body); + + reportInvalidUsage(node, { + queryMethod, + queryVariant, + prevQuery: fullQueryMethod, + waitForMethodName, + fix(fixer) { + // we know from above callee is an Identifier + if ( + helpers.isCustomQuery( + (argument.body as TSESTree.CallExpression) + .callee as TSESTree.Identifier + ) + ) { + return null; + } + const findByMethod = `${queryVariant}${queryMethod}`; + const allFixes: TSESLint.RuleFix[] = []; + // this updates waitFor with findBy* + const newCode = `${findByMethod}(${callArguments + .map((callArgNode) => sourceCode.getText(callArgNode)) + .join(', ')})`; + allFixes.push(fixer.replaceText(node, newCode)); + + // this adds the findBy* declaration - adding it to the list of destructured variables { findBy* } = render() + const definition = findRenderDefinitionDeclaration( + context.getScope(), + fullQueryMethod + ); + // I think it should always find it, otherwise code should not be valid (it'd be using undeclared variables) + if (!definition) { + return allFixes; + } + // check the declaration is part of a destructuring + if ( + definition.parent && + isObjectPattern(definition.parent.parent) + ) { + const allVariableDeclarations = definition.parent.parent; + // verify if the findBy* method was already declared + if ( + allVariableDeclarations.properties.some( + (p) => + isProperty(p) && + ASTUtils.isIdentifier(p.key) && + p.key.name === findByMethod + ) + ) { + return allFixes; + } + // the last character of a destructuring is always a "}", so we should replace it with the findBy* declaration + const textDestructuring = sourceCode.getText( + allVariableDeclarations + ); + const text = textDestructuring.replace( + /(\s*})$/, + `, ${findByMethod}$1` + ); + allFixes.push(fixer.replaceText(allVariableDeclarations, text)); + } + + return allFixes; + }, + }); + }, + }; + }, }); diff --git a/lib/rules/prefer-presence-queries.ts b/lib/rules/prefer-presence-queries.ts index 534ea8cb..9ec96c38 100644 --- a/lib/rules/prefer-presence-queries.ts +++ b/lib/rules/prefer-presence-queries.ts @@ -6,85 +6,85 @@ import { findClosestCallNode, isMemberExpression } from '../node-utils'; export const RULE_NAME = 'prefer-presence-queries'; export type MessageIds = 'wrongAbsenceQuery' | 'wrongPresenceQuery'; export type Options = [ - { - presence?: boolean; - absence?: boolean; - } + { + presence?: boolean; + absence?: boolean; + } ]; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - docs: { - description: - 'Ensure appropriate `get*`/`query*` queries are used with their respective matchers', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - wrongPresenceQuery: - 'Use `getBy*` queries rather than `queryBy*` for checking element is present', - wrongAbsenceQuery: - 'Use `queryBy*` queries rather than `getBy*` for checking element is NOT present', - }, - schema: [ - { - type: 'object', - additionalProperties: false, - properties: { - presence: { - type: 'boolean', - }, - absence: { - type: 'boolean', - }, - }, - }, - ], - type: 'suggestion', - }, - defaultOptions: [ - { - presence: true, - absence: true, - }, - ], + name: RULE_NAME, + meta: { + docs: { + description: + 'Ensure appropriate `get*`/`query*` queries are used with their respective matchers', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + wrongPresenceQuery: + 'Use `getBy*` queries rather than `queryBy*` for checking element is present', + wrongAbsenceQuery: + 'Use `queryBy*` queries rather than `getBy*` for checking element is NOT present', + }, + schema: [ + { + type: 'object', + additionalProperties: false, + properties: { + presence: { + type: 'boolean', + }, + absence: { + type: 'boolean', + }, + }, + }, + ], + type: 'suggestion', + }, + defaultOptions: [ + { + presence: true, + absence: true, + }, + ], - create(context, [{ absence = true, presence = true }], helpers) { - return { - 'CallExpression Identifier'(node: TSESTree.Identifier) { - const expectCallNode = findClosestCallNode(node, 'expect'); + create(context, [{ absence = true, presence = true }], helpers) { + return { + 'CallExpression Identifier'(node: TSESTree.Identifier) { + const expectCallNode = findClosestCallNode(node, 'expect'); - if (!expectCallNode || !isMemberExpression(expectCallNode.parent)) { - return; - } + if (!expectCallNode || !isMemberExpression(expectCallNode.parent)) { + return; + } - // Sync queries (getBy and queryBy) are corresponding ones used - // to check presence or absence. If none found, stop the rule. - if (!helpers.isSyncQuery(node)) { - return; - } + // Sync queries (getBy and queryBy) are corresponding ones used + // to check presence or absence. If none found, stop the rule. + if (!helpers.isSyncQuery(node)) { + return; + } - const isPresenceQuery = helpers.isGetQueryVariant(node); - const expectStatement = expectCallNode.parent; - const isPresenceAssert = helpers.isPresenceAssert(expectStatement); - const isAbsenceAssert = helpers.isAbsenceAssert(expectStatement); + const isPresenceQuery = helpers.isGetQueryVariant(node); + const expectStatement = expectCallNode.parent; + const isPresenceAssert = helpers.isPresenceAssert(expectStatement); + const isAbsenceAssert = helpers.isAbsenceAssert(expectStatement); - if (!isPresenceAssert && !isAbsenceAssert) { - return; - } + if (!isPresenceAssert && !isAbsenceAssert) { + return; + } - if (presence && isPresenceAssert && !isPresenceQuery) { - context.report({ node, messageId: 'wrongPresenceQuery' }); - } else if (absence && isAbsenceAssert && isPresenceQuery) { - context.report({ node, messageId: 'wrongAbsenceQuery' }); - } - }, - }; - }, + if (presence && isPresenceAssert && !isPresenceQuery) { + context.report({ node, messageId: 'wrongPresenceQuery' }); + } else if (absence && isAbsenceAssert && isPresenceQuery) { + context.report({ node, messageId: 'wrongAbsenceQuery' }); + } + }, + }; + }, }); diff --git a/lib/rules/prefer-query-by-disappearance.ts b/lib/rules/prefer-query-by-disappearance.ts index d34565ea..1819656c 100644 --- a/lib/rules/prefer-query-by-disappearance.ts +++ b/lib/rules/prefer-query-by-disappearance.ts @@ -2,14 +2,14 @@ import { TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getPropertyIdentifierNode, - isArrowFunctionExpression, - isCallExpression, - isMemberExpression, - isFunctionExpression, - isExpressionStatement, - isReturnStatement, - isBlockStatement, + getPropertyIdentifierNode, + isArrowFunctionExpression, + isCallExpression, + isMemberExpression, + isFunctionExpression, + isExpressionStatement, + isReturnStatement, + isBlockStatement, } from '../node-utils'; export const RULE_NAME = 'prefer-query-by-disappearance'; @@ -17,167 +17,167 @@ type MessageIds = 'preferQueryByDisappearance'; type Options = []; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: - 'Suggest using `queryBy*` queries when waiting for disappearance', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - preferQueryByDisappearance: - 'Prefer using queryBy* when waiting for disappearance', - }, - schema: [], - }, - defaultOptions: [], - - create(context, _, helpers) { - function isWaitForElementToBeRemoved(node: TSESTree.CallExpression) { - const identifierNode = getPropertyIdentifierNode(node); - - if (!identifierNode) { - return false; - } - - return helpers.isAsyncUtil(identifierNode, ['waitForElementToBeRemoved']); - } - - function isReportableExpression(node: TSESTree.LeftHandSideExpression) { - const argumentProperty = isMemberExpression(node) - ? getPropertyIdentifierNode(node.property) - : getPropertyIdentifierNode(node); - - if (!argumentProperty) { - return false; - } - - return ( - helpers.isGetQueryVariant(argumentProperty) || - helpers.isFindQueryVariant(argumentProperty) - ); - } - - function isNonCallbackViolation(node: TSESTree.CallExpressionArgument) { - if (!isCallExpression(node)) { - return false; - } - - if ( - !isMemberExpression(node.callee) && - !getPropertyIdentifierNode(node.callee) - ) { - return false; - } - - return isReportableExpression(node.callee); - } - - function isReturnViolation(node: TSESTree.Statement) { - if (!isReturnStatement(node) || !isCallExpression(node.argument)) { - return false; - } - - return isReportableExpression(node.argument.callee); - } - - function isNonReturnViolation(node: TSESTree.Statement) { - if (!isExpressionStatement(node) || !isCallExpression(node.expression)) { - return false; - } - - if ( - !isMemberExpression(node.expression.callee) && - !getPropertyIdentifierNode(node.expression.callee) - ) { - return false; - } - - return isReportableExpression(node.expression.callee); - } - - function isStatementViolation(statement: TSESTree.Statement) { - return isReturnViolation(statement) || isNonReturnViolation(statement); - } - - function isFunctionExpressionViolation( - node: TSESTree.CallExpressionArgument - ) { - if (!isFunctionExpression(node)) { - return false; - } - - return node.body.body.some((statement) => - isStatementViolation(statement) - ); - } - - function isArrowFunctionBodyViolation( - node: TSESTree.CallExpressionArgument - ) { - if (!isArrowFunctionExpression(node) || !isBlockStatement(node.body)) { - return false; - } - - return node.body.body.some((statement) => - isStatementViolation(statement) - ); - } - - function isArrowFunctionImplicitReturnViolation( - node: TSESTree.CallExpressionArgument - ) { - if (!isArrowFunctionExpression(node) || !isCallExpression(node.body)) { - return false; - } - - if ( - !isMemberExpression(node.body.callee) && - !getPropertyIdentifierNode(node.body.callee) - ) { - return false; - } - - return isReportableExpression(node.body.callee); - } - - function isArrowFunctionViolation(node: TSESTree.CallExpressionArgument) { - return ( - isArrowFunctionBodyViolation(node) || - isArrowFunctionImplicitReturnViolation(node) - ); - } - - function check(node: TSESTree.CallExpression) { - if (!isWaitForElementToBeRemoved(node)) { - return; - } - - const argumentNode = node.arguments[0]; - - if ( - !isNonCallbackViolation(argumentNode) && - !isArrowFunctionViolation(argumentNode) && - !isFunctionExpressionViolation(argumentNode) - ) { - return; - } - - context.report({ - node: argumentNode, - messageId: 'preferQueryByDisappearance', - }); - } - - return { - CallExpression: check, - }; - }, + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: + 'Suggest using `queryBy*` queries when waiting for disappearance', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + preferQueryByDisappearance: + 'Prefer using queryBy* when waiting for disappearance', + }, + schema: [], + }, + defaultOptions: [], + + create(context, _, helpers) { + function isWaitForElementToBeRemoved(node: TSESTree.CallExpression) { + const identifierNode = getPropertyIdentifierNode(node); + + if (!identifierNode) { + return false; + } + + return helpers.isAsyncUtil(identifierNode, ['waitForElementToBeRemoved']); + } + + function isReportableExpression(node: TSESTree.LeftHandSideExpression) { + const argumentProperty = isMemberExpression(node) + ? getPropertyIdentifierNode(node.property) + : getPropertyIdentifierNode(node); + + if (!argumentProperty) { + return false; + } + + return ( + helpers.isGetQueryVariant(argumentProperty) || + helpers.isFindQueryVariant(argumentProperty) + ); + } + + function isNonCallbackViolation(node: TSESTree.CallExpressionArgument) { + if (!isCallExpression(node)) { + return false; + } + + if ( + !isMemberExpression(node.callee) && + !getPropertyIdentifierNode(node.callee) + ) { + return false; + } + + return isReportableExpression(node.callee); + } + + function isReturnViolation(node: TSESTree.Statement) { + if (!isReturnStatement(node) || !isCallExpression(node.argument)) { + return false; + } + + return isReportableExpression(node.argument.callee); + } + + function isNonReturnViolation(node: TSESTree.Statement) { + if (!isExpressionStatement(node) || !isCallExpression(node.expression)) { + return false; + } + + if ( + !isMemberExpression(node.expression.callee) && + !getPropertyIdentifierNode(node.expression.callee) + ) { + return false; + } + + return isReportableExpression(node.expression.callee); + } + + function isStatementViolation(statement: TSESTree.Statement) { + return isReturnViolation(statement) || isNonReturnViolation(statement); + } + + function isFunctionExpressionViolation( + node: TSESTree.CallExpressionArgument + ) { + if (!isFunctionExpression(node)) { + return false; + } + + return node.body.body.some((statement) => + isStatementViolation(statement) + ); + } + + function isArrowFunctionBodyViolation( + node: TSESTree.CallExpressionArgument + ) { + if (!isArrowFunctionExpression(node) || !isBlockStatement(node.body)) { + return false; + } + + return node.body.body.some((statement) => + isStatementViolation(statement) + ); + } + + function isArrowFunctionImplicitReturnViolation( + node: TSESTree.CallExpressionArgument + ) { + if (!isArrowFunctionExpression(node) || !isCallExpression(node.body)) { + return false; + } + + if ( + !isMemberExpression(node.body.callee) && + !getPropertyIdentifierNode(node.body.callee) + ) { + return false; + } + + return isReportableExpression(node.body.callee); + } + + function isArrowFunctionViolation(node: TSESTree.CallExpressionArgument) { + return ( + isArrowFunctionBodyViolation(node) || + isArrowFunctionImplicitReturnViolation(node) + ); + } + + function check(node: TSESTree.CallExpression) { + if (!isWaitForElementToBeRemoved(node)) { + return; + } + + const argumentNode = node.arguments[0]; + + if ( + !isNonCallbackViolation(argumentNode) && + !isArrowFunctionViolation(argumentNode) && + !isFunctionExpressionViolation(argumentNode) + ) { + return; + } + + context.report({ + node: argumentNode, + messageId: 'preferQueryByDisappearance', + }); + } + + return { + CallExpression: check, + }; + }, }); diff --git a/lib/rules/prefer-screen-queries.ts b/lib/rules/prefer-screen-queries.ts index 55acafad..0d49d426 100644 --- a/lib/rules/prefer-screen-queries.ts +++ b/lib/rules/prefer-screen-queries.ts @@ -2,14 +2,14 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getDeepestIdentifierNode, - getFunctionName, - getInnermostReturningFunction, - isCallExpression, - isMemberExpression, - isObjectExpression, - isObjectPattern, - isProperty, + getDeepestIdentifierNode, + getFunctionName, + getInnermostReturningFunction, + isCallExpression, + isMemberExpression, + isObjectExpression, + isObjectPattern, + isProperty, } from '../node-utils'; export const RULE_NAME = 'prefer-screen-queries'; @@ -17,175 +17,175 @@ export type MessageIds = 'preferScreenQueries'; type Options = []; const ALLOWED_RENDER_PROPERTIES_FOR_DESTRUCTURING = [ - 'container', - 'baseElement', + 'container', + 'baseElement', ]; function usesContainerOrBaseElement(node: TSESTree.CallExpression) { - const secondArgument = node.arguments[1]; - return ( - isObjectExpression(secondArgument) && - secondArgument.properties.some( - (property) => - isProperty(property) && - ASTUtils.isIdentifier(property.key) && - ALLOWED_RENDER_PROPERTIES_FOR_DESTRUCTURING.includes(property.key.name) - ) - ); + const secondArgument = node.arguments[1]; + return ( + isObjectExpression(secondArgument) && + secondArgument.properties.some( + (property) => + isProperty(property) && + ASTUtils.isIdentifier(property.key) && + ALLOWED_RENDER_PROPERTIES_FOR_DESTRUCTURING.includes(property.key.name) + ) + ); } export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: 'Suggest using `screen` while querying', - recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - preferScreenQueries: - 'Avoid destructuring queries from `render` result, use `screen.{{ name }}` instead', - }, - schema: [], - }, - defaultOptions: [], - - create(context, _, helpers) { - const renderWrapperNames: string[] = []; - - function detectRenderWrapper(node: TSESTree.Identifier): void { - const innerFunction = getInnermostReturningFunction(context, node); - - if (innerFunction) { - renderWrapperNames.push(getFunctionName(innerFunction)); - } - } - - function isReportableRender(node: TSESTree.Identifier): boolean { - return ( - helpers.isRenderUtil(node) || renderWrapperNames.includes(node.name) - ); - } - - function reportInvalidUsage(node: TSESTree.Identifier) { - context.report({ - node, - messageId: 'preferScreenQueries', - data: { - name: node.name, - }, - }); - } - - function saveSafeDestructuredQueries(node: TSESTree.VariableDeclarator) { - if (isObjectPattern(node.id)) { - for (const property of node.id.properties) { - if ( - isProperty(property) && - ASTUtils.isIdentifier(property.key) && - helpers.isBuiltInQuery(property.key) - ) { - safeDestructuredQueries.push(property.key.name); - } - } - } - } - - function isIdentifierAllowed(name: string) { - return ['screen', ...withinDeclaredVariables].includes(name); - } - - // keep here those queries which are safe and shouldn't be reported - // (from within, from render + container/base element, not related to TL, etc) - const safeDestructuredQueries: string[] = []; - // use an array as within might be used more than once in a test - const withinDeclaredVariables: string[] = []; - - return { - VariableDeclarator(node) { - if ( - !isCallExpression(node.init) || - !ASTUtils.isIdentifier(node.init.callee) - ) { - return; - } - - const isComingFromValidRender = isReportableRender(node.init.callee); - - if (!isComingFromValidRender) { - // save the destructured query methods as safe since they are coming - // from render not related to TL - saveSafeDestructuredQueries(node); - } - - const isWithinFunction = node.init.callee.name === 'within'; - const usesRenderOptions = - isComingFromValidRender && usesContainerOrBaseElement(node.init); - - if (!isWithinFunction && !usesRenderOptions) { - return; - } - - if (isObjectPattern(node.id)) { - // save the destructured query methods as safe since they are coming - // from within or render + base/container options - saveSafeDestructuredQueries(node); - } else if (ASTUtils.isIdentifier(node.id)) { - withinDeclaredVariables.push(node.id.name); - } - }, - CallExpression(node) { - const identifierNode = getDeepestIdentifierNode(node); - - if (!identifierNode) { - return; - } - - if (helpers.isRenderUtil(identifierNode)) { - detectRenderWrapper(identifierNode); - } - - if (!helpers.isBuiltInQuery(identifierNode)) { - return; - } - - if (!isMemberExpression(identifierNode.parent)) { - const isSafeDestructuredQuery = safeDestructuredQueries.some( - (queryName) => queryName === identifierNode.name - ); - if (isSafeDestructuredQuery) { - return; - } - - reportInvalidUsage(identifierNode); - return; - } - - const memberExpressionNode = identifierNode.parent; - if ( - isCallExpression(memberExpressionNode.object) && - ASTUtils.isIdentifier(memberExpressionNode.object.callee) && - memberExpressionNode.object.callee.name !== 'within' && - isReportableRender(memberExpressionNode.object.callee) && - !usesContainerOrBaseElement(memberExpressionNode.object) - ) { - reportInvalidUsage(identifierNode); - return; - } - - if ( - ASTUtils.isIdentifier(memberExpressionNode.object) && - !isIdentifierAllowed(memberExpressionNode.object.name) - ) { - reportInvalidUsage(identifierNode); - } - }, - }; - }, + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: 'Suggest using `screen` while querying', + recommendedConfig: { + dom: 'error', + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + preferScreenQueries: + 'Avoid destructuring queries from `render` result, use `screen.{{ name }}` instead', + }, + schema: [], + }, + defaultOptions: [], + + create(context, _, helpers) { + const renderWrapperNames: string[] = []; + + function detectRenderWrapper(node: TSESTree.Identifier): void { + const innerFunction = getInnermostReturningFunction(context, node); + + if (innerFunction) { + renderWrapperNames.push(getFunctionName(innerFunction)); + } + } + + function isReportableRender(node: TSESTree.Identifier): boolean { + return ( + helpers.isRenderUtil(node) || renderWrapperNames.includes(node.name) + ); + } + + function reportInvalidUsage(node: TSESTree.Identifier) { + context.report({ + node, + messageId: 'preferScreenQueries', + data: { + name: node.name, + }, + }); + } + + function saveSafeDestructuredQueries(node: TSESTree.VariableDeclarator) { + if (isObjectPattern(node.id)) { + for (const property of node.id.properties) { + if ( + isProperty(property) && + ASTUtils.isIdentifier(property.key) && + helpers.isBuiltInQuery(property.key) + ) { + safeDestructuredQueries.push(property.key.name); + } + } + } + } + + function isIdentifierAllowed(name: string) { + return ['screen', ...withinDeclaredVariables].includes(name); + } + + // keep here those queries which are safe and shouldn't be reported + // (from within, from render + container/base element, not related to TL, etc) + const safeDestructuredQueries: string[] = []; + // use an array as within might be used more than once in a test + const withinDeclaredVariables: string[] = []; + + return { + VariableDeclarator(node) { + if ( + !isCallExpression(node.init) || + !ASTUtils.isIdentifier(node.init.callee) + ) { + return; + } + + const isComingFromValidRender = isReportableRender(node.init.callee); + + if (!isComingFromValidRender) { + // save the destructured query methods as safe since they are coming + // from render not related to TL + saveSafeDestructuredQueries(node); + } + + const isWithinFunction = node.init.callee.name === 'within'; + const usesRenderOptions = + isComingFromValidRender && usesContainerOrBaseElement(node.init); + + if (!isWithinFunction && !usesRenderOptions) { + return; + } + + if (isObjectPattern(node.id)) { + // save the destructured query methods as safe since they are coming + // from within or render + base/container options + saveSafeDestructuredQueries(node); + } else if (ASTUtils.isIdentifier(node.id)) { + withinDeclaredVariables.push(node.id.name); + } + }, + CallExpression(node) { + const identifierNode = getDeepestIdentifierNode(node); + + if (!identifierNode) { + return; + } + + if (helpers.isRenderUtil(identifierNode)) { + detectRenderWrapper(identifierNode); + } + + if (!helpers.isBuiltInQuery(identifierNode)) { + return; + } + + if (!isMemberExpression(identifierNode.parent)) { + const isSafeDestructuredQuery = safeDestructuredQueries.some( + (queryName) => queryName === identifierNode.name + ); + if (isSafeDestructuredQuery) { + return; + } + + reportInvalidUsage(identifierNode); + return; + } + + const memberExpressionNode = identifierNode.parent; + if ( + isCallExpression(memberExpressionNode.object) && + ASTUtils.isIdentifier(memberExpressionNode.object.callee) && + memberExpressionNode.object.callee.name !== 'within' && + isReportableRender(memberExpressionNode.object.callee) && + !usesContainerOrBaseElement(memberExpressionNode.object) + ) { + reportInvalidUsage(identifierNode); + return; + } + + if ( + ASTUtils.isIdentifier(memberExpressionNode.object) && + !isIdentifierAllowed(memberExpressionNode.object.name) + ) { + reportInvalidUsage(identifierNode); + } + }, + }; + }, }); diff --git a/lib/rules/prefer-user-event.ts b/lib/rules/prefer-user-event.ts index b722eacb..d7338698 100644 --- a/lib/rules/prefer-user-event.ts +++ b/lib/rules/prefer-user-event.ts @@ -2,9 +2,9 @@ import { TSESTree, ASTUtils } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - findClosestCallExpressionNode, - isCallExpression, - isMemberExpression, + findClosestCallExpressionNode, + isCallExpression, + isMemberExpression, } from '../node-utils'; export const RULE_NAME = 'prefer-user-event'; @@ -13,183 +13,183 @@ export type MessageIds = 'preferUserEvent'; export type Options = [{ allowedMethods: string[] }]; export const UserEventMethods = [ - 'click', - 'dblClick', - 'type', - 'upload', - 'clear', - 'selectOptions', - 'deselectOptions', - 'tab', - 'hover', - 'unhover', - 'paste', + 'click', + 'dblClick', + 'type', + 'upload', + 'clear', + 'selectOptions', + 'deselectOptions', + 'tab', + 'hover', + 'unhover', + 'paste', ] as const; type UserEventMethodsType = typeof UserEventMethods[number]; // maps fireEvent methods to userEvent. Those not found here, do not have an equivalent (yet) export const MAPPING_TO_USER_EVENT: Record = { - click: ['click', 'type', 'selectOptions', 'deselectOptions'], - change: ['upload', 'type', 'clear', 'selectOptions', 'deselectOptions'], - dblClick: ['dblClick'], - input: ['type', 'upload', 'selectOptions', 'deselectOptions', 'paste'], - keyDown: ['type', 'tab'], - keyPress: ['type'], - keyUp: ['type', 'tab'], - mouseDown: ['click', 'dblClick', 'selectOptions', 'deselectOptions'], - mouseEnter: ['hover', 'selectOptions', 'deselectOptions'], - mouseLeave: ['unhover'], - mouseMove: ['hover', 'unhover', 'selectOptions', 'deselectOptions'], - mouseOut: ['unhover'], - mouseOver: ['hover', 'selectOptions', 'deselectOptions'], - mouseUp: ['click', 'dblClick', 'selectOptions', 'deselectOptions'], - paste: ['paste'], - pointerDown: ['click', 'dblClick', 'selectOptions', 'deselectOptions'], - pointerEnter: ['hover', 'selectOptions', 'deselectOptions'], - pointerLeave: ['unhover'], - pointerMove: ['hover', 'unhover', 'selectOptions', 'deselectOptions'], - pointerOut: ['unhover'], - pointerOver: ['hover', 'selectOptions', 'deselectOptions'], - pointerUp: ['click', 'dblClick', 'selectOptions', 'deselectOptions'], + click: ['click', 'type', 'selectOptions', 'deselectOptions'], + change: ['upload', 'type', 'clear', 'selectOptions', 'deselectOptions'], + dblClick: ['dblClick'], + input: ['type', 'upload', 'selectOptions', 'deselectOptions', 'paste'], + keyDown: ['type', 'tab'], + keyPress: ['type'], + keyUp: ['type', 'tab'], + mouseDown: ['click', 'dblClick', 'selectOptions', 'deselectOptions'], + mouseEnter: ['hover', 'selectOptions', 'deselectOptions'], + mouseLeave: ['unhover'], + mouseMove: ['hover', 'unhover', 'selectOptions', 'deselectOptions'], + mouseOut: ['unhover'], + mouseOver: ['hover', 'selectOptions', 'deselectOptions'], + mouseUp: ['click', 'dblClick', 'selectOptions', 'deselectOptions'], + paste: ['paste'], + pointerDown: ['click', 'dblClick', 'selectOptions', 'deselectOptions'], + pointerEnter: ['hover', 'selectOptions', 'deselectOptions'], + pointerLeave: ['unhover'], + pointerMove: ['hover', 'unhover', 'selectOptions', 'deselectOptions'], + pointerOut: ['unhover'], + pointerOver: ['hover', 'selectOptions', 'deselectOptions'], + pointerUp: ['click', 'dblClick', 'selectOptions', 'deselectOptions'], }; function buildErrorMessage(fireEventMethod: string) { - const userEventMethods = MAPPING_TO_USER_EVENT[fireEventMethod].map( - (methodName) => `userEvent.${methodName}` - ); + const userEventMethods = MAPPING_TO_USER_EVENT[fireEventMethod].map( + (methodName) => `userEvent.${methodName}` + ); - // TODO: when min node version is 13, we can reimplement this using `Intl.ListFormat` - return userEventMethods.join(', ').replace(/, ([a-zA-Z.]+)$/, ', or $1'); + // TODO: when min node version is 13, we can reimplement this using `Intl.ListFormat` + return userEventMethods.join(', ').replace(/, ([a-zA-Z.]+)$/, ', or $1'); } const fireEventMappedMethods = Object.keys(MAPPING_TO_USER_EVENT); export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: - 'Suggest using `userEvent` over `fireEvent` for simulating user interactions', - recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: false, - marko: false, - }, - }, - messages: { - preferUserEvent: - 'Prefer using {{userEventMethods}} over fireEvent.{{fireEventMethod}}', - }, - schema: [ - { - type: 'object', - properties: { - allowedMethods: { type: 'array' }, - }, - }, - ], - }, - defaultOptions: [{ allowedMethods: [] }], - - create(context, [options], helpers) { - const { allowedMethods } = options; - const createEventVariables: Record = {}; - - const isfireEventMethodAllowed = (methodName: string) => - !fireEventMappedMethods.includes(methodName) || - allowedMethods.includes(methodName); - - const getFireEventMethodName = ( - callExpressionNode: TSESTree.CallExpression, - node: TSESTree.Identifier - ) => { - if ( - !ASTUtils.isIdentifier(callExpressionNode.callee) && - !isMemberExpression(callExpressionNode.callee) - ) { - return node.name; - } - const secondArgument = callExpressionNode.arguments[1]; - if ( - ASTUtils.isIdentifier(secondArgument) && - createEventVariables[secondArgument.name] !== undefined - ) { - return createEventVariables[secondArgument.name]; - } - if ( - !isCallExpression(secondArgument) || - !helpers.isCreateEventUtil(secondArgument) - ) { - return node.name; - } - if (ASTUtils.isIdentifier(secondArgument.callee)) { - // createEvent('click', foo) - return (secondArgument.arguments[0] as TSESTree.Literal) - .value as string; - } - // createEvent.click(foo) - return ( - (secondArgument.callee as TSESTree.MemberExpression) - .property as TSESTree.Identifier - ).name; - }; - return { - 'CallExpression Identifier'(node: TSESTree.Identifier) { - if (!helpers.isFireEventMethod(node)) { - return; - } - const closestCallExpression = findClosestCallExpressionNode(node, true); - - if (!closestCallExpression) { - return; - } - - const fireEventMethodName = getFireEventMethodName( - closestCallExpression, - node - ); - - if ( - !fireEventMethodName || - isfireEventMethodAllowed(fireEventMethodName) - ) { - return; - } - context.report({ - node: closestCallExpression.callee, - messageId: 'preferUserEvent', - data: { - userEventMethods: buildErrorMessage(fireEventMethodName), - fireEventMethod: fireEventMethodName, - }, - }); - }, - - VariableDeclarator(node: TSESTree.VariableDeclarator) { - if ( - !isCallExpression(node.init) || - !helpers.isCreateEventUtil(node.init) || - !ASTUtils.isIdentifier(node.id) - ) { - return; - } - let fireEventMethodName = ''; - if ( - isMemberExpression(node.init.callee) && - ASTUtils.isIdentifier(node.init.callee.property) - ) { - fireEventMethodName = node.init.callee.property.name; - } else if (node.init.arguments.length > 0) { - fireEventMethodName = (node.init.arguments[0] as TSESTree.Literal) - .value as string; - } - if (!isfireEventMethodAllowed(fireEventMethodName)) { - createEventVariables[node.id.name] = fireEventMethodName; - } - }, - }; - }, + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: + 'Suggest using `userEvent` over `fireEvent` for simulating user interactions', + recommendedConfig: { + dom: false, + angular: false, + react: false, + vue: false, + marko: false, + }, + }, + messages: { + preferUserEvent: + 'Prefer using {{userEventMethods}} over fireEvent.{{fireEventMethod}}', + }, + schema: [ + { + type: 'object', + properties: { + allowedMethods: { type: 'array' }, + }, + }, + ], + }, + defaultOptions: [{ allowedMethods: [] }], + + create(context, [options], helpers) { + const { allowedMethods } = options; + const createEventVariables: Record = {}; + + const isfireEventMethodAllowed = (methodName: string) => + !fireEventMappedMethods.includes(methodName) || + allowedMethods.includes(methodName); + + const getFireEventMethodName = ( + callExpressionNode: TSESTree.CallExpression, + node: TSESTree.Identifier + ) => { + if ( + !ASTUtils.isIdentifier(callExpressionNode.callee) && + !isMemberExpression(callExpressionNode.callee) + ) { + return node.name; + } + const secondArgument = callExpressionNode.arguments[1]; + if ( + ASTUtils.isIdentifier(secondArgument) && + createEventVariables[secondArgument.name] !== undefined + ) { + return createEventVariables[secondArgument.name]; + } + if ( + !isCallExpression(secondArgument) || + !helpers.isCreateEventUtil(secondArgument) + ) { + return node.name; + } + if (ASTUtils.isIdentifier(secondArgument.callee)) { + // createEvent('click', foo) + return (secondArgument.arguments[0] as TSESTree.Literal) + .value as string; + } + // createEvent.click(foo) + return ( + (secondArgument.callee as TSESTree.MemberExpression) + .property as TSESTree.Identifier + ).name; + }; + return { + 'CallExpression Identifier'(node: TSESTree.Identifier) { + if (!helpers.isFireEventMethod(node)) { + return; + } + const closestCallExpression = findClosestCallExpressionNode(node, true); + + if (!closestCallExpression) { + return; + } + + const fireEventMethodName = getFireEventMethodName( + closestCallExpression, + node + ); + + if ( + !fireEventMethodName || + isfireEventMethodAllowed(fireEventMethodName) + ) { + return; + } + context.report({ + node: closestCallExpression.callee, + messageId: 'preferUserEvent', + data: { + userEventMethods: buildErrorMessage(fireEventMethodName), + fireEventMethod: fireEventMethodName, + }, + }); + }, + + VariableDeclarator(node: TSESTree.VariableDeclarator) { + if ( + !isCallExpression(node.init) || + !helpers.isCreateEventUtil(node.init) || + !ASTUtils.isIdentifier(node.id) + ) { + return; + } + let fireEventMethodName = ''; + if ( + isMemberExpression(node.init.callee) && + ASTUtils.isIdentifier(node.init.callee.property) + ) { + fireEventMethodName = node.init.callee.property.name; + } else if (node.init.arguments.length > 0) { + fireEventMethodName = (node.init.arguments[0] as TSESTree.Literal) + .value as string; + } + if (!isfireEventMethodAllowed(fireEventMethodName)) { + createEventVariables[node.id.name] = fireEventMethodName; + } + }, + }; + }, }); diff --git a/lib/rules/prefer-wait-for.ts b/lib/rules/prefer-wait-for.ts index d181b105..30cb93b7 100644 --- a/lib/rules/prefer-wait-for.ts +++ b/lib/rules/prefer-wait-for.ts @@ -2,213 +2,213 @@ import { TSESTree, ASTUtils } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - isImportSpecifier, - isMemberExpression, - findClosestCallExpressionNode, - isCallExpression, - isImportNamespaceSpecifier, - isObjectPattern, - isProperty, + isImportSpecifier, + isMemberExpression, + findClosestCallExpressionNode, + isCallExpression, + isImportNamespaceSpecifier, + isObjectPattern, + isProperty, } from '../node-utils'; export const RULE_NAME = 'prefer-wait-for'; export type MessageIds = - | 'preferWaitForImport' - | 'preferWaitForMethod' - | 'preferWaitForRequire'; + | '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); - } - }, - }; - }, + 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/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts index d2aeea08..9bce2bd8 100644 --- a/lib/rules/render-result-naming-convention.ts +++ b/lib/rules/render-result-naming-convention.ts @@ -2,10 +2,10 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { - getDeepestIdentifierNode, - getFunctionName, - getInnermostReturningFunction, - isObjectPattern, + getDeepestIdentifierNode, + getFunctionName, + getInnermostReturningFunction, + isObjectPattern, } from '../node-utils'; export const RULE_NAME = 'render-result-naming-convention'; @@ -15,97 +15,97 @@ type Options = []; const ALLOWED_VAR_NAMES = ['view', 'utils']; const ALLOWED_VAR_NAMES_TEXT = ALLOWED_VAR_NAMES.map((name) => `\`${name}\``) - .join(', ') - .replace(/, ([^,]*)$/, ', or $1'); + .join(', ') + .replace(/, ([^,]*)$/, ', or $1'); export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'suggestion', - docs: { - description: 'Enforce a valid naming for return value from `render`', - recommendedConfig: { - dom: false, - angular: 'error', - react: 'error', - vue: 'error', - marko: 'error', - }, - }, - messages: { - renderResultNamingConvention: `\`{{ renderResultName }}\` is not a recommended name for \`render\` returned value. Instead, you should destructure it, or name it using one of: ${ALLOWED_VAR_NAMES_TEXT}`, - }, - schema: [], - }, - defaultOptions: [], - - create(context, _, helpers) { - const renderWrapperNames: string[] = []; - - function detectRenderWrapper(node: TSESTree.Identifier): void { - const innerFunction = getInnermostReturningFunction(context, node); - - if (innerFunction) { - renderWrapperNames.push(getFunctionName(innerFunction)); - } - } - - return { - CallExpression(node) { - const callExpressionIdentifier = getDeepestIdentifierNode(node); - - if (!callExpressionIdentifier) { - return; - } - - if (helpers.isRenderUtil(callExpressionIdentifier)) { - detectRenderWrapper(callExpressionIdentifier); - } - }, - VariableDeclarator(node) { - if (!node.init) { - return; - } - const initIdentifierNode = getDeepestIdentifierNode(node.init); - - if (!initIdentifierNode) { - return; - } - - if ( - !helpers.isRenderVariableDeclarator(node) && - !renderWrapperNames.includes(initIdentifierNode.name) - ) { - return; - } - - // check if destructuring return value from render - if (isObjectPattern(node.id)) { - return; - } - - const renderResultName = ASTUtils.isIdentifier(node.id) && node.id.name; - - if (!renderResultName) { - return; - } - - const isAllowedRenderResultName = - ALLOWED_VAR_NAMES.includes(renderResultName); - - // check if return value var name is allowed - if (isAllowedRenderResultName) { - return; - } - - context.report({ - node, - messageId: 'renderResultNamingConvention', - data: { - renderResultName, - }, - }); - }, - }; - }, + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: 'Enforce a valid naming for return value from `render`', + recommendedConfig: { + dom: false, + angular: 'error', + react: 'error', + vue: 'error', + marko: 'error', + }, + }, + messages: { + renderResultNamingConvention: `\`{{ renderResultName }}\` is not a recommended name for \`render\` returned value. Instead, you should destructure it, or name it using one of: ${ALLOWED_VAR_NAMES_TEXT}`, + }, + schema: [], + }, + defaultOptions: [], + + create(context, _, helpers) { + const renderWrapperNames: string[] = []; + + function detectRenderWrapper(node: TSESTree.Identifier): void { + const innerFunction = getInnermostReturningFunction(context, node); + + if (innerFunction) { + renderWrapperNames.push(getFunctionName(innerFunction)); + } + } + + return { + CallExpression(node) { + const callExpressionIdentifier = getDeepestIdentifierNode(node); + + if (!callExpressionIdentifier) { + return; + } + + if (helpers.isRenderUtil(callExpressionIdentifier)) { + detectRenderWrapper(callExpressionIdentifier); + } + }, + VariableDeclarator(node) { + if (!node.init) { + return; + } + const initIdentifierNode = getDeepestIdentifierNode(node.init); + + if (!initIdentifierNode) { + return; + } + + if ( + !helpers.isRenderVariableDeclarator(node) && + !renderWrapperNames.includes(initIdentifierNode.name) + ) { + return; + } + + // check if destructuring return value from render + if (isObjectPattern(node.id)) { + return; + } + + const renderResultName = ASTUtils.isIdentifier(node.id) && node.id.name; + + if (!renderResultName) { + return; + } + + const isAllowedRenderResultName = + ALLOWED_VAR_NAMES.includes(renderResultName); + + // check if return value var name is allowed + if (isAllowedRenderResultName) { + return; + } + + context.report({ + node, + messageId: 'renderResultNamingConvention', + data: { + renderResultName, + }, + }); + }, + }; + }, }); diff --git a/lib/utils/file-import.ts b/lib/utils/file-import.ts index e332ae29..8b8e5b24 100644 --- a/lib/utils/file-import.ts +++ b/lib/utils/file-import.ts @@ -1,9 +1,9 @@ // Copied from https://github.com/babel/babel/blob/b35c78f08dd854b08575fc66ebca323fdbc59dab/packages/babel-helpers/src/helpers.js#L615-L619 // eslint-disable-next-line @typescript-eslint/no-explicit-any const interopRequireDefault = (obj: any): { default: T } => - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - obj?.__esModule ? obj : { default: obj }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + obj?.__esModule ? obj : { default: obj }; export const importDefault = (moduleName: string): T => - // eslint-disable-next-line @typescript-eslint/no-var-requires - interopRequireDefault(require(moduleName)).default; + // eslint-disable-next-line @typescript-eslint/no-var-requires + interopRequireDefault(require(moduleName)).default; diff --git a/lib/utils/index.ts b/lib/utils/index.ts index 400fae42..d08c3b52 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -2,78 +2,78 @@ export * from './file-import'; export * from './types'; const combineQueries = (variants: string[], methods: string[]): string[] => { - const combinedQueries: string[] = []; - variants.forEach((variant) => { - const variantPrefix = variant.replace('By', ''); - methods.forEach((method) => { - combinedQueries.push(`${variantPrefix}${method}`); - }); - }); - - return combinedQueries; + const combinedQueries: string[] = []; + variants.forEach((variant) => { + const variantPrefix = variant.replace('By', ''); + methods.forEach((method) => { + combinedQueries.push(`${variantPrefix}${method}`); + }); + }); + + return combinedQueries; }; const getDocsUrl = (ruleName: string): string => - `https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/${ruleName}.md`; + `https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/${ruleName}.md`; const LIBRARY_MODULES = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/preact', - '@testing-library/vue', - '@testing-library/svelte', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/preact', + '@testing-library/vue', + '@testing-library/svelte', + '@marko/testing-library', ]; const SYNC_QUERIES_VARIANTS = ['getBy', 'getAllBy', 'queryBy', 'queryAllBy']; const ASYNC_QUERIES_VARIANTS = ['findBy', 'findAllBy']; const ALL_QUERIES_VARIANTS = [ - ...SYNC_QUERIES_VARIANTS, - ...ASYNC_QUERIES_VARIANTS, + ...SYNC_QUERIES_VARIANTS, + ...ASYNC_QUERIES_VARIANTS, ]; const ALL_QUERIES_METHODS = [ - 'ByLabelText', - 'ByPlaceholderText', - 'ByText', - 'ByAltText', - 'ByTitle', - 'ByDisplayValue', - 'ByRole', - 'ByTestId', + 'ByLabelText', + 'ByPlaceholderText', + 'ByText', + 'ByAltText', + 'ByTitle', + 'ByDisplayValue', + 'ByRole', + 'ByTestId', ]; const SYNC_QUERIES_COMBINATIONS = combineQueries( - SYNC_QUERIES_VARIANTS, - ALL_QUERIES_METHODS + SYNC_QUERIES_VARIANTS, + ALL_QUERIES_METHODS ); const ASYNC_QUERIES_COMBINATIONS = combineQueries( - ASYNC_QUERIES_VARIANTS, - ALL_QUERIES_METHODS + ASYNC_QUERIES_VARIANTS, + ALL_QUERIES_METHODS ); const ALL_QUERIES_COMBINATIONS = [ - ...SYNC_QUERIES_COMBINATIONS, - ...ASYNC_QUERIES_COMBINATIONS, + ...SYNC_QUERIES_COMBINATIONS, + ...ASYNC_QUERIES_COMBINATIONS, ]; const ASYNC_UTILS = [ - 'waitFor', - 'waitForElementToBeRemoved', - 'wait', - 'waitForElement', - 'waitForDomChange', + 'waitFor', + 'waitForElementToBeRemoved', + 'wait', + 'waitForElement', + 'waitForDomChange', ] as const; const DEBUG_UTILS = [ - 'debug', - 'logTestingPlaygroundURL', - 'prettyDOM', - 'logRoles', - 'logDOM', - 'prettyFormat', + 'debug', + 'logTestingPlaygroundURL', + 'prettyDOM', + 'logRoles', + 'logDOM', + 'prettyFormat', ] as const; const EVENTS_SIMULATORS = ['fireEvent', 'userEvent'] as const; @@ -81,61 +81,61 @@ const EVENTS_SIMULATORS = ['fireEvent', 'userEvent'] as const; const TESTING_FRAMEWORK_SETUP_HOOKS = ['beforeEach', 'beforeAll']; const PROPERTIES_RETURNING_NODES = [ - 'activeElement', - 'children', - 'firstChild', - 'firstElementChild', - 'fullscreenElement', - 'lastChild', - 'lastElementChild', - 'nextElementSibling', - 'nextSibling', - 'parentElement', - 'parentNode', - 'pointerLockElement', - 'previousElementSibling', - 'previousSibling', - 'rootNode', - 'scripts', + 'activeElement', + 'children', + 'firstChild', + 'firstElementChild', + 'fullscreenElement', + 'lastChild', + 'lastElementChild', + 'nextElementSibling', + 'nextSibling', + 'parentElement', + 'parentNode', + 'pointerLockElement', + 'previousElementSibling', + 'previousSibling', + 'rootNode', + 'scripts', ]; const METHODS_RETURNING_NODES = [ - 'closest', - 'getElementById', - 'getElementsByClassName', - 'getElementsByName', - 'getElementsByTagName', - 'getElementsByTagNameNS', - 'querySelector', - 'querySelectorAll', + 'closest', + 'getElementById', + 'getElementsByClassName', + 'getElementsByName', + 'getElementsByTagName', + 'getElementsByTagNameNS', + 'querySelector', + 'querySelectorAll', ]; const ALL_RETURNING_NODES = [ - ...PROPERTIES_RETURNING_NODES, - ...METHODS_RETURNING_NODES, + ...PROPERTIES_RETURNING_NODES, + ...METHODS_RETURNING_NODES, ]; const PRESENCE_MATCHERS = ['toBeInTheDocument', 'toBeTruthy', 'toBeDefined']; const ABSENCE_MATCHERS = ['toBeNull', 'toBeFalsy']; export { - combineQueries, - getDocsUrl, - SYNC_QUERIES_VARIANTS, - ASYNC_QUERIES_VARIANTS, - ALL_QUERIES_VARIANTS, - ALL_QUERIES_METHODS, - SYNC_QUERIES_COMBINATIONS, - ASYNC_QUERIES_COMBINATIONS, - ALL_QUERIES_COMBINATIONS, - ASYNC_UTILS, - DEBUG_UTILS, - EVENTS_SIMULATORS, - TESTING_FRAMEWORK_SETUP_HOOKS, - LIBRARY_MODULES, - PROPERTIES_RETURNING_NODES, - METHODS_RETURNING_NODES, - ALL_RETURNING_NODES, - PRESENCE_MATCHERS, - ABSENCE_MATCHERS, + combineQueries, + getDocsUrl, + SYNC_QUERIES_VARIANTS, + ASYNC_QUERIES_VARIANTS, + ALL_QUERIES_VARIANTS, + ALL_QUERIES_METHODS, + SYNC_QUERIES_COMBINATIONS, + ASYNC_QUERIES_COMBINATIONS, + ALL_QUERIES_COMBINATIONS, + ASYNC_UTILS, + DEBUG_UTILS, + EVENTS_SIMULATORS, + TESTING_FRAMEWORK_SETUP_HOOKS, + LIBRARY_MODULES, + PROPERTIES_RETURNING_NODES, + METHODS_RETURNING_NODES, + ALL_RETURNING_NODES, + PRESENCE_MATCHERS, + ABSENCE_MATCHERS, }; diff --git a/lib/utils/types.ts b/lib/utils/types.ts index 728e2d94..eb8fb522 100644 --- a/lib/utils/types.ts +++ b/lib/utils/types.ts @@ -1,36 +1,36 @@ import type { TSESLint } from '@typescript-eslint/utils'; type RecommendedConfig = - | TSESLint.RuleMetaDataDocs['recommended'] - | [TSESLint.RuleMetaDataDocs['recommended'], ...TOptions]; + | TSESLint.RuleMetaDataDocs['recommended'] + | [TSESLint.RuleMetaDataDocs['recommended'], ...TOptions]; // These 2 types are copied from @typescript-eslint/utils' CreateRuleMeta // and modified to our needs export type TestingLibraryRuleMetaDocs = - Omit & { - /** - * The recommendation level for the rule on a framework basis. - * Used by the build tools to generate the framework config. - * Set to false to not include it the config - */ - recommendedConfig: Record< - SupportedTestingFramework, - RecommendedConfig - >; - }; + Omit & { + /** + * The recommendation level for the rule on a framework basis. + * Used by the build tools to generate the framework config. + * Set to false to not include it the config + */ + recommendedConfig: Record< + SupportedTestingFramework, + RecommendedConfig + >; + }; export type TestingLibraryRuleMeta< - TMessageIds extends string, - TOptions extends readonly unknown[] + TMessageIds extends string, + TOptions extends readonly unknown[] > = Omit, 'docs'> & { - docs: TestingLibraryRuleMetaDocs; + docs: TestingLibraryRuleMetaDocs; }; export const SUPPORTED_TESTING_FRAMEWORKS = [ - 'dom', - 'angular', - 'react', - 'vue', - 'marko', + 'dom', + 'angular', + 'react', + 'vue', + 'marko', ] as const; export type SupportedTestingFramework = - typeof SUPPORTED_TESTING_FRAMEWORKS[number]; + typeof SUPPORTED_TESTING_FRAMEWORKS[number]; diff --git a/package.json b/package.json index a2121871..f18ad953 100644 --- a/package.json +++ b/package.json @@ -1,85 +1,85 @@ { - "name": "eslint-plugin-testing-library", - "version": "0.0.0-semantically-released", - "description": "ESLint rules for Testing Library", - "keywords": [ - "eslint", - "eslintplugin", - "eslint-plugin", - "lint", - "testing-library", - "testing" - ], - "author": { - "name": "Mario Beltrán Alarcón", - "email": "belco90@gmail.com", - "url": "https://mario.dev/" - }, - "repository": { - "type": "git", - "url": "https://github.com/testing-library/eslint-plugin-testing-library" - }, - "homepage": "https://github.com/testing-library/eslint-plugin-testing-library", - "bugs": { - "url": "https://github.com/testing-library/eslint-plugin-testing-library/issues" - }, - "main": "index.js", - "scripts": { - "build": "tsc", - "postbuild": "cpy README.md ./dist && cpy package.json ./dist && cpy LICENSE ./dist", - "format": "prettier --write .", - "format:check": "prettier --check .", - "generate:configs": "ts-node tools/generate-configs", - "generate:rules-list": "ts-node tools/generate-rules-list", - "lint": "eslint . --max-warnings 0 --ext .js,.ts", - "lint:fix": "npm run lint -- --fix", - "test": "jest", - "test:ci": "jest --ci --coverage", - "test:update": "npm run test -- --u", - "test:watch": "npm run test -- --watch", - "type-check": "tsc --noEmit", - "semantic-release": "semantic-release", - "prepare": "is-ci || husky install" - }, - "dependencies": { - "@typescript-eslint/utils": "^5.13.0" - }, - "devDependencies": { - "@babel/eslint-plugin": "^7.16.5", - "@commitlint/cli": "^17.0.3", - "@commitlint/config-conventional": "^17.0.3", - "@types/jest": "^27.5.0", - "@types/node": "^16.11.19", - "@typescript-eslint/eslint-plugin": "^5.13.0", - "@typescript-eslint/parser": "^5.13.0", - "cpy-cli": "^4.1.0", - "eslint": "^8.6.0", - "eslint-config-kentcdodds": "^20.0.1", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^26.1.5", - "eslint-plugin-jest-formatting": "^3.1.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-promise": "^6.0.0", - "eslint-remote-tester": "^3.0.0", - "eslint-remote-tester-repositories": "^0.0.6", - "husky": "^8.0.1", - "is-ci": "^3.0.1", - "jest": "^28.1.0", - "lint-staged": "^13.0.3", - "prettier": "2.7.1", - "semantic-release": "^19.0.2", - "ts-jest": "^28.0.1", - "ts-node": "^10.4.0", - "typescript": "^4.5.4" - }, - "peerDependencies": { - "eslint": "^7.5.0 || ^8.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0", - "npm": ">=6" - }, - "license": "MIT" + "name": "eslint-plugin-testing-library", + "version": "0.0.0-semantically-released", + "description": "ESLint rules for Testing Library", + "keywords": [ + "eslint", + "eslintplugin", + "eslint-plugin", + "lint", + "testing-library", + "testing" + ], + "author": { + "name": "Mario Beltrán Alarcón", + "email": "belco90@gmail.com", + "url": "https://mario.dev/" + }, + "repository": { + "type": "git", + "url": "https://github.com/testing-library/eslint-plugin-testing-library" + }, + "homepage": "https://github.com/testing-library/eslint-plugin-testing-library", + "bugs": { + "url": "https://github.com/testing-library/eslint-plugin-testing-library/issues" + }, + "main": "index.js", + "scripts": { + "build": "tsc", + "postbuild": "cpy README.md ./dist && cpy package.json ./dist && cpy LICENSE ./dist", + "format": "prettier --write .", + "format:check": "prettier --check .", + "generate:configs": "ts-node tools/generate-configs", + "generate:rules-list": "ts-node tools/generate-rules-list", + "lint": "eslint . --max-warnings 0 --ext .js,.ts", + "lint:fix": "npm run lint -- --fix", + "test": "jest", + "test:ci": "jest --ci --coverage", + "test:update": "npm run test -- --u", + "test:watch": "npm run test -- --watch", + "type-check": "tsc --noEmit", + "semantic-release": "semantic-release", + "prepare": "is-ci || husky install" + }, + "dependencies": { + "@typescript-eslint/utils": "^5.13.0" + }, + "devDependencies": { + "@babel/eslint-plugin": "^7.16.5", + "@commitlint/cli": "^17.0.3", + "@commitlint/config-conventional": "^17.0.3", + "@types/jest": "^27.5.0", + "@types/node": "^16.11.19", + "@typescript-eslint/eslint-plugin": "^5.13.0", + "@typescript-eslint/parser": "^5.13.0", + "cpy-cli": "^4.1.0", + "eslint": "^8.6.0", + "eslint-config-kentcdodds": "^20.0.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^26.1.5", + "eslint-plugin-jest-formatting": "^3.1.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-promise": "^6.0.0", + "eslint-remote-tester": "^3.0.0", + "eslint-remote-tester-repositories": "^0.0.6", + "husky": "^8.0.1", + "is-ci": "^3.0.1", + "jest": "^28.1.0", + "lint-staged": "^13.0.3", + "prettier": "2.7.1", + "semantic-release": "^19.0.2", + "ts-jest": "^28.0.1", + "ts-node": "^10.4.0", + "typescript": "^4.5.4" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "license": "MIT" } diff --git a/tests/create-testing-library-rule.test.ts b/tests/create-testing-library-rule.test.ts index 2e73e8bb..43b74f70 100644 --- a/tests/create-testing-library-rule.test.ts +++ b/tests/create-testing-library-rule.test.ts @@ -4,145 +4,145 @@ import { createRuleTester } from './lib/test-utils'; const ruleTester = createRuleTester(); ruleTester.run(RULE_NAME, rule, { - valid: [ - // Test Cases for Imports - { - code: ` + valid: [ + // Test Cases for Imports + { + code: ` // case: nothing related to Testing Library at all import { shallow } from 'enzyme'; const wrapper = shallow(); `, - }, - { - code: ` + }, + { + code: ` // case: nothing related to Testing Library at all (require version) const { shallow } = require('enzyme'); const wrapper = shallow(); `, - }, - { - code: ` + }, + { + code: ` // case: render imported from other than custom module import { render } from '@somewhere/else' const utils = render(); `, - settings: { - 'testing-library/utils-module': 'test-utils', - }, - }, - { - code: ` + settings: { + 'testing-library/utils-module': 'test-utils', + }, + }, + { + code: ` // case: render imported from other than custom module (require version) const { render } = require('@somewhere/else') const utils = render(); `, - settings: { - 'testing-library/utils-module': 'test-utils', - }, - }, - { - code: ` + settings: { + 'testing-library/utils-module': 'test-utils', + }, + }, + { + code: ` // case: prevent import which should trigger an error since it's imported // from other than settings custom module import { foo } from 'report-me' `, - settings: { - 'testing-library/utils-module': 'test-utils', - }, - }, - { - code: ` + settings: { + 'testing-library/utils-module': 'test-utils', + }, + }, + { + code: ` // case: prevent import which should trigger an error since it's imported // from other than settings custom module (require version) const { foo } = require('report-me') `, - settings: { - 'testing-library/utils-module': 'test-utils', - }, - }, - { - code: ` + settings: { + 'testing-library/utils-module': 'test-utils', + }, + }, + { + code: ` // case: import custom module forced to be reported without custom module setting import { foo } from 'custom-module-forced-report' `, - }, - { - settings: { - 'testing-library/utils-module': 'off', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'off', + }, + code: ` // case: aggressive import switched off - imported from non-built-in module import 'report-me'; require('report-me'); `, - }, + }, - // Test Cases for user-event imports - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + // Test Cases for user-event imports + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import userEvent from 'somewhere-else' userEvent.click(element) `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import '@testing-library/user-event' userEvent.click() `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { click } from '@testing-library/user-event' userEvent.click() `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import * as incorrect from '@testing-library/user-event' userEvent.click() `, - }, + }, - // Test Cases for renders - { - code: ` + // Test Cases for renders + { + code: ` // case: aggressive render enabled - method not containing "render" import { somethingElse } from '@somewhere/else' const utils = somethingElse() `, - }, - { - settings: { 'testing-library/custom-renders': ['renderWithRedux'] }, - code: ` + }, + { + settings: { 'testing-library/custom-renders': ['renderWithRedux'] }, + code: ` // case: aggressive render disabled - method not matching valid render import { customRender } from '@somewhere/else' const utils = customRender() `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` // case: aggressive render enabled, but module disabled - not coming from TL import { render } from 'somewhere-else' const utils = render() `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case (render util): aggressive reporting disabled - method with same name // as TL method but not coming from TL module is valid import { render as testingLibraryRender } from 'test-utils' @@ -150,10 +150,10 @@ ruleTester.run(RULE_NAME, rule, { const utils = render() `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` // case: aggressive module disabled and render coming from non-related module import * as somethingElse from '@somewhere/else' import { render } from '@testing-library/react' @@ -161,166 +161,166 @@ ruleTester.run(RULE_NAME, rule, { // somethingElse.render is not coming from any module related to TL const utils = somethingElse.render() `, - }, - { - settings: { - 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], - }, - code: ` + }, + { + settings: { + 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], + }, + code: ` // case: aggressive render disabled - method not matching custom-renders import { renderWithProviders } from '@somewhere/else' const utils = renderWithProviders() `, - }, - { - settings: { - 'testing-library/custom-renders': 'off', - }, - code: ` + }, + { + settings: { + 'testing-library/custom-renders': 'off', + }, + code: ` // case: aggressive render switched off import { renderWithProviders } from '@somewhere/else' const utils = renderWithProviders() `, - }, + }, - // Test Cases for presence/absence assertions - // cases: asserts not related to presence/absence - 'expect(element).toBeDisabled()', - 'expect(element).toBeEnabled()', + // Test Cases for presence/absence assertions + // cases: asserts not related to presence/absence + 'expect(element).toBeDisabled()', + 'expect(element).toBeEnabled()', - // cases: presence/absence matcher not related to assert - 'element.toBeInTheDocument()', - 'element.not.toBeInTheDocument()', + // cases: presence/absence matcher not related to assert + 'element.toBeInTheDocument()', + 'element.not.toBeInTheDocument()', - // cases: weird scenarios to check guard against parent nodes - 'expect(element).not()', - 'expect(element).not()', + // cases: weird scenarios to check guard against parent nodes + 'expect(element).not()', + 'expect(element).not()', - // Test Cases for Queries and Aggressive Queries Reporting - { - code: ` + // Test Cases for Queries and Aggressive Queries Reporting + { + code: ` // case: custom method not matching "getBy*" variant pattern getSomeElement('button') `, - }, - { - code: ` + }, + { + code: ` // case: custom method not matching "getBy*" variant pattern using within within(container).getSomeElement('button') `, - }, - { - code: ` + }, + { + code: ` // case: custom method not matching "queryBy*" variant pattern querySomeElement('button') `, - }, - { - code: ` + }, + { + code: ` // case: custom method not matching "queryBy*" variant pattern using within within(container).querySomeElement('button') `, - }, - { - code: ` + }, + { + code: ` // case: custom method not matching "findBy*" variant pattern findSomeElement('button') `, - }, - { - code: ` + }, + { + code: ` // case: custom method not matching "findBy*" variant pattern using within within(container).findSomeElement('button') `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: built-in "getBy*" query not reported because custom module not imported import { render } from 'other-module' getByRole('button') `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: built-in "getBy*" query not reported because custom module not imported using within import { render } from 'other-module' within(container).getByRole('button') `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: built-in "queryBy*" query not reported because custom module not imported import { render } from 'other-module' queryByRole('button') `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: built-in "queryBy*" query not reported because custom module not imported using within import { render } from 'other-module' within(container).queryByRole('button') `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: built-in "findBy*" query not reported because custom module not imported import { render } from 'other-module' findByRole('button') `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: built-in "findBy*" query not reported because custom module not imported using within import { render } from 'other-module' within(container).findByRole('button') `, - }, - { - settings: { - 'testing-library/custom-queries': ['ByComplexText', 'findByIcon'], - }, - code: `// case: custom "queryBy*" query not reported (custom-queries not matching) + }, + { + settings: { + 'testing-library/custom-queries': ['ByComplexText', 'findByIcon'], + }, + code: `// case: custom "queryBy*" query not reported (custom-queries not matching) queryByIcon('search')`, - }, - { - settings: { - 'testing-library/custom-queries': ['ByComplexText', 'queryByIcon'], - }, - code: `// case: custom "getBy*" query not reported (custom-queries not matching) + }, + { + settings: { + 'testing-library/custom-queries': ['ByComplexText', 'queryByIcon'], + }, + code: `// case: custom "getBy*" query not reported (custom-queries not matching) getByIcon('search')`, - }, - { - settings: { - 'testing-library/custom-queries': ['ByComplexText', 'getByIcon'], - }, - code: `// case: custom "findBy*" query not reported (custom-queries not matching) + }, + { + settings: { + 'testing-library/custom-queries': ['ByComplexText', 'getByIcon'], + }, + code: `// case: custom "findBy*" query not reported (custom-queries not matching) findByIcon('search')`, - }, - { - settings: { - 'testing-library/custom-queries': 'off', - }, - code: `// case: custom queries not reported (aggressive queries switched off) + }, + { + settings: { + 'testing-library/custom-queries': 'off', + }, + code: `// case: custom queries not reported (aggressive queries switched off) getByIcon('search'); queryByIcon('search'); findByIcon('search'); @@ -328,26 +328,26 @@ ruleTester.run(RULE_NAME, rule, { queryAllByIcon('search'); findAllByIcon('search'); `, - }, + }, - // Test Cases for async utils - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + // Test Cases for async utils + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { waitFor } from 'some-other-library'; test( 'aggressive reporting disabled - util waitFor not related to testing library is valid', () => { waitFor() } ); `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case (async util): aggressive reporting disabled - method with same name // as TL method but not coming from TL module is valid import { waitFor as testingLibraryWaitFor } from 'test-utils' @@ -357,16 +357,16 @@ ruleTester.run(RULE_NAME, rule, { waitFor() }); `, - }, + }, - // Test Cases for all settings mixed - { - settings: { - 'testing-library/utils-module': 'test-utils', - 'testing-library/custom-renders': ['customRender'], - 'testing-library/custom-queries': ['ByIcon', 'ByComplexText'], - }, - code: ` + // Test Cases for all settings mixed + { + settings: { + 'testing-library/utils-module': 'test-utils', + 'testing-library/custom-renders': ['customRender'], + 'testing-library/custom-queries': ['ByIcon', 'ByComplexText'], + }, + code: ` // case: not matching any of the custom settings import { renderWithRedux } from 'test-utils' import { render } from 'other-utils' @@ -379,14 +379,14 @@ ruleTester.run(RULE_NAME, rule, { queryBySomethingElse('foo') findBySomethingElse('foo') `, - }, - { - settings: { - 'testing-library/utils-module': 'off', - 'testing-library/custom-renders': 'off', - 'testing-library/custom-queries': 'off', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'off', + 'testing-library/custom-renders': 'off', + 'testing-library/custom-queries': 'off', + }, + code: ` // case: all settings switched off + only custom utils used import { renderWithRedux } from 'test-utils' import { render } from 'other-utils' @@ -399,53 +399,53 @@ ruleTester.run(RULE_NAME, rule, { queryBySomethingElse('foo') findBySomethingElse('foo') `, - }, + }, - // Weird edge cases - `(window as any).__THING = false;`, - `thing.method.lastCall.args[0]();`, + // Weird edge cases + `(window as any).__THING = false;`, + `thing.method.lastCall.args[0]();`, - `// edge case when setting jest-dom up in jest config file - using require + `// edge case when setting jest-dom up in jest config file - using require require('@testing-library/jest-dom') foo() `, - `// edge case when setting jest-dom up in jest config file - using import + `// edge case when setting jest-dom up in jest config file - using import import '@testing-library/jest-dom' foo() `, - ], - invalid: [ - // Test Cases for Imports - { - code: ` + ], + invalid: [ + // Test Cases for Imports + { + code: ` // case: import module forced to be reported import { foo } from 'report-me' `, - errors: [{ line: 3, column: 7, messageId: 'fakeError' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'fakeError' }], + }, + { + code: ` // case: render imported from any module by default (aggressive reporting) import { render } from '@somewhere/else' import { somethingElse } from 'another-module' const utils = render(); `, - errors: [ - { - line: 6, - column: 21, - messageId: 'renderError', - }, - ], - }, - ...['@testing-library/react', '@marko/testing-library'].map( - (testingFramework) => - ({ - code: ` + errors: [ + { + line: 6, + column: 21, + messageId: 'renderError', + }, + ], + }, + ...['@testing-library/react', '@marko/testing-library'].map( + (testingFramework) => + ({ + code: ` // case: render imported from Testing Library module import { render } from '${testingFramework}' import { somethingElse } from 'another-module' @@ -453,17 +453,17 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); `, - errors: [ - { - line: 7, - column: 21, - messageId: 'renderError', - }, - ], - } as const) - ), - { - code: ` + errors: [ + { + line: 7, + column: 21, + messageId: 'renderError', + }, + ], + } as const) + ), + { + code: ` // case: render imported from Testing Library module (require version) const { render } = require('@testing-library/react') import { somethingElse } from 'another-module' @@ -471,16 +471,16 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); `, - errors: [ - { - line: 7, - column: 21, - messageId: 'renderError', - }, - ], - }, - { - code: ` + errors: [ + { + line: 7, + column: 21, + messageId: 'renderError', + }, + ], + }, + { + code: ` // case: render imported from settings custom module import { render } from 'test-utils' import { somethingElse } from 'another-module' @@ -488,19 +488,19 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); `, - settings: { - 'testing-library/utils-module': 'test-utils', - }, - errors: [ - { - line: 7, - column: 21, - messageId: 'renderError', - }, - ], - }, - { - code: ` + settings: { + 'testing-library/utils-module': 'test-utils', + }, + errors: [ + { + line: 7, + column: 21, + messageId: 'renderError', + }, + ], + }, + { + code: ` // case: render imported from settings custom module (require version) const { render } = require('test-utils') import { somethingElse } from 'another-module' @@ -508,19 +508,19 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); `, - settings: { - 'testing-library/utils-module': 'test-utils', - }, - errors: [ - { - line: 7, - column: 21, - messageId: 'renderError', - }, - ], - }, - { - code: ` + settings: { + 'testing-library/utils-module': 'test-utils', + }, + errors: [ + { + line: 7, + column: 21, + messageId: 'renderError', + }, + ], + }, + { + code: ` // case: render imported from Testing Library module with // settings custom module import { render } from '@testing-library/react' @@ -529,19 +529,19 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); `, - settings: { - 'testing-library/utils-module': 'test-utils', - }, - errors: [ - { - line: 8, - column: 21, - messageId: 'renderError', - }, - ], - }, - { - code: ` + settings: { + 'testing-library/utils-module': 'test-utils', + }, + errors: [ + { + line: 8, + column: 21, + messageId: 'renderError', + }, + ], + }, + { + code: ` // case: render imported from Testing Library module with // settings custom module (require version) const { render } = require('@testing-library/react') @@ -550,153 +550,153 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); `, - settings: { - 'testing-library/utils-module': 'test-utils', - }, - errors: [ - { - line: 8, - column: 21, - messageId: 'renderError', - }, - ], - }, - { - settings: { - 'testing-library/utils-module': 'custom-module-forced-report', - }, - code: ` + settings: { + 'testing-library/utils-module': 'test-utils', + }, + errors: [ + { + line: 8, + column: 21, + messageId: 'renderError', + }, + ], + }, + { + settings: { + 'testing-library/utils-module': 'custom-module-forced-report', + }, + code: ` // case: import custom module forced to be reported with custom module setting import { foo } from 'custom-module-forced-report' `, - errors: [{ line: 3, column: 7, messageId: 'fakeError' }], - }, + errors: [{ line: 3, column: 7, messageId: 'fakeError' }], + }, - // Test Cases for user-event imports - { - code: ` + // Test Cases for user-event imports + { + code: ` import userEvent from 'somewhere-else' userEvent.click(element) `, - errors: [{ line: 3, column: 17, messageId: 'userEventError' }], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 3, column: 17, messageId: 'userEventError' }], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import userEvent from '@testing-library/user-event' userEvent.click(element) `, - errors: [{ line: 3, column: 17, messageId: 'userEventError' }], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 3, column: 17, messageId: 'userEventError' }], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import renamed from '@testing-library/user-event' renamed.click(element) `, - errors: [{ line: 3, column: 15, messageId: 'userEventError' }], - }, - { - code: ` + errors: [{ line: 3, column: 15, messageId: 'userEventError' }], + }, + { + code: ` const userEvent = require('somewhere-else') userEvent.click(element) `, - errors: [{ line: 3, column: 17, messageId: 'userEventError' }], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 3, column: 17, messageId: 'userEventError' }], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` const userEvent = require('@testing-library/user-event') userEvent.click(element) `, - errors: [{ line: 3, column: 17, messageId: 'userEventError' }], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 3, column: 17, messageId: 'userEventError' }], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` const renamed = require('@testing-library/user-event') renamed.click(element) `, - errors: [{ line: 3, column: 15, messageId: 'userEventError' }], - }, + errors: [{ line: 3, column: 15, messageId: 'userEventError' }], + }, - // Test Cases for renders - { - code: ` + // Test Cases for renders + { + code: ` // case: aggressive render enabled - Testing Library render import { render } from '@testing-library/react' const utils = render() `, - errors: [{ line: 5, column: 21, messageId: 'renderError' }], - }, - { - code: ` + errors: [{ line: 5, column: 21, messageId: 'renderError' }], + }, + { + code: ` // case: aggressive render enabled - Testing Library render wildcard imported import * as rtl from '@testing-library/react' const utils = rtl.render() `, - errors: [{ line: 5, column: 25, messageId: 'renderError' }], - }, - { - code: ` + errors: [{ line: 5, column: 25, messageId: 'renderError' }], + }, + { + code: ` // case: aggressive render enabled - any method containing "render" import { someRender } from '@somewhere/else' const utils = someRender() `, - errors: [{ line: 5, column: 21, messageId: 'renderError' }], - }, - { - settings: { 'testing-library/custom-renders': ['customRender'] }, - code: ` + errors: [{ line: 5, column: 21, messageId: 'renderError' }], + }, + { + settings: { 'testing-library/custom-renders': ['customRender'] }, + code: ` // case: aggressive render disabled - Testing Library render import { render } from '@testing-library/react' const utils = render() `, - errors: [{ line: 5, column: 21, messageId: 'renderError' }], - }, - { - settings: { - 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], - }, - code: ` + errors: [{ line: 5, column: 21, messageId: 'renderError' }], + }, + { + settings: { + 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], + }, + code: ` // case: aggressive render disabled - valid custom render import { customRender } from 'test-utils' const utils = customRender() `, - errors: [{ line: 5, column: 21, messageId: 'renderError' }], - }, - { - settings: { - 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], - }, - code: ` + errors: [{ line: 5, column: 21, messageId: 'renderError' }], + }, + { + settings: { + 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], + }, + code: ` // case: aggressive render disabled - default render from custom module import { render } from 'test-utils' const utils = render() `, - errors: [{ line: 5, column: 21, messageId: 'renderError' }], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 5, column: 21, messageId: 'renderError' }], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` // case: aggressive module disabled and render wildcard-imported from related module import * as rtl from '@testing-library/react' const utils = rtl.render() `, - errors: [{ line: 5, column: 25, messageId: 'renderError' }], - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [{ line: 5, column: 25, messageId: 'renderError' }], + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: matching all custom settings import { render } from 'test-utils' import { somethingElse } from 'another-module' @@ -704,295 +704,295 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); `, - errors: [{ line: 7, column: 21, messageId: 'renderError' }], - }, + errors: [{ line: 7, column: 21, messageId: 'renderError' }], + }, - // Test Cases for presence/absence assertions - { - code: ` + // Test Cases for presence/absence assertions + { + code: ` // case: presence matcher .toBeInTheDocument forced to be reported expect(element).toBeInTheDocument() `, - errors: [{ line: 3, column: 7, messageId: 'presenceAssertError' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'presenceAssertError' }], + }, + { + code: ` // case: absence matcher .not.toBeInTheDocument forced to be reported expect(element).not.toBeInTheDocument() `, - errors: [{ line: 3, column: 7, messageId: 'absenceAssertError' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'absenceAssertError' }], + }, + { + code: ` // case: presence matcher .not.toBeNull forced to be reported expect(element).not.toBeNull() `, - errors: [{ line: 3, column: 7, messageId: 'presenceAssertError' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'presenceAssertError' }], + }, + { + code: ` // case: absence matcher .toBeNull forced to be reported expect(element).toBeNull() `, - errors: [{ line: 3, column: 7, messageId: 'absenceAssertError' }], - }, + errors: [{ line: 3, column: 7, messageId: 'absenceAssertError' }], + }, - // Test Cases for async utils - { - code: ` + // Test Cases for async utils + { + code: ` import { waitFor } from 'test-utils'; test( 'aggressive reporting enabled - util waitFor reported no matter where is coming from', () => { waitFor() } ); `, - errors: [ - { - line: 5, - column: 19, - messageId: 'asyncUtilError', - data: { utilName: 'waitFor' }, - }, - ], - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + line: 5, + column: 19, + messageId: 'asyncUtilError', + data: { utilName: 'waitFor' }, + }, + ], + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { waitFor } from 'test-utils'; test( 'aggressive reporting disabled - util waitFor related to testing library', () => { waitFor() } ); `, - errors: [ - { - line: 5, - column: 19, - messageId: 'asyncUtilError', - data: { utilName: 'waitFor' }, - }, - ], - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + line: 5, + column: 19, + messageId: 'asyncUtilError', + data: { utilName: 'waitFor' }, + }, + ], + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: aggressive reporting disabled - waitFor from wildcard import related to TL import * as tl from 'test-utils' tl.waitFor(() => {}) `, - errors: [ - { - line: 4, - column: 12, - messageId: 'asyncUtilError', - data: { utilName: 'waitFor' }, - }, - ], - }, + errors: [ + { + line: 4, + column: 12, + messageId: 'asyncUtilError', + data: { utilName: 'waitFor' }, + }, + ], + }, - // Test Cases for Queries and Aggressive Queries Reporting - { - code: ` + // Test Cases for Queries and Aggressive Queries Reporting + { + code: ` // case: built-in "getBy*" query reported without import (aggressive reporting) getByRole('button') `, - errors: [{ line: 3, column: 7, messageId: 'getByError' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'getByError' }], + }, + { + code: ` // case: built-in "getBy*" query reported without import using within (aggressive reporting) within(container).getByRole('button') `, - errors: [{ line: 3, column: 25, messageId: 'getByError' }], - }, - { - code: ` + errors: [{ line: 3, column: 25, messageId: 'getByError' }], + }, + { + code: ` // case: built-in "queryBy*" query reported without import (aggressive reporting) queryByRole('button') `, - errors: [{ line: 3, column: 7, messageId: 'queryByError' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'queryByError' }], + }, + { + code: ` // case: built-in "queryBy*" query reported without import using within (aggressive reporting) within(container).queryByRole('button') `, - errors: [{ line: 3, column: 25, messageId: 'queryByError' }], - }, - { - code: ` + errors: [{ line: 3, column: 25, messageId: 'queryByError' }], + }, + { + code: ` // case: built-in "findBy*" query reported without import (aggressive reporting) findByRole('button') `, - errors: [{ line: 3, column: 7, messageId: 'findByError' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'findByError' }], + }, + { + code: ` // case: built-in "findBy*" query reported without import using within (aggressive reporting) within(container).findByRole('button') `, - errors: [{ line: 3, column: 25, messageId: 'findByError' }], - }, - { - settings: { - 'testing-library/custom-queries': ['ByIcon'], - }, - code: ` + errors: [{ line: 3, column: 25, messageId: 'findByError' }], + }, + { + settings: { + 'testing-library/custom-queries': ['ByIcon'], + }, + code: ` // case: built-in "queryBy*" query reported (aggressive reporting disabled) queryByRole('button') `, - errors: [{ line: 3, column: 7, messageId: 'queryByError' }], - }, - { - settings: { - 'testing-library/custom-queries': ['ByIcon'], - }, - code: ` + errors: [{ line: 3, column: 7, messageId: 'queryByError' }], + }, + { + settings: { + 'testing-library/custom-queries': ['ByIcon'], + }, + code: ` // case: built-in "queryBy*" query reported (aggressive reporting disabled) within(container).queryByRole('button') `, - errors: [{ line: 3, column: 25, messageId: 'queryByError' }], - }, - { - settings: { - 'testing-library/custom-queries': ['ByIcon'], - }, - code: ` + errors: [{ line: 3, column: 25, messageId: 'queryByError' }], + }, + { + settings: { + 'testing-library/custom-queries': ['ByIcon'], + }, + code: ` // case: built-in "findBy*" query reported (aggressive reporting disabled) findByRole('button') `, - errors: [{ line: 3, column: 7, messageId: 'findByError' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'findByError' }], + }, + { + code: ` // case: custom "queryBy*" query reported without import (aggressive reporting) queryByIcon('search') `, - errors: [{ line: 3, column: 7, messageId: 'customQueryError' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'customQueryError' }], + }, + { + code: ` // case: custom "queryBy*" query reported without import using within (aggressive reporting) within(container).queryByIcon('search') `, - errors: [{ line: 3, column: 25, messageId: 'customQueryError' }], - }, - { - code: ` + errors: [{ line: 3, column: 25, messageId: 'customQueryError' }], + }, + { + code: ` // case: custom "findBy*" query reported without import (aggressive reporting) findByIcon('search') `, - errors: [{ line: 3, column: 7, messageId: 'customQueryError' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'customQueryError' }], + }, + { + code: ` // case: custom "findBy*" query reported without import using within (aggressive reporting) within(container).findByIcon('search') `, - errors: [{ line: 3, column: 25, messageId: 'customQueryError' }], - }, - { - settings: { - 'testing-library/custom-queries': ['queryByIcon', 'ByComplexText'], - }, - code: ` + errors: [{ line: 3, column: 25, messageId: 'customQueryError' }], + }, + { + settings: { + 'testing-library/custom-queries': ['queryByIcon', 'ByComplexText'], + }, + code: ` // case: custom "queryBy*" query reported without import (custom-queries set) queryByIcon('search') `, - errors: [{ line: 3, column: 7, messageId: 'customQueryError' }], - }, - { - settings: { - 'testing-library/custom-queries': ['ByIcon', 'ByComplexText'], - }, - code: ` + errors: [{ line: 3, column: 7, messageId: 'customQueryError' }], + }, + { + settings: { + 'testing-library/custom-queries': ['ByIcon', 'ByComplexText'], + }, + code: ` // case: custom "queryBy*" query reported without import using within (custom-queries set) within(container).queryByIcon('search') `, - errors: [{ line: 3, column: 25, messageId: 'customQueryError' }], - }, - { - settings: { - 'testing-library/custom-queries': [ - 'queryByIcon', - 'ByComplexText', - 'findByIcon', - ], - }, - code: ` + errors: [{ line: 3, column: 25, messageId: 'customQueryError' }], + }, + { + settings: { + 'testing-library/custom-queries': [ + 'queryByIcon', + 'ByComplexText', + 'findByIcon', + ], + }, + code: ` // case: custom "findBy*" query reported without import (custom-queries set) findByIcon('search') `, - errors: [{ line: 3, column: 7, messageId: 'customQueryError' }], - }, - { - settings: { - 'testing-library/custom-queries': ['ByIcon', 'ByComplexText'], - }, - code: ` + errors: [{ line: 3, column: 7, messageId: 'customQueryError' }], + }, + { + settings: { + 'testing-library/custom-queries': ['ByIcon', 'ByComplexText'], + }, + code: ` // case: custom "findBy*" query reported without import using within (custom-queries set) within(container).findByIcon('search') `, - errors: [{ line: 3, column: 25, messageId: 'customQueryError' }], - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [{ line: 3, column: 25, messageId: 'customQueryError' }], + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: built-in "getBy*" query reported with custom module + Testing Library package import import { render } from '@testing-library/react' getByRole('button') `, - errors: [{ line: 4, column: 7, messageId: 'getByError' }], - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [{ line: 4, column: 7, messageId: 'getByError' }], + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: built-in "getBy*" query reported with custom module + custom module import import { render } from 'test-utils' getByRole('button') `, - errors: [{ line: 4, column: 7, messageId: 'getByError' }], - }, + errors: [{ line: 4, column: 7, messageId: 'getByError' }], + }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: custom "getBy*" query reported with custom module + Testing Library package import import { render } from '@testing-library/react' getByIcon('search') `, - errors: [{ line: 4, column: 7, messageId: 'customQueryError' }], - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [{ line: 4, column: 7, messageId: 'customQueryError' }], + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: custom "getBy*" query reported with custom module + custom module import import { render } from 'test-utils' getByIcon('search') `, - errors: [{ line: 4, column: 7, messageId: 'customQueryError' }], - }, + errors: [{ line: 4, column: 7, messageId: 'customQueryError' }], + }, - // Test Cases for all settings mixed - { - settings: { - 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], - 'testing-library/utils-module': 'test-utils', - 'testing-library/custom-queries': ['ByIcon', 'findByComplexText'], - }, - code: ` + // Test Cases for all settings mixed + { + settings: { + 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], + 'testing-library/utils-module': 'test-utils', + 'testing-library/custom-queries': ['ByIcon', 'findByComplexText'], + }, + code: ` // case: aggressive reporting disabled - matching all custom settings import { renderWithRedux, waitFor, screen } from 'test-utils' import { findByComplexText } from 'custom-queries' @@ -1004,26 +1004,26 @@ ruleTester.run(RULE_NAME, rule, { findByComplexText('foo') `, - errors: [ - { line: 6, column: 43, messageId: 'renderError' }, - { line: 7, column: 18, messageId: 'getByError' }, - { line: 8, column: 27, messageId: 'customQueryError' }, - { - line: 9, - column: 7, - messageId: 'asyncUtilError', - data: { utilName: 'waitFor' }, - }, - { line: 10, column: 7, messageId: 'customQueryError' }, - ], - }, - { - settings: { - 'testing-library/utils-module': 'off', - 'testing-library/custom-renders': 'off', - 'testing-library/custom-queries': 'off', - }, - code: ` + errors: [ + { line: 6, column: 43, messageId: 'renderError' }, + { line: 7, column: 18, messageId: 'getByError' }, + { line: 8, column: 27, messageId: 'customQueryError' }, + { + line: 9, + column: 7, + messageId: 'asyncUtilError', + data: { utilName: 'waitFor' }, + }, + { line: 10, column: 7, messageId: 'customQueryError' }, + ], + }, + { + settings: { + 'testing-library/utils-module': 'off', + 'testing-library/custom-renders': 'off', + 'testing-library/custom-queries': 'off', + }, + code: ` // case: built-in utils reported when all aggressive reporting completely switched off import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event' @@ -1034,33 +1034,33 @@ ruleTester.run(RULE_NAME, rule, { waitFor(); userEvent.click(el); `, - errors: [ - { - line: 6, - column: 21, - messageId: 'renderError', - }, - { - line: 7, - column: 24, - messageId: 'getByError', - }, - { - line: 8, - column: 14, - messageId: 'findByError', - }, - { - line: 9, - column: 7, - messageId: 'asyncUtilError', - }, - { - line: 10, - column: 17, - messageId: 'userEventError', - }, - ], - }, - ], + errors: [ + { + line: 6, + column: 21, + messageId: 'renderError', + }, + { + line: 7, + column: 24, + messageId: 'getByError', + }, + { + line: 8, + column: 14, + messageId: 'findByError', + }, + { + line: 9, + column: 7, + messageId: 'asyncUtilError', + }, + { + line: 10, + column: 17, + messageId: 'userEventError', + }, + ], + }, + ], }); diff --git a/tests/eslint-remote-tester.config.js b/tests/eslint-remote-tester.config.js index 37e2bc8b..730b5579 100644 --- a/tests/eslint-remote-tester.config.js +++ b/tests/eslint-remote-tester.config.js @@ -2,45 +2,45 @@ /* eslint-disable import/no-extraneous-dependencies */ const { rules } = require('eslint-plugin-testing-library'); const { - getRepositories, - getPathIgnorePattern, + getRepositories, + getPathIgnorePattern, } = require('eslint-remote-tester-repositories'); module.exports = { - repositories: getRepositories({ randomize: true }), - pathIgnorePattern: getPathIgnorePattern(), - extensions: ['js', 'jsx', 'ts', 'tsx'], - concurrentTasks: 3, - cache: false, - logLevel: 'info', - eslintrc: { - root: true, - env: { - es6: true, - }, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - plugins: ['testing-library'], - rules: { - ...Object.keys(rules).reduce( - (all, rule) => ({ - ...all, - [`testing-library/${rule}`]: 'error', - }), - {} - ), + repositories: getRepositories({ randomize: true }), + pathIgnorePattern: getPathIgnorePattern(), + extensions: ['js', 'jsx', 'ts', 'tsx'], + concurrentTasks: 3, + cache: false, + logLevel: 'info', + eslintrc: { + root: true, + env: { + es6: true, + }, + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + plugins: ['testing-library'], + rules: { + ...Object.keys(rules).reduce( + (all, rule) => ({ + ...all, + [`testing-library/${rule}`]: 'error', + }), + {} + ), - // Rules with required options without default values - 'testing-library/consistent-data-testid': [ - 'error', - { testIdPattern: '^{fileName}(__([A-Z]+[a-z]_?)+)_$' }, - ], - }, - }, + // Rules with required options without default values + 'testing-library/consistent-data-testid': [ + 'error', + { testIdPattern: '^{fileName}(__([A-Z]+[a-z]_?)+)_$' }, + ], + }, + }, }; diff --git a/tests/fake-rule.ts b/tests/fake-rule.ts index 58320162..23bf6383 100644 --- a/tests/fake-rule.ts +++ b/tests/fake-rule.ts @@ -9,125 +9,125 @@ import { createTestingLibraryRule } from '../lib/create-testing-library-rule'; export const RULE_NAME = 'fake-rule'; type Options = []; type MessageIds = - | 'absenceAssertError' - | 'asyncUtilError' - | 'customQueryError' - | 'fakeError' - | 'findByError' - | 'getByError' - | 'presenceAssertError' - | 'queryByError' - | 'renderError' - | 'userEventError'; + | 'absenceAssertError' + | 'asyncUtilError' + | 'customQueryError' + | 'fakeError' + | 'findByError' + | 'getByError' + | 'presenceAssertError' + | 'queryByError' + | 'renderError' + | 'userEventError'; export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Fake rule to test rule maker and detection helpers', - recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: false, - marko: false, - }, - }, - messages: { - fakeError: 'fake error reported', - renderError: 'some error related to render util reported', - asyncUtilError: - 'some error related to {{ utilName }} async util reported', - getByError: 'some error related to getBy reported', - queryByError: 'some error related to queryBy reported', - findByError: 'some error related to findBy reported', - customQueryError: 'some error related to a customQuery reported', - userEventError: 'some error related to userEvent reported', - presenceAssertError: 'some error related to presence assert reported', - absenceAssertError: 'some error related to absence assert reported', - }, - schema: [], - }, - defaultOptions: [], - create(context, _, helpers) { - const reportCallExpressionIdentifier = (node: TSESTree.Identifier) => { - // force "render" to be reported - if (helpers.isRenderUtil(node)) { - return context.report({ node, messageId: 'renderError' }); - } - - // force async utils to be reported - if (helpers.isAsyncUtil(node)) { - return context.report({ - node, - messageId: 'asyncUtilError', - data: { utilName: node.name }, - }); - } - - if (helpers.isUserEventMethod(node)) { - return context.report({ node, messageId: 'userEventError' }); - } - - // force queries to be reported - if (helpers.isCustomQuery(node)) { - return context.report({ node, messageId: 'customQueryError' }); - } - - if (helpers.isGetQueryVariant(node)) { - return context.report({ node, messageId: 'getByError' }); - } - - if (helpers.isQueryQueryVariant(node)) { - return context.report({ node, messageId: 'queryByError' }); - } - - if (helpers.isFindQueryVariant(node)) { - return context.report({ node, messageId: 'findByError' }); - } - - return undefined; - }; - - const reportMemberExpression = (node: TSESTree.MemberExpression) => { - if (helpers.isPresenceAssert(node)) { - return context.report({ node, messageId: 'presenceAssertError' }); - } - - if (helpers.isAbsenceAssert(node)) { - return context.report({ node, messageId: 'absenceAssertError' }); - } - - return undefined; - }; - - const reportImportDeclaration = (node: TSESTree.ImportDeclaration) => { - // This is just to check that defining an `ImportDeclaration` doesn't - // override `ImportDeclaration` from `detectTestingLibraryUtils` - if (node.source.value === 'report-me') { - context.report({ node, messageId: 'fakeError' }); - } - }; - - return { - 'CallExpression Identifier': reportCallExpressionIdentifier, - MemberExpression: reportMemberExpression, - ImportDeclaration: reportImportDeclaration, - 'Program:exit'() { - const importNode = helpers.getCustomModuleImportNode(); - const importName = helpers.getCustomModuleImportName(); - if (!importNode) { - return; - } - - if (importName === 'custom-module-forced-report') { - context.report({ - node: importNode, - messageId: 'fakeError', - }); - } - }, - }; - }, + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Fake rule to test rule maker and detection helpers', + recommendedConfig: { + dom: false, + angular: false, + react: false, + vue: false, + marko: false, + }, + }, + messages: { + fakeError: 'fake error reported', + renderError: 'some error related to render util reported', + asyncUtilError: + 'some error related to {{ utilName }} async util reported', + getByError: 'some error related to getBy reported', + queryByError: 'some error related to queryBy reported', + findByError: 'some error related to findBy reported', + customQueryError: 'some error related to a customQuery reported', + userEventError: 'some error related to userEvent reported', + presenceAssertError: 'some error related to presence assert reported', + absenceAssertError: 'some error related to absence assert reported', + }, + schema: [], + }, + defaultOptions: [], + create(context, _, helpers) { + const reportCallExpressionIdentifier = (node: TSESTree.Identifier) => { + // force "render" to be reported + if (helpers.isRenderUtil(node)) { + return context.report({ node, messageId: 'renderError' }); + } + + // force async utils to be reported + if (helpers.isAsyncUtil(node)) { + return context.report({ + node, + messageId: 'asyncUtilError', + data: { utilName: node.name }, + }); + } + + if (helpers.isUserEventMethod(node)) { + return context.report({ node, messageId: 'userEventError' }); + } + + // force queries to be reported + if (helpers.isCustomQuery(node)) { + return context.report({ node, messageId: 'customQueryError' }); + } + + if (helpers.isGetQueryVariant(node)) { + return context.report({ node, messageId: 'getByError' }); + } + + if (helpers.isQueryQueryVariant(node)) { + return context.report({ node, messageId: 'queryByError' }); + } + + if (helpers.isFindQueryVariant(node)) { + return context.report({ node, messageId: 'findByError' }); + } + + return undefined; + }; + + const reportMemberExpression = (node: TSESTree.MemberExpression) => { + if (helpers.isPresenceAssert(node)) { + return context.report({ node, messageId: 'presenceAssertError' }); + } + + if (helpers.isAbsenceAssert(node)) { + return context.report({ node, messageId: 'absenceAssertError' }); + } + + return undefined; + }; + + const reportImportDeclaration = (node: TSESTree.ImportDeclaration) => { + // This is just to check that defining an `ImportDeclaration` doesn't + // override `ImportDeclaration` from `detectTestingLibraryUtils` + if (node.source.value === 'report-me') { + context.report({ node, messageId: 'fakeError' }); + } + }; + + return { + 'CallExpression Identifier': reportCallExpressionIdentifier, + MemberExpression: reportMemberExpression, + ImportDeclaration: reportImportDeclaration, + 'Program:exit'() { + const importNode = helpers.getCustomModuleImportNode(); + const importName = helpers.getCustomModuleImportName(); + if (!importNode) { + return; + } + + if (importName === 'custom-module-forced-report') { + context.report({ + node: importNode, + messageId: 'fakeError', + }); + } + }, + }; + }, }); diff --git a/tests/index.test.ts b/tests/index.test.ts index 46dde63b..6e490f2b 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -13,72 +13,72 @@ const ruleNames = Object.keys(plugin.rules); // eslint-disable-next-line jest/expect-expect it('should have a corresponding doc for each rule', () => { - ruleNames.forEach((rule) => { - const docPath = resolve(__dirname, '../docs/rules', `${rule}.md`); - - if (!existsSync(docPath)) { - throw new Error( - `Could not find documentation file for rule "${rule}" in path "${docPath}"` - ); - } - }); + ruleNames.forEach((rule) => { + const docPath = resolve(__dirname, '../docs/rules', `${rule}.md`); + + if (!existsSync(docPath)) { + throw new Error( + `Could not find documentation file for rule "${rule}" in path "${docPath}"` + ); + } + }); }); // eslint-disable-next-line jest/expect-expect it('should have a corresponding test for each rule', () => { - ruleNames.forEach((rule) => { - const testPath = resolve(__dirname, './lib/rules/', `${rule}.test.ts`); - - if (!existsSync(testPath)) { - throw new Error( - `Could not find test file for rule "${rule}" in path "${testPath}"` - ); - } - }); + ruleNames.forEach((rule) => { + const testPath = resolve(__dirname, './lib/rules/', `${rule}.test.ts`); + + if (!existsSync(testPath)) { + throw new Error( + `Could not find test file for rule "${rule}" in path "${testPath}"` + ); + } + }); }); // eslint-disable-next-line jest/expect-expect it('should have the correct amount of rules', () => { - const { length } = ruleNames; + const { length } = ruleNames; - if (length !== numberOfRules) { - throw new Error( - `There should be exactly ${numberOfRules} rules, but there are ${length}. If you've added a new rule, please update this number.` - ); - } + if (length !== numberOfRules) { + throw new Error( + `There should be exactly ${numberOfRules} rules, but there are ${length}. If you've added a new rule, please update this number.` + ); + } }); it("should have run 'generate:configs' script when changing config rules", async () => { - await generateConfigs(); + await generateConfigs(); - const allConfigs = plugin.configs; - expect(allConfigs).toMatchSnapshot(); + const allConfigs = plugin.configs; + expect(allConfigs).toMatchSnapshot(); }, 20000); it('should export configs that refer to actual rules', () => { - const allConfigs = plugin.configs; - - expect(Object.keys(allConfigs)).toEqual([ - 'dom', - 'angular', - 'react', - 'vue', - 'marko', - ]); - const allConfigRules = Object.values(allConfigs) - .map((config) => Object.keys(config.rules)) - .reduce((previousValue, currentValue) => [ - ...previousValue, - ...currentValue, - ]); - - allConfigRules.forEach((rule) => { - const ruleNamePrefix = 'testing-library/'; - const ruleName = rule.slice(ruleNamePrefix.length); - - expect(rule.startsWith(ruleNamePrefix)).toBe(true); - expect(ruleNames).toContain(ruleName); - - expect(() => require(`../lib/rules/${ruleName}`)).not.toThrow(); - }); + const allConfigs = plugin.configs; + + expect(Object.keys(allConfigs)).toEqual([ + 'dom', + 'angular', + 'react', + 'vue', + 'marko', + ]); + const allConfigRules = Object.values(allConfigs) + .map((config) => Object.keys(config.rules)) + .reduce((previousValue, currentValue) => [ + ...previousValue, + ...currentValue, + ]); + + allConfigRules.forEach((rule) => { + const ruleNamePrefix = 'testing-library/'; + const ruleName = rule.slice(ruleNamePrefix.length); + + expect(rule.startsWith(ruleNamePrefix)).toBe(true); + expect(ruleNames).toContain(ruleName); + + expect(() => require(`../lib/rules/${ruleName}`)).not.toThrow(); + }); }); diff --git a/tests/lib/rules/await-async-query.test.ts b/tests/lib/rules/await-async-query.test.ts index 56b4818d..8054035a 100644 --- a/tests/lib/rules/await-async-query.test.ts +++ b/tests/lib/rules/await-async-query.test.ts @@ -2,35 +2,35 @@ import { TSESLint } from '@typescript-eslint/utils'; import rule, { RULE_NAME } from '../../../lib/rules/await-async-query'; import { - ASYNC_QUERIES_COMBINATIONS, - ASYNC_QUERIES_VARIANTS, - combineQueries, - SYNC_QUERIES_COMBINATIONS, + ASYNC_QUERIES_COMBINATIONS, + ASYNC_QUERIES_VARIANTS, + combineQueries, + SYNC_QUERIES_COMBINATIONS, } from '../../../lib/utils'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; interface TestCode { - code: string; - isAsync?: boolean; - testingFramework: string; + code: string; + isAsync?: boolean; + testingFramework: string; } function createTestCode({ - code, - isAsync = true, - testingFramework = '@testing-library/react', + code, + isAsync = true, + testingFramework = '@testing-library/react', }: TestCode) { - return ` + return ` import { render } from '${testingFramework}' test("An example test",${isAsync ? ' async ' : ' '}() => { ${code} @@ -39,110 +39,110 @@ function createTestCode({ } interface TestCaseParams { - isAsync?: boolean; - combinations?: string[]; - errors?: TSESLint.TestCaseError<'asyncQueryWrapper' | 'awaitAsyncQuery'>[]; - testingFramework?: string; + isAsync?: boolean; + combinations?: string[]; + errors?: TSESLint.TestCaseError<'asyncQueryWrapper' | 'awaitAsyncQuery'>[]; + testingFramework?: string; } function createTestCase( - getTest: ( - query: string - ) => - | string - | { code: string; errors?: TSESLint.TestCaseError<'awaitAsyncQuery'>[] }, - { - combinations = ALL_ASYNC_COMBINATIONS_TO_TEST, - isAsync, - testingFramework = '', - }: TestCaseParams = {} + getTest: ( + query: string + ) => + | string + | { code: string; errors?: TSESLint.TestCaseError<'awaitAsyncQuery'>[] }, + { + combinations = ALL_ASYNC_COMBINATIONS_TO_TEST, + isAsync, + testingFramework = '', + }: TestCaseParams = {} ) { - return combinations.map((query) => { - const test = getTest(query); - - return typeof test === 'string' - ? { - code: createTestCode({ code: test, isAsync, testingFramework }), - errors: [], - } - : { - code: createTestCode({ code: test.code, isAsync, testingFramework }), - errors: test.errors, - }; - }); + return combinations.map((query) => { + const test = getTest(query); + + return typeof test === 'string' + ? { + code: createTestCode({ code: test, isAsync, testingFramework }), + errors: [], + } + : { + code: createTestCode({ code: test.code, isAsync, testingFramework }), + errors: test.errors, + }; + }); } const CUSTOM_ASYNC_QUERIES_COMBINATIONS = combineQueries( - ASYNC_QUERIES_VARIANTS, - ['ByIcon', 'ByButton'] + ASYNC_QUERIES_VARIANTS, + ['ByIcon', 'ByButton'] ); // built-in queries + custom queries const ALL_ASYNC_COMBINATIONS_TO_TEST = [ - ...ASYNC_QUERIES_COMBINATIONS, - ...CUSTOM_ASYNC_QUERIES_COMBINATIONS, + ...ASYNC_QUERIES_COMBINATIONS, + ...CUSTOM_ASYNC_QUERIES_COMBINATIONS, ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - // async queries declaration from render functions are valid - ...createTestCase((query) => `const { ${query} } = render()`, { - isAsync: false, - }), - - // async screen queries declaration are valid - ...createTestCase((query) => `await screen.${query}('foo')`), - - // async @marko/testing-library screen queries declaration are valid - ...createTestCase((query) => `await screen.${query}('foo')`, { - testingFramework: '@marko/testing-library', - }), - - // async queries are valid with await operator - ...createTestCase( - (query) => ` + valid: [ + // async queries declaration from render functions are valid + ...createTestCase((query) => `const { ${query} } = render()`, { + isAsync: false, + }), + + // async screen queries declaration are valid + ...createTestCase((query) => `await screen.${query}('foo')`), + + // async @marko/testing-library screen queries declaration are valid + ...createTestCase((query) => `await screen.${query}('foo')`, { + testingFramework: '@marko/testing-library', + }), + + // async queries are valid with await operator + ...createTestCase( + (query) => ` doSomething() await ${query}('foo') ` - ), + ), - // async queries are valid when saved in a variable with await operator - ...createTestCase( - (query) => ` + // async queries are valid when saved in a variable with await operator + ...createTestCase( + (query) => ` doSomething() const foo = await ${query}('foo') expect(foo).toBeInTheDocument(); ` - ), + ), - // async queries are valid when saved in a promise variable immediately resolved - ...createTestCase( - (query) => ` + // async queries are valid when saved in a promise variable immediately resolved + ...createTestCase( + (query) => ` const promise = ${query}('foo') await promise ` - ), + ), - // async queries are valid when used with then method - ...createTestCase( - (query) => ` + // async queries are valid when used with then method + ...createTestCase( + (query) => ` ${query}('foo').then(() => { done() }) ` - ), + ), - // async queries are valid with promise in variable resolved by then method - ...createTestCase( - (query) => ` + // async queries are valid with promise in variable resolved by then method + ...createTestCase( + (query) => ` const promise = ${query}('foo') promise.then((done) => done()) ` - ), + ), - // async queries are valid when wrapped within Promise.all + await expression - ...createTestCase( - (query) => ` + // async queries are valid when wrapped within Promise.all + await expression + ...createTestCase( + (query) => ` doSomething() await Promise.all([ @@ -150,11 +150,11 @@ ruleTester.run(RULE_NAME, rule, { ${query}('bar'), ]); ` - ), + ), - // async queries are valid when wrapped within Promise.all + then chained - ...createTestCase( - (query) => ` + // async queries are valid when wrapped within Promise.all + then chained + ...createTestCase( + (query) => ` doSomething() Promise.all([ @@ -162,11 +162,11 @@ ruleTester.run(RULE_NAME, rule, { ${query}('bar'), ]).then() ` - ), + ), - // async queries are valid when wrapped within Promise.allSettled + await expression - ...createTestCase( - (query) => ` + // async queries are valid when wrapped within Promise.allSettled + await expression + ...createTestCase( + (query) => ` doSomething() await Promise.allSettled([ @@ -174,11 +174,11 @@ ruleTester.run(RULE_NAME, rule, { ${query}('bar'), ]); ` - ), + ), - // async queries are valid when wrapped within Promise.allSettled + then chained - ...createTestCase( - (query) => ` + // async queries are valid when wrapped within Promise.allSettled + then chained + ...createTestCase( + (query) => ` doSomething() Promise.allSettled([ @@ -186,72 +186,72 @@ ruleTester.run(RULE_NAME, rule, { ${query}('bar'), ]).then() ` - ), + ), - // async queries are valid with promise returned in arrow function - ...createTestCase( - (query) => `const anArrowFunction = () => ${query}('foo')` - ), + // async queries are valid with promise returned in arrow function + ...createTestCase( + (query) => `const anArrowFunction = () => ${query}('foo')` + ), - // async queries are valid with promise returned in regular function - ...createTestCase((query) => `function foo() { return ${query}('foo') }`), + // async queries are valid with promise returned in regular function + ...createTestCase((query) => `function foo() { return ${query}('foo') }`), - // async queries are valid with promise in variable and returned in regular function - ...createTestCase( - (query) => ` + // async queries are valid with promise in variable and returned in regular function + ...createTestCase( + (query) => ` async function queryWrapper() { const promise = ${query}('foo') return promise } ` - ), + ), - // sync queries are valid - ...createTestCase( - (query) => ` + // sync queries are valid + ...createTestCase( + (query) => ` doSomething() ${query}('foo') `, - { combinations: SYNC_QUERIES_COMBINATIONS } - ), + { combinations: SYNC_QUERIES_COMBINATIONS } + ), - // async queries with resolves matchers are valid - ...createTestCase( - (query) => ` + // async queries with resolves matchers are valid + ...createTestCase( + (query) => ` expect(${query}("foo")).resolves.toBe("bar") expect(wrappedQuery(${query}("foo"))).resolves.toBe("bar") ` - ), + ), - // async queries with rejects matchers are valid - ...createTestCase( - (query) => ` + // async queries with rejects matchers are valid + ...createTestCase( + (query) => ` expect(${query}("foo")).rejects.toBe("bar") expect(wrappedQuery(${query}("foo"))).rejects.toBe("bar") ` - ), + ), - // unresolved async queries with aggressive reporting opted-out are valid - ...ALL_ASYNC_COMBINATIONS_TO_TEST.map((query) => ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + // unresolved async queries with aggressive reporting opted-out are valid + ...ALL_ASYNC_COMBINATIONS_TO_TEST.map((query) => ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from "another-library" test('An example test', async () => { const example = ${query}("my example") }) `, - })), + })), - // non-matching query is valid - ` + // non-matching query is valid + ` test('A valid example test', async () => { const example = findText("my example") }) `, - // unhandled promise from non-matching query is valid - ` + // unhandled promise from non-matching query is valid + ` async function findButton() { const element = findByText('outer element') return somethingElse(element) @@ -263,33 +263,33 @@ ruleTester.run(RULE_NAME, rule, { }) `, - // unhandled promise from custom query not matching custom-queries setting is valid - { - settings: { - 'testing-library/custom-queries': ['queryByIcon', 'ByComplexText'], - }, - code: ` + // unhandled promise from custom query not matching custom-queries setting is valid + { + settings: { + 'testing-library/custom-queries': ['queryByIcon', 'ByComplexText'], + }, + code: ` test('A valid example test', () => { const element = findByIcon('search') }) `, - }, - - // unhandled promise from custom query with aggressive query switched off is valid - { - settings: { - 'testing-library/custom-queries': 'off', - }, - code: ` + }, + + // unhandled promise from custom query with aggressive query switched off is valid + { + settings: { + 'testing-library/custom-queries': 'off', + }, + code: ` test('A valid example test', () => { const element = findByIcon('search') }) `, - }, + }, - // edge case for coverage - // return non-matching query and other than Identifier or CallExpression - ` + // edge case for coverage + // return non-matching query and other than Identifier or CallExpression + ` async function someSetup() { const element = await findByText('outer element') return element ? findSomethingElse(element) : null @@ -300,8 +300,8 @@ ruleTester.run(RULE_NAME, rule, { }) `, - // https://github.com/testing-library/eslint-plugin-testing-library/issues/359 - `// issue #359 + // https://github.com/testing-library/eslint-plugin-testing-library/issues/359 + `// issue #359 import { render, screen } from 'mocks/test-utils' import userEvent from '@testing-library/user-event' @@ -326,18 +326,18 @@ ruleTester.run(RULE_NAME, rule, { }) `, - // edge case for coverage - // valid async query usage without any function defined - // so there is no innermost function scope found - `const element = await findByRole('button')`, - ], - - invalid: [ - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => - ALL_ASYNC_COMBINATIONS_TO_TEST.map( - (query) => - ({ - code: `// async queries without await operator or then method are not valid + // edge case for coverage + // valid async query usage without any function defined + // so there is no innermost function scope found + `const element = await findByRole('button')`, + ], + + invalid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => + ALL_ASYNC_COMBINATIONS_TO_TEST.map( + (query) => + ({ + code: `// async queries without await operator or then method are not valid import { render } from '${testingFramework}' test("An example test", async () => { @@ -345,34 +345,34 @@ ruleTester.run(RULE_NAME, rule, { const foo = ${query}('foo') }); `, - errors: [{ messageId: 'awaitAsyncQuery', line: 6, column: 21 }], - } as const) - ) - ), - ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( - (query) => - ({ - code: `// async screen queries without await operator or then method are not valid + errors: [{ messageId: 'awaitAsyncQuery', line: 6, column: 21 }], + } as const) + ) + ), + ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( + (query) => + ({ + code: `// async screen queries without await operator or then method are not valid import { render } from '@testing-library/react' test("An example test", async () => { screen.${query}('foo') }); `, - errors: [ - { - messageId: 'awaitAsyncQuery', - line: 5, - column: 16, - data: { name: query }, - }, - ], - } as const) - ), - ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( - (query) => - ({ - code: ` + errors: [ + { + messageId: 'awaitAsyncQuery', + line: 5, + column: 16, + data: { name: query }, + }, + ], + } as const) + ), + ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( + (query) => + ({ + code: ` import { render } from '@testing-library/react' test("An example test", async () => { @@ -380,20 +380,20 @@ ruleTester.run(RULE_NAME, rule, { const foo = ${query}('foo') }); `, - errors: [ - { - messageId: 'awaitAsyncQuery', - line: 6, - column: 21, - data: { name: query }, - }, - ], - } as const) - ), - ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( - (query) => - ({ - code: ` + errors: [ + { + messageId: 'awaitAsyncQuery', + line: 6, + column: 21, + data: { name: query }, + }, + ], + } as const) + ), + ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( + (query) => + ({ + code: ` import { render } from '@testing-library/react' test("An example test", async () => { @@ -402,37 +402,37 @@ ruleTester.run(RULE_NAME, rule, { expect(foo).toHaveAttribute('src', 'bar'); }); `, - errors: [ - { - messageId: 'awaitAsyncQuery', - line: 5, - column: 21, - data: { name: query }, - }, - ], - } as const) - ), - - // unresolved async queries are not valid (aggressive reporting) - ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( - (query) => - ({ - code: ` + errors: [ + { + messageId: 'awaitAsyncQuery', + line: 5, + column: 21, + data: { name: query }, + }, + ], + } as const) + ), + + // unresolved async queries are not valid (aggressive reporting) + ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( + (query) => + ({ + code: ` import { render } from "another-library" test('An example test', async () => { const example = ${query}("my example") }) `, - errors: [{ messageId: 'awaitAsyncQuery', line: 5, column: 27 }], - } as const) - ), - - // unhandled promise from async query function wrapper is invalid - ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( - (query) => - ({ - code: ` + errors: [{ messageId: 'awaitAsyncQuery', line: 5, column: 27 }], + } as const) + ), + + // unhandled promise from async query function wrapper is invalid + ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( + (query) => + ({ + code: ` function queryWrapper() { doSomethingElse(); @@ -447,14 +447,14 @@ ruleTester.run(RULE_NAME, rule, { const element = await queryWrapper() }) `, - errors: [{ messageId: 'asyncQueryWrapper', line: 9, column: 27 }], - } as const) - ), - // unhandled promise from async query arrow function wrapper is invalid - ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( - (query) => - ({ - code: ` + errors: [{ messageId: 'asyncQueryWrapper', line: 9, column: 27 }], + } as const) + ), + // unhandled promise from async query arrow function wrapper is invalid + ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( + (query) => + ({ + code: ` const queryWrapper = () => { doSomethingElse(); @@ -469,14 +469,14 @@ ruleTester.run(RULE_NAME, rule, { const element = await queryWrapper() }) `, - errors: [{ messageId: 'asyncQueryWrapper', line: 9, column: 27 }], - } as const) - ), - // unhandled promise implicitly returned from async query arrow function wrapper is invalid - ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( - (query) => - ({ - code: ` + errors: [{ messageId: 'asyncQueryWrapper', line: 9, column: 27 }], + } as const) + ), + // unhandled promise implicitly returned from async query arrow function wrapper is invalid + ...ALL_ASYNC_COMBINATIONS_TO_TEST.map( + (query) => + ({ + code: ` const queryWrapper = () => screen.${query}('foo') test("An invalid example test", () => { @@ -487,25 +487,25 @@ ruleTester.run(RULE_NAME, rule, { const element = await queryWrapper() }) `, - errors: [{ messageId: 'asyncQueryWrapper', line: 5, column: 27 }], - } as const) - ), - - // unhandled promise from custom query matching custom-queries setting is invalid - { - settings: { - 'testing-library/custom-queries': ['ByIcon', 'getByComplexText'], - }, - code: ` + errors: [{ messageId: 'asyncQueryWrapper', line: 5, column: 27 }], + } as const) + ), + + // unhandled promise from custom query matching custom-queries setting is invalid + { + settings: { + 'testing-library/custom-queries': ['ByIcon', 'getByComplexText'], + }, + code: ` test('An invalid example test', () => { const element = findByIcon('search') }) `, - errors: [{ messageId: 'awaitAsyncQuery', line: 3, column: 25 }], - }, + errors: [{ messageId: 'awaitAsyncQuery', line: 3, column: 25 }], + }, - { - code: `// similar to issue #359 but forcing an error in no-awaited wrapper + { + code: `// similar to issue #359 but forcing an error in no-awaited wrapper import { render, screen } from 'mocks/test-utils' import userEvent from '@testing-library/user-event' @@ -529,7 +529,7 @@ ruleTester.run(RULE_NAME, rule, { // ... }) `, - errors: [{ messageId: 'asyncQueryWrapper', line: 19, column: 34 }], - }, - ], + errors: [{ messageId: 'asyncQueryWrapper', line: 19, column: 34 }], + }, + ], }); diff --git a/tests/lib/rules/await-async-utils.test.ts b/tests/lib/rules/await-async-utils.test.ts index bf8081d5..9552eba3 100644 --- a/tests/lib/rules/await-async-utils.test.ts +++ b/tests/lib/rules/await-async-utils.test.ts @@ -5,26 +5,26 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util directly waited with await operator is valid', async () => { doSomethingElse(); await ${asyncUtil}(() => getByLabelText('email')); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util promise saved in var and waited with await operator is valid', async () => { doSomethingElse(); @@ -32,18 +32,18 @@ ruleTester.run(RULE_NAME, rule, { await aPromise; }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util directly chained with then is valid', () => { doSomethingElse(); ${asyncUtil}(() => getByLabelText('email')).then(() => { console.log('done') }); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util promise saved in var and chained with then is valid', () => { doSomethingElse(); @@ -51,9 +51,9 @@ ruleTester.run(RULE_NAME, rule, { aPromise.then(() => { console.log('done') }); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util directly returned in arrow function is valid', async () => { const makeCustomWait = () => @@ -62,9 +62,9 @@ ruleTester.run(RULE_NAME, rule, { ); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util explicitly returned in arrow function is valid', async () => { const makeCustomWait = () => { @@ -74,9 +74,9 @@ ruleTester.run(RULE_NAME, rule, { }; }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util returned in regular function is valid', async () => { function makeCustomWait() { @@ -86,9 +86,9 @@ ruleTester.run(RULE_NAME, rule, { } }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util promise saved in var and returned in function is valid', async () => { const makeCustomWait = () => { @@ -102,12 +102,12 @@ ruleTester.run(RULE_NAME, rule, { }; }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { ${asyncUtil} } from 'some-other-library'; test( 'aggressive reporting disabled - util "${asyncUtil}" which is not related to testing library is valid', @@ -116,12 +116,12 @@ ruleTester.run(RULE_NAME, rule, { ${asyncUtil}(); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import * as asyncUtils from 'some-other-library'; test( 'aggressive reporting disabled - util "asyncUtils.${asyncUtil}" which is not related to testing library is valid', @@ -130,9 +130,9 @@ ruleTester.run(RULE_NAME, rule, { asyncUtils.${asyncUtil}(); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util used in with Promise.all() is valid', async () => { await Promise.all([ @@ -141,9 +141,9 @@ ruleTester.run(RULE_NAME, rule, { ]); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util used in with Promise.all() with an await is valid', async () => { await Promise.all([ @@ -152,9 +152,9 @@ ruleTester.run(RULE_NAME, rule, { ]); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util used in with Promise.all() with ".then" is valid', async () => { Promise.all([ @@ -163,9 +163,9 @@ ruleTester.run(RULE_NAME, rule, { ]).then(() => console.log('foo')); }); `, - })), - { - code: ` + })), + { + code: ` import { waitFor, waitForElementToBeRemoved } from '${testingFramework}'; test('combining different async methods with Promise.all does not throw an error', async () => { await Promise.all([ @@ -174,9 +174,9 @@ ruleTester.run(RULE_NAME, rule, { ]) }); `, - }, - { - code: ` + }, + { + code: ` import { waitForElementToBeRemoved } from '${testingFramework}'; test('waitForElementToBeRemoved receiving element rather than callback is valid', async () => { doSomethingElse(); @@ -184,9 +184,9 @@ ruleTester.run(RULE_NAME, rule, { await waitForElementToBeRemoved(emailInput); }); `, - }, - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + }, + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util used in Promise.allSettled + await expression is valid', async () => { await Promise.allSettled([ @@ -195,9 +195,9 @@ ruleTester.run(RULE_NAME, rule, { ]); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util used in Promise.allSettled + then method is valid', async () => { Promise.allSettled([ @@ -206,9 +206,9 @@ ruleTester.run(RULE_NAME, rule, { ]).then(() => {}) }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; function waitForSomethingAsync() { @@ -219,9 +219,9 @@ ruleTester.run(RULE_NAME, rule, { await waitForSomethingAsync() }); `, - })), - { - code: ` + })), + { + code: ` test('using unrelated promises with Promise.all is valid', async () => { Promise.all([ waitForNotRelatedToTestingLibrary(), @@ -230,104 +230,104 @@ ruleTester.run(RULE_NAME, rule, { ]) }) `, - }, - { - // edge case for coverage - // valid async query usage without any function defined - // so there is no innermost function scope found - code: ` + }, + { + // edge case for coverage + // valid async query usage without any function defined + // so there is no innermost function scope found + code: ` import { waitFor } from '${testingFramework}'; test('edge case for no innermost function scope', () => { const foo = waitFor }) `, - }, - ]), - invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + }, + ]), + invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util not waited is invalid', () => { doSomethingElse(); ${asyncUtil}(() => getByLabelText('email')); }); `, - errors: [ - { - line: 5, - column: 11, - messageId: 'awaitAsyncUtil', - data: { name: asyncUtil }, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 5, + column: 11, + messageId: 'awaitAsyncUtil', + data: { name: asyncUtil }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util not waited is invalid', () => { doSomethingElse(); const el = ${asyncUtil}(() => getByLabelText('email')); }); `, - errors: [ - { - line: 5, - column: 22, - messageId: 'awaitAsyncUtil', - data: { name: asyncUtil }, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 5, + column: 22, + messageId: 'awaitAsyncUtil', + data: { name: asyncUtil }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import * as asyncUtil from '${testingFramework}'; test('asyncUtil.${asyncUtil} util not handled is invalid', () => { doSomethingElse(); asyncUtil.${asyncUtil}(() => getByLabelText('email')); }); `, - errors: [ - { - line: 5, - column: 21, - messageId: 'awaitAsyncUtil', - data: { name: asyncUtil }, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 5, + column: 21, + messageId: 'awaitAsyncUtil', + data: { name: asyncUtil }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('${asyncUtil} util promise saved not handled is invalid', () => { doSomethingElse(); const aPromise = ${asyncUtil}(() => getByLabelText('email')); }); `, - errors: [ - { - line: 5, - column: 28, - messageId: 'awaitAsyncUtil', - data: { name: asyncUtil }, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 5, + column: 28, + messageId: 'awaitAsyncUtil', + data: { name: asyncUtil }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('several ${asyncUtil} utils not handled are invalid', () => { const aPromise = ${asyncUtil}(() => getByLabelText('username')); @@ -335,26 +335,26 @@ ruleTester.run(RULE_NAME, rule, { ${asyncUtil}(() => getByLabelText('email')); }); `, - errors: [ - { - line: 4, - column: 28, - messageId: 'awaitAsyncUtil', - data: { name: asyncUtil }, - }, - { - line: 6, - column: 11, - messageId: 'awaitAsyncUtil', - data: { name: asyncUtil }, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 4, + column: 28, + messageId: 'awaitAsyncUtil', + data: { name: asyncUtil }, + }, + { + line: 6, + column: 11, + messageId: 'awaitAsyncUtil', + data: { name: asyncUtil }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil}, render } from '${testingFramework}'; function waitForSomethingAsync() { @@ -366,20 +366,20 @@ ruleTester.run(RULE_NAME, rule, { waitForSomethingAsync() }); `, - errors: [ - { - messageId: 'asyncUtilWrapper', - line: 10, - column: 11, - data: { name: 'waitForSomethingAsync' }, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + messageId: 'asyncUtilWrapper', + line: 10, + column: 11, + data: { name: 'waitForSomethingAsync' }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil} } from 'some-other-library'; test( 'aggressive reporting - util "${asyncUtil}" which is not related to testing library is invalid', @@ -388,20 +388,20 @@ ruleTester.run(RULE_NAME, rule, { ${asyncUtil}(); }); `, - errors: [ - { - line: 7, - column: 11, - messageId: 'awaitAsyncUtil', - data: { name: asyncUtil }, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 7, + column: 11, + messageId: 'awaitAsyncUtil', + data: { name: asyncUtil }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil}, render } from '${testingFramework}'; function waitForSomethingAsync() { @@ -413,20 +413,20 @@ ruleTester.run(RULE_NAME, rule, { const el = waitForSomethingAsync() }); `, - errors: [ - { - messageId: 'asyncUtilWrapper', - line: 10, - column: 22, - data: { name: 'waitForSomethingAsync' }, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + messageId: 'asyncUtilWrapper', + line: 10, + column: 22, + data: { name: 'waitForSomethingAsync' }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import * as asyncUtils from 'some-other-library'; test( 'aggressive reporting - util "asyncUtils.${asyncUtil}" which is not related to testing library is invalid', @@ -435,15 +435,15 @@ ruleTester.run(RULE_NAME, rule, { asyncUtils.${asyncUtil}(); }); `, - errors: [ - { - line: 7, - column: 22, - messageId: 'awaitAsyncUtil', - data: { name: asyncUtil }, - }, - ], - } as const) - ), - ]), + errors: [ + { + line: 7, + column: 22, + messageId: 'awaitAsyncUtil', + data: { name: asyncUtil }, + }, + ], + } as const) + ), + ]), }); diff --git a/tests/lib/rules/await-fire-event.test.ts b/tests/lib/rules/await-fire-event.test.ts index 88ae1d97..62b860e2 100644 --- a/tests/lib/rules/await-fire-event.test.ts +++ b/tests/lib/rules/await-fire-event.test.ts @@ -4,64 +4,64 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const COMMON_FIRE_EVENT_METHODS: string[] = [ - 'click', - 'change', - 'focus', - 'blur', - 'keyDown', + 'click', + 'change', + 'focus', + 'blur', + 'keyDown', ]; const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` + 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: ` + })), + ...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: ` + })), + ...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: ` + })), + ...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: ` + })), + ...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: ` + })), + ...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(() => { @@ -69,9 +69,9 @@ ruleTester.run(RULE_NAME, rule, { }) }) `, - })), - { - code: ` + })), + { + code: ` import { fireEvent } from '${testingFramework}' test('fireEvent methods wrapped with Promise.all are valid', async () => { await Promise.all([ @@ -80,9 +80,9 @@ ruleTester.run(RULE_NAME, rule, { ]) }) `, - }, - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` + }, + ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ + code: ` import { fireEvent } from '${testingFramework}' test('return promise from fire event methods is valid', () => { function triggerEvent() { @@ -91,9 +91,9 @@ ruleTester.run(RULE_NAME, rule, { } }) `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` + })), + ...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() { @@ -104,35 +104,35 @@ ruleTester.run(RULE_NAME, rule, { await triggerEvent() }) `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...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: ` + })), + ...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: ` + { + // 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() { @@ -143,125 +143,125 @@ ruleTester.run(RULE_NAME, rule, { const reassignedFunction = triggerEvent }) `, - }, - ]), + }, + ]), - invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` + 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: ` + 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: ` + 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: ` + 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: ` + 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: ` + 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', @@ -269,23 +269,23 @@ ruleTester.run(RULE_NAME, rule, { 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: ` + 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', @@ -293,21 +293,21 @@ ruleTester.run(RULE_NAME, rule, { fireEvent.${fireEventMethod}(getByLabelText('username')) }) `, - errors: [ - { - line: 6, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), + errors: [ + { + line: 6, + column: 9, + messageId: 'awaitFireEvent', + data: { name: fireEventMethod }, + }, + ], + } as const) + ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` + ...COMMON_FIRE_EVENT_METHODS.map( + (fireEventMethod) => + ({ + code: ` import { fireEvent } from '${testingFramework}' test( 'unhandled promise from fire event method kept in a var is invalid', @@ -315,20 +315,20 @@ ruleTester.run(RULE_NAME, rule, { 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: ` + 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() { @@ -339,15 +339,15 @@ ruleTester.run(RULE_NAME, rule, { triggerEvent() }) `, - errors: [ - { - line: 9, - column: 9, - messageId: 'fireEventWrapper', - data: { name: 'triggerEvent' }, - }, - ], - } as const) - ), - ]), + errors: [ + { + line: 9, + column: 9, + messageId: 'fireEventWrapper', + data: { name: 'triggerEvent' }, + }, + ], + } as const) + ), + ]), }); diff --git a/tests/lib/rules/consistent-data-testid.test.ts b/tests/lib/rules/consistent-data-testid.test.ts index 687cd8df..50c1614e 100644 --- a/tests/lib/rules/consistent-data-testid.test.ts +++ b/tests/lib/rules/consistent-data-testid.test.ts @@ -1,9 +1,9 @@ import type { TSESLint } from '@typescript-eslint/utils'; import rule, { - MessageIds, - Options, - RULE_NAME, + MessageIds, + Options, + RULE_NAME, } from '../../../lib/rules/consistent-data-testid'; import { createRuleTester } from '../test-utils'; @@ -13,18 +13,18 @@ type ValidTestCase = TSESLint.ValidTestCase; type InvalidTestCase = TSESLint.InvalidTestCase; type TestCase = InvalidTestCase | ValidTestCase; const disableAggressiveReporting = (array: T[]): T[] => - array.map((testCase) => ({ - ...testCase, - settings: { - 'testing-library/utils-module': 'off', - 'testing-library/custom-renders': 'off', - 'testing-library/custom-queries': 'off', - }, - })); + array.map((testCase) => ({ + ...testCase, + settings: { + 'testing-library/utils-module': 'off', + 'testing-library/custom-renders': 'off', + 'testing-library/custom-queries': 'off', + }, + })); const validTestCases: ValidTestCase[] = [ - { - code: ` + { + code: ` import React from 'react'; const TestComponent = props => { @@ -35,10 +35,10 @@ const validTestCases: ValidTestCase[] = [ ) }; `, - options: [{ testIdPattern: 'cool' }], - }, - { - code: ` + options: [{ testIdPattern: 'cool' }], + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -49,10 +49,10 @@ const validTestCases: ValidTestCase[] = [ ) }; `, - options: [{ testIdPattern: 'cool' }], - }, - { - code: ` + options: [{ testIdPattern: 'cool' }], + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -63,15 +63,15 @@ const validTestCases: ValidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: '^{fileName}(__([A-Z]+[a-z]*?)+)*$', - }, - ], - filename: '/my/cool/file/path/Awesome.js', - }, - { - code: ` + options: [ + { + testIdPattern: '^{fileName}(__([A-Z]+[a-z]*?)+)*$', + }, + ], + filename: '/my/cool/file/path/Awesome.js', + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -82,15 +82,15 @@ const validTestCases: ValidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: '^{fileName}(__([A-Z]+[a-z]*?)+)*$', - }, - ], - filename: '/my/cool/file/path/Awesome.js', - }, - { - code: ` + options: [ + { + testIdPattern: '^{fileName}(__([A-Z]+[a-z]*?)+)*$', + }, + ], + filename: '/my/cool/file/path/Awesome.js', + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -101,15 +101,15 @@ const validTestCases: ValidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: '^{fileName}(__([A-Z]+[a-z]*?)+)*$', - }, - ], - filename: '/my/cool/file/Parent/index.js', - }, - { - code: ` + options: [ + { + testIdPattern: '^{fileName}(__([A-Z]+[a-z]*?)+)*$', + }, + ], + filename: '/my/cool/file/Parent/index.js', + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -120,15 +120,15 @@ const validTestCases: ValidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: '{fileName}', - }, - ], - filename: '/my/cool/__tests__/Parent/index.js', - }, - { - code: ` + options: [ + { + testIdPattern: '{fileName}', + }, + ], + filename: '/my/cool/__tests__/Parent/index.js', + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -139,15 +139,15 @@ const validTestCases: ValidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: '^right(.*)$', - testIdAttribute: 'custom-attr', - }, - ], - }, - { - code: ` + options: [ + { + testIdPattern: '^right(.*)$', + testIdAttribute: 'custom-attr', + }, + ], + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -158,15 +158,15 @@ const validTestCases: ValidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: '^right(.*)$', - testIdAttribute: ['custom-attr', 'another-custom-attr'], - }, - ], - }, - { - code: ` + options: [ + { + testIdPattern: '^right(.*)$', + testIdAttribute: ['custom-attr', 'another-custom-attr'], + }, + ], + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -177,16 +177,16 @@ const validTestCases: ValidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: '{fileName}', - testIdAttribute: 'data-test-id', - }, - ], - filename: '/my/cool/__tests__/Parent/index.js', - }, - { - code: ` + options: [ + { + testIdPattern: '{fileName}', + testIdAttribute: 'data-test-id', + }, + ], + filename: '/my/cool/__tests__/Parent/index.js', + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -198,12 +198,12 @@ const validTestCases: ValidTestCase[] = [ ) }; `, - options: [{ testIdPattern: 'somethingElse' }], - }, + options: [{ testIdPattern: 'somethingElse' }], + }, ]; const invalidTestCases: InvalidTestCase[] = [ - { - code: ` + { + code: ` import React from 'react'; const TestComponent = props => { @@ -214,20 +214,20 @@ const invalidTestCases: InvalidTestCase[] = [ ) }; `, - options: [{ testIdPattern: 'error' }], - errors: [ - { - messageId: 'consistentDataTestId', - data: { - attr: 'data-testid', - value: 'Awesome__CoolStuff', - regex: '/error/', - }, - }, - ], - }, - { - code: ` + options: [{ testIdPattern: 'error' }], + errors: [ + { + messageId: 'consistentDataTestId', + data: { + attr: 'data-testid', + value: 'Awesome__CoolStuff', + regex: '/error/', + }, + }, + ], + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -238,25 +238,25 @@ const invalidTestCases: InvalidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: 'matchMe', - }, - ], - filename: '/my/cool/__tests__/Parent/index.js', - errors: [ - { - messageId: 'consistentDataTestId', - data: { - attr: 'data-testid', - value: 'Nope', - regex: '/matchMe/', - }, - }, - ], - }, - { - code: ` + options: [ + { + testIdPattern: 'matchMe', + }, + ], + filename: '/my/cool/__tests__/Parent/index.js', + errors: [ + { + messageId: 'consistentDataTestId', + data: { + attr: 'data-testid', + value: 'Nope', + regex: '/matchMe/', + }, + }, + ], + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -267,26 +267,26 @@ const invalidTestCases: InvalidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: '^{fileName}(__([A-Z]+[a-z]*?)+)*$', - testIdAttribute: 'my-custom-attr', - }, - ], - filename: '/my/cool/__tests__/Parent/index.js', - errors: [ - { - messageId: 'consistentDataTestId', - data: { - attr: 'my-custom-attr', - value: 'WrongComponent__cool', - regex: '/^Parent(__([A-Z]+[a-z]*?)+)*$/', - }, - }, - ], - }, - { - code: ` + options: [ + { + testIdPattern: '^{fileName}(__([A-Z]+[a-z]*?)+)*$', + testIdAttribute: 'my-custom-attr', + }, + ], + filename: '/my/cool/__tests__/Parent/index.js', + errors: [ + { + messageId: 'consistentDataTestId', + data: { + attr: 'my-custom-attr', + value: 'WrongComponent__cool', + regex: '/^Parent(__([A-Z]+[a-z]*?)+)*$/', + }, + }, + ], + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -297,34 +297,34 @@ const invalidTestCases: InvalidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: '^right$', - testIdAttribute: ['custom-attr', 'another-custom-attr'], - }, - ], - filename: '/my/cool/__tests__/Parent/index.js', - errors: [ - { - messageId: 'consistentDataTestId', - data: { - attr: 'custom-attr', - value: 'wrong', - regex: '/^right$/', - }, - }, - { - messageId: 'consistentDataTestId', - data: { - attr: 'another-custom-attr', - value: 'wrong', - regex: '/^right$/', - }, - }, - ], - }, - { - code: ` + options: [ + { + testIdPattern: '^right$', + testIdAttribute: ['custom-attr', 'another-custom-attr'], + }, + ], + filename: '/my/cool/__tests__/Parent/index.js', + errors: [ + { + messageId: 'consistentDataTestId', + data: { + attr: 'custom-attr', + value: 'wrong', + regex: '/^right$/', + }, + }, + { + messageId: 'consistentDataTestId', + data: { + attr: 'another-custom-attr', + value: 'wrong', + regex: '/^right$/', + }, + }, + ], + }, + { + code: ` import React from 'react'; const TestComponent = props => { @@ -335,29 +335,29 @@ const invalidTestCases: InvalidTestCase[] = [ ) }; `, - options: [ - { - testIdPattern: '^{fileName}(__([A-Z]+[a-z]*?)+)*$', - }, - ], - filename: '/my/cool/__tests__/Parent/index.js', - errors: [ - { - messageId: 'consistentDataTestId', - data: { - attr: 'data-testid', - value: 'WrongComponent__cool', - regex: '/^Parent(__([A-Z]+[a-z]*?)+)*$/', - }, - }, - ], - }, + options: [ + { + testIdPattern: '^{fileName}(__([A-Z]+[a-z]*?)+)*$', + }, + ], + filename: '/my/cool/__tests__/Parent/index.js', + errors: [ + { + messageId: 'consistentDataTestId', + data: { + attr: 'data-testid', + value: 'WrongComponent__cool', + regex: '/^Parent(__([A-Z]+[a-z]*?)+)*$/', + }, + }, + ], + }, ]; ruleTester.run(RULE_NAME, rule, { - valid: [...validTestCases, ...disableAggressiveReporting(validTestCases)], - invalid: [ - ...invalidTestCases, - ...disableAggressiveReporting(invalidTestCases), - ], + valid: [...validTestCases, ...disableAggressiveReporting(validTestCases)], + invalid: [ + ...invalidTestCases, + ...disableAggressiveReporting(invalidTestCases), + ], }); diff --git a/tests/lib/rules/no-await-sync-events.test.ts b/tests/lib/rules/no-await-sync-events.test.ts index 67f1de3f..a3bd6e65 100644 --- a/tests/lib/rules/no-await-sync-events.test.ts +++ b/tests/lib/rules/no-await-sync-events.test.ts @@ -4,164 +4,164 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const FIRE_EVENT_FUNCTIONS = [ - 'copy', - 'cut', - 'paste', - 'compositionEnd', - 'compositionStart', - 'compositionUpdate', - 'keyDown', - 'keyPress', - 'keyUp', - 'focus', - 'blur', - 'focusIn', - 'focusOut', - 'change', - 'input', - 'invalid', - 'submit', - 'reset', - 'click', - 'contextMenu', - 'dblClick', - 'drag', - 'dragEnd', - 'dragEnter', - 'dragExit', - 'dragLeave', - 'dragOver', - 'dragStart', - 'drop', - 'mouseDown', - 'mouseEnter', - 'mouseLeave', - 'mouseMove', - 'mouseOut', - 'mouseOver', - 'mouseUp', - 'popState', - 'select', - 'touchCancel', - 'touchEnd', - 'touchMove', - 'touchStart', - 'scroll', - 'wheel', - 'abort', - 'canPlay', - 'canPlayThrough', - 'durationChange', - 'emptied', - 'encrypted', - 'ended', - 'loadedData', - 'loadedMetadata', - 'loadStart', - 'pause', - 'play', - 'playing', - 'progress', - 'rateChange', - 'seeked', - 'seeking', - 'stalled', - 'suspend', - 'timeUpdate', - 'volumeChange', - 'waiting', - 'load', - 'error', - 'animationStart', - 'animationEnd', - 'animationIteration', - 'transitionEnd', - 'doubleClick', - 'pointerOver', - 'pointerEnter', - 'pointerDown', - 'pointerMove', - 'pointerUp', - 'pointerCancel', - 'pointerOut', - 'pointerLeave', - 'gotPointerCapture', - 'lostPointerCapture', + 'copy', + 'cut', + 'paste', + 'compositionEnd', + 'compositionStart', + 'compositionUpdate', + 'keyDown', + 'keyPress', + 'keyUp', + 'focus', + 'blur', + 'focusIn', + 'focusOut', + 'change', + 'input', + 'invalid', + 'submit', + 'reset', + 'click', + 'contextMenu', + 'dblClick', + 'drag', + 'dragEnd', + 'dragEnter', + 'dragExit', + 'dragLeave', + 'dragOver', + 'dragStart', + 'drop', + 'mouseDown', + 'mouseEnter', + 'mouseLeave', + 'mouseMove', + 'mouseOut', + 'mouseOver', + 'mouseUp', + 'popState', + 'select', + 'touchCancel', + 'touchEnd', + 'touchMove', + 'touchStart', + 'scroll', + 'wheel', + 'abort', + 'canPlay', + 'canPlayThrough', + 'durationChange', + 'emptied', + 'encrypted', + 'ended', + 'loadedData', + 'loadedMetadata', + 'loadStart', + 'pause', + 'play', + 'playing', + 'progress', + 'rateChange', + 'seeked', + 'seeking', + 'stalled', + 'suspend', + 'timeUpdate', + 'volumeChange', + 'waiting', + 'load', + 'error', + 'animationStart', + 'animationEnd', + 'animationIteration', + 'transitionEnd', + 'doubleClick', + 'pointerOver', + 'pointerEnter', + 'pointerDown', + 'pointerMove', + 'pointerUp', + 'pointerCancel', + 'pointerOut', + 'pointerLeave', + 'gotPointerCapture', + 'lostPointerCapture', ]; const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; const USER_EVENT_SYNC_FUNCTIONS = [ - 'clear', - 'click', - 'dblClick', - 'selectOptions', - 'deselectOptions', - 'upload', - // 'type', - // 'keyboard', - 'tab', - 'paste', - 'hover', - 'unhover', + 'clear', + 'click', + 'dblClick', + 'selectOptions', + 'deselectOptions', + 'upload', + // 'type', + // 'keyboard', + 'tab', + 'paste', + 'hover', + 'unhover', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - // sync fireEvents methods without await are valid - ...FIRE_EVENT_FUNCTIONS.map((func) => ({ - code: `() => { + valid: [ + // sync fireEvents methods without await are valid + ...FIRE_EVENT_FUNCTIONS.map((func) => ({ + code: `() => { fireEvent.${func}('foo') } `, - })), - // sync userEvent methods without await are valid - ...USER_EVENT_SYNC_FUNCTIONS.map((func) => ({ - code: `() => { + })), + // sync userEvent methods without await are valid + ...USER_EVENT_SYNC_FUNCTIONS.map((func) => ({ + code: `() => { userEvent.${func}('foo') } `, - })), - { - code: `() => { + })), + { + code: `() => { userEvent.type(element, 'foo') } `, - }, - { - code: `() => { + }, + { + code: `() => { userEvent.keyboard('foo') } `, - }, - { - code: `() => { + }, + { + code: `() => { await userEvent.type(element, 'bar', {delay: 1234}) } `, - }, - { - code: `() => { + }, + { + code: `() => { await userEvent.keyboard('foo', {delay: 1234}) } `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { fireEvent } from 'somewhere-else'; test('should not report fireEvent.click() not related to Testing Library', async() => { await fireEvent.click('foo'); }); `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { fireEvent as renamedFireEvent } from 'somewhere-else'; import renamedUserEvent from '@testing-library/user-event'; import { fireEvent, userEvent } from 'somewhere-else' @@ -172,173 +172,173 @@ ruleTester.run(RULE_NAME, rule, { await userEvent.keyboard('foo', { delay: 5 }); }); `, - }, + }, - // valid tests for fire-event when only user-event set in eventModules - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => - FIRE_EVENT_FUNCTIONS.map((func) => ({ - code: ` + // valid tests for fire-event when only user-event set in eventModules + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => + FIRE_EVENT_FUNCTIONS.map((func) => ({ + code: ` import { fireEvent } from '${testingFramework}'; test('should not report fireEvent.${func} sync event awaited', async() => { await fireEvent.${func}('foo'); }); `, - options: [{ eventModules: ['user-event'] }], - })) - ), + options: [{ eventModules: ['user-event'] }], + })) + ), - // valid tests for user-event when only fire-event set in eventModules - ...USER_EVENT_SYNC_FUNCTIONS.map((func) => ({ - code: ` + // valid tests for user-event when only fire-event set in eventModules + ...USER_EVENT_SYNC_FUNCTIONS.map((func) => ({ + code: ` import userEvent from '@testing-library/user-event'; test('should not report userEvent.${func} sync event awaited', async() => { await userEvent.${func}('foo'); }); `, - options: [{ eventModules: ['fire-event'] }], - })), - ], + options: [{ eventModules: ['fire-event'] }], + })), + ], - invalid: [ - // sync fireEvent methods with await operator are not valid - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => - FIRE_EVENT_FUNCTIONS.map( - (func) => - ({ - code: ` + invalid: [ + // sync fireEvent methods with await operator are not valid + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => + FIRE_EVENT_FUNCTIONS.map( + (func) => + ({ + code: ` import { fireEvent } from '${testingFramework}'; test('should report fireEvent.${func} sync event awaited', async() => { await fireEvent.${func}('foo'); }); `, - errors: [ - { - line: 4, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: `fireEvent.${func}` }, - }, - ], - } as const) - ) - ), - // sync userEvent sync methods with await operator are not valid - ...USER_EVENT_SYNC_FUNCTIONS.map( - (func) => - ({ - code: ` + errors: [ + { + line: 4, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: `fireEvent.${func}` }, + }, + ], + } as const) + ) + ), + // sync userEvent sync methods with await operator are not valid + ...USER_EVENT_SYNC_FUNCTIONS.map( + (func) => + ({ + code: ` import userEvent from '@testing-library/user-event'; test('should report userEvent.${func} sync event awaited', async() => { await userEvent.${func}('foo'); }); `, - errors: [ - { - line: 4, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: `userEvent.${func}` }, - }, - ], - } as const) - ), + errors: [ + { + line: 4, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: `userEvent.${func}` }, + }, + ], + } as const) + ), - // sync fireEvent methods with await operator are not valid - // when only fire-event set in eventModules - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => - FIRE_EVENT_FUNCTIONS.map( - (func) => - ({ - code: ` + // sync fireEvent methods with await operator are not valid + // when only fire-event set in eventModules + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => + FIRE_EVENT_FUNCTIONS.map( + (func) => + ({ + code: ` import { fireEvent } from '${testingFramework}'; test('should report fireEvent.${func} sync event awaited', async() => { await fireEvent.${func}('foo'); }); `, - options: [{ eventModules: ['fire-event'] }], - errors: [ - { - line: 4, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: `fireEvent.${func}` }, - }, - ], - } as const) - ) - ), - // sync userEvent sync methods with await operator are not valid - // when only fire-event set in eventModules - ...USER_EVENT_SYNC_FUNCTIONS.map( - (func) => - ({ - code: ` + options: [{ eventModules: ['fire-event'] }], + errors: [ + { + line: 4, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: `fireEvent.${func}` }, + }, + ], + } as const) + ) + ), + // sync userEvent sync methods with await operator are not valid + // when only fire-event set in eventModules + ...USER_EVENT_SYNC_FUNCTIONS.map( + (func) => + ({ + code: ` import userEvent from '@testing-library/user-event'; test('should report userEvent.${func} sync event awaited', async() => { await userEvent.${func}('foo'); }); `, - options: [{ eventModules: ['user-event'] }], - errors: [ - { - line: 4, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: `userEvent.${func}` }, - }, - ], - } as const) - ), + options: [{ eventModules: ['user-event'] }], + errors: [ + { + line: 4, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: `userEvent.${func}` }, + }, + ], + } as const) + ), - { - code: ` + { + code: ` import userEvent from '@testing-library/user-event'; test('should report async events without delay awaited', async() => { await userEvent.type('foo', 'bar'); await userEvent.keyboard('foo'); }); `, - errors: [ - { - line: 4, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: 'userEvent.type' }, - }, - { - line: 5, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: 'userEvent.keyboard' }, - }, - ], - }, - { - code: ` + errors: [ + { + line: 4, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: 'userEvent.type' }, + }, + { + line: 5, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: 'userEvent.keyboard' }, + }, + ], + }, + { + code: ` import userEvent from '@testing-library/user-event'; test('should report async events with 0 delay awaited', async() => { await userEvent.type('foo', 'bar', { delay: 0 }); await userEvent.keyboard('foo', { delay: 0 }); }); `, - errors: [ - { - line: 4, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: 'userEvent.type' }, - }, - { - line: 5, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: 'userEvent.keyboard' }, - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + line: 4, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: 'userEvent.type' }, + }, + { + line: 5, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: 'userEvent.keyboard' }, + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { fireEvent as renamedFireEvent } from 'test-utils'; import renamedUserEvent from '@testing-library/user-event'; @@ -348,26 +348,26 @@ ruleTester.run(RULE_NAME, rule, { await renamedUserEvent.keyboard('foo', { delay: 0 }); }); `, - errors: [ - { - line: 6, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: 'renamedFireEvent.click' }, - }, - { - line: 7, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: 'renamedUserEvent.type' }, - }, - { - line: 8, - column: 17, - messageId: 'noAwaitSyncEvents', - data: { name: 'renamedUserEvent.keyboard' }, - }, - ], - }, - ], + errors: [ + { + line: 6, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: 'renamedFireEvent.click' }, + }, + { + line: 7, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: 'renamedUserEvent.type' }, + }, + { + line: 8, + column: 17, + messageId: 'noAwaitSyncEvents', + data: { name: 'renamedUserEvent.keyboard' }, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/no-await-sync-query.test.ts b/tests/lib/rules/no-await-sync-query.test.ts index 445784c0..9538a979 100644 --- a/tests/lib/rules/no-await-sync-query.test.ts +++ b/tests/lib/rules/no-await-sync-query.test.ts @@ -1,102 +1,102 @@ import rule, { RULE_NAME } from '../../../lib/rules/no-await-sync-query'; import { - SYNC_QUERIES_COMBINATIONS, - ASYNC_QUERIES_COMBINATIONS, + SYNC_QUERIES_COMBINATIONS, + ASYNC_QUERIES_COMBINATIONS, } from '../../../lib/utils'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - // sync queries without await are valid - ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ - code: `() => { + valid: [ + // sync queries without await are valid + ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ + code: `() => { const element = ${query}('foo') } `, - })), - // custom sync queries without await are valid - `() => { + })), + // custom sync queries without await are valid + `() => { const element = getByIcon('search') } `, - `() => { + `() => { const element = queryByIcon('search') } `, - `() => { + `() => { const element = getAllByIcon('search') } `, - `() => { + `() => { const element = queryAllByIcon('search') } `, - `async () => { + `async () => { await waitFor(() => { getByText('search'); }); } `, - // awaited custom sync query not matching custom-queries setting is valid - { - settings: { - 'testing-library/custom-queries': ['queryByIcon', 'ByComplexText'], - }, - code: ` + // awaited custom sync query not matching custom-queries setting is valid + { + settings: { + 'testing-library/custom-queries': ['queryByIcon', 'ByComplexText'], + }, + code: ` test('A valid example test', async () => { const element = await getByIcon('search') }) `, - }, + }, - // sync queries without await inside assert are valid - ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ - code: `() => { + // sync queries without await inside assert are valid + ...SYNC_QUERIES_COMBINATIONS.map((query) => ({ + code: `() => { expect(${query}('foo')).toBeEnabled() } `, - })), + })), - // async queries with await operator are valid - ...ASYNC_QUERIES_COMBINATIONS.map((query) => ({ - code: `async () => { + // async queries with await operator are valid + ...ASYNC_QUERIES_COMBINATIONS.map((query) => ({ + code: `async () => { const element = await ${query}('foo') } `, - })), + })), - // async queries with then method are valid - ...ASYNC_QUERIES_COMBINATIONS.map((query) => ({ - code: `() => { + // async queries with then method are valid + ...ASYNC_QUERIES_COMBINATIONS.map((query) => ({ + code: `() => { ${query}('foo').then(() => {}); } `, - })), + })), - // sync query awaited but not related to custom module is invalid but not reported - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + // sync query awaited but not related to custom module is invalid but not reported + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { screen } from 'somewhere-else' () => { const element = await screen.getByRole('button') } `, - }, + }, - // https://github.com/testing-library/eslint-plugin-testing-library/issues/276 - ` + // https://github.com/testing-library/eslint-plugin-testing-library/issues/276 + ` // sync query within call expression but not part of the callee const chooseElementFromSomewhere = async (text, getAllByLabelText) => { const someElement = getAllByLabelText(text)[0].parentElement; @@ -107,7 +107,7 @@ ruleTester.run(RULE_NAME, rule, { await chooseElementFromSomewhere('someTextToUseInAQuery', getAllByLabelText); `, - `// edge case for coverage: + `// edge case for coverage: // valid use case without call expression // so there is no innermost function scope found await test('edge case for no innermost function scope', () => { @@ -115,160 +115,160 @@ ruleTester.run(RULE_NAME, rule, { }) `, - `// edge case for coverage: CallExpression without deepest Identifier + `// edge case for coverage: CallExpression without deepest Identifier await someList[0](); `, - `// element is removed + `// element is removed test('movie title no longer present in DOM', async () => { await waitForElementToBeRemoved(() => queryByText('the mummy')) }) `, - ], + ], - invalid: [ - // sync queries with await operator are not valid - ...SYNC_QUERIES_COMBINATIONS.map( - (query) => - ({ - code: `async () => { + invalid: [ + // sync queries with await operator are not valid + ...SYNC_QUERIES_COMBINATIONS.map( + (query) => + ({ + code: `async () => { const element = await ${query}('foo') } `, - errors: [ - { - messageId: 'noAwaitSyncQuery', - line: 2, - column: 31, - }, - ], - } as const) - ), - // custom sync queries with await operator are not valid - { - code: ` + errors: [ + { + messageId: 'noAwaitSyncQuery', + line: 2, + column: 31, + }, + ], + } as const) + ), + // custom sync queries with await operator are not valid + { + code: ` async () => { const element = await getByIcon('search') } `, - errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], - }, - { - code: ` + errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], + }, + { + code: ` async () => { const element = await queryByIcon('search') } `, - errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], - }, - { - code: ` + errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], + }, + { + code: ` async () => { const element = await screen.getAllByIcon('search') } `, - errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], - }, - { - code: ` + errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], + }, + { + code: ` async () => { const element = await screen.queryAllByIcon('search') } `, - errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], - }, - // sync queries with await operator inside assert are not valid - ...SYNC_QUERIES_COMBINATIONS.map( - (query) => - ({ - code: `async () => { + errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }], + }, + // sync queries with await operator inside assert are not valid + ...SYNC_QUERIES_COMBINATIONS.map( + (query) => + ({ + code: `async () => { expect(await ${query}('foo')).toBeEnabled() } `, - errors: [ - { - messageId: 'noAwaitSyncQuery', - line: 2, - column: 22, - }, - ], - } as const) - ), + errors: [ + { + messageId: 'noAwaitSyncQuery', + line: 2, + column: 22, + }, + ], + } as const) + ), - // sync queries in screen with await operator are not valid - ...SYNC_QUERIES_COMBINATIONS.map( - (query) => - ({ - code: `async () => { + // sync queries in screen with await operator are not valid + ...SYNC_QUERIES_COMBINATIONS.map( + (query) => + ({ + code: `async () => { const element = await screen.${query}('foo') } `, - errors: [ - { - messageId: 'noAwaitSyncQuery', - line: 2, - column: 38, - }, - ], - } as const) - ), + errors: [ + { + messageId: 'noAwaitSyncQuery', + line: 2, + column: 38, + }, + ], + } as const) + ), - // sync queries in screen with await operator inside assert are not valid - ...SYNC_QUERIES_COMBINATIONS.map( - (query) => - ({ - code: `async () => { + // sync queries in screen with await operator inside assert are not valid + ...SYNC_QUERIES_COMBINATIONS.map( + (query) => + ({ + code: `async () => { expect(await screen.${query}('foo')).toBeEnabled() } `, - errors: [ - { - messageId: 'noAwaitSyncQuery', - line: 2, - column: 29, - }, - ], - } as const) - ), + errors: [ + { + messageId: 'noAwaitSyncQuery', + line: 2, + column: 29, + }, + ], + } as const) + ), - // sync query awaited and related to testing library module - // with custom module setting is not valid - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + // sync query awaited and related to testing library module + // with custom module setting is not valid + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { screen } from '${testingFramework}' () => { const element = await screen.getByRole('button') } `, - errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }], - } as const) - ), - // sync query awaited and related to custom module is not valid - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }], + } as const) + ), + // sync query awaited and related to custom module is not valid + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { screen } from 'test-utils' () => { const element = await screen.getByRole('button') } `, - errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }], - }, + errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }], + }, - // awaited custom sync query matching custom-queries setting is invalid - { - settings: { - 'testing-library/custom-queries': ['queryByIcon', 'ByComplexText'], - }, - code: ` + // awaited custom sync query matching custom-queries setting is invalid + { + settings: { + 'testing-library/custom-queries': ['queryByIcon', 'ByComplexText'], + }, + code: ` test('A valid example test', async () => { const element = await queryByIcon('search') }) `, - errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], - }, - ], + errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }], + }, + ], }); diff --git a/tests/lib/rules/no-container.test.ts b/tests/lib/rules/no-container.test.ts index f1171db0..6967de9e 100644 --- a/tests/lib/rules/no-container.test.ts +++ b/tests/lib/rules/no-container.test.ts @@ -4,34 +4,34 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - { - code: ` + valid: [ + { + code: ` render(); screen.getByRole('button', {name: /click me/i}); `, - }, - { - code: ` + }, + { + code: ` const { container } = render(); expect(container.firstChild).toBeDefined(); `, - }, - { - code: ` + }, + { + code: ` const { container: alias } = render(); expect(alias.firstChild).toBeDefined(); `, - }, - { - code: ` + }, + { + code: ` function getExampleDOM() { const container = document.createElement('div'); container.innerHTML = \` @@ -48,88 +48,88 @@ ruleTester.run(RULE_NAME, rule, { const exampleDOM = getExampleDOM(); screen.getByText(exampleDOM, 'Print Username').click(); `, - }, - { - code: ` + }, + { + code: ` const { container: { firstChild } } = render(); expect(firstChild).toBeDefined(); `, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render as renamed } from '${testingFramework}' import { render } from 'somewhere-else' const { container } = render(); const button = container.querySelector('.btn-primary'); `, - } as const) - ), - { - settings: { - 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], - }, - code: ` + } as const) + ), + { + settings: { + 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], + }, + code: ` import { otherRender } from 'somewhere-else' const { container } = otherRender(); const button = container.querySelector('.btn-primary'); `, - }, - ], - invalid: [ - { - code: ` + }, + ], + invalid: [ + { + code: ` const { container } = render(); const button = container.querySelector('.btn-primary'); `, - errors: [ - { - line: 3, - column: 24, - messageId: 'noContainer', - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + line: 3, + column: 24, + messageId: 'noContainer', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from 'test-utils' const { container } = render(); const button = container.querySelector('.btn-primary'); `, - errors: [ - { - line: 4, - column: 24, - messageId: 'noContainer', - }, - ], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + line: 4, + column: 24, + messageId: 'noContainer', + }, + ], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render as testingRender } from '${testingFramework}' const { container: renamed } = testingRender(); const button = renamed.querySelector('.btn-primary'); `, - errors: [ - { - line: 4, - column: 24, - messageId: 'noContainer', - }, - ], - } as const) - ), - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + line: 4, + column: 24, + messageId: 'noContainer', + }, + ], + } as const) + ), + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from '${testingFramework}' const setup = () => render() @@ -137,100 +137,100 @@ ruleTester.run(RULE_NAME, rule, { const { container } = setup() const button = container.querySelector('.btn-primary'); `, - errors: [ - { - line: 7, - column: 24, - messageId: 'noContainer', - }, - ], - } as const) - ), - { - code: ` + errors: [ + { + line: 7, + column: 24, + messageId: 'noContainer', + }, + ], + } as const) + ), + { + code: ` const { container } = render(); container.querySelector(); `, - errors: [ - { - line: 3, - column: 9, - messageId: 'noContainer', - }, - ], - }, - { - code: ` + errors: [ + { + line: 3, + column: 9, + messageId: 'noContainer', + }, + ], + }, + { + code: ` const { container: alias } = render(); alias.querySelector(); `, - errors: [ - { - line: 3, - column: 9, - messageId: 'noContainer', - }, - ], - }, - { - code: ` + errors: [ + { + line: 3, + column: 9, + messageId: 'noContainer', + }, + ], + }, + { + code: ` const view = render(); const button = view.container.querySelector('.btn-primary'); `, - errors: [ - { - line: 3, - column: 29, - messageId: 'noContainer', - }, - ], - }, - { - code: ` + errors: [ + { + line: 3, + column: 29, + messageId: 'noContainer', + }, + ], + }, + { + code: ` const { container: { querySelector } } = render(); querySelector('foo'); `, - errors: [ - { - line: 3, - column: 9, - messageId: 'noContainer', - }, - ], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + line: 3, + column: 9, + messageId: 'noContainer', + }, + ], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from '${testingFramework}' const { container: { querySelector } } = render(); querySelector('foo'); `, - errors: [ - { - line: 4, - column: 9, - messageId: 'noContainer', - }, - ], - } as const) - ), - { - settings: { - 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], - }, - code: ` + errors: [ + { + line: 4, + column: 9, + messageId: 'noContainer', + }, + ], + } as const) + ), + { + settings: { + 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], + }, + code: ` const { container } = renderWithRedux(); container.querySelector(); `, - errors: [ - { - line: 3, - column: 9, - messageId: 'noContainer', - }, - ], - }, - ], + errors: [ + { + line: 3, + column: 9, + messageId: 'noContainer', + }, + ], + }, + ], }); diff --git a/tests/lib/rules/no-debugging-utils.test.ts b/tests/lib/rules/no-debugging-utils.test.ts index 11adce8a..3879d9e2 100644 --- a/tests/lib/rules/no-debugging-utils.test.ts +++ b/tests/lib/rules/no-debugging-utils.test.ts @@ -4,161 +4,161 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `debug()`, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + valid: [ + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `debug()`, + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { screen } from 'somewhere-else' screen.debug() `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `() => { + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `() => { const somethingElse = {} const { debug } = foo() debug() }`, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` let foo const debug = require('debug') debug() `, - }, - { - code: ` + }, + { + code: ` const { test } = render() test() `, - }, - { - code: ` + }, + { + code: ` const utils = render() utils.debug `, - }, - { - code: ` + }, + { + code: ` const utils = render() utils.foo() `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `screen.debug()`, - }, - { - code: `console.debug()`, - }, - { - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `screen.debug()`, + }, + { + code: `console.debug()`, + }, + { + code: ` const consoleDebug = console.debug consoleDebug() `, - }, - { - code: ` + }, + { + code: ` const { debug } = console debug() `, - }, - { - code: ` + }, + { + code: ` const { debug: consoleDebug } = console consoleDebug() `, - }, - { - code: ` + }, + { + code: ` const { screen } = require('@testing-library/dom') screen.debug `, - }, - { - code: ` + }, + { + code: ` import { screen } from '@testing-library/dom' screen.debug `, - }, - { - code: ` + }, + { + code: ` import { screen } from '@testing-library/dom' screen.logTestingPlaygroundURL() `, - options: [{ utilsToCheckFor: { logTestingPlaygroundURL: false } }], - }, - { - code: ` + options: [{ utilsToCheckFor: { logTestingPlaygroundURL: false } }], + }, + { + code: ` import { screen } from '@testing-library/dom' screen.logTestingPlaygroundURL() `, - options: [{ utilsToCheckFor: undefined }], - }, - { - code: `const { queries } = require('@testing-library/dom')`, - }, - { - code: `import * as dtl from '@testing-library/dom'; + options: [{ utilsToCheckFor: undefined }], + }, + { + code: `const { queries } = require('@testing-library/dom')`, + }, + { + code: `import * as dtl from '@testing-library/dom'; const foo = dtl.debug; `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import * as foo from '@somewhere/else'; foo.debug(); `, - }, - { - code: `import { queries } from '@testing-library/dom'`, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + code: `import { queries } from '@testing-library/dom'`, + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` const { screen } = require('something-else') screen.debug() `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { screen } from 'something-else' screen.debug() `, - }, - { - code: ` + }, + { + code: ` async function foo() { const foo = await bar; } `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { debug as testingDebug } from 'test-utils' import { debug } from 'somewhere-else' debug() `, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render as testingRender } from '${testingFramework}' import { render } from 'somewhere-else' @@ -167,10 +167,10 @@ ruleTester.run(RULE_NAME, rule, { somethingElse() debug() `, - })), - ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + })), + ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render as testingRender } from '${testingFramework}' import { render } from 'somewhere-else' @@ -180,89 +180,89 @@ ruleTester.run(RULE_NAME, rule, { somethingElse() debug() `, - })), + })), - { - code: ` + { + code: ` // cover edge case for https://github.com/testing-library/eslint-plugin-testing-library/issues/306 thing.method.lastCall.args[0](); `, - }, - ], + }, + ], - invalid: [ - { - code: `debug()`, - errors: [{ line: 1, column: 1, messageId: 'noDebug' }], - }, - { - code: ` + invalid: [ + { + code: `debug()`, + errors: [{ line: 1, column: 1, messageId: 'noDebug' }], + }, + { + code: ` import { screen } from 'aggressive-reporting' screen.debug() `, - errors: [{ line: 3, column: 14, messageId: 'noDebug' }], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 3, column: 14, messageId: 'noDebug' }], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { screen } from 'test-utils' screen.debug() `, - errors: [{ line: 3, column: 14, messageId: 'noDebug' }], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 3, column: 14, messageId: 'noDebug' }], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { debug as testingDebug } from 'test-utils' testingDebug() `, - errors: [{ line: 3, column: 7, messageId: 'noDebug' }], - }, - { - code: ` + errors: [{ line: 3, column: 7, messageId: 'noDebug' }], + }, + { + code: ` const { debug } = render() debug() `, - errors: [ - { - line: 3, - column: 9, - messageId: 'noDebug', - }, - ], - }, - { - settings: { - 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], - }, - code: ` + errors: [ + { + line: 3, + column: 9, + messageId: 'noDebug', + }, + ], + }, + { + settings: { + 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], + }, + code: ` const { debug } = renderWithRedux() debug() `, - errors: [ - { - line: 3, - column: 9, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + errors: [ + { + line: 3, + column: 9, + messageId: 'noDebug', + }, + ], + }, + { + code: ` const utils = render() utils.debug() `, - errors: [ - { - line: 3, - column: 15, - messageId: 'noDebug', - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + line: 3, + column: 15, + messageId: 'noDebug', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from 'test-utils' const setup = () => render() @@ -270,90 +270,90 @@ ruleTester.run(RULE_NAME, rule, { const utils = setup() utils.debug() `, - errors: [ - { - line: 7, - column: 15, - messageId: 'noDebug', - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// aggressive reporting disabled + errors: [ + { + line: 7, + column: 15, + messageId: 'noDebug', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// aggressive reporting disabled import { render } from 'test-utils' const utils = render() utils.debug() `, - errors: [ - { - line: 4, - column: 15, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + errors: [ + { + line: 4, + column: 15, + messageId: 'noDebug', + }, + ], + }, + { + code: ` const utils = render() utils.debug() utils.foo() utils.debug() `, - errors: [ - { - line: 3, - column: 15, - messageId: 'noDebug', - }, - { - line: 5, - column: 15, - messageId: 'noDebug', - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// aggressive reporting disabled + errors: [ + { + line: 3, + column: 15, + messageId: 'noDebug', + }, + { + line: 5, + column: 15, + messageId: 'noDebug', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// aggressive reporting disabled import { render } from 'test-utils' const utils = render() utils.debug() utils.foo() utils.debug() `, - errors: [ - { - line: 4, - column: 15, - messageId: 'noDebug', - }, - { - line: 6, - column: 15, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + errors: [ + { + line: 4, + column: 15, + messageId: 'noDebug', + }, + { + line: 6, + column: 15, + messageId: 'noDebug', + }, + ], + }, + { + code: ` describe(() => { test(async () => { const { debug } = await render("foo") debug() }) })`, - errors: [ - { - line: 5, - column: 11, - messageId: 'noDebug', - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// aggressive reporting disabled + errors: [ + { + line: 5, + column: 11, + messageId: 'noDebug', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// aggressive reporting disabled import { render } from 'test-utils' describe(() => { test(async () => { @@ -361,33 +361,33 @@ ruleTester.run(RULE_NAME, rule, { debug() }) })`, - errors: [ - { - line: 6, - column: 11, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + errors: [ + { + line: 6, + column: 11, + messageId: 'noDebug', + }, + ], + }, + { + code: ` describe(() => { test(async () => { const utils = await render("foo") utils.debug() }) })`, - errors: [ - { - line: 5, - column: 17, - messageId: 'noDebug', - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// aggressive reporting disabled + errors: [ + { + line: 5, + column: 17, + messageId: 'noDebug', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// aggressive reporting disabled import { render } from 'test-utils' describe(() => { test(async () => { @@ -395,195 +395,195 @@ ruleTester.run(RULE_NAME, rule, { utils.debug() }) })`, - errors: [ - { - line: 6, - column: 17, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + errors: [ + { + line: 6, + column: 17, + messageId: 'noDebug', + }, + ], + }, + { + code: ` const { screen } = require('@testing-library/dom') screen.debug() `, - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// aggressive reporting disabled + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// aggressive reporting disabled const { screen } = require('@testing-library/dom') screen.debug() `, - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + code: ` import { screen } from '@testing-library/dom' screen.debug() `, - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + code: ` import { screen } from '@testing-library/dom' screen.logTestingPlaygroundURL() `, - options: [{ utilsToCheckFor: { logTestingPlaygroundURL: true } }], - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + options: [{ utilsToCheckFor: { logTestingPlaygroundURL: true } }], + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + code: ` import { logRoles } from '@testing-library/dom' logRoles(document.createElement('nav')) `, - options: [{ utilsToCheckFor: { logRoles: true } }], - errors: [ - { - line: 3, - column: 9, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + options: [{ utilsToCheckFor: { logRoles: true } }], + errors: [ + { + line: 3, + column: 9, + messageId: 'noDebug', + }, + ], + }, + { + code: ` import { screen } from '@testing-library/dom' screen.logTestingPlaygroundURL() `, - options: [{ utilsToCheckFor: { logRoles: true } }], - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + options: [{ utilsToCheckFor: { logRoles: true } }], + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + code: ` import { screen } from '@testing-library/dom' screen.logTestingPlaygroundURL() `, - options: [{ utilsToCheckFor: { debug: false } }], - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + options: [{ utilsToCheckFor: { debug: false } }], + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + code: ` import { screen } from '@testing-library/dom' screen.logTestingPlaygroundURL() `, - options: [{ utilsToCheckFor: {} }], - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - code: ` + options: [{ utilsToCheckFor: {} }], + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + code: ` import { screen } from '@testing-library/dom' screen.logTestingPlaygroundURL() `, - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// aggressive reporting disabled + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// aggressive reporting disabled import { screen } from '@testing-library/dom' screen.debug() `, - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - // https://github.com/testing-library/eslint-plugin-testing-library/issues/174 - code: ` + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + // https://github.com/testing-library/eslint-plugin-testing-library/issues/174 + code: ` import { screen, render } from '@testing-library/dom' screen.debug() `, - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// aggressive reporting disabled + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// aggressive reporting disabled import { screen, render } from '@testing-library/dom' screen.debug() `, - errors: [ - { - line: 3, - column: 16, - messageId: 'noDebug', - }, - ], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import * as dtl from '@testing-library/dom'; dtl.debug(); `, - errors: [ - { - messageId: 'noDebug', - line: 3, - column: 13, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'noDebug', + line: 3, + column: 13, + }, + ], + }, + { + code: ` import { render } from 'aggressive-reporting' const { debug } = render(element) @@ -591,13 +591,13 @@ ruleTester.run(RULE_NAME, rule, { somethingElse() debug() `, - errors: [{ line: 7, column: 7, messageId: 'noDebug' }], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 7, column: 7, messageId: 'noDebug' }], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from '${testingFramework}' const { debug } = render(element) @@ -605,12 +605,12 @@ ruleTester.run(RULE_NAME, rule, { somethingElse() debug() `, - errors: [{ line: 7, column: 7, messageId: 'noDebug' }], - } as const) - ), - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 7, column: 7, messageId: 'noDebug' }], + } as const) + ), + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from 'test-utils' const { debug: renamed } = render(element) @@ -618,13 +618,13 @@ ruleTester.run(RULE_NAME, rule, { somethingElse() renamed() `, - errors: [{ line: 7, column: 7, messageId: 'noDebug' }], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 7, column: 7, messageId: 'noDebug' }], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from '${testingFramework}' const utils = render(element) @@ -632,15 +632,15 @@ ruleTester.run(RULE_NAME, rule, { somethingElse() utils.debug() `, - errors: [{ line: 7, column: 13, messageId: 'noDebug' }], - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - 'testing-library/custom-renders': ['testingRender'], - }, - code: `// aggressive reporting disabled, custom render set + errors: [{ line: 7, column: 13, messageId: 'noDebug' }], + } as const) + ), + { + settings: { + 'testing-library/utils-module': 'test-utils', + 'testing-library/custom-renders': ['testingRender'], + }, + code: `// aggressive reporting disabled, custom render set import { testingRender } from 'test-utils' const { debug: renamedDebug } = testingRender(element) @@ -648,13 +648,13 @@ ruleTester.run(RULE_NAME, rule, { somethingElse() renamedDebug() `, - errors: [{ line: 7, column: 7, messageId: 'noDebug' }], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 7, column: 7, messageId: 'noDebug' }], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from '${testingFramework}' const utils = render(element) @@ -668,8 +668,8 @@ ruleTester.run(RULE_NAME, rule, { utils.debug() renamedDestructuredDebug('foo') `, - errors: [{ line: 12, column: 13, messageId: 'noDebug' }], - } as const) - ), - ], + errors: [{ line: 12, column: 13, messageId: 'noDebug' }], + } as const) + ), + ], }); diff --git a/tests/lib/rules/no-dom-import.test.ts b/tests/lib/rules/no-dom-import.test.ts index d12174ff..7336bbd6 100644 --- a/tests/lib/rules/no-dom-import.test.ts +++ b/tests/lib/rules/no-dom-import.test.ts @@ -4,204 +4,204 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - { - configOption: 'angular', - oldName: '@testing-library/angular', - newName: '@testing-library/angular', - }, - { - configOption: 'react', - oldName: 'react-testing-library', - newName: '@testing-library/react', - }, - { - configOption: 'vue', - oldName: 'vue-testing-library', - newName: '@testing-library/vue', - }, - { - configOption: 'marko', - oldName: '@marko/testing-library', - newName: '@marko/testing-library', - }, + { + configOption: 'angular', + oldName: '@testing-library/angular', + newName: '@testing-library/angular', + }, + { + configOption: 'react', + oldName: 'react-testing-library', + newName: '@testing-library/react', + }, + { + configOption: 'vue', + oldName: 'vue-testing-library', + newName: '@testing-library/vue', + }, + { + configOption: 'marko', + oldName: '@marko/testing-library', + newName: '@marko/testing-library', + }, ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - 'import { foo } from "foo"', - 'import "foo"', - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap(({ oldName, newName }) => - [oldName, newName].flatMap((testingFramework) => [ - `import { fireEvent } from "${testingFramework}"`, - `import * as testing from "${testingFramework}"`, - `import "${testingFramework}"`, - ]) - ), - 'const { foo } = require("foo")', - 'require("foo")', - 'require("")', - 'require()', - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap(({ oldName, newName }) => - [oldName, newName].flatMap((testingFramework) => [ - `const { fireEvent } = require("${testingFramework}")`, - `const { fireEvent: testing } = require("${testingFramework}")`, - `require("${testingFramework}")`, - ]) - ), - { - code: 'import { fireEvent } from "test-utils"', - settings: { 'testing-library/utils-module': 'test-utils' }, - }, - ], - invalid: [ - { - code: 'import { fireEvent } from "dom-testing-library"', - errors: [ - { - messageId: 'noDomImport', - }, - ], - output: 'import { fireEvent } from "dom-testing-library"', - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + valid: [ + 'import { foo } from "foo"', + 'import "foo"', + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap(({ oldName, newName }) => + [oldName, newName].flatMap((testingFramework) => [ + `import { fireEvent } from "${testingFramework}"`, + `import * as testing from "${testingFramework}"`, + `import "${testingFramework}"`, + ]) + ), + 'const { foo } = require("foo")', + 'require("foo")', + 'require("")', + 'require()', + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap(({ oldName, newName }) => + [oldName, newName].flatMap((testingFramework) => [ + `const { fireEvent } = require("${testingFramework}")`, + `const { fireEvent: testing } = require("${testingFramework}")`, + `require("${testingFramework}")`, + ]) + ), + { + code: 'import { fireEvent } from "test-utils"', + settings: { 'testing-library/utils-module': 'test-utils' }, + }, + ], + invalid: [ + { + code: 'import { fireEvent } from "dom-testing-library"', + errors: [ + { + messageId: 'noDomImport', + }, + ], + output: 'import { fireEvent } from "dom-testing-library"', + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: dom-testing-library imported with custom module setting import { fireEvent } from "dom-testing-library"`, - errors: [ - { - line: 3, - messageId: 'noDomImport', - }, - ], - output: ` + errors: [ + { + line: 3, + messageId: 'noDomImport', + }, + ], + output: ` // case: dom-testing-library imported with custom module setting import { fireEvent } from "dom-testing-library"`, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap( - ({ configOption, oldName, newName }) => - [true, false].flatMap((isOldImport) => - // Single quote or double quotes should not be replaced - [`'`, `"`].flatMap((quote) => [ - { - code: `const { fireEvent } = require(${quote}${ - isOldImport ? 'dom-testing-library' : '@testing-library/dom' - }${quote})`, - options: [configOption], - errors: [ - { - data: { module: isOldImport ? oldName : newName }, - messageId: 'noDomImportFramework', - }, - ], - output: `const { fireEvent } = require(${quote}${ - isOldImport ? oldName : newName - }${quote})`, - } as const, - { - code: `import { fireEvent } from ${quote}${ - isOldImport ? 'dom-testing-library' : '@testing-library/dom' - }${quote}`, - options: [configOption], - errors: [ - { - data: { module: isOldImport ? oldName : newName }, - messageId: 'noDomImportFramework', - }, - ], - output: `import { fireEvent } from ${quote}${ - isOldImport ? oldName : newName - }${quote}`, - } as const, - ]) - ) - ), - { - code: 'import * as testing from "dom-testing-library"', - errors: [{ messageId: 'noDomImport' }], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap( + ({ configOption, oldName, newName }) => + [true, false].flatMap((isOldImport) => + // Single quote or double quotes should not be replaced + [`'`, `"`].flatMap((quote) => [ + { + code: `const { fireEvent } = require(${quote}${ + isOldImport ? 'dom-testing-library' : '@testing-library/dom' + }${quote})`, + options: [configOption], + errors: [ + { + data: { module: isOldImport ? oldName : newName }, + messageId: 'noDomImportFramework', + }, + ], + output: `const { fireEvent } = require(${quote}${ + isOldImport ? oldName : newName + }${quote})`, + } as const, + { + code: `import { fireEvent } from ${quote}${ + isOldImport ? 'dom-testing-library' : '@testing-library/dom' + }${quote}`, + options: [configOption], + errors: [ + { + data: { module: isOldImport ? oldName : newName }, + messageId: 'noDomImportFramework', + }, + ], + output: `import { fireEvent } from ${quote}${ + isOldImport ? oldName : newName + }${quote}`, + } as const, + ]) + ) + ), + { + code: 'import * as testing from "dom-testing-library"', + errors: [{ messageId: 'noDomImport' }], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` // case: dom-testing-library wildcard imported with custom module setting import * as testing from "dom-testing-library"`, - errors: [{ line: 3, messageId: 'noDomImport' }], - }, - { - code: 'import { fireEvent } from "@testing-library/dom"', - errors: [{ messageId: 'noDomImport' }], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 3, messageId: 'noDomImport' }], + }, + { + code: 'import { fireEvent } from "@testing-library/dom"', + errors: [{ messageId: 'noDomImport' }], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` // case: @testing-library/dom imported with custom module setting import { fireEvent } from "@testing-library/dom"`, - errors: [{ line: 3, messageId: 'noDomImport' }], - }, - { - code: 'import * as testing from "@testing-library/dom"', - errors: [{ messageId: 'noDomImport' }], - }, - { - code: 'import "dom-testing-library"', - errors: [{ messageId: 'noDomImport' }], - }, - { - code: 'import "@testing-library/dom"', - errors: [{ messageId: 'noDomImport' }], - }, - { - code: 'const { fireEvent } = require("dom-testing-library")', - errors: [{ messageId: 'noDomImport' }], - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 3, messageId: 'noDomImport' }], + }, + { + code: 'import * as testing from "@testing-library/dom"', + errors: [{ messageId: 'noDomImport' }], + }, + { + code: 'import "dom-testing-library"', + errors: [{ messageId: 'noDomImport' }], + }, + { + code: 'import "@testing-library/dom"', + errors: [{ messageId: 'noDomImport' }], + }, + { + code: 'const { fireEvent } = require("dom-testing-library")', + errors: [{ messageId: 'noDomImport' }], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` // case: dom-testing-library required with custom module setting const { fireEvent } = require("dom-testing-library")`, - errors: [{ line: 3, messageId: 'noDomImport' }], - }, - { - code: 'const { fireEvent } = require("@testing-library/dom")', - errors: [{ messageId: 'noDomImport' }], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap( - ({ configOption, oldName, newName }) => - [true, false].map( - (isOldImport) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [{ line: 3, messageId: 'noDomImport' }], + }, + { + code: 'const { fireEvent } = require("@testing-library/dom")', + errors: [{ messageId: 'noDomImport' }], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap( + ({ configOption, oldName, newName }) => + [true, false].map( + (isOldImport) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` // case: @testing-library/dom required with custom module setting const { fireEvent } = require("${ - isOldImport ? 'dom-testing-library' : '@testing-library/dom' - }") + isOldImport ? 'dom-testing-library' : '@testing-library/dom' + }") `, - options: [configOption], - errors: [ - { - data: { module: isOldImport ? oldName : newName }, - messageId: 'noDomImportFramework', - }, - ], - output: ` + options: [configOption], + errors: [ + { + data: { module: isOldImport ? oldName : newName }, + messageId: 'noDomImportFramework', + }, + ], + output: ` // case: @testing-library/dom required with custom module setting const { fireEvent } = require("${ - isOldImport ? oldName : newName - }") + isOldImport ? oldName : newName + }") `, - } as const) - ) - ), - { - code: 'require("dom-testing-library")', - errors: [{ messageId: 'noDomImport' }], - }, - { - code: 'require("@testing-library/dom")', - errors: [{ messageId: 'noDomImport' }], - }, - ], + } as const) + ) + ), + { + code: 'require("dom-testing-library")', + errors: [{ messageId: 'noDomImport' }], + }, + { + code: 'require("@testing-library/dom")', + errors: [{ messageId: 'noDomImport' }], + }, + ], }); diff --git a/tests/lib/rules/no-global-regexp-flag-in-query.test.ts b/tests/lib/rules/no-global-regexp-flag-in-query.test.ts index 9762994e..e53f7451 100644 --- a/tests/lib/rules/no-global-regexp-flag-in-query.test.ts +++ b/tests/lib/rules/no-global-regexp-flag-in-query.test.ts @@ -1,86 +1,86 @@ import rule, { - RULE_NAME, + RULE_NAME, } from '../../../lib/rules/no-global-regexp-flag-in-query'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); ruleTester.run(RULE_NAME, rule, { - valid: [ - ` + valid: [ + ` import { screen } from '@testing-library/dom' screen.getByText(/hello/) `, - ` + ` import { screen } from '@testing-library/dom' screen.getByText(/hello/i) `, - ` + ` import { screen } from '@testing-library/dom' screen.getByText('hello') `, - ` + ` import { screen } from '@testing-library/dom' screen.findByRole('button', {name: /hello/}) `, - ` + ` import { screen } from '@testing-library/dom' screen.findByRole('button', {name: /hello/im}) `, - ` + ` import { screen } from '@testing-library/dom' screen.findByRole('button', {name: 'hello'}) `, - ` + ` const utils = render() utils.findByRole('button', {name: /hello/m}) `, - ` + ` const {queryAllByPlaceholderText} = render() queryAllByPlaceholderText(/hello/i) `, - ` + ` import { within } from '@testing-library/dom' within(element).findByRole('button', {name: /hello/i}) `, - ` + ` import { within } from '@testing-library/dom' within(element).queryByText('Hello') `, - ` + ` const text = 'hello'; /hello/g.test(text) text.match(/hello/g) `, - ` + ` const text = somethingElse() /hello/g.test(text) text.match(/hello/g) `, - ` + ` import somethingElse from 'somethingElse' somethingElse.lookup(/hello/g) `, - ` + ` import { screen } from '@testing-library/dom' screen.notAQuery(/hello/g) `, - ` + ` import { screen } from '@testing-library/dom' screen.notAQuery('button', {name: /hello/g}) `, - ` + ` const utils = render() utils.notAQuery('button', {name: /hello/i}) `, - ` + ` const utils = render() utils.notAQuery(/hello/i) `, - // issue #565 - ` + // issue #565 + ` import { screen } from "@testing-library/react" describe("App", () => { @@ -89,112 +89,112 @@ ruleTester.run(RULE_NAME, rule, { }) }) `, - ], - invalid: [ - { - code: ` + ], + invalid: [ + { + code: ` import { screen } from '@testing-library/dom' screen.getByText(/hello/g)`, - errors: [ - { - messageId: 'noGlobalRegExpFlagInQuery', - line: 3, - column: 26, - }, - ], - output: ` + errors: [ + { + messageId: 'noGlobalRegExpFlagInQuery', + line: 3, + column: 26, + }, + ], + output: ` import { screen } from '@testing-library/dom' screen.getByText(/hello/)`, - }, - { - code: ` + }, + { + code: ` import { screen } from '@testing-library/dom' screen.findByRole('button', {name: /hellogg/g})`, - errors: [ - { - messageId: 'noGlobalRegExpFlagInQuery', - line: 3, - column: 44, - }, - ], - output: ` + errors: [ + { + messageId: 'noGlobalRegExpFlagInQuery', + line: 3, + column: 44, + }, + ], + output: ` import { screen } from '@testing-library/dom' screen.findByRole('button', {name: /hellogg/})`, - }, - { - code: ` + }, + { + code: ` import { screen } from '@testing-library/dom' screen.findByRole('button', {otherProp: true, name: /hello/g})`, - errors: [ - { - messageId: 'noGlobalRegExpFlagInQuery', - line: 3, - column: 61, - }, - ], - output: ` + errors: [ + { + messageId: 'noGlobalRegExpFlagInQuery', + line: 3, + column: 61, + }, + ], + output: ` import { screen } from '@testing-library/dom' screen.findByRole('button', {otherProp: true, name: /hello/})`, - }, - { - code: ` + }, + { + code: ` const utils = render() utils.findByRole('button', {name: /hello/ig})`, - errors: [ - { - messageId: 'noGlobalRegExpFlagInQuery', - line: 3, - column: 43, - }, - ], - output: ` + errors: [ + { + messageId: 'noGlobalRegExpFlagInQuery', + line: 3, + column: 43, + }, + ], + output: ` const utils = render() utils.findByRole('button', {name: /hello/i})`, - }, - { - code: ` + }, + { + code: ` const {queryAllByLabelText} = render() queryAllByLabelText(/hello/gi)`, - errors: [ - { - messageId: 'noGlobalRegExpFlagInQuery', - line: 3, - column: 29, - }, - ], - output: ` + errors: [ + { + messageId: 'noGlobalRegExpFlagInQuery', + line: 3, + column: 29, + }, + ], + output: ` const {queryAllByLabelText} = render() queryAllByLabelText(/hello/i)`, - }, - { - code: ` + }, + { + code: ` import { within } from '@testing-library/dom' within(element).findByRole('button', {name: /hello/igm})`, - errors: [ - { - messageId: 'noGlobalRegExpFlagInQuery', - line: 3, - column: 53, - }, - ], - output: ` + errors: [ + { + messageId: 'noGlobalRegExpFlagInQuery', + line: 3, + column: 53, + }, + ], + output: ` import { within } from '@testing-library/dom' within(element).findByRole('button', {name: /hello/im})`, - }, - { - code: ` + }, + { + code: ` import { within } from '@testing-library/dom' within(element).queryAllByText(/hello/ig)`, - errors: [ - { - messageId: 'noGlobalRegExpFlagInQuery', - line: 3, - column: 40, - }, - ], - output: ` + errors: [ + { + messageId: 'noGlobalRegExpFlagInQuery', + line: 3, + column: 40, + }, + ], + output: ` import { within } from '@testing-library/dom' within(element).queryAllByText(/hello/i)`, - }, - ], + }, + ], }); diff --git a/tests/lib/rules/no-manual-cleanup.test.ts b/tests/lib/rules/no-manual-cleanup.test.ts index 73732736..0b22ce6a 100644 --- a/tests/lib/rules/no-manual-cleanup.test.ts +++ b/tests/lib/rules/no-manual-cleanup.test.ts @@ -4,236 +4,236 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const ALL_TESTING_LIBRARIES_WITH_CLEANUP = [ - '@testing-library/preact', - '@testing-library/react', - '@testing-library/svelte', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/preact', + '@testing-library/react', + '@testing-library/svelte', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - { - code: `import "@testing-library/react"`, - }, - { - code: `import { cleanup } from "test-utils"`, - }, - { - // Angular Testing Library doesn't have `cleanup` util - code: `import { cleanup } from "@testing-library/angular"`, - }, - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map((lib) => ({ - code: `import { render } from "${lib}"`, - })), - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map((lib) => ({ - code: `import utils from "${lib}"`, - })), - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map((lib) => ({ - code: ` + valid: [ + { + code: `import "@testing-library/react"`, + }, + { + code: `import { cleanup } from "test-utils"`, + }, + { + // Angular Testing Library doesn't have `cleanup` util + code: `import { cleanup } from "@testing-library/angular"`, + }, + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map((lib) => ({ + code: `import { render } from "${lib}"`, + })), + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map((lib) => ({ + code: `import utils from "${lib}"`, + })), + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map((lib) => ({ + code: ` import utils from "${lib}" utils.render() `, - })), - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map((lib) => ({ - code: `const { render, within } = require("${lib}")`, - })), - { - code: `const { cleanup } = require("any-other-library")`, - }, - { - code: ` + })), + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map((lib) => ({ + code: `const { render, within } = require("${lib}")`, + })), + { + code: `const { cleanup } = require("any-other-library")`, + }, + { + code: ` const utils = require("any-other-library") utils.cleanup() `, - }, - { - // For test coverage - code: `const utils = render("something")`, - }, - { - code: `const utils = require(moduleName)`, - }, - ], - invalid: [ - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( - (lib) => - ({ - code: `import { render, cleanup } from "${lib}"`, - errors: [ - { - line: 1, - column: 18, // error points to `cleanup` - messageId: 'noManualCleanup', - }, - ], - } as const) - ), - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( - (lib) => - ({ - // official testing-library packages should be reported with custom module setting - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { cleanup, render } from "${lib}"`, - errors: [ - { - line: 1, - column: 10, // error points to `cleanup` - messageId: 'noManualCleanup', - }, - ], - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + // For test coverage + code: `const utils = render("something")`, + }, + { + code: `const utils = require(moduleName)`, + }, + ], + invalid: [ + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( + (lib) => + ({ + code: `import { render, cleanup } from "${lib}"`, + errors: [ + { + line: 1, + column: 18, // error points to `cleanup` + messageId: 'noManualCleanup', + }, + ], + } as const) + ), + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( + (lib) => + ({ + // official testing-library packages should be reported with custom module setting + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `import { cleanup, render } from "${lib}"`, + errors: [ + { + line: 1, + column: 10, // error points to `cleanup` + messageId: 'noManualCleanup', + }, + ], + } as const) + ), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { render, cleanup } from 'test-utils' `, - errors: [{ line: 2, column: 26, messageId: 'noManualCleanup' }], - }, - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( - (lib) => - ({ - code: `import { cleanup as myCustomCleanup } from "${lib}"`, - errors: [ - { - line: 1, - column: 10, // error points to `cleanup` - messageId: 'noManualCleanup', - }, - ], - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [{ line: 2, column: 26, messageId: 'noManualCleanup' }], + }, + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( + (lib) => + ({ + code: `import { cleanup as myCustomCleanup } from "${lib}"`, + errors: [ + { + line: 1, + column: 10, // error points to `cleanup` + messageId: 'noManualCleanup', + }, + ], + } as const) + ), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { cleanup as myCustomCleanup } from 'test-utils' `, - errors: [{ line: 2, column: 18, messageId: 'noManualCleanup' }], - }, - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( - (lib) => - ({ - code: `import utils, { cleanup } from "${lib}"`, - errors: [ - { - line: 1, - column: 17, // error points to `cleanup` - messageId: 'noManualCleanup', - }, - ], - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [{ line: 2, column: 18, messageId: 'noManualCleanup' }], + }, + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( + (lib) => + ({ + code: `import utils, { cleanup } from "${lib}"`, + errors: [ + { + line: 1, + column: 17, // error points to `cleanup` + messageId: 'noManualCleanup', + }, + ], + } as const) + ), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import utils, { cleanup } from 'test-utils' `, - errors: [{ line: 2, column: 25, messageId: 'noManualCleanup' }], - }, - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( - (lib) => - ({ - code: ` + errors: [{ line: 2, column: 25, messageId: 'noManualCleanup' }], + }, + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( + (lib) => + ({ + code: ` import utils from "${lib}" afterEach(() => utils.cleanup()) `, - errors: [ - { - line: 3, - column: 31, - messageId: 'noManualCleanup', - }, - ], - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + line: 3, + column: 31, + messageId: 'noManualCleanup', + }, + ], + } as const) + ), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import utils from 'test-utils' afterEach(() => utils.cleanup()) `, - errors: [{ line: 3, column: 31, messageId: 'noManualCleanup' }], - }, - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( - (lib) => - ({ - code: ` + errors: [{ line: 3, column: 31, messageId: 'noManualCleanup' }], + }, + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( + (lib) => + ({ + code: ` import utils from "${lib}" afterEach(utils.cleanup) `, - errors: [ - { - line: 3, - column: 25, - messageId: 'noManualCleanup', - }, - ], - } as const) - ), - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( - (lib) => - ({ - code: `const { cleanup } = require("${lib}")`, - errors: [ - { - line: 1, - column: 9, // error points to `cleanup` - messageId: 'noManualCleanup', - }, - ], - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + line: 3, + column: 25, + messageId: 'noManualCleanup', + }, + ], + } as const) + ), + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( + (lib) => + ({ + code: `const { cleanup } = require("${lib}")`, + errors: [ + { + line: 1, + column: 9, // error points to `cleanup` + messageId: 'noManualCleanup', + }, + ], + } as const) + ), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` const { render, cleanup } = require('test-utils') `, - errors: [{ line: 2, column: 25, messageId: 'noManualCleanup' }], - }, - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( - (lib) => - ({ - code: ` + errors: [{ line: 2, column: 25, messageId: 'noManualCleanup' }], + }, + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( + (lib) => + ({ + code: ` const utils = require("${lib}") afterEach(() => utils.cleanup()) `, - errors: [ - { - line: 3, - column: 31, - messageId: 'noManualCleanup', - }, - ], - } as const) - ), - ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( - (lib) => - ({ - code: ` + errors: [ + { + line: 3, + column: 31, + messageId: 'noManualCleanup', + }, + ], + } as const) + ), + ...ALL_TESTING_LIBRARIES_WITH_CLEANUP.map( + (lib) => + ({ + code: ` const utils = require("${lib}") afterEach(utils.cleanup) `, - errors: [ - { - line: 3, - column: 25, - messageId: 'noManualCleanup', - }, - ], - } as const) - ), - ], + errors: [ + { + line: 3, + column: 25, + messageId: 'noManualCleanup', + }, + ], + } as const) + ), + ], }); diff --git a/tests/lib/rules/no-node-access.test.ts b/tests/lib/rules/no-node-access.test.ts index d08139a2..43981e21 100644 --- a/tests/lib/rules/no-node-access.test.ts +++ b/tests/lib/rules/no-node-access.test.ts @@ -8,59 +8,59 @@ const ruleTester = createRuleTester(); type ValidTestCase = TSESLint.ValidTestCase; const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap( - (testingFramework) => [ - { - code: ` + valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap( + (testingFramework) => [ + { + code: ` import { screen } from '${testingFramework}'; const buttonText = screen.getByText('submit'); `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; const { getByText } = screen const firstChild = getByText('submit'); expect(firstChild).toBeInTheDocument() `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; const firstChild = screen.getByText('submit'); expect(firstChild).toBeInTheDocument() `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; const { getByText } = screen; const button = getByRole('button'); expect(button).toHaveTextContent('submit'); `, - }, - { - code: ` + }, + { + code: ` import { render, within } from '${testingFramework}'; const { getByLabelText } = render(); const signInModal = getByLabelText('Sign In'); within(signInModal).getByPlaceholderText('Username'); `, - }, - { - code: ` + }, + { + code: ` // case: code not related to testing library at all ReactDOM.render( @@ -75,122 +75,122 @@ ruleTester.run(RULE_NAME, rule, { document.getElementById('root') ); `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: custom module set but not imported (aggressive reporting limited) const closestButton = document.getElementById('submit-btn').closest('button'); expect(closestButton).toBeInTheDocument(); `, - }, - { - code: ` + }, + { + code: ` // case: without importing TL (aggressive reporting skipped) const closestButton = document.getElementById('submit-btn') expect(closestButton).toBeInTheDocument(); `, - }, - { - options: [{ allowContainerFirstChild: true }], - code: ` + }, + { + options: [{ allowContainerFirstChild: true }], + code: ` import { render } from '${testingFramework}'; const { container } = render() expect(container.firstChild).toMatchSnapshot() `, - }, - ] - ), - invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + ] + ), + invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: importing from custom module (aggressive reporting limited) import 'test-utils'; const closestButton = document.getElementById('submit-btn') expect(closestButton).toBeInTheDocument(); `, - errors: [{ line: 4, column: 38, messageId: 'noNodeAccess' }], - }, - { - code: ` + errors: [{ line: 4, column: 38, messageId: 'noNodeAccess' }], + }, + { + code: ` import { screen } from '${testingFramework}'; const button = document.getElementById('submit-btn').closest('button'); `, - errors: [ - { - line: 4, - column: 33, - messageId: 'noNodeAccess', - }, - { - line: 4, - column: 62, - messageId: 'noNodeAccess', - }, - ], - }, - { - code: ` + errors: [ + { + line: 4, + column: 33, + messageId: 'noNodeAccess', + }, + { + line: 4, + column: 62, + messageId: 'noNodeAccess', + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; document.getElementById('submit-btn'); `, - errors: [ - { - line: 4, - column: 18, - messageId: 'noNodeAccess', - }, - ], - }, - { - code: ` + errors: [ + { + line: 4, + column: 18, + messageId: 'noNodeAccess', + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; screen.getByText('submit').closest('button'); `, - errors: [ - { - // error points to `closest` - line: 4, - column: 36, - messageId: 'noNodeAccess', - }, - ], - }, - { - code: ` + errors: [ + { + // error points to `closest` + line: 4, + column: 36, + messageId: 'noNodeAccess', + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; expect(screen.getByText('submit').closest('button').textContent).toBe('Submit'); `, - errors: [ - { - line: 4, - column: 43, - messageId: 'noNodeAccess', - }, - ], - }, - { - code: ` + errors: [ + { + line: 4, + column: 43, + messageId: 'noNodeAccess', + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { getByText } = render() getByText('submit').closest('button'); `, - errors: [{ line: 5, column: 29, messageId: 'noNodeAccess' }], - }, - { - code: ` + errors: [{ line: 5, column: 29, messageId: 'noNodeAccess' }], + }, + { + code: ` import { screen } from '${testingFramework}'; const buttons = screen.getAllByRole('button'); @@ -198,57 +198,57 @@ ruleTester.run(RULE_NAME, rule, { const button = buttons[2]; button.lastChild `, - errors: [ - { - // error points to `firstChild` - line: 5, - column: 35, - messageId: 'noNodeAccess', - }, - { - // error points to `lastChild` - line: 7, - column: 16, - messageId: 'noNodeAccess', - }, - ], - }, - { - code: ` + errors: [ + { + // error points to `firstChild` + line: 5, + column: 35, + messageId: 'noNodeAccess', + }, + { + // error points to `lastChild` + line: 7, + column: 16, + messageId: 'noNodeAccess', + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const buttonText = screen.getByText('submit'); const button = buttonText.closest('button'); `, - errors: [{ line: 5, column: 35, messageId: 'noNodeAccess' }], - }, - { - code: ` + errors: [{ line: 5, column: 35, messageId: 'noNodeAccess' }], + }, + { + code: ` import { render } from '${testingFramework}'; const { getByText } = render() const buttonText = getByText('submit'); const button = buttonText.closest('button'); `, - errors: [ - { - line: 6, - column: 35, - messageId: 'noNodeAccess', - }, - ], - }, - { - code: ` + errors: [ + { + line: 6, + column: 35, + messageId: 'noNodeAccess', + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { getByText } = render() const button = getByText('submit').closest('button'); `, - errors: [{ line: 5, column: 44, messageId: 'noNodeAccess' }], - }, - { - code: ` + errors: [{ line: 5, column: 44, messageId: 'noNodeAccess' }], + }, + { + code: ` import { screen } from '${testingFramework}'; function getExampleDOM() { @@ -268,17 +268,17 @@ ruleTester.run(RULE_NAME, rule, { const buttons = screen.getAllByRole(exampleDOM, 'button'); const buttonText = buttons[1].firstChild; `, - errors: [ - { - // error points to `firstChild` - line: 19, - column: 39, - messageId: 'noNodeAccess', - }, - ], - }, - { - code: ` + errors: [ + { + // error points to `firstChild` + line: 19, + column: 39, + messageId: 'noNodeAccess', + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; function getExampleDOM() { @@ -298,31 +298,31 @@ ruleTester.run(RULE_NAME, rule, { const submitButton = screen.getByText(exampleDOM, 'Submit'); const previousButton = submitButton.previousSibling; `, - errors: [ - { - // error points to `previousSibling` - line: 19, - column: 45, - messageId: 'noNodeAccess', - }, - ], - }, - { - code: ` + errors: [ + { + // error points to `previousSibling` + line: 19, + column: 45, + messageId: 'noNodeAccess', + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { container } = render() expect(container.firstChild).toMatchSnapshot() `, - errors: [ - { - // error points to `firstChild` - line: 6, - column: 26, - messageId: 'noNodeAccess', - }, - ], - }, - ]), + errors: [ + { + // error points to `firstChild` + line: 6, + column: 26, + messageId: 'noNodeAccess', + }, + ], + }, + ]), }); diff --git a/tests/lib/rules/no-promise-in-fire-event.test.ts b/tests/lib/rules/no-promise-in-fire-event.test.ts index 3808f60e..82800e3f 100644 --- a/tests/lib/rules/no-promise-in-fire-event.test.ts +++ b/tests/lib/rules/no-promise-in-fire-event.test.ts @@ -4,187 +4,187 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + valid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import {fireEvent} from '${testingFramework}'; fireEvent.click(screen.getByRole('button')) `, - }, - { - code: ` + }, + { + code: ` import {fireEvent} from '${testingFramework}'; fireEvent.click(queryByRole('button')) `, - }, - { - code: ` + }, + { + code: ` import {fireEvent} from '${testingFramework}'; fireEvent.click(someRef) `, - }, - { - code: ` + }, + { + code: ` import {fireEvent} from '${testingFramework}'; fireEvent.click(await screen.findByRole('button')) `, - }, - { - code: ` + }, + { + code: ` import {fireEvent} from '${testingFramework}' const elementPromise = screen.findByRole('button') const button = await elementPromise fireEvent.click(button) `, - }, - ]), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `// invalid usage but aggressive reporting opted-out + }, + ]), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `// invalid usage but aggressive reporting opted-out import { fireEvent } from 'somewhere-else' fireEvent.click(findByText('submit')) `, - }, - `// edge case for coverage: + }, + `// edge case for coverage: // valid use case without call expression // so there is no innermost function scope found test('edge case for no innermost function scope', () => { const click = fireEvent.click }) `, - `// edge case for coverage: + `// edge case for coverage: // new expression of something else than Promise fireEvent.click(new SomeElement()) `, - ], - invalid: [ - { - // aggressive reporting opted-in - code: `fireEvent.click(findByText('submit'))`, - errors: [ - { - messageId: 'noPromiseInFireEvent', - line: 1, - column: 17, - endColumn: 37, - }, - ], - }, - { - // aggressive reporting opted-in - code: `fireEvent.click(Promise())`, - errors: [ - { - messageId: 'noPromiseInFireEvent', - line: 1, - column: 17, - endColumn: 26, - }, - ], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + ], + invalid: [ + { + // aggressive reporting opted-in + code: `fireEvent.click(findByText('submit'))`, + errors: [ + { + messageId: 'noPromiseInFireEvent', + line: 1, + column: 17, + endColumn: 37, + }, + ], + }, + { + // aggressive reporting opted-in + code: `fireEvent.click(Promise())`, + errors: [ + { + messageId: 'noPromiseInFireEvent', + line: 1, + column: 17, + endColumn: 26, + }, + ], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import {fireEvent} from '${testingFramework}'; const promise = new Promise(); fireEvent.click(promise)`, - errors: [ - { - messageId: 'noPromiseInFireEvent', - line: 5, - column: 25, - endColumn: 32, - }, - ], - } as const, - { - code: ` + errors: [ + { + messageId: 'noPromiseInFireEvent', + line: 5, + column: 25, + endColumn: 32, + }, + ], + } as const, + { + code: ` import {fireEvent} from '${testingFramework}' const elementPromise = screen.findByRole('button') fireEvent.click(elementPromise)`, - errors: [ - { - messageId: 'noPromiseInFireEvent', - line: 5, - column: 25, - endColumn: 39, - }, - ], - } as const, - { - code: ` + errors: [ + { + messageId: 'noPromiseInFireEvent', + line: 5, + column: 25, + endColumn: 39, + }, + ], + } as const, + { + code: ` import {fireEvent} from '${testingFramework}'; fireEvent.click(screen.findByRole('button'))`, - errors: [ - { - messageId: 'noPromiseInFireEvent', - line: 4, - column: 25, - endColumn: 52, - }, - ], - } as const, - { - code: ` + errors: [ + { + messageId: 'noPromiseInFireEvent', + line: 4, + column: 25, + endColumn: 52, + }, + ], + } as const, + { + code: ` import {fireEvent} from '${testingFramework}'; fireEvent.click(findByText('submit'))`, - errors: [ - { - messageId: 'noPromiseInFireEvent', - line: 4, - column: 25, - endColumn: 45, - }, - ], - } as const, - { - code: ` + errors: [ + { + messageId: 'noPromiseInFireEvent', + line: 4, + column: 25, + endColumn: 45, + }, + ], + } as const, + { + code: ` import {fireEvent} from '${testingFramework}'; fireEvent.click(Promise('foo'))`, - errors: [ - { - messageId: 'noPromiseInFireEvent', - line: 4, - column: 25, - endColumn: 39, - }, - ], - } as const, - { - code: ` + errors: [ + { + messageId: 'noPromiseInFireEvent', + line: 4, + column: 25, + endColumn: 39, + }, + ], + } as const, + { + code: ` import {fireEvent} from '${testingFramework}'; fireEvent.click(new Promise('foo'))`, - errors: [ - { - messageId: 'noPromiseInFireEvent', - line: 4, - column: 25, - endColumn: 43, - }, - ], - } as const, - ]), - ], + errors: [ + { + messageId: 'noPromiseInFireEvent', + line: 4, + column: 25, + endColumn: 43, + }, + ], + } as const, + ]), + ], }); diff --git a/tests/lib/rules/no-render-in-setup.test.ts b/tests/lib/rules/no-render-in-setup.test.ts index 5c0ba486..0e66802e 100644 --- a/tests/lib/rules/no-render-in-setup.test.ts +++ b/tests/lib/rules/no-render-in-setup.test.ts @@ -5,16 +5,16 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ - code: ` + valid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ + code: ` import { render } from '${testingFramework}'; beforeAll(() => { @@ -29,47 +29,47 @@ ruleTester.run(RULE_NAME, rule, { render() }) `, - })), - // test config options - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + })), + // test config options + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { render } from '${testingFramework}'; beforeAll(() => { render(); }); `, - options: [{ allowTestingFrameworkSetupHook: 'beforeAll' }], - }, - { - code: ` + options: [{ allowTestingFrameworkSetupHook: 'beforeAll' }], + }, + { + code: ` import { render } from '${testingFramework}'; beforeEach(() => { render(); }); `, - options: [{ allowTestingFrameworkSetupHook: 'beforeEach' }], - }, - ]), - ...TESTING_FRAMEWORK_SETUP_HOOKS.map((setupHook) => ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + options: [{ allowTestingFrameworkSetupHook: 'beforeEach' }], + }, + ]), + ...TESTING_FRAMEWORK_SETUP_HOOKS.map((setupHook) => ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from 'imNoTestingLibrary'; ${setupHook}(() => { render() }) `, - })), - ...TESTING_FRAMEWORK_SETUP_HOOKS.map((allowedSetupHook) => { - const [disallowedHook] = TESTING_FRAMEWORK_SETUP_HOOKS.filter( - (setupHook) => setupHook !== allowedSetupHook - ); - return { - settings: { - 'testing-library/utils-module': 'test-utils', - 'testing-library/custom-renders': ['show', 'renderWithRedux'], - }, - code: ` + })), + ...TESTING_FRAMEWORK_SETUP_HOOKS.map((allowedSetupHook) => { + const [disallowedHook] = TESTING_FRAMEWORK_SETUP_HOOKS.filter( + (setupHook) => setupHook !== allowedSetupHook + ); + return { + settings: { + 'testing-library/utils-module': 'test-utils', + 'testing-library/custom-renders': ['show', 'renderWithRedux'], + }, + code: ` import utils from 'imNoTestingLibrary'; import { show } from '../test-utils'; ${allowedSetupHook}(() => { @@ -79,98 +79,98 @@ ruleTester.run(RULE_NAME, rule, { utils.render() }) `, - options: [ - { - allowTestingFrameworkSetupHook: allowedSetupHook, - }, - ], - }; - }), - ...TESTING_FRAMEWORK_SETUP_HOOKS.map((setupHook) => ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + options: [ + { + allowTestingFrameworkSetupHook: allowedSetupHook, + }, + ], + }; + }), + ...TESTING_FRAMEWORK_SETUP_HOOKS.map((setupHook) => ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` const { render } = require('imNoTestingLibrary') ${setupHook}(() => { render() }) `, - errors: [ - { - messageId: 'noRenderInSetup', - }, - ], - })), - ], + errors: [ + { + messageId: 'noRenderInSetup', + }, + ], + })), + ], - invalid: [ - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...TESTING_FRAMEWORK_SETUP_HOOKS.map( - (setupHook) => - ({ - code: ` + invalid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + ...TESTING_FRAMEWORK_SETUP_HOOKS.map( + (setupHook) => + ({ + code: ` import { render } from '${testingFramework}'; ${setupHook}(() => { render() }) `, - errors: [ - { - line: 4, - column: 11, - messageId: 'noRenderInSetup', - }, - ], - } as const) - ), - ...TESTING_FRAMEWORK_SETUP_HOOKS.map( - (setupHook) => - ({ - code: ` + errors: [ + { + line: 4, + column: 11, + messageId: 'noRenderInSetup', + }, + ], + } as const) + ), + ...TESTING_FRAMEWORK_SETUP_HOOKS.map( + (setupHook) => + ({ + code: ` import { render } from '${testingFramework}'; ${setupHook}(function() { render() }) `, - errors: [ - { - line: 4, - column: 11, - messageId: 'noRenderInSetup', - }, - ], - } as const) - ), - ]), - // custom render function - ...TESTING_FRAMEWORK_SETUP_HOOKS.map( - (setupHook) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - 'testing-library/custom-renders': ['show', 'renderWithRedux'], - }, - code: ` + errors: [ + { + line: 4, + column: 11, + messageId: 'noRenderInSetup', + }, + ], + } as const) + ), + ]), + // custom render function + ...TESTING_FRAMEWORK_SETUP_HOOKS.map( + (setupHook) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + 'testing-library/custom-renders': ['show', 'renderWithRedux'], + }, + code: ` import { show } from '../test-utils'; ${setupHook}(() => { show() }) `, - errors: [ - { - line: 5, - column: 11, - messageId: 'noRenderInSetup', - }, - ], - } as const) - ), - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...TESTING_FRAMEWORK_SETUP_HOOKS.map( - (setupHook) => - ({ - code: `// call render within a wrapper function + errors: [ + { + line: 5, + column: 11, + messageId: 'noRenderInSetup', + }, + ], + } as const) + ), + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + ...TESTING_FRAMEWORK_SETUP_HOOKS.map( + (setupHook) => + ({ + code: `// call render within a wrapper function import { render } from '${testingFramework}'; const wrapper = () => render() @@ -179,64 +179,64 @@ ruleTester.run(RULE_NAME, rule, { wrapper() }) `, - errors: [ - { - line: 7, - column: 9, - messageId: 'noRenderInSetup', - }, - ], - } as const) - ), - ...TESTING_FRAMEWORK_SETUP_HOOKS.map((allowedSetupHook) => { - const [disallowedHook] = TESTING_FRAMEWORK_SETUP_HOOKS.filter( - (setupHook) => setupHook !== allowedSetupHook - ); - return { - code: ` + errors: [ + { + line: 7, + column: 9, + messageId: 'noRenderInSetup', + }, + ], + } as const) + ), + ...TESTING_FRAMEWORK_SETUP_HOOKS.map((allowedSetupHook) => { + const [disallowedHook] = TESTING_FRAMEWORK_SETUP_HOOKS.filter( + (setupHook) => setupHook !== allowedSetupHook + ); + return { + code: ` import { render } from '${testingFramework}'; ${disallowedHook}(() => { render() }) `, - options: [ - { - allowTestingFrameworkSetupHook: allowedSetupHook, - }, - ], - errors: [ - { - line: 4, - column: 13, - messageId: 'noRenderInSetup', - }, - ], - } as const; - }), - ...TESTING_FRAMEWORK_SETUP_HOOKS.map( - (setupHook) => - ({ - code: ` + options: [ + { + allowTestingFrameworkSetupHook: allowedSetupHook, + }, + ], + errors: [ + { + line: 4, + column: 13, + messageId: 'noRenderInSetup', + }, + ], + } as const; + }), + ...TESTING_FRAMEWORK_SETUP_HOOKS.map( + (setupHook) => + ({ + code: ` import * as testingLibrary from '${testingFramework}'; ${setupHook}(() => { testingLibrary.render() }) `, - errors: [ - { - line: 4, - column: 26, - messageId: 'noRenderInSetup', - }, - ], - } as const) - ), - ]), - ...TESTING_FRAMEWORK_SETUP_HOOKS.map( - (setupHook) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + line: 4, + column: 26, + messageId: 'noRenderInSetup', + }, + ], + } as const) + ), + ]), + ...TESTING_FRAMEWORK_SETUP_HOOKS.map( + (setupHook) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from 'imNoTestingLibrary'; import * as testUtils from '../test-utils'; ${setupHook}(() => { @@ -246,35 +246,35 @@ ruleTester.run(RULE_NAME, rule, { render() }) `, - errors: [ - { - line: 5, - column: 21, - messageId: 'noRenderInSetup', - }, - ], - } as const) - ), - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => - TESTING_FRAMEWORK_SETUP_HOOKS.map( - (setupHook) => - ({ - code: ` + errors: [ + { + line: 5, + column: 21, + messageId: 'noRenderInSetup', + }, + ], + } as const) + ), + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => + TESTING_FRAMEWORK_SETUP_HOOKS.map( + (setupHook) => + ({ + code: ` const { render } = require('${testingFramework}') ${setupHook}(() => { render() }) `, - errors: [ - { - line: 5, - column: 11, - messageId: 'noRenderInSetup', - }, - ], - } as const) - ) - ), - ], + errors: [ + { + line: 5, + column: 11, + messageId: 'noRenderInSetup', + }, + ], + } as const) + ) + ), + ], }); diff --git a/tests/lib/rules/no-unnecessary-act.test.ts b/tests/lib/rules/no-unnecessary-act.test.ts index 58fa097c..38110fee 100644 --- a/tests/lib/rules/no-unnecessary-act.test.ts +++ b/tests/lib/rules/no-unnecessary-act.test.ts @@ -1,9 +1,9 @@ import type { TSESLint } from '@typescript-eslint/utils'; import rule, { - MessageIds, - Options, - RULE_NAME, + MessageIds, + Options, + RULE_NAME, } from '../../../lib/rules/no-unnecessary-act'; import { createRuleTester } from '../test-utils'; @@ -14,17 +14,17 @@ type InvalidTestCase = TSESLint.InvalidTestCase; type TestCase = InvalidTestCase | ValidTestCase; const addOptions = ( - array: T[], - options?: Options[number] + array: T[], + options?: Options[number] ): T[] => - array.map((testCase) => ({ - ...testCase, - options: [options], - })); + array.map((testCase) => ({ + ...testCase, + options: [options], + })); const disableStrict = (array: T[]): T[] => - addOptions(array, { isStrict: false }); + addOptions(array, { isStrict: false }); const enableStrict = (array: T[]): T[] => - addOptions(array, { isStrict: true }); + addOptions(array, { isStrict: true }); /** * - AGR stands for Aggressive Reporting @@ -33,13 +33,13 @@ const enableStrict = (array: T[]): T[] => * - RTU stands for React Test Utils (react-dom/test-utils) */ const SUPPORTED_TESTING_FRAMEWORKS = [ - ['@testing-library/react', 'RTL'], - ['@marko/testing-library', 'Marko TL'], + ['@testing-library/react', 'RTL'], + ['@marko/testing-library', 'Marko TL'], ]; const validNonStrictTestCases: ValidTestCase[] = [ - { - code: `// case: RTL act wrapping both RTL and non-RTL calls + { + code: `// case: RTL act wrapping both RTL and non-RTL calls import { act, render, waitFor } from '@testing-library/react' test('valid case', async () => { @@ -59,12 +59,12 @@ const validNonStrictTestCases: ValidTestCase[] = [ }); }); `, - }, + }, ]; const validTestCases: ValidTestCase[] = [ - ...SUPPORTED_TESTING_FRAMEWORKS.map(([testingFramework, shortName]) => ({ - code: `// case: ${shortName} act wrapping non-${shortName} calls + ...SUPPORTED_TESTING_FRAMEWORKS.map(([testingFramework, shortName]) => ({ + code: `// case: ${shortName} act wrapping non-${shortName} calls import { act } from '${testingFramework}' test('valid case', async () => { @@ -123,9 +123,9 @@ const validTestCases: ValidTestCase[] = [ act(stuffThatDoesNotUseRTL().then(() => {})) }); `, - })), - { - code: `// case: RTU act wrapping non-RTL + })), + { + code: `// case: RTU act wrapping non-RTL import { act } from 'react-dom/test-utils' test('valid case', async () => { @@ -148,12 +148,12 @@ const validTestCases: ValidTestCase[] = [ act(() => stuffThatDoesNotUseRTL()); }); `, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map(([testingFramework, shortName]) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `// case: ${shortName} act wrapping non-${shortName} - AGR disabled + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map(([testingFramework, shortName]) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `// case: ${shortName} act wrapping non-${shortName} - AGR disabled import { act } from '${testingFramework}' import { waitFor } from 'somewhere-else' @@ -177,13 +177,13 @@ const validTestCases: ValidTestCase[] = [ act(() => waitFor()); }); `, - })), + })), - ...SUPPORTED_TESTING_FRAMEWORKS.map(([testingFramework, shortName]) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `// case: non-${shortName} act wrapping ${shortName} - AGR disabled + ...SUPPORTED_TESTING_FRAMEWORKS.map(([testingFramework, shortName]) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `// case: non-${shortName} act wrapping ${shortName} - AGR disabled import { act } from 'somewhere-else' import { waitFor } from '${testingFramework}' @@ -211,13 +211,13 @@ const validTestCases: ValidTestCase[] = [ act(function() {}) }); `, - })), + })), ]; const invalidStrictTestCases: InvalidTestCase[] = - SUPPORTED_TESTING_FRAMEWORKS.flatMap(([testingFramework, shortName]) => [ - { - code: `// case: ${shortName} act wrapping both ${shortName} and non-${shortName} calls with strict option + SUPPORTED_TESTING_FRAMEWORKS.flatMap(([testingFramework, shortName]) => [ + { + code: `// case: ${shortName} act wrapping both ${shortName} and non-${shortName} calls with strict option import { act, render } from '${testingFramework}' await act(async () => { @@ -229,26 +229,26 @@ const invalidStrictTestCases: InvalidTestCase[] = flushPromises() }) `, - errors: [ - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 4, - column: 13, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 8, - column: 7, - }, - ], - }, - ]); + errors: [ + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 4, + column: 13, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 8, + column: 7, + }, + ], + }, + ]); const invalidTestCases: InvalidTestCase[] = [ - ...SUPPORTED_TESTING_FRAMEWORKS.map( - ([testingFramework, shortName]) => - ({ - code: `// case: ${shortName} act wrapping ${shortName} calls - callbacks with body (BlockStatement) + ...SUPPORTED_TESTING_FRAMEWORKS.map( + ([testingFramework, shortName]) => + ({ + code: `// case: ${shortName} act wrapping ${shortName} calls - callbacks with body (BlockStatement) import { act, fireEvent, screen, render, waitFor, waitForElementToBeRemoved } from '${testingFramework}' import userEvent from '@testing-library/user-event' @@ -288,55 +288,55 @@ const invalidTestCases: InvalidTestCase[] = [ }); }); `, - errors: [ - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 6, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 10, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 14, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 18, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 22, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 26, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 30, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 34, - column: 9, - }, - ], - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `// case: RTL act wrapping RTL calls - callbacks with body (BlockStatement) - AGR disabled + errors: [ + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 6, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 10, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 14, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 18, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 22, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 26, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 30, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 34, + column: 9, + }, + ], + } as const) + ), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `// case: RTL act wrapping RTL calls - callbacks with body (BlockStatement) - AGR disabled import { act, fireEvent, screen, render, waitFor, waitForElementToBeRemoved } from 'test-utils' import userEvent from '@testing-library/user-event' @@ -376,47 +376,47 @@ const invalidTestCases: InvalidTestCase[] = [ }); }); `, - errors: [ - { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 6, column: 9 }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 10, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 14, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 18, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 22, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 26, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 30, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 34, - column: 9, - }, - ], - }, - { - code: `// case: RTL act wrapping RTL calls - callbacks with return + errors: [ + { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 6, column: 9 }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 10, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 14, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 18, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 22, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 26, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 30, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 34, + column: 9, + }, + ], + }, + { + code: `// case: RTL act wrapping RTL calls - callbacks with return import { act, fireEvent, screen, render, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' @@ -463,90 +463,90 @@ const invalidTestCases: InvalidTestCase[] = [ }).then(() => {}) }); `, - errors: [ - { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 6, column: 9 }, - { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 7, column: 9 }, - { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 8, column: 9 }, - { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 9, column: 9 }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 10, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 11, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 12, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 13, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 14, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 16, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 19, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 22, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 25, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 28, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 31, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 34, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 37, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 40, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 43, - column: 9, - }, - ], - }, - { - code: `// case: RTL act wrapping empty callback + errors: [ + { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 6, column: 9 }, + { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 7, column: 9 }, + { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 8, column: 9 }, + { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 9, column: 9 }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 10, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 11, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 12, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 13, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 14, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 16, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 19, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 22, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 25, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 28, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 31, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 34, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 37, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 40, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 43, + column: 9, + }, + ], + }, + { + code: `// case: RTL act wrapping empty callback import { act } from '@testing-library/react' test('invalid case', async () => { @@ -556,18 +556,18 @@ const invalidTestCases: InvalidTestCase[] = [ act(function () {}) }) `, - errors: [ - { messageId: 'noUnnecessaryActEmptyFunction', line: 5, column: 15 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 6, column: 9 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 7, column: 15 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 9 }, - ], - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `// case: RTL act wrapping empty callback - require version + errors: [ + { messageId: 'noUnnecessaryActEmptyFunction', line: 5, column: 15 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 6, column: 9 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 7, column: 15 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 9 }, + ], + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `// case: RTL act wrapping empty callback - require version const { act } = require('@testing-library/react'); test('invalid case', async () => { @@ -578,21 +578,21 @@ const invalidTestCases: InvalidTestCase[] = [ act(function () {}).then(() => {}) }) `, - errors: [ - { messageId: 'noUnnecessaryActEmptyFunction', line: 5, column: 15 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 6, column: 9 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 7, column: 15 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 9 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 9, column: 9 }, - ], - }, - - // cases for act related to React Test Utils - { - settings: { - 'testing-library/utils-module': 'custom-testing-module', - }, - code: `// case: RTU act wrapping RTL calls - callbacks with body (BlockStatement) + errors: [ + { messageId: 'noUnnecessaryActEmptyFunction', line: 5, column: 15 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 6, column: 9 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 7, column: 15 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 9 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 9, column: 9 }, + ], + }, + + // cases for act related to React Test Utils + { + settings: { + 'testing-library/utils-module': 'custom-testing-module', + }, + code: `// case: RTU act wrapping RTL calls - callbacks with body (BlockStatement) import { act } from 'react-dom/test-utils'; import { fireEvent, screen, render, waitFor, waitForElementToBeRemoved } from 'custom-testing-module' import userEvent from '@testing-library/user-event' @@ -633,50 +633,50 @@ const invalidTestCases: InvalidTestCase[] = [ }); }); `, - errors: [ - { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 7, column: 9 }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 11, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 15, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 19, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 23, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 27, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 31, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 35, - column: 9, - }, - ], - }, - { - settings: { - 'testing-library/utils-module': 'custom-testing-module', - }, - code: `// case: RTU act wrapping RTL calls - callbacks with return + errors: [ + { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 7, column: 9 }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 11, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 15, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 19, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 23, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 27, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 31, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 35, + column: 9, + }, + ], + }, + { + settings: { + 'testing-library/utils-module': 'custom-testing-module', + }, + code: `// case: RTU act wrapping RTL calls - callbacks with return import { act } from 'react-dom/test-utils'; import { fireEvent, screen, render, waitFor } from 'custom-testing-module' import userEvent from '@testing-library/user-event' @@ -721,92 +721,92 @@ const invalidTestCases: InvalidTestCase[] = [ }); }); `, - errors: [ - { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 7, column: 9 }, - { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 8, column: 9 }, - { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 9, column: 9 }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 10, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 11, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 12, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 13, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 14, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 15, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 17, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 20, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 23, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 26, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 29, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 32, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 35, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 38, - column: 9, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 41, - column: 15, - }, - ], - }, - { - settings: { - 'testing-library/utils-module': 'off', - }, - code: `// case: RTU act wrapping empty callback + errors: [ + { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 7, column: 9 }, + { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 8, column: 9 }, + { messageId: 'noUnnecessaryActTestingLibraryUtil', line: 9, column: 9 }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 10, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 11, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 12, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 13, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 14, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 15, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 17, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 20, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 23, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 26, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 29, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 32, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 35, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 38, + column: 9, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 41, + column: 15, + }, + ], + }, + { + settings: { + 'testing-library/utils-module': 'off', + }, + code: `// case: RTU act wrapping empty callback import { act } from 'react-dom/test-utils'; import { render } from '@testing-library/react' @@ -818,18 +818,18 @@ const invalidTestCases: InvalidTestCase[] = [ act(function () {}); }); `, - errors: [ - { messageId: 'noUnnecessaryActEmptyFunction', line: 7, column: 15 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 9 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 9, column: 15 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 10, column: 9 }, - ], - }, - { - settings: { - 'testing-library/utils-module': 'off', - }, - code: `// case: RTU act wrapping empty callback - require version + errors: [ + { messageId: 'noUnnecessaryActEmptyFunction', line: 7, column: 15 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 9 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 9, column: 15 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 10, column: 9 }, + ], + }, + { + settings: { + 'testing-library/utils-module': 'off', + }, + code: `// case: RTU act wrapping empty callback - require version const { act } = require('react-dom/test-utils'); const { render } = require('@testing-library/react'); @@ -841,20 +841,20 @@ const invalidTestCases: InvalidTestCase[] = [ act(function () {}); }) `, - errors: [ - { messageId: 'noUnnecessaryActEmptyFunction', line: 7, column: 15 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 9 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 9, column: 15 }, - { messageId: 'noUnnecessaryActEmptyFunction', line: 10, column: 9 }, - ], - }, - - { - settings: { - 'testing-library/utils-module': 'custom-testing-module', - 'testing-library/custom-renders': 'off', - }, - code: `// case: mixed scenarios - AGR disabled + errors: [ + { messageId: 'noUnnecessaryActEmptyFunction', line: 7, column: 15 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 9 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 9, column: 15 }, + { messageId: 'noUnnecessaryActEmptyFunction', line: 10, column: 9 }, + ], + }, + + { + settings: { + 'testing-library/utils-module': 'custom-testing-module', + 'testing-library/custom-renders': 'off', + }, + code: `// case: mixed scenarios - AGR disabled import * as ReactTestUtils from 'react-dom/test-utils'; import { act as renamedAct, fireEvent, screen as renamedScreen, render, waitFor } from 'custom-testing-module' import userEvent from '@testing-library/user-event' @@ -873,36 +873,36 @@ const invalidTestCases: InvalidTestCase[] = [ act(function() { return renamedScreen.getByText('foo') }) }); `, - errors: [ - { messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 24 }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 9, - column: 30, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 10, - column: 15, - }, - { - messageId: 'noUnnecessaryActTestingLibraryUtil', - line: 11, - column: 9, - }, - ], - }, + errors: [ + { messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 24 }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 9, + column: 30, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 10, + column: 15, + }, + { + messageId: 'noUnnecessaryActTestingLibraryUtil', + line: 11, + column: 9, + }, + ], + }, ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - ...validTestCases, - ...disableStrict(validNonStrictTestCases), - ...disableStrict(validTestCases), - ], - invalid: [ - ...invalidTestCases, - ...enableStrict(invalidStrictTestCases), - ...disableStrict(invalidTestCases), - ], + valid: [ + ...validTestCases, + ...disableStrict(validNonStrictTestCases), + ...disableStrict(validTestCases), + ], + invalid: [ + ...invalidTestCases, + ...enableStrict(invalidStrictTestCases), + ...disableStrict(invalidTestCases), + ], }); diff --git a/tests/lib/rules/no-wait-for-empty-callback.test.ts b/tests/lib/rules/no-wait-for-empty-callback.test.ts index b29ac0e7..ed65bad6 100644 --- a/tests/lib/rules/no-wait-for-empty-callback.test.ts +++ b/tests/lib/rules/no-wait-for-empty-callback.test.ts @@ -5,238 +5,238 @@ 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', + '@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}(() => { + valid: [ + ...ALL_WAIT_METHODS.map((m) => ({ + code: `${m}(() => { screen.getByText(/submit/i) })`, - })), - ...ALL_WAIT_METHODS.map((m) => ({ - code: `${m}(function() { + })), + ...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: ` + })), + { + 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: ` + }, + ...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: ` + 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: ` + 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: ` + 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) - ), + 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() { + ...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: 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() { + 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) - ), + 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) - ), - ], + ...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/no-wait-for-multiple-assertions.test.ts b/tests/lib/rules/no-wait-for-multiple-assertions.test.ts index c4c3edd6..8c3a3fec 100644 --- a/tests/lib/rules/no-wait-for-multiple-assertions.test.ts +++ b/tests/lib/rules/no-wait-for-multiple-assertions.test.ts @@ -1,45 +1,45 @@ import rule, { - RULE_NAME, + RULE_NAME, } from '../../../lib/rules/no-wait-for-multiple-assertions'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - { - code: ` + valid: [ + { + code: ` await waitFor(() => expect(a).toEqual('a')) `, - }, - { - code: ` + }, + { + code: ` await waitFor(function() { expect(a).toEqual('a') }) `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// Aggressive Reporting disabled - module imported not matching + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// Aggressive Reporting disabled - module imported not matching import { waitFor } from 'somewhere-else' await waitFor(() => { expect(a).toEqual('a') expect(b).toEqual('b') }) `, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// Aggressive Reporting disabled - waitFor renamed + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// Aggressive Reporting disabled - waitFor renamed import { waitFor as renamedWaitFor } from '${testingFramework}' import { waitFor } from 'somewhere-else' await waitFor(() => { @@ -47,113 +47,113 @@ ruleTester.run(RULE_NAME, rule, { expect(b).toEqual('b') }) `, - })), - // this needs to be check by other rule - { - code: ` + })), + // this needs to be check by other rule + { + code: ` await waitFor(() => { fireEvent.keyDown(input, {key: 'ArrowDown'}) expect(b).toEqual('b') }) `, - }, - { - code: ` + }, + { + code: ` await waitFor(function() { fireEvent.keyDown(input, {key: 'ArrowDown'}) expect(b).toEqual('b') }) `, - }, - { - code: ` + }, + { + code: ` await waitFor(() => { console.log('testing-library') expect(b).toEqual('b') }) `, - }, - { - code: ` + }, + { + code: ` await waitFor(function() { console.log('testing-library') expect(b).toEqual('b') }) `, - }, - { - code: ` + }, + { + code: ` await waitFor(() => {}) `, - }, - { - code: ` + }, + { + code: ` await waitFor(function() {}) `, - }, - { - code: ` + }, + { + code: ` await waitFor(() => { // testing }) `, - }, - ], - invalid: [ - { - code: ` + }, + ], + invalid: [ + { + code: ` await waitFor(() => { expect(a).toEqual('a') expect(b).toEqual('b') }) `, - errors: [ - { line: 4, column: 11, messageId: 'noWaitForMultipleAssertion' }, - ], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// Aggressive Reporting disabled + errors: [ + { line: 4, column: 11, messageId: 'noWaitForMultipleAssertion' }, + ], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// Aggressive Reporting disabled import { waitFor } from '${testingFramework}' await waitFor(() => { expect(a).toEqual('a') expect(b).toEqual('b') }) `, - errors: [ - { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, - ], - } as const) - ), - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// Aggressive Reporting disabled + errors: [ + { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, + ], + } as const) + ), + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// Aggressive Reporting disabled import { waitFor as renamedWaitFor } from 'test-utils' await renamedWaitFor(() => { expect(a).toEqual('a') expect(b).toEqual('b') }) `, - errors: [ - { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, - ], - }, - { - code: ` + errors: [ + { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, + ], + }, + { + code: ` await waitFor(() => { expect(a).toEqual('a') console.log('testing-library') expect(b).toEqual('b') }) `, - errors: [ - { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, - ], - }, - { - code: ` + errors: [ + { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, + ], + }, + { + code: ` test('should whatever', async () => { await waitFor(() => { expect(a).toEqual('a') @@ -162,24 +162,24 @@ ruleTester.run(RULE_NAME, rule, { }) }) `, - errors: [ - { line: 6, column: 13, messageId: 'noWaitForMultipleAssertion' }, - ], - }, - { - code: ` + errors: [ + { line: 6, column: 13, messageId: 'noWaitForMultipleAssertion' }, + ], + }, + { + code: ` await waitFor(async () => { expect(a).toEqual('a') await somethingAsync() expect(b).toEqual('b') }) `, - errors: [ - { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, - ], - }, - { - code: ` + errors: [ + { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, + ], + }, + { + code: ` await waitFor(function() { expect(a).toEqual('a') expect(b).toEqual('b') @@ -187,35 +187,35 @@ ruleTester.run(RULE_NAME, rule, { expect(d).toEqual('d') }) `, - errors: [ - { line: 4, column: 11, messageId: 'noWaitForMultipleAssertion' }, - { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, - { line: 6, column: 11, messageId: 'noWaitForMultipleAssertion' }, - ], - }, - { - code: ` + errors: [ + { line: 4, column: 11, messageId: 'noWaitForMultipleAssertion' }, + { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, + { line: 6, column: 11, messageId: 'noWaitForMultipleAssertion' }, + ], + }, + { + code: ` await waitFor(function() { expect(a).toEqual('a') console.log('testing-library') expect(b).toEqual('b') }) `, - errors: [ - { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, - ], - }, - { - code: ` + errors: [ + { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, + ], + }, + { + code: ` await waitFor(async function() { expect(a).toEqual('a') const el = await somethingAsync() expect(b).toEqual('b') }) `, - errors: [ - { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, - ], - }, - ], + errors: [ + { line: 5, column: 11, messageId: 'noWaitForMultipleAssertion' }, + ], + }, + ], }); diff --git a/tests/lib/rules/no-wait-for-side-effects.test.ts b/tests/lib/rules/no-wait-for-side-effects.test.ts index ef1542d4..2d536d51 100644 --- a/tests/lib/rules/no-wait-for-side-effects.test.ts +++ b/tests/lib/rules/no-wait-for-side-effects.test.ts @@ -4,116 +4,116 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + valid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => expect(a).toEqual('a')) `, - }, - { - code: ` + }, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { expect(a).toEqual('a') }) `, - }, - { - code: ` + }, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { console.log('testing-library') expect(b).toEqual('b') }) `, - }, - { - code: ` + }, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { console.log('testing-library') expect(b).toEqual('b') }) `, - }, - { - code: ` + }, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => {}) `, - }, - { - code: ` + }, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() {}) `, - }, - { - code: ` + }, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { // testing }) `, - }, - { - code: ` + }, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { // testing }) `, - }, - { - code: ` + }, + { + code: ` import { waitFor } from '${testingFramework}'; fireEvent.keyDown(input, {key: 'ArrowDown'}) await waitFor(() => { expect(b).toEqual('b') }) `, - }, - { - code: ` + }, + { + code: ` import { waitFor } from '${testingFramework}'; fireEvent.keyDown(input, {key: 'ArrowDown'}) await waitFor(function() { expect(b).toEqual('b') }) `, - }, - { - code: ` + }, + { + code: ` import { waitFor } from '${testingFramework}'; userEvent.click(button) await waitFor(function() { expect(b).toEqual('b') }) `, - }, - ]), - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + ]), + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'somewhere-else'; await waitFor(function() { fireEvent.keyDown(input, {key: 'ArrowDown'}) expect(b).toEqual('b') }) `, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ - code: ` + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ + code: ` import { waitFor } from '${testingFramework}'; anotherFunction(() => { @@ -127,19 +127,19 @@ ruleTester.run(RULE_NAME, rule, { expect(b).toEqual('b') }); `, - })), - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + })), + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'somewhere-else'; await waitFor(() => { fireEvent.keyDown(input, {key: 'ArrowDown'}) }) `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor as renamedWaitFor, fireEvent } from 'test-utils'; import { waitFor, userEvent } from 'somewhere-else'; @@ -148,10 +148,10 @@ ruleTester.run(RULE_NAME, rule, { userEvent.click(button) }) `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor, fireEvent as renamedFireEvent, userEvent as renamedUserEvent } from 'test-utils'; import { fireEvent, userEvent } from 'somewhere-else'; @@ -160,224 +160,224 @@ ruleTester.run(RULE_NAME, rule, { userEvent.click(button) }) `, - }, - { - code: `// weird case to cover 100% coverage + }, + { + code: `// weird case to cover 100% coverage await waitFor(() => { const click = firEvent['click'] }) `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'somewhere-else'; await waitFor(() => fireEvent.keyDown(input, {key: 'ArrowDown'})) `, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + ...SUPPORTED_TESTING_FRAMEWORKS.map((testingFramework) => ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'somewhere-else'; import { userEvent } from '${testingFramework}'; await waitFor(() => userEvent.click(button)) `, - })), - { - settings: { 'testing-library/utils-module': '~/test-utils' }, - code: ` + })), + { + settings: { 'testing-library/utils-module': '~/test-utils' }, + code: ` import { waitFor, userEvent } from '~/test-utils'; await waitFor(() => userEvent.click(button)) `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'somewhere-else'; await waitFor(() => render()) `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'somewhere-else'; await waitFor(() => { const { container } = render() }) `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'somewhere-else'; const { rerender } = render() await waitFor(() => { rerender() }) `, - }, - { - settings: { 'testing-library/utils-module': '~/test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': '~/test-utils' }, + code: ` import { waitFor } from '~/test-utils'; import { render } from 'somewhere-else'; await waitFor(() => render()) `, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - settings: { 'testing-library/utils-module': '~/test-utils' }, - code: ` + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + settings: { 'testing-library/utils-module': '~/test-utils' }, + code: ` import { waitFor } from '${testingFramework}'; import { render } from 'somewhere-else'; await waitFor(() => render()) `, - }, - { - settings: { 'testing-library/custom-renders': ['renderHelper'] }, - code: ` + }, + { + settings: { 'testing-library/custom-renders': ['renderHelper'] }, + code: ` import { waitFor } from '${testingFramework}'; import { renderWrapper } from 'somewhere-else'; await waitFor(() => renderWrapper()) `, - }, - { - settings: { 'testing-library/custom-renders': ['renderHelper'] }, - code: ` + }, + { + settings: { 'testing-library/custom-renders': ['renderHelper'] }, + code: ` import { waitFor } from '${testingFramework}'; import { renderWrapper } from 'somewhere-else'; await waitFor(() => { renderWrapper() }) `, - }, - { - settings: { 'testing-library/custom-renders': ['renderHelper'] }, - code: ` + }, + { + settings: { 'testing-library/custom-renders': ['renderHelper'] }, + code: ` import { waitFor } from '${testingFramework}'; import { renderWrapper } from 'somewhere-else'; await waitFor(() => { const { container } = renderWrapper() }) `, - }, - ]), - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + ]), + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'somewhere-else'; await waitFor(() => { render() }) `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'test-utils'; import { render } from 'somewhere-else'; await waitFor(() => { render() }) `, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - settings: { 'testing-library/custom-renders': ['renderHelper'] }, - code: ` + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + settings: { 'testing-library/custom-renders': ['renderHelper'] }, + code: ` import { waitFor } from '${testingFramework}'; import { renderWrapper } from 'somewhere-else'; await waitFor(() => { renderWrapper() }) `, - }, - { - settings: { 'testing-library/custom-renders': ['renderHelper'] }, - code: ` + }, + { + settings: { 'testing-library/custom-renders': ['renderHelper'] }, + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => result = renderWrapper()) `, - }, - ]), - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + ]), + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'test-utils'; import { render } from 'somewhere-else'; await waitFor(() => result = render()) `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { waitFor } from 'somewhere-else'; await waitFor(() => result = render()) `, - }, - ], - invalid: [ - // render - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + }, + ], + invalid: [ + // render + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => render()) `, - errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { render() }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { const { container } = renderHelper() }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - settings: { 'testing-library/custom-renders': ['renderHelper'] }, - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + settings: { 'testing-library/custom-renders': ['renderHelper'] }, + code: ` import { waitFor } from '${testingFramework}'; import { renderHelper } from 'somewhere-else'; await waitFor(() => renderHelper()) `, - errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - settings: { 'testing-library/custom-renders': ['renderHelper'] }, - code: ` + errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + settings: { 'testing-library/custom-renders': ['renderHelper'] }, + code: ` import { waitFor } from '${testingFramework}'; import { renderHelper } from 'somewhere-else'; await waitFor(() => { renderHelper() }) `, - errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - settings: { 'testing-library/custom-renders': ['renderHelper'] }, - code: ` + errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + settings: { 'testing-library/custom-renders': ['renderHelper'] }, + code: ` import { waitFor } from '${testingFramework}'; import { renderHelper } from 'somewhere-else'; await waitFor(() => { const { container } = renderHelper() }) `, - errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - settings: { 'testing-library/custom-renders': ['renderHelper'] }, - code: ` + errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + settings: { 'testing-library/custom-renders': ['renderHelper'] }, + code: ` import { waitFor } from '${testingFramework}'; import { renderHelper } from 'somewhere-else'; let container; @@ -385,348 +385,348 @@ ruleTester.run(RULE_NAME, rule, { ({ container } = renderHelper()) }) `, - errors: [{ line: 6, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 6, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => result = render()) `, - errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => (a = 5, result = render())) `, - errors: [{ line: 3, column: 30, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 3, column: 30, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; const { rerender } = render() await waitFor(() => rerender()) `, - errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor, render } from '${testingFramework}'; await waitFor(() => render()) `, - errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; const { rerender } = render() await waitFor(() => rerender()) `, - errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => renderHelper()) `, - errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; import { render } from 'somewhere-else'; await waitFor(() => render()) `, - errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const, - ]), - { - settings: { 'testing-library/utils-module': '~/test-utils' }, - code: ` + errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const, + ]), + { + settings: { 'testing-library/utils-module': '~/test-utils' }, + code: ` import { waitFor, render } from '~/test-utils'; await waitFor(() => render()) `, - errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - settings: { 'testing-library/custom-renders': ['renderWrapper'] }, - code: ` + errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + settings: { 'testing-library/custom-renders': ['renderWrapper'] }, + code: ` import { waitFor } from '${testingFramework}'; import { renderWrapper } from 'somewhere-else'; await waitFor(() => renderWrapper()) `, - errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { render() }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { const { container } = render() }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { result = render() }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { const a = 5, { container } = render() }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; const { rerender } = render() await waitFor(() => { rerender() }) `, - errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { render() fireEvent.keyDown(input, {key: 'ArrowDown'}) }) `, - errors: [ - { line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }, - { line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }, - ], - } as const, - { - code: ` + errors: [ + { line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }, + { line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }, + ], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { render() userEvent.click(button) }) `, - errors: [ - { line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }, - { line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }, - ], - } as const, - ]), - // fireEvent - ...SUPPORTED_TESTING_FRAMEWORKS.map( - (testingFramework) => - ({ - code: ` + errors: [ + { line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }, + { line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }, + ], + } as const, + ]), + // fireEvent + ...SUPPORTED_TESTING_FRAMEWORKS.map( + (testingFramework) => + ({ + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => fireEvent.keyDown(input, {key: 'ArrowDown'})) `, - errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const) - ), - { - settings: { 'testing-library/utils-module': '~/test-utils' }, - code: ` + errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const) + ), + { + settings: { 'testing-library/utils-module': '~/test-utils' }, + code: ` import { waitFor, fireEvent } from '~/test-utils'; await waitFor(() => fireEvent.keyDown(input, {key: 'ArrowDown'})) `, - errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { fireEvent.keyDown(input, {key: 'ArrowDown'}) }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor, fireEvent as renamedFireEvent } from '${testingFramework}'; await waitFor(() => { renamedFireEvent.keyDown(input, {key: 'ArrowDown'}) }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - ]), - { - settings: { 'testing-library/utils-module': '~/test-utils' }, - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + ]), + { + settings: { 'testing-library/utils-module': '~/test-utils' }, + code: ` import { waitFor, fireEvent } from '~/test-utils'; await waitFor(() => { fireEvent.keyDown(input, {key: 'ArrowDown'}) }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { expect(b).toEqual('b') fireEvent.keyDown(input, {key: 'ArrowDown'}) }) `, - errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { fireEvent.keyDown(input, {key: 'ArrowDown'}) expect(b).toEqual('b') }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { fireEvent.keyDown(input, {key: 'ArrowDown'}) }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { expect(b).toEqual('b') fireEvent.keyDown(input, {key: 'ArrowDown'}) }) `, - errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { fireEvent.keyDown(input, {key: 'ArrowDown'}) expect(b).toEqual('b') }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - ]), - // userEvent - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + ]), + // userEvent + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => userEvent.click(button)) `, - errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 3, column: 29, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { userEvent.click(button) }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; import renamedUserEvent from '@testing-library/user-event' await waitFor(() => { renamedUserEvent.click(button) }) `, - errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - ]), - { - settings: { 'testing-library/utils-module': '~/test-utils' }, - code: ` + errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + ]), + { + settings: { 'testing-library/utils-module': '~/test-utils' }, + code: ` import { waitFor } from '~/test-utils'; import userEvent from '@testing-library/user-event' await waitFor(() => { userEvent.click(); }) `, - errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { expect(b).toEqual('b') userEvent.click(button) }) `, - errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(() => { userEvent.click(button) expect(b).toEqual('b') }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { userEvent.click(button) }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { expect(b).toEqual('b') userEvent.click(button) }) `, - errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - { - code: ` + errors: [{ line: 5, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + { + code: ` import { waitFor } from '${testingFramework}'; await waitFor(function() { userEvent.click(button) expect(b).toEqual('b') }) `, - errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], - } as const, - ]), + errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], + } as const, + ]), - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: `// all mixed + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: `// all mixed import { waitFor, fireEvent as renamedFireEvent, screen } from '~/test-utils'; import userEvent from '@testing-library/user-event' import { fireEvent } from 'somewhere-else' @@ -742,10 +742,10 @@ ruleTester.run(RULE_NAME, rule, { }) }) `, - errors: [ - { line: 9, column: 13, messageId: 'noSideEffectsWaitFor' }, - { line: 12, column: 13, messageId: 'noSideEffectsWaitFor' }, - ], - }, - ], + errors: [ + { line: 9, column: 13, messageId: 'noSideEffectsWaitFor' }, + { line: 12, column: 13, messageId: 'noSideEffectsWaitFor' }, + ], + }, + ], }); diff --git a/tests/lib/rules/no-wait-for-snapshot.test.ts b/tests/lib/rules/no-wait-for-snapshot.test.ts index cfcef0aa..02eefece 100644 --- a/tests/lib/rules/no-wait-for-snapshot.test.ts +++ b/tests/lib/rules/no-wait-for-snapshot.test.ts @@ -5,18 +5,18 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + valid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('snapshot calls outside of ${asyncUtil} are valid', () => { expect(foo).toMatchSnapshot() @@ -24,9 +24,9 @@ ruleTester.run(RULE_NAME, rule, { expect(foo).toMatchInlineSnapshot() }) `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('snapshot calls outside of ${asyncUtil} are valid', () => { expect(foo).toMatchSnapshot() @@ -36,9 +36,9 @@ ruleTester.run(RULE_NAME, rule, { expect(foo).toMatchInlineSnapshot() }) `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import * as asyncUtils from '${testingFramework}'; test('snapshot calls outside of ${asyncUtil} are valid', () => { expect(foo).toMatchSnapshot() @@ -46,9 +46,9 @@ ruleTester.run(RULE_NAME, rule, { expect(foo).toMatchInlineSnapshot() }) `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` import * as asyncUtils from '${testingFramework}'; test('snapshot calls outside of ${asyncUtil} are valid', () => { expect(foo).toMatchSnapshot() @@ -58,24 +58,24 @@ ruleTester.run(RULE_NAME, rule, { expect(foo).toMatchInlineSnapshot() }) `, - })), - ]), - ...ASYNC_UTILS.map((asyncUtil) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ]), + ...ASYNC_UTILS.map((asyncUtil) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { ${asyncUtil} } from 'some-other-library'; test('aggressive reporting disabled - snapshot calls within ${asyncUtil} not related to Testing Library are valid', async () => { await ${asyncUtil}(() => expect(foo).toMatchSnapshot()); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { ${asyncUtil} } from 'some-other-library'; test('(alt) aggressive reporting disabled - snapshot calls within ${asyncUtil} not related to Testing Library are valid', async () => { await ${asyncUtil}(() => { @@ -84,23 +84,23 @@ ruleTester.run(RULE_NAME, rule, { }); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import * as asyncUtils from 'some-other-library'; test('aggressive reporting disabled - snapshot calls within ${asyncUtil} from wildcard import not related to Testing Library are valid', async () => { await asyncUtils.${asyncUtil}(() => expect(foo).toMatchSnapshot()); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import * as asyncUtils from 'some-other-library'; test('(alt) aggressive reporting disabled - snapshot calls within ${asyncUtil} from wildcard import not related to Testing Library are valid', async () => { await asyncUtils.${asyncUtil}(() => { @@ -109,23 +109,23 @@ ruleTester.run(RULE_NAME, rule, { }); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { ${asyncUtil} } from 'some-other-library'; test('aggressive reporting disabled - inline snapshot calls within ${asyncUtil} import not related to Testing Library are valid', async () => { await ${asyncUtil}(() => expect(foo).toMatchInlineSnapshot()); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { ${asyncUtil} } from 'some-other-library'; test('(alt) aggressive reporting disabled - inline snapshot calls within ${asyncUtil} import not related to Testing Library are valid', async () => { await ${asyncUtil}(() => { @@ -134,23 +134,23 @@ ruleTester.run(RULE_NAME, rule, { }); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import * as asyncUtils from 'some-other-library'; test('aggressive reporting disabled - inline snapshot calls within ${asyncUtil} from wildcard import not related to Testing Library are valid', async () => { await asyncUtils.${asyncUtil}(() => expect(foo).toMatchInlineSnapshot()); }); `, - })), - ...ASYNC_UTILS.map((asyncUtil) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import * as asyncUtils from 'some-other-library'; test('(alt) aggressive reporting disabled - inline snapshot calls within ${asyncUtil} from wildcard import not related to Testing Library are valid', async () => { await asyncUtils.${asyncUtil}(() => { @@ -159,32 +159,32 @@ ruleTester.run(RULE_NAME, rule, { }); }); `, - })), - ], - invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + })), + ], + invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('snapshot calls within ${asyncUtil} are not valid', async () => { await ${asyncUtil}(() => expect(foo).toMatchSnapshot()); }); `, - errors: [ - { - line: 4, - messageId: 'noWaitForSnapshot', - data: { name: asyncUtil }, - column: 36 + asyncUtil.length, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 4, + messageId: 'noWaitForSnapshot', + data: { name: asyncUtil }, + column: 36 + asyncUtil.length, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('snapshot calls within ${asyncUtil} are not valid', async () => { await ${asyncUtil}(() => { @@ -192,39 +192,39 @@ ruleTester.run(RULE_NAME, rule, { }); }); `, - errors: [ - { - line: 5, - messageId: 'noWaitForSnapshot', - data: { name: asyncUtil }, - column: 27, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 5, + messageId: 'noWaitForSnapshot', + data: { name: asyncUtil }, + column: 27, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import * as asyncUtils from '${testingFramework}'; test('snapshot calls within ${asyncUtil} are not valid', async () => { await asyncUtils.${asyncUtil}(() => expect(foo).toMatchSnapshot()); }); `, - errors: [ - { - line: 4, - messageId: 'noWaitForSnapshot', - data: { name: asyncUtil }, - column: 47 + asyncUtil.length, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 4, + messageId: 'noWaitForSnapshot', + data: { name: asyncUtil }, + column: 47 + asyncUtil.length, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import * as asyncUtils from '${testingFramework}'; test('snapshot calls within ${asyncUtil} are not valid', async () => { await asyncUtils.${asyncUtil}(() => { @@ -232,39 +232,39 @@ ruleTester.run(RULE_NAME, rule, { }); }); `, - errors: [ - { - line: 5, - messageId: 'noWaitForSnapshot', - data: { name: asyncUtil }, - column: 27, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 5, + messageId: 'noWaitForSnapshot', + data: { name: asyncUtil }, + column: 27, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('snapshot calls within ${asyncUtil} are not valid', async () => { await ${asyncUtil}(() => expect(foo).toMatchInlineSnapshot()); }); `, - errors: [ - { - line: 4, - messageId: 'noWaitForSnapshot', - data: { name: asyncUtil }, - column: 36 + asyncUtil.length, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 4, + messageId: 'noWaitForSnapshot', + data: { name: asyncUtil }, + column: 36 + asyncUtil.length, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import { ${asyncUtil} } from '${testingFramework}'; test('snapshot calls within ${asyncUtil} are not valid', async () => { await ${asyncUtil}(() => { @@ -272,39 +272,39 @@ ruleTester.run(RULE_NAME, rule, { }); }); `, - errors: [ - { - line: 5, - messageId: 'noWaitForSnapshot', - data: { name: asyncUtil }, - column: 27, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 5, + messageId: 'noWaitForSnapshot', + data: { name: asyncUtil }, + column: 27, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import * as asyncUtils from '${testingFramework}'; test('snapshot calls within ${asyncUtil} are not valid', async () => { await asyncUtils.${asyncUtil}(() => expect(foo).toMatchInlineSnapshot()); }); `, - errors: [ - { - line: 4, - messageId: 'noWaitForSnapshot', - data: { name: asyncUtil }, - column: 47 + asyncUtil.length, - }, - ], - } as const) - ), - ...ASYNC_UTILS.map( - (asyncUtil) => - ({ - code: ` + errors: [ + { + line: 4, + messageId: 'noWaitForSnapshot', + data: { name: asyncUtil }, + column: 47 + asyncUtil.length, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` import * as asyncUtils from '${testingFramework}'; test('snapshot calls within ${asyncUtil} are not valid', async () => { await asyncUtils.${asyncUtil}(() => { @@ -312,15 +312,15 @@ ruleTester.run(RULE_NAME, rule, { }); }); `, - errors: [ - { - line: 5, - messageId: 'noWaitForSnapshot', - data: { name: asyncUtil }, - column: 27, - }, - ], - } as const) - ), - ]), + errors: [ + { + line: 5, + messageId: 'noWaitForSnapshot', + data: { name: asyncUtil }, + column: 27, + }, + ], + } as const) + ), + ]), }); diff --git a/tests/lib/rules/prefer-explicit-assert.test.ts b/tests/lib/rules/prefer-explicit-assert.test.ts index 08dd5fb8..952f770d 100644 --- a/tests/lib/rules/prefer-explicit-assert.test.ts +++ b/tests/lib/rules/prefer-explicit-assert.test.ts @@ -7,283 +7,283 @@ const ruleTester = createRuleTester(); const COMBINED_QUERIES_METHODS = [...ALL_QUERIES_METHODS, 'ByIcon']; ruleTester.run(RULE_NAME, rule, { - valid: [ - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `get${queryMethod}('Hello')`, - settings: { - 'testing-library/utils-module': 'test-utils', - }, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `get${queryMethod}`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + valid: [ + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `get${queryMethod}('Hello')`, + settings: { + 'testing-library/utils-module': 'test-utils', + }, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `get${queryMethod}`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` const utils = render() utils.get${queryMethod} `, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `screen.get${queryMethod}`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `expect(get${queryMethod}('foo')).toBeDefined()`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `screen.get${queryMethod}`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `expect(get${queryMethod}('foo')).toBeDefined()`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` const utils = render() expect(utils.get${queryMethod}('foo')).toBeDefined() `, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `expect(screen.get${queryMethod}('foo')).toBeDefined()`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `expect(getBy${queryMethod}('foo').bar).toBeInTheDocument()`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `expect(screen.get${queryMethod}('foo')).toBeDefined()`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `expect(getBy${queryMethod}('foo').bar).toBeInTheDocument()`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` async () => { await waitForElement(() => get${queryMethod}('foo')) } `, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `fireEvent.click(get${queryMethod}('bar'));`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `const quxElement = get${queryMethod}('qux')`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `fireEvent.click(get${queryMethod}('bar'));`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `const quxElement = get${queryMethod}('qux')`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` async () => { const quxElement = await find${queryMethod}('qux') }`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` async () => { expect(await find${queryMethod}('qux')).toBeInTheDocument(); }`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` async () => { await find${queryMethod}('foo') }`, - options: [ - { - includeFindQueries: false, - }, - ], - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `const quxElement = find${queryMethod}('qux')`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `const quxElement = screen.find${queryMethod}('qux')`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + options: [ + { + includeFindQueries: false, + }, + ], + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `const quxElement = find${queryMethod}('qux')`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `const quxElement = screen.find${queryMethod}('qux')`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` async () => { const quxElement = await screen.find${queryMethod}('qux') }`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` function findBySubmit() { return screen.find${queryMethod}('foo') }`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` function findBySubmit() { return find${queryMethod}('foo') }`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` () => { return screen.find${queryMethod}('foo') }`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` () => { return find${queryMethod}('foo') }`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` () => screen.find${queryMethod}('foo')`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` () => find${queryMethod}('foo')`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `() => { return get${queryMethod}('foo') }`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `function bar() { return get${queryMethod}('foo') }`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `const { get${queryMethod} } = render()`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `it('test', () => { const { get${queryMethod} } = render() })`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `it('test', () => { const [ get${queryMethod} ] = render() })`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `const a = [ get${queryMethod}('foo') ]`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `const a = { foo: get${queryMethod}('bar') }`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `query${queryMethod}("foo")`, - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: ` + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `() => { return get${queryMethod}('foo') }`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `function bar() { return get${queryMethod}('foo') }`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `const { get${queryMethod} } = render()`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `it('test', () => { const { get${queryMethod} } = render() })`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `it('test', () => { const [ get${queryMethod} ] = render() })`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `const a = [ get${queryMethod}('foo') ]`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `const a = { foo: get${queryMethod}('bar') }`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `query${queryMethod}("foo")`, + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: ` expect(get${queryMethod}('foo')).toBeTruthy() fireEvent.click(get${queryMethod}('bar')); `, - options: [ - { - assertion: 'toBeTruthy', - }, - ], - })), - ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ - code: `expect(get${queryMethod}('foo')).toBeEnabled()`, - options: [ - { - assertion: 'toBeInTheDocument', - }, - ], - })), - { - // https://github.com/testing-library/eslint-plugin-testing-library/issues/475 - code: ` + options: [ + { + assertion: 'toBeTruthy', + }, + ], + })), + ...COMBINED_QUERIES_METHODS.map((queryMethod) => ({ + code: `expect(get${queryMethod}('foo')).toBeEnabled()`, + options: [ + { + assertion: 'toBeInTheDocument', + }, + ], + })), + { + // https://github.com/testing-library/eslint-plugin-testing-library/issues/475 + code: ` // incomplete expect statement should be ignored expect('something'); expect(getByText('foo')); `, - options: [ - { - assertion: 'toBeInTheDocument', - }, - ], - }, - ], - invalid: [ - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: `get${queryMethod}('foo')`, - errors: [ - { - messageId: 'preferExplicitAssert', - data: { - queryType: 'getBy*', - }, - }, - ], - } as const) - ), - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: `find${queryMethod}('foo')`, - errors: [ - { - messageId: 'preferExplicitAssert', - data: { queryType: 'findBy*' }, - }, - ], - } as const) - ), - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: `screen.find${queryMethod}('foo')`, - errors: [ - { - messageId: 'preferExplicitAssert', - data: { queryType: 'findBy*' }, - }, - ], - } as const) - ), - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: ` + options: [ + { + assertion: 'toBeInTheDocument', + }, + ], + }, + ], + invalid: [ + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: `get${queryMethod}('foo')`, + errors: [ + { + messageId: 'preferExplicitAssert', + data: { + queryType: 'getBy*', + }, + }, + ], + } as const) + ), + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: `find${queryMethod}('foo')`, + errors: [ + { + messageId: 'preferExplicitAssert', + data: { queryType: 'findBy*' }, + }, + ], + } as const) + ), + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: `screen.find${queryMethod}('foo')`, + errors: [ + { + messageId: 'preferExplicitAssert', + data: { queryType: 'findBy*' }, + }, + ], + } as const) + ), + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: ` async () => { await screen.find${queryMethod}('foo') } `, - errors: [ - { - messageId: 'preferExplicitAssert', - data: { queryType: 'findBy*' }, - }, - ], - } as const) - ), - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: ` + errors: [ + { + messageId: 'preferExplicitAssert', + data: { queryType: 'findBy*' }, + }, + ], + } as const) + ), + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: ` async () => { await find${queryMethod}('foo') } `, - errors: [ - { - messageId: 'preferExplicitAssert', - data: { queryType: 'findBy*' }, - }, - ], - } as const) - ), - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: ` + errors: [ + { + messageId: 'preferExplicitAssert', + data: { queryType: 'findBy*' }, + }, + ], + } as const) + ), + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: ` const utils = render() utils.get${queryMethod}('foo') `, - errors: [ - { - messageId: 'preferExplicitAssert', - line: 3, - column: 15, - data: { - queryType: 'getBy*', - }, - }, - ], - } as const) - ), - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: `screen.get${queryMethod}('foo')`, - errors: [ - { - messageId: 'preferExplicitAssert', - line: 1, - column: 8, - data: { - queryType: 'getBy*', - }, - }, - ], - } as const) - ), - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: ` + errors: [ + { + messageId: 'preferExplicitAssert', + line: 3, + column: 15, + data: { + queryType: 'getBy*', + }, + }, + ], + } as const) + ), + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: `screen.get${queryMethod}('foo')`, + errors: [ + { + messageId: 'preferExplicitAssert', + line: 1, + column: 8, + data: { + queryType: 'getBy*', + }, + }, + ], + } as const) + ), + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: ` () => { get${queryMethod}('foo') doSomething() @@ -292,102 +292,102 @@ ruleTester.run(RULE_NAME, rule, { const quxElement = get${queryMethod}('qux') } `, - errors: [ - { - messageId: 'preferExplicitAssert', - line: 3, - data: { - queryType: 'getBy*', - }, - }, - { - messageId: 'preferExplicitAssert', - line: 6, - data: { - queryType: 'getBy*', - }, - }, - ], - } as const) - ), - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + messageId: 'preferExplicitAssert', + line: 3, + data: { + queryType: 'getBy*', + }, + }, + { + messageId: 'preferExplicitAssert', + line: 6, + data: { + queryType: 'getBy*', + }, + }, + ], + } as const) + ), + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import "test-utils" getBy${queryMethod}("Hello") `, - errors: [ - { - messageId: 'preferExplicitAssert', - data: { - queryType: 'getBy*', - }, - }, - ], - } as const) - ), - { - code: `getByIcon('foo')`, // custom `getBy` query extended through options - errors: [ - { - messageId: 'preferExplicitAssert', - }, - ], - }, - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: `expect(get${queryMethod}('foo')).toBeDefined()`, - options: [ - { - assertion: 'toBeInTheDocument', - }, - ], - errors: [ - { - messageId: 'preferExplicitAssertAssertion', - data: { assertion: 'toBeInTheDocument' }, - }, - ], - } as const) - ), - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: `expect(get${queryMethod}('foo')).not.toBeNull()`, - options: [ - { - assertion: 'toBeInTheDocument', - }, - ], - errors: [ - { - messageId: 'preferExplicitAssertAssertion', - data: { assertion: 'toBeInTheDocument' }, - }, - ], - } as const) - ), - ...COMBINED_QUERIES_METHODS.map( - (queryMethod) => - ({ - code: `expect(get${queryMethod}('foo')).not.toBeFalsy()`, - options: [ - { - assertion: 'toBeInTheDocument', - }, - ], - errors: [ - { - messageId: 'preferExplicitAssertAssertion', - data: { assertion: 'toBeInTheDocument' }, - }, - ], - } as const) - ), - ], + errors: [ + { + messageId: 'preferExplicitAssert', + data: { + queryType: 'getBy*', + }, + }, + ], + } as const) + ), + { + code: `getByIcon('foo')`, // custom `getBy` query extended through options + errors: [ + { + messageId: 'preferExplicitAssert', + }, + ], + }, + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: `expect(get${queryMethod}('foo')).toBeDefined()`, + options: [ + { + assertion: 'toBeInTheDocument', + }, + ], + errors: [ + { + messageId: 'preferExplicitAssertAssertion', + data: { assertion: 'toBeInTheDocument' }, + }, + ], + } as const) + ), + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: `expect(get${queryMethod}('foo')).not.toBeNull()`, + options: [ + { + assertion: 'toBeInTheDocument', + }, + ], + errors: [ + { + messageId: 'preferExplicitAssertAssertion', + data: { assertion: 'toBeInTheDocument' }, + }, + ], + } as const) + ), + ...COMBINED_QUERIES_METHODS.map( + (queryMethod) => + ({ + code: `expect(get${queryMethod}('foo')).not.toBeFalsy()`, + options: [ + { + assertion: 'toBeInTheDocument', + }, + ], + errors: [ + { + messageId: 'preferExplicitAssertAssertion', + data: { assertion: 'toBeInTheDocument' }, + }, + ], + } as const) + ), + ], }); diff --git a/tests/lib/rules/prefer-find-by.test.ts b/tests/lib/rules/prefer-find-by.test.ts index 97ecc4b8..f67d8ce7 100644 --- a/tests/lib/rules/prefer-find-by.test.ts +++ b/tests/lib/rules/prefer-find-by.test.ts @@ -1,75 +1,75 @@ import { TSESLint } from '@typescript-eslint/utils'; import rule, { - WAIT_METHODS, - RULE_NAME, - getFindByQueryVariant, - MessageIds, + WAIT_METHODS, + RULE_NAME, + getFindByQueryVariant, + MessageIds, } from '../../../lib/rules/prefer-find-by'; import { - ASYNC_QUERIES_COMBINATIONS, - SYNC_QUERIES_COMBINATIONS, + ASYNC_QUERIES_COMBINATIONS, + SYNC_QUERIES_COMBINATIONS, } from '../../../lib/utils'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; function buildFindByMethod(queryMethod: string) { - return `${getFindByQueryVariant(queryMethod)}${queryMethod.split('By')[1]}`; + return `${getFindByQueryVariant(queryMethod)}${queryMethod.split('By')[1]}`; } function createScenario< - T extends - | TSESLint.InvalidTestCase - | TSESLint.ValidTestCase<[]> + T extends + | 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 WAIT_METHODS.reduce( + (acc: T[], waitMethod) => + acc.concat( + SYNC_QUERIES_COMBINATIONS.map((queryMethod) => + callback(waitMethod, queryMethod) + ) + ), + [] + ); } ruleTester.run(RULE_NAME, rule, { - valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...ASYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + ...ASYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` it('tests', async () => { const { ${queryMethod} } = setup() const submitButton = await ${queryMethod}('foo') }) `, - })), - ...ASYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + })), + ...ASYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` import {screen} from '${testingFramework}'; it('tests', async () => { const submitButton = await screen.${queryMethod}('foo') }) `, - })), - ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + })), + ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` import {waitForElementToBeRemoved} from '${testingFramework}'; it('tests', async () => { await waitForElementToBeRemoved(() => ${queryMethod}(baz)) }) `, - })), - ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + })), + ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` import {waitFor} from '${testingFramework}'; it('tests', async () => { await waitFor(function() { @@ -77,34 +77,34 @@ ruleTester.run(RULE_NAME, rule, { }) }) `, - })), - { - code: ` + })), + { + code: ` import {waitFor} from '${testingFramework}'; it('tests', async () => { await waitFor(() => myCustomFunction()) }) `, - }, - { - code: ` + }, + { + code: ` import {waitFor} from '${testingFramework}'; it('tests', async () => { await waitFor(customFunctionReference) }) `, - }, - { - code: ` + }, + { + code: ` import {waitForElementToBeRemoved} from '${testingFramework}'; it('tests', async () => { const { container } = render() await waitForElementToBeRemoved(container.querySelector('foo')) }) `, - }, - ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + }, + ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` import {waitFor} from '${testingFramework}'; it('tests', async () => { await waitFor(() => { @@ -113,596 +113,596 @@ ruleTester.run(RULE_NAME, rule, { }) }) `, - })), - ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + })), + ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` import {screen, waitFor} from '${testingFramework}'; it('tests', async () => { await waitFor(() => expect(screen.${queryMethod}('baz')).toBeDisabled()); }) `, - })), - ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + })), + ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` import {screen, waitFor} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod} } = render() await waitFor(() => expect(${queryMethod}('baz')).toBeDisabled()); }) `, - })), - ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + })), + ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` import {waitFor} from '${testingFramework}'; it('tests', async () => { await waitFor(() => expect(screen.${queryMethod}('baz')).not.toBeInTheDocument()); }) `, - })), - ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + })), + ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` import {waitFor} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod} } = render() await waitFor(() => expect(${queryMethod}('baz')).not.toBeInTheDocument()); }) `, - })), - { - code: ` + })), + { + code: ` import {waitFor} from '${testingFramework}'; it('tests', async () => { await waitFor(); await wait(); }) `, - }, - { - code: ` + }, + { + code: ` import {screen, waitFor} from '${testingFramework}'; it('tests', async () => { await waitFor(() => expect(screen.querySelector('baz')).toBeInTheDocument()); }) `, - }, - { - code: ` + }, + { + code: ` import {waitFor} from '${testingFramework}'; it('tests', async () => { const { container } = render() await waitFor(() => expect(container.querySelector('baz')).toBeInTheDocument()); }) `, - }, - ]), - invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + }, + ]), + invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}, screen} from '${testingFramework}'; it('tests', async () => { const submitButton = await ${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' })) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}, screen} from '${testingFramework}'; it('tests', async () => { const submitButton = await screen.${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - // // this scenario verifies it works when the render function is defined in another scope - ...WAIT_METHODS.map( - (waitMethod: string) => - ({ - code: ` + })), + // // this scenario verifies it works when the render function is defined in another scope + ...WAIT_METHODS.map( + (waitMethod: string) => + ({ + code: ` import {${waitMethod}} from '${testingFramework}'; const { getByText, queryByLabelText, findAllByRole } = customRender() it('tests', async () => { const submitButton = await ${waitMethod}(() => getByText('baz', { name: 'button' })) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: 'findBy', - queryMethod: 'Text', - prevQuery: 'getByText', - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: 'findBy', + queryMethod: 'Text', + prevQuery: 'getByText', + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} 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: ` + } 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}'; const { getAllByRole, findAllByRole } = customRender() it('tests', async () => { const submitButton = await ${waitMethod}(() => getAllByRole('baz', { name: 'button' })) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: 'findAllBy', - queryMethod: 'Role', - prevQuery: 'getAllByRole', - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: 'findAllBy', + queryMethod: 'Role', + prevQuery: 'getAllByRole', + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} 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' }))`, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: 'findBy', - queryMethod: 'Text', - prevQuery: 'getByText', - waitForMethodName: 'waitFor', - }, - }, - ], - output: `const submitButton = await findByText('baz', { name: 'button' })`, - }, - // this code would be invalid too, as findByRole is not defined anywhere. - { - code: ` + } 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' }))`, + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: 'findBy', + queryMethod: 'Text', + prevQuery: 'getByText', + waitForMethodName: 'waitFor', + }, + }, + ], + output: `const submitButton = await findByText('baz', { name: 'button' })`, + }, + // this code would be invalid too, as findByRole is not defined anywhere. + { + code: ` const getByRole = render().getByRole const submitButton = await waitFor(() => getByRole('baz', { name: 'button' })) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: 'findBy', - queryMethod: 'Role', - prevQuery: 'getByRole', - waitForMethodName: 'waitFor', - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: 'findBy', + queryMethod: 'Role', + prevQuery: 'getByRole', + waitForMethodName: 'waitFor', + }, + }, + ], + output: ` const getByRole = render().getByRole const submitButton = await findByRole('baz', { name: 'button' }) `, - }, - // custom query triggers the error but there is no fix - so output is the same - ...WAIT_METHODS.map( - (waitMethod: string) => - ({ - code: ` + }, + // 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}'; it('tests', async () => { const { getByCustomQuery } = render() const submitButton = await ${waitMethod}(() => getByCustomQuery('baz')) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: 'findBy', - queryMethod: 'CustomQuery', - prevQuery: 'getByCustomQuery', - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: 'findBy', + queryMethod: 'CustomQuery', + prevQuery: 'getByCustomQuery', + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod},render} from '${testingFramework}'; it('tests', async () => { const { getByCustomQuery } = render() const submitButton = await ${waitMethod}(() => 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: ` + } 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}'; it('tests', async () => { const { getByCustomQuery } = render() const submitButton = await ${waitMethod}(() => screen.getByCustomQuery('baz')) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: 'findBy', - queryMethod: 'CustomQuery', - prevQuery: 'getByCustomQuery', - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: 'findBy', + queryMethod: 'CustomQuery', + prevQuery: 'getByCustomQuery', + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod},render,screen} from '${testingFramework}'; it('tests', async () => { const { getByCustomQuery } = render() const submitButton = await ${waitMethod}(() => screen.getByCustomQuery('baz')) }) `, - } as const) - ), - // presence matchers - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + } as const) + ), + // presence matchers + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod} } = render() const submitButton = await ${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' })) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod}, ${buildFindByMethod(queryMethod)} } = render() const submitButton = await ${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod} } = render() const submitButton = await ${waitMethod}(() => expect(${queryMethod}('foo', { name: 'baz' })).toBeInTheDocument()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod}, ${buildFindByMethod(queryMethod)} } = render() const submitButton = await ${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod} } = render() const submitButton = await ${waitMethod}(() => expect(${queryMethod}('foo', { name: 'baz' })).toBeDefined()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod}, ${buildFindByMethod(queryMethod)} } = render() const submitButton = await ${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod} } = render() const submitButton = await ${waitMethod}(() => expect(${queryMethod}('foo', { name: 'baz' })).not.toBeNull()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod}, ${buildFindByMethod(queryMethod)} } = render() const submitButton = await ${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const {${queryMethod}} = render() const submitButton = await ${waitMethod}(() => expect(${queryMethod}('foo', { name: 'baz' })).not.toBeNull()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const {${queryMethod}, ${buildFindByMethod(queryMethod)}} = render() const submitButton = await ${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod} } = render() const submitButton = await ${waitMethod}(() => expect(${queryMethod}('foo', { name: 'baz' })).toBeTruthy()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod}, ${buildFindByMethod(queryMethod)} } = render() const submitButton = await ${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod} } = render() const submitButton = await ${waitMethod}(() => expect(${queryMethod}('foo', { name: 'baz' })).not.toBeFalsy()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const { ${queryMethod}, ${buildFindByMethod(queryMethod)} } = render() const submitButton = await ${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const submitButton = await ${waitMethod}(() => expect(screen.${queryMethod}('foo', { name: 'baz' })).toBeInTheDocument()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const submitButton = await screen.${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const submitButton = await ${waitMethod}(() => expect(screen.${queryMethod}('foo', { name: 'baz' })).toBeDefined()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const submitButton = await screen.${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const submitButton = await ${waitMethod}(() => expect(screen.${queryMethod}('foo', { name: 'baz' })).not.toBeNull()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const submitButton = await screen.${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const submitButton = await ${waitMethod}(() => expect(screen.${queryMethod}('foo', { name: 'baz' })).toBeTruthy()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const submitButton = await screen.${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ - code: ` + })), + ...createScenario((waitMethod: string, queryMethod: string) => ({ + code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const submitButton = await ${waitMethod}(() => expect(screen.${queryMethod}('foo', { name: 'baz' })).not.toBeFalsy()) }) `, - errors: [ - { - messageId: 'preferFindBy', - data: { - queryVariant: getFindByQueryVariant(queryMethod), - queryMethod: queryMethod.split('By')[1], - prevQuery: queryMethod, - waitForMethodName: waitMethod, - }, - }, - ], - output: ` + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: waitMethod, + }, + }, + ], + output: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { const submitButton = await screen.${buildFindByMethod( - queryMethod - )}('foo', { name: 'baz' }) + queryMethod + )}('foo', { name: 'baz' }) }) `, - })), - ]), + })), + ]), }); diff --git a/tests/lib/rules/prefer-presence-queries.test.ts b/tests/lib/rules/prefer-presence-queries.test.ts index 976ac6e1..2b5187f2 100644 --- a/tests/lib/rules/prefer-presence-queries.test.ts +++ b/tests/lib/rules/prefer-presence-queries.test.ts @@ -1,9 +1,9 @@ import { TSESLint } from '@typescript-eslint/utils'; import rule, { - RULE_NAME, - MessageIds, - Options, + RULE_NAME, + MessageIds, + Options, } from '../../../lib/rules/prefer-presence-queries'; import { ALL_QUERIES_METHODS } from '../../../lib/utils'; import { createRuleTester } from '../test-utils'; @@ -14,820 +14,820 @@ const getByQueries = ALL_QUERIES_METHODS.map((method) => `get${method}`); const getAllByQueries = ALL_QUERIES_METHODS.map((method) => `getAll${method}`); const queryByQueries = ALL_QUERIES_METHODS.map((method) => `query${method}`); const queryAllByQueries = ALL_QUERIES_METHODS.map( - (method) => `queryAll${method}` + (method) => `queryAll${method}` ); type RuleValidTestCase = TSESLint.ValidTestCase; type RuleInvalidTestCase = TSESLint.InvalidTestCase; type AssertionFnParams = { - query: string; - matcher: string; - messageId: MessageIds; - shouldUseScreen?: boolean; - assertionType: keyof Options[number]; + query: string; + matcher: string; + messageId: MessageIds; + shouldUseScreen?: boolean; + assertionType: keyof Options[number]; }; const getValidAssertions = ({ - query, - matcher, - shouldUseScreen = false, - assertionType, + query, + matcher, + shouldUseScreen = false, + assertionType, }: Omit): RuleValidTestCase[] => { - const finalQuery = shouldUseScreen ? `screen.${query}` : query; - const code = `expect(${finalQuery}('Hello'))${matcher}`; - return [ - { - code, - }, - { - code, - options: [ - { - [assertionType]: true, - [assertionType === 'absence' ? 'presence' : 'absence']: false, - }, - ], - }, - { - code, - options: [ - { - presence: false, - absence: false, - }, - ], - }, - ]; + const finalQuery = shouldUseScreen ? `screen.${query}` : query; + const code = `expect(${finalQuery}('Hello'))${matcher}`; + return [ + { + code, + }, + { + code, + options: [ + { + [assertionType]: true, + [assertionType === 'absence' ? 'presence' : 'absence']: false, + }, + ], + }, + { + code, + options: [ + { + presence: false, + absence: false, + }, + ], + }, + ]; }; const getDisabledValidAssertion = ({ - query, - matcher, - shouldUseScreen = false, - assertionType, + query, + matcher, + shouldUseScreen = false, + assertionType, }: Omit): RuleValidTestCase => { - const finalQuery = shouldUseScreen ? `screen.${query}` : query; - return { - code: `expect(${finalQuery}('Hello'))${matcher}`, - options: [ - { - [assertionType]: false, - [assertionType === 'absence' ? 'presence' : 'absence']: true, - }, - ], - }; + const finalQuery = shouldUseScreen ? `screen.${query}` : query; + return { + code: `expect(${finalQuery}('Hello'))${matcher}`, + options: [ + { + [assertionType]: false, + [assertionType === 'absence' ? 'presence' : 'absence']: true, + }, + ], + }; }; const getInvalidAssertions = ({ - query, - matcher, - messageId, - shouldUseScreen = false, - assertionType, + query, + matcher, + messageId, + shouldUseScreen = false, + assertionType, }: AssertionFnParams): RuleInvalidTestCase[] => { - const finalQuery = shouldUseScreen ? `screen.${query}` : query; - const code = `expect(${finalQuery}('Hello'))${matcher}`; - return [ - { - code, - errors: [{ messageId, line: 1, column: shouldUseScreen ? 15 : 8 }], - }, - { - code, - options: [ - { - [assertionType]: true, - [assertionType === 'absence' ? 'presence' : 'absence']: false, - }, - ], - errors: [{ messageId, line: 1, column: shouldUseScreen ? 15 : 8 }], - }, - ]; + const finalQuery = shouldUseScreen ? `screen.${query}` : query; + const code = `expect(${finalQuery}('Hello'))${matcher}`; + return [ + { + code, + errors: [{ messageId, line: 1, column: shouldUseScreen ? 15 : 8 }], + }, + { + code, + options: [ + { + [assertionType]: true, + [assertionType === 'absence' ? 'presence' : 'absence']: false, + }, + ], + errors: [{ messageId, line: 1, column: shouldUseScreen ? 15 : 8 }], + }, + ]; }; ruleTester.run(RULE_NAME, rule, { - valid: [ - // cases: methods not matching Testing Library queries pattern - `expect(queryElement('foo')).toBeInTheDocument()`, - `expect(getElement('foo')).not.toBeInTheDocument()`, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + valid: [ + // cases: methods not matching Testing Library queries pattern + `expect(queryElement('foo')).toBeInTheDocument()`, + `expect(getElement('foo')).not.toBeInTheDocument()`, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: invalid presence assert but not reported because custom module is not imported expect(queryByRole('button')).toBeInTheDocument() `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: invalid absence assert but not reported because custom module is not imported expect(getByRole('button')).not.toBeInTheDocument() `, - }, - // cases: asserting presence correctly with `getBy*` queries - ...getByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getValidAssertions({ - query: queryName, - matcher: '.toBeInTheDocument()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeTruthy()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeDefined()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBe("foo")', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toEqual("World")', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeFalsy()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeNull()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeDisabled()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toHaveClass("btn")', - assertionType: 'presence', - }), - ], - [] - ), - // cases: asserting presence correctly with `screen.getBy*` queries - ...getByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getValidAssertions({ - query: queryName, - matcher: '.toBeInTheDocument()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeTruthy()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeDefined()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBe("foo")', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toEqual("World")', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeFalsy()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeNull()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeDisabled()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toHaveClass("btn")', - shouldUseScreen: true, - assertionType: 'presence', - }), - ], - [] - ), - // cases: asserting presence correctly with `getAllBy*` queries - ...getAllByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getValidAssertions({ - query: queryName, - matcher: '.toBeInTheDocument()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeTruthy()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeDefined()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBe("foo")', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toEqual("World")', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeFalsy()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeNull()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeDisabled()', - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toHaveClass("btn")', - assertionType: 'presence', - }), - ], - [] - ), - // cases: asserting presence correctly with `screen.getAllBy*` queries - ...getAllByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getValidAssertions({ - query: queryName, - matcher: '.toBeInTheDocument()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeTruthy()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeDefined()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBe("foo")', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toEqual("World")', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeFalsy()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeNull()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeDisabled()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toHaveClass("btn")', - shouldUseScreen: true, - assertionType: 'presence', - }), - ], - [] - ), - // cases: asserting absence correctly with `queryBy*` queries - ...queryByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getValidAssertions({ - query: queryName, - matcher: '.toBeNull()', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeFalsy()', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeInTheDocument()', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeTruthy()', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeDefined()', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toEqual("World")', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toHaveClass("btn")', - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting absence correctly with `screen.queryBy*` queries - ...queryByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getValidAssertions({ - query: queryName, - matcher: '.toBeNull()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeFalsy()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeInTheDocument()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeTruthy()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeDefined()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toEqual("World")', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toHaveClass("btn")', - shouldUseScreen: true, - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting absence correctly with `queryAllBy*` queries - ...queryAllByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getValidAssertions({ - query: queryName, - matcher: '.toBeNull()', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeFalsy()', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeInTheDocument()', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeTruthy()', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeDefined()', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toEqual("World")', - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toHaveClass("btn")', - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting absence correctly with `screen.queryAllBy*` queries - ...queryAllByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getValidAssertions({ - query: queryName, - matcher: '.toBeNull()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toBeFalsy()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeInTheDocument()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeTruthy()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toBeDefined()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.toEqual("World")', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getValidAssertions({ - query: queryName, - matcher: '.not.toHaveClass("btn")', - shouldUseScreen: true, - assertionType: 'absence', - }), - ], - [] - ), + }, + // cases: asserting presence correctly with `getBy*` queries + ...getByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getValidAssertions({ + query: queryName, + matcher: '.toBeInTheDocument()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeTruthy()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeDefined()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBe("foo")', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toEqual("World")', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeFalsy()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeNull()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeDisabled()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toHaveClass("btn")', + assertionType: 'presence', + }), + ], + [] + ), + // cases: asserting presence correctly with `screen.getBy*` queries + ...getByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getValidAssertions({ + query: queryName, + matcher: '.toBeInTheDocument()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeTruthy()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeDefined()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBe("foo")', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toEqual("World")', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeFalsy()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeNull()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeDisabled()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toHaveClass("btn")', + shouldUseScreen: true, + assertionType: 'presence', + }), + ], + [] + ), + // cases: asserting presence correctly with `getAllBy*` queries + ...getAllByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getValidAssertions({ + query: queryName, + matcher: '.toBeInTheDocument()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeTruthy()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeDefined()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBe("foo")', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toEqual("World")', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeFalsy()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeNull()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeDisabled()', + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toHaveClass("btn")', + assertionType: 'presence', + }), + ], + [] + ), + // cases: asserting presence correctly with `screen.getAllBy*` queries + ...getAllByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getValidAssertions({ + query: queryName, + matcher: '.toBeInTheDocument()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeTruthy()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeDefined()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBe("foo")', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toEqual("World")', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeFalsy()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeNull()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeDisabled()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toHaveClass("btn")', + shouldUseScreen: true, + assertionType: 'presence', + }), + ], + [] + ), + // cases: asserting absence correctly with `queryBy*` queries + ...queryByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getValidAssertions({ + query: queryName, + matcher: '.toBeNull()', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeFalsy()', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeInTheDocument()', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeTruthy()', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeDefined()', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toEqual("World")', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toHaveClass("btn")', + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting absence correctly with `screen.queryBy*` queries + ...queryByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getValidAssertions({ + query: queryName, + matcher: '.toBeNull()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeFalsy()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeInTheDocument()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeTruthy()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeDefined()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toEqual("World")', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toHaveClass("btn")', + shouldUseScreen: true, + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting absence correctly with `queryAllBy*` queries + ...queryAllByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getValidAssertions({ + query: queryName, + matcher: '.toBeNull()', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeFalsy()', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeInTheDocument()', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeTruthy()', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeDefined()', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toEqual("World")', + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toHaveClass("btn")', + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting absence correctly with `screen.queryAllBy*` queries + ...queryAllByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getValidAssertions({ + query: queryName, + matcher: '.toBeNull()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toBeFalsy()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeInTheDocument()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeTruthy()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toBeDefined()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.toEqual("World")', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getValidAssertions({ + query: queryName, + matcher: '.not.toHaveClass("btn")', + shouldUseScreen: true, + assertionType: 'absence', + }), + ], + [] + ), - // cases: asserting absence incorrectly with `getBy*` queries with absence rule disabled - ...getByQueries.reduce( - (invalidRules, queryName) => [ - ...invalidRules, - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeNull()', - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeFalsy()', - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeInTheDocument', - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeTruthy()', - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeDefined()', - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting absence incorrectly with `screen.getBy*` queries with absence rule disabled - ...getByQueries.reduce( - (invalidRules, queryName) => [ - ...invalidRules, - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeNull()', - shouldUseScreen: true, - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeFalsy()', - shouldUseScreen: true, - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeInTheDocument', - shouldUseScreen: true, - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeTruthy()', - shouldUseScreen: true, - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeDefined()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting absence incorrectly with `getAllBy*` queries with absence rule disabled - ...getAllByQueries.reduce( - (invalidRules, queryName) => [ - ...invalidRules, - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeNull()', - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeFalsy()', - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeInTheDocument', - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeTruthy()', - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeDefined()', - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting absence incorrectly with `screen.getAllBy*` queries with absence rule disabled - ...getAllByQueries.reduce( - (invalidRules, queryName) => [ - ...invalidRules, - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeNull()', - shouldUseScreen: true, - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeFalsy()', - shouldUseScreen: true, - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeInTheDocument', - shouldUseScreen: true, - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeTruthy()', - shouldUseScreen: true, - assertionType: 'absence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeDefined()', - shouldUseScreen: true, - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting presence incorrectly with `queryBy*` queries with presence rule disabled - ...queryByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeTruthy()', - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeDefined()', - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeInTheDocument()', - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeFalsy()', - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeNull()', - assertionType: 'presence', - }), - ], - [] - ), - // cases: asserting presence incorrectly with `screen.queryBy*` queries with presence rule disabled - ...queryByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeTruthy()', - shouldUseScreen: true, - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeDefined()', - shouldUseScreen: true, - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeInTheDocument()', - shouldUseScreen: true, - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeFalsy()', - shouldUseScreen: true, - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeNull()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ], - [] - ), - // cases: asserting presence incorrectly with `queryAllBy*` queries with presence rule disabled - ...queryAllByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeTruthy()', - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeDefined()', - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeInTheDocument()', - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeFalsy()', - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeNull()', - assertionType: 'presence', - }), - ], - [] - ), - // cases: asserting presence incorrectly with `screen.queryAllBy*` queries with presence rule disabled - ...queryAllByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeTruthy()', - shouldUseScreen: true, - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeDefined()', - shouldUseScreen: true, - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.toBeInTheDocument()', - shouldUseScreen: true, - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeFalsy()', - shouldUseScreen: true, - assertionType: 'presence', - }), - getDisabledValidAssertion({ - query: queryName, - matcher: '.not.toBeNull()', - shouldUseScreen: true, - assertionType: 'presence', - }), - ], - [] - ), + // cases: asserting absence incorrectly with `getBy*` queries with absence rule disabled + ...getByQueries.reduce( + (invalidRules, queryName) => [ + ...invalidRules, + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeNull()', + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeFalsy()', + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeInTheDocument', + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeTruthy()', + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeDefined()', + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting absence incorrectly with `screen.getBy*` queries with absence rule disabled + ...getByQueries.reduce( + (invalidRules, queryName) => [ + ...invalidRules, + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeNull()', + shouldUseScreen: true, + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeFalsy()', + shouldUseScreen: true, + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeInTheDocument', + shouldUseScreen: true, + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeTruthy()', + shouldUseScreen: true, + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeDefined()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting absence incorrectly with `getAllBy*` queries with absence rule disabled + ...getAllByQueries.reduce( + (invalidRules, queryName) => [ + ...invalidRules, + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeNull()', + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeFalsy()', + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeInTheDocument', + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeTruthy()', + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeDefined()', + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting absence incorrectly with `screen.getAllBy*` queries with absence rule disabled + ...getAllByQueries.reduce( + (invalidRules, queryName) => [ + ...invalidRules, + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeNull()', + shouldUseScreen: true, + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeFalsy()', + shouldUseScreen: true, + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeInTheDocument', + shouldUseScreen: true, + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeTruthy()', + shouldUseScreen: true, + assertionType: 'absence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeDefined()', + shouldUseScreen: true, + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting presence incorrectly with `queryBy*` queries with presence rule disabled + ...queryByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeTruthy()', + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeDefined()', + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeInTheDocument()', + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeFalsy()', + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeNull()', + assertionType: 'presence', + }), + ], + [] + ), + // cases: asserting presence incorrectly with `screen.queryBy*` queries with presence rule disabled + ...queryByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeTruthy()', + shouldUseScreen: true, + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeDefined()', + shouldUseScreen: true, + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeInTheDocument()', + shouldUseScreen: true, + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeFalsy()', + shouldUseScreen: true, + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeNull()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ], + [] + ), + // cases: asserting presence incorrectly with `queryAllBy*` queries with presence rule disabled + ...queryAllByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeTruthy()', + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeDefined()', + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeInTheDocument()', + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeFalsy()', + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeNull()', + assertionType: 'presence', + }), + ], + [] + ), + // cases: asserting presence incorrectly with `screen.queryAllBy*` queries with presence rule disabled + ...queryAllByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeTruthy()', + shouldUseScreen: true, + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeDefined()', + shouldUseScreen: true, + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.toBeInTheDocument()', + shouldUseScreen: true, + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeFalsy()', + shouldUseScreen: true, + assertionType: 'presence', + }), + getDisabledValidAssertion({ + query: queryName, + matcher: '.not.toBeNull()', + shouldUseScreen: true, + assertionType: 'presence', + }), + ], + [] + ), - { - code: 'const el = getByText("button")', - }, - { - code: 'const el = queryByText("button")', - }, - { - code: `async () => { + { + code: 'const el = getByText("button")', + }, + { + code: 'const el = queryByText("button")', + }, + { + code: `async () => { const el = await findByText('button') expect(el).toBeInTheDocument() }`, - }, - `// case: query an element with getBy but then check its absence after doing + }, + `// case: query an element with getBy but then check its absence after doing // some action which makes it disappear. // submit button exists @@ -837,367 +837,367 @@ ruleTester.run(RULE_NAME, rule, { // right after clicking submit button it disappears expect(submitButton).not.toBeInTheDocument() `, - ], - invalid: [ - // cases: asserting absence incorrectly with `getBy*` queries - ...getByQueries.reduce( - (invalidRules, queryName) => [ - ...invalidRules, - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeNull()', - messageId: 'wrongAbsenceQuery', - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeFalsy()', - messageId: 'wrongAbsenceQuery', - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeInTheDocument()', - messageId: 'wrongAbsenceQuery', - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeTruthy()', - messageId: 'wrongAbsenceQuery', - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeDefined()', - messageId: 'wrongAbsenceQuery', - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting absence incorrectly with `screen.getBy*` queries - ...getByQueries.reduce( - (invalidRules, queryName) => [ - ...invalidRules, - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeNull()', - messageId: 'wrongAbsenceQuery', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeFalsy()', - messageId: 'wrongAbsenceQuery', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeInTheDocument()', - messageId: 'wrongAbsenceQuery', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeTruthy()', - messageId: 'wrongAbsenceQuery', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeDefined()', - messageId: 'wrongAbsenceQuery', - shouldUseScreen: true, - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting absence incorrectly with `getAllBy*` queries - ...getAllByQueries.reduce( - (invalidRules, queryName) => [ - ...invalidRules, - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeNull()', - messageId: 'wrongAbsenceQuery', - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeFalsy()', - messageId: 'wrongAbsenceQuery', - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeInTheDocument()', - messageId: 'wrongAbsenceQuery', - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeTruthy()', - messageId: 'wrongAbsenceQuery', - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeDefined()', - messageId: 'wrongAbsenceQuery', - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting absence incorrectly with `screen.getAllBy*` queries - ...getAllByQueries.reduce( - (invalidRules, queryName) => [ - ...invalidRules, - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeNull()', - messageId: 'wrongAbsenceQuery', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeFalsy()', - messageId: 'wrongAbsenceQuery', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeInTheDocument()', - messageId: 'wrongAbsenceQuery', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeTruthy()', - messageId: 'wrongAbsenceQuery', - shouldUseScreen: true, - assertionType: 'absence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeDefined()', - messageId: 'wrongAbsenceQuery', - shouldUseScreen: true, - assertionType: 'absence', - }), - ], - [] - ), - // cases: asserting presence incorrectly with `queryBy*` queries - ...queryByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeTruthy()', - messageId: 'wrongPresenceQuery', - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeDefined()', - messageId: 'wrongPresenceQuery', - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeInTheDocument()', - messageId: 'wrongPresenceQuery', - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeFalsy()', - messageId: 'wrongPresenceQuery', - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeNull()', - messageId: 'wrongPresenceQuery', - assertionType: 'presence', - }), - ], - [] - ), - // cases: asserting presence incorrectly with `screen.queryBy*` queries - ...queryByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeTruthy()', - messageId: 'wrongPresenceQuery', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeDefined()', - messageId: 'wrongPresenceQuery', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeInTheDocument()', - messageId: 'wrongPresenceQuery', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeFalsy()', - messageId: 'wrongPresenceQuery', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeNull()', - messageId: 'wrongPresenceQuery', - shouldUseScreen: true, - assertionType: 'presence', - }), - ], - [] - ), - // cases: asserting presence incorrectly with `queryAllBy*` queries - ...queryAllByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeTruthy()', - messageId: 'wrongPresenceQuery', - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeDefined()', - messageId: 'wrongPresenceQuery', - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeInTheDocument()', - messageId: 'wrongPresenceQuery', - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeFalsy()', - messageId: 'wrongPresenceQuery', - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeNull()', - messageId: 'wrongPresenceQuery', - assertionType: 'presence', - }), - ], - [] - ), - // cases: asserting presence incorrectly with `screen.queryAllBy*` queries - ...queryAllByQueries.reduce( - (validRules, queryName) => [ - ...validRules, - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeTruthy()', - messageId: 'wrongPresenceQuery', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeDefined()', - messageId: 'wrongPresenceQuery', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.toBeInTheDocument()', - messageId: 'wrongPresenceQuery', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeFalsy()', - messageId: 'wrongPresenceQuery', - shouldUseScreen: true, - assertionType: 'presence', - }), - ...getInvalidAssertions({ - query: queryName, - matcher: '.not.toBeNull()', - messageId: 'wrongPresenceQuery', - shouldUseScreen: true, - assertionType: 'presence', - }), - ], - [] - ), - { - code: 'expect(screen.getAllByText("button")[1]).not.toBeInTheDocument()', - errors: [{ messageId: 'wrongAbsenceQuery', line: 1, column: 15 }], - }, - { - code: 'expect(screen.queryAllByText("button")[1]).toBeInTheDocument()', - errors: [{ messageId: 'wrongPresenceQuery', line: 1, column: 15 }], - }, - { - code: ` + ], + invalid: [ + // cases: asserting absence incorrectly with `getBy*` queries + ...getByQueries.reduce( + (invalidRules, queryName) => [ + ...invalidRules, + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeNull()', + messageId: 'wrongAbsenceQuery', + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeFalsy()', + messageId: 'wrongAbsenceQuery', + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeInTheDocument()', + messageId: 'wrongAbsenceQuery', + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeTruthy()', + messageId: 'wrongAbsenceQuery', + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeDefined()', + messageId: 'wrongAbsenceQuery', + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting absence incorrectly with `screen.getBy*` queries + ...getByQueries.reduce( + (invalidRules, queryName) => [ + ...invalidRules, + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeNull()', + messageId: 'wrongAbsenceQuery', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeFalsy()', + messageId: 'wrongAbsenceQuery', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeInTheDocument()', + messageId: 'wrongAbsenceQuery', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeTruthy()', + messageId: 'wrongAbsenceQuery', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeDefined()', + messageId: 'wrongAbsenceQuery', + shouldUseScreen: true, + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting absence incorrectly with `getAllBy*` queries + ...getAllByQueries.reduce( + (invalidRules, queryName) => [ + ...invalidRules, + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeNull()', + messageId: 'wrongAbsenceQuery', + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeFalsy()', + messageId: 'wrongAbsenceQuery', + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeInTheDocument()', + messageId: 'wrongAbsenceQuery', + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeTruthy()', + messageId: 'wrongAbsenceQuery', + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeDefined()', + messageId: 'wrongAbsenceQuery', + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting absence incorrectly with `screen.getAllBy*` queries + ...getAllByQueries.reduce( + (invalidRules, queryName) => [ + ...invalidRules, + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeNull()', + messageId: 'wrongAbsenceQuery', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeFalsy()', + messageId: 'wrongAbsenceQuery', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeInTheDocument()', + messageId: 'wrongAbsenceQuery', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeTruthy()', + messageId: 'wrongAbsenceQuery', + shouldUseScreen: true, + assertionType: 'absence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeDefined()', + messageId: 'wrongAbsenceQuery', + shouldUseScreen: true, + assertionType: 'absence', + }), + ], + [] + ), + // cases: asserting presence incorrectly with `queryBy*` queries + ...queryByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeTruthy()', + messageId: 'wrongPresenceQuery', + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeDefined()', + messageId: 'wrongPresenceQuery', + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeInTheDocument()', + messageId: 'wrongPresenceQuery', + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeFalsy()', + messageId: 'wrongPresenceQuery', + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeNull()', + messageId: 'wrongPresenceQuery', + assertionType: 'presence', + }), + ], + [] + ), + // cases: asserting presence incorrectly with `screen.queryBy*` queries + ...queryByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeTruthy()', + messageId: 'wrongPresenceQuery', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeDefined()', + messageId: 'wrongPresenceQuery', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeInTheDocument()', + messageId: 'wrongPresenceQuery', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeFalsy()', + messageId: 'wrongPresenceQuery', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeNull()', + messageId: 'wrongPresenceQuery', + shouldUseScreen: true, + assertionType: 'presence', + }), + ], + [] + ), + // cases: asserting presence incorrectly with `queryAllBy*` queries + ...queryAllByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeTruthy()', + messageId: 'wrongPresenceQuery', + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeDefined()', + messageId: 'wrongPresenceQuery', + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeInTheDocument()', + messageId: 'wrongPresenceQuery', + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeFalsy()', + messageId: 'wrongPresenceQuery', + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeNull()', + messageId: 'wrongPresenceQuery', + assertionType: 'presence', + }), + ], + [] + ), + // cases: asserting presence incorrectly with `screen.queryAllBy*` queries + ...queryAllByQueries.reduce( + (validRules, queryName) => [ + ...validRules, + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeTruthy()', + messageId: 'wrongPresenceQuery', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeDefined()', + messageId: 'wrongPresenceQuery', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.toBeInTheDocument()', + messageId: 'wrongPresenceQuery', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeFalsy()', + messageId: 'wrongPresenceQuery', + shouldUseScreen: true, + assertionType: 'presence', + }), + ...getInvalidAssertions({ + query: queryName, + matcher: '.not.toBeNull()', + messageId: 'wrongPresenceQuery', + shouldUseScreen: true, + assertionType: 'presence', + }), + ], + [] + ), + { + code: 'expect(screen.getAllByText("button")[1]).not.toBeInTheDocument()', + errors: [{ messageId: 'wrongAbsenceQuery', line: 1, column: 15 }], + }, + { + code: 'expect(screen.queryAllByText("button")[1]).toBeInTheDocument()', + errors: [{ messageId: 'wrongPresenceQuery', line: 1, column: 15 }], + }, + { + code: ` // case: asserting presence incorrectly with custom queryBy* query expect(queryByCustomQuery("button")).toBeInTheDocument() `, - errors: [{ messageId: 'wrongPresenceQuery', line: 3, column: 16 }], - }, - { - code: ` + errors: [{ messageId: 'wrongPresenceQuery', line: 3, column: 16 }], + }, + { + code: ` // case: asserting absence incorrectly with custom getBy* query expect(getByCustomQuery("button")).not.toBeInTheDocument() `, - errors: [{ messageId: 'wrongAbsenceQuery', line: 3, column: 16 }], - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [{ messageId: 'wrongAbsenceQuery', line: 3, column: 16 }], + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: asserting presence incorrectly importing custom module import 'test-utils' expect(queryByRole("button")).toBeInTheDocument() `, - errors: [{ line: 4, column: 14, messageId: 'wrongPresenceQuery' }], - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [{ line: 4, column: 14, messageId: 'wrongPresenceQuery' }], + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // case: asserting absence incorrectly importing custom module import 'test-utils' expect(getByRole("button")).not.toBeInTheDocument() `, - errors: [{ line: 4, column: 14, messageId: 'wrongAbsenceQuery' }], - }, - ], + errors: [{ line: 4, column: 14, messageId: 'wrongAbsenceQuery' }], + }, + ], }); diff --git a/tests/lib/rules/prefer-query-by-disappearance.test.ts b/tests/lib/rules/prefer-query-by-disappearance.test.ts index 2e0e78fc..010de651 100644 --- a/tests/lib/rules/prefer-query-by-disappearance.test.ts +++ b/tests/lib/rules/prefer-query-by-disappearance.test.ts @@ -1,54 +1,54 @@ import rule, { - RULE_NAME, + RULE_NAME, } from '../../../lib/rules/prefer-query-by-disappearance'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { screen } from '${testingFramework}'; const button = screen.getByRole('button') await waitForElementToBeRemoved(button) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; const callback = () => screen.getByRole('button') await waitForElementToBeRemoved(callback) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(() => screen.queryByText("hello")) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(() => { screen.queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(() => { @@ -56,9 +56,9 @@ ruleTester.run(RULE_NAME, rule, { screen.queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(() => { @@ -66,27 +66,27 @@ ruleTester.run(RULE_NAME, rule, { return screen.queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(() => { return screen.queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(function() { screen.queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(function() { @@ -94,18 +94,18 @@ ruleTester.run(RULE_NAME, rule, { screen.queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(function() { return screen.queryByText('hey') }) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(function() { @@ -113,32 +113,32 @@ ruleTester.run(RULE_NAME, rule, { return screen.queryByText('hey') }) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(screen.queryByText("hello")) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; const { queryByText } = screen await waitForElementToBeRemoved(queryByText("hello")) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; const { queryByText } = screen await waitForElementToBeRemoved(() => queryByText("hello")) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; const { queryByText } = screen @@ -146,9 +146,9 @@ ruleTester.run(RULE_NAME, rule, { queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; const { queryByText } = screen @@ -156,9 +156,9 @@ ruleTester.run(RULE_NAME, rule, { return queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; const { queryByText } = render() @@ -166,9 +166,9 @@ ruleTester.run(RULE_NAME, rule, { return queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; const { queryByText } = render() @@ -176,25 +176,25 @@ ruleTester.run(RULE_NAME, rule, { queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; const { queryByText } = render() await waitForElementToBeRemoved(() => queryByText("hello")) `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; const { queryByText } = render() await waitForElementToBeRemoved(queryByText("hello")) `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; const { queryByText } = render() @@ -202,9 +202,9 @@ ruleTester.run(RULE_NAME, rule, { queryByText("hello") }) `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; const { queryByText } = render() @@ -212,209 +212,209 @@ ruleTester.run(RULE_NAME, rule, { return queryByText("hello") }) `, - }, - ]), - invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + }, + ]), + invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { screen, waitForElementToBeRemoved } from '${testingFramework}'; await waitForElementToBeRemoved(() => screen.getByText("hello")) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen, waitForElementToBeRemoved } from '@marko/testing-library'; await waitForElementToBeRemoved(() => screen.getByText("hello")) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen, waitForElementToBeRemoved } from '${testingFramework}'; await waitForElementToBeRemoved(() => screen.findByText("hello")) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen, waitForElementToBeRemoved } from '${testingFramework}'; await waitForElementToBeRemoved(() => { screen.getByText("hello") }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen, waitForElementToBeRemoved } from '${testingFramework}'; await waitForElementToBeRemoved(() => { screen.findByText("hello") }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen, waitForElementToBeRemoved } from '${testingFramework}'; await waitForElementToBeRemoved(() => { return screen.getByText("hello") }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen, waitForElementToBeRemoved } from '${testingFramework}'; await waitForElementToBeRemoved(() => { return screen.findByText("hello") }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen, waitForElementToBeRemoved } from '${testingFramework}'; await waitForElementToBeRemoved(screen.getByText("hello")) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen, waitForElementToBeRemoved } from '${testingFramework}'; await waitForElementToBeRemoved(screen.findByText("hello")) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(function() { return screen.getByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(function() { return screen.findByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(function() { screen.getByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; await waitForElementToBeRemoved(function() { screen.findByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 4, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 4, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { getByText } = screen @@ -422,16 +422,16 @@ ruleTester.run(RULE_NAME, rule, { getByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { getByText } = render() @@ -439,16 +439,16 @@ ruleTester.run(RULE_NAME, rule, { getByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { findByText } = screen @@ -456,16 +456,16 @@ ruleTester.run(RULE_NAME, rule, { findByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { findByText } = render @@ -473,16 +473,16 @@ ruleTester.run(RULE_NAME, rule, { findByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { getByText } = screen @@ -490,16 +490,16 @@ ruleTester.run(RULE_NAME, rule, { return getByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { getByText } = render() @@ -507,16 +507,16 @@ ruleTester.run(RULE_NAME, rule, { return getByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { findByText } = screen @@ -524,16 +524,16 @@ ruleTester.run(RULE_NAME, rule, { return findByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { findByText } = render() @@ -541,16 +541,16 @@ ruleTester.run(RULE_NAME, rule, { return findByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { getByText } = screen @@ -558,16 +558,16 @@ ruleTester.run(RULE_NAME, rule, { return getByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { getByText } = render() @@ -575,16 +575,16 @@ ruleTester.run(RULE_NAME, rule, { return getByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { findByText } = screen @@ -592,16 +592,16 @@ ruleTester.run(RULE_NAME, rule, { return findByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { findByText } = render() @@ -609,16 +609,16 @@ ruleTester.run(RULE_NAME, rule, { return findByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { getByText } = screen @@ -626,16 +626,16 @@ ruleTester.run(RULE_NAME, rule, { getByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { getByText } = render() @@ -643,16 +643,16 @@ ruleTester.run(RULE_NAME, rule, { getByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { findByText } = screen @@ -660,16 +660,16 @@ ruleTester.run(RULE_NAME, rule, { findByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { findByText } = render() @@ -677,103 +677,103 @@ ruleTester.run(RULE_NAME, rule, { findByText('hey') }) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { findByText } = screen await waitForElementToBeRemoved(() => findByText('hey')) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { findByText } = render() await waitForElementToBeRemoved(() => findByText('hey')) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { getByText } = screen await waitForElementToBeRemoved(getByText('hey')) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { getByText } = render() await waitForElementToBeRemoved(getByText('hey')) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { screen } from '${testingFramework}'; const { findByText } = screen await waitForElementToBeRemoved(findByText('hey')) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + { + code: ` import { render } from '${testingFramework}'; const { findByText } = render() await waitForElementToBeRemoved(findByText('hey')) `, - errors: [ - { - messageId: 'preferQueryByDisappearance', - line: 5, - column: 41, - }, - ], - }, - ]), + errors: [ + { + messageId: 'preferQueryByDisappearance', + line: 5, + column: 41, + }, + ], + }, + ]), }); diff --git a/tests/lib/rules/prefer-screen-queries.test.ts b/tests/lib/rules/prefer-screen-queries.test.ts index 823e2c4d..7c3b4a93 100644 --- a/tests/lib/rules/prefer-screen-queries.test.ts +++ b/tests/lib/rules/prefer-screen-queries.test.ts @@ -1,60 +1,60 @@ import rule, { RULE_NAME } from '../../../lib/rules/prefer-screen-queries'; import { - ALL_QUERIES_COMBINATIONS, - ALL_QUERIES_VARIANTS, - combineQueries, + ALL_QUERIES_COMBINATIONS, + ALL_QUERIES_VARIANTS, + combineQueries, } from '../../../lib/utils'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/dom', - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/dom', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; const CUSTOM_QUERY_COMBINATIONS = combineQueries(ALL_QUERIES_VARIANTS, [ - 'ByIcon', + 'ByIcon', ]); ruleTester.run(RULE_NAME, rule, { - valid: [ - { - code: `const baz = () => 'foo'`, - }, - ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: `screen.${queryMethod}()`, - })), - { - code: `otherFunctionShouldNotThrow()`, - }, - { - code: `component.otherFunctionShouldNotThrow()`, - }, - ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: `within(component).${queryMethod}()`, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: `within(screen.${queryMethod}()).${queryMethod}()`, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + valid: [ + { + code: `const baz = () => 'foo'`, + }, + ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: `screen.${queryMethod}()`, + })), + { + code: `otherFunctionShouldNotThrow()`, + }, + { + code: `component.otherFunctionShouldNotThrow()`, + }, + ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: `within(component).${queryMethod}()`, + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: `within(screen.${queryMethod}()).${queryMethod}()`, + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` const { ${queryMethod} } = within(screen.getByText('foo')) ${queryMethod}(baz) `, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ - code: ` + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` const myWithinVariable = within(foo) myWithinVariable.${queryMethod}('baz') `, - })), - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...CUSTOM_QUERY_COMBINATIONS.map((query) => ({ - code: ` + })), + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + ...CUSTOM_QUERY_COMBINATIONS.map((query) => ({ + code: ` import { render } from '${testingFramework}' import { ${query} } from 'custom-queries' @@ -63,9 +63,9 @@ ruleTester.run(RULE_NAME, rule, { ${query}('bar') }) `, - })), - ...CUSTOM_QUERY_COMBINATIONS.map((query) => ({ - code: ` + })), + ...CUSTOM_QUERY_COMBINATIONS.map((query) => ({ + code: ` import { render } from '${testingFramework}' test("render-returned custom queries, since they can't be used through screen", () => { @@ -73,12 +73,12 @@ ruleTester.run(RULE_NAME, rule, { ${query}('bar') }) `, - })), - ...CUSTOM_QUERY_COMBINATIONS.map((query) => ({ - settings: { - 'testing-library/custom-queries': [query, 'ByComplexText'], - }, - code: ` + })), + ...CUSTOM_QUERY_COMBINATIONS.map((query) => ({ + settings: { + 'testing-library/custom-queries': [query, 'ByComplexText'], + }, + code: ` import { render } from '${testingFramework}' test("custom queries + custom-queries setting, since they can't be used through screen", () => { @@ -86,348 +86,348 @@ ruleTester.run(RULE_NAME, rule, { ${query}('bar') }) `, - })), - ]), - { - code: ` + })), + ]), + { + code: ` const screen = render(baz); screen.container.querySelector('foo'); `, - }, - { - code: ` + }, + { + code: ` const screen = render(baz); screen.baseElement.querySelector('foo'); `, - }, - { - code: ` + }, + { + code: ` const { rerender } = render(baz); rerender(); `, - }, - { - code: ` + }, + { + code: ` const utils = render(baz); utils.rerender(); `, - }, - { - code: ` + }, + { + code: ` const utils = render(baz); utils.asFragment(); `, - }, - { - code: ` + }, + { + code: ` const { asFragment } = render(baz); asFragment(); `, - }, - { - code: ` + }, + { + code: ` const { unmount } = render(baz); unmount(); `, - }, - { - code: ` + }, + { + code: ` const utils = render(baz); utils.unmount(); `, - }, - ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ - code: ` + }, + ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ + code: ` const { ${queryMethod} } = render(baz, { baseElement: treeA }) expect(${queryMethod}(baz)).toBeDefined() `, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ - code: ` + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ + code: ` const { ${queryMethod}: aliasMethod } = render(baz, { baseElement: treeA }) expect(aliasMethod(baz)).toBeDefined() `, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ - code: ` + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ + code: ` const { ${queryMethod} } = render(baz, { container: treeA }) expect(${queryMethod}(baz)).toBeDefined() `, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ - code: ` + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ + code: ` const { ${queryMethod}: aliasMethod } = render(baz, { container: treeA }) expect(aliasMethod(baz)).toBeDefined() `, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ - code: ` + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ + code: ` const { ${queryMethod} } = render(baz, { baseElement: treeB, container: treeA }) expect(${queryMethod}(baz)).toBeDefined() `, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ - code: ` + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ + code: ` const { ${queryMethod}: aliasMethod } = render(baz, { baseElement: treeB, container: treeA }) expect(aliasMethod(baz)).toBeDefined() `, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ - code: ` + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ + code: ` render(foo, { baseElement: treeA }).${queryMethod}() `, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render as testUtilRender } from 'test-utils' import { render } from 'somewhere-else' const { ${queryMethod} } = render(foo) ${queryMethod}()`, - })), - ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ - settings: { - 'testing-library/custom-renders': ['customRender'], - }, - code: ` + })), + ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({ + settings: { + 'testing-library/custom-renders': ['customRender'], + }, + code: ` import { anotherRender } from 'whatever' const { ${queryMethod} } = anotherRender(foo) ${queryMethod}()`, - })), - ], + })), + ], - invalid: [ - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - code: ` + invalid: [ + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + code: ` const { ${queryMethod} } = render(foo) ${queryMethod}()`, - errors: [ - { - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from 'test-utils' const { ${queryMethod} } = render(foo) ${queryMethod}()`, - errors: [ - { - line: 4, - column: 9, - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), + errors: [ + { + line: 4, + column: 9, + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - settings: { - 'testing-library/custom-renders': ['customRender'], - }, - code: ` + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + settings: { + 'testing-library/custom-renders': ['customRender'], + }, + code: ` import { customRender } from 'whatever' const { ${queryMethod} } = customRender(foo) ${queryMethod}()`, - errors: [ - { - line: 4, - column: 9, - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => - ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + line: 4, + column: 9, + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => + ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render as testingLibraryRender} from '${testingFramework}' const { ${queryMethod} } = testingLibraryRender(foo) ${queryMethod}()`, - errors: [ - { - line: 4, - column: 9, - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ) - ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + line: 4, + column: 9, + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ) + ), + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from 'test-utils' const { ${queryMethod} } = render(foo) ${queryMethod}()`, - errors: [ - { - line: 4, - column: 9, - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - code: `render().${queryMethod}()`, - errors: [ - { - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - code: `render(foo, { hydrate: true }).${queryMethod}()`, - errors: [ - { - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - code: `component.${queryMethod}()`, - errors: [ - { - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - code: ` + errors: [ + { + line: 4, + column: 9, + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + code: `render().${queryMethod}()`, + errors: [ + { + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + code: `render(foo, { hydrate: true }).${queryMethod}()`, + errors: [ + { + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + code: `component.${queryMethod}()`, + errors: [ + { + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + code: ` const { ${queryMethod} } = render() ${queryMethod}(baz) `, - errors: [ - { - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - code: ` + errors: [ + { + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + code: ` const myRenderVariable = render() myRenderVariable.${queryMethod}(baz) `, - errors: [ - { - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - code: ` + errors: [ + { + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + code: ` const [myVariable] = render() myVariable.${queryMethod}(baz) `, - errors: [ - { - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - code: ` + errors: [ + { + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + code: ` const { ${queryMethod} } = render(baz, { hydrate: true }) ${queryMethod}(baz) `, - errors: [ - { - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...ALL_QUERIES_COMBINATIONS.map( - (queryMethod) => - ({ - code: ` + errors: [ + { + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...ALL_QUERIES_COMBINATIONS.map( + (queryMethod) => + ({ + code: ` const [myVariable] = within() myVariable.${queryMethod}(baz) `, - errors: [ - { - messageId: 'preferScreenQueries', - data: { - name: queryMethod, - }, - }, - ], - } as const) - ), - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` // issue #367 - example A + errors: [ + { + messageId: 'preferScreenQueries', + data: { + name: queryMethod, + }, + }, + ], + } as const) + ), + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` // issue #367 - example A import { render } from '${testingFramework}'; function setup() { @@ -444,27 +444,27 @@ ruleTester.run(RULE_NAME, rule, { expect(getByText('foo')).toBeInTheDocument(); }); `, - errors: [ - { - messageId: 'preferScreenQueries', - line: 10, - column: 16, - data: { - name: 'getByText', - }, - }, - { - messageId: 'preferScreenQueries', - line: 15, - column: 16, - data: { - name: 'getByText', - }, - }, - ], - } as const, - { - code: ` // issue #367 - example B + errors: [ + { + messageId: 'preferScreenQueries', + line: 10, + column: 16, + data: { + name: 'getByText', + }, + }, + { + messageId: 'preferScreenQueries', + line: 15, + column: 16, + data: { + name: 'getByText', + }, + }, + ], + } as const, + { + code: ` // issue #367 - example B import { render } from '${testingFramework}'; function setup() { @@ -482,25 +482,25 @@ ruleTester.run(RULE_NAME, rule, { expect(getByText('foo')).toBe('foo'); }); `, - errors: [ - { - messageId: 'preferScreenQueries', - line: 10, - column: 16, - data: { - name: 'getByText', - }, - }, - { - messageId: 'preferScreenQueries', - line: 16, - column: 16, - data: { - name: 'getByText', - }, - }, - ], - } as const, - ]), - ], + errors: [ + { + messageId: 'preferScreenQueries', + line: 10, + column: 16, + data: { + name: 'getByText', + }, + }, + { + messageId: 'preferScreenQueries', + line: 16, + column: 16, + data: { + name: 'getByText', + }, + }, + ], + } as const, + ]), + ], }); diff --git a/tests/lib/rules/prefer-user-event.test.ts b/tests/lib/rules/prefer-user-event.test.ts index 5cf29e47..61892761 100644 --- a/tests/lib/rules/prefer-user-event.test.ts +++ b/tests/lib/rules/prefer-user-event.test.ts @@ -1,614 +1,614 @@ import { TSESLint } from '@typescript-eslint/utils'; import rule, { - MAPPING_TO_USER_EVENT, - MessageIds, - Options, - RULE_NAME, - UserEventMethods, + MAPPING_TO_USER_EVENT, + MessageIds, + Options, + RULE_NAME, + UserEventMethods, } from '../../../lib/rules/prefer-user-event'; import { LIBRARY_MODULES } from '../../../lib/utils'; import { createRuleTester } from '../test-utils'; function createScenarioWithImport< - T extends - | TSESLint.InvalidTestCase - | TSESLint.ValidTestCase + T extends + | TSESLint.InvalidTestCase + | TSESLint.ValidTestCase >(callback: (libraryModule: string, fireEventMethod: string) => T) { - return LIBRARY_MODULES.reduce( - (acc: Array, libraryModule) => - acc.concat( - Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => - callback(libraryModule, fireEventMethod) - ) - ), - [] - ); + return LIBRARY_MODULES.reduce( + (acc: Array, libraryModule) => + acc.concat( + Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => + callback(libraryModule, fireEventMethod) + ) + ), + [] + ); } const ruleTester = createRuleTester(); function formatUserEventMethodsMessage(fireEventMethod: string): string { - const userEventMethods = MAPPING_TO_USER_EVENT[fireEventMethod].map( - (methodName) => `userEvent.${methodName}` - ); - let joinedList = ''; + const userEventMethods = MAPPING_TO_USER_EVENT[fireEventMethod].map( + (methodName) => `userEvent.${methodName}` + ); + let joinedList = ''; - for (let i = 0; i < userEventMethods.length; i++) { - const item = userEventMethods[i]; - if (i === 0) { - joinedList += item; - } else if (i + 1 === userEventMethods.length) { - joinedList += `, or ${item}`; - } else { - joinedList += `, ${item}`; - } - } + for (let i = 0; i < userEventMethods.length; i++) { + const item = userEventMethods[i]; + if (i === 0) { + joinedList += item; + } else if (i + 1 === userEventMethods.length) { + joinedList += `, or ${item}`; + } else { + joinedList += `, ${item}`; + } + } - return joinedList; + return joinedList; } ruleTester.run(RULE_NAME, rule, { - valid: [ - { - code: ` + valid: [ + { + code: ` import { screen } from '@testing-library/user-event' const element = screen.getByText(foo) `, - }, - { - code: ` + }, + { + code: ` const utils = render(baz) const element = utils.getByText(foo) `, - }, - ...UserEventMethods.map((userEventMethod) => ({ - code: ` + }, + ...UserEventMethods.map((userEventMethod) => ({ + code: ` import userEvent from '@testing-library/user-event' const node = document.createElement(elementType) userEvent.${userEventMethod}(foo) `, - })), - ...createScenarioWithImport>( - (libraryModule: string, fireEventMethod: string) => ({ - code: ` + })), + ...createScenarioWithImport>( + (libraryModule: string, fireEventMethod: string) => ({ + code: ` import { fireEvent } from '${libraryModule}' const node = document.createElement(elementType) fireEvent.${fireEventMethod}(foo) `, - options: [{ allowedMethods: [fireEventMethod] }], - }) - ), - ...createScenarioWithImport>( - (libraryModule: string, fireEventMethod: string) => ({ - code: ` + options: [{ allowedMethods: [fireEventMethod] }], + }) + ), + ...createScenarioWithImport>( + (libraryModule: string, fireEventMethod: string) => ({ + code: ` import { fireEvent as fireEventAliased } from '${libraryModule}' const node = document.createElement(elementType) fireEventAliased.${fireEventMethod}(foo) `, - options: [{ allowedMethods: [fireEventMethod] }], - }) - ), - ...createScenarioWithImport>( - (libraryModule: string, fireEventMethod: string) => ({ - code: ` + options: [{ allowedMethods: [fireEventMethod] }], + }) + ), + ...createScenarioWithImport>( + (libraryModule: string, fireEventMethod: string) => ({ + code: ` import * as dom from '${libraryModule}' dom.fireEvent.${fireEventMethod}(foo) `, - options: [{ allowedMethods: [fireEventMethod] }], - }) - ), - ...LIBRARY_MODULES.map((libraryModule) => ({ - // imported fireEvent and not used, - code: ` + options: [{ allowedMethods: [fireEventMethod] }], + }) + ), + ...LIBRARY_MODULES.map((libraryModule) => ({ + // imported fireEvent and not used, + code: ` import { fireEvent } from '${libraryModule}' import * as foo from 'someModule' foo.baz() `, - })), - ...LIBRARY_MODULES.map((libraryModule) => ({ - // imported dom, but not using fireEvent - code: ` + })), + ...LIBRARY_MODULES.map((libraryModule) => ({ + // imported dom, but not using fireEvent + code: ` import * as dom from '${libraryModule}' const button = dom.screen.getByRole('button') const foo = dom.screen.container.querySelector('baz') `, - })), - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: ` + })), + ...LIBRARY_MODULES.map((libraryModule) => ({ + code: ` import { fireEvent as aliasedFireEvent } from '${libraryModule}' function fireEvent() { console.log('foo') } fireEvent() `, - })), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { screen } from 'test-utils' const element = screen.getByText(foo) `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { render } from 'test-utils' const utils = render(baz) const element = utils.getByText(foo) `, - }, - ...UserEventMethods.map((userEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + ...UserEventMethods.map((userEventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import userEvent from 'test-utils' const node = document.createElement(elementType) userEvent.${userEventMethod}(foo) `, - })), - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod: string) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod: string) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` // fireEvent method used but not imported from TL related module // (aggressive reporting opted out) import { fireEvent } from 'somewhere-else' fireEvent.${fireEventMethod}(foo) `, - })), - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod: string) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + })), + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod: string) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent } from 'test-utils' const node = document.createElement(elementType) fireEvent.${fireEventMethod}(foo) `, - options: [{ allowedMethods: [fireEventMethod] }], - })), - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod: string) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + options: [{ allowedMethods: [fireEventMethod] }], + })), + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod: string) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent as fireEventAliased } from 'test-utils' const node = document.createElement(elementType) fireEventAliased.${fireEventMethod}(foo) `, - options: [{ allowedMethods: [fireEventMethod] }], - })), - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod: string) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + options: [{ allowedMethods: [fireEventMethod] }], + })), + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod: string) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import * as dom from 'test-utils' dom.fireEvent.${fireEventMethod}(foo) `, - options: [{ allowedMethods: [fireEventMethod] }], - })), - // edge case for coverage: - // valid use case without call expression - // so there is no innermost function scope found - ` + options: [{ allowedMethods: [fireEventMethod] }], + })), + // edge case for coverage: + // valid use case without call expression + // so there is no innermost function scope found + ` import { fireEvent } from '@testing-library/react'; test('edge case for no innermost function scope', () => { const click = fireEvent.click }) `, - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent, createEvent } from 'test-utils' const event = createEvent.${fireEventMethod}(node) fireEvent(node, event) `, - options: [{ allowedMethods: [fireEventMethod] }], - })), - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + options: [{ allowedMethods: [fireEventMethod] }], + })), + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent as fireEventAliased, createEvent } from 'test-utils' fireEventAliased(node, createEvent.${fireEventMethod}(node)) `, - options: [{ allowedMethods: [fireEventMethod] }], - })), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + options: [{ allowedMethods: [fireEventMethod] }], + })), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent, createEvent } from 'test-utils' const event = createEvent.drop(node) fireEvent(node, event) `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent, createEvent } from 'test-utils' const event = createEvent('drop', node) fireEvent(node, event) `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent as fireEventAliased, createEvent as createEventAliased } from 'test-utils' const event = createEventAliased.drop(node) fireEventAliased(node, event) `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent as fireEventAliased, createEvent as createEventAliased } from 'test-utils' const event = createEventAliased('drop', node) fireEventAliased(node, event) `, - }, - { - code: ` + }, + { + code: ` const createEvent = () => 'Event'; const event = createEvent(); `, - }, - ], - invalid: [ - ...createScenarioWithImport>( - (libraryModule: string, fireEventMethod: string) => ({ - code: ` + }, + ], + invalid: [ + ...createScenarioWithImport>( + (libraryModule: string, fireEventMethod: string) => ({ + code: ` import { fireEvent } from '${libraryModule}' const node = document.createElement(elementType) fireEvent.${fireEventMethod}(foo) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 4, - column: 9, - data: { - userEventMethods: formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - }, - ], - }) - ), - ...createScenarioWithImport>( - (libraryModule: string, fireEventMethod: string) => ({ - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 4, + column: 9, + data: { + userEventMethods: formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + }, + ], + }) + ), + ...createScenarioWithImport>( + (libraryModule: string, fireEventMethod: string) => ({ + code: ` import * as dom from '${libraryModule}' dom.fireEvent.${fireEventMethod}(foo) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 3, - column: 9, - data: { - userEventMethods: formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - }, - ], - }) - ), - ...createScenarioWithImport>( - (libraryModule: string, fireEventMethod: string) => ({ - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 3, + column: 9, + data: { + userEventMethods: formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + }, + ], + }) + ), + ...createScenarioWithImport>( + (libraryModule: string, fireEventMethod: string) => ({ + code: ` const { fireEvent } = require('${libraryModule}') fireEvent.${fireEventMethod}(foo) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 3, - column: 9, - data: { - userEventMethods: formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - }, - ], - }) - ), - ...createScenarioWithImport>( - (libraryModule: string, fireEventMethod: string) => ({ - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 3, + column: 9, + data: { + userEventMethods: formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + }, + ], + }) + ), + ...createScenarioWithImport>( + (libraryModule: string, fireEventMethod: string) => ({ + code: ` const rtl = require('${libraryModule}') rtl.fireEvent.${fireEventMethod}(foo) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 3, - column: 9, - data: { - userEventMethods: formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - }, - ], - }) - ), - ...Object.keys(MAPPING_TO_USER_EVENT).map( - (fireEventMethod: string) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 3, + column: 9, + data: { + userEventMethods: formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + }, + ], + }) + ), + ...Object.keys(MAPPING_TO_USER_EVENT).map( + (fireEventMethod: string) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import * as dom from 'test-utils' dom.fireEvent.${fireEventMethod}(foo) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 3, - column: 9, - data: { - userEventMethods: - formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - }, - ], - } as const) - ), - ...Object.keys(MAPPING_TO_USER_EVENT).map( - (fireEventMethod: string) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 3, + column: 9, + data: { + userEventMethods: + formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + }, + ], + } as const) + ), + ...Object.keys(MAPPING_TO_USER_EVENT).map( + (fireEventMethod: string) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent } from 'test-utils' fireEvent.${fireEventMethod}(foo) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 3, - column: 9, - data: { - userEventMethods: - formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - }, - ], - } as const) - ), - ...Object.keys(MAPPING_TO_USER_EVENT).map( - (fireEventMethod: string) => - ({ - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 3, + column: 9, + data: { + userEventMethods: + formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + }, + ], + } as const) + ), + ...Object.keys(MAPPING_TO_USER_EVENT).map( + (fireEventMethod: string) => + ({ + code: ` // same as previous group of test cases but without custom module set // (aggressive reporting) import { fireEvent } from 'test-utils' fireEvent.${fireEventMethod}(foo) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 5, - column: 9, - data: { - userEventMethods: - formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - }, - ], - } as const) - ), - ...Object.keys(MAPPING_TO_USER_EVENT).map( - (fireEventMethod: string) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 5, + column: 9, + data: { + userEventMethods: + formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + }, + ], + } as const) + ), + ...Object.keys(MAPPING_TO_USER_EVENT).map( + (fireEventMethod: string) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent as fireEventAliased } from 'test-utils' fireEventAliased.${fireEventMethod}(foo) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 3, - column: 9, - data: { - userEventMethods: - formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - }, - ], - } as const) - ), - { - code: ` // simple test to check error in detail + errors: [ + { + messageId: 'preferUserEvent', + line: 3, + column: 9, + data: { + userEventMethods: + formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + }, + ], + } as const) + ), + { + code: ` // simple test to check error in detail import { fireEvent } from '@testing-library/react' fireEvent.click(element) fireEvent.mouseOut(element) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 4, - endLine: 4, - column: 7, - endColumn: 22, - data: { - userEventMethods: - 'userEvent.click, userEvent.type, userEvent.selectOptions, or userEvent.deselectOptions', - fireEventMethod: 'click', - }, - }, - { - messageId: 'preferUserEvent', - line: 5, - endLine: 5, - column: 7, - endColumn: 25, - data: { - userEventMethods: 'userEvent.unhover', - fireEventMethod: 'mouseOut', - }, - }, - ], - }, - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 4, + endLine: 4, + column: 7, + endColumn: 22, + data: { + userEventMethods: + 'userEvent.click, userEvent.type, userEvent.selectOptions, or userEvent.deselectOptions', + fireEventMethod: 'click', + }, + }, + { + messageId: 'preferUserEvent', + line: 5, + endLine: 5, + column: 7, + endColumn: 25, + data: { + userEventMethods: 'userEvent.unhover', + fireEventMethod: 'mouseOut', + }, + }, + ], + }, + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent, createEvent } from 'test-utils' fireEvent(node, createEvent('${fireEventMethod}', node)) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 4, - column: 9, - data: { - userEventMethods: formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - } as const, - ], - })), - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 4, + column: 9, + data: { + userEventMethods: formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + } as const, + ], + })), + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent, createEvent } from 'test-utils' fireEvent(node, createEvent.${fireEventMethod}(node)) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 4, - column: 9, - data: { - userEventMethods: formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - } as const, - ], - })), - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 4, + column: 9, + data: { + userEventMethods: formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + } as const, + ], + })), + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent, createEvent } from 'test-utils' const event = createEvent.${fireEventMethod}(node) fireEvent(node, event) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 4, - column: 9, - data: { - userEventMethods: formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - } as const, - ], - })), - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 4, + column: 9, + data: { + userEventMethods: formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + } as const, + ], + })), + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import { fireEvent as fireEventAliased, createEvent as createEventAliased } from 'test-utils' const eventValid = createEventAliased.drop(node) fireEventAliased(node, eventValid) const eventInvalid = createEventAliased.${fireEventMethod}(node) fireEventAliased(node, eventInvalid) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 6, - column: 9, - data: { - userEventMethods: formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - } as const, - ], - })), - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 6, + column: 9, + data: { + userEventMethods: formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + } as const, + ], + })), + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import * as dom from 'test-utils' const eventValid = dom.createEvent.drop(node) dom.fireEvent(node, eventValid) const eventInvalid = dom.createEvent.${fireEventMethod}(node) dom.fireEvent(node, eventInvalid) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 6, - column: 9, - data: { - userEventMethods: formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - } as const, - ], - })), - ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + errors: [ + { + messageId: 'preferUserEvent', + line: 6, + column: 9, + data: { + userEventMethods: formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + } as const, + ], + })), + ...Object.keys(MAPPING_TO_USER_EVENT).map((fireEventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` import * as dom from 'test-utils' // valid event dom.fireEvent(node, dom.createEvent.drop(node)) // invalid event dom.fireEvent(node, dom.createEvent.${fireEventMethod}(node)) `, - errors: [ - { - messageId: 'preferUserEvent', - line: 6, - column: 9, - data: { - userEventMethods: formatUserEventMethodsMessage(fireEventMethod), - fireEventMethod, - }, - } as const, - ], - })), - ], + errors: [ + { + messageId: 'preferUserEvent', + line: 6, + column: 9, + data: { + userEventMethods: formatUserEventMethodsMessage(fireEventMethod), + fireEventMethod, + }, + } as const, + ], + })), + ], }); diff --git a/tests/lib/rules/prefer-wait-for.test.ts b/tests/lib/rules/prefer-wait-for.test.ts index ae6a85c4..d9dec019 100644 --- a/tests/lib/rules/prefer-wait-for.test.ts +++ b/tests/lib/rules/prefer-wait-for.test.ts @@ -5,224 +5,224 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); ruleTester.run(RULE_NAME, rule, { - valid: [ - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `import { waitFor, render } from '${libraryModule}'; + valid: [ + ...LIBRARY_MODULES.map((libraryModule) => ({ + code: `import { waitFor, render } from '${libraryModule}'; async () => { await waitFor(() => {}); }`, - })), - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `const { waitFor, render } = require('${libraryModule}'); + })), + ...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'; + })), + { + 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'); + }, + { + 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}'; + }, + ...LIBRARY_MODULES.map((libraryModule) => ({ + code: `import { waitForElementToBeRemoved, render } from '${libraryModule}'; async () => { await waitForElementToBeRemoved(() => {}); }`, - })), - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `const { waitForElementToBeRemoved, render } = require('${libraryModule}'); + })), + ...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'; + })), + { + 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'); + }, + { + 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}'; + }, + ...LIBRARY_MODULES.map((libraryModule) => ({ + code: `import * as testingLibrary from '${libraryModule}'; async () => { await testingLibrary.waitForElementToBeRemoved(() => {}); }`, - })), - ...LIBRARY_MODULES.map((libraryModule) => ({ - code: `const testingLibrary = require('${libraryModule}'); + })), + ...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'; + })), + { + 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'); + }, + { + 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}'; + }, + ...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}'); + })), + ...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'; + })), + { + 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'); + }, + { + 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}'; + }, + ...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}'); + })), + ...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'; + })), + { + 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'); + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `const testingLibrary = require('test-utils'); async () => { await testingLibrary.waitFor(() => {}, { timeout: 500 }); }`, - }, - { - code: `import { wait } from 'imNoTestingLibrary'; + }, + { + code: `import { wait } from 'imNoTestingLibrary'; async () => { await wait(); }`, - }, - { - code: `const { wait } = require('imNoTestingLibrary'); + }, + { + code: `const { wait } = require('imNoTestingLibrary'); async () => { await wait(); }`, - }, - { - code: `import * as foo from 'imNoTestingLibrary'; + }, + { + code: `import * as foo from 'imNoTestingLibrary'; async () => { await foo.wait(); }`, - }, - { - code: `const foo = require('imNoTestingLibrary'); + }, + { + code: `const foo = require('imNoTestingLibrary'); async () => { await foo.wait(); }`, - }, - { - code: `import * as foo from 'imNoTestingLibrary'; + }, + { + code: `import * as foo from 'imNoTestingLibrary'; cy.wait(); `, - }, - { - code: `const foo = require('imNoTestingLibrary'); + }, + { + code: `const foo = require('imNoTestingLibrary'); cy.wait(); `, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` + }, + { + 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' @@ -232,10 +232,10 @@ ruleTester.run(RULE_NAME, rule, { await wait(); } `, - }, - { - // https://github.com/testing-library/eslint-plugin-testing-library/issues/145 - code: `import * as foo from 'imNoTestingLibrary'; + }, + { + // https://github.com/testing-library/eslint-plugin-testing-library/issues/145 + code: `import * as foo from 'imNoTestingLibrary'; async function wait(): Promise { // doesn't matter } @@ -244,10 +244,10 @@ ruleTester.run(RULE_NAME, rule, { await wait(); } `, - }, - { - // https://github.com/testing-library/eslint-plugin-testing-library/issues/145 - code: `const foo = require('imNoTestingLibrary'); + }, + { + // https://github.com/testing-library/eslint-plugin-testing-library/issues/145 + code: `const foo = require('imNoTestingLibrary'); async function wait(): Promise { // doesn't matter } @@ -256,1092 +256,1092 @@ ruleTester.run(RULE_NAME, rule, { await wait(); } `, - }, - ], - - invalid: [ - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { wait, render } from '${libraryModule}'; + }, + ], + + 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}'; + 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}'); + } 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}'); + 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'; + } 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'; + 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'); + }, + { + 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'); + 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}'; + }, + // 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}'; + 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}'); + } 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}'); + 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'; + } 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'; + 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'); + }, + { + 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'); + 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}'; + }, + // 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}'; + 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}'); + } 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}'); + 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'; + } 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'; + 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'); + }, + { + 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'); + 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}' + }, + ...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}'; + 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}'); + } 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}'); + 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' + } 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'; + 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'); + }, + { + 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'); + 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}"; + }, + // 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}'; + 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}"; + } 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}'; + 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"; + } 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'; + 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'); + }, + { + 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'); + 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}' + }, + ...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}'; + 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}'); + } 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}'); + 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' + } 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'; + 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'); + }, + { + 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'); + 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}'; + }, + ...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}'; + 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}'); + } 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}'); + 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'; + } 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'; + 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'); + }, + { + 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'); + 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}'; + }, + ...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}'; + 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}'); + } 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}'); + 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'; + } 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'; + 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'); + }, + { + 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'); + 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}'; + }, + ...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}'; + 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}'); + } 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}'); + 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'; + } 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'; + 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'); + }, + { + 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'); + 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}'; + }, + ...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}'; + 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}'); + } 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}'); + 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'; + } 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'; + 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'); + }, + { + 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'); + 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}'; + }, + ...LIBRARY_MODULES.map( + (libraryModule) => + ({ + code: `import { waitForDomChange, wait, waitForElement } from '${libraryModule}'; import userEvent from '@testing-library/user-event'; async () => { @@ -1350,34 +1350,34 @@ ruleTester.run(RULE_NAME, rule, { 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}'; + 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 () => { @@ -1386,12 +1386,12 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { waitForDomChange, wait, waitForElement } = require('${libraryModule}'); + } as const) + ), + ...LIBRARY_MODULES.map( + (libraryModule) => + ({ + code: `const { waitForDomChange, wait, waitForElement } = require('${libraryModule}'); const userEvent = require('@testing-library/user-event'); async () => { @@ -1400,34 +1400,34 @@ ruleTester.run(RULE_NAME, rule, { 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}'); + 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 () => { @@ -1436,13 +1436,13 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { waitForDomChange, wait, waitForElement } from 'test-utils'; + } 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 () => { @@ -1451,34 +1451,34 @@ ruleTester.run(RULE_NAME, rule, { 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'; + 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 () => { @@ -1487,12 +1487,12 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { waitForDomChange, wait, waitForElement } = require('test-utils'); + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `const { waitForDomChange, wait, waitForElement } = require('test-utils'); const userEvent = require('@testing-library/user-event'); async () => { @@ -1501,34 +1501,34 @@ ruleTester.run(RULE_NAME, rule, { 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'); + 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 () => { @@ -1537,11 +1537,11 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { render, waitForDomChange, wait, waitForElement } from '${libraryModule}'; + }, + ...LIBRARY_MODULES.map( + (libraryModule) => + ({ + code: `import { render, waitForDomChange, wait, waitForElement } from '${libraryModule}'; async () => { await waitForDomChange({ timeout: 5000 }); @@ -1549,34 +1549,34 @@ ruleTester.run(RULE_NAME, rule, { 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}'; + 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 }); @@ -1584,12 +1584,12 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { render, waitForDomChange, wait, waitForElement } = require('${libraryModule}'); + } as const) + ), + ...LIBRARY_MODULES.map( + (libraryModule) => + ({ + code: `const { render, waitForDomChange, wait, waitForElement } = require('${libraryModule}'); async () => { await waitForDomChange({ timeout: 5000 }); @@ -1597,34 +1597,34 @@ ruleTester.run(RULE_NAME, rule, { 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}'); + 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 }); @@ -1632,13 +1632,13 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { render, waitForDomChange, wait, waitForElement } from 'test-utils'; + } as const) + ), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `import { render, waitForDomChange, wait, waitForElement } from 'test-utils'; async () => { await waitForDomChange({ timeout: 5000 }); @@ -1646,34 +1646,34 @@ ruleTester.run(RULE_NAME, rule, { 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'; + 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 }); @@ -1681,12 +1681,12 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { render, waitForDomChange, wait, waitForElement } = require('test-utils'); + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `const { render, waitForDomChange, wait, waitForElement } = require('test-utils'); async () => { await waitForDomChange({ timeout: 5000 }); @@ -1694,34 +1694,34 @@ ruleTester.run(RULE_NAME, rule, { 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'); + 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 }); @@ -1729,11 +1729,11 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { waitForDomChange, wait, render, waitForElement } from '${libraryModule}'; + }, + ...LIBRARY_MODULES.map( + (libraryModule) => + ({ + code: `import { waitForDomChange, wait, render, waitForElement } from '${libraryModule}'; async () => { await waitForDomChange({ timeout: 5000 }); @@ -1741,34 +1741,34 @@ ruleTester.run(RULE_NAME, rule, { 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}'; + 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 }); @@ -1776,12 +1776,12 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { waitForDomChange, wait, render, waitForElement } = require('${libraryModule}'); + } as const) + ), + ...LIBRARY_MODULES.map( + (libraryModule) => + ({ + code: `const { waitForDomChange, wait, render, waitForElement } = require('${libraryModule}'); async () => { await waitForDomChange({ timeout: 5000 }); @@ -1789,34 +1789,34 @@ ruleTester.run(RULE_NAME, rule, { 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}'); + 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 }); @@ -1824,13 +1824,13 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { waitForDomChange, wait, render, waitForElement } from 'test-utils'; + } as const) + ), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `import { waitForDomChange, wait, render, waitForElement } from 'test-utils'; async () => { await waitForDomChange({ timeout: 5000 }); @@ -1838,34 +1838,34 @@ ruleTester.run(RULE_NAME, rule, { 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'; + 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 }); @@ -1873,12 +1873,12 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { waitForDomChange, wait, render, waitForElement } = require('test-utils'); + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `const { waitForDomChange, wait, render, waitForElement } = require('test-utils'); async () => { await waitForDomChange({ timeout: 5000 }); @@ -1886,34 +1886,34 @@ ruleTester.run(RULE_NAME, rule, { 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'); + 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 }); @@ -1921,11 +1921,11 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - }, - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `import { + }, + ...LIBRARY_MODULES.map( + (libraryModule) => + ({ + code: `import { waitForDomChange, wait, render, @@ -1938,34 +1938,34 @@ ruleTester.run(RULE_NAME, rule, { 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}'; + 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 }); @@ -1973,12 +1973,12 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - } as const) - ), - ...LIBRARY_MODULES.map( - (libraryModule) => - ({ - code: `const { + } as const) + ), + ...LIBRARY_MODULES.map( + (libraryModule) => + ({ + code: `const { waitForDomChange, wait, render, @@ -1991,34 +1991,34 @@ ruleTester.run(RULE_NAME, rule, { 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}'); + 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 }); @@ -2026,13 +2026,13 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - } as const) - ), - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `import { + } as const) + ), + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `import { waitForDomChange, wait, render, @@ -2045,34 +2045,34 @@ ruleTester.run(RULE_NAME, rule, { 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'; + 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 }); @@ -2080,12 +2080,12 @@ ruleTester.run(RULE_NAME, rule, { await waitFor(() => {}); await waitFor(() => { doSomething() }); }`, - }, - { - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: `const { + }, + { + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: `const { waitForDomChange, wait, render, @@ -2098,34 +2098,34 @@ ruleTester.run(RULE_NAME, rule, { 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'); + 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 }); @@ -2133,126 +2133,126 @@ ruleTester.run(RULE_NAME, rule, { 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}'; + }, + ...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}'; + 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}'); + } 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}'); + 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'; + } 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'; + 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'); + }, + { + 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'); + 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); }`, - }, - ], + }, + ], }); diff --git a/tests/lib/rules/render-result-naming-convention.test.ts b/tests/lib/rules/render-result-naming-convention.test.ts index d3f72d1b..b2715300 100644 --- a/tests/lib/rules/render-result-naming-convention.test.ts +++ b/tests/lib/rules/render-result-naming-convention.test.ts @@ -1,22 +1,22 @@ import rule, { - RULE_NAME, + RULE_NAME, } from '../../../lib/rules/render-result-naming-convention'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/angular', - '@testing-library/react', - '@testing-library/vue', - '@marko/testing-library', + '@testing-library/angular', + '@testing-library/react', + '@testing-library/vue', + '@marko/testing-library', ]; ruleTester.run(RULE_NAME, rule, { - valid: [ - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + valid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { render } from '${testingFramework}'; test('should not report straight destructured render result', () => { @@ -24,9 +24,9 @@ ruleTester.run(RULE_NAME, rule, { const button = getByText('some button'); }); `, - }, - { - code: ` + }, + { + code: ` import * as RTL from '${testingFramework}'; test('should not report straight destructured render result from wildcard import', () => { @@ -34,9 +34,9 @@ ruleTester.run(RULE_NAME, rule, { const button = getByText('some button'); }); `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; test('should not report straight render result called "utils"', async () => { @@ -44,9 +44,9 @@ ruleTester.run(RULE_NAME, rule, { await utils.findByRole('button'); }); `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; test('should not report straight render result called "view"', async () => { @@ -54,9 +54,9 @@ ruleTester.run(RULE_NAME, rule, { await view.findByRole('button'); }); `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; const setup = () => render(); @@ -66,9 +66,9 @@ ruleTester.run(RULE_NAME, rule, { const button = getByText('some button'); }); `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; const setup = () => render(); @@ -78,9 +78,9 @@ ruleTester.run(RULE_NAME, rule, { await utils.findByRole('button'); }); `, - }, - { - code: ` + }, + { + code: ` import { render } from '${testingFramework}'; const setup = () => render(); @@ -90,9 +90,9 @@ ruleTester.run(RULE_NAME, rule, { await view.findByRole('button'); }); `, - }, - { - code: ` + }, + { + code: ` import { screen } from '${testingFramework}'; import { customRender } from 'test-utils'; @@ -101,11 +101,11 @@ ruleTester.run(RULE_NAME, rule, { const button = screen.getByText('some button'); }); `, - settings: { 'testing-library/custom-renders': ['customRender'] }, - }, - ]), - { - code: ` + settings: { 'testing-library/custom-renders': ['customRender'] }, + }, + ]), + { + code: ` import { customRender } from 'test-utils'; test('should not report render result called "view" from custom render', async () => { @@ -113,10 +113,10 @@ ruleTester.run(RULE_NAME, rule, { await view.findByRole('button'); }); `, - settings: { 'testing-library/custom-renders': ['customRender'] }, - }, - { - code: ` + settings: { 'testing-library/custom-renders': ['customRender'] }, + }, + { + code: ` import { customRender } from 'test-utils'; test('should not report render result called "utils" from custom render', async () => { @@ -124,11 +124,11 @@ ruleTester.run(RULE_NAME, rule, { await utils.findByRole('button'); }); `, - settings: { 'testing-library/custom-renders': ['customRender'] }, - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + settings: { 'testing-library/custom-renders': ['customRender'] }, + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { render } from '${testingFramework}'; const setup = () => { @@ -143,10 +143,10 @@ ruleTester.run(RULE_NAME, rule, { await wrapper.findByRole('button'); }); `, - }, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render as testingLibraryRender } from '${testingFramework}'; import { render } from '@somewhere/else' @@ -157,14 +157,14 @@ ruleTester.run(RULE_NAME, rule, { const button = wrapper.getByText('some button'); }); `, - }, - ]), - { - settings: { - 'testing-library/utils-module': 'test-utils', - 'testing-library/custom-renders': ['customRender'], - }, - code: ` + }, + ]), + { + settings: { + 'testing-library/utils-module': 'test-utils', + 'testing-library/custom-renders': ['customRender'], + }, + code: ` import { customRender as myRender } from 'test-utils'; import { customRender } from 'non-related' @@ -179,13 +179,13 @@ ruleTester.run(RULE_NAME, rule, { await wrapper.findByRole('button'); }); `, - }, - { - settings: { - 'testing-library/utils-module': 'off', - 'testing-library/custom-renders': 'off', - }, - code: ` + }, + { + settings: { + 'testing-library/utils-module': 'off', + 'testing-library/custom-renders': 'off', + }, + code: ` import { customRender as myRender } from 'test-utils'; import { render } from 'non-related' @@ -203,12 +203,12 @@ ruleTester.run(RULE_NAME, rule, { await wrapper1.findByRole('button'); }); `, - }, - ], - invalid: [ - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + }, + ], + invalid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { render } from '${testingFramework}'; test('should report straight render result called "wrapper"', async () => { @@ -216,19 +216,19 @@ ruleTester.run(RULE_NAME, rule, { await wrapper.findByRole('button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 5, - column: 17, - }, - ], - } as const, - { - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 5, + column: 17, + }, + ], + } as const, + { + code: ` import * as RTL from '${testingFramework}'; test('should report straight render result called "wrapper" from wildcard import', () => { @@ -236,19 +236,19 @@ ruleTester.run(RULE_NAME, rule, { const button = wrapper.getByText('some button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 5, - column: 17, - }, - ], - } as const, - { - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 5, + column: 17, + }, + ], + } as const, + { + code: ` import { render } from '${testingFramework}'; test('should report straight render result called "component"', async () => { @@ -256,19 +256,19 @@ ruleTester.run(RULE_NAME, rule, { await component.findByRole('button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'component', - }, - line: 5, - column: 17, - }, - ], - } as const, - { - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'component', + }, + line: 5, + column: 17, + }, + ], + } as const, + { + code: ` import { render } from '${testingFramework}'; test('should report straight render result called "notValidName"', async () => { @@ -276,16 +276,16 @@ ruleTester.run(RULE_NAME, rule, { await notValidName.findByRole('button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - line: 5, - column: 17, - }, - ], - } as const, - { - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + line: 5, + column: 17, + }, + ], + } as const, + { + code: ` import { render as testingLibraryRender } from '${testingFramework}'; test('should report renamed render result called "wrapper"', async () => { @@ -293,19 +293,19 @@ ruleTester.run(RULE_NAME, rule, { await wrapper.findByRole('button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 5, - column: 17, - }, - ], - } as const, - { - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 5, + column: 17, + }, + ], + } as const, + { + code: ` import { render } from '${testingFramework}'; const setup = () => { @@ -320,20 +320,20 @@ ruleTester.run(RULE_NAME, rule, { await wrapper.findByRole('button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 6, - column: 17, - }, - ], - } as const, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 6, + column: 17, + }, + ], + } as const, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from '${testingFramework}'; const setup = () => render(); @@ -343,21 +343,21 @@ ruleTester.run(RULE_NAME, rule, { const button = wrapper.getByText('some button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 7, - column: 17, - }, - ], - } as const, - ]), - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 7, + column: 17, + }, + ], + } as const, + ]), + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render } from 'test-utils'; function setup() { @@ -370,19 +370,19 @@ ruleTester.run(RULE_NAME, rule, { const button = wrapper.getByText('some button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 10, - column: 17, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 10, + column: 17, + }, + ], + }, + { + code: ` import { customRender } from 'test-utils'; test('should report from custom render function ', () => { @@ -390,20 +390,20 @@ ruleTester.run(RULE_NAME, rule, { const button = wrapper.getByText('some button'); }); `, - settings: { 'testing-library/custom-renders': ['customRender'] }, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 5, - column: 17, - }, - ], - }, - { - code: ` + settings: { 'testing-library/custom-renders': ['customRender'] }, + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 5, + column: 17, + }, + ], + }, + { + code: ` import { render } from '@foo/bar'; test('aggressive reporting - should report from render not related to testing library', () => { @@ -411,19 +411,19 @@ ruleTester.run(RULE_NAME, rule, { const button = wrapper.getByText('some button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 5, - column: 17, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 5, + column: 17, + }, + ], + }, + { + code: ` import * as RTL from '@foo/bar'; test('aggressive reporting - should report from wildcard render not imported from testing library', () => { @@ -431,19 +431,19 @@ ruleTester.run(RULE_NAME, rule, { const button = wrapper.getByText('some button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 5, - column: 17, - }, - ], - }, - { - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 5, + column: 17, + }, + ], + }, + { + code: ` function render() { return 'whatever'; } @@ -453,20 +453,20 @@ ruleTester.run(RULE_NAME, rule, { const button = wrapper.getByText('some button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 7, - column: 17, - }, - ], - }, - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 7, + column: 17, + }, + ], + }, + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ + { + code: ` import { render as testingLibraryRender } from '${testingFramework}'; const setup = () => { @@ -478,20 +478,20 @@ ruleTester.run(RULE_NAME, rule, { await wrapper.findByRole('button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 9, - column: 17, - }, - ], - } as const, - { - settings: { 'testing-library/utils-module': 'test-utils' }, - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 9, + column: 17, + }, + ], + } as const, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` import { render as testingLibraryRender } from '${testingFramework}'; const setup = () => { @@ -505,24 +505,24 @@ ruleTester.run(RULE_NAME, rule, { await wrapper.findByRole('button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 11, - column: 17, - }, - ], - } as const, - ]), - { - settings: { - 'testing-library/utils-module': 'test-utils', - 'testing-library/custom-renders': ['customRender'], - }, - code: ` + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 11, + column: 17, + }, + ], + } as const, + ]), + { + settings: { + 'testing-library/utils-module': 'test-utils', + 'testing-library/custom-renders': ['customRender'], + }, + code: ` import { customRender as myRender } from 'test-utils'; const setup = () => { @@ -536,16 +536,16 @@ ruleTester.run(RULE_NAME, rule, { await wrapper.findByRole('button'); }); `, - errors: [ - { - messageId: 'renderResultNamingConvention', - data: { - renderResultName: 'wrapper', - }, - line: 11, - column: 17, - }, - ], - }, - ], + errors: [ + { + messageId: 'renderResultNamingConvention', + data: { + renderResultName: 'wrapper', + }, + line: 11, + column: 17, + }, + ], + }, + ], }); diff --git a/tests/lib/test-utils.ts b/tests/lib/test-utils.ts index 8770755a..c9640129 100644 --- a/tests/lib/test-utils.ts +++ b/tests/lib/test-utils.ts @@ -3,48 +3,48 @@ import { resolve } from 'path'; import { TSESLint } from '@typescript-eslint/utils'; const DEFAULT_TEST_CASE_CONFIG = { - filename: 'MyComponent.test.js', + filename: 'MyComponent.test.js', }; class TestingLibraryRuleTester extends TSESLint.RuleTester { - run>( - ruleName: string, - rule: TSESLint.RuleModule, - tests: TSESLint.RunTests - ): void { - const { valid, invalid } = tests; + run>( + ruleName: string, + rule: TSESLint.RuleModule, + tests: TSESLint.RunTests + ): void { + const { valid, invalid } = tests; - const finalValid = valid.map((testCase) => { - if (typeof testCase === 'string') { - return { - ...DEFAULT_TEST_CASE_CONFIG, - code: testCase, - }; - } + const finalValid = valid.map((testCase) => { + if (typeof testCase === 'string') { + return { + ...DEFAULT_TEST_CASE_CONFIG, + code: testCase, + }; + } - return { ...DEFAULT_TEST_CASE_CONFIG, ...testCase }; - }); - const finalInvalid = invalid.map((testCase) => ({ - ...DEFAULT_TEST_CASE_CONFIG, - ...testCase, - })); + return { ...DEFAULT_TEST_CASE_CONFIG, ...testCase }; + }); + const finalInvalid = invalid.map((testCase) => ({ + ...DEFAULT_TEST_CASE_CONFIG, + ...testCase, + })); - super.run(ruleName, rule, { valid: finalValid, invalid: finalInvalid }); - } + super.run(ruleName, rule, { valid: finalValid, invalid: finalInvalid }); + } } export const createRuleTester = ( - parserOptions: Partial = {} + parserOptions: Partial = {} ): TSESLint.RuleTester => { - return new TestingLibraryRuleTester({ - parser: resolve('./node_modules/@typescript-eslint/parser'), - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - ...parserOptions, - }, - }); + return new TestingLibraryRuleTester({ + parser: resolve('./node_modules/@typescript-eslint/parser'), + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + ...parserOptions, + }, + }); }; diff --git a/tools/generate-configs/index.ts b/tools/generate-configs/index.ts index f36a9051..74773918 100644 --- a/tools/generate-configs/index.ts +++ b/tools/generate-configs/index.ts @@ -1,8 +1,8 @@ import type { LinterConfigRules } from '../../lib/configs'; import rules from '../../lib/rules'; import { - SUPPORTED_TESTING_FRAMEWORKS, - SupportedTestingFramework, + SUPPORTED_TESTING_FRAMEWORKS, + SupportedTestingFramework, } from '../../lib/utils'; import { LinterConfig, writeConfig } from './utils'; @@ -10,32 +10,32 @@ import { LinterConfig, writeConfig } from './utils'; const RULE_NAME_PREFIX = 'testing-library/'; const getRecommendedRulesForTestingFramework = ( - framework: SupportedTestingFramework + framework: SupportedTestingFramework ): LinterConfigRules => - Object.entries(rules) - .filter( - ([ - _, - { - meta: { docs }, - }, - ]) => Boolean(docs.recommendedConfig[framework]) - ) - .reduce((allRules, [ruleName, { meta }]) => { - const name = `${RULE_NAME_PREFIX}${ruleName}`; - const recommendation = meta.docs.recommendedConfig[framework]; + Object.entries(rules) + .filter( + ([ + _, + { + meta: { docs }, + }, + ]) => Boolean(docs.recommendedConfig[framework]) + ) + .reduce((allRules, [ruleName, { meta }]) => { + const name = `${RULE_NAME_PREFIX}${ruleName}`; + const recommendation = meta.docs.recommendedConfig[framework]; - return { - ...allRules, - [name]: recommendation, - }; - }, {}); + return { + ...allRules, + [name]: recommendation, + }; + }, {}); SUPPORTED_TESTING_FRAMEWORKS.forEach((framework) => { - const specificFrameworkConfig: LinterConfig = { - plugins: ['testing-library'], - rules: getRecommendedRulesForTestingFramework(framework), - }; + const specificFrameworkConfig: LinterConfig = { + plugins: ['testing-library'], + rules: getRecommendedRulesForTestingFramework(framework), + }; - writeConfig(specificFrameworkConfig, framework); + writeConfig(specificFrameworkConfig, framework); }); diff --git a/tools/generate-configs/utils.ts b/tools/generate-configs/utils.ts index 339755a0..b233b56f 100644 --- a/tools/generate-configs/utils.ts +++ b/tools/generate-configs/utils.ts @@ -9,25 +9,25 @@ const prettierConfig = resolveConfig.sync(__dirname); export type LinterConfig = TSESLint.Linter.Config; const addAutoGeneratedComment = (code: string) => - [ - '// THIS CODE WAS AUTOMATICALLY GENERATED', - '// DO NOT EDIT THIS CODE BY HAND', - '// YOU CAN REGENERATE IT USING npm run generate:configs', - '', - code, - ].join('\n'); + [ + '// THIS CODE WAS AUTOMATICALLY GENERATED', + '// DO NOT EDIT THIS CODE BY HAND', + '// YOU CAN REGENERATE IT USING npm run generate:configs', + '', + code, + ].join('\n'); /** * Helper function writes configuration. */ export const writeConfig = (config: LinterConfig, configName: string): void => { - // note: we use `export =` because ESLint will import these configs via a commonjs import - const code = `export = ${JSON.stringify(config)};`; - const configStr = format(addAutoGeneratedComment(code), { - parser: 'typescript', - ...prettierConfig, - }); - const filePath = resolve(__dirname, `../../lib/configs/${configName}.ts`); + // note: we use `export =` because ESLint will import these configs via a commonjs import + const code = `export = ${JSON.stringify(config)};`; + const configStr = format(addAutoGeneratedComment(code), { + parser: 'typescript', + ...prettierConfig, + }); + const filePath = resolve(__dirname, `../../lib/configs/${configName}.ts`); - writeFileSync(filePath, configStr); + writeFileSync(filePath, configStr); }; diff --git a/tools/generate-rules-list/index.ts b/tools/generate-rules-list/index.ts index dc683def..ef1c04b0 100644 --- a/tools/generate-rules-list/index.ts +++ b/tools/generate-rules-list/index.ts @@ -4,23 +4,23 @@ import type { TestingLibraryRuleMetaDocs } from '../../lib/utils'; import { configBadges, emojiKey, RulesList, writeRulesList } from './utils'; export const createRuleLink = (ruleName: string): string => - `[\`${ruleName}\`](./docs/rules/${ruleName}.md)`; + `[\`${ruleName}\`](./docs/rules/${ruleName}.md)`; export const generateConfigBadges = ( - recommendedConfig: TestingLibraryRuleMetaDocs['recommendedConfig'] + recommendedConfig: TestingLibraryRuleMetaDocs['recommendedConfig'] ): string => - Object.entries(recommendedConfig) - .filter(([_, config]) => Boolean(config)) - .map(([framework]) => configBadges[framework]) - .join(' '); + Object.entries(recommendedConfig) + .filter(([_, config]) => Boolean(config)) + .map(([framework]) => configBadges[framework]) + .join(' '); const rulesList: RulesList = Object.entries(rules) - .sort(([ruleNameA], [ruleNameB]) => ruleNameA.localeCompare(ruleNameB)) - .map(([name, rule]) => [ - createRuleLink(name), - rule.meta.docs.description, - Boolean(rule.meta.fixable) ? emojiKey.fixable : '', - generateConfigBadges(rule.meta.docs.recommendedConfig), - ]); + .sort(([ruleNameA], [ruleNameB]) => ruleNameA.localeCompare(ruleNameB)) + .map(([name, rule]) => [ + createRuleLink(name), + rule.meta.docs.description, + Boolean(rule.meta.fixable) ? emojiKey.fixable : '', + generateConfigBadges(rule.meta.docs.recommendedConfig), + ]); writeRulesList(rulesList); diff --git a/tools/generate-rules-list/utils.ts b/tools/generate-rules-list/utils.ts index 3f2f6051..b7237e56 100644 --- a/tools/generate-rules-list/utils.ts +++ b/tools/generate-rules-list/utils.ts @@ -4,8 +4,8 @@ import { resolve } from 'path'; import { format, resolveConfig } from 'prettier'; import { - SUPPORTED_TESTING_FRAMEWORKS, - SupportedTestingFramework, + SUPPORTED_TESTING_FRAMEWORKS, + SupportedTestingFramework, } from '../../lib/utils'; const prettierConfig = resolveConfig.sync(__dirname); @@ -14,74 +14,74 @@ const readmePath = resolve(__dirname, `../../README.md`); export type RulesList = string[][]; export const configBadges = SUPPORTED_TESTING_FRAMEWORKS.reduce( - (badges, framework) => ({ - ...badges, - [framework]: `![${framework}-badge][]`, - }), - {} + (badges, framework) => ({ + ...badges, + [framework]: `![${framework}-badge][]`, + }), + {} ) as Record; export const emojiKey = { - fixable: '🔧', + fixable: '🔧', } as const; const staticElements = { - listHeaderRow: [ - 'Name', - 'Description', - emojiKey.fixable, - 'Included in configurations', - ], - listSpacerRow: Array(4).fill('-'), - rulesListKey: [ - `**Key**: ${emojiKey.fixable} = fixable`, - '', - [ - `**Configurations**:`, - Object.entries(configBadges) - .map(([template, badge]) => `${badge} = ${template}`) - .join(', '), - ].join(' '), - ].join('\n'), + listHeaderRow: [ + 'Name', + 'Description', + emojiKey.fixable, + 'Included in configurations', + ], + listSpacerRow: Array(4).fill('-'), + rulesListKey: [ + `**Key**: ${emojiKey.fixable} = fixable`, + '', + [ + `**Configurations**:`, + Object.entries(configBadges) + .map(([template, badge]) => `${badge} = ${template}`) + .join(', '), + ].join(' '), + ].join('\n'), }; const generateRulesListTable = (rulesList: RulesList) => - [staticElements.listHeaderRow, staticElements.listSpacerRow, ...rulesList] - .map((column) => `|${column.join('|')}|`) - .join('\n'); + [staticElements.listHeaderRow, staticElements.listSpacerRow, ...rulesList] + .map((column) => `|${column.join('|')}|`) + .join('\n'); const generateRulesListMarkdown = (rulesList: RulesList) => - [ - '', - staticElements.rulesListKey, - '', - generateRulesListTable(rulesList), - '', - ].join('\n'); + [ + '', + staticElements.rulesListKey, + '', + generateRulesListTable(rulesList), + '', + ].join('\n'); const listBeginMarker = ''; const listEndMarker = ''; const overWriteRulesList = (rulesList: RulesList, readme: string) => { - const listStartIndex = readme.indexOf(listBeginMarker); - const listEndIndex = readme.indexOf(listEndMarker); + const listStartIndex = readme.indexOf(listBeginMarker); + const listEndIndex = readme.indexOf(listEndMarker); - if ([listStartIndex, listEndIndex].includes(-1)) { - throw new Error(`cannot find start or end rules-list`); - } + if ([listStartIndex, listEndIndex].includes(-1)) { + throw new Error(`cannot find start or end rules-list`); + } - return [ - readme.substring(0, listStartIndex - 1), - listBeginMarker, - '', - generateRulesListMarkdown(rulesList), - readme.substring(listEndIndex), - ].join('\n'); + return [ + readme.substring(0, listStartIndex - 1), + listBeginMarker, + '', + generateRulesListMarkdown(rulesList), + readme.substring(listEndIndex), + ].join('\n'); }; export const writeRulesList = (rulesList: RulesList): void => { - const readme = readFileSync(readmePath, 'utf8'); - const newReadme = format(overWriteRulesList(rulesList, readme), { - parser: 'markdown', - ...prettierConfig, - }); + const readme = readFileSync(readmePath, 'utf8'); + const newReadme = format(overWriteRulesList(rulesList, readme), { + parser: 'markdown', + ...prettierConfig, + }); - writeFileSync(readmePath, newReadme); + writeFileSync(readmePath, newReadme); }; diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index ab3c206a..b0a6dfc7 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -1,4 +1,4 @@ { - "extends": "./tsconfig.json", - "include": ["**/*.ts", "**/*.js"] + "extends": "./tsconfig.json", + "include": ["**/*.ts", "**/*.js"] } diff --git a/tsconfig.json b/tsconfig.json index 863be8e7..338535e1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,18 @@ { - "compilerOptions": { - "strict": true, - "target": "es6", - "module": "commonjs", - "moduleResolution": "node", - "esModuleInterop": true, - "resolveJsonModule": true, - "forceConsistentCasingInFileNames": true, - "noImplicitAny": true, - "outDir": "./dist", - "removeComments": true, - "skipLibCheck": true, - "sourceMap": false, - "suppressImplicitAnyIndexErrors": true - }, - "include": ["./lib/**/*.ts"] + "compilerOptions": { + "strict": true, + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": true, + "outDir": "./dist", + "removeComments": true, + "skipLibCheck": true, + "sourceMap": false, + "suppressImplicitAnyIndexErrors": true + }, + "include": ["./lib/**/*.ts"] }