Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --ignoreProjects CLI argument to ignore test suites by project name #12620

Merged
merged 13 commits into from Apr 4, 2022
6 changes: 5 additions & 1 deletion docs/CLI.md
Expand Up @@ -224,6 +224,10 @@ This feature is an escape-hatch. If Jest doesn't exit at the end of a test run,

Show the help information, similar to this page.

### `--igoreProjects <project1> ... <projectN>`
F3n67u marked this conversation as resolved.
Show resolved Hide resolved

Ignore the tests of the specified projects. Jest uses the attribute `displayName` in the configuration to identify each project. If you use this option, you should provide a `displayName` to all your projects.

### `--init`

Generate a basic configuration file. Based on your project, Jest will ask you a few questions that will help to generate a `jest.config.js` file with a short description for each option.
Expand Down Expand Up @@ -332,7 +336,7 @@ The default regex matching works fine on small runs, but becomes slow if provide

### `--selectProjects <project1> ... <projectN>`

Run only the tests of the specified projects. Jest uses the attribute `displayName` in the configuration to identify each project. If you use this option, you should provide a `displayName` to all your projects.
Run the tests of the specified projects. Jest uses the attribute `displayName` in the configuration to identify each project. If you use this option, you should provide a `displayName` to all your projects.

### `--setupFilesAfterEnv <path1> ... <pathN>`

Expand Down
109 changes: 109 additions & 0 deletions e2e/__tests__/selectProjects.test.ts
Expand Up @@ -111,6 +111,89 @@ describe('Given a config with two named projects, first-project and second-proje
);
});
});

describe('when Jest is started with `--ignoreProjects first-project', () => {
let result: RunJestJsonResult;
beforeAll(() => {
result = runWithJson('select-projects', [
'--ignoreProjects',
'first-project',
]);
});
it('runs the tests in the second project only', () => {
expect(result.json).toHaveProperty('success', true);
expect(result.json).toHaveProperty('numTotalTests', 1);
expect(result.json.testResults.map(({name}) => name)).toEqual([
resolve(dir, '__tests__/second-project.test.js'),
]);
});
it('prints that only second-project will run', () => {
expect(result.stderr).toMatch(/^Running one project: second-project/);
});
});

describe('when Jest is started with `--ignoreProjects second-project', () => {
let result: RunJestJsonResult;
beforeAll(() => {
result = runWithJson('select-projects', [
'--ignoreProjects',
'second-project',
]);
});
it('runs the tests in the first project only', () => {
expect(result.json).toHaveProperty('success', true);
expect(result.json).toHaveProperty('numTotalTests', 1);
expect(result.json.testResults.map(({name}) => name)).toEqual([
resolve(dir, '__tests__/first-project.test.js'),
]);
});
it('prints that only first-project will run', () => {
expect(result.stderr).toMatch(/^Running one project: first-project/);
});
});

describe('when Jest is started with `--ignoreProjects third-project`', () => {
let result: RunJestJsonResult;
beforeAll(() => {
result = runWithJson('select-projects', [
'--ignoreProjects',
'third-project',
]);
});
it('runs the tests in the first and second projects', () => {
expect(result.json).toHaveProperty('success', true);
expect(result.json).toHaveProperty('numTotalTests', 2);
expect(result.json.testResults.map(({name}) => name).sort()).toEqual([
resolve(dir, '__tests__/first-project.test.js'),
resolve(dir, '__tests__/second-project.test.js'),
]);
});
it('prints that both first-project and second-project will run', () => {
expect(result.stderr).toMatch(
/^Running 2 projects:\n- first-project\n- second-project/,
);
});
});

describe('when Jest is started with `--ignoreProjects first-project second-project`', () => {
let result: RunJestResult;
beforeAll(() => {
result = run('select-projects', [
'--ignoreProjects',
'first-project',
'second-project',
]);
});
it('fails', () => {
expect(result).toHaveProperty('failed', true);
});
// FIXME(F3n67u)
it.skip('prints that no project was found', () => {
expect(result.stdout).toMatch(
/^You provided values for --selectProjects but no projects were found matching the selection/,
);
});
});
});

