// @vitest-environment jsdom
import type { ReactNode } from "react";
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { Agent } from "@paperclipai/shared";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ToastProvider } from "../context/ToastContext";
import { Agents } from "./Agents";
const mockAgentsApi = vi.hoisted(() => ({
list: vi.fn(),
org: vi.fn(),
}));
const mockHeartbeatsApi = vi.hoisted(() => ({
liveRunsForCompany: vi.fn(),
}));
const mockResourceMembershipsApi = vi.hoisted(() => ({
listMine: vi.fn(),
updateAgent: vi.fn(),
}));
const mockOpenNewAgent = vi.hoisted(() => vi.fn());
const mockSetBreadcrumbs = vi.hoisted(() => vi.fn());
vi.mock("@/lib/router", () => ({
Link: ({ children, to, ...props }: { children: ReactNode; to: string }) => (
{children}
),
useLocation: () => ({ pathname: "/agents/all", search: "", hash: "", state: null }),
useNavigate: () => vi.fn(),
}));
vi.mock("../context/CompanyContext", () => ({
useCompany: () => ({ selectedCompanyId: "company-1" }),
}));
vi.mock("../context/DialogContext", () => ({
useDialogActions: () => ({ openNewAgent: mockOpenNewAgent }),
}));
vi.mock("../context/BreadcrumbContext", () => ({
useBreadcrumbs: () => ({ setBreadcrumbs: mockSetBreadcrumbs }),
}));
vi.mock("../context/SidebarContext", () => ({
useSidebar: () => ({ isMobile: false }),
}));
vi.mock("../api/agents", () => ({
agentsApi: mockAgentsApi,
}));
vi.mock("../api/heartbeats", () => ({
heartbeatsApi: mockHeartbeatsApi,
}));
vi.mock("../api/resourceMemberships", () => ({
resourceMembershipsApi: mockResourceMembershipsApi,
}));
vi.mock("../adapters/adapter-display-registry", () => ({
getAdapterLabel: (type: string) => type,
}));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
async function act(callback: () => void | Promise) {
let result: void | Promise = undefined;
flushSync(() => {
result = callback();
});
await result;
}
function makeAgent(overrides: Partial): Agent {
return {
id: "agent-1",
companyId: "company-1",
name: "Alpha",
urlKey: "alpha",
role: "engineer",
title: null,
icon: null,
status: "active",
reportsTo: null,
capabilities: null,
adapterType: "codex_local",
adapterConfig: {},
runtimeConfig: {},
budgetMonthlyCents: 0,
spentMonthlyCents: 0,
pauseReason: null,
pausedAt: null,
permissions: { canCreateAgents: false },
lastHeartbeatAt: null,
metadata: null,
createdAt: new Date("2026-01-01T00:00:00Z"),
updatedAt: new Date("2026-01-01T00:00:00Z"),
...overrides,
};
}
async function flushReact() {
await act(async () => {
await Promise.resolve();
await new Promise((resolve) => window.setTimeout(resolve, 0));
});
}
describe("Agents", () => {
let container: HTMLDivElement;
let root: ReturnType | null;
let queryClient: QueryClient;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
root = null;
queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
mockAgentsApi.list.mockResolvedValue([
makeAgent({ adapterConfig: { model: "gpt-5.4" } }),
]);
mockAgentsApi.org.mockResolvedValue([
{
id: "agent-1",
name: "Alpha",
role: "engineer",
status: "active",
reports: [],
},
]);
mockHeartbeatsApi.liveRunsForCompany.mockResolvedValue([]);
mockResourceMembershipsApi.listMine.mockResolvedValue({
projectMemberships: {},
agentMemberships: {},
updatedAt: null,
});
mockResourceMembershipsApi.updateAgent.mockResolvedValue({
resourceType: "agent",
resourceId: "agent-1",
state: "left",
updatedAt: new Date("2026-01-02T00:00:00Z"),
});
});
afterEach(async () => {
const currentRoot = root;
if (currentRoot) {
await act(async () => {
currentRoot.unmount();
});
}
queryClient.clear();
container.remove();
document.body.innerHTML = "";
vi.clearAllMocks();
});
it("shows the configured model beside the adapter on the all agents page", async () => {
root = createRoot(container);
await act(async () => {
root!.render(
,
);
});
await flushReact();
await flushReact();
expect(container.textContent).toContain("codex_local");
expect(container.textContent).toContain("gpt-5.4");
});
});