From 4cd397f2c6cf57c1a5e4cb43ebd7fb23df06247c Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:40:35 +0000 Subject: [PATCH 1/3] Bump WebKit for Temporal fixes: PlainDate.from options order, week rounding at the minimum date, exact era codes Picks up oven-sh/WebKit#600 and adds the three cases to test/js/web/temporal/temporal.test.ts. --- scripts/build/deps/webkit.ts | 2 +- test/js/web/temporal/temporal.test.ts | 74 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/scripts/build/deps/webkit.ts b/scripts/build/deps/webkit.ts index 7e8ddecce2ac..ed799d872e1d 100644 --- a/scripts/build/deps/webkit.ts +++ b/scripts/build/deps/webkit.ts @@ -3,7 +3,7 @@ * for local mode. Override via `--webkit-version=` to test a branch. * From https://github.com/oven-sh/WebKit releases. */ -export const WEBKIT_VERSION = "2e2aa2290fac856d6f451ceacb58f7f5b44dd057"; +export const WEBKIT_VERSION = "autobuild-preview-pr-600-cbbbe00e"; /** * WebKit (JavaScriptCore) — the JS engine. diff --git a/test/js/web/temporal/temporal.test.ts b/test/js/web/temporal/temporal.test.ts index d2d1deb7044a..295f03cf9afc 100644 --- a/test/js/web/temporal/temporal.test.ts +++ b/test/js/web/temporal/temporal.test.ts @@ -86,3 +86,77 @@ describe("Temporal core operations", () => { expect(() => structuredClone(Temporal.Instant.from("2024-06-15T00:00Z"))).toThrow(DOMException); }); }); + +// Divergences from V8 found by differential fuzzing. Expected values are what Node 26 / Chromium 148 print. +describe("Temporal spec conformance", () => { + test("PlainDate.from rejects a non-object options before it resolves the calendar fields", () => { + // ToTemporalDate: PrepareCalendarFields, then GetOptionsObject (TypeError), then CalendarDateFromFields + // (era resolution and the ISODateWithinLimits check, both RangeError). + const lateFailingBags = [ + { year: -271821, month: 2, day: 31 }, + { year: 275760, month: 9, day: 14 }, + { era: "xx", eraYear: 1, month: 1, day: 1, calendar: "gregory" }, + { year: 2024, month: 13, day: 1 }, + ]; + for (const bag of lateFailingBags) { + for (const options of [null, "constrain", 1, true]) { + expect(() => Temporal.PlainDate.from(bag, options as any)).toThrow(TypeError); + } + } + // The bag is still read first, and PrepareCalendarFields errors still win. + const reads: PropertyKey[] = []; + const bag = new Proxy({ year: -271821, month: 2, day: 31 }, { get: (t, k, r) => (reads.push(k), Reflect.get(t, k, r)) }); + expect(() => Temporal.PlainDate.from(bag, null as any)).toThrow(TypeError); + expect(reads).toEqual(["calendar", "day", "month", "monthCode", "year"]); + expect(() => Temporal.PlainDate.from({ year: 2024, month: 0, day: 1 }, null as any)).toThrow(RangeError); + // With a real options object the late RangeErrors surface. + expect(() => Temporal.PlainDate.from(lateFailingBags[0], {})).toThrow(RangeError); + expect(() => Temporal.PlainDate.from(lateFailingBags[2], {})).toThrow(RangeError); + expect(Temporal.PlainDate.from(lateFailingBags[3], {}).toString()).toBe("2024-12-01"); + }); + + test("rounding to weeks accepts a window edge on the minimum PlainDate", () => { + const later = Temporal.PlainDate.from("2016-02-29"); + const nearMin = Temporal.PlainDate.from("-271821-04-20"); + const results = Object.fromEntries( + ["trunc", "floor", "expand", "halfExpand"].map(roundingMode => [ + roundingMode, + later.since(nearMin, { smallestUnit: "weeks", roundingMode: roundingMode as Temporal.RoundingMode }).toString(), + ]), + ); + expect(results).toEqual({ + trunc: "P14288122W", + floor: "P14288122W", + expand: "P14288123W", + halfExpand: "P14288123W", + }); + expect(nearMin.until(later, { smallestUnit: "weeks" }).toString()).toBe("P14288122W"); + expect(later.since(nearMin, { smallestUnit: "days", roundingIncrement: 7 }).toString()).toBe("P100016854D"); + expect( + Temporal.Duration.from({ days: -100016860 }) + .round({ relativeTo: later, smallestUnit: "weeks", roundingMode: "trunc" }) + .toString(), + ).toBe("-P14288122W"); + // An edge that is really below the minimum still throws (here: the exact minimum as operand). + expect(() => later.since("-271821-04-19", { smallestUnit: "weeks" })).toThrow(RangeError); + }); + + test("era codes match the lowercase CLDR identifiers exactly", () => { + for (const [calendar, era] of [ + ["gregory", "CE"], + ["gregory", "Bce"], + ["gregory", "AD"], + ["japanese", "Reiwa"], + ["hebrew", "AM"], + ]) { + expect(() => Temporal.PlainDate.from({ era, eraYear: 1, month: 1, day: 1, calendar })).toThrow(RangeError); + } + expect(Temporal.PlainDate.from({ era: "ce", eraYear: 1, month: 1, day: 1, calendar: "gregory" }).toString()).toBe( + "0001-01-01[u-ca=gregory]", + ); + expect(Temporal.PlainDate.from({ era: "ad", eraYear: 1, month: 1, day: 1, calendar: "gregory" }).era).toBe("ce"); + expect(Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, month: 5, day: 1, calendar: "japanese" }).toString()).toBe( + "2019-05-01[u-ca=japanese]", + ); + }); +}); From 6185970e331107778f3bff5fd21e89f210a523cb Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:36:17 +0000 Subject: [PATCH 2/3] [autofix.ci] apply automated fixes --- test/js/web/temporal/temporal.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/js/web/temporal/temporal.test.ts b/test/js/web/temporal/temporal.test.ts index 295f03cf9afc..6610d6031acb 100644 --- a/test/js/web/temporal/temporal.test.ts +++ b/test/js/web/temporal/temporal.test.ts @@ -105,7 +105,10 @@ describe("Temporal spec conformance", () => { } // The bag is still read first, and PrepareCalendarFields errors still win. const reads: PropertyKey[] = []; - const bag = new Proxy({ year: -271821, month: 2, day: 31 }, { get: (t, k, r) => (reads.push(k), Reflect.get(t, k, r)) }); + const bag = new Proxy( + { year: -271821, month: 2, day: 31 }, + { get: (t, k, r) => (reads.push(k), Reflect.get(t, k, r)) }, + ); expect(() => Temporal.PlainDate.from(bag, null as any)).toThrow(TypeError); expect(reads).toEqual(["calendar", "day", "month", "monthCode", "year"]); expect(() => Temporal.PlainDate.from({ year: 2024, month: 0, day: 1 }, null as any)).toThrow(RangeError); @@ -155,8 +158,8 @@ describe("Temporal spec conformance", () => { "0001-01-01[u-ca=gregory]", ); expect(Temporal.PlainDate.from({ era: "ad", eraYear: 1, month: 1, day: 1, calendar: "gregory" }).era).toBe("ce"); - expect(Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, month: 5, day: 1, calendar: "japanese" }).toString()).toBe( - "2019-05-01[u-ca=japanese]", - ); + expect( + Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, month: 5, day: 1, calendar: "japanese" }).toString(), + ).toBe("2019-05-01[u-ca=japanese]"); }); }); From 667e493dcae6cca80ab55ba5942a98391fd4c578 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:48:31 +0000 Subject: [PATCH 3/3] temporal.test.ts: one test per bag and per era spelling --- test/js/web/temporal/temporal.test.ts | 76 +++++++++++++++------------ 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/test/js/web/temporal/temporal.test.ts b/test/js/web/temporal/temporal.test.ts index 6610d6031acb..401d0f3bd8ee 100644 --- a/test/js/web/temporal/temporal.test.ts +++ b/test/js/web/temporal/temporal.test.ts @@ -89,33 +89,36 @@ describe("Temporal core operations", () => { // Divergences from V8 found by differential fuzzing. Expected values are what Node 26 / Chromium 148 print. describe("Temporal spec conformance", () => { - test("PlainDate.from rejects a non-object options before it resolves the calendar fields", () => { - // ToTemporalDate: PrepareCalendarFields, then GetOptionsObject (TypeError), then CalendarDateFromFields - // (era resolution and the ISODateWithinLimits check, both RangeError). - const lateFailingBags = [ - { year: -271821, month: 2, day: 31 }, - { year: 275760, month: 9, day: 14 }, - { era: "xx", eraYear: 1, month: 1, day: 1, calendar: "gregory" }, - { year: 2024, month: 13, day: 1 }, - ]; - for (const bag of lateFailingBags) { + // ToTemporalDate: PrepareCalendarFields, then GetOptionsObject (TypeError), then CalendarDateFromFields + // (era resolution and the ISODateWithinLimits check, both RangeError). + describe("PlainDate.from rejects a non-object options before it resolves the calendar fields", () => { + test.each([ + [{ year: -271821, month: 2, day: 31 }, RangeError], + [{ year: 275760, month: 9, day: 14 }, RangeError], + [{ era: "xx", eraYear: 1, month: 1, day: 1, calendar: "gregory" }, RangeError], + [{ year: 2024, month: 13, day: 1 }, "2024-12-01"], + ] as const)("%j", (bag, withValidOptions) => { for (const options of [null, "constrain", 1, true]) { expect(() => Temporal.PlainDate.from(bag, options as any)).toThrow(TypeError); } - } - // The bag is still read first, and PrepareCalendarFields errors still win. - const reads: PropertyKey[] = []; - const bag = new Proxy( - { year: -271821, month: 2, day: 31 }, - { get: (t, k, r) => (reads.push(k), Reflect.get(t, k, r)) }, - ); - expect(() => Temporal.PlainDate.from(bag, null as any)).toThrow(TypeError); - expect(reads).toEqual(["calendar", "day", "month", "monthCode", "year"]); - expect(() => Temporal.PlainDate.from({ year: 2024, month: 0, day: 1 }, null as any)).toThrow(RangeError); - // With a real options object the late RangeErrors surface. - expect(() => Temporal.PlainDate.from(lateFailingBags[0], {})).toThrow(RangeError); - expect(() => Temporal.PlainDate.from(lateFailingBags[2], {})).toThrow(RangeError); - expect(Temporal.PlainDate.from(lateFailingBags[3], {}).toString()).toBe("2024-12-01"); + // With a real options object the late RangeError (or the constrained date) surfaces. + if (typeof withValidOptions === "string") { + expect(Temporal.PlainDate.from(bag, {}).toString()).toBe(withValidOptions); + } else { + expect(() => Temporal.PlainDate.from(bag, {})).toThrow(withValidOptions); + } + }); + + test("the bag is read first, and PrepareCalendarFields errors still win", () => { + const reads: PropertyKey[] = []; + const bag = new Proxy( + { year: -271821, month: 2, day: 31 }, + { get: (t, k, r) => (reads.push(k), Reflect.get(t, k, r)) }, + ); + expect(() => Temporal.PlainDate.from(bag, null as any)).toThrow(TypeError); + expect(reads).toEqual(["calendar", "day", "month", "monthCode", "year"]); + expect(() => Temporal.PlainDate.from({ year: 2024, month: 0, day: 1 }, null as any)).toThrow(RangeError); + }); }); test("rounding to weeks accepts a window edge on the minimum PlainDate", () => { @@ -144,22 +147,25 @@ describe("Temporal spec conformance", () => { expect(() => later.since("-271821-04-19", { smallestUnit: "weeks" })).toThrow(RangeError); }); - test("era codes match the lowercase CLDR identifiers exactly", () => { - for (const [calendar, era] of [ + describe("era codes match the lowercase CLDR identifiers exactly", () => { + test.each([ ["gregory", "CE"], ["gregory", "Bce"], ["gregory", "AD"], ["japanese", "Reiwa"], ["hebrew", "AM"], - ]) { + ])("%s rejects %s", (calendar, era) => { expect(() => Temporal.PlainDate.from({ era, eraYear: 1, month: 1, day: 1, calendar })).toThrow(RangeError); - } - expect(Temporal.PlainDate.from({ era: "ce", eraYear: 1, month: 1, day: 1, calendar: "gregory" }).toString()).toBe( - "0001-01-01[u-ca=gregory]", - ); - expect(Temporal.PlainDate.from({ era: "ad", eraYear: 1, month: 1, day: 1, calendar: "gregory" }).era).toBe("ce"); - expect( - Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, month: 5, day: 1, calendar: "japanese" }).toString(), - ).toBe("2019-05-01[u-ca=japanese]"); + }); + + test("lowercase codes and aliases resolve", () => { + expect(Temporal.PlainDate.from({ era: "ce", eraYear: 1, month: 1, day: 1, calendar: "gregory" }).toString()).toBe( + "0001-01-01[u-ca=gregory]", + ); + expect(Temporal.PlainDate.from({ era: "ad", eraYear: 1, month: 1, day: 1, calendar: "gregory" }).era).toBe("ce"); + expect( + Temporal.PlainDate.from({ era: "reiwa", eraYear: 1, month: 5, day: 1, calendar: "japanese" }).toString(), + ).toBe("2019-05-01[u-ca=japanese]"); + }); }); });