theo-agent-dashboard

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

ToolCall.test.tsx (6027B)


      1 import { describe, it, expect } from "vitest";
      2 import { render, screen, fireEvent } from "@testing-library/react";
      3 import { ToolCall } from "./ToolCall";
      4 import type { ToolCall as ToolCallType } from "../../store/chat";
      5 
      6 const baseToolCall: ToolCallType = {
      7   id: "tc1",
      8   name: "search",
      9   args: '{"query":"test"}',
     10   result: null,
     11   status: "pending",
     12 };
     13 
     14 describe("ToolCall", () => {
     15   it("renders tool name in monospace span", () => {
     16     const { container } = render(<ToolCall toolCall={baseToolCall} />);
     17     const nameSpan = container.querySelector(".font-mono");
     18     expect(nameSpan?.textContent).toBe("search");
     19   });
     20 
     21   it("renders operation badge", () => {
     22     const { container } = render(<ToolCall toolCall={{ ...baseToolCall, name: "read_file" }} />);
     23     const badge = container.querySelector(".inline-flex.uppercase");
     24     expect(badge?.textContent).toBe("READ");
     25   });
     26 
     27   it("renders SEARCH badge for search_files", () => {
     28     const { container } = render(<ToolCall toolCall={{ ...baseToolCall, name: "search_files" }} />);
     29     const badge = container.querySelector(".inline-flex.uppercase");
     30     expect(badge?.textContent).toBe("SEARCH");
     31   });
     32 
     33   it("is collapsed by default", () => {
     34     render(<ToolCall toolCall={{ ...baseToolCall, result: "found" }} />);
     35     expect(screen.queryByText("Arguments")).not.toBeInTheDocument();
     36     expect(screen.queryByText("Result")).not.toBeInTheDocument();
     37   });
     38 
     39   it("expands to show args and result on click", () => {
     40     render(<ToolCall toolCall={{ ...baseToolCall, result: "found it" }} />);
     41     fireEvent.click(screen.getByRole("button"));
     42     expect(screen.getByText("Arguments")).toBeInTheDocument();
     43     expect(screen.getByText("Result")).toBeInTheDocument();
     44   });
     45 
     46   it("collapses when clicked again", () => {
     47     render(<ToolCall toolCall={{ ...baseToolCall, result: "found it" }} />);
     48     fireEvent.click(screen.getByRole("button"));
     49     expect(screen.getByText("Arguments")).toBeInTheDocument();
     50     fireEvent.click(screen.getByRole("button"));
     51     expect(screen.queryByText("Arguments")).not.toBeInTheDocument();
     52   });
     53 
     54   it("shows running indicator when status is running", () => {
     55     render(<ToolCall toolCall={{ ...baseToolCall, status: "running" }} />);
     56     expect(screen.getByText("Running…")).toBeInTheDocument();
     57   });
     58 
     59   it("shows pending status", () => {
     60     render(<ToolCall toolCall={{ ...baseToolCall, status: "pending" }} />);
     61     expect(screen.getByText("Pending")).toBeInTheDocument();
     62   });
     63 
     64   it("shows complete status", () => {
     65     render(<ToolCall toolCall={{ ...baseToolCall, status: "complete" }} />);
     66     expect(screen.getByText("Done")).toBeInTheDocument();
     67   });
     68 
     69   it("shows error status", () => {
     70     render(<ToolCall toolCall={{ ...baseToolCall, status: "error" }} />);
     71     expect(screen.getByText("Error")).toBeInTheDocument();
     72   });
     73 
     74   it("applies error styling for error status", () => {
     75     const { container } = render(<ToolCall toolCall={{ ...baseToolCall, status: "error" }} />);
     76     const statusSpan = container.querySelector(".text-red-400");
     77     expect(statusSpan).toBeInTheDocument();
     78   });
     79 
     80   it("applies running styling", () => {
     81     const { container } = render(<ToolCall toolCall={{ ...baseToolCall, status: "running" }} />);
     82     const statusSpan = container.querySelector(".text-yellow-400");
     83     expect(statusSpan).toBeInTheDocument();
     84   });
     85 
     86   it("applies complete styling", () => {
     87     const { container } = render(<ToolCall toolCall={{ ...baseToolCall, status: "complete" }} />);
     88     const statusSpan = container.querySelector(".text-green-400");
     89     expect(statusSpan).toBeInTheDocument();
     90   });
     91 
     92   it("applies pending styling", () => {
     93     const { container } = render(<ToolCall toolCall={{ ...baseToolCall, status: "pending" }} />);
     94     const statusSpan = container.querySelector(".text-gray-400");
     95     expect(statusSpan).toBeInTheDocument();
     96   });
     97 
     98   it("formats JSON args when expanded", () => {
     99     render(<ToolCall toolCall={baseToolCall} />);
    100     fireEvent.click(screen.getByRole("button"));
    101     expect(screen.getByText(/"query":\s*"test"/)).toBeInTheDocument();
    102   });
    103 
    104   it("does not show result when result is null", () => {
    105     render(<ToolCall toolCall={baseToolCall} />);
    106     fireEvent.click(screen.getByRole("button"));
    107     expect(screen.getByText("Arguments")).toBeInTheDocument();
    108     expect(screen.queryByText("Result")).not.toBeInTheDocument();
    109   });
    110 
    111   it("shows result when result is non-null", () => {
    112     render(<ToolCall toolCall={{ ...baseToolCall, result: "some output" }} />);
    113     fireEvent.click(screen.getByRole("button"));
    114     expect(screen.getByText("Result")).toBeInTheDocument();
    115   });
    116 
    117   it("handles non-JSON args gracefully", () => {
    118     render(<ToolCall toolCall={{ ...baseToolCall, args: "not json" }} />);
    119     fireEvent.click(screen.getByRole("button"));
    120     expect(screen.getByText("not json")).toBeInTheDocument();
    121   });
    122 
    123   it("arrow rotates when expanded", () => {
    124     const { container } = render(<ToolCall toolCall={baseToolCall} />);
    125     const arrow = container.querySelector("svg");
    126     expect(arrow).not.toHaveClass("rotate-90");
    127 
    128     fireEvent.click(screen.getByRole("button"));
    129     expect(arrow).toHaveClass("rotate-90");
    130   });
    131 
    132   it("displays different tool names", () => {
    133     const { container } = render(<ToolCall toolCall={{ ...baseToolCall, name: "calculator" }} />);
    134     const nameSpan = container.querySelector(".font-mono");
    135     expect(nameSpan?.textContent).toBe("calculator");
    136   });
    137 
    138   it("running status shows pulse indicator", () => {
    139     const { container } = render(
    140       <ToolCall toolCall={{ ...baseToolCall, status: "running" }} />,
    141     );
    142     const pulse = container.querySelector(".animate-pulse");
    143     expect(pulse).toBeInTheDocument();
    144   });
    145 
    146   it("non-running status does not show pulse indicator", () => {
    147     const { container } = render(
    148       <ToolCall toolCall={{ ...baseToolCall, status: "complete" }} />,
    149     );
    150     const pulse = container.querySelector(".animate-pulse");
    151     expect(pulse).not.toBeInTheDocument();
    152   });
    153 });