import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; import { ToolDetailPanel } from "./ToolDetailPanel"; const simpleTool: Tool = { name: "send_message", inputSchema: { type: "object", properties: { message: { type: "string", description: "The message to send" }, }, required: ["message"], }, }; const titledTool: Tool = { name: "send_message", title: "Send Message", description: "Sends a message to the recipient", inputSchema: { type: "object", properties: { message: { type: "string" }, }, }, }; const annotatedTool: Tool = { name: "delete_records", description: "Deletes records", annotations: { readOnlyHint: true, destructiveHint: true, idempotentHint: true, openWorldHint: true, }, inputSchema: { type: "object", properties: { pattern: { type: "string" }, }, }, }; const baseProps = { formValues: {}, isExecuting: false, onFormChange: vi.fn(), onExecute: vi.fn(), onCancel: vi.fn(), }; describe("ToolDetailPanel", () => { it("renders the tool name when no title is provided", () => { renderWithMantine(); expect(screen.getByText("send_message")).toBeInTheDocument(); }); it("prefers the title over the name when title is provided", () => { renderWithMantine(); expect(screen.getByText("Send Message")).toBeInTheDocument(); expect( screen.getByText("Sends a message to the recipient"), ).toBeInTheDocument(); }); it("does not render the description when none is provided", () => { renderWithMantine(); expect( screen.queryByText("Sends a message to the recipient"), ).not.toBeInTheDocument(); }); it("renders all annotation badges when annotations are present", () => { renderWithMantine(); expect(screen.getByText("read-only")).toBeInTheDocument(); expect(screen.getByText("destructive")).toBeInTheDocument(); expect(screen.getByText("idempotent")).toBeInTheDocument(); expect(screen.getByText("open-world")).toBeInTheDocument(); }); it("does not render annotation row when no annotation flags are set", () => { renderWithMantine(); expect(screen.queryByText("read-only")).not.toBeInTheDocument(); expect(screen.queryByText("destructive")).not.toBeInTheDocument(); }); it("does not render annotation row when annotations object is empty", () => { const toolWithEmptyAnnotations: Tool = { name: "no_hints", annotations: { title: "Does not matter" }, inputSchema: { type: "object", properties: {}, }, }; renderWithMantine( , ); expect(screen.queryByText("read-only")).not.toBeInTheDocument(); expect(screen.queryByText("destructive")).not.toBeInTheDocument(); expect(screen.queryByText("idempotent")).not.toBeInTheDocument(); expect(screen.queryByText("open-world")).not.toBeInTheDocument(); }); it("renders the Execute Tool button enabled when not executing", () => { renderWithMantine(); const button = screen.getByRole("button", { name: "Execute Tool" }); expect(button).toBeInTheDocument(); expect(button).not.toBeDisabled(); }); it("invokes onExecute when Execute Tool is clicked", async () => { const user = userEvent.setup(); const onExecute = vi.fn(); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: "Execute Tool" })); expect(onExecute).toHaveBeenCalledTimes(1); }); it("disables the Execute Tool button while executing and renders Cancel", () => { renderWithMantine( , ); expect(screen.getByRole("button", { name: "Execute Tool" })).toBeDisabled(); expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument(); }); it("invokes onCancel when Cancel is clicked", async () => { const user = userEvent.setup(); const onCancel = vi.fn(); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: "Cancel" })); expect(onCancel).toHaveBeenCalledTimes(1); }); it("renders progress display when progress is provided", () => { renderWithMantine( , ); expect(screen.getByText("Step 3 of 5")).toBeInTheDocument(); expect(screen.getByText("60%")).toBeInTheDocument(); }); it("renders the schema form using the tool's inputSchema", () => { renderWithMantine(); // The string field 'message' should render with its description expect(screen.getByText("The message to send")).toBeInTheDocument(); }); it("invokes onFormChange when the user types in a form field", async () => { const user = userEvent.setup(); const onFormChange = vi.fn(); renderWithMantine( , ); const input = screen.getByRole("textbox"); await user.type(input, "hi"); expect(onFormChange).toHaveBeenCalled(); }); });