From 4015e89c3baee56f2e415132bd864af95a8f32a8 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:15:43 +0000 Subject: [PATCH 1/5] expect/deepEquals: treat two Invalid Dates as equal and fix their failure-message rendering Two Dates with NaN time values now compare equal in deepEquals (toEqual, toStrictEqual, Bun.deepEquals, assert.deepStrictEqual, util.isDeepStrictEqual), matching Node.js. The test runner's failure diff printed a sliced 'null' ("ul") for Invalid Date because JSON.stringify(new Date(NaN)) is unquoted null; it now prints "Invalid Date" like console.log already does. Fixes #34816 --- src/jsc/bindings/bindings.cpp | 5 +++- src/runtime/test_runner/pretty_format.rs | 5 +++- test/js/bun/bun-object/deep-equals.spec.ts | 2 ++ .../test/expect-invalid-date-message.test.ts | 29 +++++++++++++++++++ test/js/bun/test/expect.test.js | 8 +++++ test/js/node/assert/deep-equal.test.ts | 2 ++ 6 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 test/js/bun/test/expect-invalid-date-message.test.ts diff --git a/src/jsc/bindings/bindings.cpp b/src/jsc/bindings/bindings.cpp index c82c4a503b98..af9b9d441fdf 100644 --- a/src/jsc/bindings/bindings.cpp +++ b/src/jsc/bindings/bindings.cpp @@ -1180,7 +1180,10 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark JSC::DateInstance* left = uncheckedDowncast(c1); JSC::DateInstance* right = uncheckedDowncast(c2); - return left->internalNumber() == right->internalNumber(); + double leftTime = left->internalNumber(); + double rightTime = right->internalNumber(); + // Two Invalid Dates (NaN time values) are equal, matching Node.js. + return leftTime == rightTime || (std::isnan(leftTime) && std::isnan(rightTime)); } case RegExpObjectType: { if (c2Type != RegExpObjectType) { diff --git a/src/runtime/test_runner/pretty_format.rs b/src/runtime/test_runner/pretty_format.rs index 0c59618aa3d4..181c2cb720ca 100644 --- a/src/runtime/test_runner/pretty_format.rs +++ b/src/runtime/test_runner/pretty_format.rs @@ -1937,7 +1937,10 @@ impl<'a> Formatter<'a> { Err(_) => b"", } }; - if out_buf.len() > 2 { + if out_buf == b"null" { + // JSON.stringify(new Date(NaN)) is `null` (unquoted) + out_buf = b"Invalid Date"; + } else if out_buf.len() > 2 { // trim the quotes out_buf = &out_buf[1..out_buf.len() - 1]; } diff --git a/test/js/bun/bun-object/deep-equals.spec.ts b/test/js/bun/bun-object/deep-equals.spec.ts index ddce5ed77cda..e445349152d2 100644 --- a/test/js/bun/bun-object/deep-equals.spec.ts +++ b/test/js/bun/bun-object/deep-equals.spec.ts @@ -14,6 +14,7 @@ describe.each([true, false])("Bun.deepEquals(a, b, strict: %p)", strict => { [new Set(), new Set()], [Symbol.for("foo"), Symbol.for("foo")], [NaN, NaN], + [new Date(NaN), new Date(NaN)], ])("Bun.deepEquals(%p, %p) === true, regardless of strict modee", (a, b) => { expect(Bun.deepEquals(a, b, true)).toBe(true); expect(Bun.deepEquals(a, b, false)).toBe(true); @@ -24,6 +25,7 @@ describe.each([true, false])("Bun.deepEquals(a, b, strict: %p)", strict => { [-0, +0], // [{ a: 1 }, { a: 2 }], ["foo", "bar"], + [new Date(NaN), new Date(0)], ])("Bun.deepEquals(%p, %p) !== true, regardless of strict modee", (a, b) => { expect(Bun.deepEquals(a, b, true)).toBe(false); expect(Bun.deepEquals(a, b, false)).toBe(false); diff --git a/test/js/bun/test/expect-invalid-date-message.test.ts b/test/js/bun/test/expect-invalid-date-message.test.ts new file mode 100644 index 000000000000..4a09eb776ebb --- /dev/null +++ b/test/js/bun/test/expect-invalid-date-message.test.ts @@ -0,0 +1,29 @@ +// Failure messages for Invalid Date must print "Invalid Date". +// JSON.stringify(new Date(NaN)) is `null` (unquoted), and the quote-trimming +// pass used to slice it down to the garbage string "ul". + +import { expect, test } from "bun:test"; +import { bunEnv, bunExe, tempDir } from "harness"; + +test("expect() failure messages render Invalid Date, not a sliced 'null'", async () => { + using dir = tempDir("expect-invalid-date", { + "invalid-date.test.ts": ` + import { test, expect } from "bun:test"; + test("invalid date vs valid date", () => { + expect(new Date(NaN)).toEqual(new Date(0)); + }); + `, + }); + await using proc = Bun.spawn({ + cmd: [bunExe(), "test", "invalid-date.test.ts"], + env: { ...bunEnv, NO_COLOR: "1", FORCE_COLOR: undefined }, + cwd: String(dir), + stdout: "pipe", + stderr: "pipe", + }); + const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]); + + expect(stderr).toContain("Expected: 1970-01-01T00:00:00.000Z"); + expect(stderr).toContain("Received: Invalid Date"); + expect(exitCode).toBe(1); +}); diff --git a/test/js/bun/test/expect.test.js b/test/js/bun/test/expect.test.js index 92a569d6ce08..5d90b50d3952 100644 --- a/test/js/bun/test/expect.test.js +++ b/test/js/bun/test/expect.test.js @@ -722,6 +722,14 @@ describe("expect()", () => { expect(d).toEqual(e); expect(e).toEqual(d); + if (isBun) { + // Two Invalid Dates are equal, matching Node.js (jest disagrees). + expect(new Date(NaN)).toEqual(new Date(NaN)); + expect(new Date(NaN)).toStrictEqual(new Date(NaN)); + expect(new Date(NaN)).not.toEqual(new Date(0)); + expect(new Date(0)).not.toEqual(new Date(NaN)); + } + class Date2 extends Date { constructor() { // @ts-ignore diff --git a/test/js/node/assert/deep-equal.test.ts b/test/js/node/assert/deep-equal.test.ts index 9a507e1ceb04..b8e249a3382a 100644 --- a/test/js/node/assert/deep-equal.test.ts +++ b/test/js/node/assert/deep-equal.test.ts @@ -287,6 +287,8 @@ const cases: Case[] = [ // Date. { name: "two equal dates", a: () => new Date(0), b: () => new Date(0), strict: true, loose: true }, { name: "two different dates", a: () => new Date(0), b: () => new Date(1), strict: false, loose: false }, + { name: "two invalid dates", a: () => new Date(NaN), b: () => new Date(NaN), strict: true, loose: true }, + { name: "an invalid and a valid date", a: () => new Date(NaN), b: () => new Date(0), strict: false, loose: false }, { name: "a date with an extra own property", a: () => withExtraProperty(new Date(0)), From 3cc58c6333d2a7e33ab47ba7c597b84d9f30f4c1 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:24:32 +0000 Subject: [PATCH 2/5] test: drain stdout in invalid date message test --- test/js/bun/test/expect-invalid-date-message.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/js/bun/test/expect-invalid-date-message.test.ts b/test/js/bun/test/expect-invalid-date-message.test.ts index 4a09eb776ebb..384e2ccd9679 100644 --- a/test/js/bun/test/expect-invalid-date-message.test.ts +++ b/test/js/bun/test/expect-invalid-date-message.test.ts @@ -21,7 +21,7 @@ test("expect() failure messages render Invalid Date, not a sliced 'null'", async stdout: "pipe", stderr: "pipe", }); - const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]); + const [, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); expect(stderr).toContain("Expected: 1970-01-01T00:00:00.000Z"); expect(stderr).toContain("Received: Invalid Date"); From f99d21d298bb53ac2b130d3ecdbdfff1cafc9a96 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:03:43 +0000 Subject: [PATCH 3/5] ci: retrigger From 825a3fcac79a83986f1cc4d24042819608873cd8 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:14:33 +0000 Subject: [PATCH 4/5] test: cover toStrictEqual for invalid vs valid Date --- test/js/bun/test/expect.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/js/bun/test/expect.test.js b/test/js/bun/test/expect.test.js index 5d90b50d3952..af47356a867b 100644 --- a/test/js/bun/test/expect.test.js +++ b/test/js/bun/test/expect.test.js @@ -727,7 +727,9 @@ describe("expect()", () => { expect(new Date(NaN)).toEqual(new Date(NaN)); expect(new Date(NaN)).toStrictEqual(new Date(NaN)); expect(new Date(NaN)).not.toEqual(new Date(0)); + expect(new Date(NaN)).not.toStrictEqual(new Date(0)); expect(new Date(0)).not.toEqual(new Date(NaN)); + expect(new Date(0)).not.toStrictEqual(new Date(NaN)); } class Date2 extends Date { From 6e737f32c2ca70b48bf68912eabb850c28731321 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:46:44 +0000 Subject: [PATCH 5/5] chore: rerun checks