/** * docgraph_ticket_list tool: registration, initialization guard, empty states, * kanban grouping/ordering, status filtering, and detail summaries. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { Text } from "@earendil-works/pi-tui"; import { registerDocgraphTicketList } from "../src/tools/ticket-list.ts"; import { writeTicket } from "../src/utils.ts"; import { captureTool, makeTempRepo, cleanupTemp, makeCtx, runTool, toolText, componentText, renderCall, renderResult, sampleTicket, } from "./helpers.ts"; function tool() { return captureTool(registerDocgraphTicketList); } function seededCtx(cwd: string) { return makeCtx({ cwd, state: { initialized: true, schemaVersion: 1 } }).ctx; } test("docgraph_ticket_list registers a typed schema with the status enum", () => { const t = tool(); assert.equal(t.name, "docgraph_ticket_list"); const properties = (t.parameters as unknown as { properties: Record }).properties; assert.deepEqual(properties.status!.enum, [ "backlog", "ready", "in-progress", "review", "done", "blocked", ]); }); test("docgraph_ticket_list refuses to run before initialization", async () => { const cwd = makeTempRepo(); try { const { ctx } = makeCtx({ cwd }); const res = await runTool(tool(), {}, ctx); assert.match(toolText(res), /not initialized/i); assert.equal((res.details as { error: string }).error, "not_initialized"); } finally { cleanupTemp(cwd); } }); test("docgraph_ticket_list reports an empty board without a filter", async () => { const cwd = makeTempRepo(); try { const ctx = seededCtx(cwd); const res = await runTool(tool(), {}, ctx); assert.match(toolText(res), /No tickets found\. Use `docgraph_ticket_create` to create one\./); const details = res.details as { tickets: unknown[]; total: number }; assert.deepEqual(details.tickets, []); assert.equal(details.total, 0); } finally { cleanupTemp(cwd); } }); test("docgraph_ticket_list reports an empty status column when filtered", async () => { const cwd = makeTempRepo(); try { const ctx = seededCtx(cwd); const res = await runTool(tool(), { status: "done" }, ctx); assert.match(toolText(res), /No tickets with status: done/); assert.equal((res.details as { total: number }).total, 0); } finally { cleanupTemp(cwd); } }); test("docgraph_ticket_list renders a kanban board grouped in canonical column order", async () => { const cwd = makeTempRepo(); try { writeTicket(sampleTicket({ id: "001", title: "Alpha", status: "in-progress" }), cwd); writeTicket(sampleTicket({ id: "002", title: "Beta", status: "backlog", priority: "P1" }), cwd); writeTicket(sampleTicket({ id: "003", title: "Gamma", status: "done" }), cwd); const ctx = seededCtx(cwd); const res = await runTool(tool(), {}, ctx); const text = toolText(res); assert.match(text, /3 ticket\(s\)/); // Columns appear in canonical order (backlog before in-progress before done). const backlogAt = text.indexOf("## 📋 BACKLOG (1)"); const inProgressAt = text.indexOf("## 🚧 IN-PROGRESS (1)"); const doneAt = text.indexOf("## 🎉 DONE (1)"); assert.ok(backlogAt >= 0 && inProgressAt >= 0 && doneAt >= 0); assert.ok(backlogAt < inProgressAt && inProgressAt < doneAt); // Each ticket row carries its file link, title and priority. assert.match(text, /\[T-002\]\(docs\/tickets\/T-002-beta\.md\) \*\*Beta\*\* \(P1\)/); const details = res.details as { total: number; filtered: number }; assert.equal(details.total, 3); assert.equal(details.filtered, 3); } finally { cleanupTemp(cwd); } }); test("docgraph_ticket_list filters by status", async () => { const cwd = makeTempRepo(); try { writeTicket(sampleTicket({ id: "001", title: "Alpha", status: "in-progress" }), cwd); writeTicket(sampleTicket({ id: "002", title: "Beta", status: "backlog" }), cwd); const ctx = seededCtx(cwd); const res = await runTool(tool(), { status: "in-progress" }, ctx); const text = toolText(res); assert.match(text, /1 ticket\(s\)/); assert.match(text, /Alpha/); assert.doesNotMatch(text, /Beta/); const details = res.details as { total: number; filtered: number }; assert.equal(details.total, 2); assert.equal(details.filtered, 1); } finally { cleanupTemp(cwd); } }); test("docgraph_ticket_list renderers summarize counts", () => { const t = tool(); const call = componentText(renderCall(t, { status: "ready" })); assert.match(call, /docgraph-ticket-list/); assert.match(call, /ready/); const result = { content: [{ type: "text", text: "2 ticket(s)" }], details: { filtered: 2, total: 4 }, }; const rendered = componentText(renderResult(t, result as never)); assert.match(rendered, /2\/4 ticket\(s\)/); assert.ok(renderCall(t, {}) instanceof Text); });