generated from coulomb/repo-seed
Per-evidence-item export: click Export → Copy as Markdown / Copy as HTML writes a portable citation card to the clipboard. Cmd/Ctrl+Shift+C exports the active evidence as Markdown. - ADR-0007 locks the Markdown + HTML output formats. - New shared types: CitationCard, openContextUrl(), resolveSourceLabel(). - Engine renderers under src/engine/rendering/: renderCitationCardMarkdown, renderCitationCardHtml — snapshot-tested, escape-safe, BEM classes for HTML. - src/work/useExportEvidence.ts wires engine + renderers + clipboard. - EvidenceSidebar gains an Export popover per row + auto-dismissing toast. - E2E test (tests/integration/citation-card-export-e2e.dom.test.tsx) walks PRD scenario steps 10-11 and asserts the clipboard payload. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
195 lines
5.6 KiB
TypeScript
195 lines
5.6 KiB
TypeScript
/**
|
|
* Markdown citation card renderer tests (CE-WP-0004-T02).
|
|
*
|
|
* Snapshots lock the output format defined in
|
|
* `docs/decisions/ADR-0007-citation-card-format.md`. If a snapshot
|
|
* intentionally changes, update ADR-0007 in the same commit so the
|
|
* written contract and the runtime stay in sync.
|
|
*/
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import type { Annotation } from "@shared/annotation";
|
|
import type { Document } from "@shared/document";
|
|
import type { EvidenceItem } from "@shared/evidence";
|
|
import type {
|
|
AnnotationId,
|
|
DocumentId,
|
|
EvidenceItemId,
|
|
RepresentationId,
|
|
} from "@shared/ids";
|
|
|
|
import { renderCitationCardMarkdown } from "./markdown";
|
|
|
|
const DOC_ID = "doc_2024-order" as DocumentId;
|
|
const REP_ID = "rep_2024-order" as RepresentationId;
|
|
const ANN_ID = "ann_para3" as AnnotationId;
|
|
const EV_ID = "ev_para3" as EvidenceItemId;
|
|
|
|
function makeDoc(overrides: Partial<Document> = {}): Document {
|
|
return {
|
|
id: DOC_ID,
|
|
title: "Order from 14 Mar 2024",
|
|
mediaType: "application/pdf",
|
|
createdAt: "2026-05-25T00:00:00.000Z",
|
|
updatedAt: "2026-05-25T00:00:00.000Z",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeAnn(overrides: Partial<Annotation> = {}): Annotation {
|
|
return {
|
|
id: ANN_ID,
|
|
documentId: DOC_ID,
|
|
representationId: REP_ID,
|
|
selectors: [],
|
|
quote: "Die Frist endet am 31. März 2024.",
|
|
normalizeVersion: 1,
|
|
createdAt: "2026-05-25T00:00:00.000Z",
|
|
updatedAt: "2026-05-25T00:00:00.000Z",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeEv(overrides: Partial<EvidenceItem> = {}): EvidenceItem {
|
|
return {
|
|
id: EV_ID,
|
|
annotationIds: [ANN_ID],
|
|
status: "candidate",
|
|
createdAt: "2026-05-25T00:00:00.000Z",
|
|
updatedAt: "2026-05-25T00:00:00.000Z",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("renderCitationCardMarkdown()", () => {
|
|
it("renders quote + attribution + commentary", () => {
|
|
const out = renderCitationCardMarkdown({
|
|
evidenceItem: makeEv({ commentary: "Deadline clause for the buyer." }),
|
|
document: makeDoc(),
|
|
annotation: makeAnn(),
|
|
});
|
|
expect(out).toMatchInlineSnapshot(`
|
|
"> Die Frist endet am 31. März 2024.
|
|
|
|
— *Order from 14 Mar 2024* · [Open source](/viewer?document=doc_2024-order&annotation=ann_para3)
|
|
|
|
Deadline clause for the buyer.
|
|
"
|
|
`);
|
|
});
|
|
|
|
it("omits the commentary paragraph when none is set", () => {
|
|
const out = renderCitationCardMarkdown({
|
|
evidenceItem: makeEv(),
|
|
document: makeDoc(),
|
|
annotation: makeAnn(),
|
|
});
|
|
expect(out).toMatchInlineSnapshot(`
|
|
"> Die Frist endet am 31. März 2024.
|
|
|
|
— *Order from 14 Mar 2024* · [Open source](/viewer?document=doc_2024-order&annotation=ann_para3)
|
|
"
|
|
`);
|
|
});
|
|
|
|
it("preserves multi-line quotes by prefixing each line with '> '", () => {
|
|
const out = renderCitationCardMarkdown({
|
|
evidenceItem: makeEv(),
|
|
document: makeDoc(),
|
|
annotation: makeAnn({ quote: "Line one.\nLine two.\nLine three." }),
|
|
});
|
|
expect(out).toMatchInlineSnapshot(`
|
|
"> Line one.
|
|
> Line two.
|
|
> Line three.
|
|
|
|
— *Order from 14 Mar 2024* · [Open source](/viewer?document=doc_2024-order&annotation=ann_para3)
|
|
"
|
|
`);
|
|
});
|
|
|
|
it("falls back to metadata.filename when document.title is missing", () => {
|
|
const out = renderCitationCardMarkdown({
|
|
evidenceItem: makeEv(),
|
|
document: {
|
|
id: DOC_ID,
|
|
mediaType: "application/pdf",
|
|
createdAt: "2026-05-25T00:00:00.000Z",
|
|
updatedAt: "2026-05-25T00:00:00.000Z",
|
|
metadata: { filename: "order-2024.pdf" },
|
|
},
|
|
annotation: makeAnn(),
|
|
});
|
|
expect(out).toMatchInlineSnapshot(`
|
|
"> Die Frist endet am 31. März 2024.
|
|
|
|
— *order-2024.pdf* · [Open source](/viewer?document=doc_2024-order&annotation=ann_para3)
|
|
"
|
|
`);
|
|
});
|
|
|
|
it("escapes * and _ in the source label to keep italics intact", () => {
|
|
const out = renderCitationCardMarkdown({
|
|
evidenceItem: makeEv(),
|
|
document: makeDoc({ title: "Order *2024_v2*" }),
|
|
annotation: makeAnn(),
|
|
});
|
|
expect(out).toMatchInlineSnapshot(`
|
|
"> Die Frist endet am 31. März 2024.
|
|
|
|
— *Order \\*2024\\_v2\\** · [Open source](/viewer?document=doc_2024-order&annotation=ann_para3)
|
|
"
|
|
`);
|
|
});
|
|
|
|
it("omits the open-context link when override is empty", () => {
|
|
const out = renderCitationCardMarkdown({
|
|
evidenceItem: makeEv(),
|
|
document: makeDoc(),
|
|
annotation: makeAnn(),
|
|
openContextUrlOverride: "",
|
|
});
|
|
expect(out).toMatchInlineSnapshot(`
|
|
"> Die Frist endet am 31. März 2024.
|
|
|
|
— *Order from 14 Mar 2024*
|
|
"
|
|
`);
|
|
});
|
|
|
|
it("honours an openContextUrlOverride for mounted-prefix hosts", () => {
|
|
const out = renderCitationCardMarkdown({
|
|
evidenceItem: makeEv(),
|
|
document: makeDoc(),
|
|
annotation: makeAnn(),
|
|
openContextUrlOverride: "https://citations.example.test/v?d=foo&a=bar",
|
|
});
|
|
expect(out).toContain(
|
|
"[Open source](https://citations.example.test/v?d=foo&a=bar)",
|
|
);
|
|
});
|
|
|
|
it("renders an empty blockquote line when the annotation has no quote", () => {
|
|
const out = renderCitationCardMarkdown({
|
|
evidenceItem: makeEv(),
|
|
document: makeDoc(),
|
|
annotation: {
|
|
id: ANN_ID,
|
|
documentId: DOC_ID,
|
|
representationId: REP_ID,
|
|
selectors: [],
|
|
normalizeVersion: 1,
|
|
createdAt: "2026-05-25T00:00:00.000Z",
|
|
updatedAt: "2026-05-25T00:00:00.000Z",
|
|
},
|
|
});
|
|
expect(out).toMatchInlineSnapshot(`
|
|
">
|
|
|
|
— *Order from 14 Mar 2024* · [Open source](/viewer?document=doc_2024-order&annotation=ann_para3)
|
|
"
|
|
`);
|
|
});
|
|
});
|