From 63e17952d26f8597817d659dbfd4f7faeee49bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcolas=20Iensen?= Date: Sun, 29 Oct 2017 11:57:21 +0100 Subject: [PATCH 1/5] Fix stack trace for errors thrown by snapshot serializers In this commit, we are creating a new custom error called `PrettyFormatPluginError`, this error is thrown by `prettyFormat` function when a serializer throws an error. In the `expect` module, we skip `Error.captureStackTrace` if the error name is `PrettyFormatPluginError`, this way the stack trace stays intact. Fixes #3302 --- packages/expect/src/index.js | 12 ++-- .../src/__tests__/pretty_format.test.js | 57 ++++++++++++++++++ packages/pretty-format/src/index.js | 60 ++++++++++++------- 3 files changed, 103 insertions(+), 26 deletions(-) diff --git a/packages/expect/src/index.js b/packages/expect/src/index.js index 8a1f3164f4cc..a23c4457606a 100644 --- a/packages/expect/src/index.js +++ b/packages/expect/src/index.js @@ -197,12 +197,14 @@ const makeThrowingMatcher = ( try { result = matcher.apply(matcherContext, [actual].concat(args)); } catch (error) { - if (!(error instanceof JestAssertionError)) { - // Try to remove this and deeper functions from the stack trace frame. + if ( + !(error instanceof JestAssertionError) && + error.name != 'PrettyFormatPluginError' && // Guard for some environments (browsers) that do not support this feature. - if (Error.captureStackTrace) { - Error.captureStackTrace(error, throwingMatcher); - } + Error.captureStackTrace + ) { + // Try to remove this and deeper functions from the stack trace frame. + Error.captureStackTrace(error, throwingMatcher); } throw error; } diff --git a/packages/pretty-format/src/__tests__/pretty_format.test.js b/packages/pretty-format/src/__tests__/pretty_format.test.js index 28ea1dd05b94..fd6dd77e211a 100644 --- a/packages/pretty-format/src/__tests__/pretty_format.test.js +++ b/packages/pretty-format/src/__tests__/pretty_format.test.js @@ -549,6 +549,63 @@ describe('prettyFormat()', () => { }).toThrow(); }); + it('throws PrettyFormatPluginError if test throws an error', () => { + const options = { + plugins: [ + { + print: () => '', + test() { + throw new Error('Where is the error?'); + }, + }, + ], + }; + + try { + prettyFormat('', options); + } catch (error) { + expect(error.name).toBe('PrettyFormatPluginError'); + } + }); + + it('throws PrettyFormatPluginError if print throws an error', () => { + const options = { + plugins: [ + { + print: () => { + throw new Error('Where is the error?'); + }, + test: () => true, + }, + ], + }; + + try { + prettyFormat('', options); + } catch (error) { + expect(error.name).toBe('PrettyFormatPluginError'); + } + }); + + it('throws PrettyFormatPluginError if serialize throws an error', () => { + const options = { + plugins: [ + { + serialize: () => { + throw new Error('Where is the error?'); + }, + test: () => true, + }, + ], + }; + + try { + prettyFormat('', options); + } catch (error) { + expect(error.name).toBe('PrettyFormatPluginError'); + } + }); + it('supports plugins with deeply nested arrays (#24)', () => { const val = [[1, 2], [3, 4]]; expect( diff --git a/packages/pretty-format/src/index.js b/packages/pretty-format/src/index.js index c1df9ac4ebcd..f48206364729 100644 --- a/packages/pretty-format/src/index.js +++ b/packages/pretty-format/src/index.js @@ -48,6 +48,14 @@ const isWindow = val => typeof window !== 'undefined' && val === window; const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/; const NEWLINE_REGEXP = /\n/gi; +class PrettyFormatPluginError extends Error { + constructor(message, stack) { + super(message); + this.stack = stack; + this.name = this.constructor.name; + } +} + function isToStringedArrayType(toStringed: string): boolean { return ( toStringed === '[object Array]' || @@ -246,25 +254,31 @@ function printPlugin( depth: number, refs: Refs, ): string { - const printed = plugin.serialize - ? plugin.serialize(val, config, indentation, depth, refs, printer) - : plugin.print( - val, - valChild => printer(valChild, config, indentation, depth, refs), - str => { - const indentationNext = indentation + config.indent; - return ( - indentationNext + - str.replace(NEWLINE_REGEXP, '\n' + indentationNext) - ); - }, - { - edgeSpacing: config.spacingOuter, - min: config.min, - spacing: config.spacingInner, - }, - config.colors, - ); + let printed; + + try { + printed = plugin.serialize + ? plugin.serialize(val, config, indentation, depth, refs, printer) + : plugin.print( + val, + valChild => printer(valChild, config, indentation, depth, refs), + str => { + const indentationNext = indentation + config.indent; + return ( + indentationNext + + str.replace(NEWLINE_REGEXP, '\n' + indentationNext) + ); + }, + { + edgeSpacing: config.spacingOuter, + min: config.min, + spacing: config.spacingInner, + }, + config.colors, + ); + } catch (error) { + throw new PrettyFormatPluginError(error.message, error.stack); + } if (typeof printed !== 'string') { throw new Error( `pretty-format: Plugin must return type "string" but instead returned "${typeof printed}".`, @@ -275,8 +289,12 @@ function printPlugin( function findPlugin(plugins: Plugins, val: any) { for (let p = 0; p < plugins.length; p++) { - if (plugins[p].test(val)) { - return plugins[p]; + try { + if (plugins[p].test(val)) { + return plugins[p]; + } + } catch (error) { + throw new PrettyFormatPluginError(error.message, error.stack); } } From d523a228d09345cc92712548c28e3a3554d76f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcolas=20Iensen?= Date: Sun, 29 Oct 2017 13:00:15 +0100 Subject: [PATCH 2/5] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 013b611055fe..9e81fec5d3e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ * `[pretty-format]` Prevent error in pretty-format for window in jsdom test env ([#4750](https://github.com/facebook/jest/pull/4750)) * `[jest-resolve]` Preserve module identity for symlinks ([#4761](https://github.com/facebook/jest/pull/4761)) * `[jest-config]` Include error message for `preset` json ([#4766](https://github.com/facebook/jest/pull/4766)) +* `[pretty-format]` Throw `PrettyFormatPluginError` if a plugin halts with an exception ([#4787](https://github.com/facebook/jest/pull/4787)) +* `[expect]` Keep the stack trace unchanged when `PrettyFormatPluginError` is thrown by pretty-format ### Features * `[jest-environment-jsdom]` [**BREAKING**] Upgrade to JSDOM@11 ([#4770](https://github.com/facebook/jest/pull/4770)) From 7c7069cb2fc39f93e679a3f7ef5dfec201943e11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcolas=20Iensen?= Date: Sun, 29 Oct 2017 23:14:15 +0100 Subject: [PATCH 3/5] Make sure new tests have assertions --- packages/pretty-format/src/__tests__/pretty_format.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/pretty-format/src/__tests__/pretty_format.test.js b/packages/pretty-format/src/__tests__/pretty_format.test.js index fd6dd77e211a..41a833b0e798 100644 --- a/packages/pretty-format/src/__tests__/pretty_format.test.js +++ b/packages/pretty-format/src/__tests__/pretty_format.test.js @@ -550,6 +550,7 @@ describe('prettyFormat()', () => { }); it('throws PrettyFormatPluginError if test throws an error', () => { + expect.hasAssertions(); const options = { plugins: [ { @@ -569,6 +570,7 @@ describe('prettyFormat()', () => { }); it('throws PrettyFormatPluginError if print throws an error', () => { + expect.hasAssertions(); const options = { plugins: [ { @@ -588,6 +590,7 @@ describe('prettyFormat()', () => { }); it('throws PrettyFormatPluginError if serialize throws an error', () => { + expect.hasAssertions(); const options = { plugins: [ { From ee00b4e9dcd510e8b427fe3c9c00c4f29c867ee1 Mon Sep 17 00:00:00 2001 From: Christoph Nakazawa Date: Mon, 30 Oct 2017 08:33:23 +0000 Subject: [PATCH 4/5] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e81fec5d3e1..ff05fda5d1e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ * `[jest-resolve]` Preserve module identity for symlinks ([#4761](https://github.com/facebook/jest/pull/4761)) * `[jest-config]` Include error message for `preset` json ([#4766](https://github.com/facebook/jest/pull/4766)) * `[pretty-format]` Throw `PrettyFormatPluginError` if a plugin halts with an exception ([#4787](https://github.com/facebook/jest/pull/4787)) -* `[expect]` Keep the stack trace unchanged when `PrettyFormatPluginError` is thrown by pretty-format +* `[expect]` Keep the stack trace unchanged when `PrettyFormatPluginError` is thrown by pretty-format ([#4787](https://github.com/facebook/jest/pull/4787)) ### Features * `[jest-environment-jsdom]` [**BREAKING**] Upgrade to JSDOM@11 ([#4770](https://github.com/facebook/jest/pull/4770)) From c0d78a92591fbef075d504a75759cfd49beecffb Mon Sep 17 00:00:00 2001 From: Christoph Nakazawa Date: Mon, 30 Oct 2017 08:33:50 +0000 Subject: [PATCH 5/5] Update index.js --- packages/expect/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/expect/src/index.js b/packages/expect/src/index.js index a23c4457606a..e43ebb343987 100644 --- a/packages/expect/src/index.js +++ b/packages/expect/src/index.js @@ -199,7 +199,7 @@ const makeThrowingMatcher = ( } catch (error) { if ( !(error instanceof JestAssertionError) && - error.name != 'PrettyFormatPluginError' && + error.name !== 'PrettyFormatPluginError' && // Guard for some environments (browsers) that do not support this feature. Error.captureStackTrace ) {