describe('Given a config with two projects, first-project and an unnamed project', () => {
Expand Down Expand Up @@ -185,4 +268,30 @@ describe('Given a config with two projects, first-project and an unnamed project
);
});
});

describe('when Jest is started with `--ignoreProjects first-project`', () => {
let result: RunJestJsonResult;
beforeAll(() => {
result = runWithJson('select-projects-missing-name', [
'--ignoreProjects',
'first-project',
]);
});
it('runs the tests in the second project only', () => {
expect(result.json.success).toBe(true);
expect(result.json.numTotalTests).toBe(1);
expect(result.json.testResults.map(({name}) => name)).toEqual([
resolve(dir, '__tests__/second-project.test.js'),
]);
});
it('prints that a project does not have a name', () => {
expect(result.stderr).toMatch(
/^You provided values for --ignoreProjects but a project does not have a name/,
);
});
it('prints that only second-project will run', () => {
const stderrThirdLine = result.stderr.split('\n')[2];
expect(stderrThirdLine).toMatch(/^Running one project: second-project/);
});
});
});
6 changes: 6 additions & 0 deletions packages/jest-cli/src/__tests__/cli/args.test.ts
Expand Up @@ -81,6 +81,12 @@ describe('check', () => {
);
});

it('raises an exception if ignoreProjects is not provided any project names', () => {
expect(() => check(argv({ignoreProjects: []}))).toThrow(
'The --ignoreProjects option requires the name of at least one project to be specified.\n',
);
});

