mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
chore(release): v1.28.35 — timestamps honor the profile timezone
Client-rendered times fell back to the hardcoded display zone (Europe/Berlin) because neither useFormatters() nor the legacy format.ts helpers ever received users.timezone. A third localStorage mirror (timezone-mirror.ts, following the time-format/date-format pattern) carries the validated profile zone from /api/auth/me into both read paths; the fallback chain is strictly profile → Europe/Berlin — never the browser zone, so screen, PDF, and export cannot disagree. makeFormatters validates its zone before Intl (a poison value falls back instead of throwing inside every render). Same pass, per the two-document audit: formatUpdatedLabel boundary and clock now resolve one zone in every state (the undefined-tz browser hole is closed); @db.Date calendar fields render UTC-pinned so profiles west of UTC cannot shift a day; dose-history day headings and GLP-1 weekday labels derive from the display zone; chart day/month labels are encoding-derived and UTC-pinned (bucket math untouched, byte-identical for Berlin profiles); the clinician share view renders in the patient's timezone instead of the container's; Playwright pins Europe/Berlin. Server day-key semantics, streak anchoring, insight cache keys, and verbatim wall-clock strings are untouched by construction and pinned by the new Auckland/New_York/Manila + DST test matrix.
This commit is contained in:
1 parent
136653e3d7
commit
9bbd8a89dc
41 files changed
+958
-90
No files matched your search
@@ -0,0 +1,100 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
|
||||
/**
|
||||
* Issue #490 — client-rendered timestamps follow the PROFILE timezone.
|
||||
*
|
||||
* `/api/auth/me` is mocked with an `Asia/Manila` profile; the sessions
|
||||
* card on /settings/privacy renders `lastActiveAt` through
|
||||
* `useFormatters()`, which reads the localStorage timezone mirror that
|
||||
* `fetchMe` fills from the mocked payload. A fixed UTC instant must
|
||||
* therefore paint the MANILA wall clock (14:30), never the Berlin
|
||||
* fallback (08:30) and never the browser zone (the Playwright context is
|
||||
* pinned to Europe/Berlin precisely so this assertion is meaningful).
|
||||
*
|
||||
* Asserts against the stable `data-slot="security-session-row"` hook,
|
||||
* not viewport-dependent text.
|
||||
*/
|
||||
|
||||
// 06:30 UTC = 14:30 Asia/Manila (+8, no DST) = 08:30 Europe/Berlin (CEST).
|
||||
const LAST_ACTIVE_UTC = "2026-07-14T06:30:00.000Z";
|
||||
|
||||
test.describe("profile-timezone display (#490)", () => {
|
||||
test.use({ storageState: STORAGE_STATE_PATH });
|
||||
|
||||
test("sessions card renders lastActiveAt in the Manila profile zone", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.route("**/api/auth/me", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
id: "user_e2e",
|
||||
username: "e2e-tester",
|
||||
email: "e2e@healthlog.test",
|
||||
role: "USER",
|
||||
heightCm: 180,
|
||||
dateOfBirth: "1990-01-01",
|
||||
gender: "MALE",
|
||||
timezone: "Asia/Manila",
|
||||
timeFormat: "H24",
|
||||
dateFormat: "AUTO",
|
||||
onboardingCompletedAt: "2025-01-01T00:00:00.000Z",
|
||||
onboardingTourCompleted: true,
|
||||
gravatarUrl: null,
|
||||
glucoseUnit: "mg/dL",
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.route("**/api/auth/me/sessions", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
sessions: [
|
||||
{
|
||||
id: "sess_e2e_current",
|
||||
device: "Chrome on macOS",
|
||||
ipMasked: "192.168.1.xxx",
|
||||
location: null,
|
||||
lastActiveAt: LAST_ACTIVE_UTC,
|
||||
createdAt: LAST_ACTIVE_UTC,
|
||||
isCurrent: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.goto("/settings/privacy", { waitUntil: "domcontentloaded" });
|
||||
|
||||
// The sessions card is collapsed by default — expand it first.
|
||||
const toggle = page.locator(
|
||||
'[data-slot="settings-security-sessions-toggle"]',
|
||||
);
|
||||
await expect(toggle).toBeVisible({ timeout: 10_000 });
|
||||
await toggle.click();
|
||||
|
||||
const row = page.locator('[data-slot="security-session-row"]').first();
|
||||
await expect(row).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// Manila wall clock (H24). The mirror write from the mocked /me may
|
||||
// land after the first paint of the sessions row — the poll absorbs
|
||||
// the re-render.
|
||||
await expect(row).toContainText("14:30", { timeout: 10_000 });
|
||||
// Never the Berlin fallback for this instant…
|
||||
await expect(row).not.toContainText("08:30");
|
||||
// …and never a 12-hour rendering (the H24 preference rides the same
|
||||
// mirror pipeline).
|
||||
await expect(row).not.toContainText("PM");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user