import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react"; interface MockButtonProps extends ButtonHTMLAttributes { children: ReactNode; } interface MockContainerProps extends HTMLAttributes { children: ReactNode; } interface MockSession { id: string; providerId: string; providerType: string; status: "active" | "paused" | "killed"; createdAt: string; updatedAt: string; metadata?: Record; } const mockApiGet = vi.fn<(endpoint: string) => Promise>(); const mockApiPost = vi.fn<(endpoint: string, body?: unknown) => Promise<{ message: string }>>(); const mockKillAllDialog = vi.fn<() => React.JSX.Element>(); vi.mock("@/lib/api/client", () => ({ apiGet: (endpoint: string): Promise => mockApiGet(endpoint), apiPost: (endpoint: string, body?: unknown): Promise<{ message: string }> => mockApiPost(endpoint, body), })); vi.mock("@/components/mission-control/KillAllDialog", () => ({ KillAllDialog: (): React.JSX.Element => mockKillAllDialog(), })); vi.mock("@/components/ui/button", () => ({ Button: ({ children, ...props }: MockButtonProps): React.JSX.Element => ( ), })); vi.mock("@/components/ui/badge", () => ({ Badge: ({ children, ...props }: MockContainerProps): React.JSX.Element => ( {children} ), })); vi.mock("@/components/ui/card", () => ({ Card: ({ children, ...props }: MockContainerProps): React.JSX.Element => (
{children}
), CardHeader: ({ children, ...props }: MockContainerProps): React.JSX.Element => (
{children}
), CardContent: ({ children, ...props }: MockContainerProps): React.JSX.Element => (
{children}
), CardTitle: ({ children, ...props }: MockContainerProps): React.JSX.Element => (

{children}

), })); import { GlobalAgentRoster } from "./GlobalAgentRoster"; function renderWithQueryClient(ui: React.JSX.Element): ReturnType { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); return render({ui}); } function makeSession(overrides: Partial): MockSession { return { id: "session-12345678", providerId: "internal", providerType: "internal", status: "active", createdAt: "2026-03-07T10:00:00.000Z", updatedAt: "2026-03-07T10:01:00.000Z", ...overrides, }; } function getRowForSessionLabel(label: string): HTMLElement { const sessionLabel = screen.getByText(label); const row = sessionLabel.closest('[role="button"]'); if (!(row instanceof HTMLElement)) { throw new Error(`Expected a row element for session label ${label}`); } return row; } describe("GlobalAgentRoster", (): void => { beforeEach((): void => { vi.clearAllMocks(); vi.stubGlobal("fetch", vi.fn()); mockApiGet.mockResolvedValue([]); mockApiPost.mockResolvedValue({ message: "ok" }); mockKillAllDialog.mockImplementation( (): React.JSX.Element =>
kill-all-dialog
); }); afterEach((): void => { vi.unstubAllGlobals(); }); it("renders the empty state when no active sessions are returned", async (): Promise => { renderWithQueryClient(); await waitFor((): void => { expect(screen.getByText("No active agents")).toBeInTheDocument(); }); expect(screen.queryByTestId("kill-all-dialog")).not.toBeInTheDocument(); }); it("groups sessions by provider and shows kill-all control when sessions exist", async (): Promise => { mockApiGet.mockResolvedValue([ makeSession({ id: "alpha123456", providerId: "internal", providerType: "internal" }), makeSession({ id: "bravo123456", providerId: "codex", providerType: "openai" }), ]); renderWithQueryClient(); await waitFor((): void => { expect(screen.getByText("internal")).toBeInTheDocument(); expect(screen.getByText("codex (openai)")).toBeInTheDocument(); }); expect(screen.getByText("alpha123")).toBeInTheDocument(); expect(screen.getByText("bravo123")).toBeInTheDocument(); expect(screen.getByTestId("kill-all-dialog")).toBeInTheDocument(); }); it("calls onSelectSession on row click and keyboard activation", async (): Promise => { const onSelectSession = vi.fn<(sessionId: string) => void>(); mockApiGet.mockResolvedValue([makeSession({ id: "target123456" })]); renderWithQueryClient(); await waitFor((): void => { expect(screen.getByText("target12")).toBeInTheDocument(); }); const row = getRowForSessionLabel("target12"); fireEvent.click(row); fireEvent.keyDown(row, { key: "Enter" }); expect(onSelectSession).toHaveBeenCalledTimes(2); expect(onSelectSession).toHaveBeenNthCalledWith(1, "target123456"); expect(onSelectSession).toHaveBeenNthCalledWith(2, "target123456"); }); it("kills a session from the roster", async (): Promise => { mockApiGet.mockResolvedValue([makeSession({ id: "killme123456" })]); renderWithQueryClient(); await waitFor((): void => { expect(screen.getByRole("button", { name: "Kill session killme12" })).toBeInTheDocument(); }); fireEvent.click(screen.getByRole("button", { name: "Kill session killme12" })); await waitFor((): void => { expect(mockApiPost).toHaveBeenCalledWith("/api/mission-control/sessions/killme123456/kill", { force: false, }); }); }); it("collapses and reopens provider groups", async (): Promise => { mockApiGet.mockResolvedValue([makeSession({ id: "grouped12345" })]); renderWithQueryClient(); await waitFor((): void => { expect(screen.getByText("grouped1")).toBeInTheDocument(); }); fireEvent.click(screen.getByRole("button", { name: /internal/i })); expect(screen.queryByText("grouped1")).not.toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: /internal/i })); expect(screen.getByText("grouped1")).toBeInTheDocument(); }); });