test(web): MS23-P2-009 Mission Control frontend tests
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user