From 83e91279fde3ca87b4f02cb8a744b62a26c47d11 Mon Sep 17 00:00:00 2001
From: TrickyPi <530257315@qq.com>
Date: Mon, 28 Feb 2022 13:42:09 +0800
Subject: [PATCH] feat(jest-config): Throw an error instead of showing a
 warning if multiple configs are used

---
 CHANGELOG.md                                  |  1 +
 .../__snapshots__/multipleConfigs.ts.snap     | 22 ++++----
 e2e/__tests__/multipleConfigs.ts              | 16 ++----
 .../src/__tests__/resolveConfigPath.test.ts   | 53 ++++---------------
 packages/jest-config/src/index.ts             |  6 +--
 packages/jest-config/src/resolveConfigPath.ts | 44 ++++++++-------
 6 files changed, 48 insertions(+), 94 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1f622dc41e6..1a4dcedbd1ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 ### Features
 
+- `[jest-config]` Throw an error instead of showing a warning if multiple configs are used ([#12506](https://github.com/facebook/jest/pull/12506))
 - `[babel-jest]` Export `createTransformer` function ([#12399](https://github.com/facebook/jest/pull/12399))
 - `[expect]` Expose `AsymmetricMatchers`, `MatcherFunction` and `MatcherFunctionWithState` interfaces ([#12363](https://github.com/facebook/jest/pull/12363), [#12376](https://github.com/facebook/jest/pull/12376))
 - `[jest-circus, jest-jasmine2]` Allowed classes and functions as `describe` and `it`/`test` names ([#12484](https://github.com/facebook/jest/pull/12484))
diff --git a/e2e/__tests__/__snapshots__/multipleConfigs.ts.snap b/e2e/__tests__/__snapshots__/multipleConfigs.ts.snap
index 3805df0c73fb..f86f030a4ab4 100644
--- a/e2e/__tests__/__snapshots__/multipleConfigs.ts.snap
+++ b/e2e/__tests__/__snapshots__/multipleConfigs.ts.snap
@@ -1,7 +1,7 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`multiple configs will warn 1`] = `
-"● Multiple configurations found:
+exports[`multiple configs will throw error 1`] = `
+"Error: ● 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
@@ -12,14 +12,10 @@ exports[`multiple configs will warn 1`] = `
   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."
-`;
+    at resolveConfigPathByTraversing (<rootDir>/packages/jest-config/build/resolveConfigPath.js:166:11)
+    at resolveConfigPath (<rootDir>/packages/jest-config/build/resolveConfigPath.js:141:10)
+    at readConfig (<rootDir>/packages/jest-config/build/index.js:211:49)
+    at readConfigs (<rootDir>/packages/jest-config/build/index.js:403:32)
+    at runCLI (<rootDir>/packages/jest-core/build/cli/index.js:133:29)
+    at Object.run (<rootDir>/packages/jest-cli/build/cli/index.js:155:62)"
+`;
\ No newline at end of file
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'), '<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);
 });
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<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));
 
@@ -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 = <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([
@@ -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<ReadConfig> {
   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..80e90d2f852e 100644
--- a/packages/jest-config/src/resolveConfigPath.ts
+++ b/packages/jest-config/src/resolveConfigPath.ts
@@ -23,7 +23,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 +58,7 @@ export default function resolveConfigPath(
     absolutePath,
     pathToResolve,
     cwd,
-    skipMultipleConfigWarning,
+    skipMultipleConfigError,
   );
 }
 
@@ -66,7 +66,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 +77,8 @@ const resolveConfigPathByTraversing = (
     configFiles.push(packageJson);
   }
 
-  if (!skipMultipleConfigWarning && configFiles.length > 1) {
-    console.warn(makeMultipleConfigsWarning(configFiles));
+  if (!skipMultipleConfigError && configFiles.length > 1) {
+    throw new Error(makeMultipleConfigsErrorMessage(configFiles));
   }
 
   if (configFiles.length > 0 || packageJson) {
@@ -96,7 +96,7 @@ const resolveConfigPathByTraversing = (
     path.dirname(pathToResolve),
     initialPath,
     cwd,
-    skipMultipleConfigWarning,
+    skipMultipleConfigError,
   );
 };
 
@@ -137,20 +137,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>) =>
+  [
+    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');