mirror of https://github.com/facebook/jest.git
Merge 0eae97d6d2
into 430e4debc1
This commit is contained in:
commit
a460b08be3
|
@ -2,6 +2,7 @@
|
|||
|
||||
### Features
|
||||
|
||||
- `[@jest/core, @jest/cli, @jest/resolve-dependencies]` Add `--maxRelatedTestsDepth` flag to control the depth of related test selection ([#15495](https://github.com/jestjs/jest/pull/15495))
|
||||
- `[babel-jest]` Add option `excludeJestPreset` to allow opting out of `babel-preset-jest` ([#15164](https://github.com/jestjs/jest/pull/15164))
|
||||
- `[jest-circus, jest-cli, jest-config]` Add `waitNextEventLoopTurnForUnhandledRejectionEvents` flag to minimise performance impact of correct detection of unhandled promise rejections introduced in [#14315](https://github.com/jestjs/jest/pull/14315) ([#14681](https://github.com/jestjs/jest/pull/14681))
|
||||
- `[jest-circus]` Add a `waitBeforeRetry` option to `jest.retryTimes` ([#14738](https://github.com/jestjs/jest/pull/14738))
|
||||
|
|
10
docs/CLI.md
10
docs/CLI.md
|
@ -34,6 +34,12 @@ Run tests related to `path/to/fileA.js` and `path/to/fileB.js`:
|
|||
jest --findRelatedTests path/to/fileA.js path/to/fileB.js
|
||||
```
|
||||
|
||||
Used with `--findRelatedTests`, sets the maximum import depth that it will accept tests from.
|
||||
|
||||
```bash
|
||||
jest --maxRelatedTestsDepth=5
|
||||
```
|
||||
|
||||
Run tests that match this spec name (match against the name in `describe` or `test`, basically).
|
||||
|
||||
```bash
|
||||
|
@ -213,6 +219,10 @@ module.exports = testPaths => {
|
|||
|
||||
Find and run the tests that cover a space separated list of source files that were passed in as arguments. Useful for pre-commit hook integration to run the minimal amount of tests necessary. Can be used together with `--coverage` to include a test coverage for the source files, no duplicate `--collectCoverageFrom` arguments needed.
|
||||
|
||||
### `--maxRelatedTestsDepth=<number>`
|
||||
|
||||
Used with `--findRelatedTests`, limits how deep Jest will traverse the dependency graph when finding related tests. A test at depth 1 directly imports the changed file, a test at depth 2 imports a file that imports the changed file, and so on. This is useful for large projects where you want to limit the scope of related test execution.
|
||||
|
||||
### `--forceExit`
|
||||
|
||||
Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up.
|
||||
|
|
|
@ -126,6 +126,7 @@ exports[`--showConfig outputs config info and exits 1`] = `
|
|||
"listTests": false,
|
||||
"logHeapUsage": false,
|
||||
"maxConcurrency": 5,
|
||||
"maxRelatedTestsDepth": Infinity,
|
||||
"maxWorkers": "[maxWorkers]",
|
||||
"noStackTrace": false,
|
||||
"nonFlagArgs": [],
|
||||
|
|
|
@ -262,4 +262,50 @@ describe('--findRelatedTests flag', () => {
|
|||
expect(stdout).toMatch('No tests found');
|
||||
expect(stderr).toBe('');
|
||||
});
|
||||
|
||||
test.each([
|
||||
[4, ['a', 'b', 'b2', 'c', 'd']],
|
||||
[3, ['a', 'b', 'b2', 'c']],
|
||||
[2, ['a', 'b', 'b2']],
|
||||
[1, ['a']],
|
||||
])(
|
||||
'runs tests with dependency chain and --maxRelatedTestsDepth=%d',
|
||||
(depth, expectedTests) => {
|
||||
writeFiles(DIR, {
|
||||
'.watchmanconfig': '{}',
|
||||
'__tests__/a.test.js':
|
||||
"const a = require('../a'); test('a', () => {expect(a).toBe(\"value\")});",
|
||||
'__tests__/b.test.js':
|
||||
"const b = require('../b'); test('b', () => {expect(b).toBe(\"value\")});",
|
||||
'__tests__/b2.test.js':
|
||||
"const b = require('../b2'); test('b', () => {expect(b).toBe(\"value\")});",
|
||||
'__tests__/c.test.js':
|
||||
"const c = require('../c'); test('c', () => {expect(c).toBe(\"value\")});",
|
||||
'__tests__/d.test.js':
|
||||
"const d = require('../d'); test('d', () => {expect(d).toBe(\"value\")});",
|
||||
'a.js': 'module.exports = "value";',
|
||||
'b.js': 'module.exports = require("./a");',
|
||||
'b2.js': 'module.exports = require("./a");',
|
||||
'c.js': 'module.exports = require("./b");',
|
||||
'd.js': 'module.exports = require("./c");',
|
||||
'package.json': JSON.stringify({jest: {testEnvironment: 'node'}}),
|
||||
});
|
||||
|
||||
const {stderr} = runJest(DIR, [
|
||||
`--maxRelatedTestsDepth=${depth}`,
|
||||
'--findRelatedTests',
|
||||
'a.js',
|
||||
]);
|
||||
|
||||
for (const testFile of expectedTests) {
|
||||
expect(stderr).toMatch(`PASS __tests__/${testFile}.test.js`);
|
||||
}
|
||||
|
||||
for (const testFile of ['a', 'b', 'b2', 'c', 'd']) {
|
||||
if (!expectedTests.includes(testFile)) {
|
||||
expect(stderr).not.toMatch(`PASS __tests__/${testFile}.test.js`);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
@ -48,6 +48,22 @@ export function check(argv: Config.Argv): true {
|
|||
);
|
||||
}
|
||||
|
||||
if (argv.maxRelatedTestsDepth && !argv.findRelatedTests) {
|
||||
throw new Error(
|
||||
'The --maxRelatedTestsDepth option requires --findRelatedTests is being used.',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(argv, 'maxRelatedTestsDepth') &&
|
||||
typeof argv.maxRelatedTestsDepth !== 'number'
|
||||
) {
|
||||
throw new Error(
|
||||
'The --maxRelatedTestsDepth option must be a number.\n' +
|
||||
'Example usage: jest --findRelatedTests --maxRelatedTestsDepth 2',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(argv, 'maxWorkers') &&
|
||||
argv.maxWorkers === undefined
|
||||
|
@ -349,6 +365,12 @@ export const options: {[key: string]: Options} = {
|
|||
requiresArg: true,
|
||||
type: 'number',
|
||||
},
|
||||
maxRelatedTestsDepth: {
|
||||
description:
|
||||
'Specifies the maximum depth for finding related tests. Requires ' +
|
||||
'--findRelatedTests to be used. Helps limit the scope of related test detection.',
|
||||
type: 'number',
|
||||
},
|
||||
maxWorkers: {
|
||||
alias: 'w',
|
||||
description:
|
||||
|
|
|
@ -94,6 +94,7 @@ export const initialOptions: Config.InitialOptions = {
|
|||
listTests: false,
|
||||
logHeapUsage: true,
|
||||
maxConcurrency: 5,
|
||||
maxRelatedTestsDepth: Infinity,
|
||||
maxWorkers: '50%',
|
||||
moduleDirectories: ['node_modules'],
|
||||
moduleFileExtensions: [
|
||||
|
|
|
@ -105,6 +105,7 @@ const groupOptions = (
|
|||
listTests: options.listTests,
|
||||
logHeapUsage: options.logHeapUsage,
|
||||
maxConcurrency: options.maxConcurrency,
|
||||
maxRelatedTestsDepth: options.maxRelatedTestsDepth,
|
||||
maxWorkers: options.maxWorkers,
|
||||
noSCM: undefined,
|
||||
noStackTrace: options.noStackTrace,
|
||||
|
|
|
@ -900,6 +900,7 @@ export default async function normalize(
|
|||
case 'globals':
|
||||
case 'fakeTimers':
|
||||
case 'findRelatedTests':
|
||||
case 'maxRelatedTestsDepth':
|
||||
case 'forceCoverageMatch':
|
||||
case 'forceExit':
|
||||
case 'injectGlobals':
|
||||
|
|
|
@ -178,6 +178,7 @@ export default class SearchSource {
|
|||
async findRelatedTests(
|
||||
allPaths: Set<string>,
|
||||
collectCoverage: boolean,
|
||||
maxDepth: number,
|
||||
): Promise<SearchResult> {
|
||||
const dependencyResolver = await this._getOrBuildDependencyResolver();
|
||||
|
||||
|
@ -188,7 +189,10 @@ export default class SearchSource {
|
|||
dependencyResolver.resolveInverse(
|
||||
allPaths,
|
||||
this.isTestFilePath.bind(this),
|
||||
{skipNodeResolution: this._context.config.skipNodeResolution},
|
||||
{
|
||||
maxDepth,
|
||||
skipNodeResolution: this._context.config.skipNodeResolution,
|
||||
},
|
||||
),
|
||||
),
|
||||
};
|
||||
|
@ -197,7 +201,10 @@ export default class SearchSource {
|
|||
const testModulesMap = dependencyResolver.resolveInverseModuleMap(
|
||||
allPaths,
|
||||
this.isTestFilePath.bind(this),
|
||||
{skipNodeResolution: this._context.config.skipNodeResolution},
|
||||
{
|
||||
maxDepth,
|
||||
skipNodeResolution: this._context.config.skipNodeResolution,
|
||||
},
|
||||
);
|
||||
|
||||
const allPathsAbsolute = new Set([...allPaths].map(p => path.resolve(p)));
|
||||
|
@ -246,12 +253,17 @@ export default class SearchSource {
|
|||
async findRelatedTestsFromPattern(
|
||||
paths: Array<string>,
|
||||
collectCoverage: boolean,
|
||||
maxDepth: number,
|
||||
): Promise<SearchResult> {
|
||||
if (Array.isArray(paths) && paths.length > 0) {
|
||||
const resolvedPaths = paths.map(p =>
|
||||
path.resolve(this._context.config.cwd, p),
|
||||
);
|
||||
return this.findRelatedTests(new Set(resolvedPaths), collectCoverage);
|
||||
return this.findRelatedTests(
|
||||
new Set(resolvedPaths),
|
||||
collectCoverage,
|
||||
maxDepth,
|
||||
);
|
||||
}
|
||||
return {tests: []};
|
||||
}
|
||||
|
@ -259,12 +271,13 @@ export default class SearchSource {
|
|||
async findTestRelatedToChangedFiles(
|
||||
changedFilesInfo: ChangedFiles,
|
||||
collectCoverage: boolean,
|
||||
maxDepth: number,
|
||||
): Promise<SearchResult> {
|
||||
if (!hasSCM(changedFilesInfo)) {
|
||||
return {noSCM: true, tests: []};
|
||||
}
|
||||
const {changedFiles} = changedFilesInfo;
|
||||
return this.findRelatedTests(changedFiles, collectCoverage);
|
||||
return this.findRelatedTests(changedFiles, collectCoverage, maxDepth);
|
||||
}
|
||||
|
||||
private async _getTestPaths(
|
||||
|
@ -280,6 +293,7 @@ export default class SearchSource {
|
|||
return this.findTestRelatedToChangedFiles(
|
||||
changedFiles,
|
||||
globalConfig.collectCoverage,
|
||||
globalConfig.maxRelatedTestsDepth,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -295,6 +309,7 @@ export default class SearchSource {
|
|||
return this.findRelatedTestsFromPattern(
|
||||
paths,
|
||||
globalConfig.collectCoverage,
|
||||
globalConfig.maxRelatedTestsDepth,
|
||||
);
|
||||
} else {
|
||||
return this.findMatchingTests(
|
||||
|
|
|
@ -102,6 +102,7 @@ exports[`prints the config object 1`] = `
|
|||
"listTests": false,
|
||||
"logHeapUsage": false,
|
||||
"maxConcurrency": 5,
|
||||
"maxRelatedTestsDepth": Infinity,
|
||||
"maxWorkers": 2,
|
||||
"noStackTrace": false,
|
||||
"nonFlagArgs": [],
|
||||
|
|
|
@ -70,6 +70,10 @@ export default function updateGlobalConfig(
|
|||
newConfig.findRelatedTests = options.findRelatedTests;
|
||||
}
|
||||
|
||||
if (options.maxRelatedTestsDepth !== undefined) {
|
||||
newConfig.maxRelatedTestsDepth = options.maxRelatedTestsDepth;
|
||||
}
|
||||
|
||||
if (options.nonFlagArgs !== undefined) {
|
||||
newConfig.nonFlagArgs = options.nonFlagArgs;
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ export default async function watch(
|
|||
coverageDirectory,
|
||||
coverageReporters,
|
||||
findRelatedTests,
|
||||
maxRelatedTestsDepth,
|
||||
mode,
|
||||
nonFlagArgs,
|
||||
notify,
|
||||
|
@ -135,6 +136,7 @@ export default async function watch(
|
|||
coverageDirectory,
|
||||
coverageReporters,
|
||||
findRelatedTests,
|
||||
maxRelatedTestsDepth,
|
||||
mode,
|
||||
nonFlagArgs,
|
||||
notify,
|
||||
|
|
|
@ -118,7 +118,10 @@ export class DependencyResolver {
|
|||
) => {
|
||||
const visitedModules = new Set();
|
||||
const result: Array<ResolvedModule> = [];
|
||||
while (changed.size > 0) {
|
||||
let depth = 0;
|
||||
const maxDepth = options?.maxDepth ?? Infinity;
|
||||
|
||||
while (changed.size > 0 && depth < maxDepth) {
|
||||
changed = new Set(
|
||||
moduleMap.reduce<Array<string>>((acc, module) => {
|
||||
if (
|
||||
|
@ -138,6 +141,7 @@ export class DependencyResolver {
|
|||
return acc;
|
||||
}, []),
|
||||
);
|
||||
depth++;
|
||||
}
|
||||
return [
|
||||
...result,
|
||||
|
@ -165,6 +169,7 @@ export class DependencyResolver {
|
|||
file,
|
||||
});
|
||||
}
|
||||
|
||||
return collectModules(relatedPaths, modules, changed);
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ export type ResolveModuleConfig = {
|
|||
conditions?: Array<string>;
|
||||
skipNodeResolution?: boolean;
|
||||
paths?: Array<string>;
|
||||
maxDepth?: number;
|
||||
};
|
||||
|
||||
const NATIVE_PLATFORM = 'native';
|
||||
|
|
|
@ -251,6 +251,7 @@ export const InitialOptions = Type.Partial(
|
|||
fakeTimers: FakeTimers,
|
||||
filter: Type.String(),
|
||||
findRelatedTests: Type.Boolean(),
|
||||
maxRelatedTestsDepth: Type.Number(),
|
||||
forceCoverageMatch: Type.Array(Type.String()),
|
||||
forceExit: Type.Boolean(),
|
||||
json: Type.Boolean(),
|
||||
|
|
|
@ -173,6 +173,7 @@ export type DefaultOptions = {
|
|||
injectGlobals: boolean;
|
||||
listTests: boolean;
|
||||
maxConcurrency: number;
|
||||
maxRelatedTestsDepth: number;
|
||||
maxWorkers: number | string;
|
||||
moduleDirectories: Array<string>;
|
||||
moduleFileExtensions: Array<string>;
|
||||
|
@ -271,6 +272,7 @@ export type GlobalConfig = {
|
|||
expand: boolean;
|
||||
filter?: string;
|
||||
findRelatedTests: boolean;
|
||||
maxRelatedTestsDepth: number;
|
||||
forceExit: boolean;
|
||||
json: boolean;
|
||||
globalSetup?: string;
|
||||
|
@ -424,6 +426,7 @@ export type Argv = Arguments<
|
|||
env: string;
|
||||
expand: boolean;
|
||||
findRelatedTests: boolean;
|
||||
maxRelatedTestsDepth: number;
|
||||
forceExit: boolean;
|
||||
globals: string;
|
||||
globalSetup: string | null | undefined;
|
||||
|
|
|
@ -57,6 +57,7 @@ export type AllowedConfigOptions = Partial<
|
|||
| 'coverageDirectory'
|
||||
| 'coverageReporters'
|
||||
| 'findRelatedTests'
|
||||
| 'maxRelatedTestsDepth'
|
||||
| 'nonFlagArgs'
|
||||
| 'notify'
|
||||
| 'notifyMode'
|
||||
|
|
|
@ -33,6 +33,7 @@ const DEFAULT_GLOBAL_CONFIG: Config.GlobalConfig = {
|
|||
listTests: false,
|
||||
logHeapUsage: false,
|
||||
maxConcurrency: 5,
|
||||
maxRelatedTestsDepth: Infinity,
|
||||
maxWorkers: 2,
|
||||
noSCM: undefined,
|
||||
noStackTrace: false,
|
||||
|
|
Loading…
Reference in New Issue