Files
template-app/modules/agent/__tests__/page.test.tsx
T
2026-11-14 09:56:45 +00:00

157 lines
6.1 KiB
TypeScript

// The agent page on F10: its loader reads through the caller and returns plain data with every label
// translated, and its view renders what reached the browser, the run page's first paint being the seed of
// the live island. A path that is not one run is the kernel's 404, which is what the shell answers.
import { AppError, buildRegistry, Container, forbidden, type TransportCaller } from "@conusai-kit/core"
import { builtInCatalogues, createI18n, uiLabels } from "@conusai-kit/i18n"
import { createDataClient, DataProvider } from "@conusai-kit/ui/data"
import { UiLabelsProvider } from "@conusai-kit/ui/primitives"
import { renderToStaticMarkup as renderMarkup } from "react-dom/server"
import { describe, expect, it, vi } from "vitest"
import { appModules } from "../../index.js"
import { modulePages } from "../../pages.js"
import type { ApprovalView, RunView } from "../contract/index.js"
import { agentPage } from "../ui/pages.js"
const kitStrings = uiLabels(createI18n("en", builtInCatalogues))
const renderToStaticMarkup = (node: React.ReactNode) =>
renderMarkup(<UiLabelsProvider labels={kitStrings}>{node}</UiLabelsProvider>)
const catalogues = buildRegistry([...appModules]).catalogues
const i18n = createI18n("en", {
en: { agent: catalogues.en?.agent ?? {}, app: { failed: "That did not work: {detail}" } },
})
const slugs = { org: "acme", workspace: "ws", project: "p" }
const run: RunView = {
runId: "01a19940-0000-7000-0000-000000000001",
goal: "Order the parts",
status: "running",
failureCode: null,
failureDetail: null,
plan: [
{
key: "read",
label: "Read",
tool: "documents.read",
args: {},
needsApproval: false,
status: "pending",
attempts: 0,
error: null,
},
],
usageCostMicrocents: 120,
usageSteps: 3,
percent: 0.5,
cancelRequested: false,
answer: null,
answerProvenance: null,
actorId: "user-1",
createdAt: "2026-11-02T10:00:00.000Z",
startedAt: "2026-11-02T10:00:01.000Z",
finishedAt: null,
}
const approval: ApprovalView = {
runId: run.runId,
stepKey: "read",
goal: run.goal,
tool: "orders.create",
arguments: { total: 4200 },
reviewUrl: null,
createdAt: "2026-11-02T10:00:00.000Z",
}
const callerOf = (answers: Record<string, unknown>, asked: string[] = []): TransportCaller =>
({
call: async (contract: { name: string }) => {
asked.push(contract.name)
const answer = answers[contract.name]
if (answer instanceof Error) throw answer
return answer
},
}) as unknown as TransportCaller
const load = (segments: string[], answers: Record<string, unknown>, asked?: string[]) =>
agentPage.load({ slugs, segments, search: {}, caller: callerOf(answers, asked), 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<string, unknown>) => {
const data = await load(segments, answers)
expect(JSON.parse(JSON.stringify(data))).toStrictEqual(data)
const { View } = agentPage
return renderToStaticMarkup(
<DataProvider client={createDataClient({ fetch: async () => new Response(null, { status: 501 }) })}>
<View slugs={slugs} segments={segments} locale="en" data={JSON.parse(JSON.stringify(data))} />
</DataProvider>,
)
}
const lists = { "agent.run.list": { runs: [run] }, "agent.approval.list": { approvals: [approval] } }
describe("the agent page", () => {
it("is listed for the shell", async () => {
await expect(modulePages.agent?.()).resolves.toBe(agentPage)
})
it("lists the runs, each with its status in words, its progress and a link to it", async () => {
const html = await render([], lists)
const link = html.match(/<a\b[^>]*>Order the parts<\/a>/)?.[0] ?? ""
expect(link.match(/class="([^"]*)"/)?.[1]?.split(/\s+/)).toContain("kit-row-link")
expect(link).toContain(`href="/acme/ws/p/agent/${run.runId}"`)
expect(link).toContain('data-slot="row-link"')
expect(html).toContain('<progress value="0.5" max="1">50%</progress>')
expect(html).toContain("user-1")
expect(html).toContain("orders.create")
})
it("says when there is no run", async () => {
const data = await load([], { "agent.run.list": { runs: [] }, "agent.approval.list": { approvals: [] } })
if (data.kind !== "list") throw new Error("not the list")
expect(data.runs).toEqual([])
expect(await render([], { ...lists, "agent.run.list": { runs: [] } })).toContain(data.runLabels.empty)
})
it("renders one run from its status, the seed of the live island", async () => {
const asked: string[] = []
const html = await render([run.runId], { "agent.run.status": run })
await load([run.runId], { "agent.run.status": run }, asked)
expect(asked).toEqual(["agent.run.status"])
expect(html).toContain(">Order the parts</h1>")
expect(html).toContain('href="/acme/ws/p/agent"')
expect(html).toContain("<dt>Usage</dt><dd>3 steps, 120 microcents</dd>")
expect(html).toContain('data-live="agent.run"')
})
it("answers a path that is not one run with the kernel's 404, without reading", async () => {
const asked: string[] = []
const refused = load([run.runId, "extra"], lists, asked)
await expect(refused).rejects.toBeInstanceOf(AppError)
await expect(refused).rejects.toMatchObject({ status: 404 })
expect(asked).toEqual([])
})
it("hands a procedure's refusal on, as the AppError the shell answers", async () => {
const call = vi.fn(async () => {
throw forbidden("this page needs agent.run.read")
})
await expect(
agentPage.load({
slugs,
segments: [],
search: {},
caller: { call } as unknown as TransportCaller,
i18n,
container: new Container(),
}),
).rejects.toMatchObject({ status: 403 })
})
})
// This journey requires explicit optional-module admission.
vi.mock("../../inclusion.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../../inclusion.js")>()),
...(await import("../../../test/support/native-readiness.js")).selectedModules(["agent"]),
}))