import { describe, expect, test } from "bun:test"; import { capabilities } from "./index"; import { buildChatSystemPrompt } from "./prompt"; import { AUTO_RENDERED_TOOL_UI, chatUiTools, RENDER_CHART, RENDER_STAT_CARDS, RENDER_TABLE, renderChartInputSchema, renderTableInputSchema, } from "./ui-tools"; describe("chatUiTools", () => { test("exposes the three render tools with parseable schemas", () => { const names = chatUiTools.map((t) => t.name); expect(names).toEqual([RENDER_CHART, RENDER_TABLE, RENDER_STAT_CARDS]); expect( renderChartInputSchema.safeParse({ type: "pie", title: "Time by project", unit: "m", data: [{ label: "scribe", value: 3500 }], }).success, ).toBe(true); // A chart needs at least one data point. expect(renderChartInputSchema.safeParse({ data: [] }).success).toBe(false); expect( renderChartInputSchema.parse({ data: [{ label: "scribe", value: 3500 }], }).type, ).toBe("bar"); expect( renderTableInputSchema.safeParse({ columns: ["Name", "Events"], rows: [["scribe", "3500"]], }).success, ).toBe(true); }); }); describe("AUTO_RENDERED_TOOL_UI", () => { test("every entry names a real capability (catches renames/drift)", () => { const capabilityNames = new Set(capabilities.map((cap) => cap.name)); for (const name of Object.keys(AUTO_RENDERED_TOOL_UI)) { expect(capabilityNames).toContain(name); } }); }); describe("buildChatSystemPrompt UI guidance", () => { const prompt = buildChatSystemPrompt({ mode: "hosted", date: "1/1/2026", capabilities, }); test("tells the model it can render UI and to prefer it", () => { expect(prompt).toContain(RENDER_CHART); expect(prompt).toContain(RENDER_TABLE); expect(prompt).toContain(RENDER_STAT_CARDS); }); test("forbids repeating rendered data as a markdown table (no duplicate)", () => { expect(prompt.toLowerCase()).toContain("do not also repeat"); }); test("tells the model which tool results are auto-visualized", () => { expect(prompt).toContain("AUTOMATICALLY visualizes"); for (const [name, ui] of Object.entries(AUTO_RENDERED_TOOL_UI)) { expect(prompt).toContain(`- ${name}: ${ui}`); } expect(prompt).toContain("never re-present data"); }); test("caps redundant reporting-tool chains to one per question", () => { expect(prompt).toContain("at most ONE reporting tool"); expect(prompt).toContain("do not also call getRecentActivity"); }); test("steers full per-event detail to export/report links instead of the context window", () => { expect(prompt).toContain("raw per-event data is never loaded"); expect(prompt).toContain("eventsExport"); expect(prompt).toContain("call saveReport and give them the returned link"); }); test("omits the auto-render section when no visualized capability is registered", () => { const bare = buildChatSystemPrompt({ mode: "hosted", date: "1/1/2026", capabilities: [], }); expect(bare).not.toContain("AUTOMATICALLY visualizes"); // The generative-UI tools are still offered for non-auto-rendered data. expect(bare).toContain(RENDER_CHART); }); test("nudges toward the unassigned → map-to-project flow", () => { expect(prompt).toContain("getUnassignedWork"); expect(prompt).toContain("mapUnassignedWork"); }); });