Files
stack/apps/web/src/components/mission-control/KillAllDialog.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

171 lines
5.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import type {
ButtonHTMLAttributes,
InputHTMLAttributes,
LabelHTMLAttributes,
ReactNode,
} from "react";
interface MockButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
}
interface MockInputProps extends InputHTMLAttributes<HTMLInputElement> {
children?: ReactNode;
}
interface MockLabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
children: ReactNode;
}
interface MockSession {
id: string;
providerId: string;
providerType: string;
status: "active" | "paused";
createdAt: Date;
updatedAt: Date;
}
const mockApiPost = vi.fn<(endpoint: string, body?: unknown) => Promise<{ message: string }>>();
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 => (
<button {...props}>{children}</button>
),
}));
vi.mock("@/components/ui/input", () => ({
Input: ({ ...props }: MockInputProps): React.JSX.Element => <input {...props} />,
}));
vi.mock("@/components/ui/label", () => ({
Label: ({ children, ...props }: MockLabelProps): React.JSX.Element => (
<label {...props}>{children}</label>
),
}));
import { KillAllDialog } from "./KillAllDialog";
function makeSession(overrides: Partial<MockSession>): MockSession {
return {
id: "session-1",
providerId: "internal",
providerType: "internal",
status: "active",
createdAt: new Date("2026-03-07T10:00:00.000Z"),
updatedAt: new Date("2026-03-07T10:01:00.000Z"),
...overrides,
};
}
describe("KillAllDialog", (): void => {
beforeEach((): void => {
vi.clearAllMocks();
mockApiPost.mockResolvedValue({ message: "killed" });
});
it("renders trigger button and requires exact confirmation text", async (): Promise<void> => {
const user = userEvent.setup();
render(<KillAllDialog sessions={[makeSession({})]} />);
await user.click(screen.getByRole("button", { name: "Kill All" }));
const confirmInput = screen.getByLabelText("Type KILL ALL to confirm");
const confirmButton = screen.getByRole("button", { name: "Kill All Agents" });
expect(confirmButton).toBeDisabled();
await user.type(confirmInput, "kill all");
expect(confirmButton).toBeDisabled();
await user.clear(confirmInput);
await user.type(confirmInput, "KILL ALL");
expect(confirmButton).toBeEnabled();
});
it("kills only internal sessions by default and invokes completion callback", async (): Promise<void> => {
const onComplete = vi.fn<() => void>();
const user = userEvent.setup();
render(
<KillAllDialog
sessions={[
makeSession({ id: "internal-1", providerType: "internal" }),
makeSession({ id: "external-1", providerType: "external" }),
]}
onComplete={onComplete}
/>
);
await user.click(screen.getByRole("button", { name: "Kill All" }));
await user.type(screen.getByLabelText("Type KILL ALL to confirm"), "KILL ALL");
await user.click(screen.getByRole("button", { name: "Kill All Agents" }));
await waitFor((): void => {
expect(mockApiPost).toHaveBeenCalledWith("/api/mission-control/sessions/internal-1/kill", {
force: true,
});
});
expect(mockApiPost).not.toHaveBeenCalledWith("/api/mission-control/sessions/external-1/kill", {
force: true,
});
expect(onComplete).toHaveBeenCalledTimes(1);
});
it("kills all providers when all scope is selected", async (): Promise<void> => {
const user = userEvent.setup();
render(
<KillAllDialog
sessions={[
makeSession({ id: "internal-2", providerType: "internal" }),
makeSession({ id: "external-2", providerType: "external" }),
]}
/>
);
await user.click(screen.getByRole("button", { name: "Kill All" }));
await user.click(screen.getByRole("radio", { name: /All providers \(2\)/ }));
await user.type(screen.getByLabelText("Type KILL ALL to confirm"), "KILL ALL");
await user.click(screen.getByRole("button", { name: "Kill All Agents" }));
await waitFor((): void => {
expect(mockApiPost).toHaveBeenCalledWith("/api/mission-control/sessions/internal-2/kill", {
force: true,
});
expect(mockApiPost).toHaveBeenCalledWith("/api/mission-control/sessions/external-2/kill", {
force: true,
});
});
});
it("shows empty-scope warning when internal sessions are unavailable", async (): Promise<void> => {
const user = userEvent.setup();
render(
<KillAllDialog
sessions={[
makeSession({ id: "external-only", providerId: "ext", providerType: "external" }),
]}
/>
);
await user.click(screen.getByRole("button", { name: "Kill All" }));
await user.type(screen.getByLabelText("Type KILL ALL to confirm"), "KILL ALL");
expect(screen.getByText("No sessions in the selected scope.")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Kill All Agents" })).toBeDisabled();
});
});