Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions src/runtime/test_runner/expect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1182,14 +1182,7 @@ impl Expect {
}
}

if value.jest_snapshot_pretty_format(pretty_value, global_this).is_err() {
let mut formatter = ConsoleObject::Formatter::new(global_this);
return Err(global_this.throw(format_args!(
"Failed to pretty format value: {}",
value.to_fmt(&mut formatter),
)));
}
Ok(())
value.jest_snapshot_pretty_format(pretty_value, global_this)
}

pub(crate) fn snapshot(
Expand Down
6 changes: 1 addition & 5 deletions src/runtime/test_runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,7 @@ pub mod expect {
out,
fmt_options,
)?;
// `FormatOptions.flush` is false, so the formatter does not flush
// internally; a buffered `out` would otherwise drop trailing
// snapshot bytes. Propagate the writer error as a thrown JS error
// so the caller's `.is_err()` branch
// (expect.rs `to_match_snapshot_value_kind`) fires.
// The formatter ignores its own flush result; a writer error has to be thrown like any other failure.
out.flush().map_err(|e| global.throw_error(e, "snapshot writer flush failed"))?;
Ok(())
}
Expand Down
104 changes: 104 additions & 0 deletions test/js/bun/test/snapshot-tests/bun-snapshots.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,108 @@ describe("toMatchSnapshot errors", () => {
expect({ a: 4 }).toMatchSnapshot({ a: expect.any("not a constructor") });
}).toThrow();
});

describe("when formatting the received value throws", () => {
// The snapshot formatter reads `$$typeof` off every object (React element
// detection), `size` off Maps and Sets, and JSON-stringifies Dates, so
// user code on any of those runs while the value is being formatted.
const received: [string, () => unknown][] = [
[
"$$typeof getter on the received value",
() => ({
get $$typeof(): unknown {
throw new Error("boom");
},
}),
],
[
// Keep this a single property: until #37331 lands, the property walk
// only surfaces an exception thrown while formatting the last key.
"$$typeof getter on a nested value",
() => ({
a: {
get $$typeof(): unknown {
throw new Error("boom");
},
},
}),
],
[
"size getter on a Map",
() =>
Object.defineProperty(new Map(), "size", {
get() {
throw new Error("boom");
},
}),
],
[
"size getter on a Set",
() =>
Object.defineProperty(new Set(), "size", {
get() {
throw new Error("boom");
},
}),
],
[
"toJSON on a Date",
() =>
Object.assign(new Date(0), {
toJSON() {
throw new Error("boom");
},
}),
],
];

it.each(received)("toMatchSnapshot throws the error from the %s", (_, makeValue) => {
expect(() => expect(makeValue()).toMatchSnapshot()).toThrow("boom");
});

it.each(received)("toMatchInlineSnapshot throws the error from the %s", (_, makeValue) => {
// Passing the inline snapshot means a build that does not throw fails on
// the mismatch instead of writing into this file.
expect(() => expect(makeValue()).toMatchInlineSnapshot(`"never recorded"`)).toThrow("boom");
});

it("throws the exception itself rather than a wrapper", () => {
const error = new Error("boom");
const value = {
get $$typeof(): unknown {
throw error;
},
};

let thrown: unknown;
try {
expect(value).toMatchSnapshot();
} catch (e) {
thrown = e;
}
expect(thrown).toBe(error);

thrown = undefined;
try {
expect(value).toMatchInlineSnapshot(`"never recorded"`);
} catch (e) {
thrown = e;
}
expect(thrown).toBe(error);
});

it("still throws the formatting error after the property matchers matched", () => {
// Fresh object per call: matched property matchers are written into the received object (#3521).
const makeValue = () => ({
n: 1,
get $$typeof(): unknown {
throw new Error("boom");
},
});
expect(() => expect(makeValue()).toMatchSnapshot({ n: expect.any(Number) })).toThrow("boom");
expect(() => expect(makeValue()).toMatchInlineSnapshot({ n: expect.any(Number) }, `"never recorded"`)).toThrow(
"boom",
);
});
});
});