// The pages and components, rendered the way the shell renders them, from the loader's data: the platform's // refusals are the page's own states, a control is offered to whoever may use it, every table has a // caption and scoped headers. import { AppError, buildRegistry, Container, type TransportCaller } from "@conusai-kit/core" import { filesModule } from "@conusai-kit/files" import { builtInCatalogues, createI18n, uiLabels } from "@conusai-kit/i18n" import { tenancyModule } from "@conusai-kit/tenancy" import { createDataClient, DataProvider, type PermissionKey, PermissionsProvider } from "@conusai-kit/ui/data" import { UiLabelsProvider } from "@conusai-kit/ui/primitives" import type { ReactNode } from "react" import { renderToStaticMarkup as renderMarkup } from "react-dom/server" import { describe, expect, it, vi } from "vitest" import { modulePages } from "../../pages.js" import { archiveModule } from "../module.js" import { archivePage } from "../ui/pages.js" const catalogues = buildRegistry([tenancyModule, filesModule, archiveModule]).catalogues const i18n = createI18n("en", { en: { archive: catalogues.en?.archive ?? {}, app: { failed: "That did not work: {detail}" } }, }) const lithuanian = createI18n("lt", { lt: { archive: catalogues.lt?.archive ?? {}, app: { failed: "Nepavyko: {detail}" } }, }) const slugs = { org: "acme", workspace: "ws", project: "p" } const VIEWER: readonly PermissionKey[] = ["archive.document.read"] const EDITOR: readonly PermissionKey[] = [...VIEWER, "archive.document.write"] const ADMIN: readonly PermissionKey[] = [...EDITOR, "archive.retention.manage"] const kitStrings = uiLabels(createI18n("en", builtInCatalogues)) const renderToStaticMarkup = (node: ReactNode) => renderMarkup({node}) const inProvider = (node: ReactNode, permissions: readonly PermissionKey[] = ADMIN) => renderToStaticMarkup( new Response(null, { status: 501 }) })}> {node} , ) const callerOf = (answers: Record): TransportCaller => ({ call: async (contract: { name: string }) => { const answer = answers[contract.name] if (answer instanceof Error) throw answer return answer }, }) as unknown as TransportCaller /** The page as the shell renders it: the loader's data through JSON, as the browser receives it, then the view. */ const render = async (segments: string[], answers: Record, permissions?: readonly PermissionKey[]) => { const data = await archivePage.load({ slugs, segments, search: {}, caller: callerOf(answers), i18n, container: new Container(), }) expect(JSON.parse(JSON.stringify(data))).toEqual(data) const { View } = archivePage return inProvider( , permissions, ) } const escaped = (text: string): string => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") /** Every label of a form names a control the page holds, and every hint is in its control's description. */ const expectLabelled = (html: string, labels: number): void => { const targets = [...html.matchAll(/]* for="([^"]+)"/g)].map((match) => match[1] ?? "") expect(targets).toHaveLength(labels) for (const id of targets) expect(html).toMatch(new RegExp(`<(?:input|select|button)[^>]* id="${escaped(id)}"`)) for (const [, id = ""] of html.matchAll(/

