test(documents): share entry point seeds the create flow with the document

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).
This commit is contained in:
Marc-André Bombeck committed 2026-07-08 09:50:26 +02:00
1 parent d4009a3587
commit 46c85e8671
2 files changed
+129

No files matched your search

+45
View File
@@ -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`);
@@ -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(
<I18nProvider initialLocale={locale}>
<ShareLinkCreateForm {...props} />
</I18nProvider>,
);
}
describe("<ShareLinkCreateForm> — 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");
});
});