mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
test(e2e): align export and integrations specs with removed surfaces
Drop the obsolete doctor-report dialog spec (the interactive export-page dialog was removed; the report now lives under the health-record export), remove the doctor-report testid from the export smoke, and update the integrations layout spec to three cards after the moodLog removal.
This commit is contained in:
1 parent
7176afa9ef
commit
ab1eea7f23
3 files changed
+15
-114
No files matched your search
@@ -1,98 +0,0 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
|
||||
/**
|
||||
* Doctor-report PDF flow. The real flow:
|
||||
* 1. User clicks "Configure & generate" on `/settings/export`
|
||||
* (relocated from `/settings/advanced` in v1.4.16 phase B7)
|
||||
* 2. The dialog opens; user picks dates and submits.
|
||||
* 3. Frontend POSTs `/api/doctor-report` to fetch aggregated data
|
||||
* 4. Frontend dynamic-imports `src/lib/doctor-report-pdf.ts` and
|
||||
* generates the PDF client-side via jsPDF; calls `doc.save(...)`
|
||||
* which triggers a download.
|
||||
*
|
||||
* This spec mocks step 3 (so we don't need 90 days of seeded data) and
|
||||
* captures step 4's download via Playwright's download API. We then
|
||||
* confirm the file is non-empty and starts with `%PDF` magic bytes.
|
||||
*/
|
||||
test.describe("doctor report PDF generation", () => {
|
||||
test.use({ storageState: STORAGE_STATE_PATH });
|
||||
|
||||
test("button click triggers a non-empty PDF download", async ({ page }) => {
|
||||
// Mock the data endpoint with a minimal-but-shape-correct payload.
|
||||
// The shape below mirrors the `ReportData` interface in
|
||||
// `src/lib/doctor-report-pdf.ts` — empty stats / medications / mood
|
||||
// is enough to exercise the table-rendering path without 90 days
|
||||
// of seeded readings.
|
||||
await page.route("**/api/doctor-report", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
period: {
|
||||
days: 90,
|
||||
since: new Date(Date.now() - 90 * 86_400_000).toISOString(),
|
||||
},
|
||||
patient: {
|
||||
username: "e2e-tester",
|
||||
dateOfBirth: "1985-06-15T00:00:00.000Z",
|
||||
gender: "MALE",
|
||||
heightCm: 180,
|
||||
},
|
||||
stats: {},
|
||||
bmi: null,
|
||||
compliance: {},
|
||||
medications: [],
|
||||
mood: null,
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
// Capture page errors so a failed dynamic-import surfaces in the
|
||||
// test output instead of timing out silently.
|
||||
const pageErrors: string[] = [];
|
||||
page.on("pageerror", (err) => pageErrors.push(`pageerror: ${err.message}`));
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error")
|
||||
pageErrors.push(`console.error: ${msg.text()}`);
|
||||
});
|
||||
|
||||
await page.goto("/settings/export", { waitUntil: "domcontentloaded" });
|
||||
|
||||
// v1.4.37 renamed the trigger testid from `export-action-doctor-report`
|
||||
// to `export-hero-doctor-report-action` when the card was lifted to a
|
||||
// hero block on the export page.
|
||||
const reportBtn = page.getByTestId("export-hero-doctor-report-action");
|
||||
await expect(reportBtn).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// Click opens the dialog; the dialog's submit button triggers the PDF
|
||||
// generation. Trigger the download and capture the resulting Download
|
||||
// object once the user confirms.
|
||||
const downloadPromise = page.waitForEvent("download", { timeout: 30_000 });
|
||||
await reportBtn.click();
|
||||
// Submit the dialog with the default dates (last 90 days).
|
||||
const submitBtn = page.getByRole("button", {
|
||||
name: /generate pdf|pdf generieren/i,
|
||||
});
|
||||
await expect(submitBtn).toBeVisible({ timeout: 5_000 });
|
||||
await submitBtn.click();
|
||||
const download = await downloadPromise.catch((err) => {
|
||||
throw new Error(
|
||||
`Download did not fire. Page errors:\n${pageErrors.join("\n")}\n\nOriginal: ${err}`,
|
||||
);
|
||||
});
|
||||
|
||||
const path = await download.path();
|
||||
expect(path).toBeTruthy();
|
||||
|
||||
// Read first 4 bytes — a valid PDF starts with `%PDF`.
|
||||
const fs = await import("node:fs/promises");
|
||||
const buf = await fs.readFile(path!);
|
||||
expect(buf.byteLength).toBeGreaterThan(1024);
|
||||
expect(buf.subarray(0, 4).toString("ascii")).toBe("%PDF");
|
||||
});
|
||||
});
|
||||
@@ -24,16 +24,16 @@ test.describe("Settings → Export", () => {
|
||||
|
||||
test("renders all export surfaces with stable testids", async ({ page }) => {
|
||||
await page.goto("/settings/export", { waitUntil: "domcontentloaded" });
|
||||
// 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.
|
||||
// The health-record export is the page hero; the four CSV/backup
|
||||
// tiles keep the `export-card-*` shape. The doctor report lives
|
||||
// under the health-record export, so it is no longer a separate
|
||||
// surface here.
|
||||
for (const id of [
|
||||
"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 });
|
||||
}
|
||||
|
||||
@@ -15,11 +15,10 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
* verifies neither path leaks into a horizontal scrollbar on the
|
||||
* <html> element.
|
||||
*
|
||||
* 2. Render exactly one pill per integration card (Withings + Mood
|
||||
* Log when both are configured). Belt-and-braces guard for the
|
||||
* consolidation Marc requested — the Vitest spec covers this on
|
||||
* the SSR markup level, but the Pixel-5 layout is what matters
|
||||
* to him in practice.
|
||||
* 2. Render exactly one pill per integration card. Belt-and-braces
|
||||
* guard for the card consolidation — the Vitest spec covers this
|
||||
* on the SSR markup level, but the Pixel-5 layout is what matters
|
||||
* in practice.
|
||||
*
|
||||
* Mobile-only spec — desktop project skips it.
|
||||
*/
|
||||
@@ -144,18 +143,18 @@ test.describe("/settings/integrations Pixel-5 layout", () => {
|
||||
|
||||
// Wait for the pills to mount (queries resolve after first paint).
|
||||
const pills = page.locator('[data-testid="integration-status-pill"]');
|
||||
// Four integration cards now (Withings, moodLog, WHOOP, Google Health) →
|
||||
// four pills.
|
||||
await expect(pills).toHaveCount(4, { timeout: 10_000 });
|
||||
// Three integration cards now (Withings, WHOOP, Google Health) → three
|
||||
// pills.
|
||||
await expect(pills).toHaveCount(3, { timeout: 10_000 });
|
||||
|
||||
// Withings + moodLog are connected in this fixture; WHOOP is a BYO-keys
|
||||
// card that stays dormant until an operator adds credentials, so assert
|
||||
// the two connected pills order-independently.
|
||||
// Withings is connected in this fixture; WHOOP is a BYO-keys card that
|
||||
// stays dormant until an operator adds credentials, so assert the one
|
||||
// connected pill.
|
||||
await expect(
|
||||
page.locator(
|
||||
'[data-testid="integration-status-pill"][data-state="connected"]',
|
||||
),
|
||||
).toHaveCount(2);
|
||||
).toHaveCount(1);
|
||||
|
||||
// The redundant v1.4.18 banner must be gone.
|
||||
await expect(
|
||||
|
||||
Reference in new issue
Block a user