Files
fusion/packages/dashboard/app/components/__tests__/AgentImportModal.test.tsx
gsxdsm 914b93cc43 test(FN-1282): expand dashboard hook and modal coverage
- 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
2026-04-08 10:48:42 -07:00

289 lines
8.5 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 { AgentImportModal } from "../AgentImportModal";
interface MockResponse {
ok: boolean;
status?: number;
body: unknown;
}
function mockResponse({ ok, status = ok ? 200 : 400, body }: MockResponse): Promise<Response> {
return Promise.resolve({
ok,
status,
json: async () => body,
} as Response);
}
describe("AgentImportModal", () => {
const onClose = vi.fn();
const onImported = vi.fn();
const originalFetch = globalThis.fetch;
beforeEach(() => {
vi.clearAllMocks();
globalThis.fetch = vi.fn();
});
afterEach(() => {
vi.restoreAllMocks();
globalThis.fetch = originalFetch;
});
function renderModal(isOpen = true) {
return render(
<AgentImportModal
isOpen={isOpen}
onClose={onClose}
onImported={onImported}
/>,
);
}
async function goToPreview(manifest = "---\nname: Reviewer\nrole: reviewer\n---") {
vi.mocked(globalThis.fetch).mockResolvedValueOnce(
await mockResponse({
ok: true,
body: {
companyName: "Acme AI",
agents: [
{ name: "Reviewer", role: "reviewer", title: "Code Reviewer", skills: ["review"] },
{ name: "Planner", role: "triage", title: "Planner" },
],
created: ["Reviewer", "Planner"],
skipped: [],
errors: [],
dryRun: true,
},
}),
);
const user = userEvent.setup();
await user.type(screen.getByLabelText("Manifest content"), manifest);
await user.click(screen.getByRole("button", { name: "Preview" }));
await waitFor(() => {
expect(screen.getByText("Acme AI")).toBeInTheDocument();
expect(screen.getByText("2 agents found")).toBeInTheDocument();
});
}
it("returns null when isOpen=false", () => {
renderModal(false);
expect(screen.queryByText("Import Agents")).not.toBeInTheDocument();
});
it("renders title when open", () => {
renderModal(true);
expect(screen.getByText("Import Agents")).toBeInTheDocument();
});
it("shows file upload area and textarea in input step", () => {
renderModal(true);
expect(screen.getByRole("button", { name: "Choose File" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Select Directory" })).toBeInTheDocument();
expect(screen.getByLabelText("Manifest content")).toBeInTheDocument();
});
it("disables Preview when manifest is empty and enables after typing", async () => {
renderModal(true);
const user = userEvent.setup();
const preview = screen.getByRole("button", { name: "Preview" });
expect(preview).toBeDisabled();
await user.type(screen.getByLabelText("Manifest content"), "name: test-agent");
expect(preview).toBeEnabled();
});
it("handleParse posts dryRun import request and moves to preview step", async () => {
renderModal(true);
await goToPreview();
expect(globalThis.fetch).toHaveBeenCalledWith(
"/api/agents/import",
expect.objectContaining({
method: "POST",
headers: { "Content-Type": "application/json" },
}),
);
const body = JSON.parse(vi.mocked(globalThis.fetch).mock.calls[0][1]!.body as string);
expect(body).toMatchObject({
dryRun: true,
manifest: expect.stringContaining("name: Reviewer"),
});
});
it("preview shows company name, count, and agent list", async () => {
renderModal(true);
await goToPreview();
expect(screen.getByText("Acme AI")).toBeInTheDocument();
expect(screen.getByText("2 agents found")).toBeInTheDocument();
expect(screen.getByText("Reviewer")).toBeInTheDocument();
expect(screen.getByText("Planner")).toBeInTheDocument();
expect(screen.getByText(/reviewer/)).toBeInTheDocument();
expect(screen.getByText(/triage/)).toBeInTheDocument();
});
it("Back button returns to input step", async () => {
renderModal(true);
await goToPreview();
const user = userEvent.setup();
await user.click(screen.getByRole("button", { name: "Back" }));
expect(screen.getByLabelText("Manifest content")).toBeInTheDocument();
expect(screen.queryByText("2 agents found")).not.toBeInTheDocument();
});
it("handleImport posts live import request and transitions to result step", async () => {
renderModal(true);
await goToPreview();
vi.mocked(globalThis.fetch).mockResolvedValueOnce(
await mockResponse({
ok: true,
body: {
companyName: "Acme AI",
created: [{ id: "agent-1", name: "Reviewer" }],
skipped: ["Planner"],
errors: [{ name: "Writer", error: "Invalid role" }],
},
}),
);
const user = userEvent.setup();
await user.click(screen.getByRole("button", { name: /Import 2 Agents/i }));
await waitFor(() => {
expect(screen.getByText("Import Complete")).toBeInTheDocument();
});
const body = JSON.parse(vi.mocked(globalThis.fetch).mock.calls[1][1]!.body as string);
expect(body).toMatchObject({
manifest: expect.stringContaining("name: Reviewer"),
skipExisting: true,
});
expect(body).not.toHaveProperty("dryRun");
});
it("result step shows created/skipped/error counts and created names", async () => {
renderModal(true);
await goToPreview();
vi.mocked(globalThis.fetch).mockResolvedValueOnce(
await mockResponse({
ok: true,
body: {
companyName: "Acme AI",
created: [{ id: "agent-1", name: "Reviewer" }, { id: "agent-2", name: "Planner" }],
skipped: ["Writer"],
errors: [{ name: "Ops", error: "Bad schema" }],
},
}),
);
const user = userEvent.setup();
await user.click(screen.getByRole("button", { name: /Import 2 Agents/i }));
await waitFor(() => {
expect(screen.getByText(/2 created/)).toBeInTheDocument();
expect(screen.getByText(/1 skipped/)).toBeInTheDocument();
expect(screen.getByText(/1 error/)).toBeInTheDocument();
});
expect(screen.getByText("Reviewer")).toBeInTheDocument();
expect(screen.getByText("Planner")).toBeInTheDocument();
});
it("calls onImported after successful import", async () => {
renderModal(true);
await goToPreview();
vi.mocked(globalThis.fetch).mockResolvedValueOnce(
await mockResponse({
ok: true,
body: {
companyName: "Acme AI",
created: [{ id: "agent-1", name: "Reviewer" }],
skipped: [],
errors: [],
},
}),
);
const user = userEvent.setup();
await user.click(screen.getByRole("button", { name: /Import 2 Agents/i }));
await waitFor(() => {
expect(onImported).toHaveBeenCalledTimes(1);
});
});
it("shows parse API errors", async () => {
renderModal(true);
vi.mocked(globalThis.fetch).mockResolvedValueOnce(
await mockResponse({ ok: false, body: { error: "No agents found" } }),
);
const user = userEvent.setup();
await user.type(screen.getByLabelText("Manifest content"), "invalid manifest");
await user.click(screen.getByRole("button", { name: "Preview" }));
await waitFor(() => {
expect(screen.getByText("No agents found")).toBeInTheDocument();
});
});
it("shows import API errors", async () => {
renderModal(true);
await goToPreview();
vi.mocked(globalThis.fetch).mockResolvedValueOnce(
await mockResponse({ ok: false, body: { error: "Import failed" } }),
);
const user = userEvent.setup();
await user.click(screen.getByRole("button", { name: /Import 2 Agents/i }));
await waitFor(() => {
expect(screen.getByText("Import failed")).toBeInTheDocument();
});
});
it("Cancel/Close calls onClose and resets state", async () => {
const { rerender } = renderModal(true);
await goToPreview();
const user = userEvent.setup();
await user.click(screen.getByRole("button", { name: "Cancel" }));
expect(onClose).toHaveBeenCalledTimes(1);
rerender(<AgentImportModal isOpen={true} onClose={onClose} onImported={onImported} />);
expect(screen.getByLabelText("Manifest content")).toHaveValue("");
expect(screen.queryByText("2 agents found")).not.toBeInTheDocument();
});
it("clicking overlay triggers handleClose", () => {
const { container } = renderModal(true);
const overlay = container.querySelector(".agent-dialog-overlay");
expect(overlay).toBeTruthy();
fireEvent.click(overlay!);
expect(onClose).toHaveBeenCalledTimes(1);
});
});