Skip to content

Commit

Permalink
Add --ignoreProjects CLI argument to ignore test suites by project …
Browse files Browse the repository at this point in the history
…name (#12620)
  • Loading branch information
F3n67u committed Apr 4, 2022
1 parent f88b7db commit eb2146f
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,7 @@
- `[jest-config]` [**BREAKING**] Rename `extraGlobals` to `sandboxInjectedGlobals` ([#10817](https://github.com/facebook/jest/pull/10817))
- `[jest-config]` [**BREAKING**] Throw an error instead of showing a warning if multiple configs are used ([#12510](https://github.com/facebook/jest/pull/12510))
- `[jest-core]` Pass project config to `globalSetup`/`globalTeardown` function as second argument ([#12440](https://github.com/facebook/jest/pull/12440))
- `[jest-cli, jest-core]` Add `--ignoreProjects` CLI argument to ignore test suites by project name ([#12620](https://github.com/facebook/jest/pull/12620))
- `[jest-environment-jsdom]` [**BREAKING**] Upgrade jsdom to 19.0.0 ([#12290](https://github.com/facebook/jest/pull/12290))
- `[jest-environment-jsdom]` [**BREAKING**] Add default `browser` condition to `exportConditions` for `jsdom` environment ([#11924](https://github.com/facebook/jest/pull/11924))
- `[jest-environment-jsdom]` [**BREAKING**] Pass global config to Jest environment constructor for `jsdom` environment ([#12461](https://github.com/facebook/jest/pull/12461))
Expand Down
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.

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

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
153 changes: 153 additions & 0 deletions e2e/__tests__/selectProjects.test.ts
Expand Up @@ -111,6 +111,131 @@ 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);
});
it.skip('prints that no project was found', () => {
expect(result.stdout).toMatch(
/^You provided values for --ignoreProjects, but no projects were found matching the selection/,
);
});
});

describe('when Jest is started with `--selectProjects first-project second-project --ignoreProjects first-project` ', () => {
let result: RunJestJsonResult;
beforeAll(() => {
result = runWithJson('select-projects', [
'--selectProjects',
'first-project',
'second-project',
'--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 `--selectProjects first-project --ignoreProjects first-project` ', () => {
let result: RunJestResult;
beforeAll(() => {
result = run('select-projects', [
'--selectProjects',
'first-project',
'--ignoreProjects',
'first-project',
]);
});
it('fails', () => {
expect(result).toHaveProperty('failed', true);
});
it.skip('prints that no project was found', () => {
expect(result.stdout).toMatch(
/^You provided values for --selectProjects and --ignoreProjects, 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 +310,32 @@ 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: <unnamed 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;
};
21 changes: 14 additions & 7 deletions packages/jest-core/src/cli/index.ts
Expand Up @@ -71,17 +71,24 @@ 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, {
ignoreProjects: argv.ignoreProjects,
selectProjects: argv.selectProjects,
}),
);
outputStream.write(getSelectProjectsMessage(configsOfProjectsToRun));
}

await _run10000(
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

0 comments on commit eb2146f

Please sign in to comment.