Files
HealthLog/src/components/documents/__tests__/document-ai-section.test.tsx
T
Marc-André Bombeck afeb693562 feat(documents): review-first AI assist and session-only summary in the vault detail sheet
Add an optional AI layer to a stored document, gated on the existing AI
provider and consent — no new toggle. All of it rides the shared OCR
capability probe so the client only posts a transport the endpoint accepts
(vision empty-body POST, or local-OCR text posted from the browser).

- "Suggest details" prefills the title / type / date as reviewable DRAFTS.
  Nothing is written on its own: the title lands in the editable field and
  is saved through the existing commit path; type and date each apply on an
  explicit tap. The card carries a visible "review before saving" affordance.
- A summary / extracted-text button opens a transient panel that renders the
  result once and always shows a "not saved / not a diagnosis" note; closing
  it discards the text. Nothing persists beyond the view.
- When no provider is configured the AI area is a calm pointer to the AI
  settings, never an error; the manual metadata form stays fully usable.

The detail-sheet AI area is lifted into a presentational section component so
the availability gate and the review-first wiring render without the sheet's
dialog portal and are pinned by static-render tests. Strings land in all six
locales.
2026-07-07 19:14:34 +02:00

115 lines
3.8 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 { DocumentAiSection } from "../document-ai-section";
/**
* The detail sheet's AI area is availability-gated: with a provider it shows the
* assist / summary toolbar; without one it shows ONLY the calm settings pointer
* (never an action the endpoint would 422). The summary panel, when open, keeps
* the session-only note visible.
*/
function render(node: React.ReactNode) {
return renderToStaticMarkup(
<I18nProvider initialLocale="en">{node}</I18nProvider>,
);
}
const noop = () => {};
const suggestion: DocumentSuggestionDto = {
title: "MRI knee report",
kind: "IMAGING",
documentDate: "2026-02-14",
};
function base(
overrides: Partial<React.ComponentProps<typeof DocumentAiSection>> = {},
) {
const props: React.ComponentProps<typeof DocumentAiSection> = {
aiEnabled: true,
indexEnabled: true,
unavailableReason: null,
actionsDisabled: false,
onSuggest: noop,
suggestPending: false,
suggestErrorKey: null,
onSummarise: noop,
summaryPending: false,
suggestion: null,
suggestionKindLabel: null,
suggestionDateLabel: null,
appliedFields: { title: false, kind: false, date: false },
onUseTitle: noop,
onUseKind: noop,
onUseDate: noop,
onDismissSuggestion: noop,
summaryOutput: null,
summaryResult: null,
summaryErrorKey: null,
onCloseSummary: noop,
hasContentIndex: false,
indexPending: false,
onIndex: noop,
...overrides,
};
return <DocumentAiSection {...props} />;
}
describe("<DocumentAiSection>", () => {
it("offers the assist + summary toolbar when a provider is available", () => {
const html = render(base({ aiEnabled: true }));
expect(html).toContain('data-slot="assist-suggest"');
expect(html).toContain("Suggest details");
expect(html).toContain("Summarise");
expect(html).toContain("Show extracted text");
// No unavailable pointer when the affordance is offered.
expect(html).not.toContain('data-slot="assist-unavailable"');
});
it("shows ONLY the calm settings pointer when no provider is available", () => {
const html = render(
base({ aiEnabled: false, unavailableReason: "no-provider" }),
);
expect(html).toContain('data-slot="assist-unavailable"');
expect(html).toContain('href="/settings/ai"');
// The assist / index actions must be absent — never a 422-guaranteed tap.
expect(html).not.toContain('data-slot="assist-suggest"');
expect(html).not.toContain('data-slot="content-index-status"');
});
it("renders the reviewed suggestion drafts when present", () => {
const html = render(
base({
suggestion,
suggestionKindLabel: "Imaging",
suggestionDateLabel: "14 Feb 2026",
}),
);
expect(html).toContain('data-slot="assist-suggestion-review"');
expect(html).toContain("MRI knee report");
expect(html).toContain("review before saving");
});
it("renders the transient summary panel with the session-only note", () => {
const html = render(
base({
summaryOutput: "summary",
summaryResult: { summary: "An MRI report of the left knee." },
}),
);
expect(html).toContain('data-slot="document-summary-panel"');
expect(html).toContain("An MRI report of the left knee.");
expect(html).toContain("Not saved");
});
it("hides the content-index row when indexing is unavailable", () => {
const html = render(base({ aiEnabled: true, indexEnabled: false }));
expect(html).toContain('data-slot="assist-suggest"');
expect(html).not.toContain('data-slot="content-index-status"');
});
});