it('raises an exception if config is not a valid JSON string', () => {
expect(() => check(argv({config: 'x:1'}))).toThrow(
'The --config option requires a JSON string literal, or a file path with one of these extensions: .js, .ts, .mjs, .cjs, .json',
Expand Down
21 changes: 18 additions & 3 deletions packages/jest-cli/src/cli/args.ts
Expand Up @@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import type {Options} from 'yargs';
import type {Config} from '@jest/types';
import {constants, isJSONString} from 'jest-config';

Expand Down Expand Up @@ -68,6 +69,13 @@ export function check(argv: Config.Argv): true {
);
}

if (argv.ignoreProjects && argv.ignoreProjects.length === 0) {
throw new Error(
'The --ignoreProjects option requires the name of at least one project to be specified.\n' +
'Example usage: jest --ignoreProjects my-first-project my-second-project',
);
}

if (
argv.config &&
!isJSONString(argv.config) &&
Expand Down Expand Up @@ -95,7 +103,7 @@ export const usage =
export const docs = 'Documentation: https://jestjs.io/';

// The default values are all set in jest-config
export const options = {
export const options: {[key: string]: Options} = {
all: {
description:
'The opposite of `onlyChanged`. If `onlyChanged` is set by ' +
Expand Down Expand Up @@ -301,6 +309,13 @@ export const options = {
'A JSON string with map of variables for the haste module system',
type: 'string',
},
ignoreProjects: {
description:
'Ignore the tests of the specified projects.' +
'Jest uses the attribute `displayName` in the configuration to identify each project.',
string: true,
type: 'array',
},
init: {
description: 'Generate a basic configuration file',
type: 'boolean',
Expand Down Expand Up @@ -502,7 +517,7 @@ export const options = {
},
selectProjects: {
description:
'Run only the tests of the specified projects.' +
'Run the tests of the specified projects.' +
'Jest uses the attribute `displayName` in the configuration to identify each project.',
string: true,
type: 'array',
Expand Down Expand Up @@ -695,4 +710,4 @@ export const options = {
'--no-watchman.',
type: 'boolean',
},
} as const;
};
16 changes: 9 additions & 7 deletions packages/jest-core/src/cli/index.ts
Expand Up @@ -71,16 +71,18 @@ export async function runCLI(
exit(0);
}

let configsOfProjectsToRun = configs;
if (argv.selectProjects) {
const namesMissingWarning = getProjectNamesMissingWarning(configs);
const configsOfProjectsToRun = getConfigsOfProjectsToRun(configs, {
ignoreProjects: argv.ignoreProjects,
selectProjects: argv.selectProjects,
});
if (argv.selectProjects || argv.ignoreProjects) {
const namesMissingWarning = getProjectNamesMissingWarning(configs, {
ignoreProjects: argv.ignoreProjects,
selectProjects: argv.selectProjects,
});
if (namesMissingWarning) {
outputStream.write(namesMissingWarning);
}
configsOfProjectsToRun = getConfigsOfProjectsToRun(
argv.selectProjects,
configs,
);
outputStream.write(getSelectProjectsMessage(configsOfProjectsToRun));
}

Expand Down
32 changes: 29 additions & 3 deletions packages/jest-core/src/getConfigsOfProjectsToRun.ts
Expand Up @@ -9,12 +9,38 @@ import type {Config} from '@jest/types';
import getProjectDisplayName from './getProjectDisplayName';

export default function getConfigsOfProjectsToRun(
namesOfProjectsToRun: Array<string>,
projectConfigs: Array<Config.ProjectConfig>,
opts: {
ignoreProjects: Array<string> | undefined;
selectProjects: Array<string> | undefined;
},
): Array<Config.ProjectConfig> {
const setOfProjectsToRun = new Set<string>(namesOfProjectsToRun);
const projectFilter = createProjectFilter(opts);
return projectConfigs.filter(config => {
const name = getProjectDisplayName(config);
return name && setOfProjectsToRun.has(name);
return projectFilter(name);
});
}

function createProjectFilter(opts: {
ignoreProjects: Array<string> | undefined;
selectProjects: Array<string> | undefined;
}) {
const {selectProjects, ignoreProjects} = opts;

const always = () => true;

const selected = selectProjects
? (name: string | undefined) => name && selectProjects.includes(name)
: always;

const notIgnore = ignoreProjects
? (name: string | undefined) => !name || !ignoreProjects.includes(name)
: always;

function test(name: string | undefined) {
return selected(name) && notIgnore(name);
}

return test;
}
13 changes: 12 additions & 1 deletion packages/jest-core/src/getProjectNamesMissingWarning.ts
Expand Up @@ -11,15 +11,26 @@ import getProjectDisplayName from './getProjectDisplayName';

export default function getProjectNamesMissingWarning(
projectConfigs: Array<Config.ProjectConfig>,
opts: {
ignoreProjects: Array<string> | undefined;
selectProjects: Array<string> | undefined;
},
): string | undefined {
const numberOfProjectsWithoutAName = projectConfigs.filter(
config => !getProjectDisplayName(config),
).length;
if (numberOfProjectsWithoutAName === 0) {
return undefined;
}
const args: Array<string> = [];
if (opts.selectProjects) {
args.push('--selectProjects');
}
if (opts.ignoreProjects) {
args.push('--ignoreProjects');
}
return chalk.yellow(
`You provided values for --selectProjects but ${
`You provided values for ${args.join(' and ')} but ${
numberOfProjectsWithoutAName === 1
? 'a project does not have a name'
: `${numberOfProjectsWithoutAName} projects do not have a name`
Expand Down
1 change: 1 addition & 0 deletions packages/jest-types/src/Config.ts
Expand Up @@ -468,6 +468,7 @@ export type Argv = Arguments<
roots: Array<string>;
runInBand: boolean;
selectProjects: Array<string>;
ignoreProjects: Array<string>;
F3n67u marked this conversation as resolved.
Show resolved Hide resolved
setupFiles: Array<string>;
setupFilesAfterEnv: Array<string>;
shard: string;
Expand Down