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..14b7957d 100644 --- a/lib/rules/no-node-access.ts +++ b/lib/rules/no-node-access.ts @@ -52,11 +52,17 @@ export default createTestingLibraryRule({ return; } + const propertyName = ASTUtils.isIdentifier(node.property) + ? node.property.name + : null; + if ( - ASTUtils.isIdentifier(node.property) && - ALL_RETURNING_NODES.includes(node.property.name) + propertyName && + ALL_RETURNING_NODES.some( + (allReturningNode) => allReturningNode === propertyName + ) ) { - 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/utils/index.ts b/lib/utils/index.ts index cb0e8e03..e43b8102 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,20 @@ const METHODS_RETURNING_NODES = [ 'getElementsByTagNameNS', 'querySelector', 'querySelectorAll', -]; +] 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, 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-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/prefer-find-by.test.ts b/tests/lib/rules/prefer-find-by.test.ts index 3521db71..c9d2c64b 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) diff --git a/tests/lib/rules/prefer-presence-queries.test.ts b/tests/lib/rules/prefer-presence-queries.test.ts index 488534c7..f185b00c 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; @@ -921,7 +924,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() `, 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: [