Files
HealthLog/src/components/documents/__tests__/document-card.test.tsx
T
Marc-André Bombeck 4852421f05 chore(release): v1.28.38 — documents polish, waist reminders, bulk sharing
Document vault: card list drops a stray glyph and gives the real filename
its own line (was truncating on the meta row); the detail sheet's close
button aligns correctly — ResponsiveSheet reserved the right gutter for a
close button it wasn't rendering, now conditional on showCloseButton. The
open-in-coach action reads 'Coach fragen'.

Bulk share: a multi-selection in the overview shares several documents in
ONE link (documents-only privacy preserved, owner-scoped, up to 50 per
link; >50 refuses with a toast rather than silently truncating).

Reminders can link to WAIST_CIRCUMFERENCE (additive MeasurementReminderType
enum value, OpenAPI regenerated). The WHO-5 and SCI well-being reminder
labels were absent in all six locales and rendered their raw dotted keys
(the shared 'measurementReminders.types' prefix is what showed as the stray
'Measurement Reminders Types'); added in every language. A new
type-labels-i18n test asserts every dynamic reminder-type key resolves
non-empty in all six bundles — the static coverage guard cannot see the
dynamic key that let this through.
2026-07-16 00:33:20 +02:00

236 lines
6.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { I18nProvider } from "@/lib/i18n/context";
import type { InboundDocumentDto } from "@/lib/validations/inbound-documents";
import { DocumentCard, UploadStateCard } from "../document-card";
import type { UploadQueueItem } from "../use-document-upload";
/**
* The vault card's render contract: title falls back filename → "untitled",
* the meta line stays muted, the attachment-class badge appears ONLY for
* download-only formats, condition links paint as pills, and the two
* transient upload cards (in-flight ring / translated failure) render from
* the same footprint.
*/
function doc(overrides: Partial<InboundDocumentDto> = {}): InboundDocumentDto {
return {
id: "doc-1",
kind: "IMAGING",
title: "MRT Knie",
filename: "mrt-knie.pdf",
mimeType: "application/pdf",
byteSize: 2_500_000,
status: "STORED",
providerType: null,
reportDate: null,
documentDate: "2025-10-04",
errorReason: null,
factCount: 0,
pendingCount: 0,
conditionLinks: [{ episodeId: "ep-knee", name: "Knie" }],
servingClass: "inline",
hasContentIndex: false,
contentIndexSource: null,
hasThumbnail: false,
createdAt: "2025-10-05T08:00:00.000Z",
updatedAt: "2025-10-05T08:00:00.000Z",
...overrides,
};
}
function render(node: React.ReactNode) {
return renderToStaticMarkup(
<I18nProvider initialLocale="en">{node}</I18nProvider>,
);
}
const noop = () => {};
describe("<DocumentCard>", () => {
it("renders title, condition pill, and NO attachment badge for inline docs", () => {
const html = render(
<DocumentCard
document={doc()}
selected={false}
onToggleSelected={noop}
onOpen={noop}
highlighted={false}
/>,
);
expect(html).toContain("MRT Knie");
expect(html).toContain("Knie");
expect(html).toContain('data-document-id="doc-1"');
expect(html).not.toContain("Download only");
});
it("shows the download-only badge for attachment-class documents", () => {
const html = render(
<DocumentCard
document={doc({
servingClass: "attachment",
hasContentIndex: false,
mimeType: "application/octet-stream",
filename: "befund.docx",
title: null,
})}
selected={false}
onToggleSelected={noop}
onOpen={noop}
highlighted={false}
/>,
);
expect(html).toContain("Download only");
// Title falls back to the filename.
expect(html).toContain("befund.docx");
});
it("renders the preview thumbnail image when hasThumbnail is set", () => {
const html = render(
<DocumentCard
document={doc({ hasThumbnail: true })}
selected={false}
onToggleSelected={noop}
onOpen={noop}
highlighted={false}
/>,
);
expect(html).toContain('data-slot="document-thumbnail"');
expect(html).toContain('src="/api/documents/inbound/doc-1/thumbnail"');
expect(html).toContain('loading="lazy"');
});
it("shows the kind icon (no thumbnail image) when hasThumbnail is false", () => {
const html = render(
<DocumentCard
document={doc({ hasThumbnail: false })}
selected={false}
onToggleSelected={noop}
onOpen={noop}
highlighted={false}
/>,
);
expect(html).not.toContain('data-slot="document-thumbnail"');
expect(html).not.toContain("/thumbnail");
});
it("does NOT render the AI-read glyph on the card face, even when AI-read", () => {
// The AI-read state stays surfaced authoritatively in the detail sheet's
// DocumentAiSection; the card face no longer duplicates it (v1.28.38).
const html = render(
<DocumentCard
document={doc({ hasContentIndex: true, contentIndexSource: "vision" })}
selected={false}
onToggleSelected={noop}
onOpen={noop}
highlighted={false}
/>,
);
expect(html).not.toContain('data-slot="document-card-ai-read"');
});
it("renders the filename on its own line with the full name in title", () => {
const html = render(
<DocumentCard
document={doc({
title: "MRT Knie",
filename: "mrt-knie-2025-10-04.pdf",
})}
selected={false}
onToggleSelected={noop}
onOpen={noop}
highlighted={false}
/>,
);
// Dedicated filename line, muted text-xs, full name reachable on hover.
expect(html).toContain('data-slot="document-card-filename"');
expect(html).toContain('title="mrt-knie-2025-10-04.pdf"');
expect(html).toContain("mrt-knie-2025-10-04.pdf");
});
it("omits the filename line when the filename equals the title", () => {
const html = render(
<DocumentCard
document={doc({ title: "report.pdf", filename: "report.pdf" })}
selected={false}
onToggleSelected={noop}
onOpen={noop}
highlighted={false}
/>,
);
expect(html).not.toContain('data-slot="document-card-filename"');
});
it("falls back to the untitled label and rings when highlighted", () => {
const html = render(
<DocumentCard
document={doc({ title: null, filename: null })}
selected={false}
onToggleSelected={noop}
onOpen={noop}
highlighted
/>,
);
expect(html).toContain("Untitled document");
expect(html).toContain("ring-2");
});
});
describe("<UploadStateCard>", () => {
const base: UploadQueueItem = {
localId: "u1",
fileName: "scan.jpg",
byteSize: 4_000_000,
status: "uploading",
progress: 0.4,
};
it("paints the progress ring while uploading", () => {
const html = render(<UploadStateCard item={base} onDismiss={noop} />);
expect(html).toContain('role="progressbar"');
expect(html).toContain('aria-valuenow="40"');
expect(html).toContain("scan.jpg");
});
it("renders the translated over-limit reason with the configured cap", () => {
const html = render(
<UploadStateCard
item={{
...base,
status: "error",
failure: {
ok: false,
reason: "fileTooLarge",
maxFileBytes: 26_214_400,
},
}}
onDismiss={noop}
/>,
);
expect(html).toContain('role="alert"');
expect(html).toContain("25 MB");
expect(html).not.toContain('role="progressbar"');
});
it("renders the quota reason with used/quota figures", () => {
const html = render(
<UploadStateCard
item={{
...base,
status: "error",
failure: {
ok: false,
reason: "quotaExceeded",
quotaBytes: 1_073_741_824,
usedBytes: 1_020_054_732,
},
}}
onDismiss={noop}
/>,
);
expect(html).toContain("Storage is full");
expect(html).toContain("1 GB");
});
});