import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import * as MosaicUi from "@mosaic/ui"; import type { ButtonHTMLAttributes, ReactNode } from "react"; interface MockButtonProps extends ButtonHTMLAttributes { children: ReactNode; } const mockApiPost = vi.fn<(endpoint: string, body?: unknown) => Promise<{ message?: string }>>(); const mockShowToast = vi.fn<(message: string, variant?: string) => void>(); const useToastSpy = vi.spyOn(MosaicUi, "useToast"); vi.mock("@/lib/api/client", () => ({ apiPost: (endpoint: string, body?: unknown): Promise<{ message?: string }> => mockApiPost(endpoint, body), })); vi.mock("@/components/ui/button", () => ({ Button: ({ children, ...props }: MockButtonProps): React.JSX.Element => ( ), })); import { BargeInInput } from "./BargeInInput"; describe("BargeInInput", (): void => { beforeEach((): void => { vi.clearAllMocks(); vi.stubGlobal("fetch", vi.fn()); mockApiPost.mockResolvedValue({ message: "ok" }); useToastSpy.mockReturnValue({ showToast: mockShowToast, removeToast: vi.fn(), } as ReturnType); }); afterEach((): void => { vi.unstubAllGlobals(); }); it("renders input controls and keeps send disabled for empty content", (): void => { render(); expect(screen.getByLabelText("Inject message")).toBeInTheDocument(); expect(screen.getByRole("checkbox", { name: "Pause before send" })).not.toBeChecked(); expect(screen.getByRole("button", { name: "Send" })).toBeDisabled(); }); it("sends a trimmed message and clears the textarea", async (): Promise => { const onSent = vi.fn<() => void>(); const user = userEvent.setup(); render(); const textarea = screen.getByLabelText("Inject message"); await user.type(textarea, " execute plan "); await user.click(screen.getByRole("button", { name: "Send" })); await waitFor((): void => { expect(mockApiPost).toHaveBeenCalledWith("/api/mission-control/sessions/session-1/inject", { content: "execute plan", }); }); expect(onSent).toHaveBeenCalledTimes(1); expect(textarea).toHaveValue(""); }); it("pauses and resumes the session around injection when checkbox is enabled", async (): Promise => { const user = userEvent.setup(); render(); await user.click(screen.getByRole("checkbox", { name: "Pause before send" })); await user.type(screen.getByLabelText("Inject message"), "hello world"); await user.click(screen.getByRole("button", { name: "Send" })); await waitFor((): void => { expect(mockApiPost).toHaveBeenCalledTimes(3); }); const calls = mockApiPost.mock.calls as [string, unknown?][]; expect(calls[0]).toEqual(["/api/mission-control/sessions/session-2/pause", undefined]); expect(calls[1]).toEqual([ "/api/mission-control/sessions/session-2/inject", { content: "hello world" }, ]); expect(calls[2]).toEqual(["/api/mission-control/sessions/session-2/resume", undefined]); }); it("submits with Enter and does not submit on Shift+Enter", async (): Promise => { const user = userEvent.setup(); render(); const textarea = screen.getByLabelText("Inject message"); await user.type(textarea, "first"); fireEvent.keyDown(textarea, { key: "Enter", code: "Enter", shiftKey: true }); expect(mockApiPost).not.toHaveBeenCalled(); fireEvent.keyDown(textarea, { key: "Enter", code: "Enter", shiftKey: false }); await waitFor((): void => { expect(mockApiPost).toHaveBeenCalledWith("/api/mission-control/sessions/session-3/inject", { content: "first", }); }); }); it("shows an inline error and toast when injection fails", async (): Promise => { const user = userEvent.setup(); mockApiPost.mockRejectedValueOnce(new Error("Injection failed")); render(); await user.type(screen.getByLabelText("Inject message"), "help"); await user.click(screen.getByRole("button", { name: "Send" })); await waitFor((): void => { expect(screen.getByRole("alert")).toHaveTextContent("Injection failed"); }); expect(mockShowToast).toHaveBeenCalledWith("Injection failed", "error"); }); it("reports resume failures after a successful send", async (): Promise => { const user = userEvent.setup(); mockApiPost .mockResolvedValueOnce({ message: "paused" }) .mockResolvedValueOnce({ message: "sent" }) .mockRejectedValueOnce(new Error("resume failed")); render(); await user.click(screen.getByRole("checkbox", { name: "Pause before send" })); await user.type(screen.getByLabelText("Inject message"), "deploy now"); await user.click(screen.getByRole("button", { name: "Send" })); await waitFor((): void => { expect(screen.getByRole("alert")).toHaveTextContent( "Message sent, but failed to resume session: resume failed" ); }); expect(mockShowToast).toHaveBeenCalledWith( "Message sent, but failed to resume session: resume failed", "error" ); }); });