import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; import { setAceText } from "../../../test/aceEditor"; import { ExperimentalFeaturesPanel } from "./ExperimentalFeaturesPanel"; import type { RequestHistoryItem } from "./ExperimentalFeaturesPanel"; const baseProps = { serverExperimental: undefined, clientExperimental: undefined, requestDraft: '{"jsonrpc":"2.0","id":1,"method":"experimental/x","params":{}}', customHeaders: [], requestHistory: [], onToggleClientCapability: vi.fn(), onRequestChange: vi.fn(), onSendRequest: vi.fn(), onAddHeader: vi.fn(), onRemoveHeader: vi.fn(), onHeaderChange: vi.fn(), onCopyResponse: vi.fn(), onTestCapability: vi.fn(), }; describe("ExperimentalFeaturesPanel", () => { it("renders the warning alert and section titles", () => { renderWithMantine(); expect( screen.getByText( "These features are non-standard and may change or be removed.", ), ).toBeInTheDocument(); expect( screen.getByText("Server Experimental Capabilities:"), ).toBeInTheDocument(); expect( screen.getByText("Client Experimental Capabilities:"), ).toBeInTheDocument(); expect(screen.getByText("Advanced JSON-RPC Tester")).toBeInTheDocument(); }); it("shows the empty state when no server experimental capabilities are present", () => { renderWithMantine(); expect( screen.getByText("No experimental capabilities"), ).toBeInTheDocument(); }); it("renders server capability cards with description and methods", () => { renderWithMantine( , ); expect(screen.getByText("experimental/streaming")).toBeInTheDocument(); expect( screen.getByText("Supports streaming responses"), ).toBeInTheDocument(); expect( screen.getByText( "Methods: experimental/stream.start, experimental/stream.cancel", ), ).toBeInTheDocument(); }); it("renders the capability card without description or methods when not provided", () => { renderWithMantine( , ); expect(screen.getByText("experimental/bare")).toBeInTheDocument(); expect(screen.queryByText(/^Methods:/)).not.toBeInTheDocument(); }); it("invokes onTestCapability when Test is clicked on a capability card", async () => { const user = userEvent.setup(); const onTestCapability = vi.fn(); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: /Test/ })); expect(onTestCapability).toHaveBeenCalledWith("experimental/echo"); }); it("renders the known client capability toggles with their friendly labels", () => { renderWithMantine(); expect( screen.getByRole("checkbox", { name: "Custom sampling" }), ).toBeInTheDocument(); expect( screen.getByRole("checkbox", { name: "Batch requests" }), ).toBeInTheDocument(); }); it("marks a known client capability checkbox as checked when enabled", () => { renderWithMantine( , ); const sampling = screen.getByRole("checkbox", { name: "Custom sampling", }) as HTMLInputElement; const batch = screen.getByRole("checkbox", { name: "Batch requests", }) as HTMLInputElement; expect(sampling.checked).toBe(false); expect(batch.checked).toBe(true); }); it("renders unknown client capabilities by their key", () => { renderWithMantine( , ); expect( screen.getByRole("checkbox", { name: "experimental/futureFeatureXyz" }), ).toBeInTheDocument(); }); it("invokes onToggleClientCapability when a client checkbox is clicked", async () => { const user = userEvent.setup(); const onToggleClientCapability = vi.fn(); renderWithMantine( , ); await user.click(screen.getByRole("checkbox", { name: "Custom sampling" })); expect(onToggleClientCapability).toHaveBeenCalledWith( "experimental/customSampling", true, ); }); it("invokes onAddHeader when + Add Header is clicked", async () => { const user = userEvent.setup(); const onAddHeader = vi.fn(); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: "+ Add Header" })); expect(onAddHeader).toHaveBeenCalledTimes(1); }); it("renders custom header rows and invokes onHeaderChange when key changes", async () => { const user = userEvent.setup(); const onHeaderChange = vi.fn(); renderWithMantine( , ); const keyInput = screen.getByDisplayValue("X-Auth"); await user.type(keyInput, "Z"); expect(onHeaderChange).toHaveBeenCalled(); const lastCall = onHeaderChange.mock.calls[onHeaderChange.mock.calls.length - 1]; expect(lastCall[0]).toBe(0); expect(lastCall[2]).toBe("Bearer 1"); }); it("invokes onHeaderChange when value changes for a header", async () => { const user = userEvent.setup(); const onHeaderChange = vi.fn(); renderWithMantine( , ); const valueInput = screen.getByDisplayValue("abc"); await user.type(valueInput, "Z"); expect(onHeaderChange).toHaveBeenCalled(); const lastCall = onHeaderChange.mock.calls[onHeaderChange.mock.calls.length - 1]; expect(lastCall[0]).toBe(0); expect(lastCall[1]).toBe("X-Auth"); }); it("clears a header key via its clear button, invoking onHeaderChange with empty key", async () => { const user = userEvent.setup(); const onHeaderChange = vi.fn(); renderWithMantine( , ); // Both the key and value inputs render a Clear button when populated. // The first one belongs to the key input (line 252). const clearButtons = screen.getAllByRole("button", { name: "Clear" }); await user.click(clearButtons[0]); expect(onHeaderChange).toHaveBeenCalledWith(0, "", "Bearer 1"); }); it("clears a header value via its clear button, invoking onHeaderChange with empty value", async () => { const user = userEvent.setup(); const onHeaderChange = vi.fn(); renderWithMantine( , ); // The second Clear button belongs to the value input (line 267). const clearButtons = screen.getAllByRole("button", { name: "Clear" }); await user.click(clearButtons[1]); expect(onHeaderChange).toHaveBeenCalledWith(0, "X-Auth", ""); }); it("clears the request draft via its clear button, invoking onRequestChange with empty string", async () => { const user = userEvent.setup(); const onRequestChange = vi.fn(); renderWithMantine( , ); // The request textarea shows a Clear button when non-empty (line 292). const clearButtons = screen.getAllByRole("button", { name: "Clear" }); await user.click(clearButtons[clearButtons.length - 1]); expect(onRequestChange).toHaveBeenCalledWith(""); }); it("invokes onRemoveHeader with the header index when the remove icon is clicked", async () => { const user = userEvent.setup(); const onRemoveHeader = vi.fn(); renderWithMantine( , ); const removeButtons = screen.getAllByRole("button", { name: "✕" }); await user.click(removeButtons[1]); expect(onRemoveHeader).toHaveBeenCalledWith(1); }); it("invokes onRequestChange when the request editor changes", async () => { const onRequestChange = vi.fn(); renderWithMantine( , ); await setAceText("x"); expect(onRequestChange).toHaveBeenCalledWith("x"); }); it("invokes onSendRequest when Send Request is clicked", async () => { const user = userEvent.setup(); const onSendRequest = vi.fn(); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: "Send Request" })); expect(onSendRequest).toHaveBeenCalledTimes(1); }); it("renders a successful response without an Error badge", () => { renderWithMantine( , ); expect(screen.getByText("Response")).toBeInTheDocument(); expect(screen.queryByText("Error")).not.toBeInTheDocument(); }); it("renders an error response with the Error badge", () => { renderWithMantine( , ); expect(screen.getByText("Response")).toBeInTheDocument(); expect(screen.getByText("Error")).toBeInTheDocument(); }); it("invokes onCopyResponse when Copy is clicked", async () => { const user = userEvent.setup(); const onCopyResponse = vi.fn(); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: "Copy" })); expect(onCopyResponse).toHaveBeenCalledTimes(1); }); it("renders the request history table when entries are present", () => { const items: RequestHistoryItem[] = [ { timestamp: new Date("2026-03-17T10:30:15Z"), method: "experimental/metrics.get", status: "success", durationMs: 42, }, { timestamp: new Date("2026-03-17T10:29:50Z"), method: "experimental/echo", status: "error", durationMs: 120, }, ]; renderWithMantine( , ); expect(screen.getByText("Request History:")).toBeInTheDocument(); expect(screen.getByText("experimental/metrics.get")).toBeInTheDocument(); expect(screen.getByText("experimental/echo")).toBeInTheDocument(); expect(screen.getByText("42ms")).toBeInTheDocument(); expect(screen.getByText("120ms")).toBeInTheDocument(); }); it("does not render the history section when requestHistory is empty", () => { renderWithMantine(); expect(screen.queryByText("Request History:")).not.toBeInTheDocument(); }); });