diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b0f703e4bc..0cba8bf10f8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,11 +62,11 @@ - `[jest-leak-detector]` Make leak-detector more aggressive when running GC ([#14526](https://github.com/jestjs/jest/pull/14526)) - `[jest-runtime]` Properly handle re-exported native modules in ESM via CJS ([#14589](https://github.com/jestjs/jest/pull/14589)) - `[jest-util]` Make sure `isInteractive` works in a browser ([#14552](https://github.com/jestjs/jest/pull/14552)) -- `[jest-util]` Add missing dependency on `jest-regex-util` ([#15030](https://github.com/jestjs/jest/pull/15030)) - `[pretty-format]` [**BREAKING**] Print `ArrayBuffer` and `DataView` correctly ([#14290](https://github.com/jestjs/jest/pull/14290)) - `[jest-cli]` When specifying paths on the command line, only match against the relative paths of the test files ([#12519](https://github.com/jestjs/jest/pull/12519)) - [**BREAKING**] Changes `testPathPattern` configuration option to `testPathPatterns`, which now takes a list of patterns instead of the regex. - [**BREAKING**] `--testPathPattern` is now `--testPathPatterns` + - [**BREAKING**] Specifying `testPathPatterns` when programmatically calling `watch` must be specified as `new TestPathPatterns(patterns)`, where `TestPathPatterns` can be imported from `@jest/pattern` - `[jest-reporters, jest-runner]` Unhandled errors without stack get correctly logged to console ([#14619](https://github.com/jestjs/jest/pull/14619)) ### Performance diff --git a/e2e/__tests__/executeTestsOnceInMpr.ts b/e2e/__tests__/executeTestsOnceInMpr.ts index 016d6eeec9a3..926307b4c5d6 100644 --- a/e2e/__tests__/executeTestsOnceInMpr.ts +++ b/e2e/__tests__/executeTestsOnceInMpr.ts @@ -46,7 +46,7 @@ test('Tests are executed only once even in an MPR', () => { }); /* eslint-enable sort-keys */ - const {stderr, exitCode} = runJest(DIR, ['foo/folder/my-test-bar.js']); + const {stderr, exitCode} = runJest(DIR, ['my-test-bar.js']); expect(exitCode).toBe(0); diff --git a/e2e/__tests__/globalSetup.test.ts b/e2e/__tests__/globalSetup.test.ts index 454b5f76e8bf..ffadb5b5b20c 100644 --- a/e2e/__tests__/globalSetup.test.ts +++ b/e2e/__tests__/globalSetup.test.ts @@ -109,7 +109,7 @@ test('should not call a globalSetup of a project if there are no tests to run fr const result = runWithJson(e2eDir, [ `--config=${configPath}`, - '--testPathPatterns=project-1', + '--testPathPatterns=setup1', ]); expect(result.exitCode).toBe(0); diff --git a/e2e/__tests__/globalTeardown.test.ts b/e2e/__tests__/globalTeardown.test.ts index cefd7e88accb..863f71e260e2 100644 --- a/e2e/__tests__/globalTeardown.test.ts +++ b/e2e/__tests__/globalTeardown.test.ts @@ -93,7 +93,7 @@ test('should not call a globalTeardown of a project if there are no tests to run const result = runWithJson('global-teardown', [ `--config=${configPath}`, - '--testPathPatterns=project-1', + '--testPathPatterns=teardown1', ]); expect(result.exitCode).toBe(0); diff --git a/e2e/__tests__/testPathPatternsSubprojects.test.ts b/e2e/__tests__/testPathPatternsSubprojects.test.ts new file mode 100644 index 000000000000..78590c98cabf --- /dev/null +++ b/e2e/__tests__/testPathPatternsSubprojects.test.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import {json} from '../runJest'; + +it('works when specifying --testPathPatterns when config is in subdir', () => { + const { + json: {numTotalTests}, + } = json('test-path-patterns-subprojects', [ + '--config=config/jest.config.js', + '--testPathPatterns=testA', + ]); + expect(numTotalTests).toBe(1); +}); diff --git a/e2e/global-setup/project-1/setup.test.js b/e2e/global-setup/project-1/setup1.test.js similarity index 100% rename from e2e/global-setup/project-1/setup.test.js rename to e2e/global-setup/project-1/setup1.test.js diff --git a/e2e/global-setup/project-2/setup.test.js b/e2e/global-setup/project-2/setup2.test.js similarity index 100% rename from e2e/global-setup/project-2/setup.test.js rename to e2e/global-setup/project-2/setup2.test.js diff --git a/e2e/global-setup/setupWithConfig.js b/e2e/global-setup/setupWithConfig.js index ff9b308107b1..08068255c459 100644 --- a/e2e/global-setup/setupWithConfig.js +++ b/e2e/global-setup/setupWithConfig.js @@ -6,6 +6,6 @@ */ module.exports = function (globalConfig, projectConfig) { - console.log(globalConfig.testPathPatterns); + console.log(globalConfig.testPathPatterns.patterns); console.log(projectConfig.cache); }; diff --git a/e2e/global-setup/setupWithDefaultExport.js b/e2e/global-setup/setupWithDefaultExport.js index 85b2c298a4d7..abb89050f448 100644 --- a/e2e/global-setup/setupWithDefaultExport.js +++ b/e2e/global-setup/setupWithDefaultExport.js @@ -6,6 +6,6 @@ */ export default function (globalConfig, projectConfig): void { - console.log(globalConfig.testPathPatterns); + console.log(globalConfig.testPathPatterns.patterns); console.log(projectConfig.cache); } diff --git a/e2e/global-teardown/project-1/teardown.test.js b/e2e/global-teardown/project-1/teardown1.test.js similarity index 100% rename from e2e/global-teardown/project-1/teardown.test.js rename to e2e/global-teardown/project-1/teardown1.test.js diff --git a/e2e/global-teardown/project-2/teardown.test.js b/e2e/global-teardown/project-2/teardown2.test.js similarity index 100% rename from e2e/global-teardown/project-2/teardown.test.js rename to e2e/global-teardown/project-2/teardown2.test.js diff --git a/e2e/global-teardown/teardownWithConfig.js b/e2e/global-teardown/teardownWithConfig.js index ff9b308107b1..08068255c459 100644 --- a/e2e/global-teardown/teardownWithConfig.js +++ b/e2e/global-teardown/teardownWithConfig.js @@ -6,6 +6,6 @@ */ module.exports = function (globalConfig, projectConfig) { - console.log(globalConfig.testPathPatterns); + console.log(globalConfig.testPathPatterns.patterns); console.log(projectConfig.cache); }; diff --git a/e2e/global-teardown/teardownWithDefaultExport.js b/e2e/global-teardown/teardownWithDefaultExport.js index 85b2c298a4d7..abb89050f448 100644 --- a/e2e/global-teardown/teardownWithDefaultExport.js +++ b/e2e/global-teardown/teardownWithDefaultExport.js @@ -6,6 +6,6 @@ */ export default function (globalConfig, projectConfig): void { - console.log(globalConfig.testPathPatterns); + console.log(globalConfig.testPathPatterns.patterns); console.log(projectConfig.cache); } diff --git a/e2e/runJest.ts b/e2e/runJest.ts index 659b9f64e12c..055be2becbb4 100644 --- a/e2e/runJest.ts +++ b/e2e/runJest.ts @@ -12,6 +12,7 @@ import dedent from 'dedent'; import execa = require('execa'); import * as fs from 'graceful-fs'; import stripAnsi = require('strip-ansi'); +import {TestPathPatterns} from '@jest/pattern'; import type {FormattedTestResults} from '@jest/test-result'; import {normalizeIcons} from '@jest/test-utils'; import type {Config} from '@jest/types'; @@ -285,5 +286,10 @@ export function getConfig( throw error; } - return JSON.parse(stdout); + const {testPathPatterns, ...globalConfig} = JSON.parse(stdout); + + return { + ...globalConfig, + testPathPatterns: new TestPathPatterns(testPathPatterns), + }; } diff --git a/e2e/test-path-patterns-subprojects/config/jest.config.js b/e2e/test-path-patterns-subprojects/config/jest.config.js new file mode 100644 index 000000000000..2b787227949b --- /dev/null +++ b/e2e/test-path-patterns-subprojects/config/jest.config.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +module.exports = { + projects: [{rootDir: 'src'}], +}; diff --git a/e2e/test-path-patterns-subprojects/package.json b/e2e/test-path-patterns-subprojects/package.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/e2e/test-path-patterns-subprojects/package.json @@ -0,0 +1 @@ +{} diff --git a/e2e/test-path-patterns-subprojects/src/__tests__/testA.js b/e2e/test-path-patterns-subprojects/src/__tests__/testA.js new file mode 100644 index 000000000000..94e2b1e87729 --- /dev/null +++ b/e2e/test-path-patterns-subprojects/src/__tests__/testA.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +it('test', () => {}); diff --git a/e2e/test-path-patterns-subprojects/src/__tests__/testB.js b/e2e/test-path-patterns-subprojects/src/__tests__/testB.js new file mode 100644 index 000000000000..94e2b1e87729 --- /dev/null +++ b/e2e/test-path-patterns-subprojects/src/__tests__/testB.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +it('test', () => {}); diff --git a/e2e/test-path-patterns-subprojects/src/__tests__/testC.js b/e2e/test-path-patterns-subprojects/src/__tests__/testC.js new file mode 100644 index 000000000000..94e2b1e87729 --- /dev/null +++ b/e2e/test-path-patterns-subprojects/src/__tests__/testC.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +it('test', () => {}); diff --git a/packages/jest-cli/src/args.ts b/packages/jest-cli/src/args.ts index 5e84991fd47d..f6993afcd784 100644 --- a/packages/jest-cli/src/args.ts +++ b/packages/jest-cli/src/args.ts @@ -655,7 +655,7 @@ export const options: {[key: string]: Options} = { }, testPathPatterns: { description: - 'A regexp pattern string that is matched against all tests ' + + 'An array of regexp pattern strings that are matched against all tests ' + 'paths before executing the test.', requiresArg: true, string: true, diff --git a/packages/jest-config/package.json b/packages/jest-config/package.json index ef9ffc9318f2..ad91f9b91960 100644 --- a/packages/jest-config/package.json +++ b/packages/jest-config/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@babel/core": "^7.11.6", + "@jest/pattern": "workspace:*", "@jest/test-sequencer": "workspace:*", "@jest/types": "workspace:*", "babel-jest": "workspace:*", diff --git a/packages/jest-config/src/__tests__/normalize.test.ts b/packages/jest-config/src/__tests__/normalize.test.ts index 101db1ba51c0..9e9cb86d8d5a 100644 --- a/packages/jest-config/src/__tests__/normalize.test.ts +++ b/packages/jest-config/src/__tests__/normalize.test.ts @@ -1601,7 +1601,7 @@ describe('testPathPatterns', () => { it('defaults to empty', async () => { const {options} = await normalize(initialOptions, {} as Config.Argv); - expect(options.testPathPatterns).toEqual([]); + expect(options.testPathPatterns.patterns).toEqual([]); }); const cliOptions = [ @@ -1614,14 +1614,14 @@ describe('testPathPatterns', () => { const argv = {[opt.property]: ['a/b']} as Config.Argv; const {options} = await normalize(initialOptions, argv); - expect(options.testPathPatterns).toEqual(['a/b']); + expect(options.testPathPatterns.patterns).toEqual(['a/b']); }); it('ignores invalid regular expressions and logs a warning', async () => { const argv = {[opt.property]: ['a(']} as Config.Argv; const {options} = await normalize(initialOptions, argv); - expect(options.testPathPatterns).toEqual([]); + expect(options.testPathPatterns.patterns).toEqual([]); expect(jest.mocked(console.log).mock.calls[0][0]).toMatchSnapshot(); }); @@ -1629,7 +1629,7 @@ describe('testPathPatterns', () => { const argv = {[opt.property]: ['a/b', 'c/d']} as Config.Argv; const {options} = await normalize(initialOptions, argv); - expect(options.testPathPatterns).toEqual(['a/b', 'c/d']); + expect(options.testPathPatterns.patterns).toEqual(['a/b', 'c/d']); }); }); } @@ -1638,7 +1638,7 @@ describe('testPathPatterns', () => { const argv = {_: [1]} as Config.Argv; const {options} = await normalize(initialOptions, argv); - expect(options.testPathPatterns).toEqual(['1']); + expect(options.testPathPatterns.patterns).toEqual(['1']); }); it('joins multiple --testPathPatterns and ', async () => { @@ -1646,7 +1646,7 @@ describe('testPathPatterns', () => { _: ['a', 'b'], testPathPatterns: ['c', 'd'], } as Config.Argv); - expect(options.testPathPatterns).toEqual(['a', 'b', 'c', 'd']); + expect(options.testPathPatterns.patterns).toEqual(['a', 'b', 'c', 'd']); }); it('gives precedence to --all', async () => { diff --git a/packages/jest-config/src/normalize.ts b/packages/jest-config/src/normalize.ts index b317a0e86691..e8b3cc9d726a 100644 --- a/packages/jest-config/src/normalize.ts +++ b/packages/jest-config/src/normalize.ts @@ -13,6 +13,7 @@ import merge = require('deepmerge'); import {glob} from 'glob'; import {statSync} from 'graceful-fs'; import micromatch = require('micromatch'); +import {TestPathPatterns} from '@jest/pattern'; import type {Config} from '@jest/types'; import {replacePathSepForRegex} from 'jest-regex-util'; import Resolver, { @@ -22,7 +23,6 @@ import Resolver, { resolveWatchPlugin, } from 'jest-resolve'; import { - TestPathPatterns, clearLine, replacePathSepForGlob, requireOrImportModule, @@ -393,10 +393,7 @@ const normalizeReporters = ({ }); }; -const buildTestPathPatterns = ( - argv: Config.Argv, - rootDir: string, -): TestPathPatterns => { +const buildTestPathPatterns = (argv: Config.Argv): TestPathPatterns => { const patterns = []; if (argv._) { @@ -406,12 +403,9 @@ const buildTestPathPatterns = ( patterns.push(...argv.testPathPatterns); } - const config = {rootDir}; - const testPathPatterns = new TestPathPatterns(patterns, config); + const testPathPatterns = new TestPathPatterns(patterns); - try { - testPathPatterns.validate(); - } catch { + if (!testPathPatterns.isValid()) { clearLine(process.stdout); // eslint-disable-next-line no-console @@ -422,7 +416,7 @@ const buildTestPathPatterns = ( ), ); - return new TestPathPatterns([], config); + return new TestPathPatterns([]); } return testPathPatterns; @@ -1012,8 +1006,8 @@ export default async function normalize( } newOptions.nonFlagArgs = argv._?.map(arg => `${arg}`); - const testPathPatterns = buildTestPathPatterns(argv, options.rootDir); - newOptions.testPathPatterns = testPathPatterns.patterns; + const testPathPatterns = buildTestPathPatterns(argv); + newOptions.testPathPatterns = testPathPatterns; newOptions.json = !!argv.json; newOptions.testFailureExitCode = Number.parseInt( diff --git a/packages/jest-config/tsconfig.json b/packages/jest-config/tsconfig.json index 6ed9554e6476..2e008d38e257 100644 --- a/packages/jest-config/tsconfig.json +++ b/packages/jest-config/tsconfig.json @@ -12,6 +12,7 @@ "references": [ {"path": "../jest-environment-node"}, {"path": "../jest-get-type"}, + {"path": "../jest-pattern"}, {"path": "../jest-regex-util"}, {"path": "../jest-resolve"}, {"path": "../jest-runner"}, diff --git a/packages/jest-core/package.json b/packages/jest-core/package.json index b288250ef0dd..cd9f2abaa4ef 100644 --- a/packages/jest-core/package.json +++ b/packages/jest-core/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@jest/console": "workspace:*", + "@jest/pattern": "workspace:*", "@jest/reporters": "workspace:*", "@jest/test-result": "workspace:*", "@jest/transform": "workspace:*", diff --git a/packages/jest-core/src/SearchSource.ts b/packages/jest-core/src/SearchSource.ts index db8230b16f9d..772feeff6b08 100644 --- a/packages/jest-core/src/SearchSource.ts +++ b/packages/jest-core/src/SearchSource.ts @@ -8,6 +8,7 @@ import * as os from 'os'; import * as path from 'path'; import micromatch = require('micromatch'); +import type {TestPathPatternsExecutor} from '@jest/pattern'; import type {Test, TestContext} from '@jest/test-result'; import type {Config} from '@jest/types'; import type {ChangedFiles} from 'jest-changed-files'; @@ -15,7 +16,7 @@ import {replaceRootDirInPath} from 'jest-config'; import {escapePathForRegex} from 'jest-regex-util'; import {DependencyResolver} from 'jest-resolve-dependencies'; import {buildSnapshotResolver} from 'jest-snapshot'; -import {TestPathPatterns, globsToMatcher} from 'jest-util'; +import {globsToMatcher} from 'jest-util'; import type {Filter, Stats, TestPathCases} from './types'; export type SearchResult = { @@ -114,7 +115,7 @@ export default class SearchSource { private _filterTestPathsWithStats( allPaths: Array, - testPathPatterns: TestPathPatterns, + testPathPatternsExecutor: TestPathPatternsExecutor, ): SearchResult { const data: { stats: Stats; @@ -132,9 +133,9 @@ export default class SearchSource { }; const testCases = [...this._testPathCases]; // clone - if (testPathPatterns.isSet()) { + if (testPathPatternsExecutor.isSet()) { testCases.push({ - isMatch: (path: string) => testPathPatterns.isMatch(path), + isMatch: (path: string) => testPathPatternsExecutor.isMatch(path), stat: 'testPathPatterns', }); data.stats.testPathPatterns = 0; @@ -155,10 +156,12 @@ export default class SearchSource { return data; } - private _getAllTestPaths(testPathPatterns: TestPathPatterns): SearchResult { + private _getAllTestPaths( + testPathPatternsExecutor: TestPathPatternsExecutor, + ): SearchResult { return this._filterTestPathsWithStats( toTests(this._context, this._context.hasteFS.getAllFiles()), - testPathPatterns, + testPathPatternsExecutor, ); } @@ -166,8 +169,10 @@ export default class SearchSource { return this._testPathCases.every(testCase => testCase.isMatch(path)); } - findMatchingTests(testPathPatterns: TestPathPatterns): SearchResult { - return this._getAllTestPaths(testPathPatterns); + findMatchingTests( + testPathPatternsExecutor: TestPathPatternsExecutor, + ): SearchResult { + return this._getAllTestPaths(testPathPatternsExecutor); } async findRelatedTests( @@ -264,6 +269,7 @@ export default class SearchSource { private async _getTestPaths( globalConfig: Config.GlobalConfig, + projectConfig: Config.ProjectConfig, changedFiles?: ChangedFiles, ): Promise { if (globalConfig.onlyChanged) { @@ -292,7 +298,9 @@ export default class SearchSource { ); } else { return this.findMatchingTests( - TestPathPatterns.fromGlobalConfig(globalConfig), + globalConfig.testPathPatterns.toExecutor({ + rootDir: projectConfig.rootDir, + }), ); } } @@ -321,10 +329,15 @@ export default class SearchSource { async getTestPaths( globalConfig: Config.GlobalConfig, + projectConfig: Config.ProjectConfig, changedFiles?: ChangedFiles, filter?: Filter, ): Promise { - const searchResult = await this._getTestPaths(globalConfig, changedFiles); + const searchResult = await this._getTestPaths( + globalConfig, + projectConfig, + changedFiles, + ); const filterPath = globalConfig.filter; diff --git a/packages/jest-core/src/__tests__/SearchSource.test.ts b/packages/jest-core/src/__tests__/SearchSource.test.ts index e7fecf9bdc96..4752eeb1febf 100644 --- a/packages/jest-core/src/__tests__/SearchSource.test.ts +++ b/packages/jest-core/src/__tests__/SearchSource.test.ts @@ -7,6 +7,7 @@ */ import * as path from 'path'; +import {TestPathPatterns} from '@jest/pattern'; import type {Test} from '@jest/test-result'; import type {Config} from '@jest/types'; import {normalize} from 'jest-config'; @@ -111,12 +112,14 @@ describe('SearchSource', () => { filter?: Filter, ) => { const {searchSource, config} = await initSearchSource(initialOptions); + const allConfig = { + ...config, + ...initialOptions, + testPathPatterns: new TestPathPatterns([]), + }; const {tests: paths} = await searchSource.getTestPaths( - { - ...config, - ...initialOptions, - testPathPatterns: [], - }, + allConfig, + allConfig, null, filter, ); diff --git a/packages/jest-core/src/__tests__/__snapshots__/watchFilenamePatternMode.test.js.snap b/packages/jest-core/src/__tests__/__snapshots__/watchFilenamePatternMode.test.js.snap index 724b415277b6..085808de5e8b 100644 --- a/packages/jest-core/src/__tests__/__snapshots__/watchFilenamePatternMode.test.js.snap +++ b/packages/jest-core/src/__tests__/__snapshots__/watchFilenamePatternMode.test.js.snap @@ -93,9 +93,12 @@ Object { "onlyChanged": false, "passWithNoTests": true, "rootDir": "", - "testPathPatterns": Array [ - "p.*3", - ], + "testPathPatterns": Object { + "patterns": Array [ + "p.*3", + ], + "type": "TestPathPatterns", + }, "watch": true, "watchAll": false, } diff --git a/packages/jest-core/src/__tests__/getNoTestsFoundMessage.test.ts b/packages/jest-core/src/__tests__/getNoTestsFoundMessage.test.ts index 19fd684dea0a..559f3dbfe02c 100644 --- a/packages/jest-core/src/__tests__/getNoTestsFoundMessage.test.ts +++ b/packages/jest-core/src/__tests__/getNoTestsFoundMessage.test.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {TestPathPatterns} from '@jest/pattern'; import {makeGlobalConfig} from '@jest/test-utils'; import type {Config} from '@jest/types'; import getNoTestsFoundMessage from '../getNoTestsFoundMessage'; @@ -18,7 +19,7 @@ describe('getNoTestsFoundMessage', () => { function createGlobalConfig(options?: Partial) { return makeGlobalConfig({ rootDir: '/root/dir', - testPathPatterns: ['/path/pattern'], + testPathPatterns: new TestPathPatterns(['/path/pattern']), ...options, }); } diff --git a/packages/jest-core/src/__tests__/runJest.test.js b/packages/jest-core/src/__tests__/runJest.test.js index 559a8bdb8baf..f2266263ad05 100644 --- a/packages/jest-core/src/__tests__/runJest.test.js +++ b/packages/jest-core/src/__tests__/runJest.test.js @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {TestPathPatterns} from '@jest/pattern'; import runJest from '../runJest'; jest.mock('@jest/console'); @@ -23,7 +24,7 @@ describe('runJest', () => { contexts: [], globalConfig: { rootDir: '', - testPathPatterns: [], + testPathPatterns: new TestPathPatterns([]), testSequencer: require.resolve('@jest/test-sequencer'), watch: true, }, diff --git a/packages/jest-core/src/__tests__/watch.test.js b/packages/jest-core/src/__tests__/watch.test.js index afef7be7cbb4..2ed12541f83d 100644 --- a/packages/jest-core/src/__tests__/watch.test.js +++ b/packages/jest-core/src/__tests__/watch.test.js @@ -7,6 +7,7 @@ */ import chalk from 'chalk'; +import {TestPathPatterns} from '@jest/pattern'; // eslint-disable-next-line import/order import {JestHook, KEYS, TestWatcher} from 'jest-watcher'; @@ -142,7 +143,7 @@ describe('Watch mode flows', () => { pipe = {write: jest.fn()}; globalConfig = { rootDir: '', - testPathPatterns: [], + testPathPatterns: new TestPathPatterns([]), watch: true, }; hasteMapInstances = [{on: () => {}}]; @@ -156,7 +157,7 @@ describe('Watch mode flows', () => { }); it('Correctly passing test path pattern', async () => { - globalConfig.testPathPatterns = ['test-*']; + globalConfig.testPathPatterns = new TestPathPatterns(['test-*']); await watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); @@ -690,6 +691,14 @@ describe('Watch mode flows', () => { ok = ok === '✔︎'; const pluginPath = `${__dirname}/__fixtures__/plugin_path_config_updater_${option}`; + const newVal = (() => { + if (option === 'testPathPatterns') { + return new TestPathPatterns(['a/b', 'c']); + } + + return '__JUST_TRYING__'; + })(); + jest.doMock( pluginPath, () => @@ -699,7 +708,7 @@ describe('Watch mode flows', () => { } run(globalConfig, updateConfigAndRun) { - updateConfigAndRun({[option]: '__JUST_TRYING__'}); + updateConfigAndRun({[option]: newVal}); return Promise.resolve(); } }, @@ -726,7 +735,7 @@ describe('Watch mode flows', () => { if (!ok) { expector = expector.not; } - expector.toHaveProperty(option, '__JUST_TRYING__'); + expector.toHaveProperty(option, newVal); }, ); @@ -904,7 +913,7 @@ describe('Watch mode flows', () => { await nextTick(); expect(runJestMock.mock.calls[0][0].globalConfig).toMatchObject({ - testPathPatterns: ['file'], + testPathPatterns: {patterns: ['file']}, watch: true, watchAll: false, }); @@ -928,7 +937,7 @@ describe('Watch mode flows', () => { expect(runJestMock.mock.calls[1][0].globalConfig).toMatchObject({ testNamePattern: 'test', - testPathPatterns: ['file'], + testPathPatterns: {patterns: ['file']}, watch: true, watchAll: false, }); diff --git a/packages/jest-core/src/__tests__/watchFilenamePatternMode.test.js b/packages/jest-core/src/__tests__/watchFilenamePatternMode.test.js index 05c51cd14c64..0a775ffad1bd 100644 --- a/packages/jest-core/src/__tests__/watchFilenamePatternMode.test.js +++ b/packages/jest-core/src/__tests__/watchFilenamePatternMode.test.js @@ -7,6 +7,7 @@ */ import chalk from 'chalk'; +import {TestPathPatterns} from '@jest/pattern'; // eslint-disable-next-line import/order import {KEYS} from 'jest-watcher'; @@ -72,7 +73,7 @@ const nextTick = () => new Promise(resolve => process.nextTick(resolve)); const globalConfig = { rootDir: '', - testPathPatterns: [], + testPathPatterns: new TestPathPatterns([]), watch: true, }; diff --git a/packages/jest-core/src/__tests__/watchTestNamePatternMode.test.js b/packages/jest-core/src/__tests__/watchTestNamePatternMode.test.js index 0be6674c2cd7..265a97dc1f71 100644 --- a/packages/jest-core/src/__tests__/watchTestNamePatternMode.test.js +++ b/packages/jest-core/src/__tests__/watchTestNamePatternMode.test.js @@ -7,6 +7,7 @@ */ import chalk from 'chalk'; +import {TestPathPatterns} from '@jest/pattern'; // eslint-disable-next-line import/order import {KEYS} from 'jest-watcher'; @@ -84,7 +85,7 @@ const watch = require('../watch').default; const globalConfig = { rootDir: '', - testPathPatterns: [], + testPathPatterns: new TestPathPatterns([]), watch: true, }; diff --git a/packages/jest-core/src/getNoTestFound.ts b/packages/jest-core/src/getNoTestFound.ts index cbfe05cbf6e9..7d6ae6e5e7bb 100644 --- a/packages/jest-core/src/getNoTestFound.ts +++ b/packages/jest-core/src/getNoTestFound.ts @@ -7,7 +7,7 @@ import chalk = require('chalk'); import type {Config} from '@jest/types'; -import {TestPathPatterns, pluralize} from 'jest-util'; +import {pluralize} from 'jest-util'; import type {TestRunData} from './types'; export default function getNoTestFound( @@ -26,9 +26,8 @@ export default function getNoTestFound( .map(p => `"${p}"`) .join(', ')}`; } else { - const testPathPatterns = TestPathPatterns.fromGlobalConfig(globalConfig); dataMessage = `Pattern: ${chalk.yellow( - testPathPatterns.toPretty(), + globalConfig.testPathPatterns.toPretty(), )} - 0 matches`; } diff --git a/packages/jest-core/src/getNoTestFoundVerbose.ts b/packages/jest-core/src/getNoTestFoundVerbose.ts index 14dc2e2af263..bd5f2ab355b4 100644 --- a/packages/jest-core/src/getNoTestFoundVerbose.ts +++ b/packages/jest-core/src/getNoTestFoundVerbose.ts @@ -7,7 +7,7 @@ import chalk = require('chalk'); import type {Config} from '@jest/types'; -import {TestPathPatterns, pluralize} from 'jest-util'; +import {pluralize} from 'jest-util'; import type {Stats, TestRunData} from './types'; export default function getNoTestFoundVerbose( @@ -56,9 +56,8 @@ export default function getNoTestFoundVerbose( .map(p => `"${p}"`) .join(', ')}`; } else { - const testPathPatterns = TestPathPatterns.fromGlobalConfig(globalConfig); dataMessage = `Pattern: ${chalk.yellow( - testPathPatterns.toPretty(), + globalConfig.testPathPatterns.toPretty(), )} - 0 matches`; } diff --git a/packages/jest-core/src/lib/activeFiltersMessage.ts b/packages/jest-core/src/lib/activeFiltersMessage.ts index 170760456cda..c1c9230c6544 100644 --- a/packages/jest-core/src/lib/activeFiltersMessage.ts +++ b/packages/jest-core/src/lib/activeFiltersMessage.ts @@ -7,11 +7,11 @@ import chalk = require('chalk'); import type {Config} from '@jest/types'; -import {TestPathPatterns, isNonNullable} from 'jest-util'; +import {isNonNullable} from 'jest-util'; const activeFilters = (globalConfig: Config.GlobalConfig): string => { const {testNamePattern} = globalConfig; - const testPathPatterns = TestPathPatterns.fromGlobalConfig(globalConfig); + const testPathPatterns = globalConfig.testPathPatterns; if (testNamePattern || testPathPatterns.isSet()) { const filters = [ testPathPatterns.isSet() diff --git a/packages/jest-core/src/lib/logDebugMessages.ts b/packages/jest-core/src/lib/logDebugMessages.ts index 0082607bfd56..a3220bf15965 100644 --- a/packages/jest-core/src/lib/logDebugMessages.ts +++ b/packages/jest-core/src/lib/logDebugMessages.ts @@ -17,7 +17,10 @@ export default function logDebugMessages( ): void { const output = { configs, - globalConfig, + globalConfig: { + ...globalConfig, + testPathPatterns: globalConfig.testPathPatterns.patterns, + }, version: VERSION, }; outputStream.write(`${JSON.stringify(output, null, ' ')}\n`); diff --git a/packages/jest-core/src/lib/updateGlobalConfig.ts b/packages/jest-core/src/lib/updateGlobalConfig.ts index a05dc5ae9fcd..115e53142cfd 100644 --- a/packages/jest-core/src/lib/updateGlobalConfig.ts +++ b/packages/jest-core/src/lib/updateGlobalConfig.ts @@ -5,8 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +import {TestPathPatterns} from '@jest/pattern'; import type {Config} from '@jest/types'; -import {TestPathPatterns} from 'jest-util'; import type {AllowedConfigOptions} from 'jest-watcher'; type ExtraConfigOptions = Partial< @@ -32,13 +32,13 @@ export default function updateGlobalConfig( } if (options.testPathPatterns !== undefined) { - newConfig.testPathPatterns = options.testPathPatterns; + newConfig.testPathPatterns = new TestPathPatterns(options.testPathPatterns); } newConfig.onlyChanged = !newConfig.watchAll && !newConfig.testNamePattern && - !TestPathPatterns.fromGlobalConfig(newConfig).isSet(); + !newConfig.testPathPatterns.isSet(); if (typeof options.bail === 'boolean') { newConfig.bail = options.bail ? 1 : 0; diff --git a/packages/jest-core/src/plugins/TestPathPattern.ts b/packages/jest-core/src/plugins/TestPathPattern.ts index 9163e20d29b9..d7704e273d8b 100644 --- a/packages/jest-core/src/plugins/TestPathPattern.ts +++ b/packages/jest-core/src/plugins/TestPathPattern.ts @@ -49,7 +49,10 @@ class TestPathPatternPlugin extends BaseWatchPlugin { testPathPatternPrompt.run( (value: string) => { - updateConfigAndRun({mode: 'watch', testPathPatterns: [value]}); + updateConfigAndRun({ + mode: 'watch', + testPathPatterns: [value], + }); resolve(); }, reject, diff --git a/packages/jest-core/src/runJest.ts b/packages/jest-core/src/runJest.ts index fb753aa7a06e..28d97d83c222 100644 --- a/packages/jest-core/src/runJest.ts +++ b/packages/jest-core/src/runJest.ts @@ -38,13 +38,19 @@ import type {Filter, TestRunData} from './types'; const getTestPaths = async ( globalConfig: Config.GlobalConfig, + projectConfig: Config.ProjectConfig, source: SearchSource, outputStream: WriteStream, changedFiles: ChangedFiles | undefined, jestHooks: JestHookEmitter, filter?: Filter, ) => { - const data = await source.getTestPaths(globalConfig, changedFiles, filter); + const data = await source.getTestPaths( + globalConfig, + projectConfig, + changedFiles, + filter, + ); if (data.tests.length === 0 && globalConfig.onlyChanged && data.noSCM) { new CustomConsole(outputStream, outputStream).log( @@ -188,6 +194,7 @@ export default async function runJest({ const searchSource = searchSources[index]; const matches = await getTestPaths( globalConfig, + context.config, searchSource, outputStream, changedFilesPromise && (await changedFilesPromise), diff --git a/packages/jest-core/src/watch.ts b/packages/jest-core/src/watch.ts index 42f798f6fab2..a8cc3e1ff769 100644 --- a/packages/jest-core/src/watch.ts +++ b/packages/jest-core/src/watch.ts @@ -11,12 +11,12 @@ import ansiEscapes = require('ansi-escapes'); import chalk = require('chalk'); import exit = require('exit'); import slash = require('slash'); +import {TestPathPatterns} from '@jest/pattern'; import type {TestContext} from '@jest/test-result'; import type {Config} from '@jest/types'; import type {IHasteMap as HasteMap} from 'jest-haste-map'; import {formatExecError} from 'jest-message-util'; import { - TestPathPatterns, isInteractive, preRunMessage, requireOrImportModule, @@ -230,11 +230,14 @@ export default async function watch( const emitFileChange = () => { if (hooks.isUsed('onFileChange')) { - const testPathPatterns = new TestPathPatterns([], globalConfig); const projects = searchSources.map(({context, searchSource}) => ({ config: context.config, testPaths: searchSource - .findMatchingTests(testPathPatterns) + .findMatchingTests( + new TestPathPatterns([]).toExecutor({ + rootDir: context.config.rootDir, + }), + ) .tests.map(t => t.path), })); hooks.getEmitter().onFileChange({projects}); @@ -533,7 +536,7 @@ const usage = ( watchPlugins: Array, delimiter = '\n', ) => { - const testPathPatterns = TestPathPatterns.fromGlobalConfig(globalConfig); + const testPathPatterns = globalConfig.testPathPatterns; const messages = [ activeFilters(globalConfig), diff --git a/packages/jest-core/tsconfig.json b/packages/jest-core/tsconfig.json index ac309da620f3..716fbc207277 100644 --- a/packages/jest-core/tsconfig.json +++ b/packages/jest-core/tsconfig.json @@ -12,6 +12,7 @@ {"path": "../jest-console"}, {"path": "../jest-haste-map"}, {"path": "../jest-message-util"}, + {"path": "../jest-pattern"}, {"path": "../jest-regex-util"}, {"path": "../jest-reporters"}, {"path": "../jest-resolve"}, diff --git a/packages/jest-pattern/README.md b/packages/jest-pattern/README.md new file mode 100644 index 000000000000..4920bb6cdf87 --- /dev/null +++ b/packages/jest-pattern/README.md @@ -0,0 +1,3 @@ +# @jest/pattern + +`@jest/pattern` is a helper library for the jest library that implements the logic for parsing and matching patterns. diff --git a/packages/jest-pattern/package.json b/packages/jest-pattern/package.json new file mode 100644 index 000000000000..31de45c8f972 --- /dev/null +++ b/packages/jest-pattern/package.json @@ -0,0 +1,31 @@ +{ + "name": "@jest/pattern", + "version": "30.0.0-alpha.4", + "repository": { + "type": "git", + "url": "https://github.com/jestjs/jest.git", + "directory": "packages/jest-pattern" + }, + "license": "MIT", + "main": "./build/index.js", + "types": "./build/index.d.ts", + "exports": { + ".": { + "types": "./build/index.d.ts", + "require": "./build/index.js", + "import": "./build/index.mjs", + "default": "./build/index.js" + }, + "./package.json": "./package.json" + }, + "dependencies": { + "@types/node": "*", + "jest-regex-util": "workspace:*" + }, + "engines": { + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/jest-util/src/TestPathPatterns.ts b/packages/jest-pattern/src/TestPathPatterns.ts similarity index 52% rename from packages/jest-util/src/TestPathPatterns.ts rename to packages/jest-pattern/src/TestPathPatterns.ts index 558f4a68e99b..26b0f4132117 100644 --- a/packages/jest-util/src/TestPathPatterns.ts +++ b/packages/jest-pattern/src/TestPathPatterns.ts @@ -5,42 +5,83 @@ * LICENSE file in the root directory of this source tree. */ -import type {Config} from '@jest/types'; import {escapePathForRegex, replacePathSepForRegex} from 'jest-regex-util'; -type PatternsConfig = { +export class TestPathPatterns { + constructor(readonly patterns: Array) {} + + /** + * Return true if there are any patterns. + */ + isSet(): boolean { + return this.patterns.length > 0; + } + + /** + * Return true if the patterns are valid. + */ + isValid(): boolean { + return this.toExecutor({ + // isValid() doesn't require rootDir to be accurate, so just + // specify a dummy rootDir here + rootDir: '/', + }).isValid(); + } + + /** + * Return a human-friendly version of the pattern regex. + */ + toPretty(): string { + return this.patterns.join('|'); + } + + /** + * Return a TestPathPatternsExecutor that can execute the patterns. + */ + toExecutor( + options: TestPathPatternsExecutorOptions, + ): TestPathPatternsExecutor { + return new TestPathPatternsExecutor(this, options); + } + + /** For jest serializers */ + toJSON(): any { + return { + patterns: this.patterns, + type: 'TestPathPatterns', + }; + } +} + +export type TestPathPatternsExecutorOptions = { rootDir: string; }; -export default class TestPathPatterns { +export class TestPathPatternsExecutor { private _regexString: string | null = null; constructor( - readonly patterns: Array, - private readonly config: PatternsConfig, + readonly patterns: TestPathPatterns, + private readonly options: TestPathPatternsExecutorOptions, ) {} - static fromGlobalConfig(globalConfig: Config.GlobalConfig): TestPathPatterns { - return new TestPathPatterns(globalConfig.testPathPatterns, globalConfig); - } - private get regexString(): string { if (this._regexString !== null) { return this._regexString; } - const rootDir = this.config.rootDir.replace(/\/*$/, '/'); + const rootDir = this.options.rootDir.replace(/\/*$/, '/'); const rootDirRegex = escapePathForRegex(rootDir); - const regexString = this.patterns + const regexString = this.patterns.patterns .map(p => { // absolute paths passed on command line should stay same - if (/^\//.test(p)) { + if (p.startsWith('/')) { return p; } // explicit relative paths should resolve against rootDir - if (/^\.\//.test(p)) { + if (p.startsWith('./')) { return p.replace(/^\.\//, rootDirRegex); } @@ -62,14 +103,19 @@ export default class TestPathPatterns { * Return true if there are any patterns. */ isSet(): boolean { - return this.patterns.length > 0; + return this.patterns.isSet(); } /** - * Throw an error if the patterns don't form a valid regex. + * Return true if the patterns are valid. */ - validate(): void { - this.toRegex(); + isValid(): boolean { + try { + this.toRegex(); + return true; + } catch { + return false; + } } /** @@ -85,6 +131,6 @@ export default class TestPathPatterns { * Return a human-friendly version of the pattern regex. */ toPretty(): string { - return this.patterns.join('|'); + return this.patterns.toPretty(); } } diff --git a/packages/jest-util/src/__tests__/TestPathPatterns.test.ts b/packages/jest-pattern/src/__tests__/TestPathPatterns.test.ts similarity index 62% rename from packages/jest-util/src/__tests__/TestPathPatterns.test.ts rename to packages/jest-pattern/src/__tests__/TestPathPatterns.test.ts index 39c8f272efa1..ced625fb17ed 100644 --- a/packages/jest-util/src/__tests__/TestPathPatterns.test.ts +++ b/packages/jest-pattern/src/__tests__/TestPathPatterns.test.ts @@ -6,16 +6,20 @@ */ import type * as path from 'path'; -import TestPathPatterns from '../TestPathPatterns'; +import { + TestPathPatterns, + TestPathPatternsExecutor, + type TestPathPatternsExecutorOptions, +} from '../TestPathPatterns'; -const mockSep = jest.fn(); +const mockSep: jest.Mock<() => string> = jest.fn(); jest.mock('path', () => { return { - ...(jest.requireActual('path') as typeof path), + ...jest.requireActual('path'), get sep() { return mockSep() || '/'; }, - }; + } as typeof path; }); beforeEach(() => { jest.resetAllMocks(); @@ -23,88 +27,117 @@ beforeEach(() => { const config = {rootDir: ''}; -describe('TestPathPatterns', () => { +interface TestPathPatternsLike { + isSet(): boolean; + isValid(): boolean; + toPretty(): string; +} + +const testPathPatternsLikeTests = ( + makePatterns: ( + patterns: Array, + options: TestPathPatternsExecutorOptions, + ) => TestPathPatternsLike, +) => { describe('isSet', () => { it('returns false if no patterns specified', () => { - const testPathPatterns = new TestPathPatterns([], config); + const testPathPatterns = makePatterns([], config); expect(testPathPatterns.isSet()).toBe(false); }); it('returns true if patterns specified', () => { - const testPathPatterns = new TestPathPatterns(['a'], config); + const testPathPatterns = makePatterns(['a'], config); expect(testPathPatterns.isSet()).toBe(true); }); }); - describe('validate', () => { + describe('isValid', () => { it('succeeds for empty patterns', () => { - const testPathPatterns = new TestPathPatterns([], config); - expect(() => testPathPatterns.validate()).not.toThrow(); + const testPathPatterns = makePatterns([], config); + expect(testPathPatterns.isValid()).toBe(true); }); it('succeeds for valid patterns', () => { - const testPathPatterns = new TestPathPatterns(['abc+', 'z.*'], config); - expect(() => testPathPatterns.validate()).not.toThrow(); + const testPathPatterns = makePatterns(['abc+', 'z.*'], config); + expect(testPathPatterns.isValid()).toBe(true); }); it('fails for at least one invalid pattern', () => { - const testPathPatterns = new TestPathPatterns( - ['abc+', '(', 'z.*'], - config, - ); - expect(() => testPathPatterns.validate()).toThrow( - 'Invalid regular expression', - ); + const testPathPatterns = makePatterns(['abc+', '(', 'z.*'], config); + expect(testPathPatterns.isValid()).toBe(false); }); }); + describe('toPretty', () => { + it('renders a human-readable string', () => { + const testPathPatterns = makePatterns(['a/b', 'c/d'], config); + expect(testPathPatterns.toPretty()).toMatchSnapshot(); + }); + }); +}; + +describe('TestPathPatterns', () => { + testPathPatternsLikeTests( + (patterns: Array, _: TestPathPatternsExecutorOptions) => + new TestPathPatterns(patterns), + ); +}); + +describe('TestPathPatternsExecutor', () => { + const makeExecutor = ( + patterns: Array, + options: TestPathPatternsExecutorOptions, + ) => new TestPathPatternsExecutor(new TestPathPatterns(patterns), options); + + testPathPatternsLikeTests(makeExecutor); + describe('isMatch', () => { it('returns true with no patterns', () => { - const testPathPatterns = new TestPathPatterns([], config); + const testPathPatterns = makeExecutor([], config); expect(testPathPatterns.isMatch('/a/b')).toBe(true); }); it('returns true for same path', () => { - const testPathPatterns = new TestPathPatterns(['/a/b'], config); + const testPathPatterns = makeExecutor(['/a/b'], config); expect(testPathPatterns.isMatch('/a/b')).toBe(true); }); it('returns true for same path with case insensitive', () => { - const testPathPatternsUpper = new TestPathPatterns(['/A/B'], config); + const testPathPatternsUpper = makeExecutor(['/A/B'], config); expect(testPathPatternsUpper.isMatch('/a/b')).toBe(true); expect(testPathPatternsUpper.isMatch('/A/B')).toBe(true); - const testPathPatternsLower = new TestPathPatterns(['/a/b'], config); + const testPathPatternsLower = makeExecutor(['/a/b'], config); expect(testPathPatternsLower.isMatch('/A/B')).toBe(true); expect(testPathPatternsLower.isMatch('/a/b')).toBe(true); }); it('returns true for contained path', () => { - const testPathPatterns = new TestPathPatterns(['b/c'], config); + const testPathPatterns = makeExecutor(['b/c'], config); expect(testPathPatterns.isMatch('/a/b/c/d')).toBe(true); }); it('returns true for explicit relative path', () => { - const testPathPatterns = new TestPathPatterns(['./b/c'], { + const testPathPatterns = makeExecutor(['./b/c'], { rootDir: '/a', }); expect(testPathPatterns.isMatch('/a/b/c')).toBe(true); }); it('returns true for partial file match', () => { - const testPathPatterns = new TestPathPatterns(['aaa'], config); + const testPathPatterns = makeExecutor(['aaa'], config); expect(testPathPatterns.isMatch('/foo/..aaa..')).toBe(true); expect(testPathPatterns.isMatch('/foo/..aaa')).toBe(true); expect(testPathPatterns.isMatch('/foo/aaa..')).toBe(true); }); it('returns true for path suffix', () => { - const testPathPatterns = new TestPathPatterns(['c/d'], config); + const testPathPatterns = makeExecutor(['c/d'], config); expect(testPathPatterns.isMatch('/a/b/c/d')).toBe(true); }); it('returns true if regex matches', () => { - const testPathPatterns = new TestPathPatterns(['ab*c?'], config); + const testPathPatterns = makeExecutor(['ab*c?'], config); expect(testPathPatterns.isMatch('/foo/a')).toBe(true); expect(testPathPatterns.isMatch('/foo/ab')).toBe(true); @@ -117,7 +150,7 @@ describe('TestPathPatterns', () => { }); it('returns true only if matches relative path', () => { - const testPathPatterns = new TestPathPatterns(['home'], { + const testPathPatterns = makeExecutor(['home'], { rootDir: '/home/myuser/', }); expect(testPathPatterns.isMatch('/home/myuser/LoginPage.js')).toBe(false); @@ -125,14 +158,14 @@ describe('TestPathPatterns', () => { }); it('matches absolute paths regardless of rootDir', () => { - const testPathPatterns = new TestPathPatterns(['/a/b'], { + const testPathPatterns = makeExecutor(['/a/b'], { rootDir: '/foo/bar', }); expect(testPathPatterns.isMatch('/a/b')).toBe(true); }); it('returns true if match any paths', () => { - const testPathPatterns = new TestPathPatterns(['a/b', 'c/d'], config); + const testPathPatterns = makeExecutor(['a/b', 'c/d'], config); expect(testPathPatterns.isMatch('/foo/a/b')).toBe(true); expect(testPathPatterns.isMatch('/foo/c/d')).toBe(true); @@ -143,21 +176,14 @@ describe('TestPathPatterns', () => { it('does not normalize Windows paths on POSIX', () => { mockSep.mockReturnValue('/'); - const testPathPatterns = new TestPathPatterns(['a\\z', 'a\\\\z'], config); + const testPathPatterns = makeExecutor(['a\\z', 'a\\\\z'], config); expect(testPathPatterns.isMatch('/foo/a/z')).toBe(false); }); it('normalizes paths for Windows', () => { mockSep.mockReturnValue('\\'); - const testPathPatterns = new TestPathPatterns(['a/b'], config); + const testPathPatterns = makeExecutor(['a/b'], config); expect(testPathPatterns.isMatch('\\foo\\a\\b')).toBe(true); }); }); - - describe('toPretty', () => { - it('renders a human-readable string', () => { - const testPathPatterns = new TestPathPatterns(['a/b', 'c/d'], config); - expect(testPathPatterns.toPretty()).toMatchSnapshot(); - }); - }); }); diff --git a/packages/jest-util/src/__tests__/__snapshots__/TestPathPatterns.test.ts.snap b/packages/jest-pattern/src/__tests__/__snapshots__/TestPathPatterns.test.ts.snap similarity index 57% rename from packages/jest-util/src/__tests__/__snapshots__/TestPathPatterns.test.ts.snap rename to packages/jest-pattern/src/__tests__/__snapshots__/TestPathPatterns.test.ts.snap index 5b97f3ca2f59..14a6ec2592d0 100644 --- a/packages/jest-util/src/__tests__/__snapshots__/TestPathPatterns.test.ts.snap +++ b/packages/jest-pattern/src/__tests__/__snapshots__/TestPathPatterns.test.ts.snap @@ -1,3 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TestPathPatterns toPretty renders a human-readable string 1`] = `"a/b|c/d"`; + +exports[`TestPathPatternsExecutor toPretty renders a human-readable string 1`] = `"a/b|c/d"`; diff --git a/packages/jest-pattern/src/index.ts b/packages/jest-pattern/src/index.ts new file mode 100644 index 000000000000..3880956e40eb --- /dev/null +++ b/packages/jest-pattern/src/index.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export { + TestPathPatterns, + TestPathPatternsExecutor, + type TestPathPatternsExecutorOptions, +} from './TestPathPatterns'; diff --git a/packages/jest-pattern/tsconfig.json b/packages/jest-pattern/tsconfig.json new file mode 100644 index 000000000000..43d4a6838508 --- /dev/null +++ b/packages/jest-pattern/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build" + }, + "include": ["./src/**/*"], + "exclude": ["./**/__tests__/**/*"], + "references": [{"path": "../jest-regex-util"}] +} diff --git a/packages/jest-reporters/package.json b/packages/jest-reporters/package.json index a834a4795d06..233d1163ba12 100644 --- a/packages/jest-reporters/package.json +++ b/packages/jest-reporters/package.json @@ -40,6 +40,7 @@ "v8-to-istanbul": "^9.0.1" }, "devDependencies": { + "@jest/pattern": "workspace:*", "@jest/test-utils": "workspace:*", "@tsd/typescript": "^5.0.4", "@types/exit": "^0.1.30", diff --git a/packages/jest-reporters/src/SummaryReporter.ts b/packages/jest-reporters/src/SummaryReporter.ts index 06f6383384a7..14a8884b27d3 100644 --- a/packages/jest-reporters/src/SummaryReporter.ts +++ b/packages/jest-reporters/src/SummaryReporter.ts @@ -12,7 +12,6 @@ import type { TestContext, } from '@jest/test-result'; import type {Config} from '@jest/types'; -import {TestPathPatterns} from 'jest-util'; import BaseReporter from './BaseReporter'; import getResultHeader from './getResultHeader'; import getSnapshotSummary from './getSnapshotSummary'; @@ -212,7 +211,7 @@ export default class SummaryReporter extends BaseReporter { testContexts: Set, globalConfig: Config.GlobalConfig, ) { - const testPathPatterns = TestPathPatterns.fromGlobalConfig(globalConfig); + const testPathPatterns = globalConfig.testPathPatterns; const getMatchingTestsInfo = () => { const prefix = globalConfig.findRelatedTests diff --git a/packages/jest-reporters/src/__tests__/SummaryReporter.test.js b/packages/jest-reporters/src/__tests__/SummaryReporter.test.js index 53f0f61a22b3..38091fa100d5 100644 --- a/packages/jest-reporters/src/__tests__/SummaryReporter.test.js +++ b/packages/jest-reporters/src/__tests__/SummaryReporter.test.js @@ -6,6 +6,8 @@ */ 'use strict'; +import {TestPathPatterns} from '@jest/pattern'; + let SummaryReporter; const env = {...process.env}; @@ -13,7 +15,7 @@ const now = Date.now; const write = process.stderr.write; const globalConfig = { rootDir: 'root', - testPathPatterns: [], + testPathPatterns: new TestPathPatterns([]), watch: false, }; diff --git a/packages/jest-reporters/tsconfig.json b/packages/jest-reporters/tsconfig.json index 86a51036fda6..d7113a369320 100644 --- a/packages/jest-reporters/tsconfig.json +++ b/packages/jest-reporters/tsconfig.json @@ -9,6 +9,7 @@ "references": [ {"path": "../jest-console"}, {"path": "../jest-message-util"}, + {"path": "../jest-pattern"}, {"path": "../jest-resolve"}, {"path": "../jest-test-result"}, {"path": "../jest-transform"}, diff --git a/packages/jest-types/package.json b/packages/jest-types/package.json index 22423c702fe2..064575420c71 100644 --- a/packages/jest-types/package.json +++ b/packages/jest-types/package.json @@ -20,6 +20,7 @@ "./package.json": "./package.json" }, "dependencies": { + "@jest/pattern": "workspace:*", "@jest/schemas": "workspace:*", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", diff --git a/packages/jest-types/src/Config.ts b/packages/jest-types/src/Config.ts index 5055d386195e..f4ff9f9ab3db 100644 --- a/packages/jest-types/src/Config.ts +++ b/packages/jest-types/src/Config.ts @@ -8,6 +8,7 @@ import type {ForegroundColor} from 'chalk'; import type {ReportOptions} from 'istanbul-reports'; import type {Arguments} from 'yargs'; +import type {TestPathPatterns} from '@jest/pattern'; import type {InitialOptions, SnapshotFormat} from '@jest/schemas'; export type {InitialOptions} from '@jest/schemas'; @@ -305,7 +306,7 @@ export type GlobalConfig = { errorOnDeprecated: boolean; testFailureExitCode: number; testNamePattern?: string; - testPathPatterns: Array; + testPathPatterns: TestPathPatterns; testResultsProcessor?: string; testSequencer: string; testTimeout?: number; diff --git a/packages/jest-types/tsconfig.json b/packages/jest-types/tsconfig.json index 515871de443d..78e07b4b087c 100644 --- a/packages/jest-types/tsconfig.json +++ b/packages/jest-types/tsconfig.json @@ -5,5 +5,5 @@ "outDir": "build" }, "include": ["./src/**/*"], - "references": [{"path": "../jest-schemas"}] + "references": [{"path": "../jest-pattern"}, {"path": "../jest-schemas"}] } diff --git a/packages/jest-util/package.json b/packages/jest-util/package.json index 75fa0cadc68c..a6ddcfedc967 100644 --- a/packages/jest-util/package.json +++ b/packages/jest-util/package.json @@ -24,7 +24,6 @@ "chalk": "^4.0.0", "ci-info": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "workspace:*", "picomatch": "^4.0.0" }, "devDependencies": { diff --git a/packages/jest-util/src/index.ts b/packages/jest-util/src/index.ts index ca4ea9ec29d1..dbfd9025175b 100644 --- a/packages/jest-util/src/index.ts +++ b/packages/jest-util/src/index.ts @@ -21,7 +21,6 @@ export {default as deepCyclicCopy} from './deepCyclicCopy'; export {default as convertDescriptorToString} from './convertDescriptorToString'; export {specialChars}; export {default as replacePathSepForGlob} from './replacePathSepForGlob'; -export {default as TestPathPatterns} from './TestPathPatterns'; export {default as globsToMatcher} from './globsToMatcher'; export {preRunMessage}; export {default as pluralize} from './pluralize'; diff --git a/packages/jest-util/tsconfig.json b/packages/jest-util/tsconfig.json index 1ed0dbe62371..b69d4caaeea9 100644 --- a/packages/jest-util/tsconfig.json +++ b/packages/jest-util/tsconfig.json @@ -6,5 +6,5 @@ }, "include": ["./src/**/*"], "exclude": ["./**/__tests__/**/*"], - "references": [{"path": "../jest-regex-util"}, {"path": "../jest-types"}] + "references": [{"path": "../jest-types"}] } diff --git a/packages/jest-watcher/src/types.ts b/packages/jest-watcher/src/types.ts index 18c5ba64e41a..3096326bc497 100644 --- a/packages/jest-watcher/src/types.ts +++ b/packages/jest-watcher/src/types.ts @@ -63,10 +63,12 @@ export type AllowedConfigOptions = Partial< | 'onlyFailures' | 'reporters' | 'testNamePattern' - | 'testPathPatterns' | 'updateSnapshot' | 'verbose' - > & {mode: 'watch' | 'watchAll'} + > & { + mode: 'watch' | 'watchAll'; + testPathPatterns: Array; + } >; export type UpdateConfigCallback = (config?: AllowedConfigOptions) => void; diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index a74aa4467fe0..eca4e468fceb 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@jest/globals": "workspace:*", + "@jest/pattern": "workspace:*", "@jest/types": "workspace:*", "@types/node": "*", "ansi-regex": "^5.0.1", diff --git a/packages/test-utils/src/config.ts b/packages/test-utils/src/config.ts index 383d0afc0768..868c3cfce9f8 100644 --- a/packages/test-utils/src/config.ts +++ b/packages/test-utils/src/config.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {TestPathPatterns} from '@jest/pattern'; import type {Config} from '@jest/types'; const DEFAULT_GLOBAL_CONFIG: Config.GlobalConfig = { @@ -55,7 +56,7 @@ const DEFAULT_GLOBAL_CONFIG: Config.GlobalConfig = { snapshotFormat: {}, testFailureExitCode: 1, testNamePattern: '', - testPathPatterns: [], + testPathPatterns: new TestPathPatterns([]), testResultsProcessor: undefined, testSequencer: '@jest/test-sequencer', testTimeout: 5000, diff --git a/packages/test-utils/tsconfig.json b/packages/test-utils/tsconfig.json index bcc1e9b530ae..1547e54ecbb4 100644 --- a/packages/test-utils/tsconfig.json +++ b/packages/test-utils/tsconfig.json @@ -7,6 +7,7 @@ "include": ["./src/**/*"], "references": [ {"path": "../jest-globals"}, + {"path": "../jest-pattern"}, {"path": "../jest-types"}, {"path": "../pretty-format"} ] diff --git a/scripts/lintTs.mjs b/scripts/lintTs.mjs index 59dc3fa48028..f775524a23b2 100644 --- a/scripts/lintTs.mjs +++ b/scripts/lintTs.mjs @@ -50,6 +50,7 @@ const packagesNotToTest = [ 'jest-matcher-utils', 'jest-message-util', 'jest-mock', + 'jest-pattern', 'jest-phabricator', 'jest-regex-util', 'jest-repl', diff --git a/yarn.lock b/yarn.lock index 1bc608450fc2..33ab86f08588 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2873,6 +2873,7 @@ __metadata: resolution: "@jest/core@workspace:packages/jest-core" dependencies: "@jest/console": "workspace:*" + "@jest/pattern": "workspace:*" "@jest/reporters": "workspace:*" "@jest/test-result": "workspace:*" "@jest/test-sequencer": "workspace:*" @@ -3100,12 +3101,22 @@ __metadata: languageName: unknown linkType: soft +"@jest/pattern@workspace:*, @jest/pattern@workspace:packages/jest-pattern": + version: 0.0.0-use.local + resolution: "@jest/pattern@workspace:packages/jest-pattern" + dependencies: + "@types/node": "*" + jest-regex-util: "workspace:*" + languageName: unknown + linkType: soft + "@jest/reporters@workspace:*, @jest/reporters@workspace:packages/jest-reporters": version: 0.0.0-use.local resolution: "@jest/reporters@workspace:packages/jest-reporters" dependencies: "@bcoe/v8-coverage": ^0.2.3 "@jest/console": "workspace:*" + "@jest/pattern": "workspace:*" "@jest/test-result": "workspace:*" "@jest/test-utils": "workspace:*" "@jest/transform": "workspace:*" @@ -3232,6 +3243,7 @@ __metadata: resolution: "@jest/test-utils@workspace:packages/test-utils" dependencies: "@jest/globals": "workspace:*" + "@jest/pattern": "workspace:*" "@jest/types": "workspace:*" "@types/node": "*" "@types/semver": ^7.1.0 @@ -3302,6 +3314,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jest/types@workspace:packages/jest-types" dependencies: + "@jest/pattern": "workspace:*" "@jest/schemas": "workspace:*" "@tsd/typescript": ^5.0.4 "@types/istanbul-lib-coverage": ^2.0.0 @@ -12738,6 +12751,7 @@ __metadata: resolution: "jest-config@workspace:packages/jest-config" dependencies: "@babel/core": ^7.11.6 + "@jest/pattern": "workspace:*" "@jest/test-sequencer": "workspace:*" "@jest/types": "workspace:*" "@types/graceful-fs": ^4.1.3 @@ -13261,7 +13275,6 @@ __metadata: chalk: ^4.0.0 ci-info: ^4.0.0 graceful-fs: ^4.2.9 - jest-regex-util: "workspace:*" picomatch: ^4.0.0 languageName: unknown linkType: soft