import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import { waitFor } from "@testing-library/react"; import type { ServerEntry } from "@inspector/core/mcp/types.js"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; import { ServerRemoveConfirmModal } from "./ServerRemoveConfirmModal"; const stdioTarget: ServerEntry = { id: "alpha", name: "alpha", config: { type: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-everything"], }, connection: { status: "disconnected" }, }; const httpTarget: ServerEntry = { id: "remote", name: "remote", config: { type: "streamable-http", url: "https://x.test/mcp" }, connection: { status: "disconnected" }, }; const stdioNoTypeNoArgs: ServerEntry = { id: "bare", name: "bare", // No `type` field and no `args` — exercises the `type ?? "stdio"` fallback // and the `args ?? []` fallback in summarize(). config: { command: "run-it" }, connection: { status: "disconnected" }, }; describe("ServerRemoveConfirmModal", () => { it("does not render when opened is false", () => { renderWithMantine( , ); expect(screen.queryByText(/Remove server\?/i)).not.toBeInTheDocument(); }); it("shows the target id and stdio command summary", () => { renderWithMantine( , ); expect(screen.getByText("alpha")).toBeInTheDocument(); expect( screen.getByText(/npx -y @modelcontextprotocol\/server-everything/), ).toBeInTheDocument(); }); it("shows the url for http transports", () => { renderWithMantine( , ); expect( screen.getByText(/streamable-http · https:\/\/x\.test\/mcp/), ).toBeInTheDocument(); }); it("calls 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).toHaveBeenCalledOnce(); }); it("calls onConfirm when Remove is clicked", async () => { const user = userEvent.setup(); const onConfirm = vi.fn().mockResolvedValue(undefined); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: /^Remove$/ })); await waitFor(() => expect(onConfirm).toHaveBeenCalledOnce()); }); it("surfaces an error message and stays open when onConfirm rejects", async () => { const user = userEvent.setup(); const onConfirm = vi.fn().mockRejectedValue(new Error("disk full")); const onCancel = vi.fn(); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: /^Remove$/ })); await waitFor(() => { expect(screen.getByRole("alert")).toHaveTextContent(/disk full/); }); expect(onCancel).not.toHaveBeenCalled(); }); it("defaults the transport label to stdio and omits args when neither is set", () => { renderWithMantine( , ); // `type ?? "stdio"` falls back to "stdio"; summarize joins just the command // since `args` is undefined (`args ?? []`). expect(screen.getByText(/stdio · run-it/)).toBeInTheDocument(); }); it("stringifies a non-Error rejection value in the alert", async () => { const user = userEvent.setup(); // Rejecting with a plain string takes the `String(err)` branch. const onConfirm = vi.fn().mockRejectedValue("plain string failure"); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: /^Remove$/ })); await waitFor(() => { expect(screen.getByRole("alert")).toHaveTextContent( /plain string failure/, ); }); }); it("is a no-op when Remove is clicked with no target", async () => { const user = userEvent.setup(); const onConfirm = vi.fn(); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: /^Remove$/ })); expect(onConfirm).not.toHaveBeenCalled(); }); });