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

Allow configuration object in projects array #5176

Merged
merged 10 commits into from
Dec 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
([#5154](https://github.com/facebook/jest/pull/5154))
* `[jest-jasmine2]` Support generator functions as specs.
([#5166](https://github.com/facebook/jest/pull/5166))
* `[jest-config]` Allow configuration objects inside `projects` array
([#5176](https://github.com/facebook/jest/pull/5176))

### Chore & Maintenance

Expand Down
51 changes: 50 additions & 1 deletion docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ Default: `undefined`
A preset that is used as a base for Jest's configuration. A preset should point
to an npm module that exports a `jest-preset.json` module on its top level.

### `projects` [array<string>]
### `projects` [array<string | ProjectConfig>]

Default: `undefined`

Expand All @@ -446,6 +446,27 @@ This example configuration will run Jest in the root directory as well as in
every folder in the examples directory. You can have an unlimited amount of
projects running in the same Jest instance.

The projects feature can also be used to run multiple configurations or multiple
[runners](#runner-string). For this purpose you can pass an array of
configuration objects. For example, to run both tests and ESLint (via
[jest-runner-eslint](https://github.com/jest-community/jest-runner-eslint)) in
the same invocation of Jest:

```json
{
"projects": [
{
"displayName": "test"
},
{
"displayName": "lint",
"runner": "jest-runner-eslint",
"testMatch": ["<rootDir>/**/*.js"]
}
]
}
```

### `clearMocks` [boolean]

Default: `false`
Expand Down Expand Up @@ -619,6 +640,34 @@ _Note: By default, `roots` has a single entry `<rootDir>` but there are cases
where you may want to have multiple roots within one project, for example
`roots: ["<rootDir>/src/", "<rootDir>/tests/"]`._

### `runner` [string]

##### available in Jest **21.0.0+**

Default: `"jest-runner"`

This option allows you to use a custom runner instead of Jest's default test
runner. Examples of runners include:

* [`jest-runner-eslint`](https://github.com/jest-community/jest-runner-eslint)
* [`jest-runner-mocha`](https://github.com/rogeliog/jest-runner-mocha)
* [`jest-runner-tsc`](https://github.com/azz/jest-runner-tsc)
* [`jest-runner-prettier`](https://github.com/keplersj/jest-runner-prettier)

To write a test-runner, export a class with which accepts `globalConfig` in the
constructor, and has a `runTests` method with the signature:

```ts
async runTests(
tests: Array<Test>,
watcher: TestWatcher,
onStart: OnTestStart,
onResult: OnTestSuccess,
onFailure: OnTestFailure,
options: TestRunnerOptions,
): Promise<void>
```

### `setupFiles` [array]

Default: `[]`
Expand Down
46 changes: 46 additions & 0 deletions integration_tests/__tests__/multi_project_runner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,52 @@ test('"No tests found" message for projects', () => {
);
});

test('objects in project configuration', () => {
writeFiles(DIR, {
'__tests__/file1.test.js': `
test('foo', () => {});
`,
'__tests__/file2.test.js': `
test('bar', () => {});
`,
'jest.config.js': `module.exports = {
projects: [
{ testMatch: ['<rootDir>/__tests__/file1.test.js'] },
{ testMatch: ['<rootDir>/__tests__/file2.test.js'] },
]
};`,
'package.json': '{}',
});

const {stdout, stderr, status} = runJest(DIR);
expect(stderr).toContain('Test Suites: 2 passed, 2 total');
expect(stderr).toContain('PASS __tests__/file1.test.js');
expect(stderr).toContain('PASS __tests__/file2.test.js');
expect(stderr).toContain('Ran all test suites in 2 projects.');
expect(stdout).toEqual('');
expect(status).toEqual(0);
});

test('allows a single project', () => {
writeFiles(DIR, {
'__tests__/file1.test.js': `
test('foo', () => {});
`,
'jest.config.js': `module.exports = {
projects: [
{ testMatch: ['<rootDir>/__tests__/file1.test.js'] },
]
};`,
'package.json': '{}',
});

const {stdout, stderr, status} = runJest(DIR);
expect(stderr).toContain('PASS __tests__/file1.test.js');
expect(stderr).toContain('Test Suites: 1 passed, 1 total');
expect(stdout).toEqual('');
expect(status).toEqual(0);
});

test('resolves projects and their <rootDir> properly', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
Expand Down
10 changes: 8 additions & 2 deletions packages/jest-cli/src/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ const ensureNoDuplicateConfigs = (parsedConfigs, projects) => {
});
throw new Error(message);
}
configPathSet.add(configPath);
if (configPath !== null) {
configPathSet.add(configPath);
}
}
};

Expand All @@ -225,9 +227,11 @@ const getConfigs = (
let hasDeprecationWarnings;
let configs: Array<ProjectConfig> = [];
let projects = projectsFromCLIArgs;
let configPath: ?Path;

if (projectsFromCLIArgs.length === 1) {
const parsedConfig = readConfig(argv, projects[0]);
configPath = parsedConfig.configPath;

if (parsedConfig.globalConfig.projects) {
// If this was a single project, and its config has `projects`
Expand All @@ -246,7 +250,9 @@ const getConfigs = (
}

if (projects.length > 1) {
const parsedConfigs = projects.map(root => readConfig(argv, root, true));
const parsedConfigs = projects.map(root =>
readConfig(argv, root, true, configPath),
);
ensureNoDuplicateConfigs(parsedConfigs, projects);
configs = parsedConfigs.map(({projectConfig}) => projectConfig);
if (!hasDeprecationWarnings) {
Expand Down
14 changes: 14 additions & 0 deletions packages/jest-config/src/__tests__/read_config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {readConfig} from '../index';

test('readConfig() throws when an object is passed without a file path', () => {
expect(() => {
readConfig(
null /* argv */,
{} /* packageRootOrConfig */,
false /* skipArgvConfigOption */,
null /* parentConfigPath */,
);
}).toThrowError(
'Jest: Cannot use configuration as an object without a file path',
);
});
46 changes: 29 additions & 17 deletions packages/jest-config/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,52 @@
*/

import type {Argv} from 'types/Argv';
import type {GlobalConfig, Path, ProjectConfig} from 'types/Config';
import type {
GlobalConfig,
InitialOptions,
Path,
ProjectConfig,
} from 'types/Config';

import {getTestEnvironment, isJSONString} from './utils';
import path from 'path';
import {isJSONString} from './utils';
import normalize from './normalize';
import resolveConfigPath from './resolve_config_path';
import readConfigFileAndSetRootDir from './read_config_file_and_set_root_dir';

function readConfig(
export {getTestEnvironment, isJSONString} from './utils';
export {default as normalize} from './normalize';

export function readConfig(
argv: Argv,
packageRoot: string,
packageRootOrConfig: Path | InitialOptions,
// Whether it needs to look into `--config` arg passed to CLI.
// It only used to read initial config. If the initial config contains
// `project` property, we don't want to read `--config` value and rather
// read individual configs for every project.
skipArgvConfigOption?: boolean,
parentConfigPath: ?Path,
): {
configPath: ?Path,
globalConfig: GlobalConfig,
hasDeprecationWarnings: boolean,
projectConfig: ProjectConfig,
} {
let rawOptions;
let configPath;
let configPath = null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anywhere that requires a configPath to be set other than readConfigFileAndSetRootDir?


// A JSON string was passed to `--config` argument and we can parse it
// and use as is.
if (isJSONString(argv.config)) {
if (typeof packageRootOrConfig !== 'string') {
if (parentConfigPath) {
rawOptions = packageRootOrConfig;
rawOptions.rootDir = path.dirname(parentConfigPath);
} else {
throw new Error(
'Jest: Cannot use configuration as an object without a file path.',
);
}
} else if (isJSONString(argv.config)) {
// A JSON string was passed to `--config` argument and we can parse it
// and use as is.
let config;
try {
config = JSON.parse(argv.config);
Expand All @@ -45,7 +64,7 @@ function readConfig(
}

// NOTE: we might need to resolve this dir to an absolute path in the future
config.rootDir = config.rootDir || packageRoot;
config.rootDir = config.rootDir || packageRootOrConfig;
rawOptions = config;
// A string passed to `--config`, which is either a direct path to the config
// or a path to directory containing `package.json` or `jest.conf.js`
Expand All @@ -54,7 +73,7 @@ function readConfig(
rawOptions = readConfigFileAndSetRootDir(configPath);
} else {
// Otherwise just try to find config in the current rootDir.
configPath = resolveConfigPath(packageRoot, process.cwd());
configPath = resolveConfigPath(packageRootOrConfig, process.cwd());
rawOptions = readConfigFileAndSetRootDir(configPath);
}

Expand Down Expand Up @@ -166,10 +185,3 @@ const getConfigs = (
}),
};
};

module.exports = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this on purpose?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is, you export instead. Cool! 🙂

getTestEnvironment,
isJSONString,
normalize,
readConfig,
};
3 changes: 2 additions & 1 deletion packages/jest-config/src/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,8 @@ export default function normalize(options: InitialOptions, argv: Argv) {
// Project can be specified as globs. If a glob matches any files,
// We expand it to these paths. If not, we keep the original path
// for the future resolution.
const globMatches = glob.sync(project);
const globMatches =
typeof project === 'string' ? glob.sync(project) : [];
return projects.concat(globMatches.length ? globMatches : project);
}, []);
break;
Expand Down