From 0d4e3b711fb902e3cb08662576e354362d0f769d Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Tue, 26 Dec 2017 00:17:57 +1100 Subject: [PATCH 01/10] Allow configuration object in projects array --- .../__tests__/multi_project_runner.test.js | 25 +++++++++++++++++++ packages/jest-cli/src/cli/index.js | 10 ++++++-- packages/jest-config/src/index.js | 24 ++++++++++++------ packages/jest-config/src/normalize.js | 3 ++- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/integration_tests/__tests__/multi_project_runner.test.js b/integration_tests/__tests__/multi_project_runner.test.js index 3bbe28227842..6776b9d76f17 100644 --- a/integration_tests/__tests__/multi_project_runner.test.js +++ b/integration_tests/__tests__/multi_project_runner.test.js @@ -154,6 +154,31 @@ 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('foo', () => {}); + `, + 'jest.config.js': `module.exports = { + projects: [ + { testMatch: ['/__tests__/file1.test.js$'] }, + { testMatch: ['/__tests__/file2.test.js$'] }, + ] + };`, + 'package.json': '{}', + }); + + const {stdout, stderr} = runJest(DIR); + expect(stderr).toEqual(''); + expect(stdout).toContain( + ' 2 files checked across 2 projects. ' + + 'Run with `--verbose` for more details.', + ); +}); + test('resolves projects and their properly', () => { writeFiles(DIR, { '.watchmanconfig': '', diff --git a/packages/jest-cli/src/cli/index.js b/packages/jest-cli/src/cli/index.js index ea4755411219..f67b66033df9 100644 --- a/packages/jest-cli/src/cli/index.js +++ b/packages/jest-cli/src/cli/index.js @@ -199,7 +199,9 @@ const ensureNoDuplicateConfigs = (parsedConfigs, projects) => { }); throw new Error(message); } - configPathSet.add(configPath); + if (configPath !== null) { + configPathSet.add(configPath); + } } }; @@ -225,9 +227,11 @@ const getConfigs = ( let hasDeprecationWarnings; let configs: Array = []; 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` @@ -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) { diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index 7cd8eccbfce5..2c322ca6fbf8 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -17,12 +17,13 @@ import readConfigFileAndSetRootDir from './read_config_file_and_set_root_dir'; function readConfig( argv: Argv, - packageRoot: string, + packageRootOrConfig: Path | ProjectConfig, // 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, @@ -30,11 +31,20 @@ function readConfig( projectConfig: ProjectConfig, } { let rawOptions; - let configPath; + let configPath = null; - // 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 = 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); @@ -45,7 +55,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` @@ -54,7 +64,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); } diff --git a/packages/jest-config/src/normalize.js b/packages/jest-config/src/normalize.js index e7fba4434f83..7fcec43e6fbc 100644 --- a/packages/jest-config/src/normalize.js +++ b/packages/jest-config/src/normalize.js @@ -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; From 0a4cf9869b02156bd3aee1ba2fccb9c1a754d6fb Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Tue, 26 Dec 2017 00:23:51 +1100 Subject: [PATCH 02/10] Add changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f526e2ace31..4fe79a7283a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From 75615e6d3a564f2f51d5318218f84822e6434c8b Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Tue, 26 Dec 2017 00:30:11 +1100 Subject: [PATCH 03/10] Fix type of packageRootOrConfig argument --- packages/jest-config/src/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index 2c322ca6fbf8..a51c5ede6c57 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -8,7 +8,12 @@ */ 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 normalize from './normalize'; @@ -17,7 +22,7 @@ import readConfigFileAndSetRootDir from './read_config_file_and_set_root_dir'; function readConfig( argv: Argv, - packageRootOrConfig: Path | ProjectConfig, + 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 From fb2b4d4d7060070771f61c5fbe943c1af5a24c48 Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Tue, 26 Dec 2017 00:34:39 +1100 Subject: [PATCH 04/10] Move along, nothing to see here... --- packages/jest-cli/src/cli/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-cli/src/cli/index.js b/packages/jest-cli/src/cli/index.js index f67b66033df9..6c7b3f45eb92 100644 --- a/packages/jest-cli/src/cli/index.js +++ b/packages/jest-cli/src/cli/index.js @@ -251,7 +251,7 @@ const getConfigs = ( if (projects.length > 1) { const parsedConfigs = projects.map(root => - readConfig(argv, root, true, configPath) + readConfig(argv, root, true, configPath), ); ensureNoDuplicateConfigs(parsedConfigs, projects); configs = parsedConfigs.map(({projectConfig}) => projectConfig); From 50d01f7c95bdcac793ec00d224036b59cfe89160 Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Tue, 26 Dec 2017 00:38:28 +1100 Subject: [PATCH 05/10] Change error message --- packages/jest-config/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index a51c5ede6c57..11c9a19506ae 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -44,7 +44,7 @@ function readConfig( rawOptions.rootDir = parentConfigPath; } else { throw new Error( - 'jest: Cannot use configuration as an object without a file path', + 'Jest: Cannot use configuration as an object without a file path.', ); } } else if (isJSONString(argv.config)) { From b61f30ebcf477197be3011a35e405d23ea4fd50c Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Wed, 27 Dec 2017 23:48:28 +1100 Subject: [PATCH 06/10] Add unit test for readConfig() --- .../src/__tests__/read_config.test.js | 14 ++++++++++++++ packages/jest-config/src/index.js | 17 +++++++---------- 2 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 packages/jest-config/src/__tests__/read_config.test.js diff --git a/packages/jest-config/src/__tests__/read_config.test.js b/packages/jest-config/src/__tests__/read_config.test.js new file mode 100644 index 000000000000..e4d053ebad5e --- /dev/null +++ b/packages/jest-config/src/__tests__/read_config.test.js @@ -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', + ); +}); diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index 11c9a19506ae..4bcc1e7a3b04 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -15,12 +15,16 @@ import type { 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, packageRootOrConfig: Path | InitialOptions, // Whether it needs to look into `--config` arg passed to CLI. @@ -41,7 +45,7 @@ function readConfig( if (typeof packageRootOrConfig !== 'string') { if (parentConfigPath) { rawOptions = packageRootOrConfig; - rawOptions.rootDir = parentConfigPath; + rawOptions.rootDir = path.dirname(parentConfigPath); } else { throw new Error( 'Jest: Cannot use configuration as an object without a file path.', @@ -181,10 +185,3 @@ const getConfigs = ( }), }; }; - -module.exports = { - getTestEnvironment, - isJSONString, - normalize, - readConfig, -}; From b227bad3653ce226df94112846eaacd0d3f37122 Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Wed, 27 Dec 2017 23:50:53 +1100 Subject: [PATCH 07/10] Remove unneeded ? --- packages/jest-config/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index 4bcc1e7a3b04..739b6e69bbec 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -32,7 +32,7 @@ export function readConfig( // `project` property, we don't want to read `--config` value and rather // read individual configs for every project. skipArgvConfigOption?: boolean, - parentConfigPath?: ?Path, + parentConfigPath: ?Path, ): { configPath: ?Path, globalConfig: GlobalConfig, From 3e4c82511423a0271e77c26db6fb72e361f8d47f Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Thu, 28 Dec 2017 09:34:02 +1100 Subject: [PATCH 08/10] Update integration tests --- .../__tests__/multi_project_runner.test.js | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/integration_tests/__tests__/multi_project_runner.test.js b/integration_tests/__tests__/multi_project_runner.test.js index 6776b9d76f17..f0f880258251 100644 --- a/integration_tests/__tests__/multi_project_runner.test.js +++ b/integration_tests/__tests__/multi_project_runner.test.js @@ -160,23 +160,42 @@ test('objects in project configuration', () => { test('foo', () => {}); `, '__tests__/file2.test.js': ` + test('bar', () => {}); + `, + 'jest.config.js': `module.exports = { + projects: [ + { testMatch: ['/__tests__/file1.test.js'] }, + { testMatch: ['/__tests__/file2.test.js'] }, + ] + };`, + 'package.json': '{}', + }); + + const {stdout, stderr} = 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(''); +}); + +test('allows a single project', () => { + writeFiles(DIR, { + '__tests__/file1.test.js': ` test('foo', () => {}); `, 'jest.config.js': `module.exports = { projects: [ - { testMatch: ['/__tests__/file1.test.js$'] }, - { testMatch: ['/__tests__/file2.test.js$'] }, + { testMatch: ['/__tests__/file1.test.js'] }, ] };`, 'package.json': '{}', }); const {stdout, stderr} = runJest(DIR); - expect(stderr).toEqual(''); - expect(stdout).toContain( - ' 2 files checked across 2 projects. ' + - 'Run with `--verbose` for more details.', - ); + expect(stderr).toContain('PASS __tests__/file1.test.js'); + expect(stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(stdout).toEqual(''); }); test('resolves projects and their properly', () => { From d91e99964ab0198294eefee5845bdf64c17504f4 Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Thu, 28 Dec 2017 21:57:01 +1100 Subject: [PATCH 09/10] Add documentation for runner and projects objects --- docs/Configuration.md | 51 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index ad7c3d76d79f..d1b77e5c4b46 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -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] +### `projects` [array] Default: `undefined` @@ -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": ["/**/*.js"] + } + ] +} +``` + ### `clearMocks` [boolean] Default: `false` @@ -619,6 +640,34 @@ _Note: By default, `roots` has a single entry `` but there are cases where you may want to have multiple roots within one project, for example `roots: ["/src/", "/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, + watcher: TestWatcher, + onStart: OnTestStart, + onResult: OnTestSuccess, + onFailure: OnTestFailure, + options: TestRunnerOptions, +): Promise +``` + ### `setupFiles` [array] Default: `[]` From c9d5feceba1950b65ecc549becd8b300a06f960b Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Thu, 28 Dec 2017 22:03:58 +1100 Subject: [PATCH 10/10] Add status code checks --- integration_tests/__tests__/multi_project_runner.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/integration_tests/__tests__/multi_project_runner.test.js b/integration_tests/__tests__/multi_project_runner.test.js index f0f880258251..a4ffc3f837aa 100644 --- a/integration_tests/__tests__/multi_project_runner.test.js +++ b/integration_tests/__tests__/multi_project_runner.test.js @@ -171,12 +171,13 @@ test('objects in project configuration', () => { 'package.json': '{}', }); - const {stdout, stderr} = runJest(DIR); + 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', () => { @@ -192,10 +193,11 @@ test('allows a single project', () => { 'package.json': '{}', }); - const {stdout, stderr} = runJest(DIR); + 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 properly', () => {