mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
The sessions card toggle's onClick could be dropped when Playwright clicked before React hydrated the handler, leaving the region hidden for the whole wait and failing intermittently in CI. Retry the click until aria-expanded actually flips.
109 lines
3.7 KiB
TypeScript
109 lines
3.7 KiB
TypeScript
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. The
|
|
// toggle's onClick can be dropped if Playwright clicks before React has
|
|
// hydrated the handler, leaving the region `hidden` for the whole wait.
|
|
// Retry the click until `aria-expanded` actually flips.
|
|
const toggle = page.locator(
|
|
'[data-slot="settings-security-sessions-toggle"]',
|
|
);
|
|
await expect(toggle).toBeVisible({ timeout: 10_000 });
|
|
await expect(async () => {
|
|
await toggle.click();
|
|
await expect(toggle).toHaveAttribute("aria-expanded", "true", {
|
|
timeout: 2_000,
|
|
});
|
|
}).toPass({ timeout: 15_000 });
|
|
|
|
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");
|
|
});
|
|
});
|