// 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 { 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 { crmModule } from "../module.js" import { crmPage } from "../ui/pages.js" const catalogues = buildRegistry([tenancyModule, crmModule]).catalogues const i18n = createI18n("en", { en: { crm: catalogues.en?.crm ?? {}, app: { failed: "That did not work: {detail}" } } }) const slugs = { org: "acme", workspace: "ws", project: "p" } const EVERYTHING: readonly PermissionKey[] = ["crm.party.read", "crm.party.write"] const kitStrings = uiLabels(createI18n("en", builtInCatalogues)) const renderToStaticMarkup = (node: React.ReactNode) => renderMarkup({node}) const inProvider = (node: ReactNode, permissions: readonly PermissionKey[] = EVERYTHING) => 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 const load = (segments: string[], answers: Record) => crmPage.load({ slugs, segments, search: {}, caller: callerOf(answers), i18n, container: new Container() }) /** 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 load(segments, answers) expect(JSON.parse(JSON.stringify(data))).toEqual(data) const { View } = crmPage return inProvider( , permissions, ) } const party = { partyId: "p-1", kind: "organization", displayName: "Acme Supplies", legalName: "Acme Supplies UAB", country: "LT", status: "active", linked: false, } describe("the crm page", () => { it("lists the parties, with the form for whoever may add one", async () => { const answers = { "crm.party.list": { rows: [party] } } const html = await render([], answers) expect(html).toContain(">Customers") // The row's name is its link: the kit's row link (`kit-row-link`). const link = html.match(/]*>Acme Supplies<\/a>/)?.[0] ?? "" expect(link).toContain('data-slot="row-link"') expect(link.match(/class="([^"]*)"/)?.[1]?.split(/\s+/)).toContain("kit-row-link") expect(link).toContain('href="/acme/ws/p/crm/p-1"') expect(link).toContain('aria-label="Open Acme Supplies"') expect(html).toContain('aria-label="Parties"') expect(html).toContain("New party") expect(await render([], answers, ["crm.party.read"])).not.toContain("New party") }) it("shows a party's contact points and the form that adds one", async () => { const html = await render(["p-1"], { "crm.party.get": { party: { ...party, linked: true }, contacts: [ { contactId: "c1", kind: "email", value: "orders@acme.example", purposes: ["invoice_email"], isPrimary: true, erased: false, }, { contactId: "c2", kind: "phone", value: "", purposes: [], isPrimary: false, erased: true }, ], }, }) expect(html).toContain(">Acme Supplies") expect(html).toContain("Linked to a member's account") expect(html).toContain("2 contact points") expect(html).toContain('aria-label="Contact points"') for (const text of ["E-mail", "orders@acme.example", "Invoices by e-mail", "Yes"]) expect(html).toContain(text) expect(html).toContain("Erased") expect(html).toContain("Add a contact point") }) it("shows the kit's not-enabled state when the organization has not enabled the service", async () => { const refused = new AppError({ type: "/problems/service_not_enabled", title: "Service prerequisite unavailable", status: 404, code: "service_not_enabled", detail: "Service admission refused: not_enabled.", }) const html = await render([], { "crm.party.list": refused }) expect(html).toContain('data-state="service_not_enabled"') expect(html).toContain("This service is not enabled for your organization.") expect(html).not.toContain('role="grid"') }) it("tells a Lithuanian reader in the kit's Lithuanian sentences", async () => { const lt = createI18n("lt", { lt: { crm: catalogues.lt?.crm ?? {} } }) const silent = new AppError({ type: "/problems/business_unavailable", title: "Refused", status: 503, code: "business_unavailable", }) const data = await crmPage.load({ slugs, segments: [], search: {}, caller: callerOf({ "crm.party.list": silent }), i18n: lt, container: new Container(), }) expect(data).toMatchObject({ kind: "state", notice: { code: "business_unavailable", text: "Paslauga neatsakė. Bandykite vėliau." }, }) const list = await crmPage.load({ slugs, segments: [], search: {}, caller: callerOf({ "crm.party.list": { rows: [] } }), i18n: lt, container: new Container(), }) expect(list.kind === "list" && list.form.refusals.business_stale_revision).toBe( "Kažkas tai pakeitė, kol jūs dirbote. Atnaujinkite ir bandykite dar kartą.", ) }) it("leaves a party that is not there, and a path it does not have, to the shell", async () => { const missing = new AppError({ type: "/problems/business_not_found", title: "Not found", status: 404, code: "business_not_found", }) await expect(render(["p-2"], { "crm.party.get": missing })).rejects.toMatchObject({ problem: { code: "business_not_found" }, }) await expect(render(["p-1", "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.crm?.()).resolves.toBe(crmPage) }) }) // Explicit synthetic admission: module-local fixtures exercise the selected module. vi.mock("../../inclusion.json", () => ({ default: { schemaVersion: 1, selected: ["crm"], everAdopted: ["crm"] } }))