- Add tests for useAgents and useProjectHealth hooks, including loading, success, and error flows - Add useFileBrowser and useFileEditor hook tests for file navigation, save behavior, and edge cases - Add component tests for AgentImportModal and AgentGenerationModal interaction paths - Add MissionInterviewModal tests covering question flow, submission, and state transitions - Stabilize dashboard test suite cleanup and mocking patterns to reduce flakiness
249 lines
8.1 KiB
TypeScript
249 lines
8.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { AgentGenerationModal } from "../AgentGenerationModal";
|
|
import * as api from "../../api";
|
|
import type { AgentGenerationSpec } from "../../api";
|
|
|
|
vi.mock("../../api", () => ({
|
|
startAgentGeneration: vi.fn(),
|
|
generateAgentSpec: vi.fn(),
|
|
cancelAgentGeneration: vi.fn(),
|
|
}));
|
|
|
|
const mockStartAgentGeneration = vi.mocked(api.startAgentGeneration);
|
|
const mockGenerateAgentSpec = vi.mocked(api.generateAgentSpec);
|
|
const mockCancelAgentGeneration = vi.mocked(api.cancelAgentGeneration);
|
|
|
|
const generatedSpec: AgentGenerationSpec = {
|
|
title: "Accessibility Reviewer",
|
|
icon: "♿",
|
|
role: "reviewer",
|
|
description: "Reviews React code for accessibility compliance",
|
|
systemPrompt: "You are an expert accessibility reviewer...",
|
|
thinkingLevel: "high",
|
|
maxTurns: 12,
|
|
};
|
|
|
|
describe("AgentGenerationModal", () => {
|
|
const onClose = vi.fn();
|
|
const onGenerated = vi.fn();
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockStartAgentGeneration.mockResolvedValue({
|
|
sessionId: "session-1",
|
|
roleDescription: "review accessibility",
|
|
});
|
|
mockGenerateAgentSpec.mockResolvedValue({
|
|
spec: generatedSpec,
|
|
});
|
|
mockCancelAgentGeneration.mockResolvedValue({ success: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
function renderModal(isOpen = true) {
|
|
return render(
|
|
<AgentGenerationModal
|
|
isOpen={isOpen}
|
|
onClose={onClose}
|
|
onGenerated={onGenerated}
|
|
/>,
|
|
);
|
|
}
|
|
|
|
async function startGeneration(description = "Build an accessibility-focused reviewer") {
|
|
const user = userEvent.setup();
|
|
await user.type(screen.getByLabelText("Role Description"), description);
|
|
await user.click(screen.getByRole("button", { name: "Generate" }));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("button", { name: "Use This" })).toBeInTheDocument();
|
|
});
|
|
}
|
|
|
|
it("returns null when isOpen=false", () => {
|
|
renderModal(false);
|
|
expect(screen.queryByText("Generate Agent")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("renders title and role description textarea when open", () => {
|
|
renderModal(true);
|
|
|
|
expect(screen.getByText("Generate Agent")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Role Description")).toBeInTheDocument();
|
|
});
|
|
|
|
it("focuses role description textarea on open", () => {
|
|
renderModal(true);
|
|
|
|
expect(screen.getByLabelText("Role Description")).toHaveFocus();
|
|
});
|
|
|
|
it("disables Generate when description has fewer than 3 chars and enables at 3+", async () => {
|
|
renderModal(true);
|
|
|
|
const user = userEvent.setup();
|
|
const generateButton = screen.getByRole("button", { name: "Generate" });
|
|
|
|
expect(generateButton).toBeDisabled();
|
|
|
|
await user.type(screen.getByLabelText("Role Description"), "ab");
|
|
expect(generateButton).toBeDisabled();
|
|
|
|
await user.type(screen.getByLabelText("Role Description"), "c");
|
|
expect(generateButton).toBeEnabled();
|
|
});
|
|
|
|
it("shows character counter for role description", async () => {
|
|
renderModal(true);
|
|
|
|
const user = userEvent.setup();
|
|
await user.type(screen.getByLabelText("Role Description"), "abcd");
|
|
|
|
expect(screen.getByText("4/1000")).toBeInTheDocument();
|
|
});
|
|
|
|
it("handleGenerate calls startAgentGeneration then generateAgentSpec and transitions to preview", async () => {
|
|
renderModal(true);
|
|
|
|
await startGeneration();
|
|
|
|
expect(mockStartAgentGeneration).toHaveBeenCalledWith(
|
|
"Build an accessibility-focused reviewer",
|
|
undefined,
|
|
);
|
|
expect(mockGenerateAgentSpec).toHaveBeenCalledWith("session-1", undefined);
|
|
});
|
|
|
|
it("supports Enter key shortcut to generate when valid", async () => {
|
|
renderModal(true);
|
|
|
|
const textarea = screen.getByLabelText("Role Description");
|
|
fireEvent.change(textarea, { target: { value: "Generate with enter" } });
|
|
fireEvent.keyDown(textarea, { key: "Enter" });
|
|
|
|
await waitFor(() => {
|
|
expect(mockStartAgentGeneration).toHaveBeenCalledWith("Generate with enter", undefined);
|
|
});
|
|
});
|
|
|
|
it("preview shows title/icon/role/description/thinking/maxTurns with system prompt collapsed", async () => {
|
|
renderModal(true);
|
|
|
|
await startGeneration();
|
|
|
|
expect(screen.getByText(/Accessibility Reviewer/)).toBeInTheDocument();
|
|
expect(screen.getByText("reviewer")).toBeInTheDocument();
|
|
expect(screen.getByText("Reviews React code for accessibility compliance")).toBeInTheDocument();
|
|
expect(screen.getByText("high")).toBeInTheDocument();
|
|
expect(screen.getByText("12")).toBeInTheDocument();
|
|
expect(screen.getByText("System Prompt")).toBeInTheDocument();
|
|
expect(screen.getByText("Expand")).toBeInTheDocument();
|
|
});
|
|
|
|
it("Use This calls onGenerated with spec and closes modal", async () => {
|
|
renderModal(true);
|
|
|
|
await startGeneration();
|
|
|
|
const user = userEvent.setup();
|
|
await user.click(screen.getByRole("button", { name: "Use This" }));
|
|
|
|
expect(onGenerated).toHaveBeenCalledWith(generatedSpec);
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("Regenerate cancels existing session and re-runs generation", async () => {
|
|
mockStartAgentGeneration
|
|
.mockResolvedValueOnce({ sessionId: "session-1", roleDescription: "first" })
|
|
.mockResolvedValueOnce({ sessionId: "session-2", roleDescription: "first" });
|
|
mockGenerateAgentSpec
|
|
.mockResolvedValueOnce({ spec: generatedSpec })
|
|
.mockResolvedValueOnce({ spec: { ...generatedSpec, title: "Second Spec" } });
|
|
|
|
renderModal(true);
|
|
|
|
await startGeneration();
|
|
|
|
const user = userEvent.setup();
|
|
await user.click(screen.getByRole("button", { name: "Regenerate" }));
|
|
|
|
await waitFor(() => {
|
|
expect(mockCancelAgentGeneration).toHaveBeenCalledWith("session-1", undefined);
|
|
expect(screen.getByText(/Second Spec/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("Cancel button calls onClose and cancels active session", async () => {
|
|
renderModal(true);
|
|
|
|
await startGeneration();
|
|
|
|
const user = userEvent.setup();
|
|
await user.click(screen.getByRole("button", { name: "Cancel" }));
|
|
|
|
expect(mockCancelAgentGeneration).toHaveBeenCalledWith("session-1", undefined);
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("Escape key triggers cancel", async () => {
|
|
renderModal(true);
|
|
|
|
await startGeneration();
|
|
|
|
fireEvent.keyDown(document, { key: "Escape" });
|
|
|
|
await waitFor(() => {
|
|
expect(mockCancelAgentGeneration).toHaveBeenCalledWith("session-1", undefined);
|
|
expect(onClose).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it("shows error and returns to input when startAgentGeneration fails", async () => {
|
|
mockStartAgentGeneration.mockRejectedValueOnce(new Error("start failed"));
|
|
|
|
renderModal(true);
|
|
|
|
const user = userEvent.setup();
|
|
await user.type(screen.getByLabelText("Role Description"), "some role");
|
|
await user.click(screen.getByRole("button", { name: "Generate" }));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("start failed")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Role Description")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("shows friendly message for 429/rate-limit errors", async () => {
|
|
mockStartAgentGeneration.mockRejectedValueOnce(new Error("429 Too Many Requests"));
|
|
|
|
renderModal(true);
|
|
|
|
const user = userEvent.setup();
|
|
await user.type(screen.getByLabelText("Role Description"), "rate limit role");
|
|
await user.click(screen.getByRole("button", { name: "Generate" }));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Too many requests. Please wait a moment and try again.")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("cancels active session when modal closes via prop change", async () => {
|
|
const { rerender } = render(
|
|
<AgentGenerationModal isOpen={true} onClose={onClose} onGenerated={onGenerated} />,
|
|
);
|
|
|
|
await startGeneration();
|
|
|
|
rerender(<AgentGenerationModal isOpen={false} onClose={onClose} onGenerated={onGenerated} />);
|
|
|
|
await waitFor(() => {
|
|
expect(mockCancelAgentGeneration).toHaveBeenCalledWith("session-1", undefined);
|
|
});
|
|
});
|
|
});
|