Skip to content

Commit

Permalink
feat(jest-config): Throw an error instead of showing a warning if mul…
Browse files Browse the repository at this point in the history
…tiple configs are used (#12510)
  • Loading branch information
TrickyPi committed Feb 28, 2022
1 parent e20c671 commit 49322b6
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 94 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@
- `[jest-config, @jest/types]` Add `ci` to `GlobalConfig` ([#12378](https://github.com/facebook/jest/pull/12378))
- `[jest-config]` [**BREAKING**] Rename `moduleLoader` to `runtime` ([#10817](https://github.com/facebook/jest/pull/10817))
- `[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-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))
Expand Down
17 changes: 4 additions & 13 deletions e2e/__tests__/__snapshots__/multipleConfigs.ts.snap
@@ -1,7 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`multiple configs will warn 1`] = `
exports[`multiple configs will throw error 1`] = `
"● Multiple configurations found:
* <rootDir>/e2e/multiple-configs/jest.config.js
* <rootDir>/e2e/multiple-configs/jest.config.json
* \`jest\` key in <rootDir>/e2e/multiple-configs/package.json
Expand All @@ -10,16 +11,6 @@ exports[`multiple configs will warn 1`] = `
Either remove unused config files or select one explicitly with \`--config\`.
Configuration Documentation:
https://jestjs.io/docs/configuration.html
PASS Config from js file __tests__/test.js
✓ dummy test"
`;
exports[`multiple configs will warn 2`] = `
"Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites."
https://jestjs.io/docs/configuration
"
`;
16 changes: 5 additions & 11 deletions e2e/__tests__/multipleConfigs.ts
Expand Up @@ -7,36 +7,30 @@

import * as path from 'path';
import slash = require('slash');
import {extractSummary} from '../Utils';
import runJest from '../runJest';

const MULTIPLE_CONFIGS_WARNING_TEXT = 'Multiple configurations found';

test('multiple configs will warn', () => {
test('multiple configs will throw error', () => {
const rootDir = slash(path.resolve(__dirname, '../..'));
const {exitCode, stderr} = runJest('multiple-configs', [], {
skipPkgJsonCheck: true,
});

expect(exitCode).toBe(0);
expect(exitCode).toBe(1);
expect(stderr).toContain(MULTIPLE_CONFIGS_WARNING_TEXT);

const cleanStdErr = stderr.replace(new RegExp(rootDir, 'g'), '<rootDir>');
const {rest, summary} = extractSummary(cleanStdErr);

expect(rest).toMatchSnapshot();
expect(summary).toMatchSnapshot();
expect(cleanStdErr).toMatchSnapshot();
});

test('multiple configs warning can be suppressed by using --config', () => {
const {exitCode, stderr} = runJest(
test('multiple configs error can be suppressed by using --config', () => {
const {exitCode} = runJest(
'multiple-configs',
['--config', 'jest.config.json'],
{
skipPkgJsonCheck: true,
},
);

expect(exitCode).toBe(0);
expect(stderr).not.toContain(MULTIPLE_CONFIGS_WARNING_TEXT);
});
53 changes: 9 additions & 44 deletions packages/jest-config/src/__tests__/resolveConfigPath.test.ts
Expand Up @@ -16,16 +16,6 @@ const ERROR_PATTERN = /Could not find a config file based on provided values/;
const NO_ROOT_DIR_ERROR_PATTERN = /Can't find a root directory/;
const MULTIPLE_CONFIGS_ERROR_PATTERN = /Multiple configurations found/;

const mockConsoleWarn = () => {
jest.spyOn(console, 'warn');
const mockedConsoleWarn = console.warn as jest.Mock<void, Array<any>>;

// We will mock console.warn because it would produce a lot of noise in the tests
mockedConsoleWarn.mockImplementation(() => {});

return mockedConsoleWarn;
};

beforeEach(() => cleanup(DIR));
afterEach(() => cleanup(DIR));

Expand Down Expand Up @@ -56,8 +46,6 @@ describe.each(JEST_CONFIG_EXT_ORDER.slice(0))(
});

test(`directory path with "${extension}"`, () => {
const mockedConsoleWarn = mockConsoleWarn();

const relativePackageJsonPath = 'a/b/c/package.json';
const absolutePackageJsonPath = path.resolve(
DIR,
Expand All @@ -81,7 +69,6 @@ describe.each(JEST_CONFIG_EXT_ORDER.slice(0))(

writeFiles(DIR, {[relativePackageJsonPath]: ''});

mockedConsoleWarn.mockClear();
// absolute
expect(
resolveConfigPath(path.dirname(absolutePackageJsonPath), DIR),
Expand All @@ -91,12 +78,10 @@ describe.each(JEST_CONFIG_EXT_ORDER.slice(0))(
expect(
resolveConfigPath(path.dirname(relativePackageJsonPath), DIR),
).toBe(absolutePackageJsonPath);
expect(mockedConsoleWarn).not.toBeCalled();

// jest.config.js takes precedence
writeFiles(DIR, {[relativeJestConfigPath]: ''});

mockedConsoleWarn.mockClear();
// absolute
expect(
resolveConfigPath(path.dirname(absolutePackageJsonPath), DIR),
Expand All @@ -106,30 +91,19 @@ describe.each(JEST_CONFIG_EXT_ORDER.slice(0))(
expect(
resolveConfigPath(path.dirname(relativePackageJsonPath), DIR),
).toBe(absoluteJestConfigPath);
expect(mockedConsoleWarn).not.toBeCalled();

// jest.config.js and package.json with 'jest' cannot be used together
writeFiles(DIR, {[relativePackageJsonPath]: JSON.stringify({jest: {}})});

// absolute
mockedConsoleWarn.mockClear();
expect(
expect(() =>
resolveConfigPath(path.dirname(absolutePackageJsonPath), DIR),
).toBe(absoluteJestConfigPath);
expect(mockedConsoleWarn).toBeCalledTimes(1);
expect(mockedConsoleWarn.mock.calls[0].join()).toMatch(
MULTIPLE_CONFIGS_ERROR_PATTERN,
);
).toThrowError(MULTIPLE_CONFIGS_ERROR_PATTERN);

// relative
mockedConsoleWarn.mockClear();
expect(
expect(() =>
resolveConfigPath(path.dirname(relativePackageJsonPath), DIR),
).toBe(absoluteJestConfigPath);
expect(mockedConsoleWarn).toBeCalledTimes(1);
expect(mockedConsoleWarn.mock.calls[0].join()).toMatch(
MULTIPLE_CONFIGS_ERROR_PATTERN,
);
).toThrowError(MULTIPLE_CONFIGS_ERROR_PATTERN);

expect(() => {
resolveConfigPath(
Expand All @@ -146,8 +120,7 @@ const pickPairsWithSameOrder = <T>(array: ReadonlyArray<T>) =>
.map((value1, idx, arr) =>
arr.slice(idx + 1).map(value2 => [value1, value2]),
)
// TODO: use .flat() when we drop Node 10
.reduce((acc, val) => acc.concat(val), []);
.flat();

test('pickPairsWithSameOrder', () => {
expect(pickPairsWithSameOrder([1, 2, 3])).toStrictEqual([
Expand All @@ -158,11 +131,9 @@ test('pickPairsWithSameOrder', () => {
});

describe.each(pickPairsWithSameOrder(JEST_CONFIG_EXT_ORDER))(
'Using multiple configs shows warning',
'Using multiple configs shows error',
(extension1, extension2) => {
test(`Using jest.config${extension1} and jest.config${extension2} shows warning`, () => {
const mockedConsoleWarn = mockConsoleWarn();

test(`Using jest.config${extension1} and jest.config${extension2} shows error`, () => {
const relativeJestConfigPaths = [
`a/b/c/jest.config${extension1}`,
`a/b/c/jest.config${extension2}`,
Expand All @@ -173,15 +144,9 @@ describe.each(pickPairsWithSameOrder(JEST_CONFIG_EXT_ORDER))(
[relativeJestConfigPaths[1]]: '',
});

// multiple configs here, should print warning
mockedConsoleWarn.mockClear();
expect(
expect(() =>
resolveConfigPath(path.dirname(relativeJestConfigPaths[0]), DIR),
).toBe(path.resolve(DIR, relativeJestConfigPaths[0]));
expect(mockedConsoleWarn).toBeCalledTimes(1);
expect(mockedConsoleWarn.mock.calls[0].join()).toMatch(
MULTIPLE_CONFIGS_ERROR_PATTERN,
);
).toThrowError(MULTIPLE_CONFIGS_ERROR_PATTERN);
});
},
);
6 changes: 3 additions & 3 deletions packages/jest-config/src/index.ts
Expand Up @@ -41,7 +41,7 @@ export async function readConfig(
skipArgvConfigOption?: boolean,
parentConfigDirname?: string | null,
projectIndex = Infinity,
skipMultipleConfigWarning = false,
skipMultipleConfigError = false,
): Promise<ReadConfig> {
let rawOptions: Config.InitialOptions;
let configPath = null;
Expand Down Expand Up @@ -78,15 +78,15 @@ export async function readConfig(
configPath = resolveConfigPath(
argv.config,
process.cwd(),
skipMultipleConfigWarning,
skipMultipleConfigError,
);
rawOptions = await readConfigFileAndSetRootDir(configPath);
} else {
// Otherwise just try to find config in the current rootDir.
configPath = resolveConfigPath(
packageRootOrConfig,
process.cwd(),
skipMultipleConfigWarning,
skipMultipleConfigError,
);
rawOptions = await readConfigFileAndSetRootDir(configPath);
}
Expand Down
46 changes: 23 additions & 23 deletions packages/jest-config/src/resolveConfigPath.ts
Expand Up @@ -9,11 +9,13 @@ import * as path from 'path';
import chalk = require('chalk');
import * as fs from 'graceful-fs';
import slash = require('slash');
import {ValidationError} from 'jest-validate';
import {
JEST_CONFIG_BASE_NAME,
JEST_CONFIG_EXT_ORDER,
PACKAGE_JSON,
} from './constants';
import {BULLET, DOCUMENTATION_NOTE} from './utils';

const isFile = (filePath: string) =>
fs.existsSync(filePath) && !fs.lstatSync(filePath).isDirectory();
Expand All @@ -23,7 +25,7 @@ const getConfigFilename = (ext: string) => JEST_CONFIG_BASE_NAME + ext;
export default function resolveConfigPath(
pathToResolve: string,
cwd: string,
skipMultipleConfigWarning = false,
skipMultipleConfigError = false,
): string {
if (!path.isAbsolute(cwd)) {
throw new Error(`"cwd" must be an absolute path. cwd: ${cwd}`);
Expand Down Expand Up @@ -58,15 +60,15 @@ export default function resolveConfigPath(
absolutePath,
pathToResolve,
cwd,
skipMultipleConfigWarning,
skipMultipleConfigError,
);
}

const resolveConfigPathByTraversing = (
pathToResolve: string,
initialPath: string,
cwd: string,
skipMultipleConfigWarning: boolean,
skipMultipleConfigError: boolean,
): string => {
const configFiles = JEST_CONFIG_EXT_ORDER.map(ext =>
path.resolve(pathToResolve, getConfigFilename(ext)),
Expand All @@ -77,8 +79,8 @@ const resolveConfigPathByTraversing = (
configFiles.push(packageJson);
}

if (!skipMultipleConfigWarning && configFiles.length > 1) {
console.warn(makeMultipleConfigsWarning(configFiles));
if (!skipMultipleConfigError && configFiles.length > 1) {
throw new ValidationError(...makeMultipleConfigsErrorMessage(configFiles));
}

if (configFiles.length > 0 || packageJson) {
Expand All @@ -96,7 +98,7 @@ const resolveConfigPathByTraversing = (
path.dirname(pathToResolve),
initialPath,
cwd,
skipMultipleConfigWarning,
skipMultipleConfigError,
);
};

Expand Down Expand Up @@ -137,20 +139,18 @@ function extraIfPackageJson(configPath: string) {
return '';
}

const makeMultipleConfigsWarning = (configPaths: Array<string>) =>
chalk.yellow(
[
chalk.bold('\u25cf Multiple configurations found:'),
...configPaths.map(
configPath =>
` * ${extraIfPackageJson(configPath)}${slash(configPath)}`,
),
'',
' Implicit config resolution does not allow multiple configuration files.',
' Either remove unused config files or select one explicitly with `--config`.',
'',
' Configuration Documentation:',
' https://jestjs.io/docs/configuration.html',
'',
].join('\n'),
);
const makeMultipleConfigsErrorMessage = (
configPaths: Array<string>,
): [string, string, string] => [
`${BULLET}${chalk.bold('Multiple configurations found')}`,
[
...configPaths.map(
configPath =>
` * ${extraIfPackageJson(configPath)}${slash(configPath)}`,
),
'',
' Implicit config resolution does not allow multiple configuration files.',
' Either remove unused config files or select one explicitly with `--config`.',
].join('\n'),
DOCUMENTATION_NOTE,
];

0 comments on commit 49322b6

Please sign in to comment.