diff --git a/e2e/settings-export.spec.ts b/e2e/settings-export.spec.ts index b8150aa4f..d38f04aff 100644 --- a/e2e/settings-export.spec.ts +++ b/e2e/settings-export.spec.ts @@ -3,12 +3,16 @@ import { expect, test } from "@playwright/test"; import { STORAGE_STATE_PATH } from "./setup/global-setup"; /** - * v1.4.16 phase B7 — Settings → Export consolidated UI smoke. + * Settings → Export consolidated UI smoke. * * Validates that: - * 1. The five export cards are all rendered with their stable testids. - * 2. Clicking the Measurements CSV download button fires a real - * browser download — proving the new `/api/export/measurements` + * 1. The export surfaces are all rendered with their stable testids: + * the health-record export hero, the four CSV/JSON tiles, and the + * demoted doctor-report card (v1.12). + * 2. The health-record "included data" checklist is a disclosure, + * collapsed by default, and expands on demand. + * 3. Clicking the Measurements CSV download button fires a real + * browser download — proving the `/api/export/measurements` * endpoint is reachable from the browser end-to-end. * * The other CSV / JSON cards share the same code path; one happy-path @@ -18,24 +22,42 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup"; test.describe("Settings → Export", () => { test.use({ storageState: STORAGE_STATE_PATH }); - test("renders all five export cards with stable testids", async ({ - page, - }) => { + test("renders all export surfaces with stable testids", async ({ page }) => { await page.goto("/settings/export", { waitUntil: "domcontentloaded" }); - // v1.4.37 lifted the doctor-report card to a hero block with the - // `export-hero-*` testid prefix; the four CSV/backup tiles keep - // the `export-card-*` shape. + // v1.12 — the health-record export is the page hero; the + // doctor-report card (`export-hero-*` prefix) is demoted to the + // bottom; the four CSV/backup tiles keep the `export-card-*` shape. for (const id of [ - "export-hero-doctor-report", + "health-record-export-panel", "export-card-measurements-csv", "export-card-medications-csv", "export-card-mood-csv", "export-card-full-backup", + "export-hero-doctor-report", ]) { await expect(page.getByTestId(id)).toBeVisible({ timeout: 10_000 }); } }); + test("included-data checklist is collapsed by default and expands on demand", async ({ + page, + }) => { + await page.goto("/settings/export", { waitUntil: "domcontentloaded" }); + const toggle = page.getByTestId("health-record-included-data-toggle"); + await expect(toggle).toBeVisible({ timeout: 10_000 }); + // Collapsed on first render — the checklist panel is absent. + await expect(toggle).toHaveAttribute("aria-expanded", "false"); + await expect( + page.getByTestId("health-record-included-data-panel"), + ).toHaveCount(0); + // Expands on demand. + await toggle.click(); + await expect(toggle).toHaveAttribute("aria-expanded", "true"); + await expect( + page.getByTestId("health-record-included-data-panel"), + ).toBeVisible(); + }); + test("Measurements CSV download fires a real download event", async ({ page, }) => { diff --git a/src/components/settings/__tests__/arztbericht-hero-card.test.tsx b/src/components/settings/__tests__/arztbericht-hero-card.test.tsx index 1ed711b9d..06c1076cc 100644 --- a/src/components/settings/__tests__/arztbericht-hero-card.test.tsx +++ b/src/components/settings/__tests__/arztbericht-hero-card.test.tsx @@ -1,11 +1,13 @@ /** - * v1.4.37 W7a — Arztbericht hero card contract. + * Arztbericht (doctor-report) card contract. * - * The flagship doctor-report PDF surface was promoted from one of five - * grid cards into a hero card at the top of `/settings/export`. This - * suite pins the SSR contract: + * v1.4.37 W7a promoted this surface to the page hero; v1.12 demotes it + * back to a small secondary card at the bottom of `/settings/export` + * (the health-record export now owns the hero). The card stays fully + * functional — only the framing and visual weight changed. This suite + * pins the SSR contract: * - * 1. The hero renders with the expected `data-testid` slot. + * 1. The card renders with the expected `data-testid` slot. * 2. The value statement is present (Marc-Voice copy, no AI mention). * 3. The CTA button is present, clears the 44 px touch floor, and * carries an `aria-describedby` link to the value statement. @@ -37,7 +39,7 @@ function renderSSR(node: React.ReactElement, locale: "en" | "de" = "en") { } describe(" — SSR contract", () => { - it("renders the hero slot with eyebrow + title", () => { + it("renders the card slot with eyebrow + title", () => { const html = renderSSR(); expect(html).toContain('data-testid="export-hero-doctor-report"'); // Title falls back to the existing doctorReport.title key — keeps @@ -59,10 +61,10 @@ describe(" — SSR contract", () => { it("renders the CTA with min-h-11 touch target + aria-describedby", () => { const html = renderSSR(); expect(html).toContain('data-testid="export-hero-doctor-report-action"'); - // 44 px touch floor (WCAG 2.5.5) — the hero CTA pins `h-11` on - // mobile and falls back to the compact `sm:h-10` on pointer + // 44 px touch floor (WCAG 2.5.5) — the demoted CTA keeps `min-h-11` + // on mobile and falls back to the compact `sm:min-h-9` on pointer // devices. - expect(html).toContain("h-11"); + expect(html).toContain("min-h-11"); // Aria-describedby links the CTA to the value statement so screen // readers announce the framing alongside the action label. expect(html).toMatch(/aria-describedby="[^"]+"/); @@ -84,12 +86,15 @@ describe(" — SSR contract", () => { expect(html).not.toContain("settings.sections.export.hero"); }); - it("uses the hero-gradient + glow-purple visual treatment", () => { + it("renders as a plain secondary card, not a hero", () => { const html = renderSSR(); - // Same surface utilities as the Insights hero strip — keeps the - // app's hero language consistent. - expect(html).toContain("hero-gradient"); - expect(html).toContain("glow-purple"); + // v1.12 demoted the doctor-report from the page hero — the + // health-record export now owns the hero treatment, so this card + // must NOT carry the hero gradient/glow utilities. + expect(html).not.toContain("hero-gradient"); + expect(html).not.toContain("glow-purple"); + // It renders with the standard secondary-card surface. + expect(html).toContain("bg-card"); }); it("does not paint dialog markup in the closed-state SSR pass", () => { diff --git a/src/components/settings/__tests__/export-section.test.tsx b/src/components/settings/__tests__/export-section.test.tsx index fe76616ff..1cfdfd331 100644 --- a/src/components/settings/__tests__/export-section.test.tsx +++ b/src/components/settings/__tests__/export-section.test.tsx @@ -1,24 +1,24 @@ /** * v1.4.16 Phase B7 — Settings → Export section. * - * The acceptance criterion for the consolidated Export menu was "five - * cards": - * 1. Doctor Report (PDF) — promoted to the hero in v1.4.37 W7a; - * now lives at the top of the page, NOT in the grid - * 2. Measurements CSV - * 3. Medications CSV (with optional intake-history toggle) - * 4. Mood CSV - * 5. Full JSON Backup + * The consolidated Export page surfaces: + * - Health Record export panel (the page hero, v1.12) + * - Measurements CSV + * - Medications CSV (with optional intake-history toggle) + * - Mood CSV + * - Full JSON Backup + * - Doctor Report (PDF) — small secondary card at the bottom (v1.12) * * Each card must surface a title, a 1-line description, and a * download/generate button. SSR-only smoke test — interaction is * exercised by the e2e suite. * - * v1.4.37 W7a — the doctor-report card was extracted into - * `` and rendered above the grid. The remaining - * four cards stay under a "Weitere Export-Optionen" / "Other export - * options" sub-heading. The hero owns its own contract test; this - * suite pins the page-level shape. + * v1.12 — the health-record export takes the page hero; the + * doctor-report card (``) is demoted to a small + * secondary card at the bottom of the page. The four CSV/JSON tiles + * stay under a "Weitere Export-Optionen" / "Other export options" + * sub-heading. Each component owns its own contract test; this suite + * pins the page-level shape. */ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; @@ -60,11 +60,19 @@ describe(" — SSR smoke", () => { expect(html).not.toContain("settings.sections.export."); }); - it("renders the Arztbericht hero card at the top of the page", () => { + it("mounts the health-record export panel as the page hero", () => { const html = render(); - // The hero owns its own contract suite; here we pin that the page + // v1.12 — the health-record export owns the hero treatment. + expect(html).toContain('data-testid="health-record-export-panel"'); + expect(html).toContain("hero-gradient"); + expect(html).toContain("glow-purple"); + }); + + it("mounts the demoted doctor-report card", () => { + const html = render(); + // The card owns its own contract suite; here we pin that the page // mounts it, and that the legacy in-grid doctor-report card is - // gone (v1.4.37 W7a moved it into the hero). + // gone. The card no longer carries the hero gradient/glow (v1.12). expect(html).toContain('data-testid="export-hero-doctor-report"'); expect(html).not.toContain('data-testid="export-card-doctor-report"'); }); diff --git a/src/components/settings/arztbericht-hero-card.tsx b/src/components/settings/arztbericht-hero-card.tsx index 64b56bd81..2dfbd0754 100644 --- a/src/components/settings/arztbericht-hero-card.tsx +++ b/src/components/settings/arztbericht-hero-card.tsx @@ -1,46 +1,30 @@ "use client"; /** - * v1.4.37 W7a — Settings → Export Arztbericht hero card. + * Settings → Export Arztbericht (doctor-report) card. * - * The doctor-report PDF is HealthLog's flagship export — the artefact a - * user actually carries into a doctor's appointment. The v1.4.16 phase - * B7 layout treated it as one of five equal cards in the grid, which - * undersold the feature. Marc 2026-05-17: + * The doctor-report PDF is the artefact a user carries into a doctor's + * appointment. v1.4.37 W7a promoted it to the page hero; v1.12 demotes + * it back to a small secondary card at the bottom of the export page — + * the health-record export now owns the page hero. The card is fully + * functional: the CTA routes through the existing `` + * + `/api/doctor-report` flow exactly as before. Only the framing and + * visual weight changed. * - * "Können wir unter Einstellungen > Export den Arztbericht als Hero - * Card machen?" + * Layout (compact card, no hero gradient): * - * This component lifts the doctor-report card out of the grid and - * paints it with the same `hero-gradient + glow-purple` visual treatment - * as the Insights hero strip (`src/components/insights/hero-strip.tsx`), - * so the surface feels consistent with the rest of the app. The CTA - * routes through the existing `` + `/api/doctor- - * report` flow — only the framing changed. - * - * Layout: - * - * ┌─ hero ────────────────────────────────────────────┐ - * │ [icon] eyebrow │ - * │ H2 title (settings.sections.export.cards. │ - * │ doctorReport.title) │ + * ┌─ card ────────────────────────────────────────────┐ + * │ [icon] H2 title eyebrow │ * │ one-line value statement │ - * │ ───── │ * │ [Configure & generate] PDF · printable │ * └───────────────────────────────────────────────────┘ * - * Mobile-first: at < sm the CTA stacks below the value statement (the - * action row is `flex-wrap`), the hero pads down to `px-4 py-5`, and - * the title clamps to a single line. - * * a11y: * - The CTA button clears the 44 px touch floor (`min-h-11`). * - `focus-visible:ring` ships via the default ` + {includedDataOpen && ( +

{t("settings.healthRecord.groupVitals")} @@ -372,6 +423,8 @@ export function HealthRecordExportPanel() { onToggle={() => setIncludeAiSummary((v) => !v)} /> )} +

+ )} {isFhirLike && (