diff --git a/CHANGELOG.md b/CHANGELOG.md index fc943294bc3f..dbed475bddec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[jest-console]` Add code frame to `console.error` and `console.warn` ([#9741](https://github.com/facebook/jest/pull/9741)) - `[@jest/globals]` New package so Jest's globals can be explicitly imported ([#9801](https://github.com/facebook/jest/pull/9801)) ### Fixes diff --git a/e2e/__tests__/__snapshots__/beforeAllFiltered.ts.snap b/e2e/__tests__/__snapshots__/beforeAllFiltered.ts.snap index 247de33015d5..77cb833acb56 100644 --- a/e2e/__tests__/__snapshots__/beforeAllFiltered.ts.snap +++ b/e2e/__tests__/__snapshots__/beforeAllFiltered.ts.snap @@ -1,19 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Correct BeforeAll run ensures the BeforeAll of ignored suite is not run 1`] = ` - console.log __tests__/beforeAllFiltered.test.js:10 + console.log beforeAll 1 - console.log __tests__/beforeAllFiltered.test.js:13 + at log (__tests__/beforeAllFiltered.test.js:10:13) + + console.log beforeEach 1 - console.log __tests__/beforeAllFiltered.test.js:22 + at log (__tests__/beforeAllFiltered.test.js:13:13) + + console.log It Foo - console.log __tests__/beforeAllFiltered.test.js:16 + at log (__tests__/beforeAllFiltered.test.js:22:13) + + console.log afterEach 1 - console.log __tests__/beforeAllFiltered.test.js:19 + at log (__tests__/beforeAllFiltered.test.js:16:13) + + console.log afterAll 1 + at log (__tests__/beforeAllFiltered.test.js:19:13) + `; diff --git a/e2e/__tests__/__snapshots__/beforeEachQueue.ts.snap b/e2e/__tests__/__snapshots__/beforeEachQueue.ts.snap index a665905a9f06..4acca9e4a59d 100644 --- a/e2e/__tests__/__snapshots__/beforeEachQueue.ts.snap +++ b/e2e/__tests__/__snapshots__/beforeEachQueue.ts.snap @@ -1,19 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Correct beforeEach order ensures the correct order for beforeEach 1`] = ` - console.log __tests__/beforeEachQueue.test.js:10 + console.log BeforeEach - console.log __tests__/beforeEachQueue.test.js:14 + at Object.log (__tests__/beforeEachQueue.test.js:10:13) + + console.log It Foo - console.log __tests__/beforeEachQueue.test.js:17 + at Object.log (__tests__/beforeEachQueue.test.js:14:13) + + console.log BeforeEach Inline Foo - console.log __tests__/beforeEachQueue.test.js:10 + at Object.log (__tests__/beforeEachQueue.test.js:17:15) + + console.log BeforeEach - console.log __tests__/beforeEachQueue.test.js:22 + at Object.log (__tests__/beforeEachQueue.test.js:10:13) + + console.log It Bar + at Object.log (__tests__/beforeEachQueue.test.js:22:13) + `; diff --git a/e2e/__tests__/__snapshots__/console.test.ts.snap b/e2e/__tests__/__snapshots__/console.test.ts.snap index 8077db761de9..161e2e9e4c21 100644 --- a/e2e/__tests__/__snapshots__/console.test.ts.snap +++ b/e2e/__tests__/__snapshots__/console.test.ts.snap @@ -4,14 +4,40 @@ exports[`console printing 1`] = ` PASS __tests__/console.test.js ● Console - console.log __tests__/console.test.js:10 + console.log This is a log message. - console.info __tests__/console.test.js:12 + + at Object.log (__tests__/console.test.js:10:11) + + console.info This is an info message. - console.warn __tests__/console.test.js:14 + + at Object.info (__tests__/console.test.js:12:11) + + console.warn This is a warning message. - console.error __tests__/console.test.js:16 + + 12 | console.info('This is an info message.'); + 13 | + > 14 | console.warn('This is a warning message.'); + | ^ + 15 | + 16 | console.error('This is an error message.'); + 17 | }); + + at Object.warn (__tests__/console.test.js:14:11) + + console.error This is an error message. + + 14 | console.warn('This is a warning message.'); + 15 | + > 16 | console.error('This is an error message.'); + | ^ + 17 | }); + 18 | + + at Object.error (__tests__/console.test.js:16:11) `; exports[`console printing 2`] = ` @@ -23,18 +49,41 @@ Ran all test suites. `; exports[`console printing with --verbose 1`] = ` - console.log __tests__/console.test.js:10 + console.log This is a log message. - console.info __tests__/console.test.js:12 + at Object.log (__tests__/console.test.js:10:11) + + console.info This is an info message. - console.warn __tests__/console.test.js:14 + at Object.info (__tests__/console.test.js:12:11) + + console.warn This is a warning message. - console.error __tests__/console.test.js:16 + 12 | console.info('This is an info message.'); + 13 | + > 14 | console.warn('This is a warning message.'); + | ^ + 15 | + 16 | console.error('This is an error message.'); + 17 | }); + + at Object.warn (__tests__/console.test.js:14:11) + + console.error This is an error message. + 14 | console.warn('This is a warning message.'); + 15 | + > 16 | console.error('This is an error message.'); + | ^ + 17 | }); + 18 | + + at Object.error (__tests__/console.test.js:16:11) + `; exports[`console printing with --verbose 2`] = ` @@ -56,17 +105,25 @@ exports[`does not error out when using winston 2`] = ` PASS __tests__/console.test.js ● Console - console.log node_modules/winston/lib/winston/transports/console.js:79 + console.log {"level":"info","message":"Log message from winston"} - - console.log node_modules/winston/lib/winston/transports/console.js:79 + + at Console.log (node_modules/winston/lib/winston/transports/console.js:79:23) + + console.log {"message":"Info message from winston","level":"info"} - - console.log node_modules/winston/lib/winston/transports/console.js:79 + + at Console.log (node_modules/winston/lib/winston/transports/console.js:79:23) + + console.log {"message":"Warn message from winston","level":"warn"} - - console.log node_modules/winston/lib/winston/transports/console.js:79 + + at Console.log (node_modules/winston/lib/winston/transports/console.js:79:23) + + console.log {"message":"Error message from winston","level":"error"} + + at Console.log (node_modules/winston/lib/winston/transports/console.js:79:23) `; exports[`does not error out when using winston 3`] = ` diff --git a/e2e/__tests__/__snapshots__/consoleAfterTeardown.test.ts.snap b/e2e/__tests__/__snapshots__/consoleAfterTeardown.test.ts.snap index 80308c7d489c..c75128492c2e 100644 --- a/e2e/__tests__/__snapshots__/consoleAfterTeardown.test.ts.snap +++ b/e2e/__tests__/__snapshots__/consoleAfterTeardown.test.ts.snap @@ -15,6 +15,6 @@ PASS __tests__/console.test.js 14 | }); 15 | - at BufferedConsole.log (../../packages/jest-console/build/BufferedConsole.js:199:10) + at BufferedConsole.log (../../packages/jest-console/build/BufferedConsole.js:201:10) at log (__tests__/console.test.js:12:13) `; diff --git a/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap b/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap index fb009e134aed..571dd419e639 100644 --- a/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap +++ b/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap @@ -17,7 +17,9 @@ Ran all test suites. `; exports[`prints console.logs when run with forceExit 3`] = ` - console.log __tests__/a-banana.js:1 + console.log Hey + at Object. (__tests__/a-banana.js:1:1) + `; diff --git a/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap b/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap index ee816f95e8a1..017f5cc6d332 100644 --- a/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap +++ b/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap @@ -16,7 +16,7 @@ exports[`warns if describe returns a Promise 1`] = ` 14 | }); at Object.describe (__tests__/describeReturnPromise.test.js:11:1) - + `; @@ -36,6 +36,6 @@ exports[`warns if describe returns something 1`] = ` 14 | }); at Object.describe (__tests__/describeReturnSomething.test.js:11:1) - + `; diff --git a/e2e/__tests__/__snapshots__/jest.config.js.test.ts.snap b/e2e/__tests__/__snapshots__/jest.config.js.test.ts.snap index 498c5c97bfd8..db94a91b08e4 100644 --- a/e2e/__tests__/__snapshots__/jest.config.js.test.ts.snap +++ b/e2e/__tests__/__snapshots__/jest.config.js.test.ts.snap @@ -1,9 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`traverses directory tree up until it finds jest.config 1`] = ` - console.log ../../../__tests__/a-banana.js:3 + console.log <>/jest.config.js/some/nested/directory + at Object.log (__tests__/a-banana.js:3:27) + `; exports[`traverses directory tree up until it finds jest.config 2`] = ` diff --git a/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap b/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap new file mode 100644 index 000000000000..a3fabf97c9d7 --- /dev/null +++ b/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`on node >=10 prints coverage 1`] = ` +" console.log + 42 + + at Object.log (__tests__/Thing.test.js:10:9) + +----------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +----------|---------|----------|---------|---------|------------------- +All files | 100 | 100 | 100 | 100 | + Thing.js | 100 | 100 | 100 | 100 | + x.css | 100 | 100 | 100 | 100 | +----------|---------|----------|---------|---------|-------------------" +`; diff --git a/e2e/__tests__/beforeAllFiltered.ts b/e2e/__tests__/beforeAllFiltered.ts index fdd03d373486..7d0a5cf4fbc5 100644 --- a/e2e/__tests__/beforeAllFiltered.ts +++ b/e2e/__tests__/beforeAllFiltered.ts @@ -10,7 +10,11 @@ import runJest from '../runJest'; describe('Correct BeforeAll run', () => { it('ensures the BeforeAll of ignored suite is not run', () => { - const result = runJest('before-all-filtered'); - expect(wrap(result.stdout.replace(/\\/g, '/'))).toMatchSnapshot(); + let {stdout} = runJest('before-all-filtered'); + + // for some reason Circus does not have the `Object` part + stdout = stdout.replace(/at Object.log \(/g, 'at log ('); + + expect(wrap(stdout)).toMatchSnapshot(); }); }); diff --git a/e2e/__tests__/consoleLogOutputWhenRunInBand.test.ts b/e2e/__tests__/consoleLogOutputWhenRunInBand.test.ts index e49a61328672..3e505c906365 100644 --- a/e2e/__tests__/consoleLogOutputWhenRunInBand.test.ts +++ b/e2e/__tests__/consoleLogOutputWhenRunInBand.test.ts @@ -15,6 +15,8 @@ const DIR = path.resolve(__dirname, '../console-log-output-when-run-in-band'); beforeEach(() => cleanup(DIR)); afterAll(() => cleanup(DIR)); +const nodeMajorVersion = Number(process.versions.node.split('.')[0]); + test('prints console.logs when run with forceExit', () => { writeFiles(DIR, { '__tests__/a-banana.js': ` @@ -23,12 +25,26 @@ test('prints console.logs when run with forceExit', () => { 'package.json': '{}', }); - const {stderr, stdout, exitCode} = runJest(DIR, [ + const {stderr, exitCode, ...res} = runJest(DIR, [ '-i', '--ci=false', '--forceExit', ]); + let {stdout} = res; + const {rest, summary} = extractSummary(stderr); + + if (nodeMajorVersion < 12) { + expect(stdout).toContain( + 'at Object..test (__tests__/a-banana.js:1:1)', + ); + + stdout = stdout.replace( + 'at Object..test (__tests__/a-banana.js:1:1)', + 'at Object. (__tests__/a-banana.js:1:1)', + ); + } + expect(exitCode).toBe(0); expect(wrap(rest)).toMatchSnapshot(); expect(wrap(summary)).toMatchSnapshot(); diff --git a/e2e/__tests__/declarationErrors.test.ts b/e2e/__tests__/declarationErrors.test.ts index e0c10bb09a3f..d0d1dda1db16 100644 --- a/e2e/__tests__/declarationErrors.test.ts +++ b/e2e/__tests__/declarationErrors.test.ts @@ -12,7 +12,8 @@ const normalizeCircusJasmine = (str: string) => wrap( str .replace(/console\.log .+:\d+/, 'console.log') - .replace(/.+addSpecsToSuite (.+:\d+:\d+).+\n/g, ''), + .replace(/.+addSpecsToSuite (.+:\d+:\d+).+\n/g, '') + .replace(/.+_dispatchDescribe (.+:\d+:\d+).+\n/g, ''), ); it('warns if describe returns a Promise', () => { diff --git a/e2e/__tests__/resolveBrowserField.test.ts b/e2e/__tests__/resolveBrowserField.test.ts index f2e9383ed22c..2c37924e2bc5 100644 --- a/e2e/__tests__/resolveBrowserField.test.ts +++ b/e2e/__tests__/resolveBrowserField.test.ts @@ -63,8 +63,10 @@ test('preserves module identity for symlinks when using browser field resolution const {stdout, stderr, exitCode} = runJest(DIR, ['--no-watchman']); expect(stderr).toContain('Test Suites: 1 passed, 1 total'); expect(wrap(stdout.trim())).toMatchInlineSnapshot(` - console.log packages/needs-preserved-id/index.js:1 + console.log needs-preserved-id executed + + at Object. (packages/needs-preserved-id/index.js:1:13) `); expect(exitCode).toEqual(0); }); diff --git a/e2e/__tests__/v8Coverage.test.ts b/e2e/__tests__/v8Coverage.test.ts index 97dfea44252f..902d324aab10 100644 --- a/e2e/__tests__/v8Coverage.test.ts +++ b/e2e/__tests__/v8Coverage.test.ts @@ -14,6 +14,7 @@ const DIR = path.resolve(__dirname, '../v8-coverage'); onNodeVersions('>=10', () => { test('prints coverage', () => { const sourcemapDir = path.join(DIR, 'no-sourcemap'); + const {stdout, exitCode} = runJest( sourcemapDir, ['--coverage', '--coverage-provider', 'v8'], @@ -23,24 +24,6 @@ onNodeVersions('>=10', () => { ); expect(exitCode).toBe(0); - expect( - '\n' + - stdout - .split('\n') - .map(s => s.trimRight()) - .join('\n') + - '\n', - ).toEqual(` - console.log __tests__/Thing.test.js:10 - 42 - -----------|---------|----------|---------|---------|------------------- -File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s -----------|---------|----------|---------|---------|------------------- -All files | 100 | 100 | 100 | 100 | - Thing.js | 100 | 100 | 100 | 100 | - x.css | 100 | 100 | 100 | 100 | -----------|---------|----------|---------|---------|------------------- -`); + expect(stdout).toMatchSnapshot(); }); }); diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index e5501896ebfe..3b7e6f1ce578 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -17,8 +17,9 @@ } }, "dependencies": { - "@jest/source-map": "^25.2.6", + "@jest/types": "^25.3.0", "chalk": "^3.0.0", + "jest-message-util": "^25.3.0", "jest-util": "^25.3.0", "slash": "^3.0.0" }, diff --git a/packages/jest-console/src/BufferedConsole.ts b/packages/jest-console/src/BufferedConsole.ts index 2ac94ac5c83b..2bf53f350f72 100644 --- a/packages/jest-console/src/BufferedConsole.ts +++ b/packages/jest-console/src/BufferedConsole.ts @@ -9,7 +9,7 @@ import assert = require('assert'); import {Console} from 'console'; import {format} from 'util'; import chalk = require('chalk'); -import {SourceMapRegistry, getCallsite} from '@jest/source-map'; +import {ErrorWithStack} from 'jest-util'; import type { ConsoleBuffer, LogCounters, @@ -23,18 +23,16 @@ export default class BufferedConsole extends Console { private _counters: LogCounters; private _timers: LogTimers; private _groupDepth: number; - private _getSourceMaps: () => SourceMapRegistry | null | undefined; - constructor(getSourceMaps: () => SourceMapRegistry | null | undefined) { + constructor() { const buffer: ConsoleBuffer = []; super({ write: (message: string) => { - BufferedConsole.write(buffer, 'log', message, null, getSourceMaps()); + BufferedConsole.write(buffer, 'log', message, null); return true; }, } as NodeJS.WritableStream); - this._getSourceMaps = getSourceMaps; this._buffer = buffer; this._counters = {}; this._timers = {}; @@ -46,10 +44,17 @@ export default class BufferedConsole extends Console { type: LogType, message: LogMessage, level?: number | null, - sourceMaps?: SourceMapRegistry | null, ): ConsoleBuffer { - const callsite = getCallsite(level != null ? level : 2, sourceMaps); - const origin = callsite.getFileName() + ':' + callsite.getLineNumber(); + const stackLevel = level != null ? level : 2; + const rawStack = new ErrorWithStack(undefined, BufferedConsole.write).stack; + + invariant(rawStack, 'always have a stack trace'); + + const origin = rawStack + .split('\n') + .slice(stackLevel) + .filter(Boolean) + .join('\n'); buffer.push({ message, @@ -66,7 +71,6 @@ export default class BufferedConsole extends Console { type, ' '.repeat(this._groupDepth) + message, 3, - this._getSourceMaps(), ); } @@ -163,3 +167,9 @@ export default class BufferedConsole extends Console { return this._buffer.length ? this._buffer : undefined; } } + +function invariant(condition: unknown, message?: string): asserts condition { + if (!condition) { + throw new Error(message); + } +} diff --git a/packages/jest-console/src/getConsoleOutput.ts b/packages/jest-console/src/getConsoleOutput.ts index 09a3232b557e..1c3dba19f898 100644 --- a/packages/jest-console/src/getConsoleOutput.ts +++ b/packages/jest-console/src/getConsoleOutput.ts @@ -5,44 +5,68 @@ * LICENSE file in the root directory of this source tree. */ -import * as path from 'path'; import chalk = require('chalk'); -import slash = require('slash'); +import { + StackTraceConfig, + StackTraceOptions, + formatStackTrace, +} from 'jest-message-util'; import type {ConsoleBuffer} from './types'; export default ( + // TODO: remove in 26 root: string, verbose: boolean, buffer: ConsoleBuffer, + // TODO: make mandatory and take Config.ProjectConfig in 26 + config: StackTraceConfig = { + rootDir: root, + testMatch: [], + }, ): string => { const TITLE_INDENT = verbose ? ' ' : ' '; const CONSOLE_INDENT = TITLE_INDENT + ' '; - return buffer.reduce((output, {type, message, origin}) => { - origin = slash(path.relative(root, origin)); + const logEntries = buffer.reduce((output, {type, message, origin}) => { message = message .split(/\n/) .map(line => CONSOLE_INDENT + line) .join('\n'); let typeMessage = 'console.' + type; + let noStackTrace = true; + let noCodeFrame = true; + if (type === 'warn') { message = chalk.yellow(message); typeMessage = chalk.yellow(typeMessage); + noStackTrace = false; + noCodeFrame = false; } else if (type === 'error') { message = chalk.red(message); typeMessage = chalk.red(typeMessage); + noStackTrace = false; + noCodeFrame = false; } + const options: StackTraceOptions = { + noCodeFrame, + noStackTrace, + }; + + const formattedStackTrace = formatStackTrace(origin, config, options); + return ( output + TITLE_INDENT + chalk.dim(typeMessage) + - ' ' + - chalk.dim(origin) + '\n' + - message + - '\n' + message.trimRight() + + '\n' + + chalk.dim(formattedStackTrace.trimRight()) + + '\n\n' ); }, ''); + + return logEntries.trimRight() + '\n'; }; diff --git a/packages/jest-console/tsconfig.json b/packages/jest-console/tsconfig.json index 719bab0f554b..a436b61458e6 100644 --- a/packages/jest-console/tsconfig.json +++ b/packages/jest-console/tsconfig.json @@ -5,7 +5,8 @@ "outDir": "build" }, "references": [ - {"path": "../jest-source-map"}, + {"path": "../jest-message-util"}, + {"path": "../jest-types"}, {"path": "../jest-util"} ] } diff --git a/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap b/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap index 8fb8249ca9b5..490db062e6fb 100644 --- a/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap +++ b/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap @@ -7,6 +7,18 @@ exports[`.formatExecError() 1`] = ` " `; +exports[`codeframe 1`] = ` +" ● Test suite failed to run + + Whoops! + + > 1 | throw new Error(\\"Whoops!\\"); + | ^ + + at Object. (file.js:1:7) +" +`; + exports[`formatStackTrace should strip node internals 1`] = ` " Unix test @@ -23,6 +35,22 @@ exports[`formatStackTrace should strip node internals 1`] = ` " `; +exports[`no codeframe 1`] = ` +" ● Test suite failed to run + + Whoops! + + at Object. (file.js:1:7) +" +`; + +exports[`no stack 1`] = ` +" ● Test suite failed to run + + Whoops! +" +`; + exports[`retains message in babel code frame error 1`] = ` " Babel test @@ -30,7 +58,7 @@ exports[`retains message in babel code frame error 1`] = ` packages/react/src/forwardRef.js: Unexpected token, expected , (20:10) 18 | false, - 19 | 'forwardRef requires a render function but received a \`memo\` ' + 19 | 'forwardRef requires a render function but received a \`memo\` ' > 20 | 'component. Instead of forwardRef(memo(...)), use ' + | ^ 21 | 'memo(forwardRef(...)).', diff --git a/packages/jest-message-util/src/__tests__/messages.test.ts b/packages/jest-message-util/src/__tests__/messages.test.ts index 667038690713..41a14d0100da 100644 --- a/packages/jest-message-util/src/__tests__/messages.test.ts +++ b/packages/jest-message-util/src/__tests__/messages.test.ts @@ -6,8 +6,18 @@ * */ +import {readFileSync} from 'fs'; +import slash = require('slash'); +import tempy = require('tempy'); import {formatExecError, formatResultsErrors} from '..'; +const rootDir = tempy.directory(); + +jest.mock('fs', () => ({ + ...jest.requireActual('fs'), + readFileSync: jest.fn(), +})); + const unixStackTrace = ` ` + `at stack (../jest-jasmine2/build/jasmine-2.4.1.js:1580:17) @@ -61,7 +71,7 @@ const babelStack = ` packages/react/src/forwardRef.js: Unexpected token, expected , (20:10) \u001b[0m \u001b[90m 18 | \u001b[39m \u001b[36mfalse\u001b[39m\u001b[33m,\u001b[39m - \u001b[90m 19 | \u001b[39m \u001b[32m'forwardRef requires a render function but received a \`memo\` '\u001b[39m + \u001b[90m 19 | \u001b[39m \u001b[32m'forwardRef requires a render function but received a \`memo\` '\u001b[39m \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 20 | \u001b[39m \u001b[32m'component. Instead of forwardRef(memo(...)), use '\u001b[39m \u001b[33m+\u001b[39m \u001b[90m | \u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m \u001b[90m 21 | \u001b[39m \u001b[32m'memo(forwardRef(...)).'\u001b[39m\u001b[33m,\u001b[39m @@ -69,6 +79,10 @@ const babelStack = \u001b[90m 23 | \u001b[39m } \u001b[36melse\u001b[39m \u001b[36mif\u001b[39m (\u001b[36mtypeof\u001b[39m render \u001b[33m!==\u001b[39m \u001b[32m'function'\u001b[39m) {\u001b[0m `; +beforeEach(() => { + jest.clearAllMocks(); +}); + it('should exclude jasmine from stack trace for Unix paths.', () => { const messages = formatResultsErrors( [ @@ -187,3 +201,91 @@ it('retains message in babel code frame error', () => { expect(messages).toMatchSnapshot(); }); + +it('codeframe', () => { + readFileSync.mockImplementationOnce(() => 'throw new Error("Whoops!");'); + + const message = formatExecError( + { + message: 'Whoops!', + stack: ` + at Object. (${slash(rootDir)}/file.js:1:7) + at Module._compile (internal/modules/cjs/loader.js:1158:30) + at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10) + at Module.load (internal/modules/cjs/loader.js:1002:32) + at Function.Module._load (internal/modules/cjs/loader.js:901:14) + at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12) +`, + }, + { + rootDir, + testMatch: [], + }, + { + noCodeFrame: false, + noStackTrace: false, + }, + 'path_test', + ); + + expect(message).toMatchSnapshot(); +}); + +it('no codeframe', () => { + readFileSync.mockImplementationOnce(() => 'throw new Error("Whoops!");'); + + const message = formatExecError( + { + message: 'Whoops!', + stack: ` + at Object. (${slash(rootDir)}/file.js:1:7) + at Module._compile (internal/modules/cjs/loader.js:1158:30) + at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10) + at Module.load (internal/modules/cjs/loader.js:1002:32) + at Function.Module._load (internal/modules/cjs/loader.js:901:14) + at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12) +`, + }, + { + rootDir, + testMatch: [], + }, + { + noCodeFrame: true, + noStackTrace: false, + }, + 'path_test', + ); + + expect(message).toMatchSnapshot(); +}); + +it('no stack', () => { + readFileSync.mockImplementationOnce(() => 'throw new Error("Whoops!");'); + + const message = formatExecError( + { + message: 'Whoops!', + stack: ` + at Object. (${slash(rootDir)}/file.js:1:7) + at Module._compile (internal/modules/cjs/loader.js:1158:30) + at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10) + at Module.load (internal/modules/cjs/loader.js:1002:32) + at Function.Module._load (internal/modules/cjs/loader.js:901:14) + at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12) +`, + }, + { + rootDir, + testMatch: [], + }, + { + // if no stack, no codeframe is implied + noCodeFrame: true, + noStackTrace: true, + }, + 'path_test', + ); + + expect(message).toMatchSnapshot(); +}); diff --git a/packages/jest-message-util/src/index.ts b/packages/jest-message-util/src/index.ts index b82b512690fa..b3c1fbb5967b 100644 --- a/packages/jest-message-util/src/index.ts +++ b/packages/jest-message-util/src/index.ts @@ -38,6 +38,7 @@ export type StackTraceConfig = Pick< export type StackTraceOptions = { noStackTrace: boolean; + noCodeFrame?: boolean; }; const PATH_NODE_MODULES = `${path.sep}node_modules${path.sep}`; @@ -251,7 +252,7 @@ const formatPaths = ( export const getStackTraceLines = ( stack: string, - options: StackTraceOptions = {noStackTrace: false}, + options: StackTraceOptions = {noCodeFrame: false, noStackTrace: false}, ): Array => removeInternalStackEntries(stack.split(/\n/), options); export const getTopFrame = (lines: Array): Frame | null => { @@ -277,24 +278,27 @@ export const formatStackTrace = ( testPath?: Path, ): string => { const lines = getStackTraceLines(stack, options); - const topFrame = getTopFrame(lines); let renderedCallsite = ''; const relativeTestPath = testPath ? slash(path.relative(config.rootDir, testPath)) : null; - if (topFrame) { - const {column, file: filename, line} = topFrame; - - if (line && filename && path.isAbsolute(filename)) { - let fileContent; - try { - // TODO: check & read HasteFS instead of reading the filesystem: - // see: https://github.com/facebook/jest/pull/5405#discussion_r164281696 - fileContent = fs.readFileSync(filename, 'utf8'); - renderedCallsite = getRenderedCallsite(fileContent, line, column); - } catch (e) { - // the file does not exist or is inaccessible, we ignore + if (!options.noCodeFrame) { + const topFrame = getTopFrame(lines); + + if (topFrame) { + const {column, file: filename, line} = topFrame; + + if (line && filename && path.isAbsolute(filename)) { + let fileContent; + try { + // TODO: check & read HasteFS instead of reading the filesystem: + // see: https://github.com/facebook/jest/pull/5405#discussion_r164281696 + fileContent = fs.readFileSync(filename, 'utf8'); + renderedCallsite = getRenderedCallsite(fileContent, line, column); + } catch (e) { + // the file does not exist or is inaccessible, we ignore + } } } } @@ -307,7 +311,9 @@ export const formatStackTrace = ( ) .join('\n'); - return `${renderedCallsite}\n${stacktrace}`; + return renderedCallsite + ? `${renderedCallsite}\n${stacktrace}` + : `\n${stacktrace}`; }; type FailedResults = Array<{ diff --git a/packages/jest-reporters/src/default_reporter.ts b/packages/jest-reporters/src/default_reporter.ts index ae90384cd2b8..9053fbf20eac 100644 --- a/packages/jest-reporters/src/default_reporter.ts +++ b/packages/jest-reporters/src/default_reporter.ts @@ -182,6 +182,7 @@ export default class DefaultReporter extends BaseReporter { config.cwd, !!this._globalConfig.verbose, result.console, + config, ), ); } diff --git a/packages/jest-runner/src/runTest.ts b/packages/jest-runner/src/runTest.ts index a617bc9d510c..87b9e809720c 100644 --- a/packages/jest-runner/src/runTest.ts +++ b/packages/jest-runner/src/runTest.ts @@ -114,21 +114,14 @@ async function runTestInternal( ? require(config.moduleLoader) : require('jest-runtime'); - let runtime: RuntimeClass | undefined = undefined; - const consoleOut = globalConfig.useStderr ? process.stderr : process.stdout; const consoleFormatter = (type: LogType, message: LogMessage) => getConsoleOutput( config.cwd, !!globalConfig.verbose, // 4 = the console call is buried 4 stack frames deep - BufferedConsole.write( - [], - type, - message, - 4, - runtime && runtime.getSourceMaps(), - ), + BufferedConsole.write([], type, message, 4), + config, ); let testConsole; @@ -138,7 +131,7 @@ async function runTestInternal( } else if (globalConfig.verbose) { testConsole = new CustomConsole(consoleOut, consoleOut, consoleFormatter); } else { - testConsole = new BufferedConsole(() => runtime && runtime.getSourceMaps()); + testConsole = new BufferedConsole(); } const environment = new TestEnvironment(config, { @@ -153,7 +146,7 @@ async function runTestInternal( const cacheFS = {[path]: testSource}; setGlobal(environment.global, 'console', testConsole); - runtime = new Runtime(config, environment, resolver, cacheFS, { + const runtime = new Runtime(config, environment, resolver, cacheFS, { changedFiles: context && context.changedFiles, collectCoverage: globalConfig.collectCoverage, collectCoverageFrom: globalConfig.collectCoverageFrom, @@ -163,13 +156,13 @@ async function runTestInternal( const start = Date.now(); - config.setupFiles.forEach(path => runtime!.requireModule(path)); + config.setupFiles.forEach(path => runtime.requireModule(path)); const sourcemapOptions: sourcemapSupport.Options = { environment: 'node', handleUncaughtExceptions: false, retrieveSourceMap: source => { - const sourceMaps = runtime && runtime.getSourceMaps(); + const sourceMaps = runtime.getSourceMaps(); const sourceMapSource = sourceMaps && sourceMaps[source]; if (sourceMapSource) {