diff --git a/.all-contributorsrc b/.all-contributorsrc index 515ea578..ccff7e6e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -718,6 +718,16 @@ "test", "doc" ] + }, + { + "login": "y-hsgw", + "name": "Yukihiro Hasegawa", + "avatar_url": "https://avatars.githubusercontent.com/u/49516827?v=4", + "profile": "https://github.com/y-hsgw", + "contributions": [ + "code", + "test" + ] } ], "contributorsPerLine": 7, diff --git a/.github/workflows/main-coverage.yml b/.github/workflows/main-coverage.yml new file mode 100644 index 00000000..8eb4854e --- /dev/null +++ b/.github/workflows/main-coverage.yml @@ -0,0 +1,38 @@ +name: Code Coverage (main) +on: + push: + branches: + - 'main' + +permissions: + contents: read + statuses: write + +jobs: + coverage: + name: Code Coverage + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + cache: 'pnpm' + node-version-file: '.nvmrc' + + - name: Install dependencies + run: pnpm install + + - name: Run tests with coverage + run: pnpm run test:ci + + - name: Upload coverage report + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/README.md b/README.md index 981ae6fc..18e61661 100644 --- a/README.md +++ b/README.md @@ -346,7 +346,7 @@ module.exports = [ | [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than standalone queries | | | | | [prefer-find-by](docs/rules/prefer-find-by.md) | Suggest using `find(All)By*` query instead of `waitFor` + `get(All)By*` to wait for elements | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | πŸ”§ | | [prefer-implicit-assert](docs/rules/prefer-implicit-assert.md) | Suggest using implicit assertions for getBy* & findBy* queries | | | | -| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Ensure appropriate `get*`/`query*` queries are used with their respective matchers | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | | +| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Ensure appropriate `get*`/`query*` queries are used with their respective matchers | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | πŸ”§ | | [prefer-query-by-disappearance](docs/rules/prefer-query-by-disappearance.md) | Suggest using `queryBy*` queries when waiting for disappearance | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | | | [prefer-query-matchers](docs/rules/prefer-query-matchers.md) | Ensure the configured `get*`/`query*` query is used with the corresponding matchers | | | | | [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using `screen` while querying | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | | @@ -552,6 +552,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d nostro
nostro

πŸ’» Daniel Rentz
Daniel Rentz

πŸ“– StyleShit
StyleShit

πŸ’» ⚠️ πŸ“– + Yukihiro Hasegawa
Yukihiro Hasegawa

πŸ’» ⚠️ diff --git a/docs/rules/no-node-access.md b/docs/rules/no-node-access.md index 7290cec3..3ae667b7 100644 --- a/docs/rules/no-node-access.md +++ b/docs/rules/no-node-access.md @@ -4,11 +4,15 @@ -The Testing Library already provides methods for querying DOM elements. +Disallow direct access or manipulation of DOM nodes in favor of Testing Library's user-centric APIs. ## Rule Details -This rule aims to disallow DOM traversal using native HTML methods and properties, such as `closest`, `lastChild` and all that returns another Node element from an HTML tree. +This rule aims to disallow direct access and manipulation of DOM nodes using native HTML properties and methods β€” including traversal (e.g. `closest`, `lastChild`) as well as direct actions (e.g. `click()`, `select()`). Use Testing Library’s queries and userEvent APIs instead. + +> [!NOTE] +> This rule does not report usage of `focus()` or `blur()`, because imperative usage (e.g. `getByText('focus me').focus()` or .`blur()`) is recommended over `fireEvent.focus()` or `fireEvent.blur()`. +> If an element is not focusable, related assertions will fail, leading to more robust tests. See [Testing Library Events Guide](https://testing-library.com/docs/guide-events/) for more details. Examples of **incorrect** code for this rule: @@ -21,6 +25,12 @@ screen.getByText('Submit').closest('button'); // chaining with Testing Library m ```js import { screen } from '@testing-library/react'; +screen.getByText('Submit').click(); +``` + +```js +import { screen } from '@testing-library/react'; + const buttons = screen.getAllByRole('button'); expect(buttons[1].lastChild).toBeInTheDocument(); ``` @@ -41,6 +51,12 @@ const button = screen.getByRole('button'); expect(button).toHaveTextContent('submit'); ``` +```js +import { screen } from '@testing-library/react'; + +userEvent.click(screen.getByText('Submit')); +``` + ```js import { render, within } from '@testing-library/react'; @@ -92,3 +108,7 @@ expect(container.firstChild).toMatchSnapshot(); - [`Document`](https://developer.mozilla.org/en-US/docs/Web/API/Document) - [`Element`](https://developer.mozilla.org/en-US/docs/Web/API/Element) - [`Node`](https://developer.mozilla.org/en-US/docs/Web/API/Node) + +### Testing Library Guides + +- [Testing Library Events Guide](https://testing-library.com/docs/guide-events/) diff --git a/docs/rules/prefer-find-by.md b/docs/rules/prefer-find-by.md index 2a39b131..0431e27b 100644 --- a/docs/rules/prefer-find-by.md +++ b/docs/rules/prefer-find-by.md @@ -41,6 +41,12 @@ const submitButton = await waitFor(() => const submitButton = await waitFor(() => expect(queryByLabel('button', { name: /submit/i })).not.toBeFalsy() ); + +// unnecessary usage of waitFor with findBy*, which already includes waiting logic +await waitFor(async () => { + const button = await findByRole('button', { name: 'Submit' }); + expect(button).toBeInTheDocument(); +}); ``` Examples of **correct** code for this rule: diff --git a/docs/rules/prefer-presence-queries.md b/docs/rules/prefer-presence-queries.md index 2bb3174a..b7e40121 100644 --- a/docs/rules/prefer-presence-queries.md +++ b/docs/rules/prefer-presence-queries.md @@ -2,6 +2,8 @@ πŸ’Ό This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `svelte`, `vue`. +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + The (DOM) Testing Library allows to query DOM elements using different types of queries such as `get*` and `query*`. Using `get*` throws an error in case the element is not found, while `query*` returns null instead of throwing (or empty array for `queryAllBy*` ones). These differences are useful in some situations: 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 001dc532..09688bef 100644 --- a/lib/create-testing-library-rule/detect-testing-library-utils.ts +++ b/lib/create-testing-library-rule/detect-testing-library-utils.ts @@ -802,8 +802,10 @@ export function detectTestingLibraryUtils< } return isNegated - ? ABSENCE_MATCHERS.includes(matcher) - : PRESENCE_MATCHERS.includes(matcher); + ? ABSENCE_MATCHERS.some((absenceMather) => absenceMather === matcher) + : PRESENCE_MATCHERS.some( + (presenceMather) => presenceMather === matcher + ); }; /** @@ -821,8 +823,8 @@ export function detectTestingLibraryUtils< } return isNegated - ? PRESENCE_MATCHERS.includes(matcher) - : ABSENCE_MATCHERS.includes(matcher); + ? PRESENCE_MATCHERS.some((presenceMather) => presenceMather === matcher) + : ABSENCE_MATCHERS.some((absenceMather) => absenceMather === matcher); }; const isMatchingAssert: IsMatchingAssertFn = (node, matcherName) => { diff --git a/lib/create-testing-library-rule/index.ts b/lib/create-testing-library-rule/index.ts index 82e28403..8ce3b334 100644 --- a/lib/create-testing-library-rule/index.ts +++ b/lib/create-testing-library-rule/index.ts @@ -1,6 +1,10 @@ import { ESLintUtils } from '@typescript-eslint/utils'; -import { getDocsUrl, TestingLibraryPluginDocs } from '../utils'; +import { + getDocsUrl, + TestingLibraryPluginDocs, + TestingLibraryPluginRuleModule, +} from '../utils'; import { DetectionOptions, @@ -27,11 +31,20 @@ export const createTestingLibraryRule = < create: EnhancedRuleCreate; detectionOptions?: Partial; } ->) => - ESLintUtils.RuleCreator>(getDocsUrl)({ +>): TestingLibraryPluginRuleModule => { + const rule = ESLintUtils.RuleCreator>( + getDocsUrl + )({ ...remainingConfig, create: detectTestingLibraryUtils( create, detectionOptions ), }); + const { docs } = rule.meta; + if (docs === undefined) { + throw new Error('Rule metadata must contain `docs` property'); + } + + return { ...rule, meta: { ...rule.meta, docs } }; +}; diff --git a/lib/rules/no-node-access.ts b/lib/rules/no-node-access.ts index 0fcd78ac..bfbbc346 100644 --- a/lib/rules/no-node-access.ts +++ b/lib/rules/no-node-access.ts @@ -1,12 +1,21 @@ import { TSESTree, ASTUtils } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; -import { ALL_RETURNING_NODES } from '../utils'; +import { + ALL_RETURNING_NODES, + EVENT_HANDLER_METHODS, + EVENTS_SIMULATORS, +} from '../utils'; export const RULE_NAME = 'no-node-access'; export type MessageIds = 'noNodeAccess'; export type Options = [{ allowContainerFirstChild: boolean }]; +const ALL_PROHIBITED_MEMBERS = [ + ...ALL_RETURNING_NODES, + ...EVENT_HANDLER_METHODS, +] as const; + export default createTestingLibraryRule({ name: RULE_NAME, meta: { @@ -52,11 +61,26 @@ export default createTestingLibraryRule({ return; } + const propertyName = ASTUtils.isIdentifier(node.property) + ? node.property.name + : null; + + const objectName = ASTUtils.isIdentifier(node.object) + ? node.object.name + : null; if ( - ASTUtils.isIdentifier(node.property) && - ALL_RETURNING_NODES.includes(node.property.name) + propertyName && + ALL_PROHIBITED_MEMBERS.some( + (allReturningNode) => allReturningNode === propertyName + ) && + ![ + ...EVENTS_SIMULATORS, + // TODO: As discussed in https://github.com/testing-library/eslint-plugin-testing-library/issues/1024, this is just a temporary workaround. + // We should address the root cause and implement a proper solution instead of explicitly excluding 'user' here. + 'user', + ].some((simulator) => simulator === objectName) ) { - if (allowContainerFirstChild && node.property.name === 'firstChild') { + if (allowContainerFirstChild && propertyName === 'firstChild') { return; } diff --git a/lib/rules/no-render-in-lifecycle.ts b/lib/rules/no-render-in-lifecycle.ts index c15fcd19..1e1aee24 100644 --- a/lib/rules/no-render-in-lifecycle.ts +++ b/lib/rules/no-render-in-lifecycle.ts @@ -68,7 +68,7 @@ export default createTestingLibraryRule({ type: 'object', properties: { allowTestingFrameworkSetupHook: { - enum: TESTING_FRAMEWORK_SETUP_HOOKS, + enum: [...TESTING_FRAMEWORK_SETUP_HOOKS], type: 'string', }, }, diff --git a/lib/rules/prefer-explicit-assert.ts b/lib/rules/prefer-explicit-assert.ts index 7dc78f2c..ed0ffc8c 100644 --- a/lib/rules/prefer-explicit-assert.ts +++ b/lib/rules/prefer-explicit-assert.ts @@ -92,7 +92,7 @@ export default createTestingLibraryRule({ properties: { assertion: { type: 'string', - enum: PRESENCE_MATCHERS, + enum: [...PRESENCE_MATCHERS], }, includeFindQueries: { type: 'boolean' }, }, @@ -182,8 +182,14 @@ export default createTestingLibraryRule({ } const shouldEnforceAssertion = - (!isNegatedMatcher && PRESENCE_MATCHERS.includes(matcher)) || - (isNegatedMatcher && ABSENCE_MATCHERS.includes(matcher)); + (!isNegatedMatcher && + PRESENCE_MATCHERS.some( + (presenceMather) => presenceMather === matcher + )) || + (isNegatedMatcher && + ABSENCE_MATCHERS.some( + (absenceMather) => absenceMather === matcher + )); if (shouldEnforceAssertion && matcher !== assertion) { context.report({ diff --git a/lib/rules/prefer-find-by.ts b/lib/rules/prefer-find-by.ts index 1fc26ebd..5e3f35a7 100644 --- a/lib/rules/prefer-find-by.ts +++ b/lib/rules/prefer-find-by.ts @@ -2,12 +2,15 @@ import { TSESTree, ASTUtils, TSESLint } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { + getDeepestIdentifierNode, isArrowFunctionExpression, + isBlockStatement, isCallExpression, isMemberExpression, isObjectExpression, isObjectPattern, isProperty, + isVariableDeclaration, } from '../node-utils'; import { getScope, getSourceCode } from '../utils'; @@ -329,20 +332,82 @@ export default createTestingLibraryRule({ } return { - 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { + 'AwaitExpression > CallExpression'( + node: TSESTree.CallExpression & { parent: TSESTree.AwaitExpression } + ) { if ( !ASTUtils.isIdentifier(node.callee) || !helpers.isAsyncUtil(node.callee, ['waitFor']) ) { return; } - // ensure the only argument is an arrow function expression - if the arrow function is a block - // we skip it + // ensure the only argument is an arrow function expression const argument = node.arguments[0]; - if ( - !isArrowFunctionExpression(argument) || - !isCallExpression(argument.body) - ) { + + if (!isArrowFunctionExpression(argument)) { + return; + } + + if (isBlockStatement(argument.body) && argument.async) { + const { body } = argument.body; + const declarations = body + .filter(isVariableDeclaration) + ?.flatMap((declaration) => declaration.declarations); + + const findByDeclarator = declarations.find((declaration) => { + if ( + !ASTUtils.isAwaitExpression(declaration.init) || + !isCallExpression(declaration.init.argument) + ) { + return false; + } + + const { callee } = declaration.init.argument; + const node = getDeepestIdentifierNode(callee); + return node ? helpers.isFindQueryVariant(node) : false; + }); + + const init = ASTUtils.isAwaitExpression(findByDeclarator?.init) + ? findByDeclarator.init.argument + : null; + + if (!isCallExpression(init)) { + return; + } + const queryIdentifier = getDeepestIdentifierNode(init.callee); + + // ensure the query is a supported async query like findBy* + if (!queryIdentifier || !helpers.isAsyncQuery(queryIdentifier)) { + return; + } + + const fullQueryMethod = queryIdentifier.name; + const queryMethod = fullQueryMethod.split('By')[1]; + const queryVariant = getFindByQueryVariant(fullQueryMethod); + + reportInvalidUsage(node, { + queryMethod, + queryVariant, + prevQuery: fullQueryMethod, + fix(fixer) { + const { parent: expressionStatement } = node.parent; + const bodyText = sourceCode + .getText(argument.body) + .slice(1, -1) + .trim(); + const { line, column } = expressionStatement.loc.start; + const indent = sourceCode.getLines()[line - 1].slice(0, column); + const newText = bodyText + .split('\n') + .map((line) => line.trim()) + .join(`\n${indent}`); + return fixer.replaceText(expressionStatement, newText); + }, + }); + return; + } + + if (!isCallExpression(argument.body)) { return; } diff --git a/lib/rules/prefer-presence-queries.ts b/lib/rules/prefer-presence-queries.ts index a810f869..504633c3 100644 --- a/lib/rules/prefer-presence-queries.ts +++ b/lib/rules/prefer-presence-queries.ts @@ -33,6 +33,7 @@ export default createTestingLibraryRule({ wrongAbsenceQuery: 'Use `queryBy*` queries rather than `getBy*` for checking element is NOT present', }, + fixable: 'code', schema: [ { type: 'object', @@ -62,7 +63,7 @@ export default createTestingLibraryRule({ const expectCallNode = findClosestCallNode(node, 'expect'); const withinCallNode = findClosestCallNode(node, 'within'); - if (!expectCallNode || !isMemberExpression(expectCallNode.parent)) { + if (!isMemberExpression(expectCallNode?.parent)) { return; } @@ -86,14 +87,25 @@ export default createTestingLibraryRule({ (withinCallNode || isPresenceAssert) && !isPresenceQuery ) { - context.report({ node, messageId: 'wrongPresenceQuery' }); + const newQueryName = node.name.replace(/^query/, 'get'); + + context.report({ + node, + messageId: 'wrongPresenceQuery', + fix: (fixer) => fixer.replaceText(node, newQueryName), + }); } else if ( !withinCallNode && absence && isAbsenceAssert && isPresenceQuery ) { - context.report({ node, messageId: 'wrongAbsenceQuery' }); + const newQueryName = node.name.replace(/^get/, 'query'); + context.report({ + node, + messageId: 'wrongAbsenceQuery', + fix: (fixer) => fixer.replaceText(node, newQueryName), + }); } }, }; diff --git a/lib/utils/index.ts b/lib/utils/index.ts index cb0e8e03..299d4c36 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -2,7 +2,10 @@ export * from './compat'; export * from './file-import'; export * from './types'; -const combineQueries = (variants: string[], methods: string[]): string[] => { +const combineQueries = ( + variants: readonly string[], + methods: readonly string[] +): string[] => { const combinedQueries: string[] = []; variants.forEach((variant) => { const variantPrefix = variant.replace('By', ''); @@ -25,14 +28,19 @@ const LIBRARY_MODULES = [ '@testing-library/vue', '@testing-library/svelte', '@marko/testing-library', -]; +] as const; -const SYNC_QUERIES_VARIANTS = ['getBy', 'getAllBy', 'queryBy', 'queryAllBy']; -const ASYNC_QUERIES_VARIANTS = ['findBy', 'findAllBy']; +const SYNC_QUERIES_VARIANTS = [ + 'getBy', + 'getAllBy', + 'queryBy', + 'queryAllBy', +] as const; +const ASYNC_QUERIES_VARIANTS = ['findBy', 'findAllBy'] as const; const ALL_QUERIES_VARIANTS = [ ...SYNC_QUERIES_VARIANTS, ...ASYNC_QUERIES_VARIANTS, -]; +] as const; const ALL_QUERIES_METHODS = [ 'ByLabelText', @@ -43,7 +51,7 @@ const ALL_QUERIES_METHODS = [ 'ByDisplayValue', 'ByRole', 'ByTestId', -]; +] as const; const SYNC_QUERIES_COMBINATIONS = combineQueries( SYNC_QUERIES_VARIANTS, @@ -58,7 +66,7 @@ const ASYNC_QUERIES_COMBINATIONS = combineQueries( const ALL_QUERIES_COMBINATIONS = [ ...SYNC_QUERIES_COMBINATIONS, ...ASYNC_QUERIES_COMBINATIONS, -]; +] as const; const ASYNC_UTILS = ['waitFor', 'waitForElementToBeRemoved'] as const; @@ -73,7 +81,7 @@ const DEBUG_UTILS = [ const EVENTS_SIMULATORS = ['fireEvent', 'userEvent'] as const; -const TESTING_FRAMEWORK_SETUP_HOOKS = ['beforeEach', 'beforeAll']; +const TESTING_FRAMEWORK_SETUP_HOOKS = ['beforeEach', 'beforeAll'] as const; const PROPERTIES_RETURNING_NODES = [ 'activeElement', @@ -93,7 +101,7 @@ const PROPERTIES_RETURNING_NODES = [ 'previousSibling', 'rootNode', 'scripts', -]; +] as const; const METHODS_RETURNING_NODES = [ 'closest', @@ -104,20 +112,22 @@ const METHODS_RETURNING_NODES = [ 'getElementsByTagNameNS', 'querySelector', 'querySelectorAll', -]; +] as const; + +const EVENT_HANDLER_METHODS = ['click', 'select', 'submit'] as const; const ALL_RETURNING_NODES = [ ...PROPERTIES_RETURNING_NODES, ...METHODS_RETURNING_NODES, -]; +] as const; const PRESENCE_MATCHERS = [ 'toBeOnTheScreen', 'toBeInTheDocument', 'toBeTruthy', 'toBeDefined', -]; -const ABSENCE_MATCHERS = ['toBeNull', 'toBeFalsy']; +] as const; +const ABSENCE_MATCHERS = ['toBeNull', 'toBeFalsy'] as const; export { combineQueries, @@ -139,4 +149,5 @@ export { ALL_RETURNING_NODES, PRESENCE_MATCHERS, ABSENCE_MATCHERS, + EVENT_HANDLER_METHODS, }; diff --git a/package.json b/package.json index 4ba0d923..639769de 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "types": "index.d.ts", "scripts": { "prebuild": "del-cli dist", - "build": "tsc", + "build": "tsc -p ./tsconfig.build.json", "generate-all": "pnpm run --parallel \"/^generate:.*/\"", "generate-all:check": "pnpm run generate-all && git diff --exit-code", "generate:configs": "ts-node tools/generate-configs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72f8abd0..985b9771 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,7 +17,7 @@ importers: devDependencies: '@commitlint/cli': specifier: ^19.6.0 - version: 19.8.0(@types/node@22.10.1)(typescript@5.7.2) + version: 19.8.0(@types/node@22.15.29)(typescript@5.7.2) '@commitlint/config-conventional': specifier: ^19.6.0 version: 19.8.0 @@ -32,7 +32,7 @@ importers: version: 29.5.14 '@types/node': specifier: ^22.9.3 - version: 22.10.1 + version: 22.15.29 '@typescript-eslint/eslint-plugin': specifier: ^8.15.0 version: 8.15.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2) @@ -62,7 +62,7 @@ importers: version: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jest: specifier: ^28.9.0 - version: 28.9.0(@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)))(typescript@5.7.2) + version: 28.12.0(@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)))(typescript@5.7.2) eslint-plugin-jest-formatting: specifier: ^3.1.0 version: 3.1.0(eslint@8.57.1) @@ -74,7 +74,7 @@ importers: version: 7.2.1(eslint@8.57.1) eslint-remote-tester: specifier: ^3.0.1 - version: 3.0.1(eslint@8.57.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)) + version: 3.0.1(eslint@8.57.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)) eslint-remote-tester-repositories: specifier: ^1.0.1 version: 1.0.1 @@ -86,7 +86,7 @@ importers: version: 3.0.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)) + version: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)) lint-staged: specifier: ^15.2.10 version: 15.4.3 @@ -101,7 +101,7 @@ importers: version: 7.7.1 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2) + version: 10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2) typescript: specifier: ^5.7.2 version: 5.7.2 @@ -777,8 +777,8 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@22.10.1': - resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} + '@types/node@22.15.29': + resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1060,11 +1060,11 @@ packages: bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -1564,8 +1564,8 @@ packages: peerDependencies: eslint: '>=0.8.0' - eslint-plugin-jest@28.9.0: - resolution: {integrity: sha512-rLu1s1Wf96TgUUxSw6loVIkNtUjq1Re7A9QdCCHSohnvXEBAjuL420h0T/fMmkQlNsQP2GhQzEUpYHPfxBkvYQ==} + eslint-plugin-jest@28.12.0: + resolution: {integrity: sha512-J6zmDp8WiQ9tyvYXE+3RFy7/+l4hraWLzmsabYXyehkmmDd36qV4VQFc7XzcsD8C1PTNt646MSx25bO1mdd9Yw==} engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0} peerDependencies: '@typescript-eslint/eslint-plugin': ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3470,8 +3470,8 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} unicode-emoji-modifier-base@1.0.0: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} @@ -3861,11 +3861,11 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@commitlint/cli@19.8.0(@types/node@22.10.1)(typescript@5.7.2)': + '@commitlint/cli@19.8.0(@types/node@22.15.29)(typescript@5.7.2)': dependencies: '@commitlint/format': 19.8.0 '@commitlint/lint': 19.8.0 - '@commitlint/load': 19.8.0(@types/node@22.10.1)(typescript@5.7.2) + '@commitlint/load': 19.8.0(@types/node@22.15.29)(typescript@5.7.2) '@commitlint/read': 19.8.0 '@commitlint/types': 19.8.0 tinyexec: 0.3.2 @@ -3912,7 +3912,7 @@ snapshots: '@commitlint/rules': 19.8.0 '@commitlint/types': 19.8.0 - '@commitlint/load@19.8.0(@types/node@22.10.1)(typescript@5.7.2)': + '@commitlint/load@19.8.0(@types/node@22.15.29)(typescript@5.7.2)': dependencies: '@commitlint/config-validator': 19.8.0 '@commitlint/execute-rule': 19.8.0 @@ -3920,7 +3920,7 @@ snapshots: '@commitlint/types': 19.8.0 chalk: 5.4.1 cosmiconfig: 9.0.0(typescript@5.7.2) - cosmiconfig-typescript-loader: 6.1.0(@types/node@22.10.1)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2) + cosmiconfig-typescript-loader: 6.1.0(@types/node@22.15.29)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -4023,27 +4023,27 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.10.1 + '@types/node': 22.15.29 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2))': + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.10.1 + '@types/node': 22.15.29 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)) + jest-config: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4072,7 +4072,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.10.1 + '@types/node': 22.15.29 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -4090,7 +4090,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.10.1 + '@types/node': 22.15.29 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -4112,7 +4112,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 22.10.1 + '@types/node': 22.15.29 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -4182,7 +4182,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.10.1 + '@types/node': 22.15.29 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -4488,11 +4488,11 @@ snapshots: '@types/conventional-commits-parser@5.0.1': dependencies: - '@types/node': 22.10.1 + '@types/node': 22.15.29 '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.10.1 + '@types/node': 22.15.29 '@types/istanbul-lib-coverage@2.0.6': {} @@ -4513,9 +4513,9 @@ snapshots: '@types/json5@0.0.29': {} - '@types/node@22.10.1': + '@types/node@22.15.29': dependencies: - undici-types: 6.20.0 + undici-types: 6.21.0 '@types/normalize-package-data@2.4.4': {} @@ -4873,12 +4873,12 @@ snapshots: bottleneck@2.19.5: {} - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -5066,9 +5066,9 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.1)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2): + cosmiconfig-typescript-loader@6.1.0(@types/node@22.15.29)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2): dependencies: - '@types/node': 22.10.1 + '@types/node': 22.15.29 cosmiconfig: 9.0.0(typescript@5.7.2) jiti: 2.4.2 typescript: 5.7.2 @@ -5091,13 +5091,13 @@ snapshots: optionalDependencies: typescript: 5.7.2 - create-jest@29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)): + create-jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)) + jest-config: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5427,13 +5427,13 @@ snapshots: dependencies: eslint: 8.57.1 - eslint-plugin-jest@28.9.0(@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)))(typescript@5.7.2): + eslint-plugin-jest@28.12.0(@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)))(typescript@5.7.2): dependencies: '@typescript-eslint/utils': 8.15.0(eslint@8.57.1)(typescript@5.7.2) eslint: 8.57.1 optionalDependencies: '@typescript-eslint/eslint-plugin': 8.15.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2) - jest: 29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)) + jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)) transitivePeerDependencies: - supports-color - typescript @@ -5455,7 +5455,7 @@ snapshots: eslint-remote-tester-repositories@1.0.1: {} - eslint-remote-tester@3.0.1(eslint@8.57.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)): + eslint-remote-tester@3.0.1(eslint@8.57.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)): dependencies: '@babel/code-frame': 7.26.2 JSONStream: 1.3.5 @@ -5466,7 +5466,7 @@ snapshots: react: 17.0.2 simple-git: 3.27.0 optionalDependencies: - ts-node: 10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2) + ts-node: 10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2) transitivePeerDependencies: - '@types/react' - bufferutil @@ -6184,7 +6184,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.10.1 + '@types/node': 22.15.29 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -6204,16 +6204,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)): + jest-cli@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)) + create-jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)) + jest-config: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -6223,7 +6223,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)): + jest-config@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 @@ -6248,8 +6248,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.10.1 - ts-node: 10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2) + '@types/node': 22.15.29 + ts-node: 10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -6278,7 +6278,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.10.1 + '@types/node': 22.15.29 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -6288,7 +6288,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 22.10.1 + '@types/node': 22.15.29 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -6327,7 +6327,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.10.1 + '@types/node': 22.15.29 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -6362,7 +6362,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.10.1 + '@types/node': 22.15.29 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -6390,7 +6390,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.10.1 + '@types/node': 22.15.29 chalk: 4.1.2 cjs-module-lexer: 1.4.1 collect-v8-coverage: 1.0.2 @@ -6436,7 +6436,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.10.1 + '@types/node': 22.15.29 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -6455,7 +6455,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.10.1 + '@types/node': 22.15.29 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -6464,17 +6464,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 22.10.1 + '@types/node': 22.15.29 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)): + jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.10.1)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2)) + jest-cli: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -6689,11 +6689,11 @@ snapshots: minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimist@1.2.8: {} @@ -7403,14 +7403,14 @@ snapshots: dependencies: typescript: 5.7.2 - ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.10.1)(typescript@5.7.2): + ts-node@10.9.2(@swc/core@1.9.3)(@types/node@22.15.29)(typescript@5.7.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.10.1 + '@types/node': 22.15.29 acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -7504,7 +7504,7 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 - undici-types@6.20.0: {} + undici-types@6.21.0: {} unicode-emoji-modifier-base@1.0.0: {} diff --git a/tests/index.test.ts b/tests/index.test.ts index 03cdbe2b..0d3ab20d 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -61,7 +61,7 @@ it('should export configs that refer to actual rules', () => { 'flat/marko', ]); const allConfigRules = Object.values(allConfigs) - .map((config) => Object.keys(config.rules)) + .map((config) => Object.keys(config.rules ?? {})) .reduce((previousValue, currentValue) => [ ...previousValue, ...currentValue, diff --git a/tests/lib/rules/consistent-data-testid.test.ts b/tests/lib/rules/consistent-data-testid.test.ts index b0f5874a..a2cdc46c 100644 --- a/tests/lib/rules/consistent-data-testid.test.ts +++ b/tests/lib/rules/consistent-data-testid.test.ts @@ -1,4 +1,7 @@ -import { type TSESLint } from '@typescript-eslint/utils'; +import { + type InvalidTestCase, + type ValidTestCase, +} from '@typescript-eslint/rule-tester'; import rule, { MessageIds, @@ -9,9 +12,9 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); -type ValidTestCase = TSESLint.ValidTestCase; -type InvalidTestCase = TSESLint.InvalidTestCase; -type TestCase = InvalidTestCase | ValidTestCase; +type RuleValidTestCase = ValidTestCase; +type RuleInvalidTestCase = InvalidTestCase; +type TestCase = RuleValidTestCase | RuleInvalidTestCase; const disableAggressiveReporting = (array: T[]): T[] => array.map((testCase) => ({ ...testCase, @@ -22,11 +25,11 @@ const disableAggressiveReporting = (array: T[]): T[] => }, })); -const validTestCases: ValidTestCase[] = [ +const validTestCases: RuleValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -40,7 +43,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -54,7 +57,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -73,7 +76,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -92,7 +95,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -111,7 +114,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -130,7 +133,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -149,7 +152,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -168,7 +171,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -188,7 +191,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { const dynamicTestId = 'somethingDynamic'; return ( @@ -205,7 +208,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -224,7 +227,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -244,7 +247,7 @@ const validTestCases: ValidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -262,11 +265,11 @@ const validTestCases: ValidTestCase[] = [ filename: '/my/cool/file/path/[...wildcard].js', }, ]; -const invalidTestCases: InvalidTestCase[] = [ +const invalidTestCases: RuleInvalidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -291,7 +294,7 @@ const invalidTestCases: InvalidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -321,7 +324,7 @@ const invalidTestCases: InvalidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -352,7 +355,7 @@ const invalidTestCases: InvalidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -391,7 +394,7 @@ const invalidTestCases: InvalidTestCase[] = [ { code: ` import React from 'react'; - + const TestComponent = props => { return (
@@ -421,7 +424,7 @@ const invalidTestCases: InvalidTestCase[] = [ { code: ` // test for custom message import React from 'react'; - + const TestComponent = props => { return (
diff --git a/tests/lib/rules/no-node-access.test.ts b/tests/lib/rules/no-node-access.test.ts index cdecd5a9..32960944 100644 --- a/tests/lib/rules/no-node-access.test.ts +++ b/tests/lib/rules/no-node-access.test.ts @@ -1,11 +1,17 @@ -import { type TSESLint } from '@typescript-eslint/utils'; +import { InvalidTestCase, ValidTestCase } from '@typescript-eslint/rule-tester'; -import rule, { RULE_NAME, Options } from '../../../lib/rules/no-node-access'; +import rule, { + RULE_NAME, + Options, + MessageIds, +} from '../../../lib/rules/no-node-access'; +import { EVENT_HANDLER_METHODS, EVENTS_SIMULATORS } from '../../../lib/utils'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); -type ValidTestCase = TSESLint.ValidTestCase; +type RuleValidTestCase = ValidTestCase; +type RuleInvalidTestCase = InvalidTestCase; const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/angular', @@ -15,7 +21,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ ]; ruleTester.run(RULE_NAME, rule, { - valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap( + valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap( (testingFramework) => [ { code: ` @@ -100,7 +106,7 @@ ruleTester.run(RULE_NAME, rule, { code: `/* related to issue #386 fix * now all node accessing properties (listed in lib/utils/index.ts, in PROPERTIES_RETURNING_NODES) * will not be reported by this rule because anything props.something won't be reported. - */ + */ import { screen } from '${testingFramework}'; function ComponentA(props) { if (props.firstChild) { @@ -142,21 +148,39 @@ ruleTester.run(RULE_NAME, rule, { // Example from discussions in issue #386 code: ` import { render } from '${testingFramework}'; - + function Wrapper({ children }) { // this should NOT be reported if (children) { // ... } - + // this should NOT be reported return
{children}
}; - + render(); expect(screen.getByText('SomeComponent')).toBeInTheDocument(); `, }, + { + code: ` + import userEvent from '@testing-library/user-event'; + import { screen } from '${testingFramework}'; + + const buttonText = screen.getByText('submit'); + const user = userEvent.setup(); + user.click(buttonText); + `, + }, + ...EVENTS_SIMULATORS.map((simulator) => ({ + code: ` + import { screen } from '${testingFramework}'; + + const buttonText = screen.getByText('submit'); + ${simulator}.click(buttonText); + `, + })), ] ), invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ @@ -395,5 +419,24 @@ ruleTester.run(RULE_NAME, rule, { }, ], }, + ...EVENT_HANDLER_METHODS.map((method) => ({ + code: ` + import { screen } from '${testingFramework}'; + + const button = document.getElementById('submit-btn').${method}(); + `, + errors: [ + { + line: 4, + column: 33, + messageId: 'noNodeAccess', + }, + { + line: 4, + column: 62, + messageId: 'noNodeAccess', + }, + ], + })), ]), }); diff --git a/tests/lib/rules/no-unnecessary-act.test.ts b/tests/lib/rules/no-unnecessary-act.test.ts index 3dcac3d8..26305961 100644 --- a/tests/lib/rules/no-unnecessary-act.test.ts +++ b/tests/lib/rules/no-unnecessary-act.test.ts @@ -1,4 +1,7 @@ -import { type TSESLint } from '@typescript-eslint/utils'; +import { + type InvalidTestCase, + type ValidTestCase, +} from '@typescript-eslint/rule-tester'; import rule, { MessageIds, @@ -9,9 +12,9 @@ import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); -type ValidTestCase = TSESLint.ValidTestCase; -type InvalidTestCase = TSESLint.InvalidTestCase; -type TestCase = InvalidTestCase | ValidTestCase; +type RuleValidTestCase = ValidTestCase; +type RuleInvalidTestCase = InvalidTestCase; +type TestCase = RuleInvalidTestCase | RuleValidTestCase; const addOptions = ( array: T[], @@ -37,7 +40,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ ['@marko/testing-library', 'Marko TL'], ]; -const validNonStrictTestCases: ValidTestCase[] = [ +const validNonStrictTestCases: RuleValidTestCase[] = [ { code: `// case: RTL act wrapping both RTL and non-RTL calls import { act, render, waitFor } from '@testing-library/react' @@ -62,7 +65,7 @@ const validNonStrictTestCases: ValidTestCase[] = [ }, ]; -const validTestCases: ValidTestCase[] = [ +const validTestCases: RuleValidTestCase[] = [ ...SUPPORTED_TESTING_FRAMEWORKS.map(([testingFramework, shortName]) => ({ code: `// case: ${shortName} act wrapping non-${shortName} calls import { act } from '${testingFramework}' @@ -214,7 +217,7 @@ const validTestCases: ValidTestCase[] = [ })), ]; -const invalidStrictTestCases: InvalidTestCase[] = +const invalidStrictTestCases: RuleInvalidTestCase[] = SUPPORTED_TESTING_FRAMEWORKS.flatMap(([testingFramework, shortName]) => [ { code: `// case: ${shortName} act wrapping both ${shortName} and non-${shortName} calls with strict option @@ -244,7 +247,7 @@ const invalidStrictTestCases: InvalidTestCase[] = }, ]); -const invalidTestCases: InvalidTestCase[] = [ +const invalidTestCases: RuleInvalidTestCase[] = [ ...SUPPORTED_TESTING_FRAMEWORKS.map( ([testingFramework, shortName]) => ({ 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 2cdbe093..c7eed01a 100644 --- a/tests/lib/rules/no-wait-for-side-effects.test.ts +++ b/tests/lib/rules/no-wait-for-side-effects.test.ts @@ -1,4 +1,9 @@ -import rule, { RULE_NAME } from '../../../lib/rules/no-wait-for-side-effects'; +import { InvalidTestCase } from '@typescript-eslint/rule-tester'; + +import rule, { + RULE_NAME, + type MessageIds, +} from '../../../lib/rules/no-wait-for-side-effects'; import { createRuleTester } from '../test-utils'; const ruleTester = createRuleTester(); @@ -118,7 +123,7 @@ ruleTester.run(RULE_NAME, rule, { code: ` import { waitFor } from '${testingFramework}'; import { notUserEvent } from 'somewhere-else'; - + waitFor(() => { await notUserEvent.click(button) }) @@ -736,7 +741,7 @@ ruleTester.run(RULE_NAME, rule, { expect(b).toEqual('b') }).then(() => { userEvent.click(button) // Side effects are allowed inside .then() - expect(b).toEqual('b') + expect(b).toEqual('b') }) `, errors: [{ line: 4, column: 11, messageId: 'noSideEffectsWaitFor' }], @@ -808,9 +813,10 @@ ruleTester.run(RULE_NAME, rule, { } as const, ]), - ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - { - code: ` + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap>( + (testingFramework) => [ + { + code: ` import { waitFor } from '${testingFramework}'; import userEvent from '@testing-library/user-event' @@ -820,8 +826,9 @@ ruleTester.run(RULE_NAME, rule, { }); }); `, - errors: [{ line: 7, column: 13, messageId: 'noSideEffectsWaitFor' }], - }, - ]), + errors: [{ line: 7, column: 13, messageId: 'noSideEffectsWaitFor' }], + }, + ] + ), ], }); diff --git a/tests/lib/rules/prefer-find-by.test.ts b/tests/lib/rules/prefer-find-by.test.ts index 2f27043e..b4728c15 100644 --- a/tests/lib/rules/prefer-find-by.test.ts +++ b/tests/lib/rules/prefer-find-by.test.ts @@ -1,4 +1,7 @@ -import { TSESLint } from '@typescript-eslint/utils'; +import { + type InvalidTestCase, + type ValidTestCase, +} from '@typescript-eslint/rule-tester'; import rule, { RULE_NAME, @@ -26,9 +29,7 @@ function buildFindByMethod(queryMethod: string) { } function createScenario< - T extends - | TSESLint.InvalidTestCase - | TSESLint.ValidTestCase<[]>, + T extends InvalidTestCase | ValidTestCase<[]>, >(callback: (waitMethod: string, queryMethod: string) => T) { return SYNC_QUERIES_COMBINATIONS.map((queryMethod) => callback('waitFor', queryMethod) @@ -51,6 +52,17 @@ ruleTester.run(RULE_NAME, rule, { it('tests', async () => { const submitButton = await screen.${queryMethod}('foo') }) + `, + })), + ...ASYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ + code: ` + import {waitFor} from '${testingFramework}'; + it('tests', async () => { + await waitFor(async () => { + const button = screen.${queryMethod}("button", { name: "Submit" }) + expect(button).toBeInTheDocument() + }) + }) `, })), ...SYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({ @@ -164,11 +176,22 @@ ruleTester.run(RULE_NAME, rule, { const { container } = render() await waitFor(() => expect(container.querySelector('baz')).toBeInTheDocument()); }) + `, + }, + { + code: ` + import {waitFor} from '${testingFramework}'; + it('tests', async () => { + await waitFor(async () => { + const button = await foo("button", { name: "Submit" }) + expect(button).toBeInTheDocument() + }) + }) `, }, ]), invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}, screen} from '${testingFramework}'; it('tests', async () => { @@ -330,7 +353,7 @@ ruleTester.run(RULE_NAME, rule, { output: null, }, // presence matchers - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -359,7 +382,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -388,7 +411,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -417,7 +440,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -446,7 +469,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -475,7 +498,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -504,7 +527,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -533,7 +556,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -560,7 +583,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -587,7 +610,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -614,7 +637,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -641,7 +664,7 @@ ruleTester.run(RULE_NAME, rule, { }) `, })), - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: ` import {${waitMethod}} from '${testingFramework}'; it('tests', async () => { @@ -670,7 +693,7 @@ ruleTester.run(RULE_NAME, rule, { })), // Issue #579, https://github.com/testing-library/eslint-plugin-testing-library/issues/579 // findBy can have two sets of options: await screen.findByText('text', queryOptions, waitForOptions) - ...createScenario((waitMethod: string, queryMethod: string) => ({ + ...createScenario((waitMethod, queryMethod) => ({ code: `import {${waitMethod}} from '${testingFramework}'; const button = await ${waitMethod}(() => screen.${queryMethod}('Count is: 0'), { timeout: 100, interval: 200 }) `, @@ -691,5 +714,36 @@ ruleTester.run(RULE_NAME, rule, { )}('Count is: 0', { timeout: 100, interval: 200 }) `, })), + ...ASYNC_QUERIES_COMBINATIONS.map>( + (queryMethod) => ({ + code: ` + import {waitFor} from '${testingFramework}'; + it('tests', async () => { + await waitFor(async () => { + const button = await screen.${queryMethod}("button", { name: "Submit" }) + expect(button).toBeInTheDocument() + }) + }) + `, + errors: [ + { + messageId: 'preferFindBy', + data: { + queryVariant: getFindByQueryVariant(queryMethod), + queryMethod: queryMethod.split('By')[1], + prevQuery: queryMethod, + waitForMethodName: 'waitFor', + }, + }, + ], + output: ` + import {waitFor} from '${testingFramework}'; + it('tests', async () => { + const button = await screen.${queryMethod}("button", { name: "Submit" }) + expect(button).toBeInTheDocument() + }) + `, + }) + ), ]), }); diff --git a/tests/lib/rules/prefer-presence-queries.test.ts b/tests/lib/rules/prefer-presence-queries.test.ts index 488534c7..72cbe4d5 100644 --- a/tests/lib/rules/prefer-presence-queries.test.ts +++ b/tests/lib/rules/prefer-presence-queries.test.ts @@ -1,4 +1,7 @@ -import { TSESLint } from '@typescript-eslint/utils'; +import { + type InvalidTestCase, + type ValidTestCase, +} from '@typescript-eslint/rule-tester'; import rule, { RULE_NAME, @@ -17,8 +20,8 @@ const queryAllByQueries = ALL_QUERIES_METHODS.map( (method) => `queryAll${method}` ); -type RuleValidTestCase = TSESLint.ValidTestCase; -type RuleInvalidTestCase = TSESLint.InvalidTestCase; +type RuleValidTestCase = ValidTestCase; +type RuleInvalidTestCase = InvalidTestCase; type AssertionFnParams = { query: string; @@ -79,6 +82,15 @@ const getDisabledValidAssertion = ({ }; }; +const toggleQueryPrefix = (query: string): string => { + if (query.startsWith('get')) return query.replace(/^get/, 'query'); + if (query.startsWith('query')) return query.replace(/^query/, 'get'); + return query; +}; + +const applyScreenPrefix = (query: string, shouldUseScreen: boolean): string => + shouldUseScreen ? `screen.${query}` : query; + const getInvalidAssertions = ({ query, matcher, @@ -86,12 +98,18 @@ const getInvalidAssertions = ({ shouldUseScreen = false, assertionType, }: AssertionFnParams): RuleInvalidTestCase[] => { - const finalQuery = shouldUseScreen ? `screen.${query}` : query; + const finalQuery = applyScreenPrefix(query, shouldUseScreen); const code = `expect(${finalQuery}('Hello'))${matcher}`; + + const outputQuery = toggleQueryPrefix(query); + const finalOutputQuery = applyScreenPrefix(outputQuery, shouldUseScreen); + const output = `expect(${finalOutputQuery}('Hello'))${matcher}`; + return [ { code, errors: [{ messageId, line: 1, column: shouldUseScreen ? 15 : 8 }], + output, }, { code, @@ -102,6 +120,7 @@ const getInvalidAssertions = ({ }, ], errors: [{ messageId, line: 1, column: shouldUseScreen ? 15 : 8 }], + output, }, ]; }; @@ -921,7 +940,7 @@ ruleTester.run(RULE_NAME, rule, { // submit button exists const submitButton = screen.getByRole('button') fireEvent.click(submitButton) - + // right after clicking submit button it disappears expect(submitButton).not.toBeInTheDocument() `, @@ -1304,18 +1323,24 @@ ruleTester.run(RULE_NAME, rule, { { code: 'expect(screen.getAllByText("button")[1]).not.toBeInTheDocument()', errors: [{ messageId: 'wrongAbsenceQuery', line: 1, column: 15 }], + output: + 'expect(screen.queryAllByText("button")[1]).not.toBeInTheDocument()', }, { code: 'expect(screen.getAllByText("button")[1]).not.toBeOnTheScreen()', errors: [{ messageId: 'wrongAbsenceQuery', line: 1, column: 15 }], + output: + 'expect(screen.queryAllByText("button")[1]).not.toBeOnTheScreen()', }, { code: 'expect(screen.queryAllByText("button")[1]).toBeInTheDocument()', errors: [{ messageId: 'wrongPresenceQuery', line: 1, column: 15 }], + output: 'expect(screen.getAllByText("button")[1]).toBeInTheDocument()', }, { code: 'expect(screen.queryAllByText("button")[1]).toBeOnTheScreen()', errors: [{ messageId: 'wrongPresenceQuery', line: 1, column: 15 }], + output: 'expect(screen.getAllByText("button")[1]).toBeOnTheScreen()', }, { code: ` @@ -1323,6 +1348,10 @@ ruleTester.run(RULE_NAME, rule, { expect(queryByCustomQuery("button")).toBeInTheDocument() `, errors: [{ messageId: 'wrongPresenceQuery', line: 3, column: 16 }], + output: ` + // case: asserting presence incorrectly with custom queryBy* query + expect(getByCustomQuery("button")).toBeInTheDocument() + `, }, { code: ` @@ -1330,6 +1359,10 @@ ruleTester.run(RULE_NAME, rule, { expect(queryByCustomQuery("button")).toBeOnTheScreen() `, errors: [{ messageId: 'wrongPresenceQuery', line: 3, column: 16 }], + output: ` + // case: asserting presence incorrectly with custom queryBy* query + expect(getByCustomQuery("button")).toBeOnTheScreen() + `, }, { code: ` @@ -1337,6 +1370,10 @@ ruleTester.run(RULE_NAME, rule, { expect(getByCustomQuery("button")).not.toBeInTheDocument() `, errors: [{ messageId: 'wrongAbsenceQuery', line: 3, column: 16 }], + output: ` + // case: asserting absence incorrectly with custom getBy* query + expect(queryByCustomQuery("button")).not.toBeInTheDocument() + `, }, { code: ` @@ -1344,6 +1381,10 @@ ruleTester.run(RULE_NAME, rule, { expect(getByCustomQuery("button")).not.toBeOnTheScreen() `, errors: [{ messageId: 'wrongAbsenceQuery', line: 3, column: 16 }], + output: ` + // case: asserting absence incorrectly with custom getBy* query + expect(queryByCustomQuery("button")).not.toBeOnTheScreen() + `, }, { settings: { @@ -1355,6 +1396,11 @@ ruleTester.run(RULE_NAME, rule, { expect(queryByRole("button")).toBeInTheDocument() `, errors: [{ line: 4, column: 14, messageId: 'wrongPresenceQuery' }], + output: ` + // case: asserting presence incorrectly importing custom module + import 'test-utils' + expect(getByRole("button")).toBeInTheDocument() + `, }, { settings: { @@ -1366,6 +1412,11 @@ ruleTester.run(RULE_NAME, rule, { expect(queryByRole("button")).toBeOnTheScreen() `, errors: [{ line: 4, column: 14, messageId: 'wrongPresenceQuery' }], + output: ` + // case: asserting presence incorrectly importing custom module + import 'test-utils' + expect(getByRole("button")).toBeOnTheScreen() + `, }, { settings: { @@ -1377,6 +1428,11 @@ ruleTester.run(RULE_NAME, rule, { expect(getByRole("button")).not.toBeInTheDocument() `, errors: [{ line: 4, column: 14, messageId: 'wrongAbsenceQuery' }], + output: ` + // case: asserting absence incorrectly importing custom module + import 'test-utils' + expect(queryByRole("button")).not.toBeInTheDocument() + `, }, { settings: { @@ -1388,18 +1444,29 @@ ruleTester.run(RULE_NAME, rule, { expect(getByRole("button")).not.toBeOnTheScreen() `, errors: [{ line: 4, column: 14, messageId: 'wrongAbsenceQuery' }], + output: ` + // case: asserting absence incorrectly importing custom module + import 'test-utils' + expect(queryByRole("button")).not.toBeOnTheScreen() + `, }, { code: ` // case: asserting within check does still work with improper outer clause expect(within(screen.getByRole("button")).getByText("Hello")).not.toBeInTheDocument()`, errors: [{ line: 3, column: 46, messageId: 'wrongAbsenceQuery' }], + output: ` + // case: asserting within check does still work with improper outer clause + expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeInTheDocument()`, }, { code: ` // case: asserting within check does still work with improper outer clause expect(within(screen.getByRole("button")).queryByText("Hello")).toBeInTheDocument()`, errors: [{ line: 3, column: 46, messageId: 'wrongPresenceQuery' }], + output: ` + // case: asserting within check does still work with improper outer clause + expect(within(screen.getByRole("button")).getByText("Hello")).toBeInTheDocument()`, }, { code: ` @@ -1409,18 +1476,27 @@ ruleTester.run(RULE_NAME, rule, { { line: 3, column: 25, messageId: 'wrongPresenceQuery' }, { line: 3, column: 48, messageId: 'wrongAbsenceQuery' }, ], + output: ` + // case: asserting within check does still work with improper outer clause and improper inner clause + expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeInTheDocument()`, }, { code: ` // case: asserting within check does still work with proper outer clause and improper inner clause expect(within(screen.queryByRole("button")).queryByText("Hello")).not.toBeInTheDocument()`, errors: [{ line: 3, column: 25, messageId: 'wrongPresenceQuery' }], + output: ` + // case: asserting within check does still work with proper outer clause and improper inner clause + expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeInTheDocument()`, }, { code: ` // case: asserting within check does still work with proper outer clause and improper inner clause expect(within(screen.queryByRole("button")).getByText("Hello")).toBeInTheDocument()`, errors: [{ line: 3, column: 25, messageId: 'wrongPresenceQuery' }], + output: ` + // case: asserting within check does still work with proper outer clause and improper inner clause + expect(within(screen.getByRole("button")).getByText("Hello")).toBeInTheDocument()`, }, { code: ` @@ -1430,18 +1506,27 @@ ruleTester.run(RULE_NAME, rule, { { line: 3, column: 25, messageId: 'wrongPresenceQuery' }, { line: 3, column: 48, messageId: 'wrongPresenceQuery' }, ], + output: ` + // case: asserting within check does still work with improper outer clause and improper inner clause + expect(within(screen.getByRole("button")).getByText("Hello")).toBeInTheDocument()`, }, { code: ` // case: asserting within check does still work with improper outer clause expect(within(screen.getByRole("button")).getByText("Hello")).not.toBeOnTheScreen()`, errors: [{ line: 3, column: 46, messageId: 'wrongAbsenceQuery' }], + output: ` + // case: asserting within check does still work with improper outer clause + expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeOnTheScreen()`, }, { code: ` // case: asserting within check does still work with improper outer clause expect(within(screen.getByRole("button")).queryByText("Hello")).toBeOnTheScreen()`, errors: [{ line: 3, column: 46, messageId: 'wrongPresenceQuery' }], + output: ` + // case: asserting within check does still work with improper outer clause + expect(within(screen.getByRole("button")).getByText("Hello")).toBeOnTheScreen()`, }, { code: ` @@ -1451,18 +1536,27 @@ ruleTester.run(RULE_NAME, rule, { { line: 3, column: 25, messageId: 'wrongPresenceQuery' }, { line: 3, column: 48, messageId: 'wrongAbsenceQuery' }, ], + output: ` + // case: asserting within check does still work with improper outer clause and improper inner clause + expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeOnTheScreen()`, }, { code: ` // case: asserting within check does still work with proper outer clause and improper inner clause expect(within(screen.queryByRole("button")).queryByText("Hello")).not.toBeOnTheScreen()`, errors: [{ line: 3, column: 25, messageId: 'wrongPresenceQuery' }], + output: ` + // case: asserting within check does still work with proper outer clause and improper inner clause + expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeOnTheScreen()`, }, { code: ` // case: asserting within check does still work with proper outer clause and improper inner clause expect(within(screen.queryByRole("button")).getByText("Hello")).toBeOnTheScreen()`, errors: [{ line: 3, column: 25, messageId: 'wrongPresenceQuery' }], + output: ` + // case: asserting within check does still work with proper outer clause and improper inner clause + expect(within(screen.getByRole("button")).getByText("Hello")).toBeOnTheScreen()`, }, { code: ` @@ -1472,6 +1566,9 @@ ruleTester.run(RULE_NAME, rule, { { line: 3, column: 25, messageId: 'wrongPresenceQuery' }, { line: 3, column: 48, messageId: 'wrongPresenceQuery' }, ], + output: ` + // case: asserting within check does still work with improper outer clause and improper inner clause + expect(within(screen.getByRole("button")).getByText("Hello")).toBeOnTheScreen()`, }, ], }); diff --git a/tests/lib/rules/prefer-query-matchers.test.ts b/tests/lib/rules/prefer-query-matchers.test.ts index 908f1b27..66f03644 100644 --- a/tests/lib/rules/prefer-query-matchers.test.ts +++ b/tests/lib/rules/prefer-query-matchers.test.ts @@ -1,4 +1,7 @@ -import { TSESLint } from '@typescript-eslint/utils'; +import { + type InvalidTestCase, + type ValidTestCase, +} from '@typescript-eslint/rule-tester'; import rule, { RULE_NAME, @@ -17,8 +20,8 @@ const queryAllByQueries = ALL_QUERIES_METHODS.map( (method) => `queryAll${method}` ); -type RuleValidTestCase = TSESLint.ValidTestCase; -type RuleInvalidTestCase = TSESLint.InvalidTestCase; +type RuleValidTestCase = ValidTestCase; +type RuleInvalidTestCase = InvalidTestCase; type AssertionFnParams = { query: string; diff --git a/tests/lib/rules/prefer-user-event.test.ts b/tests/lib/rules/prefer-user-event.test.ts index aae52d9b..299a65dc 100644 --- a/tests/lib/rules/prefer-user-event.test.ts +++ b/tests/lib/rules/prefer-user-event.test.ts @@ -1,4 +1,7 @@ -import { TSESLint } from '@typescript-eslint/utils'; +import { + type InvalidTestCase, + type ValidTestCase, +} from '@typescript-eslint/rule-tester'; import rule, { MAPPING_TO_USER_EVENT, @@ -11,9 +14,7 @@ import { LIBRARY_MODULES } from '../../../lib/utils'; import { createRuleTester } from '../test-utils'; function createScenarioWithImport< - T extends - | TSESLint.InvalidTestCase - | TSESLint.ValidTestCase, + T extends InvalidTestCase | ValidTestCase, >(callback: (libraryModule: string, fireEventMethod: string) => T) { return LIBRARY_MODULES.reduce( (acc: Array, libraryModule) => @@ -69,7 +70,7 @@ ruleTester.run(RULE_NAME, rule, { userEvent.${userEventMethod}(foo) `, })), - ...createScenarioWithImport>( + ...createScenarioWithImport>( (libraryModule: string, fireEventMethod: string) => ({ code: ` import { fireEvent } from '${libraryModule}' @@ -79,7 +80,7 @@ ruleTester.run(RULE_NAME, rule, { options: [{ allowedMethods: [fireEventMethod] }], }) ), - ...createScenarioWithImport>( + ...createScenarioWithImport>( (libraryModule: string, fireEventMethod: string) => ({ code: ` import { fireEvent as fireEventAliased } from '${libraryModule}' @@ -89,7 +90,7 @@ ruleTester.run(RULE_NAME, rule, { options: [{ allowedMethods: [fireEventMethod] }], }) ), - ...createScenarioWithImport>( + ...createScenarioWithImport>( (libraryModule: string, fireEventMethod: string) => ({ code: ` import * as dom from '${libraryModule}' @@ -273,7 +274,7 @@ ruleTester.run(RULE_NAME, rule, { }, ], invalid: [ - ...createScenarioWithImport>( + ...createScenarioWithImport>( (libraryModule: string, fireEventMethod: string) => ({ code: ` import { fireEvent } from '${libraryModule}' @@ -293,7 +294,7 @@ ruleTester.run(RULE_NAME, rule, { ], }) ), - ...createScenarioWithImport>( + ...createScenarioWithImport>( (libraryModule: string, fireEventMethod: string) => ({ code: ` import * as dom from '${libraryModule}' @@ -312,7 +313,7 @@ ruleTester.run(RULE_NAME, rule, { ], }) ), - ...createScenarioWithImport>( + ...createScenarioWithImport>( (libraryModule: string, fireEventMethod: string) => ({ code: ` const { fireEvent } = require('${libraryModule}') @@ -331,7 +332,7 @@ ruleTester.run(RULE_NAME, rule, { ], }) ), - ...createScenarioWithImport>( + ...createScenarioWithImport>( (libraryModule: string, fireEventMethod: string) => ({ code: ` const rtl = require('${libraryModule}') @@ -484,7 +485,7 @@ ruleTester.run(RULE_NAME, rule, { }, code: ` import { fireEvent, createEvent } from 'test-utils' - + fireEvent(node, createEvent('${fireEventMethod}', node)) `, errors: [ diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..f46aafbc --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["./tests/**/*.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index b4fb3559..c52787a0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,5 +16,5 @@ "outDir": "dist", "sourceMap": false }, - "include": ["./lib/**/*.ts"] + "include": ["./lib/**/*.ts", "./tests/**/*.ts"] }