Skip to content

Commit d78baab

Browse files
authored
feat(config): add read initial options helper (#13356)
1 parent 8219820 commit d78baab

File tree

14 files changed

+352
-48
lines changed

14 files changed

+352
-48
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### Features
44

5+
- `[jest-config]` Add `readInitialConfig` utility function ([#13356](https://github.com/facebook/jest/pull/13356))
56
- `[jest-core]` Enable testResultsProcessor to be async ([#13343](https://github.com/facebook/jest/pull/13343))
67
- `[expect, @jest/expect-utils]` Allow `isA` utility to take a type argument ([#13355](https://github.com/facebook/jest/pull/13355))
78

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
import path = require('path');
8+
import execa = require('execa');
9+
import type {ReadJestConfigOptions, readInitialOptions} from 'jest-config';
10+
11+
function resolveFixture(...pathSegments: Array<string>) {
12+
return path.resolve(__dirname, '..', 'read-initial-options', ...pathSegments);
13+
}
14+
15+
interface ProxyReadJestConfigOptions extends ReadJestConfigOptions {
16+
cwd?: string;
17+
}
18+
19+
/**
20+
* These e2e tests are running via a child process, because we're running in a VM and are not allowed to `import` directly
21+
* It also represents a more real-world example of how to run.
22+
*/
23+
async function proxyReadInitialOptions(
24+
configFile: string | undefined,
25+
options: ProxyReadJestConfigOptions,
26+
): ReturnType<typeof readInitialOptions> {
27+
const {stdout} = await execa(
28+
'node',
29+
[
30+
require.resolve('../read-initial-options/readOptions.js'),
31+
configFile ?? '',
32+
JSON.stringify(options),
33+
],
34+
{cwd: options?.cwd},
35+
);
36+
return JSON.parse(stdout);
37+
}
38+
39+
describe('readInitialOptions', () => {
40+
test('should read from the cwd by default', async () => {
41+
const configFile = resolveFixture('js-config', 'jest.config.js');
42+
const rootDir = resolveFixture('js-config');
43+
const {config, configPath} = await proxyReadInitialOptions(undefined, {
44+
cwd: rootDir,
45+
});
46+
expect(config).toEqual({jestConfig: 'jest.config.js', rootDir});
47+
expect(configPath).toEqual(configFile);
48+
});
49+
test('should read a jest.config.js file', async () => {
50+
const configFile = resolveFixture('js-config', 'jest.config.js');
51+
const rootDir = resolveFixture('js-config');
52+
const {config, configPath} = await proxyReadInitialOptions(undefined, {
53+
cwd: rootDir,
54+
});
55+
expect(config).toEqual({jestConfig: 'jest.config.js', rootDir});
56+
expect(configPath).toEqual(configFile);
57+
});
58+
test('should read a package.json file', async () => {
59+
const configFile = resolveFixture('pkg-config', 'package.json');
60+
const rootDir = resolveFixture('pkg-config');
61+
const {config, configPath} = await proxyReadInitialOptions(undefined, {
62+
cwd: rootDir,
63+
});
64+
expect(config).toEqual({jestConfig: 'package.json', rootDir});
65+
expect(configPath).toEqual(configFile);
66+
});
67+
test('should read a jest.config.ts file', async () => {
68+
const configFile = resolveFixture('ts-config', 'jest.config.ts');
69+
const rootDir = resolveFixture('ts-config');
70+
const {config, configPath} = await proxyReadInitialOptions(undefined, {
71+
cwd: rootDir,
72+
});
73+
expect(config).toEqual({jestConfig: 'jest.config.ts', rootDir});
74+
expect(configPath).toEqual(configFile);
75+
});
76+
test('should read a jest.config.mjs file', async () => {
77+
const configFile = resolveFixture('mjs-config', 'jest.config.mjs');
78+
const rootDir = resolveFixture('mjs-config');
79+
const {config, configPath} = await proxyReadInitialOptions(undefined, {
80+
cwd: rootDir,
81+
});
82+
expect(config).toEqual({jestConfig: 'jest.config.mjs', rootDir});
83+
expect(configPath).toEqual(configFile);
84+
});
85+
test('should read a jest.config.json file', async () => {
86+
const configFile = resolveFixture('json-config', 'jest.config.json');
87+
const rootDir = resolveFixture('json-config');
88+
const {config, configPath} = await proxyReadInitialOptions(undefined, {
89+
cwd: rootDir,
90+
});
91+
expect(config).toEqual({jestConfig: 'jest.config.json', rootDir});
92+
expect(configPath).toEqual(configFile);
93+
});
94+
test('should read a jest config exporting an async function', async () => {
95+
const configFile = resolveFixture('async-config', 'jest.config.js');
96+
const rootDir = resolveFixture('async-config');
97+
const {config, configPath} = await proxyReadInitialOptions(undefined, {
98+
cwd: rootDir,
99+
});
100+
expect(config).toEqual({jestConfig: 'async-config', rootDir});
101+
expect(configPath).toEqual(configFile);
102+
});
103+
104+
test('should be able to skip config reading, instead read from cwd', async () => {
105+
const expectedConfigFile = resolveFixture(
106+
'json-config',
107+
'jest.config.json',
108+
);
109+
const {config, configPath} = await proxyReadInitialOptions(
110+
resolveFixture('js-config', 'jest.config.js'),
111+
{
112+
cwd: resolveFixture('json-config'),
113+
readFromCwd: true,
114+
},
115+
);
116+
117+
expect(config).toEqual({
118+
jestConfig: 'jest.config.json',
119+
rootDir: path.dirname(expectedConfigFile),
120+
});
121+
expect(configPath).toEqual(expectedConfigFile);
122+
});
123+
124+
test('should give an error when there are multiple config files', async () => {
125+
const cwd = resolveFixture('multiple-config-files');
126+
const error: Error = await proxyReadInitialOptions(undefined, {cwd}).catch(
127+
error => error,
128+
);
129+
expect(error.message).toContain('Multiple configurations found');
130+
expect(error.message).toContain('multiple-config-files/jest.config.js');
131+
expect(error.message).toContain('multiple-config-files/jest.config.json');
132+
});
133+
134+
test('should be able to ignore multiple config files error', async () => {
135+
const cwd = resolveFixture('multiple-config-files');
136+
const {config, configPath} = await proxyReadInitialOptions(undefined, {
137+
cwd,
138+
skipMultipleConfigError: true,
139+
});
140+
expect(config).toEqual({
141+
jestConfig: 'jest.config.js',
142+
rootDir: resolveFixture('multiple-config-files'),
143+
});
144+
expect(configPath).toEqual(
145+
resolveFixture('multiple-config-files', 'jest.config.js'),
146+
);
147+
});
148+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
module.exports = async function () {
8+
return {
9+
jestConfig: 'async-config',
10+
};
11+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
module.exports = {
8+
jestConfig: 'jest.config.js',
9+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"jestConfig": "jest.config.json"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
export default {
8+
jestConfig: 'jest.config.mjs',
9+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
module.exports = {
8+
jestConfig: 'jest.config.js',
9+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"jestConfig": "jest.config.json"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"jest": {
3+
"jestConfig": "package.json"
4+
}
5+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
const {readInitialOptions} = require('jest-config');
8+
async function readConfig() {
9+
let config = process.argv[2];
10+
let options = undefined;
11+
if (config === '') {
12+
config = undefined;
13+
}
14+
if (process.argv[3]) {
15+
options = JSON.parse(process.argv[3]);
16+
}
17+
console.log(JSON.stringify(await readInitialOptions(config, options)));
18+
}
19+
readConfig().catch(err => {
20+
console.error(err);
21+
process.exitCode = 1;
22+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
export default {
8+
jestConfig: 'jest.config.ts',
9+
};

packages/jest-config/src/__tests__/readConfig.test.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import {readConfig} from '../index';
1010
test('readConfig() throws when an object is passed without a file path', async () => {
1111
await expect(
1212
readConfig(
13-
// @ts-expect-error
14-
null /* argv */,
13+
{$0: '', _: []},
1514
{} /* packageRootOrConfig */,
1615
false /* skipArgvConfigOption */,
1716
null /* parentConfigPath */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
import type {Config} from '@jest/types';
8+
import {readInitialOptions} from '../index';
9+
10+
describe(readInitialOptions, () => {
11+
test('should be able to use serialized jest config', async () => {
12+
const inputConfig = {jestConfig: 'serialized'};
13+
const {config, configPath} = await readInitialOptions(
14+
JSON.stringify(inputConfig),
15+
);
16+
expect(config).toEqual({...inputConfig, rootDir: process.cwd()});
17+
expect(configPath).toBeNull();
18+
});
19+
20+
test('should allow deserialized options', async () => {
21+
const inputConfig = {jestConfig: 'deserialized'};
22+
const {config, configPath} = await readInitialOptions(undefined, {
23+
packageRootOrConfig: inputConfig as Config.InitialOptions,
24+
parentConfigDirname: process.cwd(),
25+
});
26+
expect(config).toEqual({...inputConfig, rootDir: process.cwd()});
27+
expect(configPath).toBeNull();
28+
});
29+
// Note: actual file reading is tested in e2e test
30+
});

0 commit comments

Comments
 (0)