// The components' markup, rendered on the server the way a page renders it: what a screen reader and // a keyboard user get is in the HTML, so the accessibility basics are asserted there -- a name for // every control, a caption and scoped headers for every table, a status that says what a colour would. import { buildRegistry } from "@conusai-kit/core" import { TransportProvider as KitTransport } from "@conusai-kit/ui" import { renderToStaticMarkup } from "react-dom/server" import { describe, expect, it } from "vitest" import { appModules } from "../../index.js" import type { DocumentView, LineView } from "../contract/index.js" import { DocumentList, ExportButton, exportLabels, LineReview, listLabels, reviewLabels, TransportProvider, UploadForm, uploadLabels, } from "../ui/index.js" const en = buildRegistry([...appModules]).catalogues.en?.documents ?? {} const t = (key: string, values?: Record) => { let text = en[key as keyof typeof en] ?? `?${key}` for (const [name, value] of Object.entries(values ?? {})) text = text.replace(`{${name}}`, String(value)) return text } const slugs = { org: "acme", workspace: "ws", project: "p" } const shared = { failed: "That did not work: {detail}", aiDisclosure: "AI-generated content", pageWord: "Page" } const line = (over: Partial = {}): LineView => ({ lineId: "l1", expectedVersion: 1, status: "pending", label: "Reference", value: "QR-1", page: 1, quote: "Reference: QR-1", box: { x: 0.1, y: 0.2, width: 0.5, height: 0.03 }, confidence: 0.97, edited: false, verification: "verified", note: null, blockId: null, model: "fixture+gateway", promptVersion: "extract-lines.v2", decidedAt: null, ...over, }) const inProvider = (node: React.ReactNode) => renderToStaticMarkup( undefined} describe={() => "x"}> {node} , ) const review = (lines: LineView[]) => inProvider() describe("the review table", () => { const html = review([ line(), line({ lineId: "l2", label: "Total", status: "approved" }), line({ lineId: "l3", label: "Date", status: "rejected" }), ]) it("tells a reader the content is machine-proposed, in text", () => { expect(html).toContain("AI-generated content") expect(html).toContain("Proposed by fixture+gateway extract-lines.v2") }) it("is a table with a caption and a scoped header for every column", () => { expect(html).toContain("Extracted lines") expect(html.match(//g)).toHaveLength(7) expect(html).toContain('Reference') }) it("names each action by the line it acts on, so a list of buttons is not a list of 'Approve'", () => { for (const name of ["Approve Reference", "Edit Reference", "Reject Reference"]) expect(html).toContain(name) }) it("offers no action on a line that has been decided, and says what was decided in words", () => { expect(html).not.toContain("Approve Total") expect(html).not.toContain("Reject Date") for (const word of ["To review", "Approved", "Rejected"]) expect(html).toContain(word) }) it("shows the source of every line, labelled with its page", () => { expect(html).toContain('aria-label="Source, page 1"') expect(html).toContain("Reference: QR-1") }) it("approves every verified pending line at once, only while there is one", () => { expect(html).toContain("Approve all verified lines") expect(review([line({ status: "approved" })])).not.toContain("Approve all verified lines") }) it("marks a line whose quote was not found, and does not offer it to the bulk approval", () => { const flagged = review([line({ verification: "needs_review" })]) expect(flagged).toContain("Needs review: the quoted words were not found on the page.") expect(flagged).not.toContain("Approve all verified lines") expect(html).not.toContain("Needs review") }) it("says a quote read off a scanned page must be checked against the image, instead of the plain note", () => { const flagged = review([line({ verification: "needs_review", note: "scanned_quote" })]) expect(flagged).toContain("comes from a scanned page") expect(flagged).toContain("Check it against the page image.") expect(flagged).not.toContain("were not found on the page") }) it("names the reader's block beside the source when it gave one", () => { expect(review([line({ blockId: "p3-b2" })])).toContain("Block p3-b2") expect(review([line()])).not.toContain("Block ") }) it("says so when there is nothing to review", () => { expect(review([])).toContain("No lines were proposed for this document.") }) }) describe("the document list", () => { const documents: DocumentView[] = [ { fileId: "f1", filename: "report.pdf", status: "extracted", failureCode: null, pageCount: 2, skippedPages: [], pending: 3, approved: 1, uploadedAt: "2026-10-19T09:00:00.000Z", }, { fileId: "f2", filename: "scan.png", status: "failed", failureCode: "ocr_failed", pageCount: null, skippedPages: [], pending: 0, approved: 0, uploadedAt: "2026-10-19T09:05:00.000Z", }, { fileId: "f3", filename: "new.pdf", status: "queued", failureCode: null, pageCount: null, skippedPages: [], pending: 0, approved: 0, uploadedAt: "2026-10-19T09:06:00.000Z", }, ] const html = renderToStaticMarkup( `/d/${id}`} downloadHrefFor={(id) => `/api/v1/files.file.download?fileId=${id}`} labels={listLabels(t)} formatDate={(iso) => iso.slice(0, 10)} />, ) it("links each document by a name that says which one", () => { expect(html).toContain('href="/d/f1"') expect(html).toContain('aria-label="Review report.pdf"') expect(html).toContain('href="/api/v1/files.file.download?fileId=f1"') expect(html).toContain('aria-label="Download report.pdf"') }) it("writes each state and each failure as text", () => { expect(html).toContain("Ready for review") expect(html).toContain("Could not be read The reader could not process this file.") expect(html).toContain("Waiting to be read") }) it("is a captioned table, or a sentence when there is nothing to list", () => { expect(html).toContain("Documents") expect( renderToStaticMarkup( ""} downloadHrefFor={() => ""} labels={listLabels(t)} formatDate={() => ""} />, ), ).toContain("No documents yet.") }) }) describe("the export button", () => { const render = (approved: number) => inProvider() it("is disabled with nothing approved, and the reason is the text its description points at", () => { const html = render(0) expect(html).toMatch(/]*disabled=""[^>]*aria-describedby="([^"]+)"/) const id = /aria-describedby="([^"]+)"/.exec(html)?.[1] ?? "" expect(html).toContain(`id="${id}">Approve at least one line to export.`) }) it("is enabled once a line is approved, and says only approved lines are exported", () => { const html = render(2) expect(html).not.toContain('disabled=""') expect(html).toContain("Only approved lines are exported.") }) }) describe("the upload form", () => { // The kit's provider, not the module's: the form uploads through the kit's `uploadFile`, which // asks the transport the app provides for a way to send a body. const html = renderToStaticMarkup( undefined} describe={() => "x"}> , ) it("labels its file input and describes it with the hint", () => { // React writes an input's attributes in its own order (`type` first), so the pattern does not fix one. const id = /]*\bid="([^"]+)"[^>]*\bname="file"/.exec(html)?.[1] expect(id).toBeTruthy() expect(html).toContain(``) expect(html).toContain("aria-describedby=") expect(html).toContain("PDF, PNG or JPEG, up to 50 MB.") }) it("asks the browser for the types the module reads and requires a file", () => { expect(html).toContain('accept="application/pdf"') expect(html).toContain('required=""') }) it("announces its outcome in a status region", () => { expect(html).toContain('

') }) })