From ec240bc07a42b47777ec1bb1491f97bf6997edbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 21 Oct 2026 19:44:07 +0200 Subject: [PATCH] test(settings): follow the moved pages in e2e and cover the phone navigation Point the header, overscroll and timezone specs at the pages that hold those cards now, add the module pages and Preview features to the one-piece sweep, and add a 390 px journey through the grouped section sheet. Unit coverage for the Preview features card. --- e2e/settings-admin-one-piece.spec.ts | 8 ++- e2e/settings-mobile-nav.spec.ts | 47 +++++++++++++++++ e2e/settings-overscroll.spec.ts | 2 +- e2e/timezone-profile.spec.ts | 4 +- .../__tests__/preview-section.test.tsx | 51 +++++++++++++++++++ 5 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 e2e/settings-mobile-nav.spec.ts create mode 100644 src/components/settings/__tests__/preview-section.test.tsx diff --git a/e2e/settings-admin-one-piece.spec.ts b/e2e/settings-admin-one-piece.spec.ts index 4fc1b69d2..3f10f6540 100644 --- a/e2e/settings-admin-one-piece.spec.ts +++ b/e2e/settings-admin-one-piece.spec.ts @@ -58,11 +58,13 @@ const SETTINGS_ROUTES = [ "/settings/api", "/settings/mcp", "/settings/advanced", + "/settings/preview", "/settings/about", ] as const; /** - * The Layout hub's seven children. They are the routes whose header block + * The Arrangement hub's children, plus (v1.44) the module pages under + * Modules, which use the same back-link header. They are the routes whose header block * changed MOST — the shell used to hand-roll a `space-y-6` between the * back-link and the title, and `PageHeader`'s documented contract is * `space-y-1.5`. Nothing measured them before, so the step went from 24 px @@ -73,10 +75,12 @@ const LAYOUT_CHILD_ROUTES = [ "/settings/layout/dashboard", "/settings/layout/insights", "/settings/layout/medications", - "/settings/layout/mood", "/settings/layout/labs", "/settings/layout/illness", "/settings/layout/vorsorge", + "/settings/modules/medications", + "/settings/modules/mood", + "/settings/modules/labs", ] as const; /** The `space-y-1.5` between a back-link and the title it sits above. */ diff --git a/e2e/settings-mobile-nav.spec.ts b/e2e/settings-mobile-nav.spec.ts new file mode 100644 index 000000000..e8a115c68 --- /dev/null +++ b/e2e/settings-mobile-nav.spec.ts @@ -0,0 +1,47 @@ +import { expect, test } from "./setup/test"; + +import { STORAGE_STATE_PATH } from "./setup/global-setup"; + +/** + * Settings navigation at 390 px: one button names the current section and + * opens a sheet with every section under its group. It replaced a strip of + * every section as chips, where most of Settings sat off screen. + */ +test.describe("settings navigation on a phone", () => { + test.use({ storageState: STORAGE_STATE_PATH }); + + test("names the section, opens the grouped list and moves on a tap", async ({ + page, + }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto("/settings/account", { waitUntil: "domcontentloaded" }); + + const trigger = page.locator('[data-slot="settings-mobile-nav-trigger"]'); + await expect(trigger).toBeVisible({ timeout: 15_000 }); + await expect(trigger).toContainText("Account"); + + // The click can land before hydration; retry until the sheet opens. + const list = page.locator('[data-slot="settings-mobile-nav-list"]'); + await expect(async () => { + await trigger.click(); + await expect(list).toBeVisible({ timeout: 2_000 }); + }).toPass({ timeout: 15_000 }); + + for (const group of ["Account", "Tracking", "Notifications", "System"]) { + await expect( + list.getByRole("heading", { name: group, exact: true }), + ).toBeVisible(); + } + + await list.getByRole("link", { name: "Security" }).click(); + await expect(page).toHaveURL(/\/settings\/security$/); + await expect(list).toHaveCount(0); + await expect(trigger).toContainText("Security"); + + // Nothing scrolls sideways. + const overflow = await page.evaluate( + () => document.documentElement.scrollWidth - window.innerWidth, + ); + expect(overflow).toBeLessThanOrEqual(1); + }); +}); diff --git a/e2e/settings-overscroll.spec.ts b/e2e/settings-overscroll.spec.ts index 59f858dfc..0df30bde2 100644 --- a/e2e/settings-overscroll.spec.ts +++ b/e2e/settings-overscroll.spec.ts @@ -33,7 +33,7 @@ const ROUTES = [ // one-scroll-floor assertion below). "/settings/layout/insights", "/settings/layout/medications", - "/settings/layout/mood", + "/settings/modules/mood", "/settings/modules", "/settings/account", "/settings/notifications", diff --git a/e2e/timezone-profile.spec.ts b/e2e/timezone-profile.spec.ts index f4865aa1d..7f4177b9a 100644 --- a/e2e/timezone-profile.spec.ts +++ b/e2e/timezone-profile.spec.ts @@ -6,7 +6,7 @@ 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 + * card on /settings/security 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 @@ -75,7 +75,7 @@ test.describe("profile-timezone display (#490)", () => { }), ); - await page.goto("/settings/privacy", { waitUntil: "domcontentloaded" }); + await page.goto("/settings/security", { 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 diff --git a/src/components/settings/__tests__/preview-section.test.tsx b/src/components/settings/__tests__/preview-section.test.tsx new file mode 100644 index 000000000..7dc3e11ba --- /dev/null +++ b/src/components/settings/__tests__/preview-section.test.tsx @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +import { I18nProvider } from "@/lib/i18n/context"; +import { queryKeys } from "@/lib/query-keys"; +import { PreviewSection } from "../preview-section"; +import { primeLocales } from "@/__tests__/helpers/prime-locales"; + +primeLocales("de"); + +function render(topics: boolean | undefined, locale: "en" | "de" = "en") { + const client = new QueryClient({ + defaultOptions: { queries: { retry: false, refetchOnMount: false } }, + }); + if (topics !== undefined) { + client.setQueryData(queryKeys.previewFeatures(), { topics }); + } + return renderToStaticMarkup( + + + + + , + ); +} + +describe("", () => { + it("shows the Topics switch off by default, saved on change", () => { + const html = render(false); + expect(html).toContain('data-slot="settings-preview-topics-switch"'); + expect(html).toMatch( + /data-slot="settings-preview-topics-switch"[^>]*aria-checked="false"|aria-checked="false"[^>]*data-slot="settings-preview-topics-switch"/, + ); + expect(html).toContain("Topics (beta)"); + // A switch, no Save: one save model per card. + expect(html).not.toContain('type="submit"'); + }); + + it("reads on once the person switched it on", () => { + expect(render(true)).toMatch( + /data-slot="settings-preview-topics-switch"[^>]*aria-checked="true"|aria-checked="true"[^>]*data-slot="settings-preview-topics-switch"/, + ); + }); + + it("says the existing pages stay as they are and nothing raw leaks", () => { + const html = render(false, "de"); + expect(html).toContain("Themen (Beta)"); + expect(html).not.toContain("settings.preview."); + }); +});