Files
stack/apps/web/src/components/mission-control/MissionControlPanel.test.tsx
Jason Woltje 7147dc3503
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
test(web): MS23-P2-009 Mission Control frontend tests
2026-03-07 15:27:36 -06:00

154 lines
4.8 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import type { ButtonHTMLAttributes, ReactNode } from "react";
import { MAX_PANEL_COUNT, MissionControlPanel, type PanelConfig } from "./MissionControlPanel";
interface MockButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
}
interface MockOrchestratorPanelProps {
sessionId?: string;
onClose?: () => void;
closeDisabled?: boolean;
onExpand?: () => void;
expanded?: boolean;
}
const mockOrchestratorPanel = vi.fn<(props: MockOrchestratorPanelProps) => React.JSX.Element>();
vi.mock("@/components/mission-control/OrchestratorPanel", () => ({
OrchestratorPanel: (props: MockOrchestratorPanelProps): React.JSX.Element =>
mockOrchestratorPanel(props),
}));
vi.mock("@/components/ui/button", () => ({
Button: ({ children, ...props }: MockButtonProps): React.JSX.Element => (
<button {...props}>{children}</button>
),
}));
function buildPanels(count: number): PanelConfig[] {
return Array.from({ length: count }, (_, index) => ({
sessionId: `session-${String(index + 1)}`,
}));
}
describe("MissionControlPanel", (): void => {
beforeEach((): void => {
vi.clearAllMocks();
mockOrchestratorPanel.mockImplementation(
({ sessionId, closeDisabled, expanded }: MockOrchestratorPanelProps): React.JSX.Element => (
<div
data-testid="orchestrator-panel"
data-session-id={sessionId ?? ""}
data-close-disabled={String(closeDisabled ?? false)}
data-expanded={String(expanded ?? false)}
/>
)
);
});
it("renders the panel grid and default heading", (): void => {
render(
<MissionControlPanel
panels={[{}]}
onAddPanel={vi.fn<() => void>()}
onRemovePanel={vi.fn<(index: number) => void>()}
onExpandPanel={vi.fn<(index: number) => void>()}
/>
);
expect(screen.getByRole("heading", { name: "Panels" })).toBeInTheDocument();
expect(screen.getAllByTestId("orchestrator-panel")).toHaveLength(1);
});
it("calls onAddPanel when the add button is clicked", (): void => {
const onAddPanel = vi.fn<() => void>();
render(
<MissionControlPanel
panels={[{}]}
onAddPanel={onAddPanel}
onRemovePanel={vi.fn<(index: number) => void>()}
onExpandPanel={vi.fn<(index: number) => void>()}
/>
);
fireEvent.click(screen.getByRole("button", { name: "Add panel" }));
expect(onAddPanel).toHaveBeenCalledTimes(1);
});
it("disables add panel at the configured maximum", (): void => {
render(
<MissionControlPanel
panels={buildPanels(MAX_PANEL_COUNT)}
onAddPanel={vi.fn<() => void>()}
onRemovePanel={vi.fn<(index: number) => void>()}
onExpandPanel={vi.fn<(index: number) => void>()}
/>
);
const addButton = screen.getByRole("button", { name: "Add panel" });
expect(addButton).toBeDisabled();
expect(addButton).toHaveAttribute("title", "Maximum of 6 panels");
});
it("passes closeDisabled=false when more than one panel exists", (): void => {
render(
<MissionControlPanel
panels={buildPanels(2)}
onAddPanel={vi.fn<() => void>()}
onRemovePanel={vi.fn<(index: number) => void>()}
onExpandPanel={vi.fn<(index: number) => void>()}
/>
);
const renderedPanels = screen.getAllByTestId("orchestrator-panel");
expect(renderedPanels).toHaveLength(2);
for (const panel of renderedPanels) {
expect(panel).toHaveAttribute("data-close-disabled", "false");
}
});
it("renders only the expanded panel in focused mode", (): void => {
render(
<MissionControlPanel
panels={[{ sessionId: "session-1" }, { sessionId: "session-2", expanded: true }]}
onAddPanel={vi.fn<() => void>()}
onRemovePanel={vi.fn<(index: number) => void>()}
onExpandPanel={vi.fn<(index: number) => void>()}
/>
);
const renderedPanels = screen.getAllByTestId("orchestrator-panel");
expect(renderedPanels).toHaveLength(1);
expect(renderedPanels[0]).toHaveAttribute("data-session-id", "session-2");
expect(renderedPanels[0]).toHaveAttribute("data-expanded", "true");
});
it("handles Escape key by toggling expanded panel", async (): Promise<void> => {
const onExpandPanel = vi.fn<(index: number) => void>();
render(
<MissionControlPanel
panels={[{ sessionId: "session-1", expanded: true }, { sessionId: "session-2" }]}
onAddPanel={vi.fn<() => void>()}
onRemovePanel={vi.fn<(index: number) => void>()}
onExpandPanel={onExpandPanel}
/>
);
fireEvent.keyDown(window, { key: "Escape" });
await waitFor((): void => {
expect(onExpandPanel).toHaveBeenCalledWith(0);
});
});
});