import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import type { ElicitRequest } from "@modelcontextprotocol/client"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; import { InlineElicitationRequest } from "./InlineElicitationRequest"; const formRequest = { message: "Please provide your database connection details.", requestedSchema: { type: "object" as const, properties: { host: { type: "string" as const, title: "Host" }, port: { type: "string" as const, title: "Port" }, }, }, } satisfies ElicitRequest["params"]; const urlRequest: ElicitRequest["params"] = { mode: "url", message: "Please authenticate via the external URL.", url: "https://example.com/auth/callback?session=abc123", elicitationId: "elicit-abc-123", }; const baseProps = { queuePosition: "1 of 1", onChange: vi.fn(), onSubmit: vi.fn(), onCancel: vi.fn(), }; describe("InlineElicitationRequest", () => { it("renders the form badge, message, and queue position in form mode", () => { renderWithMantine( , ); expect(screen.getByText("elicitation/create (form)")).toBeInTheDocument(); expect(screen.getByText(formRequest.message)).toBeInTheDocument(); expect(screen.getByText("1 of 1")).toBeInTheDocument(); // Default origin is a legacy server request — no MRTR note. expect(screen.queryByText("input_required")).not.toBeInTheDocument(); }); it("shows the MRTR note for a modern input_required round", () => { renderWithMantine( , ); expect(screen.getByText("input_required")).toBeInTheDocument(); expect(screen.getByText(/sent back as a retry/i)).toBeInTheDocument(); }); it("renders schema fields and a Submit button in form mode", () => { renderWithMantine( , ); expect(screen.getByText("Host")).toBeInTheDocument(); expect(screen.getByText("Port")).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Submit" })).toBeInTheDocument(); }); it("invokes onSubmit when Submit is clicked", async () => { const user = userEvent.setup(); const onSubmit = vi.fn(); renderWithMantine( , ); await user.click(screen.getByRole("button", { name: "Submit" })); expect(onSubmit).toHaveBeenCalledTimes(1); }); 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("invokes onChange when a schema field changes", async () => { const user = userEvent.setup(); const onChange = vi.fn(); renderWithMantine( , ); await user.type(screen.getByLabelText("Host"), "x"); expect(onChange).toHaveBeenCalled(); }); it("renders the URL badge and url in url mode", () => { renderWithMantine( , ); expect(screen.getByText("elicitation/create (url)")).toBeInTheDocument(); expect(screen.getByText(urlRequest.url)).toBeInTheDocument(); }); it("does not render Submit button in url mode", () => { renderWithMantine( , ); expect( screen.queryByRole("button", { name: "Submit" }), ).not.toBeInTheDocument(); }); it("shows the waiting indicator when isWaiting is true", () => { renderWithMantine( , ); expect(screen.getByText("Waiting...")).toBeInTheDocument(); }); it("hides the waiting indicator when isWaiting is false", () => { renderWithMantine( , ); expect(screen.queryByText("Waiting...")).not.toBeInTheDocument(); }); it("uses an empty values object when values prop is undefined", () => { renderWithMantine( , ); expect(screen.getByText("Host")).toBeInTheDocument(); }); // #2020: this inline form had no submit gate at all — neither the missing // required answer that `ElicitationFormPanel` already checked, nor text a // field could not turn into a value (which never reaches `values`). describe("submit gating (#2020)", () => { const requiredRequest = { message: "Please provide your name.", requestedSchema: { type: "object" as const, properties: { name: { type: "string" as const, title: "Name" } }, required: ["name"], }, } satisfies ElicitRequest["params"]; const numberRequest = { message: "Please provide the port.", requestedSchema: { type: "object" as const, properties: { port: { type: "number" as const, title: "Port number" } }, }, } satisfies ElicitRequest["params"]; it("disables Submit while a required answer is missing", () => { renderWithMantine( , ); expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled(); }); it("enables Submit once the required answer is given", () => { renderWithMantine( , ); expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled(); }); it("disables Submit while a field holds text it cannot send", async () => { const user = userEvent.setup(); renderWithMantine( , ); const submit = screen.getByRole("button", { name: "Submit" }); expect(submit).toBeEnabled(); // Past MAX_SAFE_INTEGER the field reports no value rather than a rounded // one, so the answer would go out without the property. const input = screen.getByLabelText(/Port number/); await user.type(input, "90071992547409910"); expect(submit).toBeDisabled(); await user.clear(input); expect(submit).toBeEnabled(); }); }); });