From 46c85e86712ebcf1a8524eaa75b192a1c9e6e163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 8 Jul 2026 09:50:26 +0200 Subject: [PATCH] test(documents): share entry point seeds the create flow with the document MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unit: the shared ShareLinkCreateForm mounts bare (Settings default) and seeded (document flow) — a pre-attached document rides in as a chip with the label pre-filled. e2e: the document detail Share action opens the create flow with that document pre-attached and reaches the one-time QR reveal; marked slow (two stacked sheets + a real create). --- e2e/share-documents.spec.ts | 45 ++++++++++ .../__tests__/share-link-create-form.test.tsx | 84 +++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/components/settings/__tests__/share-link-create-form.test.tsx diff --git a/e2e/share-documents.spec.ts b/e2e/share-documents.spec.ts index d6c81f481..518853a56 100644 --- a/e2e/share-documents.spec.ts +++ b/e2e/share-documents.spec.ts @@ -30,6 +30,7 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup"; import { ensureShareDocFixture, ensureVaultFixture, + MRT_DOC_ID, SHARE_DOC_PREFIX, SHARE_JPEG_DOC_ID, SHARE_PDF_DOC_ID, @@ -233,6 +234,50 @@ test.describe("clinician document sharing", () => { await clinician.context().close(); }); + test("the document detail Share action opens the create flow with that document pre-attached", async ({ + page, + }) => { + // Opens two stacked sheets (detail → share) and drives a real create with + // the one-time QR render — heavier than the default 30s budget on a loaded + // runner. Runs on the desktop and 390px mobile projects. + test.slow(); + + // Deep-link straight to the document detail sheet. + await page.goto(`/documents?doc=${MRT_DOC_ID}`); + const detail = page.getByRole("dialog").filter({ hasText: "MRT Knie" }); + await expect(detail).toBeVisible(); + + // The Share action sits beside Download in the footer. + await detail.getByRole("button", { name: "Share" }).click(); + + // The share flow opens with the document already attached and its title + // pre-filled as the link label — no context switch to Settings. + const shareSheet = page + .getByRole("dialog") + .filter({ hasText: "Share this document" }); + await expect(shareSheet).toBeVisible(); + await expect(shareSheet.locator("#share-label")).toHaveValue("MRT Knie"); + const chips = shareSheet + .getByTestId("share-attached-chips") + .getByRole("listitem"); + await expect(chips).toHaveCount(1); + await expect(chips.first()).toContainText("MRT Knie"); + + // Create the link — the one-time reveal (with the scannable QR) lands in + // the same sheet, carrying the one attached document. + await shareSheet.getByRole("button", { name: "Create link" }).click(); + const reveal = shareSheet.getByTestId("share-token-reveal"); + await expect(reveal).toBeVisible(); + await expect( + shareSheet.getByTestId("share-created-doc-count"), + ).toContainText("1"); + const qrImg = shareSheet.getByTestId("share-qr-block").getByRole("img"); + await expect(qrImg).toBeVisible(); + const box = await qrImg.boundingBox(); + expect(box, "QR image has a rendered box").not.toBeNull(); + expect(box!.width).toBeGreaterThanOrEqual(160); + }); + test("picker enforces the document cap at the limit", async ({ page }) => { await page.goto("/settings/sharing"); await page.locator("#share-label").fill(`${SHARE_DOC_PREFIX} cap`); diff --git a/src/components/settings/__tests__/share-link-create-form.test.tsx b/src/components/settings/__tests__/share-link-create-form.test.tsx new file mode 100644 index 000000000..bbde00093 --- /dev/null +++ b/src/components/settings/__tests__/share-link-create-form.test.tsx @@ -0,0 +1,84 @@ +/** + * The shared clinician share-link CREATE flow. It is mounted from two places + * — Settings → Sharing and the document detail sheet's Share action — so the + * test pins the two behaviours the document entry point relies on: + * 1. Mounted bare (the Settings default) it renders the create form with the + * 90-day-capped expiry, the attach affordance, the frozen-set warning, + * and no pre-attached document. + * 2. Mounted with `initialDocuments` + `initialLabel` (the document flow) it + * pre-fills the label and seeds the attached-document chip, so sharing a + * document lands on a create form already scoped to it. + * + * SSR-static render only (no jsdom), matching the sibling settings tests. The + * TanStack hooks are mocked; the QR (`qrcode`) only ever renders in an effect + * after a create, so it never runs under `renderToStaticMarkup`. + */ +import { describe, expect, it, vi } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; + +vi.mock("@tanstack/react-query", () => ({ + // The always-mounted ShareDocumentPicker reads via useQuery; the form + // itself creates via useMutation. + useQuery: () => ({ data: undefined, isPending: false, isError: false }), + useMutation: () => ({ mutate: vi.fn(), isPending: false }), + useQueryClient: () => ({ invalidateQueries: vi.fn() }), +})); + +import { I18nProvider } from "@/lib/i18n/context"; +import { ShareLinkCreateForm } from "../share-link-create-form"; +import type { PickedDocument } from "../share-document-picker"; + +function render( + props: { + initialDocuments?: PickedDocument[]; + initialLabel?: string; + } = {}, + locale: "en" | "de" = "en", +) { + return renderToStaticMarkup( + + + , + ); +} + +describe(" — shared create flow", () => { + it("renders the create form with the 90-day-capped expiry and attach affordance", () => { + const html = render(); + expect(html).toContain('max="90"'); + expect(html).toContain("Create link"); + expect(html).toContain('data-testid="share-attach-open"'); + expect(html).toContain("Choose documents"); + }); + + it("surfaces the frozen-set warning so the write-once contract is explicit", () => { + const html = render(); + expect(html).toContain("fixed once you create the link"); + }); + + it("does not reveal a token before a create succeeds", () => { + const html = render(); + expect(html).not.toContain('data-testid="share-token-reveal"'); + }); + + it("mounts bare with no pre-attached document (the Settings default)", () => { + const html = render(); + expect(html).not.toContain('data-testid="share-attached-chips"'); + expect(html).toContain("0 of 50 selected"); + }); + + it("seeds the label and the attached-document chip from the document flow", () => { + const html = render({ + initialDocuments: [{ id: "doc-1", title: "Blood panel 2026" }], + initialLabel: "Blood panel 2026", + }); + // Label pre-filled with the document title. + expect(html).toContain('value="Blood panel 2026"'); + // The document rides in as a removable chip, and the count reflects it. + expect(html).toContain('data-testid="share-attached-chips"'); + expect(html).toContain("Blood panel 2026"); + expect(html).toContain("1 of 50 selected"); + // A seeded set surfaces the EXIF note the picker path also shows. + expect(html).toContain("Camera metadata"); + }); +});