154 lines
4.8 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|