test(coverage): bootstrap vitest unit infra and add tests at 90%+ per file (#1245) (#1269)

* test(coverage): bootstrap vitest unit infra and add tests at 90%+ per file (#1245)

Establishes the unit-test runtime AGENTS.md mandates: a vitest "unit"
project alongside the existing storybook project, RTL + jest-dom +
happy-dom, a Mantine-aware render helper, and a 90%-line per-file
coverage gate enforced by CI. Adds 66 test files covering all 62
components under elements/groups/screens/views, plus core/mcp and
clients/web/src/utils. Total: 577 tests, 98.7% line coverage globally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(coverage): address PR #1269 review feedback

- AGENTS.md: correct coverage gate description (lines ≥ 90, statements ≥ 85,
  functions ≥ 80, branches ≥ 50) with rationale for the relaxed branch
  threshold
- InspectorView.test.tsx: document what the Math.random() mock values
  (0.1 / 0.99) control vs. STUB_SUCCESS_RATE in InspectorView.tsx
- vite.config.ts / tsconfig.test.json: drop unused `globals: true` and the
  `vitest/globals` type now that every test file imports describe/it/expect/vi
  explicitly; manual `afterEach(cleanup)` in setup.ts remains required because
  testing-library auto-cleanup relies on globalThis.afterEach

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-05-01 15:15:32 -04:00 • committed by GitHub
commit 75f74d64c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -0,0 +1,184 @@
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(<ToolDetailPanel {...baseProps} tool={simpleTool} />);
expect(screen.getByText("send_message")).toBeInTheDocument();
});
it("prefers the title over the name when title is provided", () => {
renderWithMantine(<ToolDetailPanel {...baseProps} tool={titledTool} />);
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(<ToolDetailPanel {...baseProps} tool={simpleTool} />);
expect(
screen.queryByText("Sends a message to the recipient"),
).not.toBeInTheDocument();
});
it("renders all annotation badges when annotations are present", () => {
renderWithMantine(<ToolDetailPanel {...baseProps} tool={annotatedTool} />);
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(<ToolDetailPanel {...baseProps} tool={simpleTool} />);
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(
<ToolDetailPanel {...baseProps} tool={toolWithEmptyAnnotations} />,
);
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(<ToolDetailPanel {...baseProps} tool={simpleTool} />);
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(
<ToolDetailPanel
{...baseProps}
tool={simpleTool}
onExecute={onExecute}
/>,
);
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(
<ToolDetailPanel {...baseProps} tool={simpleTool} isExecuting={true} />,
);
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(
<ToolDetailPanel
{...baseProps}
tool={simpleTool}
isExecuting={true}
onCancel={onCancel}
/>,
);
await user.click(screen.getByRole("button", { name: "Cancel" }));
expect(onCancel).toHaveBeenCalledTimes(1);
});
it("renders progress display when progress is provided", () => {
renderWithMantine(
<ToolDetailPanel
{...baseProps}
tool={simpleTool}
progress={{ progress: 3, total: 5, message: "Step 3 of 5" }}
/>,
);
expect(screen.getByText("Step 3 of 5")).toBeInTheDocument();
expect(screen.getByText("60%")).toBeInTheDocument();
});
it("renders the schema form using the tool's inputSchema", () => {
renderWithMantine(<ToolDetailPanel {...baseProps} tool={simpleTool} />);
// 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(
<ToolDetailPanel
{...baseProps}
tool={simpleTool}
onFormChange={onFormChange}
/>,
);
const input = screen.getByRole("textbox");
await user.type(input, "hi");
expect(onFormChange).toHaveBeenCalled();
});
});