mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
fix(desktop): pin the transcript baseline fixture's locale too
Pinning TZ fixed one ambient input to the captured-markup fixture and missed a second one. The session-boundary divider formats its date with a bare `toLocaleString()` (`AgentSessionTranscriptChrome.tsx`), which is locale-sensitive as well as zone-sensitive: the fixture holds "6/14/2026, 7:05:00 PM", and under `LANG=de-DE` the same render produces "14.6.2026, 19:05:00". Node takes its default locale from LANG/LC_ALL, so the byte-for-byte comparison failed on any machine with a non-US locale — the same class of bug as the timezone one, one variable over, and the TZ guard passed happily while it happened. Assigning `process.env.LANG` at runtime does not fix this: node resolves its default locale once at startup, so unlike `TZ` (which `Date` reads lazily) the assignment is silently ineffective. Pinning the locale means overriding the two formatting surfaces the render path reaches — `Intl.DateTimeFormat` constructed without an explicit locale, and `Date.prototype.toLocale*`, which does not route through `Intl.DateTimeFormat` and needs its own patch. `Intl.DateTimeFormat` must be replaced with a plain function, not an arrow: the render path calls it with `new`, and an arrow function is not a constructor. The guard assertion now covers locale and `toLocaleString` output alongside the zone, so a future drift fails with a specific message instead of a 12KB markup diff. Verified: the de-DE failure reproduces before this change, and the suite passes after it under TZ/LANG combinations UTC/en-US, UTC/de-DE, America/New_York/ja-JP, Asia/Tokyo/de-DE, and UTC with LANG unset. Signed-off-by: ss-dev-01 <11939edb7df583f855dbef923f2358f1184538f88ca452e19e7e35e42ad6d796@buzz.block.builderlab.xyz> Co-authored-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Bradley Axen <baxen@squareup.com>
This commit is contained in:
@@ -20,15 +20,54 @@ import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { after, afterEach, before, test } from "node:test";
|
||||
|
||||
// The captured markup embeds locally-formatted timestamps ("… at 7:00:01 PM"),
|
||||
// because `formatTranscriptTimestampTitle` formats in the ambient zone. Pin the
|
||||
// zone so the fixture is reproducible on any developer machine and in CI (which
|
||||
// runs UTC) — otherwise the byte-for-byte comparison fails purely on the
|
||||
// capturer's offset. Must be set before the transcript modules are imported:
|
||||
// their `Intl.DateTimeFormat` instances are module-level constants that resolve
|
||||
// the zone once, at construction.
|
||||
// The captured markup embeds formatted dates and times, so the fixture is only
|
||||
// reproducible if every ambient formatting input is pinned. Two of them bite:
|
||||
//
|
||||
// - **Zone.** `formatTranscriptTimestampTitle` formats in the ambient zone
|
||||
// ("… at 7:00:01 PM"), so a capture at UTC-7 fails against CI's UTC.
|
||||
// - **Locale.** The session-boundary divider uses a bare `toLocaleString()`
|
||||
// (`AgentSessionTranscriptChrome.tsx`), which is locale-sensitive as well as
|
||||
// zone-sensitive: "6/14/2026, 7:05:00 PM" becomes "14.6.2026, 19:05:00"
|
||||
// under de-DE. Node derives its default locale from LANG/LC_ALL, so this
|
||||
// varies by machine independently of the zone.
|
||||
//
|
||||
// `TZ` can be set here because `Date` reads it lazily. The locale CANNOT: node
|
||||
// resolves its default locale once at startup, so assigning `process.env.LANG`
|
||||
// at runtime has no effect (verified — it silently keeps the startup locale).
|
||||
// Pinning it therefore means overriding the two formatting surfaces the render
|
||||
// path can reach: `Intl.DateTimeFormat` when constructed with no explicit
|
||||
// locale, and `Date.prototype.toLocale*`, which does NOT route through
|
||||
// `Intl.DateTimeFormat` and so needs its own patch.
|
||||
//
|
||||
// All of this must happen before the transcript modules are imported: their
|
||||
// `Intl.DateTimeFormat` instances are module-level constants that resolve zone
|
||||
// and locale once, at construction.
|
||||
process.env.TZ = "UTC";
|
||||
|
||||
const FIXTURE_LOCALE = "en-US";
|
||||
const OriginalDateTimeFormat = Intl.DateTimeFormat;
|
||||
// A plain function, not an arrow: the render path calls
|
||||
// `new Intl.DateTimeFormat(...)`, and an arrow function is not a constructor.
|
||||
// Returning a genuine instance keeps `new`, plain calls, and `instanceof` all
|
||||
// working.
|
||||
function LocalePinnedDateTimeFormat(locales, options) {
|
||||
return new OriginalDateTimeFormat(locales ?? FIXTURE_LOCALE, options);
|
||||
}
|
||||
LocalePinnedDateTimeFormat.prototype = OriginalDateTimeFormat.prototype;
|
||||
LocalePinnedDateTimeFormat.supportedLocalesOf =
|
||||
OriginalDateTimeFormat.supportedLocalesOf.bind(OriginalDateTimeFormat);
|
||||
Intl.DateTimeFormat = LocalePinnedDateTimeFormat;
|
||||
for (const method of [
|
||||
"toLocaleString",
|
||||
"toLocaleDateString",
|
||||
"toLocaleTimeString",
|
||||
]) {
|
||||
const original = Date.prototype[method];
|
||||
Date.prototype[method] = function (locales, options) {
|
||||
return original.call(this, locales ?? FIXTURE_LOCALE, options);
|
||||
};
|
||||
}
|
||||
|
||||
import { JSDOM } from "jsdom";
|
||||
|
||||
const BASELINE_MARKUP = JSON.parse(
|
||||
@@ -740,14 +779,24 @@ test("default and compactPreview markup is unchanged by the conversation variant
|
||||
});
|
||||
|
||||
test("the byte-for-byte baseline actually exercises every renderable item kind", async () => {
|
||||
// The fixture embeds formatted timestamps, so a zone other than the one it was
|
||||
// captured in fails the comparison above for a reason that has nothing to do
|
||||
// with markup. Fail loudly and specifically instead.
|
||||
// The fixture embeds formatted dates and times, so a zone or locale other than
|
||||
// the one it was captured in fails the comparison above for a reason that has
|
||||
// nothing to do with markup. Fail loudly and specifically instead.
|
||||
assert.equal(
|
||||
new Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
"UTC",
|
||||
"the baseline fixture is captured in UTC — see the TZ pin at the top of this file",
|
||||
);
|
||||
assert.equal(
|
||||
new Intl.DateTimeFormat().resolvedOptions().locale,
|
||||
FIXTURE_LOCALE,
|
||||
`the baseline fixture is captured in ${FIXTURE_LOCALE} — see the locale pin at the top of this file`,
|
||||
);
|
||||
assert.equal(
|
||||
new Date("2026-06-14T19:05:00.000Z").toLocaleString(),
|
||||
"6/14/2026, 7:05:00 PM",
|
||||
"Date.prototype.toLocaleString must be locale-pinned too — the session-boundary divider uses it",
|
||||
);
|
||||
// Guards the fixture itself: a baseline that silently stopped covering a kind
|
||||
// would keep passing the comparison above while protecting nothing. Asserted
|
||||
// against `default`, which renders every kind.
|
||||
|
||||
Reference in New Issue
Block a user