From add65ba4a293ddaa46162f7529a786aab8f094f7 Mon Sep 17 00:00:00 2001 From: Tonio Date: Sun, 16 Aug 2026 10:15:38 -0700 Subject: [PATCH] test(ui): make the suite pass outside UTC (#11480) The ui suite was green in CI and red on a clean checkout in any other timezone. GitHub's runners default to UTC, so nothing ever reported it. A contributor elsewhere sees two failures on their first run, which reads as "this project is broken" rather than "your clock differs from the runner's". Two independent causes. `IssueProperties` supplies a UTC instant and asserts on the local-time string the UI renders from it - "2026-07-17T16:08:00.000Z" is expected to read "Today, 4:08 PM". That holds only where local time is UTC. Pinned with `env: { TZ: "UTC" }` rather than rewritten: those assertions are about what a person sees, and "4:08 PM" is worth more to a reader than an expectation computed from the same formatter the component uses, which would pass whatever that formatter did. `StatusCards/format` was wrong in two ways at once, and the pin hides only one, so it is fixed directly. `rollupUpdatesToday` filters on the *UTC* day boundary, while the test built fixtures from local noon on the real clock. East of UTC+12, "today at local noon" is already yesterday in UTC and the rows the test means to count are filtered out; and any run crossing midnight UTC lands `iso(0)` and the function's default `now` on different days. The fixtures now come from a fixed instant, passed as `now` - the parameter exists for this, and the sibling test already used it. Each fix was confirmed load-bearing by removing it under TZ=Pacific/Auckland. Without the pin, IssueProperties fails; without the fixed instant, StatusCards fails even with the pin removed, so neither rides on the other. Full ui suite 4017 pass, 0 fail, in UTC, Pacific/Auckland and Asia/Kolkata. Co-Authored-By: Claude Opus 5 --- ui/src/pages/StatusCards/format.test.ts | 18 +++++++++++++----- ui/vitest.config.ts | 9 +++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/ui/src/pages/StatusCards/format.test.ts b/ui/src/pages/StatusCards/format.test.ts index 5a39b0a925..d44f58e3e4 100644 --- a/ui/src/pages/StatusCards/format.test.ts +++ b/ui/src/pages/StatusCards/format.test.ts @@ -30,11 +30,19 @@ function update(overrides: Partial): StatusCardUpdate { }; } +// A fixed instant rather than the real clock. `rollupUpdatesToday` filters on +// the *UTC* day boundary, so a suite that builds its fixtures from `new Date()` +// fails in two ways: it straddles midnight UTC if the run happens to cross it, +// and in any zone east of UTC+12 "today at local noon" is already yesterday in +// UTC, so the rows it means to count are filtered out. The function takes `now` +// for exactly this reason — the sibling test below already passes one. +const NOW = new Date("2026-07-23T12:00:00.000Z"); + function iso(daysAgo: number): string { - const d = new Date(); - d.setDate(d.getDate() - daysAgo); - // Noon avoids DST/midnight edge cases in the local-day filter. - d.setHours(12, 0, 0, 0); + const d = new Date(NOW); + d.setUTCDate(d.getUTCDate() - daysAgo); + // Noon UTC, so a row is unambiguously inside the UTC day it belongs to. + d.setUTCHours(12, 0, 0, 0); return d.toISOString(); } @@ -61,7 +69,7 @@ describe("rollupUpdatesToday", () => { // yesterday + last week — must not be counted as "today" update({ kind: "full", inputTokens: 9999, outputTokens: 9999, costCents: 99, startedAt: iso(1) }), update({ kind: "incremental", inputTokens: 9999, outputTokens: 9999, costCents: 99, startedAt: iso(7) }), - ]); + ], NOW); // Only today's full rebuild counts as an update (compile excluded). expect(rollup.updateCount).toBe(1); // Today's tokens/cost include today's compile but not older days. diff --git a/ui/vitest.config.ts b/ui/vitest.config.ts index 8ffcafbe9e..2ae4a757cb 100644 --- a/ui/vitest.config.ts +++ b/ui/vitest.config.ts @@ -11,5 +11,14 @@ export default defineConfig({ test: { environment: "node", setupFiles: ["./vitest.setup.ts"], + // Pin the clock's timezone so date rendering is the same everywhere. + // + // Several suites supply a UTC instant and assert on the local-time string + // the UI renders from it — "2026-07-17T16:08:00.000Z" is expected to read + // "Today, 4:08 PM". That only holds where local time is UTC. CI passes + // because GitHub's runners happen to default to UTC; a contributor in any + // other zone sees those tests fail on a clean checkout, which reads as + // "the suite is broken" rather than "your clock differs". + env: { TZ: "UTC" }, }, });