diff --git a/lib/detect-testing-library-utils.ts b/lib/detect-testing-library-utils.ts index 503729c1..61e9383e 100644 --- a/lib/detect-testing-library-utils.ts +++ b/lib/detect-testing-library-utils.ts @@ -3,6 +3,8 @@ import { TSESLint, TSESTree, } from '@typescript-eslint/experimental-utils'; +import micromatch from 'micromatch'; + import { getAssertNodeInfo, getDeepestIdentifierNode, @@ -27,7 +29,7 @@ import { export type TestingLibrarySettings = { 'testing-library/utils-module'?: string; - 'testing-library/filename-pattern'?: string; + 'testing-library/file-patterns'?: string[]; 'testing-library/custom-renders'?: string[]; }; @@ -56,7 +58,7 @@ type GetCustomModuleImportNodeFn = () => ImportModuleNode | null; type GetTestingLibraryImportNameFn = () => string | undefined; type GetCustomModuleImportNameFn = () => string | undefined; type IsTestingLibraryImportedFn = () => boolean; -type IsValidFilenameFn = () => boolean; +type IsMatchingFilenameFn = () => boolean; type IsGetQueryVariantFn = (node: TSESTree.Identifier) => boolean; type IsQueryQueryVariantFn = (node: TSESTree.Identifier) => boolean; type IsFindQueryVariantFn = (node: TSESTree.Identifier) => boolean; @@ -90,7 +92,7 @@ export interface DetectionHelpers { getTestingLibraryImportName: GetTestingLibraryImportNameFn; getCustomModuleImportName: GetCustomModuleImportNameFn; isTestingLibraryImported: IsTestingLibraryImportedFn; - isValidFilename: IsValidFilenameFn; + isMatchingFilename: IsMatchingFilenameFn; isGetQueryVariant: IsGetQueryVariantFn; isQueryQueryVariant: IsQueryQueryVariantFn; isFindQueryVariant: IsFindQueryVariantFn; @@ -110,7 +112,10 @@ export interface DetectionHelpers { isNodeComingFromTestingLibrary: IsNodeComingFromTestingLibraryFn; } -const DEFAULT_FILENAME_PATTERN = '^.*\\.(test|spec)\\.[jt]sx?$'; +const DEFAULT_FILE_PATTERNS = [ + '**/__tests__/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', +]; const FIRE_EVENT_NAME = 'fireEvent'; const RENDER_NAME = 'render'; @@ -132,9 +137,9 @@ export function detectTestingLibraryUtils< // Init options based on shared ESLint settings const customModule = context.settings['testing-library/utils-module']; - const filenamePattern = - context.settings['testing-library/filename-pattern'] ?? - DEFAULT_FILENAME_PATTERN; + const filePatterns = + context.settings['testing-library/file-patterns'] ?? + DEFAULT_FILE_PATTERNS; const customRenders = context.settings['testing-library/custom-renders']; /** @@ -244,12 +249,12 @@ export function detectTestingLibraryUtils< }; /** - * Determines whether filename is valid or not for current file - * being analyzed based on "testing-library/filename-pattern" setting. + * Determines whether file matches given patterns for being analyzed or not + * based on "testing-library/file-patterns" setting. */ - const isValidFilename: IsValidFilenameFn = () => { + const isMatchingFilename: IsMatchingFilenameFn = () => { const fileName = context.getFilename(); - return !!fileName.match(filenamePattern); + return micromatch.isMatch(fileName, filePatterns); }; /** @@ -536,7 +541,7 @@ export function detectTestingLibraryUtils< * Determines if file inspected meets all conditions to be reported by rules or not. */ const canReportErrors: CanReportErrorsFn = () => { - return isTestingLibraryImported() && isValidFilename(); + return isTestingLibraryImported() && isMatchingFilename(); }; /** @@ -566,7 +571,7 @@ export function detectTestingLibraryUtils< getTestingLibraryImportName, getCustomModuleImportName, isTestingLibraryImported, - isValidFilename, + isMatchingFilename, isGetQueryVariant, isQueryQueryVariant, isFindQueryVariant, diff --git a/package.json b/package.json index e98fd824..ffeab315 100644 --- a/package.json +++ b/package.json @@ -53,12 +53,14 @@ "semantic-release": "semantic-release" }, "dependencies": { - "@typescript-eslint/experimental-utils": "^4.18.0" + "@typescript-eslint/experimental-utils": "^4.18.0", + "micromatch": "^4.0.2" }, "devDependencies": { "@commitlint/cli": "^12.0.1", "@commitlint/config-conventional": "^12.0.1", "@types/jest": "^26.0.20", + "@types/micromatch": "^4.0.1", "@typescript-eslint/eslint-plugin": "^4.18.0", "@typescript-eslint/parser": "^4.18.0", "cpy-cli": "^3.1.1", diff --git a/tests/create-testing-library-rule.test.ts b/tests/create-testing-library-rule.test.ts index 4907e995..3be02512 100644 --- a/tests/create-testing-library-rule.test.ts +++ b/tests/create-testing-library-rule.test.ts @@ -70,7 +70,7 @@ ruleTester.run(RULE_NAME, rule, { import { foo } from 'report-me' `, settings: { - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': ['**/?(*.)+(not-matching).[jt]s?(x)'], }, }, { @@ -80,7 +80,7 @@ ruleTester.run(RULE_NAME, rule, { const { foo } = require('report-me') `, settings: { - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': ['**/?(*.)+(not-matching).[jt]s?(x)'], }, }, { @@ -252,7 +252,7 @@ ruleTester.run(RULE_NAME, rule, { }, { settings: { - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': ['**/?(*.)+(not-matching).[jt]s?(x)'], }, code: ` // case: built-in "getBy*" query not reported because custom filename doesn't match @@ -261,7 +261,7 @@ ruleTester.run(RULE_NAME, rule, { }, { settings: { - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': ['**/?(*.)+(not-matching).[jt]s?(x)'], }, code: ` // case: built-in "queryBy*" query not reported because custom filename doesn't match @@ -270,7 +270,7 @@ ruleTester.run(RULE_NAME, rule, { }, { settings: { - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': ['**/?(*.)+(not-matching).[jt]s?(x)'], }, code: ` // case: built-in "findBy*" query not reported because custom filename doesn't match @@ -320,7 +320,7 @@ ruleTester.run(RULE_NAME, rule, { { settings: { 'testing-library/utils-module': 'test-utils', - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': ['**/?(*.)+(not-matching).[jt]s?(x)'], }, code: ` // case: matching custom settings partially - module but not filename @@ -334,9 +334,9 @@ ruleTester.run(RULE_NAME, rule, { { settings: { 'testing-library/utils-module': 'test-utils', - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': ['**/?(*.)+(not-matching).[jt]s?(x)'], }, - filename: 'MyComponent.testing-library.js', + filename: 'project/src/MyComponent.testing-library.js', code: ` // case: matching custom settings partially - filename but not module import { render } from 'other-utils' @@ -368,7 +368,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 3, column: 7, messageId: 'fakeError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', code: ` // case: import module forced to be reported but from .spec.js named file import { foo } from 'report-me' @@ -376,13 +376,23 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 3, column: 7, messageId: 'fakeError' }], }, { - filename: 'MyComponent.testing-library.js', + filename: 'project/src/__tests__/MyComponent.tsx', + code: ` + // case: import module forced to be reported from __tests__ folder without .spec or .test suffix + import { foo } from 'report-me' + `, + errors: [{ line: 3, column: 7, messageId: 'fakeError' }], + }, + { + filename: 'project/src/MyComponent.testing-library.js', code: ` // case: import module forced to be reported with custom file name import { foo } from 'report-me' `, settings: { - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': [ + '**/?(*.)+(testing-library).[jt]s?(x)', + ], }, errors: [{ line: 3, column: 7, messageId: 'fakeError' }], }, @@ -733,7 +743,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 3, column: 25, messageId: 'findByError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', code: ` // case: custom "getBy*" query reported without import (aggressive reporting) getByIcon('search') @@ -741,7 +751,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 3, column: 7, messageId: 'customQueryError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', code: ` // case: custom "getBy*" query reported without import using within (aggressive reporting) within(container).getByIcon('search') @@ -788,7 +798,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 4, column: 7, messageId: 'getByError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', settings: { 'testing-library/utils-module': 'test-utils', }, @@ -800,7 +810,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 4, column: 7, messageId: 'queryByError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', settings: { 'testing-library/utils-module': 'test-utils', }, @@ -823,7 +833,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 4, column: 7, messageId: 'getByError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', settings: { 'testing-library/utils-module': 'test-utils', }, @@ -835,7 +845,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 4, column: 7, messageId: 'queryByError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', settings: { 'testing-library/utils-module': 'test-utils', }, @@ -859,7 +869,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 4, column: 7, messageId: 'customQueryError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', settings: { 'testing-library/utils-module': 'test-utils', }, @@ -871,7 +881,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 4, column: 7, messageId: 'customQueryError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', settings: { 'testing-library/utils-module': 'test-utils', }, @@ -894,7 +904,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 4, column: 7, messageId: 'customQueryError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', settings: { 'testing-library/utils-module': 'test-utils', }, @@ -906,7 +916,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [{ line: 4, column: 7, messageId: 'customQueryError' }], }, { - filename: 'MyComponent.spec.js', + filename: 'project/src/MyComponent.spec.js', settings: { 'testing-library/utils-module': 'test-utils', }, @@ -920,11 +930,11 @@ ruleTester.run(RULE_NAME, rule, { // Test Cases for all settings mixed { - filename: 'MyComponent.custom-suffix.js', + filename: 'project/src/MyComponent.custom-suffix.js', settings: { 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], 'testing-library/utils-module': 'test-utils', - 'testing-library/filename-pattern': 'custom-suffix\\.js', + 'testing-library/file-patterns': ['**/?(*.)+(custom-suffix).[jt]s?(x)'], }, code: ` // case: all aggressive reporting disabled and filename setup - matching all custom settings @@ -948,9 +958,11 @@ ruleTester.run(RULE_NAME, rule, { { settings: { 'testing-library/utils-module': 'test-utils', - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': [ + '**/?(*.)+(testing-library|custom-suffix).[jt]s?(x)', + ], }, - filename: 'MyComponent.testing-library.js', + filename: 'project/src/MyComponent.testing-library.js', code: ` // case: matching all custom settings import { render } from 'test-utils' diff --git a/tests/lib/rules/no-await-sync-query.test.ts b/tests/lib/rules/no-await-sync-query.test.ts index 2bef1ab9..b014430d 100644 --- a/tests/lib/rules/no-await-sync-query.test.ts +++ b/tests/lib/rules/no-await-sync-query.test.ts @@ -69,7 +69,9 @@ ruleTester.run(RULE_NAME, rule, { }, // sync query awaited but not matching filename pattern is invalid but not reported { - settings: { 'testing-library/filename-pattern': 'nope\\.js' }, + settings: { + 'testing-library/file-patterns': ['**/?(*.)+(nope).[jt]s?(x)'], + }, code: ` () => { const element = await getByRole('button') diff --git a/tests/lib/rules/no-dom-import.test.ts b/tests/lib/rules/no-dom-import.test.ts index d459f9e7..8d8e8ffe 100644 --- a/tests/lib/rules/no-dom-import.test.ts +++ b/tests/lib/rules/no-dom-import.test.ts @@ -27,35 +27,43 @@ ruleTester.run(RULE_NAME, rule, { }, { code: 'import { fireEvent } from "dom-testing-library"', - filename: 'filename.not-matching.js', + filename: 'project/src/filename.not-matching.js', }, { code: 'import { fireEvent } from "dom-testing-library"', - settings: { 'testing-library/filename-pattern': 'nope\\.js' }, + settings: { + 'testing-library/file-patterns': ['**/?(*.)+(nope).[jt]s?(x)'], + }, }, { code: 'const { fireEvent } = require("dom-testing-library")', - filename: 'filename.not-matching.js', + filename: 'project/src/filename.not-matching.js', }, { code: 'const { fireEvent } = require("dom-testing-library")', - settings: { 'testing-library/filename-pattern': 'nope\\.js' }, + settings: { + 'testing-library/file-patterns': ['**/?(*.)+(nope).[jt]s?(x)'], + }, }, { code: 'import { fireEvent } from "@testing-library/dom"', - filename: 'filename.not-matching.js', + filename: 'project/src/filename.not-matching.js', }, { code: 'import { fireEvent } from "@testing-library/dom"', - settings: { 'testing-library/filename-pattern': 'nope\\.js' }, + settings: { + 'testing-library/file-patterns': ['**/?(*.)+(nope).[jt]s?(x)'], + }, }, { code: 'const { fireEvent } = require("@testing-library/dom")', - filename: 'filename.not-matching.js', + filename: 'project/src/filename.not-matching.js', }, { code: 'const { fireEvent } = require("@testing-library/dom")', - settings: { 'testing-library/filename-pattern': 'nope\\.js' }, + settings: { + 'testing-library/file-patterns': ['**/?(*.)+(nope).[jt]s?(x)'], + }, }, ], invalid: [ diff --git a/tests/lib/rules/no-manual-cleanup.test.ts b/tests/lib/rules/no-manual-cleanup.test.ts index 3c681b27..6b56683e 100644 --- a/tests/lib/rules/no-manual-cleanup.test.ts +++ b/tests/lib/rules/no-manual-cleanup.test.ts @@ -56,7 +56,9 @@ ruleTester.run(RULE_NAME, rule, { }, { settings: { - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': [ + '**/?(*.)+(testing-library).[jt]s?(x)', + ], }, code: ` import { render, cleanup } from "${ALL_TESTING_LIBRARIES_WITH_CLEANUP[0]}" diff --git a/tests/lib/rules/no-node-access.test.ts b/tests/lib/rules/no-node-access.test.ts index c8a9e3f7..4df15a40 100644 --- a/tests/lib/rules/no-node-access.test.ts +++ b/tests/lib/rules/no-node-access.test.ts @@ -54,7 +54,9 @@ ruleTester.run(RULE_NAME, rule, { } `, settings: { - 'testing-library/filename-pattern': 'testing-library\\.js', + 'testing-library/file-patterns': [ + '**/?(*.)+(testing-library).[jt]s?(x)', + ], }, }, { diff --git a/tests/lib/test-utils.ts b/tests/lib/test-utils.ts index 88c1b778..b02a3a87 100644 --- a/tests/lib/test-utils.ts +++ b/tests/lib/test-utils.ts @@ -2,7 +2,8 @@ import { resolve } from 'path'; import { TSESLint } from '@typescript-eslint/experimental-utils'; const DEFAULT_TEST_CASE_CONFIG = { - filename: 'MyComponent.test.js', + filename: + '/Users/whoever/project/src/components/MyComponent/MyComponent.test.js', }; class TestingLibraryRuleTester extends TSESLint.RuleTester {