diff --git a/CHANGELOG.md b/CHANGELOG.md index 460ec7669bbd..b7fc6aa8f62c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - `[babel-plugin-jest-hoist]` Expand list of whitelisted globals in global mocks ([#8429](https://github.com/facebook/jest/pull/8429) +- `[jest-core]` Make watch plugin initialization errors look nice ([#8422](https://github.com/facebook/jest/pull/8422)) ### Chore & Maintenance diff --git a/packages/jest-cli/src/cli/index.ts b/packages/jest-cli/src/cli/index.ts index 800899896039..ea73a76292bd 100644 --- a/packages/jest-cli/src/cli/index.ts +++ b/packages/jest-cli/src/cli/index.ts @@ -36,7 +36,12 @@ export async function run(maybeArgv?: Array, project?: Config.Path) { } catch (error) { clearLine(process.stderr); clearLine(process.stdout); - console.error(chalk.red(error.stack)); + if (error.stack) { + console.error(chalk.red(error.stack)); + } else { + console.error(chalk.red(error)); + } + exit(1); throw error; } diff --git a/packages/jest-core/package.json b/packages/jest-core/package.json index 8f7ca0c135f2..9f980af3c53d 100644 --- a/packages/jest-core/package.json +++ b/packages/jest-core/package.json @@ -31,6 +31,7 @@ "pirates": "^4.0.1", "realpath-native": "^1.1.0", "rimraf": "^2.5.4", + "slash": "^2.0.0", "strip-ansi": "^5.0.0" }, "devDependencies": { @@ -41,6 +42,7 @@ "@types/micromatch": "^3.1.0", "@types/p-each-series": "^1.0.0", "@types/rimraf": "^2.0.2", + "@types/slash": "^2.0.0", "@types/strip-ansi": "^3.0.0" }, "engines": { diff --git a/packages/jest-core/src/__tests__/__fixtures__/watch_plugin_throws.js b/packages/jest-core/src/__tests__/__fixtures__/watch_plugin_throws.js new file mode 100644 index 000000000000..076596c2837d --- /dev/null +++ b/packages/jest-core/src/__tests__/__fixtures__/watch_plugin_throws.js @@ -0,0 +1,3 @@ +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + +throw new Error('initialization error'); diff --git a/packages/jest-core/src/__tests__/__snapshots__/watch.test.js.snap b/packages/jest-core/src/__tests__/__snapshots__/watch.test.js.snap index 85d1828379c5..be0c72787eaa 100644 --- a/packages/jest-core/src/__tests__/__snapshots__/watch.test.js.snap +++ b/packages/jest-core/src/__tests__/__snapshots__/watch.test.js.snap @@ -50,6 +50,17 @@ Watch Usage ] `; +exports[`Watch mode flows makes watch plugin initialization errors look nice 1`] = ` +[Error: Failed to initialize watch plugin "packages/jest-core/src/__tests__/__fixtures__/watch_plugin_throws": + + ● Test suite failed to run + + initialization error + + at Object. (__fixtures__/watch_plugin_throws.js:3:7) +] +`; + exports[`Watch mode flows shows prompts for WatchPlugins in alphabetical order 1`] = ` Array [ Array [ diff --git a/packages/jest-core/src/__tests__/watch.test.js b/packages/jest-core/src/__tests__/watch.test.js index 3d480a29fc23..358446807c64 100644 --- a/packages/jest-core/src/__tests__/watch.test.js +++ b/packages/jest-core/src/__tests__/watch.test.js @@ -108,7 +108,12 @@ describe('Watch mode flows', () => { isInteractive = true; jest.doMock('jest-util/build/isInteractive', () => isInteractive); watch = require('../watch').default; - const config = {roots: [], testPathIgnorePatterns: [], testRegex: []}; + const config = { + rootDir: __dirname, + roots: [], + testPathIgnorePatterns: [], + testRegex: [], + }; pipe = {write: jest.fn()}; globalConfig = {watch: true}; hasteMapInstances = [{on: () => {}}]; @@ -571,6 +576,24 @@ describe('Watch mode flows', () => { }); }); + it('makes watch plugin initialization errors look nice', async () => { + const pluginPath = `${__dirname}/__fixtures__/watch_plugin_throws`; + + await expect( + watch( + { + ...globalConfig, + rootDir: __dirname, + watchPlugins: [{config: {}, path: pluginPath}], + }, + contexts, + pipe, + hasteMapInstances, + stdin, + ), + ).rejects.toMatchSnapshot(); + }); + it.each` ok | option ${'✔︎'} | ${'bail'} diff --git a/packages/jest-core/src/watch.ts b/packages/jest-core/src/watch.ts index 2efe8e05b165..5b97d1623709 100644 --- a/packages/jest-core/src/watch.ts +++ b/packages/jest-core/src/watch.ts @@ -5,9 +5,11 @@ * LICENSE file in the root directory of this source tree. */ +import path from 'path'; import ansiEscapes from 'ansi-escapes'; import chalk from 'chalk'; import exit from 'exit'; +import slash from 'slash'; import HasteMap, {HasteChangeEvent} from 'jest-haste-map'; import {formatExecError} from 'jest-message-util'; import {isInteractive, preRunMessage, specialChars} from 'jest-util'; @@ -171,12 +173,25 @@ export default function watch( } for (const pluginWithConfig of globalConfig.watchPlugins) { - const ThirdPartyPlugin = require(pluginWithConfig.path); - const plugin: WatchPlugin = new ThirdPartyPlugin({ - config: pluginWithConfig.config, - stdin, - stdout: outputStream, - }); + let plugin: WatchPlugin; + try { + const ThirdPartyPlugin = require(pluginWithConfig.path); + plugin = new ThirdPartyPlugin({ + config: pluginWithConfig.config, + stdin, + stdout: outputStream, + }); + } catch (error) { + const errorWithContext = new Error( + `Failed to initialize watch plugin "${chalk.bold( + slash(path.relative(process.cwd(), pluginWithConfig.path)), + )}":\n\n${formatExecError(error, contexts[0].config, { + noStackTrace: false, + })}`, + ); + delete errorWithContext.stack; + return Promise.reject(errorWithContext); + } checkForConflicts(watchPluginKeys, plugin, globalConfig); const hookSubscriber = hooks.getSubscriber();