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();
});
});