agent-orchestrator/frontend/src/renderer/components/MigrationPopup.test.tsx

90 lines
3.8 KiB
TypeScript

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { MigrationPopup } from "./MigrationPopup";
const { getMock, postMock, getMigration, setMigration } = vi.hoisted(() => ({
getMock: vi.fn(),
postMock: vi.fn(),
getMigration: vi.fn(),
setMigration: vi.fn(),
}));
vi.mock("../lib/api-client", () => ({
apiClient: { GET: getMock, POST: postMock },
apiErrorMessage: (e: unknown, fb = "Request failed") =>
e instanceof Error ? e.message : ((e as { message?: string })?.message ?? fb),
}));
vi.mock("../lib/bridge", () => ({ aoBridge: { appState: { getMigration, setMigration } } }));
function renderPopup() {
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(
<QueryClientProvider client={qc}>
<MigrationPopup />
</QueryClientProvider>,
);
return qc;
}
beforeEach(() => {
getMock.mockReset();
postMock.mockReset();
getMigration.mockReset();
setMigration.mockReset();
getMigration.mockResolvedValue({ status: "pending" });
getMock.mockResolvedValue({ data: { available: true, legacyRoot: "/home/u/.agent-orchestrator" }, error: undefined });
postMock.mockResolvedValue({ data: { report: { projectsImported: 2, projectsSkipped: 1 } }, error: undefined });
setMigration.mockResolvedValue(undefined);
});
describe("MigrationPopup", () => {
it("shows when a legacy install is available and the marker is pending", async () => {
renderPopup();
expect(await screen.findByText(/Import projects from your earlier AO/i)).toBeInTheDocument();
expect(screen.getByText("/home/u/.agent-orchestrator")).toBeInTheDocument();
});
it("renders nothing when the marker is declined", async () => {
getMigration.mockResolvedValue({ status: "declined" });
renderPopup();
await waitFor(() => expect(getMigration).toHaveBeenCalled());
expect(screen.queryByText(/Import projects from your earlier AO/i)).not.toBeInTheDocument();
expect(getMock).not.toHaveBeenCalled();
});
it("Proceed imports, marks completed, and retires", async () => {
renderPopup();
await screen.findByText(/Import projects from your earlier AO/i);
await userEvent.click(screen.getByRole("button", { name: "Proceed" }));
await waitFor(() => expect(postMock).toHaveBeenCalledWith("/api/v1/import"));
expect(setMigration).toHaveBeenCalledWith(expect.objectContaining({ status: "completed" }));
await waitFor(() => expect(screen.queryByText(/Import projects from your earlier AO/i)).not.toBeInTheDocument());
});
it("Don't Migrate records declined", async () => {
renderPopup();
await screen.findByText(/Import projects from your earlier AO/i);
await userEvent.click(screen.getByRole("button", { name: "Don't Migrate" }));
expect(setMigration).toHaveBeenCalledWith(expect.objectContaining({ status: "declined" }));
});
it("Skip dismisses without writing the marker", async () => {
renderPopup();
await screen.findByText(/Import projects from your earlier AO/i);
await userEvent.click(screen.getByRole("button", { name: "Skip" }));
expect(setMigration).not.toHaveBeenCalled();
expect(screen.queryByText(/Import projects from your earlier AO/i)).not.toBeInTheDocument();
});
it("a failed import shows the lossless reassurance and marks failed", async () => {
postMock.mockResolvedValue({ data: undefined, error: { message: "disk full" } });
renderPopup();
await screen.findByText(/Import projects from your earlier AO/i);
await userEvent.click(screen.getByRole("button", { name: "Proceed" }));
expect(await screen.findByText(/nothing is ever deleted/i)).toBeInTheDocument();
expect(setMigration).toHaveBeenCalledWith(expect.objectContaining({ status: "failed", error: "disk full" }));
});
});