diff --git a/CHANGELOG.md b/CHANGELOG.md index 940664135da9..a5ede8a994e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ ### Chore & Maintenance +- `[jest-util]` Add ErrorWithStack class ([#7067](https://github.com/facebook/jest/pull/7067)) - `[docs]` Document `--runTestsByPath` CLI parameter ([#7046](https://github.com/facebook/jest/pull/7046)) - `[docs]` Fix babel-core installation instructions ([#6745](https://github.com/facebook/jest/pull/6745)) - `[docs]` Explain how to rewrite assertions to avoid large irrelevant diff ([#6971](https://github.com/facebook/jest/pull/6971)) diff --git a/packages/jest-circus/src/index.js b/packages/jest-circus/src/index.js index c6463ce0cc1e..536a4c25a4ae 100644 --- a/packages/jest-circus/src/index.js +++ b/packages/jest-circus/src/index.js @@ -17,6 +17,7 @@ import type { TestName, } from 'types/Circus'; import {bind as bindEach} from 'jest-each'; +import {ErrorWithStack} from 'jest-util'; import {dispatch} from './state'; type THook = (fn: HookFn, timeout?: number) => void; @@ -44,10 +45,7 @@ const _addHook = (fn: HookFn, hookType: HookType, hookFn, timeout: ?number) => { throw new Error('Invalid first argument. It must be a callback function.'); } - const asyncError = new Error(); - if (Error.captureStackTrace) { - Error.captureStackTrace(asyncError, hookFn); - } + const asyncError = new ErrorWithStack(undefined, hookFn); dispatch({asyncError, fn, hookType, name: 'add_hook', timeout}); }; @@ -78,10 +76,7 @@ const test = (testName: TestName, fn: TestFn, timeout?: number) => { ); } - const asyncError = new Error(); - if (Error.captureStackTrace) { - Error.captureStackTrace(asyncError, test); - } + const asyncError = new ErrorWithStack(undefined, test); return dispatch({ asyncError, @@ -93,10 +88,7 @@ const test = (testName: TestName, fn: TestFn, timeout?: number) => { }; const it = test; test.skip = (testName: TestName, fn?: TestFn, timeout?: number) => { - const asyncError = new Error(); - if (Error.captureStackTrace) { - Error.captureStackTrace(asyncError, test); - } + const asyncError = new ErrorWithStack(undefined, test); return dispatch({ asyncError, @@ -108,10 +100,7 @@ test.skip = (testName: TestName, fn?: TestFn, timeout?: number) => { }); }; test.only = (testName: TestName, fn: TestFn, timeout?: number) => { - const asyncError = new Error(); - if (Error.captureStackTrace) { - Error.captureStackTrace(asyncError, test); - } + const asyncError = new ErrorWithStack(undefined, test); return dispatch({ asyncError, @@ -125,19 +114,13 @@ test.only = (testName: TestName, fn: TestFn, timeout?: number) => { test.todo = (testName: TestName, ...rest: Array) => { if (rest.length > 0 || typeof testName !== 'string') { - const e = new Error('Todo must be called with only a description.'); - - if (Error.captureStackTrace) { - Error.captureStackTrace(e, test.todo); - } - - throw e; + throw new ErrorWithStack( + 'Todo must be called with only a description.', + test.todo, + ); } - const asyncError = new Error(); - if (Error.captureStackTrace) { - Error.captureStackTrace(asyncError, test); - } + const asyncError = new ErrorWithStack(undefined, test); return dispatch({ asyncError, diff --git a/packages/jest-cli/src/collectHandles.js b/packages/jest-cli/src/collectHandles.js index 51e07c502aef..1cd22994fe2c 100644 --- a/packages/jest-cli/src/collectHandles.js +++ b/packages/jest-cli/src/collectHandles.js @@ -10,6 +10,7 @@ import type {ProjectConfig} from 'types/Config'; import {formatExecError} from 'jest-message-util'; +import {ErrorWithStack} from 'jest-util'; import stripAnsi from 'strip-ansi'; function stackIsFromUser(stack) { @@ -43,11 +44,7 @@ export default function collectHandles(): () => Array { if (type === 'PROMISE' || type === 'TIMERWRAP') { return; } - const error = new Error(type); - - if (Error.captureStackTrace) { - Error.captureStackTrace(error, initHook); - } + const error = new ErrorWithStack(type, initHook); if (stackIsFromUser(error.stack)) { activeHandles.set(asyncId, error); diff --git a/packages/jest-each/package.json b/packages/jest-each/package.json index bd9bcde1f642..b985159c7057 100644 --- a/packages/jest-each/package.json +++ b/packages/jest-each/package.json @@ -17,6 +17,7 @@ "license": "MIT", "dependencies": { "chalk": "^2.0.1", + "jest-util": "^23.4.0", "pretty-format": "^23.6.0" } } diff --git a/packages/jest-each/src/bind.js b/packages/jest-each/src/bind.js index 88d68c11cecd..9d78fc25dc81 100644 --- a/packages/jest-each/src/bind.js +++ b/packages/jest-each/src/bind.js @@ -10,6 +10,7 @@ import util from 'util'; import chalk from 'chalk'; import pretty from 'pretty-format'; +import {ErrorWithStack} from 'jest-util'; type Table = Array>; type PrettyArgs = { @@ -23,21 +24,13 @@ const SUPPORTED_PLACEHOLDERS = /%[sdifjoOp%]/g; const PRETTY_PLACEHOLDER = '%p'; const INDEX_PLACEHOLDER = '%#'; -const errorWithStack = (message, callsite) => { - const error = new Error(message); - if (Error.captureStackTrace) { - Error.captureStackTrace(error, callsite); - } - return error; -}; - export default (cb: Function, supportsDone: boolean = true) => (...args: any) => function eachBind(title: string, test: Function, timeout: number): void { if (args.length === 1) { const [tableArg] = args; if (!Array.isArray(tableArg)) { - const error = errorWithStack( + const error = new ErrorWithStack( '`.each` must be called with an Array or Tagged Template String.\n\n' + `Instead was called with: ${pretty(tableArg, { maxDepth: 1, @@ -70,7 +63,7 @@ export default (cb: Function, supportsDone: boolean = true) => (...args: any) => const missingData = data.length % keys.length; if (missingData > 0) { - const error = errorWithStack( + const error = new ErrorWithStack( 'Not enough arguments supplied for given headings:\n' + EXPECTED_COLOR(keys.join(' | ')) + '\n\n' + diff --git a/packages/jest-jasmine2/src/error_on_private.js b/packages/jest-jasmine2/src/error_on_private.js index eef265001822..b2e3aad0e4d3 100644 --- a/packages/jest-jasmine2/src/error_on_private.js +++ b/packages/jest-jasmine2/src/error_on_private.js @@ -8,6 +8,7 @@ */ import type {Global} from '../../../types/Global'; +import {ErrorWithStack} from 'jest-util'; // prettier-ignore const disabledGlobals = { @@ -60,9 +61,5 @@ export function installErrorOnPrivate(global: Global): void { } function throwAtFunction(message, fn) { - const e = new Error(message); - if (Error.captureStackTrace) { - Error.captureStackTrace(e, fn); - } - throw e; + throw new ErrorWithStack(message, fn); } diff --git a/packages/jest-jasmine2/src/jasmine/Env.js b/packages/jest-jasmine2/src/jasmine/Env.js index 0e94b1b46f5d..5b184b4efbb4 100644 --- a/packages/jest-jasmine2/src/jasmine/Env.js +++ b/packages/jest-jasmine2/src/jasmine/Env.js @@ -35,6 +35,7 @@ import queueRunner from '../queue_runner'; import treeProcessor from '../tree_processor'; import checkIsError from '../is_error'; import assertionErrorMessage from '../assert_support'; +import {ErrorWithStack} from 'jest-util'; // Try getting the real promise object from the context, if available. Someone // could have overridden it in a test. Async functions return it implicitly. @@ -497,13 +498,10 @@ export default function(j$) { this.todo = function() { const description = arguments[0]; if (arguments.length !== 1 || typeof description !== 'string') { - const e = new Error('Todo must be called with only a description.'); - - if (Error.captureStackTrace) { - Error.captureStackTrace(e, test.todo); - } - - throw e; + throw new ErrorWithStack( + 'Todo must be called with only a description.', + test.todo, + ); } const spec = specFactory(description, () => {}, currentDeclarationSuite); diff --git a/packages/jest-runner/src/run_test.js b/packages/jest-runner/src/run_test.js index d511c31c52c6..bba71b2c2dfe 100644 --- a/packages/jest-runner/src/run_test.js +++ b/packages/jest-runner/src/run_test.js @@ -18,6 +18,7 @@ import fs from 'graceful-fs'; import { BufferedConsole, Console, + ErrorWithStack, NullConsole, getConsoleOutput, setGlobal, @@ -154,11 +155,10 @@ async function runTestInternal( const realExit = environment.global.process.exit; environment.global.process.exit = function exit(...args) { - const error = new Error(`process.exit called with "${args.join(', ')}"`); - - if (Error.captureStackTrace) { - Error.captureStackTrace(error, exit); - } + const error = new ErrorWithStack( + `process.exit called with "${args.join(', ')}"`, + exit, + ); const formattedError = formatExecError( error, diff --git a/packages/jest-util/src/__tests__/error_with_stack.test.js b/packages/jest-util/src/__tests__/error_with_stack.test.js new file mode 100644 index 000000000000..98c35c57c6b9 --- /dev/null +++ b/packages/jest-util/src/__tests__/error_with_stack.test.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2018-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import ErrorWithStack from '../error_with_stack'; + +describe('ErrorWithStack', () => { + const message = '💩 something went wrong'; + const callsite = () => {}; + + it('calls Error.captureStackTrace with given callsite when capture exists', () => { + jest.spyOn(Error, 'captureStackTrace'); + + const actual = new ErrorWithStack(message, callsite); + + expect(actual).toBeInstanceOf(Error); + expect(actual.message).toBe(message); + expect(Error.captureStackTrace).toHaveBeenCalledWith(actual, callsite); + }); +}); diff --git a/packages/jest-util/src/error_with_stack.js b/packages/jest-util/src/error_with_stack.js new file mode 100644 index 000000000000..2fb1cc6c2b34 --- /dev/null +++ b/packages/jest-util/src/error_with_stack.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2018-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export default class ErrorWithStack extends Error { + constructor(message: ?string, callsite: Function) { + super(message); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, callsite); + } + } +} diff --git a/packages/jest-util/src/index.js b/packages/jest-util/src/index.js index 2b00ae975dca..0d206057f5a5 100644 --- a/packages/jest-util/src/index.js +++ b/packages/jest-util/src/index.js @@ -12,6 +12,7 @@ import mkdirp from 'mkdirp'; import BufferedConsole from './buffered_console'; import clearLine from './clear_line'; import Console from './Console'; +import ErrorWithStack from './error_with_stack'; import FakeTimers from './fake_timers'; import formatTestResults from './format_test_results'; import getFailedSnapshotTests from './get_failed_snapshot_tests'; @@ -37,6 +38,7 @@ const createDirectory = (path: string) => { module.exports = { BufferedConsole, Console, + ErrorWithStack, FakeTimers, NullConsole, clearLine,