diff --git a/CHANGELOG.md b/CHANGELOG.md index d1f622dc41e6..8b7e320792a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/e2e/__tests__/__snapshots__/multipleConfigs.ts.snap b/e2e/__tests__/__snapshots__/multipleConfigs.ts.snap index 3805df0c73fb..c58bb848c34e 100644 --- a/e2e/__tests__/__snapshots__/multipleConfigs.ts.snap +++ b/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: + * /e2e/multiple-configs/jest.config.js * /e2e/multiple-configs/jest.config.json * \`jest\` key in /e2e/multiple-configs/package.json @@ -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: <> -Ran all test suites." + https://jestjs.io/docs/configuration +" `; diff --git a/e2e/__tests__/multipleConfigs.ts b/e2e/__tests__/multipleConfigs.ts index 84644f94a59f..bf5a71a1dadb 100644 --- a/e2e/__tests__/multipleConfigs.ts +++ b/e2e/__tests__/multipleConfigs.ts @@ -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'), ''); - 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); }); diff --git a/packages/jest-config/src/__tests__/resolveConfigPath.test.ts b/packages/jest-config/src/__tests__/resolveConfigPath.test.ts index 96db06c9213b..167ab607bb31 100644 --- a/packages/jest-config/src/__tests__/resolveConfigPath.test.ts +++ b/packages/jest-config/src/__tests__/resolveConfigPath.test.ts @@ -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>; - - // 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)); @@ -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, @@ -81,7 +69,6 @@ describe.each(JEST_CONFIG_EXT_ORDER.slice(0))( writeFiles(DIR, {[relativePackageJsonPath]: ''}); - mockedConsoleWarn.mockClear(); // absolute expect( resolveConfigPath(path.dirname(absolutePackageJsonPath), DIR), @@ -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), @@ -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( @@ -146,8 +120,7 @@ const pickPairsWithSameOrder = (array: ReadonlyArray) => .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([ @@ -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}`, @@ -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); }); }, ); diff --git a/packages/jest-config/src/index.ts b/packages/jest-config/src/index.ts index 1146e84b11b5..1e1448bdf285 100644 --- a/packages/jest-config/src/index.ts +++ b/packages/jest-config/src/index.ts @@ -41,7 +41,7 @@ export async function readConfig( skipArgvConfigOption?: boolean, parentConfigDirname?: string | null, projectIndex = Infinity, - skipMultipleConfigWarning = false, + skipMultipleConfigError = false, ): Promise { let rawOptions: Config.InitialOptions; let configPath = null; @@ -78,7 +78,7 @@ export async function readConfig( configPath = resolveConfigPath( argv.config, process.cwd(), - skipMultipleConfigWarning, + skipMultipleConfigError, ); rawOptions = await readConfigFileAndSetRootDir(configPath); } else { @@ -86,7 +86,7 @@ export async function readConfig( configPath = resolveConfigPath( packageRootOrConfig, process.cwd(), - skipMultipleConfigWarning, + skipMultipleConfigError, ); rawOptions = await readConfigFileAndSetRootDir(configPath); } diff --git a/packages/jest-config/src/resolveConfigPath.ts b/packages/jest-config/src/resolveConfigPath.ts index e513340fb5dd..a8a28de68a28 100644 --- a/packages/jest-config/src/resolveConfigPath.ts +++ b/packages/jest-config/src/resolveConfigPath.ts @@ -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(); @@ -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}`); @@ -58,7 +60,7 @@ export default function resolveConfigPath( absolutePath, pathToResolve, cwd, - skipMultipleConfigWarning, + skipMultipleConfigError, ); } @@ -66,7 +68,7 @@ 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)), @@ -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) { @@ -96,7 +98,7 @@ const resolveConfigPathByTraversing = ( path.dirname(pathToResolve), initialPath, cwd, - skipMultipleConfigWarning, + skipMultipleConfigError, ); }; @@ -137,20 +139,18 @@ function extraIfPackageJson(configPath: string) { return ''; } -const makeMultipleConfigsWarning = (configPaths: Array) => - 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] => [ + `${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, +];