Skip to content

Commit cc802fb

Browse files
nicolasiensencpojer
authored andcommitted
Fix stack trace for errors thrown by snapshot serializers (#4787)
* 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 * Update changelog * Make sure new tests have assertions * Update CHANGELOG.md * Update index.js
1 parent 27228e8 commit cc802fb

4 files changed

Lines changed: 108 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* `[pretty-format]` Prevent error in pretty-format for window in jsdom test env ([#4750](https://github.com/facebook/jest/pull/4750))
1818
* `[jest-resolve]` Preserve module identity for symlinks ([#4761](https://github.com/facebook/jest/pull/4761))
1919
* `[jest-config]` Include error message for `preset` json ([#4766](https://github.com/facebook/jest/pull/4766))
20+
* `[pretty-format]` Throw `PrettyFormatPluginError` if a plugin halts with an exception ([#4787](https://github.com/facebook/jest/pull/4787))
21+
* `[expect]` Keep the stack trace unchanged when `PrettyFormatPluginError` is thrown by pretty-format ([#4787](https://github.com/facebook/jest/pull/4787))
2022

2123
### Features
2224
* `[jest-environment-jsdom]` [**BREAKING**] Upgrade to JSDOM@11 ([#4770](https://github.com/facebook/jest/pull/4770))

packages/expect/src/index.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,14 @@ const makeThrowingMatcher = (
197197
try {
198198
result = matcher.apply(matcherContext, [actual].concat(args));
199199
} catch (error) {
200-
if (!(error instanceof JestAssertionError)) {
201-
// Try to remove this and deeper functions from the stack trace frame.
200+
if (
201+
!(error instanceof JestAssertionError) &&
202+
error.name !== 'PrettyFormatPluginError' &&
202203
// Guard for some environments (browsers) that do not support this feature.
203-
if (Error.captureStackTrace) {
204-
Error.captureStackTrace(error, throwingMatcher);
205-
}
204+
Error.captureStackTrace
205+
) {
206+
// Try to remove this and deeper functions from the stack trace frame.
207+
Error.captureStackTrace(error, throwingMatcher);
206208
}
207209
throw error;
208210
}

packages/pretty-format/src/__tests__/pretty_format.test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,66 @@ describe('prettyFormat()', () => {
549549
}).toThrow();
550550
});
551551

552+
it('throws PrettyFormatPluginError if test throws an error', () => {
553+
expect.hasAssertions();
554+
const options = {
555+
plugins: [
556+
{
557+
print: () => '',
558+
test() {
559+
throw new Error('Where is the error?');
560+
},
561+
},
562+
],
563+
};
564+
565+
try {
566+
prettyFormat('', options);
567+
} catch (error) {
568+
expect(error.name).toBe('PrettyFormatPluginError');
569+
}
570+
});
571+
572+
it('throws PrettyFormatPluginError if print throws an error', () => {
573+
expect.hasAssertions();
574+
const options = {
575+
plugins: [
576+
{
577+
print: () => {
578+
throw new Error('Where is the error?');
579+
},
580+
test: () => true,
581+
},
582+
],
583+
};
584+
585+
try {
586+
prettyFormat('', options);
587+
} catch (error) {
588+
expect(error.name).toBe('PrettyFormatPluginError');
589+
}
590+
});
591+
592+
it('throws PrettyFormatPluginError if serialize throws an error', () => {
593+
expect.hasAssertions();
594+
const options = {
595+
plugins: [
596+
{
597+
serialize: () => {
598+
throw new Error('Where is the error?');
599+
},
600+
test: () => true,
601+
},
602+
],
603+
};
604+
605+
try {
606+
prettyFormat('', options);
607+
} catch (error) {
608+
expect(error.name).toBe('PrettyFormatPluginError');
609+
}
610+
});
611+
552612
it('supports plugins with deeply nested arrays (#24)', () => {
553613
const val = [[1, 2], [3, 4]];
554614
expect(

packages/pretty-format/src/index.js

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ const isWindow = val => typeof window !== 'undefined' && val === window;
4848
const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;
4949
const NEWLINE_REGEXP = /\n/gi;
5050

51+
class PrettyFormatPluginError extends Error {
52+
constructor(message, stack) {
53+
super(message);
54+
this.stack = stack;
55+
this.name = this.constructor.name;
56+
}
57+
}
58+
5159
function isToStringedArrayType(toStringed: string): boolean {
5260
return (
5361
toStringed === '[object Array]' ||
@@ -246,25 +254,31 @@ function printPlugin(
246254
depth: number,
247255
refs: Refs,
248256
): string {
249-
const printed = plugin.serialize
250-
? plugin.serialize(val, config, indentation, depth, refs, printer)
251-
: plugin.print(
252-
val,
253-
valChild => printer(valChild, config, indentation, depth, refs),
254-
str => {
255-
const indentationNext = indentation + config.indent;
256-
return (
257-
indentationNext +
258-
str.replace(NEWLINE_REGEXP, '\n' + indentationNext)
259-
);
260-
},
261-
{
262-
edgeSpacing: config.spacingOuter,
263-
min: config.min,
264-
spacing: config.spacingInner,
265-
},
266-
config.colors,
267-
);
257+
let printed;
258+
259+
try {
260+
printed = plugin.serialize
261+
? plugin.serialize(val, config, indentation, depth, refs, printer)
262+
: plugin.print(
263+
val,
264+
valChild => printer(valChild, config, indentation, depth, refs),
265+
str => {
266+
const indentationNext = indentation + config.indent;
267+
return (
268+
indentationNext +
269+
str.replace(NEWLINE_REGEXP, '\n' + indentationNext)
270+
);
271+
},
272+
{
273+
edgeSpacing: config.spacingOuter,
274+
min: config.min,
275+
spacing: config.spacingInner,
276+
},
277+
config.colors,
278+
);
279+
} catch (error) {
280+
throw new PrettyFormatPluginError(error.message, error.stack);
281+
}
268282
if (typeof printed !== 'string') {
269283
throw new Error(
270284
`pretty-format: Plugin must return type "string" but instead returned "${typeof printed}".`,
@@ -275,8 +289,12 @@ function printPlugin(
275289

276290
function findPlugin(plugins: Plugins, val: any) {
277291
for (let p = 0; p < plugins.length; p++) {
278-
if (plugins[p].test(val)) {
279-
return plugins[p];
292+
try {
293+
if (plugins[p].test(val)) {
294+
return plugins[p];
295+
}
296+
} catch (error) {
297+
throw new PrettyFormatPluginError(error.message, error.stack);
280298
}
281299
}
282300

0 commit comments

Comments
 (0)