{ it("lists the documents, with the register form for whoever may add one", async () => { const html = await render([], listAnswers) expect(html).toMatch(/]*>Archive<\/h1>/) expect(html).toMatch( /]*href="\/acme\/ws\/p\/archive\/doc_1")(?=[^>]*aria-label="Open Signed contract")[^>]*>Signed contract<\/a>/, ) expect(html).toMatch(/]*aria-label="Documents"/) expect(html).toMatch(/]*value="contracts"[^>]*>contracts<\/option>/) expect(html).toContain('type="file"') expectLabelled(html, 5) expect(html.match(/data-slot="field-description"/g)).toHaveLength(1) expect(await render([], listAnswers, VIEWER)).not.toContain('type="file"') }) it("says a document cannot be registered before there is a retention class", async () => { const html = await render([], { ...listAnswers, "archive.retention.list": { rows: [] } }) expect(html).toContain("Define a retention class first.") expect(html).not.toContain('type="file"') }) it("shows a document's versions, with the controls each role may use", async () => { const html = await render(["doc_1"], documentAnswers) expect(html).toContain("

Signed contract

") expect(html).toMatch(/]*>Versions<\/caption>/) expect(html).toMatch(/]*scope="row"[^>]*>1<\/th>]*>application\/pdf<\/td>]*>1234<\/td>/) expect(html).toContain("Add a version") expectLabelled(html, 1) expect(html).toContain("Place a legal hold") expect(html).toContain("Dispose") const editor = await render(["doc_1"], documentAnswers, EDITOR) expect(editor).toContain("Add a version") expect(editor).not.toContain("Place a legal hold") const viewer = await render(["doc_1"], documentAnswers, VIEWER) expect(viewer).not.toContain("Add a version") }) it("offers nothing on a disposed document", async () => { const disposed = { ...document, status: "disposed", disposed: true, disposedAt: "2026-12-01T10:00:00.000Z" } const html = await render(["doc_1"], { "archive.document.get": { document: disposed, versions: [version] } }) expect(html).toContain("Disposed") expect(html).not.toContain("Add a version") expect(html).not.toContain("Place a legal hold") }) it("lists the retention classes and what is due, with the form for an administrator", async () => { const html = await render(["retention"], retentionAnswers) expect(html).toMatch(/]*>Retention<\/h1>/) expect(html).toMatch( /]*scope="row"[^>]*>contracts<\/th>]*>P10Y<\/td>]*>When the document is registered<\/td>/, ) expect(html).toMatch(/]*>Disposal due<\/caption>/) expect(html).toContain("New retention class") expectLabelled(html, 3) expect(html.match(/data-slot="field-description"/g)).toHaveLength(1) expect(await render(["retention"], retentionAnswers, EDITOR)).not.toContain("New retention class") }) it.each([ ["service_not_enabled", "This service is not enabled for your organization."], ["service_permission_missing", "This app is not allowed to use this service. Ask an administrator to grant it."], ["business_unavailable", "The service did not answer. Try again later."], ])("shows the typed state %s", async (code, text) => { const refused = new AppError({ type: `/problems/${code}`, title: "Refused", status: 503, code, detail: "why" }) const html = await render([], { ...listAnswers, "archive.document.list": refused }) expect(html).toContain(`data-state="${code}"`) expect(html).toContain('role="status"') expect(html).toContain(text) expect(html).not.toContain(" { const code = "service_not_enabled" const refused = new AppError({ type: `/problems/${code}`, title: "Refused", status: 404, code }) const data = await archivePage.load({ slugs, segments: [], search: {}, caller: callerOf({ ...listAnswers, "archive.document.list": refused }), i18n: lithuanian, container: new Container(), }) expect(data).toMatchObject({ kind: "state", notice: { code, text: "Ši paslauga jūsų organizacijai neįjungta." }, }) }) it("leaves a document that is not there, and a path it does not have, to the shell", async () => { const missing = new AppError({ type: "/problems/not_found", title: "Not found", status: 404, code: "not_found" }) await expect(render(["doc_2"], { "archive.document.get": missing })).rejects.toMatchObject({ problem: { code: "not_found" }, }) await expect(render(["doc_1", "x"], {})).rejects.toMatchObject({ problem: { status: 404 } }) await expect(render(["retention", "x"], {})).rejects.toMatchObject({ problem: { status: 404 } }) await expect(render(["%2Fetc"], {})).rejects.toMatchObject({ problem: { status: 404 } }) await expect(render(["%E0%A4%A"], {})).rejects.toMatchObject({ problem: { status: 404 } }) }) it("is listed for the shell", async () => { await expect(modulePages.archive?.()).resolves.toBe(archivePage) }) }) // Explicit synthetic admission: module-local fixtures exercise the selected module. vi.mock("../../inclusion.json", () => ({ default: { schemaVersion: 1, selected: ["archive"], everAdopted: ["archive"] }, }))