import { render } from "@testing-library/react" import { describe, expect, it } from "vitest" import { ToolCallCard } from "@/components/chat/ToolCallCard" const dict = { tools: { complete: "Complete" }, chat: { copied: "c", failedToCopy: "f", copyResponse: "r" }, } const base = { expandedTools: {}, setExpandedTools: () => {}, onCopy: () => {}, copiedToolCallId: null, copyFailedToolCallId: null, dict, } /** * What the tool-call card shows in the chat. * * Both diagram tools happen to name their argument `operations`, but the items have * different shapes: edit_diagram sends `operation`/`cell_id`/`new_xml` patches, while * restructure_diagram sends `op`/`id` structural steps. The card used to dispatch on * "does an operations key exist", so a restructure call was rendered as edit patches and * every row printed a blank `cell_id:` label with nothing after it. */ describe("ToolCallCard", () => { it("shows restructure_diagram operations, not blank cell_id rows", () => { const { container } = render( , ) const text = container.textContent ?? "" // The bug: every row printed "cell_id:" with nothing after it. expect(text).not.toContain("cell_id:") // Operation names and ids are visible. for (const s of [ "set_page", "add_container", "add_box", "add_graph", "page", "mast", ]) expect(text).toContain(s) // Arguments are summarised. expect(text).toContain("class=gap-4") expect(text).toContain("2 nodes, 1 edge") // The tool's own answer is shown, not thrown away. expect(text).toContain("Diagram updated.") // And it has a readable name. expect(text).toContain("Build Diagram") }) it("still renders edit_diagram patches the old way", () => { const { container } = render( ', }, ], }, }} />, ) const text = container.textContent ?? "" expect(text).toContain("cell_id: 3") expect(text).toContain("update") }) }) /** * Streaming: the tool input arrives character by character. * * The card renders on every frame of that, so it is handed JSON that has been repaired * mid-flight — an operation may be `{}`, have a half-typed name, or be a hole in the array. * The first version of the restructure renderer read `op.op.startsWith(...)` and crashed the * whole message with "Cannot read properties of undefined". These cases are what the earlier * tests missed by only ever passing complete input. */ describe("ToolCallCard while the input is still streaming", () => { const partial = (operations: unknown[]) => ({ type: "tool-restructure_diagram", toolCallId: "s1", state: "input-streaming", input: { operations }, }) it("renders an operation with no name yet", () => { const { container } = render( , ) expect(container.textContent).toContain("…") }) it("renders a half-typed operation name", () => { const { container } = render( , ) expect(container.textContent).toContain("add_contai") }) it("survives a hole in the array", () => { // A repaired JSON array can have missing entries, which arrive as undefined. const { container } = render( , ) const text = container.textContent ?? "" expect(text).toContain("set_page") expect(text).toContain("add_box") }) it("survives an operation whose fields are half-formed", () => { const { container } = render( , ) expect(container.textContent).toContain("add_graph") }) it("edit_diagram's renderer survives the same partial input", () => { const { container } = render( , ) const text = container.textContent ?? "" expect(text).toContain("cell_id: 3") // Exactly one label, for the one entry that has an id — a half-formed entry must // not print a bare "cell_id:" with nothing after it, which is the original bug. expect(text.match(/cell_id:/g)).toHaveLength(1) }) })