From d6389b38b1a614924b4099ba2ae692049d7fe52b Mon Sep 17 00:00:00 2001 From: Sophie Alpert Date: Thu, 1 Nov 2018 19:03:42 -0700 Subject: [PATCH] Improve message/stack parsing for Babel code frame errors Previously, _addMissingMessageToStack decides not to add in the message to what we display, because it can't identify it as a stack. If we fix that by setting stack to include the message upfront (as many browsers do anyway), then it is printed but not prominently -- it is grayed out and with the stack with still only "Error" on the first line. Now if separateMessageFromStack can't tell where the stack begins, we assume the first line is the message -- which will cause it to be displayed more prominently. We could also try to detect code frames in separateMessageFromStack specifically (as we do stacks) but that doesn't seem trivial, in part because the code frame has already been colorized by the time it gets here. Before: ![before screenshot](https://user-images.githubusercontent.com/6820/47889870-6d064700-de09-11e8-8d4a-a044f2ad278b.png) After: ![after screenshot](https://user-images.githubusercontent.com/6820/47889756-cae65f00-de08-11e8-99c1-acfc6e7cb90c.png) --- .../expect-async-matcher.test.js.snap | 6 ++-- .../__snapshots__/failures.test.js.snap | 4 +-- .../__snapshots__/messages.test.js.snap | 16 +++++++++ .../src/__tests__/messages.test.js | 33 +++++++++++++++++++ packages/jest-message-util/src/index.js | 18 +++++----- 5 files changed, 61 insertions(+), 16 deletions(-) diff --git a/e2e/__tests__/__snapshots__/expect-async-matcher.test.js.snap b/e2e/__tests__/__snapshots__/expect-async-matcher.test.js.snap index 2b179d34966a..21c961f43033 100644 --- a/e2e/__tests__/__snapshots__/expect-async-matcher.test.js.snap +++ b/e2e/__tests__/__snapshots__/expect-async-matcher.test.js.snap @@ -9,9 +9,8 @@ exports[`shows the correct errors in stderr when failing tests 1`] = ` ● fail with expected non promise values - Error + Expected value to have length: - Error: Expected value to have length: 2 Received: 1 @@ -20,9 +19,8 @@ exports[`shows the correct errors in stderr when failing tests 1`] = ` ● fail with expected non promise values and not - Error + Expected value to not have length: - Error: Expected value to not have length: 2 Received: 1,2 diff --git a/e2e/__tests__/__snapshots__/failures.test.js.snap b/e2e/__tests__/__snapshots__/failures.test.js.snap index 274061f1e37b..ab3f4c76b947 100644 --- a/e2e/__tests__/__snapshots__/failures.test.js.snap +++ b/e2e/__tests__/__snapshots__/failures.test.js.snap @@ -13,9 +13,7 @@ exports[`not throwing Error objects 2`] = ` "FAIL __tests__/throw_string.test.js ● Test suite failed to run - Error - - banana + banana " `; diff --git a/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.js.snap b/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.js.snap index a39921bd1894..4cf7e417890e 100644 --- a/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.js.snap +++ b/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.js.snap @@ -22,6 +22,22 @@ exports[`formatStackTrace should strip node internals 1`] = ` " `; +exports[`retains message in babel code frame error 1`] = ` +" Babel test + + + packages/react/src/forwardRef.js: Unexpected token, expected , (20:10) + + 18 | false, + 19 | 'forwardRef requires a render function but received a \`memo\` ' + > 20 | 'component. Instead of forwardRef(memo(...)), use ' + + | ^ + 21 | 'memo(forwardRef(...)).', + 22 | ); + 23 | } else if (typeof render !== 'function') { +" +`; + exports[`should exclude jasmine from stack trace for Unix paths. 1`] = ` " Unix test diff --git a/packages/jest-message-util/src/__tests__/messages.test.js b/packages/jest-message-util/src/__tests__/messages.test.js index 6f8c9964e0c6..0a2078ca299c 100644 --- a/packages/jest-message-util/src/__tests__/messages.test.js +++ b/packages/jest-message-util/src/__tests__/messages.test.js @@ -58,6 +58,19 @@ const vendorStack = at Object.asyncFn (__tests__/vendor/sulu/node_modules/sulu-content-bundle/best_component.js:1:5) `; +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[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 + \u001b[90m 22 | \u001b[39m )\u001b[33m;\u001b[39m + \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 +`; + it('should exclude jasmine from stack trace for Unix paths.', () => { const messages = formatResultsErrors( [ @@ -134,3 +147,23 @@ it('should not exclude vendor from stack trace', () => { expect(messages).toMatchSnapshot(); }); + +it('retains message in babel code frame error', () => { + const messages = formatResultsErrors( + [ + { + ancestorTitles: [], + failureMessages: [babelStack], + title: 'Babel test', + }, + ], + { + rootDir: '', + }, + { + noStackTrace: false, + }, + ); + + expect(messages).toMatchSnapshot(); +}); diff --git a/packages/jest-message-util/src/index.js b/packages/jest-message-util/src/index.js index 1f8f4d5f62ba..c601747871f2 100644 --- a/packages/jest-message-util/src/index.js +++ b/packages/jest-message-util/src/index.js @@ -62,7 +62,6 @@ const TITLE_BULLET = chalk.bold('\u25cf '); const STACK_TRACE_COLOR = chalk.dim; const STACK_PATH_REGEXP = /\s*at.*\(?(\:\d*\:\d*|native)\)?/; const EXEC_ERROR_MESSAGE = 'Test suite failed to run'; -const ERROR_TEXT = 'Error: '; const NOT_EMPTY_LINE_REGEXP = /^(?!$)/gm; const indentAllLines = (lines: string, indent: string) => @@ -335,13 +334,14 @@ export const separateMessageFromStack = (content: string) => { return {message: '', stack: ''}; } - const messageMatch = content.match(/(^(.|\n)*?(?=\n\s*at\s.*\:\d*\:\d*))/); - let message = messageMatch ? messageMatch[0] : 'Error'; - const stack = messageMatch ? content.slice(message.length) : content; - // If the error is a plain error instead of a SyntaxError or TypeError - // we remove it from the message because it is generally not useful. - if (message.startsWith(ERROR_TEXT)) { - message = message.substr(ERROR_TEXT.length); - } + // All lines up to what looks like a stack -- or if nothing looks like a stack + // (maybe it's a code frame instead), just the first non-empty line. + // If the error is a plain "Error:" instead of a SyntaxError or TypeError we + // remove the prefix from the message because it is generally not useful. + const messageMatch = content.match( + /^(?:Error: )?([\s\S]*?(?=\n\s*at\s.*\:\d*\:\d*)|\s*.*)([\s\S]*)$/, + ); + const message = messageMatch[1]; + const stack = messageMatch[2]; return {message, stack}; };