Files
HealthLog/src/components/documents/__tests__/document-ai-panels.test.tsx
T
Marc-André Bombeck e5de72cae2 feat(documents): hide manual AI actions under auto-read, trim suggestion chrome
When "read documents automatically with AI" is on, the detail sheet no longer
shows the manual per-document action row (Read / Suggest / Summarise) — reading
already happens on upload, so the buttons only nagged. The section header and
the searchable status pill stay, and content search keeps working. With the
setting off the manual controls render exactly as before.

Drop the "review before saving" heading and its grey sub-caption from the
suggestion panel; the reviewable fields and the apply / dismiss controls stay.
Remove the now-unused documents.assist.reviewTitle / reviewHint keys from every
locale.
2026-07-09 17:27:47 +02:00

220 lines
6.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { I18nProvider } from "@/lib/i18n/context";
import type { DocumentSuggestionDto } from "@/lib/validations/inbound-documents";
import {
AiUnavailableHint,
AssistSuggestionReview,
ContentSearchStatus,
DocumentSummaryPanel,
} from "../document-ai-panels";
/**
* The AI panels' render contract:
* - the unavailable state is a CALM pointer to the AI settings, never an
* error, and adapts its copy to the actionable reason;
* - the suggestion card is review-first: it applies nothing on its own —
* every value has its own explicit apply tap, and a dismiss control;
* - the summary panel ALWAYS carries the "not saved · not a diagnosis" note,
* even while loading, so the session-only contract is never off-screen.
*/
function render(node: React.ReactNode) {
return renderToStaticMarkup(
<I18nProvider initialLocale="en">{node}</I18nProvider>,
);
}
const noop = () => {};
const suggestion = (
overrides: Partial<DocumentSuggestionDto> = {},
): DocumentSuggestionDto => ({
title: "Blood panel — March 2026",
kind: "LAB_RESULT",
documentDate: "2026-03-01",
...overrides,
});
describe("<AiUnavailableHint>", () => {
it("points calmly at the AI settings when no provider is configured", () => {
const html = render(<AiUnavailableHint reason="no-provider" />);
expect(html).toContain('data-slot="assist-unavailable"');
expect(html).toContain('href="/settings/ai"');
expect(html).toContain("Set up an AI provider");
// Never an error affordance.
expect(html).not.toContain('role="alert"');
});
it("nudges toward local OCR when a text-only provider needs it", () => {
const html = render(<AiUnavailableHint reason="enable-local-ocr" />);
expect(html).toContain("local OCR");
expect(html).toContain('href="/settings/ai"');
});
});
describe("<AssistSuggestionReview>", () => {
it("presents drafts as reviewable fields with explicit apply controls", () => {
const html = render(
<AssistSuggestionReview
suggestion={suggestion()}
kindLabel="Lab result"
dateLabel="1 Mar 2026"
applied={{ title: false, kind: false, date: false }}
onUseTitle={noop}
onUseKind={noop}
onUseDate={noop}
onDismiss={noop}
/>,
);
expect(html).toContain('data-slot="assist-suggestion-review"');
// The heading + grey sub-caption were retired — the panel leads straight
// with the reviewable draft fields.
expect(html).not.toContain("review before saving");
expect(html).not.toContain("Nothing is saved until you apply it");
// The suggested values are shown as reviewable drafts.
expect(html).toContain("Blood panel — March 2026");
expect(html).toContain("Lab result");
expect(html).toContain("1 Mar 2026");
// Each field has an explicit apply control.
expect(html).toContain("Use title");
expect(html).toContain("Apply");
});
it("marks an applied field instead of re-offering it", () => {
const html = render(
<AssistSuggestionReview
suggestion={suggestion()}
kindLabel="Lab result"
dateLabel="1 Mar 2026"
applied={{ title: true, kind: false, date: false }}
onUseTitle={noop}
onUseKind={noop}
onUseDate={noop}
onDismiss={noop}
/>,
);
expect(html).toContain("Applied");
});
it("shows an empty state when nothing could be read", () => {
const html = render(
<AssistSuggestionReview
suggestion={suggestion({ title: null, kind: null, documentDate: null })}
kindLabel={null}
dateLabel={null}
applied={{ title: false, kind: false, date: false }}
onUseTitle={noop}
onUseKind={noop}
onUseDate={noop}
onDismiss={noop}
/>,
);
expect(html).toContain("Nothing could be read from this document");
});
});
describe("<DocumentSummaryPanel>", () => {
it("keeps the not-saved / not-a-diagnosis note on screen while loading", () => {
const html = render(
<DocumentSummaryPanel
output="summary"
result={null}
isPending
errorKey={null}
onClose={noop}
/>,
);
expect(html).toContain('data-slot="document-summary-panel"');
expect(html).toContain('data-slot="document-summary-loading"');
});
it("renders the summary body", () => {
const html = render(
<DocumentSummaryPanel
output="summary"
result={{ summary: "A lipid panel dated 1 March 2026." }}
isPending={false}
errorKey={null}
onClose={noop}
/>,
);
expect(html).toContain("A lipid panel dated 1 March 2026.");
});
it("renders extracted text in a monospace block", () => {
const html = render(
<DocumentSummaryPanel
output="text"
result={{ text: "LDL 3.1 mmol/L" }}
isPending={false}
errorKey={null}
onClose={noop}
/>,
);
expect(html).toContain("LDL 3.1 mmol/L");
expect(html).toContain("Extracted text");
expect(html).toContain("font-mono");
});
it("surfaces an error via role=alert", () => {
const html = render(
<DocumentSummaryPanel
output="summary"
result={null}
isPending={false}
errorKey="documents.assist.errorGeneric"
onClose={noop}
/>,
);
expect(html).toContain('role="alert"');
expect(html).toContain("read the document");
});
});
describe("<ContentSearchStatus>", () => {
it("shows a calm not-searchable-yet pill before the auto-index lands", () => {
const html = render(
<ContentSearchStatus
hasContentIndex={false}
source={null}
isPending={false}
/>,
);
expect(html).toContain('data-slot="content-search-status"');
expect(html).toContain('data-state="none"');
expect(html).toContain("Not searchable yet");
// A status, never a chore button.
expect(html).not.toContain("<button");
});
it("highlights an AI-read document distinctly from a locally-indexed one", () => {
const aiRead = render(
<ContentSearchStatus hasContentIndex source="vision" isPending={false} />,
);
expect(aiRead).toContain('data-state="ai-read"');
expect(aiRead).toContain("Read by AI");
expect(aiRead).toContain("text-primary");
const local = render(
<ContentSearchStatus
hasContentIndex
source="local-pdf"
isPending={false}
/>,
);
expect(local).toContain('data-state="searchable"');
expect(local).toContain("Searchable");
expect(local).not.toContain("Read by AI");
});
it("reflects a running read as an indexing state", () => {
const html = render(
<ContentSearchStatus hasContentIndex={false} source={null} isPending />,
);
expect(html).toContain('data-state="indexing"');
expect(html).toContain("Making searchable…");
});
});