Only match relative paths when specifying paths (#12519)

Co-authored-by: Brandon Chinn <brandonchinn178@gmail.com>
This commit is contained in:
Brandon Chinn 2023-10-02 22:56:50 -07:00 committed by GitHub
parent 46285d8bd2
commit 41133b526d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 488 additions and 330 deletions

View File

@ -25,6 +25,9 @@
- `[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))
- `[pretty-format]` [**BREAKING**] Print `ArrayBuffer` and `DataView` correctly ([#14290](https://github.com/facebook/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/facebook/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`
### Performance

View File

@ -481,11 +481,11 @@ The regex is matched against the full name, which is a combination of the test n
### `--testPathIgnorePatterns=<regex>|[array]`
A single or array of regexp pattern strings that are tested against all tests paths before executing the test. Contrary to `--testPathPattern`, it will only run those tests with a path that does not match with the provided regexp expressions.
A single or array of regexp pattern strings that are tested against all tests paths before executing the test. Contrary to `--testPathPatterns`, it will only run those tests with a path that does not match with the provided regexp expressions.
To pass as an array use escaped parentheses and space delimited regexps such as `\(/node_modules/ /tests/e2e/\)`. Alternatively, you can omit parentheses by combining regexps into a single regexp like `/node_modules/|/tests/e2e/`. These two examples are equivalent.
### `--testPathPattern=<regex>`
### `--testPathPatterns=<regex>`
A regexp pattern string that is matched against all tests paths before executing the test. On Windows, you will need to use `/` as a path separator or escape `\` as `\\`.

View File

@ -787,7 +787,7 @@ While code transformation is applied to the linked setup-file, Jest will **not**
```js title="setup.js"
module.exports = async function (globalConfig, projectConfig) {
console.log(globalConfig.testPathPattern);
console.log(globalConfig.testPathPatterns);
console.log(projectConfig.cache);
// Set reference to mongod in order to close the server during teardown.
@ -797,7 +797,7 @@ module.exports = async function (globalConfig, projectConfig) {
```js title="teardown.js"
module.exports = async function (globalConfig, projectConfig) {
console.log(globalConfig.testPathPattern);
console.log(globalConfig.testPathPatterns);
console.log(projectConfig.cache);
await globalThis.__MONGOD__.stop();

View File

@ -172,7 +172,7 @@ For stability and safety reasons, only part of the global configuration keys can
- [`onlyFailures`](configuration#onlyfailures-boolean)
- [`reporters`](configuration#reporters-arraymodulename--modulename-options)
- [`testNamePattern`](cli#--testnamepatternregex)
- [`testPathPattern`](cli#--testpathpatternregex)
- [`testPathPatterns`](cli#--testpathpatternsregex)
- [`updateSnapshot`](cli#--updatesnapshot)
- [`verbose`](configuration#verbose-boolean)

View File

@ -13,5 +13,5 @@ exports[`CLI accepts exact file names if matchers matched 2`] = `
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /.\\/foo\\/bar.spec.js/i."
Ran all test suites matching ./foo/bar.spec.js."
`;

View File

@ -82,7 +82,7 @@ exports[`Custom Reporters Integration default reporters enabled 2`] = `
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /add.test.js/i."
Ran all test suites matching add.test.js."
`;
exports[`Custom Reporters Integration default reporters enabled 3`] = `

View File

@ -7,7 +7,7 @@ Object {
Tests: 1 skipped, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /hookInDescribeWithSkippedTest.test.js/i.",
Ran all test suites matching hookInDescribeWithSkippedTest.test.js.",
}
`;
@ -34,7 +34,7 @@ Object {
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /hookInEmptyDescribe.test.js/i.",
Ran all test suites matching hookInEmptyDescribe.test.js.",
}
`;
@ -61,7 +61,7 @@ Object {
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /hookInEmptyNestedDescribe.test.js/i.",
Ran all test suites matching hookInEmptyNestedDescribe.test.js.",
}
`;
@ -133,6 +133,6 @@ Object {
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /multipleHooksInEmptyDescribe.test.js/i.",
Ran all test suites matching multipleHooksInEmptyDescribe.test.js.",
}
`;

View File

@ -5,7 +5,7 @@ exports[`--findRelatedTests flag coverage configuration is applied correctly 1`]
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites related to files matching /a.js|b.js/i."
Ran all test suites related to files matching a.js|b.js."
`;
exports[`--findRelatedTests flag coverage configuration is applied correctly 2`] = `
@ -50,7 +50,7 @@ exports[`--findRelatedTests flag generates coverage report for filename 4`] = `
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites related to files matching /a.js/i."
Ran all test suites related to files matching a.js."
`;
exports[`--findRelatedTests flag generates coverage report for filename 5`] = `

View File

@ -5,7 +5,7 @@ exports[`does not enforce import assertions 1`] = `
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /native-esm-missing-import-assertions.test.js/i."
Ran all test suites matching native-esm-missing-import-assertions.test.js."
`;
exports[`on node >=16.11.0 support re-exports from CJS of dual packages 1`] = `
@ -13,7 +13,7 @@ exports[`on node >=16.11.0 support re-exports from CJS of dual packages 1`] = `
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /native-esm-deep-cjs-reexport.test.js/i."
Ran all test suites matching native-esm-deep-cjs-reexport.test.js."
`;
exports[`on node >=16.12.0 supports import assertions 1`] = `
@ -21,7 +21,7 @@ exports[`on node >=16.12.0 supports import assertions 1`] = `
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /native-esm-import-assertions.test.js/i."
Ran all test suites matching native-esm-import-assertions.test.js."
`;
exports[`properly handle re-exported native modules in ESM via CJS 1`] = `
@ -29,7 +29,7 @@ exports[`properly handle re-exported native modules in ESM via CJS 1`] = `
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /native-esm-native-module.test.js/i."
Ran all test suites matching native-esm-native-module.test.js."
`;
exports[`runs WebAssembly (Wasm) test with native ESM 1`] = `
@ -37,7 +37,7 @@ exports[`runs WebAssembly (Wasm) test with native ESM 1`] = `
Tests: 6 passed, 6 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /native-esm-wasm.test.js/i."
Ran all test suites matching native-esm-wasm.test.js."
`;
exports[`runs test with native ESM 1`] = `
@ -45,7 +45,7 @@ exports[`runs test with native ESM 1`] = `
Tests: 33 passed, 33 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /native-esm.test.js/i."
Ran all test suites matching native-esm.test.js."
`;
exports[`support re-exports from CJS of core module 1`] = `
@ -53,7 +53,7 @@ exports[`support re-exports from CJS of core module 1`] = `
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /native-esm-core-cjs-reexport.test.js/i."
Ran all test suites matching native-esm-core-cjs-reexport.test.js."
`;
exports[`supports top-level await 1`] = `
@ -61,5 +61,5 @@ exports[`supports top-level await 1`] = `
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /native-esm.tla.test.js/i."
Ran all test suites matching native-esm.tla.test.js."
`;

View File

@ -22,7 +22,7 @@ Object {
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /unhandledRejectionAfterAll.test.js/i.",
Ran all test suites matching unhandledRejectionAfterAll.test.js.",
}
`;
@ -63,7 +63,7 @@ Object {
Tests: 2 failed, 2 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /unhandledRejectionAfterEach.test.js/i.",
Ran all test suites matching unhandledRejectionAfterEach.test.js.",
}
`;
@ -89,7 +89,7 @@ Object {
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /unhandledRejectionBeforeAll.test.js/i.",
Ran all test suites matching unhandledRejectionBeforeAll.test.js.",
}
`;
@ -130,7 +130,7 @@ Object {
Tests: 2 failed, 2 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /unhandledRejectionBeforeEach.test.js/i.",
Ran all test suites matching unhandledRejectionBeforeEach.test.js.",
}
`;
@ -217,7 +217,7 @@ Object {
Tests: 4 failed, 4 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /unhandledRejectionTest.test.js/i.",
Ran all test suites matching unhandledRejectionTest.test.js.",
}
`;
@ -231,6 +231,6 @@ Object {
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /rejectionHandled.test.js/i.",
Ran all test suites matching rejectionHandled.test.js.",
}
`;

View File

@ -139,7 +139,7 @@ exports[`--showConfig outputs config info and exits 1`] = `
"printBasicPrototype": false
},
"testFailureExitCode": 1,
"testPathPattern": "",
"testPathPatterns": [],
"testSequencer": "<<REPLACED_JEST_PACKAGES_DIR>>/jest-test-sequencer/build/index.js",
"updateSnapshot": "none",
"useStderr": false,

View File

@ -85,7 +85,7 @@ exports[`Snapshot works with escaped characters 1`] = `
Tests: 1 passed, 1 total
Snapshots: 1 written, 1 total
Time: <<REPLACED>>
Ran all test suites matching /snapshot.test.js/i."
Ran all test suites matching snapshot.test.js."
`;
exports[`Snapshot works with escaped characters 2`] = `
@ -93,7 +93,7 @@ exports[`Snapshot works with escaped characters 2`] = `
Tests: 2 passed, 2 total
Snapshots: 1 written, 1 passed, 2 total
Time: <<REPLACED>>
Ran all test suites matching /snapshot.test.js/i."
Ran all test suites matching snapshot.test.js."
`;
exports[`Snapshot works with escaped characters 3`] = `
@ -101,7 +101,7 @@ exports[`Snapshot works with escaped characters 3`] = `
Tests: 2 passed, 2 total
Snapshots: 2 passed, 2 total
Time: <<REPLACED>>
Ran all test suites matching /snapshot.test.js/i."
Ran all test suites matching snapshot.test.js."
`;
exports[`Snapshot works with escaped regex 1`] = `
@ -109,7 +109,7 @@ exports[`Snapshot works with escaped regex 1`] = `
Tests: 2 passed, 2 total
Snapshots: 2 written, 2 total
Time: <<REPLACED>>
Ran all test suites matching /snapshotEscapeRegex.js/i."
Ran all test suites matching snapshotEscapeRegex.js."
`;
exports[`Snapshot works with escaped regex 2`] = `
@ -117,7 +117,7 @@ exports[`Snapshot works with escaped regex 2`] = `
Tests: 2 passed, 2 total
Snapshots: 2 passed, 2 total
Time: <<REPLACED>>
Ran all test suites matching /snapshotEscapeRegex.js/i."
Ran all test suites matching snapshotEscapeRegex.js."
`;
exports[`Snapshot works with template literal substitutions 1`] = `
@ -125,7 +125,7 @@ exports[`Snapshot works with template literal substitutions 1`] = `
Tests: 1 passed, 1 total
Snapshots: 1 written, 1 total
Time: <<REPLACED>>
Ran all test suites matching /snapshotEscapeSubstitution.test.js/i."
Ran all test suites matching snapshotEscapeSubstitution.test.js."
`;
exports[`Snapshot works with template literal substitutions 2`] = `
@ -133,5 +133,5 @@ exports[`Snapshot works with template literal substitutions 2`] = `
Tests: 1 passed, 1 total
Snapshots: 1 passed, 1 total
Time: <<REPLACED>>
Ran all test suites matching /snapshotEscapeSubstitution.test.js/i."
Ran all test suites matching snapshotEscapeSubstitution.test.js."
`;

View File

@ -5,7 +5,7 @@ exports[`Stack Trace does not print a stack trace for errors when --noStackTrace
Tests: 3 failed, 3 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /testError.test.js/i."
Ran all test suites matching testError.test.js."
`;
exports[`Stack Trace does not print a stack trace for matching errors when --noStackTrace is given 1`] = `
@ -13,7 +13,7 @@ exports[`Stack Trace does not print a stack trace for matching errors when --noS
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /stackTrace.test.js/i."
Ran all test suites matching stackTrace.test.js."
`;
exports[`Stack Trace does not print a stack trace for runtime errors when --noStackTrace is given 1`] = `
@ -21,7 +21,7 @@ exports[`Stack Trace does not print a stack trace for runtime errors when --noSt
Tests: 0 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /runtimeError.test.js/i."
Ran all test suites matching runtimeError.test.js."
`;
exports[`Stack Trace prints a stack trace for errors 1`] = `
@ -29,7 +29,7 @@ exports[`Stack Trace prints a stack trace for errors 1`] = `
Tests: 3 failed, 3 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /testError.test.js/i."
Ran all test suites matching testError.test.js."
`;
exports[`Stack Trace prints a stack trace for errors without message in stack trace 1`] = `
@ -37,7 +37,7 @@ exports[`Stack Trace prints a stack trace for errors without message in stack tr
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /stackTraceWithoutMessage.test.js/i."
Ran all test suites matching stackTraceWithoutMessage.test.js."
`;
exports[`Stack Trace prints a stack trace for matching errors 1`] = `
@ -45,7 +45,7 @@ exports[`Stack Trace prints a stack trace for matching errors 1`] = `
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /stackTrace.test.js/i."
Ran all test suites matching stackTrace.test.js."
`;
exports[`Stack Trace prints a stack trace for runtime errors 1`] = `
@ -53,5 +53,5 @@ exports[`Stack Trace prints a stack trace for runtime errors 1`] = `
Tests: 0 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /runtimeError.test.js/i."
Ran all test suites matching runtimeError.test.js."
`;

View File

@ -11,7 +11,7 @@ Test Suites: 1 passed, 1 total
Tests: 2 todo, 1 passed, 3 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /only-todo.test.js/i."
Ran all test suites matching only-todo.test.js."
`;
exports[`shows error messages when called with invalid argument 1`] = `

View File

@ -66,7 +66,7 @@ exports[`can press "p" to filter by file name: test summary 2`] = `
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /bar/i."
Ran all test suites matching bar."
`;
exports[`can press "t" to filter by test name 1`] = `

View File

@ -33,7 +33,7 @@ describe('--findRelatedTests flag', () => {
const {stderr} = runJest(DIR, ['--findRelatedTests', 'a.js']);
expect(stderr).toMatch('PASS __tests__/test.test.js');
const summaryMsg = 'Ran all test suites related to files matching /a.js/i.';
const summaryMsg = 'Ran all test suites related to files matching a.js.';
expect(stderr).toMatch(summaryMsg);
});
@ -59,7 +59,7 @@ describe('--findRelatedTests flag', () => {
const {stderr} = runJest(DIR, ['--findRelatedTests', 'A.JS']);
expect(stderr).toMatch('PASS __tests__/test.test.js');
const summaryMsg = 'Ran all test suites related to files matching /A.JS/i.';
const summaryMsg = 'Ran all test suites related to files matching A.JS.';
expect(stderr).toMatch(summaryMsg);
});
@ -112,7 +112,7 @@ describe('--findRelatedTests flag', () => {
expect(stderr).toMatch('PASS __tests__/test.test.js');
expect(stderr).not.toMatch('PASS __tests__/test-skip-deps.test.js');
const summaryMsg = 'Ran all test suites related to files matching /a.js/i.';
const summaryMsg = 'Ran all test suites related to files matching a.js.';
expect(stderr).toMatch(summaryMsg);
});
@ -162,7 +162,7 @@ describe('--findRelatedTests flag', () => {
expect(stderr).toMatch('PASS __tests__/test.test.js');
expect(stderr).not.toMatch('PASS __tests__/test-skip-deps.test.js');
const summaryMsg = 'Ran all test suites related to files matching /a.js/i.';
const summaryMsg = 'Ran all test suites related to files matching a.js.';
expect(stderr).toMatch(summaryMsg);
});

View File

@ -56,7 +56,7 @@ test('globalSetup is triggered once before all test suites', () => {
const setupPath = path.join(e2eDir, 'setup.js');
const result = runWithJson(e2eDir, [
`--globalSetup=${setupPath}`,
'--testPathPattern=__tests__',
'--testPathPatterns=__tests__',
]);
expect(result.exitCode).toBe(0);
@ -70,7 +70,7 @@ test('jest throws an error when globalSetup does not export a function', () => {
const setupPath = path.resolve(__dirname, '../global-setup/invalidSetup.js');
const {exitCode, stderr} = runJest(e2eDir, [
`--globalSetup=${setupPath}`,
'--testPathPattern=__tests__',
'--testPathPatterns=__tests__',
]);
expect(exitCode).toBe(1);
@ -83,15 +83,13 @@ test('jest throws an error when globalSetup does not export a function', () => {
test('globalSetup function gets global config object and project config as parameters', () => {
const setupPath = path.resolve(e2eDir, 'setupWithConfig.js');
const testPathPattern = 'pass';
const result = runJest(e2eDir, [
`--globalSetup=${setupPath}`,
`--testPathPattern=${testPathPattern}`,
'--testPathPatterns=pass',
'--cache=true',
]);
expect(result.stdout).toBe(`${testPathPattern}\ntrue`);
expect(result.stdout).toBe("[ 'pass' ]\ntrue");
});
test('should call globalSetup function of multiple projects', () => {
@ -111,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}`,
'--testPathPattern=project-1',
'--testPathPatterns=project-1',
]);
expect(result.exitCode).toBe(0);
@ -140,15 +138,13 @@ test('should not call any globalSetup if there are no tests to run', () => {
test('globalSetup works with default export', () => {
const setupPath = path.resolve(e2eDir, 'setupWithDefaultExport.js');
const testPathPattern = 'pass';
const result = runJest(e2eDir, [
`--globalSetup=${setupPath}`,
`--testPathPattern=${testPathPattern}`,
'--testPathPatterns=pass',
'--cache=true',
]);
expect(result.stdout).toBe(`${testPathPattern}\ntrue`);
expect(result.stdout).toBe("[ 'pass' ]\ntrue");
});
test('globalSetup throws with named export', () => {
@ -156,7 +152,7 @@ test('globalSetup throws with named export', () => {
const {exitCode, stderr} = runJest(e2eDir, [
`--globalSetup=${setupPath}`,
'--testPathPattern=__tests__',
'--testPathPatterns=__tests__',
]);
expect(exitCode).toBe(1);

View File

@ -40,7 +40,7 @@ test('globalTeardown is triggered once after all test suites', () => {
const teardownPath = path.resolve(e2eDir, 'teardown.js');
const result = runWithJson('global-teardown', [
`--globalTeardown=${teardownPath}`,
'--testPathPattern=__tests__',
'--testPathPatterns=__tests__',
]);
expect(result.exitCode).toBe(0);
@ -54,7 +54,7 @@ test('jest throws an error when globalTeardown does not export a function', () =
const teardownPath = path.resolve(e2eDir, 'invalidTeardown.js');
const {exitCode, stderr} = runJest(e2eDir, [
`--globalTeardown=${teardownPath}`,
'--testPathPattern=__tests__',
'--testPathPatterns=__tests__',
]);
expect(exitCode).toBe(1);
@ -67,15 +67,13 @@ test('jest throws an error when globalTeardown does not export a function', () =
test('globalSetup function gets global config object and project config as parameters', () => {
const teardownPath = path.resolve(e2eDir, 'teardownWithConfig.js');
const testPathPattern = 'pass';
const result = runJest(e2eDir, [
`--globalTeardown=${teardownPath}`,
`--testPathPattern=${testPathPattern}`,
'--testPathPatterns=pass',
'--cache=true',
]);
expect(result.stdout).toBe(`${testPathPattern}\ntrue`);
expect(result.stdout).toBe("[ 'pass' ]\ntrue");
});
test('should call globalTeardown function of multiple projects', () => {
@ -95,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}`,
'--testPathPattern=project-1',
'--testPathPatterns=project-1',
]);
expect(result.exitCode).toBe(0);
@ -108,15 +106,13 @@ test('should not call a globalTeardown of a project if there are no tests to run
test('globalTeardown works with default export', () => {
const teardownPath = path.resolve(e2eDir, 'teardownWithDefaultExport.js');
const testPathPattern = 'pass';
const result = runJest(e2eDir, [
`--globalTeardown=${teardownPath}`,
`--testPathPattern=${testPathPattern}`,
'--testPathPatterns=pass',
'--cache=true',
]);
expect(result.stdout).toBe(`${testPathPattern}\ntrue`);
expect(result.stdout).toBe("[ 'pass' ]\ntrue");
});
test('globalTeardown throws with named export', () => {
@ -127,7 +123,7 @@ test('globalTeardown throws with named export', () => {
const {exitCode, stderr} = runJest(e2eDir, [
`--globalTeardown=${teardownPath}`,
'--testPathPattern=__tests__',
'--testPathPatterns=__tests__',
]);
expect(exitCode).toBe(1);

View File

@ -13,7 +13,7 @@ const DIR = path.resolve(__dirname, '../no-tests-found-test');
describe('No tests are found', () => {
test('fails the test suite in standard situation', () => {
const {exitCode, stdout} = runJest(DIR, [
'--testPathPattern',
'--testPathPatterns',
'/non/existing/path/',
]);
@ -26,7 +26,7 @@ describe('No tests are found', () => {
test("doesn't fail the test suite if --passWithNoTests passed", () => {
const {exitCode, stdout} = runJest(DIR, [
'--testPathPattern',
'--testPathPatterns',
'/non/existing/path/',
'--passWithNoTests',
]);

View File

@ -246,7 +246,7 @@ test('collect test coverage when using onlyChanged', () => {
expect(exitCode).toBe(0);
});
test('onlyChanged in config is overwritten by --all or testPathPattern', () => {
test('onlyChanged in config is overwritten by --all or testPathPatterns', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'__tests__/file1.test.js': "require('../file1'); test('file1', () => {});",

View File

@ -25,14 +25,14 @@ test('prints a message with path pattern at the end', () => {
let stderr;
({stderr} = runJest(DIR, ['a']));
expect(stderr).toMatch('Ran all test suites matching /a/i');
expect(stderr).toMatch('Ran all test suites matching a');
({stderr} = runJest(DIR, ['a', 'b']));
expect(stderr).toMatch('Ran all test suites matching /a|b/i');
expect(stderr).toMatch('Ran all test suites matching a|b');
({stderr} = runJest(DIR, ['--testPathPattern', 'a']));
expect(stderr).toMatch('Ran all test suites matching /a/i');
({stderr} = runJest(DIR, ['--testPathPatterns', 'a']));
expect(stderr).toMatch('Ran all test suites matching a');
({stderr} = runJest(DIR, ['--testPathPattern', 'a|b']));
expect(stderr).toMatch('Ran all test suites matching /a|b/i');
({stderr} = runJest(DIR, ['--testPathPatterns', 'a|b']));
expect(stderr).toMatch('Ran all test suites matching a|b');
});

View File

@ -6,7 +6,7 @@
*/
function invalidSetupWithNamedExport(jestConfig): void {
console.log(jestConfig.testPathPattern);
console.log(jestConfig.testPathPatterns);
}
export {invalidSetupWithNamedExport};

View File

@ -6,6 +6,6 @@
*/
module.exports = function (globalConfig, projectConfig) {
console.log(globalConfig.testPathPattern);
console.log(globalConfig.testPathPatterns);
console.log(projectConfig.cache);
};

View File

@ -6,6 +6,6 @@
*/
export default function (globalConfig, projectConfig): void {
console.log(globalConfig.testPathPattern);
console.log(globalConfig.testPathPatterns);
console.log(projectConfig.cache);
}

View File

@ -6,7 +6,7 @@
*/
function invalidTeardownWithNamedExport(jestConfig): void {
console.log(jestConfig.testPathPattern);
console.log(jestConfig.testPathPatterns);
}
export {invalidTeardownWithNamedExport};

View File

@ -6,6 +6,6 @@
*/
module.exports = function (globalConfig, projectConfig) {
console.log(globalConfig.testPathPattern);
console.log(globalConfig.testPathPatterns);
console.log(projectConfig.cache);
};

View File

@ -6,6 +6,6 @@
*/
export default function (globalConfig, projectConfig): void {
console.log(globalConfig.testPathPattern);
console.log(globalConfig.testPathPatterns);
console.log(projectConfig.cache);
}

View File

@ -97,7 +97,7 @@ export function check(argv: Config.Argv): true {
}
export const usage =
'Usage: $0 [--config=<pathToConfigFile>] [TestPathPattern]';
'Usage: $0 [--config=<pathToConfigFile>] [TestPathPatterns]';
export const docs = 'Documentation: https://jestjs.io/';
// The default values are all set in jest-config
@ -431,7 +431,7 @@ export const options: {[key: string]: Options} = {
},
passWithNoTests: {
description:
'Will not fail if no tests are found (for example while using `--testPathPattern`.)',
'Will not fail if no tests are found (for example while using `--testPathPatterns`.)',
type: 'boolean',
},
preset: {
@ -609,7 +609,7 @@ export const options: {[key: string]: Options} = {
string: true,
type: 'array',
},
testPathPattern: {
testPathPatterns: {
description:
'A regexp pattern string that is matched against all tests ' +
'paths before executing the test.',

View File

@ -8,6 +8,15 @@
import chalk = require('chalk');
import type {DeprecatedOptions} from 'jest-validate';
function formatDeprecation(message: string): string {
const lines = [
message.replace(/\*(.+?)\*/g, (_, s) => chalk.bold(`"${s}"`)),
'',
'Please update your configuration.',
];
return lines.map(s => ` ${s}`).join('\n');
}
const deprecatedOptions: DeprecatedOptions = {
browser: () =>
` Option ${chalk.bold(
@ -78,6 +87,11 @@ const deprecatedOptions: DeprecatedOptions = {
Please update your configuration.
`,
testPathPattern: () =>
formatDeprecation(
'Option *testPathPattern* was replaced by *testPathPatterns*.',
),
testURL: (_options: {testURL?: string}) => ` Option ${chalk.bold(
'"testURL"',
)} was replaced by passing the URL via ${chalk.bold(

View File

@ -482,9 +482,9 @@ exports[`testMatch throws if testRegex and testMatch are both specified 1`] = `
<red></color>"
`;
exports[`testPathPattern <regexForTestFiles> ignores invalid regular expressions and logs a warning 1`] = `"<red> Invalid testPattern a( supplied. Running all tests instead.</color>"`;
exports[`testPathPatterns <regexForTestFiles> ignores invalid regular expressions and logs a warning 1`] = `"<red> Invalid testPattern a( supplied. Running all tests instead.</color>"`;
exports[`testPathPattern --testPathPattern ignores invalid regular expressions and logs a warning 1`] = `"<red> Invalid testPattern a( supplied. Running all tests instead.</color>"`;
exports[`testPathPatterns --testPathPatterns ignores invalid regular expressions and logs a warning 1`] = `"<red> Invalid testPattern a( supplied. Running all tests instead.</color>"`;
exports[`testTimeout should throw an error if timeout is a negative number 1`] = `
"<red><bold><bold>● </intensity><bold>Validation Error</intensity>:</color>

View File

@ -1585,7 +1585,7 @@ describe('watchPlugins', () => {
});
});
describe('testPathPattern', () => {
describe('testPathPatterns', () => {
const initialOptions = {rootDir: '/root'};
const consoleLog = console.log;
@ -1600,11 +1600,11 @@ describe('testPathPattern', () => {
it('defaults to empty', async () => {
const {options} = await normalize(initialOptions, {} as Config.Argv);
expect(options.testPathPattern).toBe('');
expect(options.testPathPatterns).toEqual([]);
});
const cliOptions = [
{name: '--testPathPattern', property: 'testPathPattern'},
{name: '--testPathPatterns', property: 'testPathPatterns'},
{name: '<regexForTestFiles>', property: '_'},
];
for (const opt of cliOptions) {
@ -1613,14 +1613,14 @@ describe('testPathPattern', () => {
const argv = {[opt.property]: ['a/b']} as Config.Argv;
const {options} = await normalize(initialOptions, argv);
expect(options.testPathPattern).toBe('a/b');
expect(options.testPathPatterns).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.testPathPattern).toBe('');
expect(options.testPathPatterns).toEqual([]);
expect(jest.mocked(console.log).mock.calls[0][0]).toMatchSnapshot();
});
@ -1628,78 +1628,24 @@ describe('testPathPattern', () => {
const argv = {[opt.property]: ['a/b', 'c/d']} as Config.Argv;
const {options} = await normalize(initialOptions, argv);
expect(options.testPathPattern).toBe('a/b|c/d');
});
it('coerces all patterns to strings', async () => {
const argv = {[opt.property]: [1]} as Config.Argv;
const {options} = await normalize(initialOptions, argv);
expect(options.testPathPattern).toBe('1');
});
describe('posix', () => {
it('should not escape the pattern', async () => {
const argv = {
[opt.property]: ['a\\/b', 'a/b', 'a\\b', 'a\\\\b'],
} as Config.Argv;
const {options} = await normalize(initialOptions, argv);
expect(options.testPathPattern).toBe('a\\/b|a/b|a\\b|a\\\\b');
});
});
describe('win32', () => {
beforeEach(() => {
jest.mock(
'path',
() => jest.requireActual<typeof import('path')>('path').win32,
);
(
require('jest-resolve') as typeof import('jest-resolve')
).default.findNodeModule = findNodeModule;
});
afterEach(() => {
jest.resetModules();
});
it('preserves any use of "\\"', async () => {
const argv = {[opt.property]: ['a\\b', 'c\\\\d']} as Config.Argv;
const {options} = await (
require('../normalize') as typeof import('../normalize')
).default(initialOptions, argv);
expect(options.testPathPattern).toBe('a\\b|c\\\\d');
});
it('replaces POSIX path separators', async () => {
const argv = {[opt.property]: ['a/b']} as Config.Argv;
const {options} = await (
require('../normalize') as typeof import('../normalize')
).default(initialOptions, argv);
expect(options.testPathPattern).toBe('a\\\\b');
});
it('replaces POSIX paths in multiple args', async () => {
const argv = {[opt.property]: ['a/b', 'c/d']} as Config.Argv;
const {options} = await (
require('../normalize') as typeof import('../normalize')
).default(initialOptions, argv);
expect(options.testPathPattern).toBe('a\\\\b|c\\\\d');
});
expect(options.testPathPatterns).toEqual(['a/b', 'c/d']);
});
});
}
it('coerces <regexForTestFiles> patterns to strings', async () => {
const argv = {_: [1]} as Config.Argv;
const {options} = await normalize(initialOptions, argv);
expect(options.testPathPatterns).toEqual(['1']);
});
it('joins multiple --testPathPatterns and <regexForTestFiles>', async () => {
const {options} = await normalize(initialOptions, {
_: ['a', 'b'],
testPathPattern: ['c', 'd'],
testPathPatterns: ['c', 'd'],
} as Config.Argv);
expect(options.testPathPattern).toBe('a|b|c|d');
expect(options.testPathPatterns).toEqual(['a', 'b', 'c', 'd']);
});
it('gives precedence to --all', async () => {

View File

@ -1,35 +0,0 @@
/**
* 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 validatePattern from '../validatePattern';
describe('validate pattern function', () => {
it('without passed args returns true', () => {
const isValid = validatePattern();
expect(isValid).toBeTruthy();
});
it('returns true for empty pattern', () => {
const isValid = validatePattern('');
expect(isValid).toBeTruthy();
});
it('returns true for valid pattern', () => {
const isValid = validatePattern('abc+');
expect(isValid).toBeTruthy();
});
it('returns false for invalid pattern', () => {
const isValid = validatePattern('\\');
expect(isValid).toBeFalsy();
});
});

View File

@ -131,7 +131,7 @@ const groupOptions = (
snapshotFormat: options.snapshotFormat,
testFailureExitCode: options.testFailureExitCode,
testNamePattern: options.testNamePattern,
testPathPattern: options.testPathPattern,
testPathPatterns: options.testPathPatterns,
testResultsProcessor: options.testResultsProcessor,
testSequencer: options.testSequencer,
testTimeout: options.testTimeout,

View File

@ -22,6 +22,7 @@ import Resolver, {
resolveWatchPlugin,
} from 'jest-resolve';
import {
TestPathPatterns,
clearLine,
replacePathSepForGlob,
requireOrImportModule,
@ -49,7 +50,6 @@ import {
replaceRootDirInPath,
resolve,
} from './utils';
import validatePattern from './validatePattern';
const ERROR = `${BULLET}Validation Error`;
const PRESET_EXTENSIONS = ['.json', '.js', '.cjs', '.mjs'];
@ -391,44 +391,39 @@ const normalizeReporters = ({
});
};
const buildTestPathPattern = (argv: Config.Argv): string => {
const buildTestPathPatterns = (
argv: Config.Argv,
rootDir: string,
): TestPathPatterns => {
const patterns = [];
if (argv._) {
patterns.push(...argv._);
patterns.push(...argv._.map(x => x.toString()));
}
if (argv.testPathPattern) {
patterns.push(...argv.testPathPattern);
if (argv.testPathPatterns) {
patterns.push(...argv.testPathPatterns);
}
const replacePosixSep = (pattern: string | number) => {
// yargs coerces positional args into numbers
const patternAsString = pattern.toString();
if (path.sep === '/') {
return patternAsString;
}
return patternAsString.replace(/\//g, '\\\\');
};
const config = {rootDir};
const testPathPatterns = new TestPathPatterns(patterns, config);
const testPathPattern = patterns.map(replacePosixSep).join('|');
if (validatePattern(testPathPattern)) {
return testPathPattern;
} else {
showTestPathPatternError(testPathPattern);
return '';
try {
testPathPatterns.validate();
} catch {
clearLine(process.stdout);
// eslint-disable-next-line no-console
console.log(
chalk.red(
` Invalid testPattern ${testPathPatterns.toPretty()} supplied. ` +
'Running all tests instead.',
),
);
return new TestPathPatterns([], config);
}
};
const showTestPathPatternError = (testPathPattern: string) => {
clearLine(process.stdout);
// eslint-disable-next-line no-console
console.log(
chalk.red(
` Invalid testPattern ${testPathPattern} supplied. ` +
'Running all tests instead.',
),
);
return testPathPatterns;
};
function validateExtensionsToTreatAsEsm(
@ -1007,7 +1002,8 @@ export default async function normalize(
}
newOptions.nonFlagArgs = argv._?.map(arg => `${arg}`);
newOptions.testPathPattern = buildTestPathPattern(argv);
const testPathPatterns = buildTestPathPatterns(argv, options.rootDir);
newOptions.testPathPatterns = testPathPatterns.patterns;
newOptions.json = !!argv.json;
newOptions.testFailureExitCode = parseInt(
@ -1026,7 +1022,7 @@ export default async function normalize(
if (argv.all) {
newOptions.onlyChanged = false;
newOptions.onlyFailures = false;
} else if (newOptions.testPathPattern) {
} else if (testPathPatterns.isSet()) {
// When passing a test path pattern we don't want to only monitor changed
// files unless `--watch` is also passed.
newOptions.onlyChanged = newOptions.watch;

View File

@ -1,19 +0,0 @@
/**
* 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 default function validatePattern(pattern?: string): boolean {
if (pattern) {
try {
// eslint-disable-next-line no-new
new RegExp(pattern, 'i');
} catch {
return false;
}
}
return true;
}

View File

@ -15,7 +15,7 @@ import {replaceRootDirInPath} from 'jest-config';
import {escapePathForRegex} from 'jest-regex-util';
import {DependencyResolver} from 'jest-resolve-dependencies';
import {buildSnapshotResolver} from 'jest-snapshot';
import {globsToMatcher, testPathPatternToRegExp} from 'jest-util';
import {TestPathPatterns, globsToMatcher} from 'jest-util';
import type {Filter, Stats, TestPathCases} from './types';
export type SearchResult = {
@ -110,7 +110,7 @@ export default class SearchSource {
private _filterTestPathsWithStats(
allPaths: Array<Test>,
testPathPattern: string,
testPathPatterns: TestPathPatterns,
): SearchResult {
const data: {
stats: Stats;
@ -128,13 +128,12 @@ export default class SearchSource {
};
const testCases = Array.from(this._testPathCases); // clone
if (testPathPattern) {
const regex = testPathPatternToRegExp(testPathPattern);
if (testPathPatterns.isSet()) {
testCases.push({
isMatch: (path: string) => regex.test(path),
stat: 'testPathPattern',
isMatch: (path: string) => testPathPatterns.isMatch(path),
stat: 'testPathPatterns',
});
data.stats.testPathPattern = 0;
data.stats.testPathPatterns = 0;
}
data.tests = allPaths.filter(test => {
@ -152,10 +151,10 @@ export default class SearchSource {
return data;
}
private _getAllTestPaths(testPathPattern: string): SearchResult {
private _getAllTestPaths(testPathPatterns: TestPathPatterns): SearchResult {
return this._filterTestPathsWithStats(
toTests(this._context, this._context.hasteFS.getAllFiles()),
testPathPattern,
testPathPatterns,
);
}
@ -163,8 +162,8 @@ export default class SearchSource {
return this._testPathCases.every(testCase => testCase.isMatch(path));
}
findMatchingTests(testPathPattern: string): SearchResult {
return this._getAllTestPaths(testPathPattern);
findMatchingTests(testPathPatterns: TestPathPatterns): SearchResult {
return this._getAllTestPaths(testPathPatterns);
}
async findRelatedTests(
@ -287,10 +286,10 @@ export default class SearchSource {
paths,
globalConfig.collectCoverage,
);
} else if (globalConfig.testPathPattern == null) {
return {tests: []};
} else {
return this.findMatchingTests(globalConfig.testPathPattern);
return this.findMatchingTests(
TestPathPatterns.fromGlobalConfig(globalConfig),
);
}
}

View File

@ -109,7 +109,8 @@ describe('SearchSource', () => {
const {searchSource, config} = await initSearchSource(initialOptions);
const {tests: paths} = await searchSource.getTestPaths({
...config,
testPathPattern: '',
...initialOptions,
testPathPatterns: [],
});
return paths.map(({path: p}) => path.relative(rootDir, p)).sort();
};

View File

@ -92,7 +92,10 @@ exports[`Watch mode flows Pressing "P" enters pattern mode 9`] = `
Object {
"onlyChanged": false,
"passWithNoTests": true,
"testPathPattern": "p.*3",
"rootDir": "",
"testPathPatterns": Array [
"p.*3",
],
"watch": true,
"watchAll": false,
}

View File

@ -18,7 +18,7 @@ describe('getNoTestsFoundMessage', () => {
function createGlobalConfig(options?: Partial<Config.GlobalConfig>) {
return makeGlobalConfig({
rootDir: '/root/dir',
testPathPattern: '/path/pattern',
testPathPatterns: ['/path/pattern'],
...options,
});
}

View File

@ -22,6 +22,8 @@ describe('runJest', () => {
changedFilesPromise: Promise.resolve({repos: {git: {size: 0}}}),
contexts: [],
globalConfig: {
rootDir: '',
testPathPatterns: [],
testSequencer: require.resolve('@jest/test-sequencer'),
watch: true,
},

View File

@ -140,7 +140,11 @@ describe('Watch mode flows', () => {
testRegex: [],
};
pipe = {write: jest.fn()};
globalConfig = {watch: true};
globalConfig = {
rootDir: '',
testPathPatterns: [],
watch: true,
};
hasteMapInstances = [{on: () => {}}];
contexts = [{config}];
stdin = new MockStdin();
@ -152,7 +156,7 @@ describe('Watch mode flows', () => {
});
it('Correctly passing test path pattern', async () => {
globalConfig.testPathPattern = 'test-*';
globalConfig.testPathPatterns = ['test-*'];
await watch(globalConfig, contexts, pipe, hasteMapInstances, stdin);
@ -671,7 +675,7 @@ describe('Watch mode flows', () => {
${'✖︎'} | ${'skipFilter'}
${'✖︎'} | ${'testFailureExitCode'}
${'✔︎'} | ${'testNamePattern'}
${'✔︎'} | ${'testPathPattern'}
${'✔︎'} | ${'testPathPatterns'}
${'✖︎'} | ${'testResultsProcessor'}
${'✔︎'} | ${'updateSnapshot'}
${'✖︎'} | ${'useStderr'}
@ -898,7 +902,7 @@ describe('Watch mode flows', () => {
await nextTick();
expect(runJestMock.mock.calls[0][0].globalConfig).toMatchObject({
testPathPattern: 'file',
testPathPatterns: ['file'],
watch: true,
watchAll: false,
});
@ -922,7 +926,7 @@ describe('Watch mode flows', () => {
expect(runJestMock.mock.calls[1][0].globalConfig).toMatchObject({
testNamePattern: 'test',
testPathPattern: 'file',
testPathPatterns: ['file'],
watch: true,
watchAll: false,
});

View File

@ -70,7 +70,11 @@ const watch = require('../watch').default;
const nextTick = () => new Promise(res => process.nextTick(res));
const globalConfig = {watch: true};
const globalConfig = {
rootDir: '',
testPathPatterns: [],
watch: true,
};
afterEach(runJestMock.mockReset);

View File

@ -83,6 +83,8 @@ jest.doMock(
const watch = require('../watch').default;
const globalConfig = {
rootDir: '',
testPathPatterns: [],
watch: true,
};

View File

@ -7,7 +7,7 @@
import chalk = require('chalk');
import type {Config} from '@jest/types';
import {pluralize} from 'jest-util';
import {TestPathPatterns, pluralize} from 'jest-util';
import type {TestRunData} from './types';
export default function getNoTestFound(
@ -26,8 +26,9 @@ export default function getNoTestFound(
.map(p => `"${p}"`)
.join(', ')}`;
} else {
const testPathPatterns = TestPathPatterns.fromGlobalConfig(globalConfig);
dataMessage = `Pattern: ${chalk.yellow(
globalConfig.testPathPattern,
testPathPatterns.toPretty(),
)} - 0 matches`;
}

View File

@ -7,7 +7,7 @@
import chalk = require('chalk');
import type {Config} from '@jest/types';
import {pluralize} from 'jest-util';
import {TestPathPatterns, pluralize} from 'jest-util';
import type {Stats, TestRunData} from './types';
export default function getNoTestFoundVerbose(
@ -56,8 +56,9 @@ export default function getNoTestFoundVerbose(
.map(p => `"${p}"`)
.join(', ')}`;
} else {
const testPathPatterns = TestPathPatterns.fromGlobalConfig(globalConfig);
dataMessage = `Pattern: ${chalk.yellow(
globalConfig.testPathPattern,
testPathPatterns.toPretty(),
)} - 0 matches`;
}

View File

@ -109,7 +109,7 @@ exports[`prints the config object 1`] = `
"snapshotFormat": {},
"testFailureExitCode": 1,
"testNamePattern": "",
"testPathPattern": "",
"testPathPatterns": [],
"testSequencer": "@jest/test-sequencer",
"testTimeout": 5000,
"updateSnapshot": "none",

View File

@ -7,14 +7,15 @@
import chalk = require('chalk');
import type {Config} from '@jest/types';
import {isNonNullable} from 'jest-util';
import {TestPathPatterns, isNonNullable} from 'jest-util';
const activeFilters = (globalConfig: Config.GlobalConfig): string => {
const {testNamePattern, testPathPattern} = globalConfig;
if (testNamePattern || testPathPattern) {
const {testNamePattern} = globalConfig;
const testPathPatterns = TestPathPatterns.fromGlobalConfig(globalConfig);
if (testNamePattern || testPathPatterns.isSet()) {
const filters = [
testPathPattern
? chalk.dim('filename ') + chalk.yellow(`/${testPathPattern}/`)
testPathPatterns.isSet()
? chalk.dim('filename ') + chalk.yellow(testPathPatterns.toPretty())
: null,
testNamePattern
? chalk.dim('test name ') + chalk.yellow(`/${testNamePattern}/`)

View File

@ -6,7 +6,7 @@
*/
import type {Config} from '@jest/types';
import {replacePathSepForRegex} from 'jest-regex-util';
import {TestPathPatterns} from 'jest-util';
import type {AllowedConfigOptions} from 'jest-watcher';
type ExtraConfigOptions = Partial<
@ -31,15 +31,14 @@ export default function updateGlobalConfig(
newConfig.testNamePattern = options.testNamePattern || '';
}
if (options.testPathPattern !== undefined) {
newConfig.testPathPattern =
replacePathSepForRegex(options.testPathPattern) || '';
if (options.testPathPatterns !== undefined) {
newConfig.testPathPatterns = options.testPathPatterns;
}
newConfig.onlyChanged =
!newConfig.watchAll &&
!newConfig.testNamePattern &&
!newConfig.testPathPattern;
!TestPathPatterns.fromGlobalConfig(newConfig).isSet();
if (typeof options.bail === 'boolean') {
newConfig.bail = options.bail ? 1 : 0;

View File

@ -58,7 +58,7 @@ export default class FailedTestsInteractivePlugin extends BaseWatchPlugin {
updateConfigAndRun({
mode: 'watch',
testNamePattern: failure ? `^${failure.fullName}$` : '',
testPathPattern: failure?.path || '',
testPathPatterns: failure ? [failure.path] : [],
});
if (!this._manager.isActive()) {

View File

@ -48,7 +48,7 @@ class TestPathPatternPlugin extends BaseWatchPlugin {
testPathPatternPrompt.run(
(value: string) => {
updateConfigAndRun({mode: 'watch', testPathPattern: value});
updateConfigAndRun({mode: 'watch', testPathPatterns: [value]});
res();
},
rej,

View File

@ -70,7 +70,7 @@ class UpdateSnapshotInteractivePlugin extends BaseWatchPlugin {
updateConfigAndRun({
mode: 'watch',
testNamePattern: assertion ? `^${assertion.fullName}$` : '',
testPathPattern: assertion ? assertion.path : '',
testPathPatterns: assertion ? [assertion.path] : [],
updateSnapshot: shouldUpdateSnapshot ? 'all' : 'none',
});

View File

@ -40,7 +40,7 @@ describe('FailedTestsInteractive', () => {
expect(mockUpdate).toHaveBeenCalledWith({
mode: 'watch',
testNamePattern: `^${testAggregate.testResults[0].testResults[0].fullName}$`,
testPathPattern: testAggregate.testResults[0].testFilePath,
testPathPatterns: [testAggregate.testResults[0].testFilePath],
});
});
});

View File

@ -12,7 +12,7 @@ export type Stats = {
testMatch: number;
testPathIgnorePatterns: number;
testRegex: number;
testPathPattern?: number;
testPathPatterns?: number;
};
export type TestRunData = Array<{
@ -31,7 +31,7 @@ export type TestPathCases = Array<{
}>;
export type TestPathCasesWithPathPattern = TestPathCases & {
testPathPattern: (path: string) => boolean;
testPathPatterns: (path: string) => boolean;
};
export type FilterResult = {

View File

@ -15,6 +15,7 @@ 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,
@ -120,7 +121,7 @@ export default async function watch(
onlyFailures,
reporters,
testNamePattern,
testPathPattern,
testPathPatterns,
updateSnapshot,
verbose,
}: AllowedConfigOptions = {}) => {
@ -140,7 +141,7 @@ export default async function watch(
onlyFailures,
reporters,
testNamePattern,
testPathPattern,
testPathPatterns,
updateSnapshot,
verbose,
});
@ -227,9 +228,12 @@ 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('').tests.map(t => t.path),
testPaths: searchSource
.findMatchingTests(testPathPatterns)
.tests.map(t => t.path),
}));
hooks.getEmitter().onFileChange({projects});
}
@ -404,7 +408,7 @@ export default async function watch(
globalConfig = updateGlobalConfig(globalConfig, {
mode: 'watchAll',
testNamePattern: '',
testPathPattern: '',
testPathPatterns: [],
});
startRun(globalConfig);
break;
@ -412,7 +416,7 @@ export default async function watch(
updateConfigAndRun({
mode: 'watch',
testNamePattern: '',
testPathPattern: '',
testPathPatterns: [],
});
break;
case 'f':
@ -425,7 +429,7 @@ export default async function watch(
globalConfig = updateGlobalConfig(globalConfig, {
mode: 'watch',
testNamePattern: '',
testPathPattern: '',
testPathPatterns: [],
});
startRun(globalConfig);
break;
@ -528,10 +532,11 @@ const usage = (
watchPlugins: Array<WatchPlugin>,
delimiter = '\n',
) => {
const testPathPatterns = TestPathPatterns.fromGlobalConfig(globalConfig);
const messages = [
activeFilters(globalConfig),
globalConfig.testPathPattern || globalConfig.testNamePattern
testPathPatterns.isSet() || globalConfig.testNamePattern
? `${chalk.dim(' \u203A Press ')}c${chalk.dim(' to clear filters.')}`
: null,
`\n${chalk.bold('Watch Usage')}`,
@ -549,7 +554,7 @@ const usage = (
)}`,
(globalConfig.watchAll ||
globalConfig.testPathPattern ||
testPathPatterns.isSet() ||
globalConfig.testNamePattern) &&
!globalConfig.noSCM
? `${chalk.dim(' \u203A Press ')}o${chalk.dim(

View File

@ -12,7 +12,7 @@ import type {
TestContext,
} from '@jest/test-result';
import type {Config} from '@jest/types';
import {testPathPatternToRegExp} from 'jest-util';
import {TestPathPatterns} from 'jest-util';
import BaseReporter from './BaseReporter';
import getResultHeader from './getResultHeader';
import getSnapshotSummary from './getSnapshotSummary';
@ -212,15 +212,14 @@ export default class SummaryReporter extends BaseReporter {
testContexts: Set<TestContext>,
globalConfig: Config.GlobalConfig,
) {
const testPathPatterns = TestPathPatterns.fromGlobalConfig(globalConfig);
const getMatchingTestsInfo = () => {
const prefix = globalConfig.findRelatedTests
? ' related to files matching '
: ' matching ';
return (
chalk.dim(prefix) +
testPathPatternToRegExp(globalConfig.testPathPattern).toString()
);
return chalk.dim(prefix) + testPathPatterns.toPretty();
};
let testInfo = '';
@ -229,7 +228,7 @@ export default class SummaryReporter extends BaseReporter {
testInfo = chalk.dim(' within paths');
} else if (globalConfig.onlyChanged) {
testInfo = chalk.dim(' related to changed files');
} else if (globalConfig.testPathPattern) {
} else if (testPathPatterns.isSet()) {
testInfo = getMatchingTestsInfo();
}

View File

@ -13,6 +13,7 @@ const now = Date.now;
const write = process.stderr.write;
const globalConfig = {
rootDir: 'root',
testPathPatterns: [],
watch: false,
};

View File

@ -412,7 +412,7 @@ export type GlobalConfig = {
errorOnDeprecated: boolean;
testFailureExitCode: number;
testNamePattern?: string;
testPathPattern: string;
testPathPatterns: Array<string>;
testResultsProcessor?: string;
testSequencer: string;
testTimeout?: number;
@ -569,7 +569,7 @@ export type Argv = Arguments<
testMatch: Array<string>;
testNamePattern: string;
testPathIgnorePatterns: Array<string>;
testPathPattern: Array<string>;
testPathPatterns: Array<string>;
testRegex: string | Array<string>;
testResultsProcessor: string;
testRunner: string;

View File

@ -78,9 +78,9 @@ Used to set properties with specified values within a global object. It is desig
It defines constants and conditional values for handling platform-specific behaviors in a terminal environment. It determines if the current platform is Windows ('win32') and sets up constants for various symbols and terminal screen clearing escape sequences accordingly, ensuring proper display and behavior on both Windows and non-Windows operating systems.
## `testPathPatternToRegExp`
## `TestPathPatterns`
This function is used for consistency when serializing/deserializing global configurations and ensures that consistent regular expressions are produced for matching test paths.
This class takes test patterns and provides the API for deciding if a test matches any of the patterns.
## `tryRealpath`

View File

@ -0,0 +1,90 @@
/**
* 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 type {Config} from '@jest/types';
import {escapePathForRegex, replacePathSepForRegex} from 'jest-regex-util';
type PatternsConfig = {
rootDir: string;
};
export default class TestPathPatterns {
private _regexString: string | null = null;
constructor(
readonly patterns: Array<string>,
private readonly config: PatternsConfig,
) {}
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 rootDirRegex = escapePathForRegex(rootDir);
const regexString = this.patterns
.map(p => {
// absolute paths passed on command line should stay same
if (p.match(/^\//)) {
return p;
}
// explicit relative paths should resolve against rootDir
if (p.match(/^\.\//)) {
return p.replace(/^\.\//, rootDirRegex);
}
// all other patterns should only match the relative part of the test
return `${rootDirRegex}(.*)?${p}`;
})
.map(replacePathSepForRegex)
.join('|');
this._regexString = regexString;
return regexString;
}
private toRegex(): RegExp {
return new RegExp(this.regexString, 'i');
}
/**
* Return true if there are any patterns.
*/
isSet(): boolean {
return this.patterns.length > 0;
}
/**
* Throw an error if the patterns don't form a valid regex.
*/
validate(): void {
this.toRegex();
}
/**
* Return true if the given ABSOLUTE path matches the patterns.
*
* Throws an error if the patterns form an invalid regex (see `validate`).
*/
isMatch(path: string): boolean {
return this.toRegex().test(path);
}
/**
* Return a human-friendly version of the pattern regex.
*/
toPretty(): string {
return this.patterns.join('|');
}
}

View File

@ -0,0 +1,163 @@
/**
* 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 type * as path from 'path';
import TestPathPatterns from '../TestPathPatterns';
const mockSep = jest.fn();
jest.mock('path', () => {
return {
...(jest.requireActual('path') as typeof path),
get sep() {
return mockSep() || '/';
},
};
});
beforeEach(() => {
jest.resetAllMocks();
});
const config = {rootDir: ''};
describe('TestPathPatterns', () => {
describe('isSet', () => {
it('returns false if no patterns specified', () => {
const testPathPatterns = new TestPathPatterns([], config);
expect(testPathPatterns.isSet()).toBe(false);
});
it('returns true if patterns specified', () => {
const testPathPatterns = new TestPathPatterns(['a'], config);
expect(testPathPatterns.isSet()).toBe(true);
});
});
describe('validate', () => {
it('succeeds for empty patterns', () => {
const testPathPatterns = new TestPathPatterns([], config);
expect(() => testPathPatterns.validate()).not.toThrow();
});
it('succeeds for valid patterns', () => {
const testPathPatterns = new TestPathPatterns(['abc+', 'z.*'], config);
expect(() => testPathPatterns.validate()).not.toThrow();
});
it('fails for at least one invalid pattern', () => {
const testPathPatterns = new TestPathPatterns(
['abc+', '(', 'z.*'],
config,
);
expect(() => testPathPatterns.validate()).toThrow(
'Invalid regular expression',
);
});
});
describe('isMatch', () => {
it('returns true with no patterns', () => {
const testPathPatterns = new TestPathPatterns([], config);
expect(testPathPatterns.isMatch('/a/b')).toBe(true);
});
it('returns true for same path', () => {
const testPathPatterns = new TestPathPatterns(['/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);
expect(testPathPatternsUpper.isMatch('/a/b')).toBe(true);
expect(testPathPatternsUpper.isMatch('/A/B')).toBe(true);
const testPathPatternsLower = new TestPathPatterns(['/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);
expect(testPathPatterns.isMatch('/a/b/c/d')).toBe(true);
});
it('returns true for explicit relative path', () => {
const testPathPatterns = new TestPathPatterns(['./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);
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);
expect(testPathPatterns.isMatch('/a/b/c/d')).toBe(true);
});
it('returns true if regex matches', () => {
const testPathPatterns = new TestPathPatterns(['ab*c?'], config);
expect(testPathPatterns.isMatch('/foo/a')).toBe(true);
expect(testPathPatterns.isMatch('/foo/ab')).toBe(true);
expect(testPathPatterns.isMatch('/foo/abb')).toBe(true);
expect(testPathPatterns.isMatch('/foo/ac')).toBe(true);
expect(testPathPatterns.isMatch('/foo/abc')).toBe(true);
expect(testPathPatterns.isMatch('/foo/abbc')).toBe(true);
expect(testPathPatterns.isMatch('/foo/bc')).toBe(false);
});
it('returns true only if matches relative path', () => {
const testPathPatterns = new TestPathPatterns(['home'], {
rootDir: '/home/myuser/',
});
expect(testPathPatterns.isMatch('/home/myuser/LoginPage.js')).toBe(false);
expect(testPathPatterns.isMatch('/home/myuser/HomePage.js')).toBe(true);
});
it('matches absolute paths regardless of rootDir', () => {
const testPathPatterns = new TestPathPatterns(['/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);
expect(testPathPatterns.isMatch('/foo/a/b')).toBe(true);
expect(testPathPatterns.isMatch('/foo/c/d')).toBe(true);
expect(testPathPatterns.isMatch('/foo/a')).toBe(false);
expect(testPathPatterns.isMatch('/foo/b/c')).toBe(false);
});
it('does not normalize Windows paths on POSIX', () => {
mockSep.mockReturnValue('/');
const testPathPatterns = new TestPathPatterns(['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);
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();
});
});
});

View File

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TestPathPatterns toPretty renders a human-readable string 1`] = `"a/b|c/d"`;

View File

@ -21,7 +21,7 @@ export {default as deepCyclicCopy} from './deepCyclicCopy';
export {default as convertDescriptorToString} from './convertDescriptorToString';
export {specialChars};
export {default as replacePathSepForGlob} from './replacePathSepForGlob';
export {default as testPathPatternToRegExp} from './testPathPatternToRegExp';
export {default as TestPathPatterns} from './TestPathPatterns';
export {default as globsToMatcher} from './globsToMatcher';
export {preRunMessage};
export {default as pluralize} from './pluralize';

View File

@ -1,17 +0,0 @@
/**
* 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 type {Config} from '@jest/types';
// Because we serialize/deserialize globalConfig when we spawn workers,
// we can't pass regular expression. Using this shared function on both sides
// will ensure that we produce consistent regexp for testPathPattern.
export default function testPathPatternToRegExp(
testPathPattern: Config.GlobalConfig['testPathPattern'],
): RegExp {
return new RegExp(testPathPattern, 'i');
}

View File

@ -62,7 +62,7 @@ export type AllowedConfigOptions = Partial<
| 'onlyFailures'
| 'reporters'
| 'testNamePattern'
| 'testPathPattern'
| 'testPathPatterns'
| 'updateSnapshot'
| 'verbose'
> & {mode: 'watch' | 'watchAll'}

View File

@ -55,7 +55,7 @@ const DEFAULT_GLOBAL_CONFIG: Config.GlobalConfig = {
snapshotFormat: {},
testFailureExitCode: 1,
testNamePattern: '',
testPathPattern: '',
testPathPatterns: [],
testResultsProcessor: undefined,
testSequencer: '@jest/test-sequencer',
testTimeout: 5000,