import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { AgentsView } from "../AgentsView"; import * as apiModule from "../../api"; import type { Agent, AgentState, AgentCapability, OrgTreeNode } from "../../api"; import { scopedKey } from "../../utils/projectStorage"; // Mock the API module vi.mock("../../api", () => ({ fetchAgents: vi.fn(), fetchAgentStats: vi.fn(), createAgent: vi.fn(), updateAgent: vi.fn(), updateAgentState: vi.fn(), deleteAgent: vi.fn(), startAgentRun: vi.fn(), fetchOrgTree: vi.fn(), fetchModels: vi.fn().mockResolvedValue({ models: [] }), })); vi.mock("../AgentDetailView", () => ({ AgentDetailView: ({ agentId }: { agentId: string }) =>
Agent detail: {agentId}
, })); const mockFetchAgents = vi.mocked(apiModule.fetchAgents); const mockCreateAgent = vi.mocked(apiModule.createAgent); const mockUpdateAgentState = vi.mocked(apiModule.updateAgentState); const mockDeleteAgent = vi.mocked(apiModule.deleteAgent); const mockStartAgentRun = vi.mocked(apiModule.startAgentRun); const mockFetchOrgTree = vi.mocked((apiModule as any).fetchOrgTree); const mockFetchAgentStats = vi.mocked((apiModule as any).fetchAgentStats); describe("AgentsView", () => { const mockAddToast = vi.fn(); const projectId = "proj_123"; const mockAgents: Agent[] = [ { id: "agent-001", name: "Test Agent 1", role: "executor" as AgentCapability, state: "idle" as AgentState, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, { id: "agent-002", name: "Test Agent 2", role: "triage" as AgentCapability, state: "active" as AgentState, taskId: "FN-001", lastHeartbeatAt: new Date().toISOString(), createdAt: new Date(Date.now() - 86400000).toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, { id: "agent-003", name: "Test Agent 3", role: "custom" as AgentCapability, state: "paused" as AgentState, createdAt: new Date(Date.now() - 172800000).toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, { id: "agent-004", name: "Test Agent 4", role: "reviewer" as AgentCapability, state: "terminated" as AgentState, createdAt: new Date(Date.now() - 259200000).toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, ]; beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); mockFetchAgents.mockResolvedValue(mockAgents); mockFetchAgentStats.mockResolvedValue({ total: 4, byState: {}, byRole: {} }); mockCreateAgent.mockResolvedValue(mockAgents[0]); mockUpdateAgentState.mockResolvedValue({ ...mockAgents[0], state: "active" }); mockDeleteAgent.mockResolvedValue(undefined); mockStartAgentRun.mockResolvedValue({ id: "run-001", agentId: "agent-001", startedAt: new Date().toISOString(), endedAt: null, status: "active", }); mockFetchOrgTree.mockResolvedValue([]); }); describe("rendering", () => { it("renders the agents view header", async () => { render(); await waitFor(() => { expect(screen.getByText("Agents")).toBeTruthy(); }); }); it("renders agent list on mount", async () => { render(); await waitFor(() => { // Active agents may appear in both ActiveAgentsPanel and main list expect(screen.getAllByText("Test Agent 1").length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText("Test Agent 2").length).toBeGreaterThanOrEqual(1); }); }); it("fetches agents on mount", async () => { render(); await waitFor(() => { expect(mockFetchAgents).toHaveBeenCalled(); }); }); it("passes projectId to agent fetches", async () => { render(); await waitFor(() => { expect(mockFetchAgents).toHaveBeenCalledWith(undefined, projectId); }); }); it("renders empty state when no agents", async () => { mockFetchAgents.mockResolvedValue([]); render(); await waitFor(() => { expect(screen.getByText("No agents found")).toBeTruthy(); expect(screen.getByText("Create an agent to get started")).toBeTruthy(); }); }); it("displays agent states", async () => { render(); await waitFor(() => { expect(screen.getAllByText("idle").length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText("active").length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText("paused").length).toBeGreaterThanOrEqual(1); // Terminated agents are hidden in default "All States" view expect(screen.queryAllByText("terminated").length).toBe(0); }); }); it("shows terminated agents when explicitly filtered", async () => { render(); await waitFor(() => { expect(screen.getByText("All States")).toBeTruthy(); }); // Switch to terminated filter const filterSelect = screen.getByDisplayValue("All States"); fireEvent.change(filterSelect, { target: { value: "terminated" } }); await waitFor(() => { expect(screen.getAllByText("terminated").length).toBeGreaterThanOrEqual(1); }); }); it("displays agent task when working on one", async () => { render(); await waitFor(() => { expect(screen.getAllByText("FN-001").length).toBeGreaterThanOrEqual(1); }); }); it("shows refresh button", async () => { render(); const refreshBtn = screen.getByTitle("Refresh"); expect(refreshBtn).toBeTruthy(); }); }); describe("view toggle (list/board)", () => { it("can toggle between list and board view", async () => { render(); await waitFor(() => { expect(screen.getAllByText("Test Agent 1").length).toBeGreaterThanOrEqual(1); }); // Initially should show list view (default) expect(document.querySelector(".agent-list")).toBeTruthy(); // Switch to board view fireEvent.click(screen.getByTitle("Board view")); await waitFor(() => { expect(document.querySelector(".agent-board")).toBeTruthy(); }); // Switch back to list view fireEvent.click(screen.getByTitle("List view")); await waitFor(() => { expect(document.querySelector(".agent-list")).toBeTruthy(); }); }); it("board view shows compact cards", async () => { render(); await waitFor(() => { expect(screen.getByText("Agents")).toBeTruthy(); }); fireEvent.click(screen.getByTitle("Board view")); await waitFor(() => { const boardCards = document.querySelectorAll(".agent-board-card"); // 4 agents total, but terminated (agent-004) is filtered out in default view expect(boardCards.length).toBe(3); }); }); it("persists view toggle preference to project-scoped localStorage", async () => { render(); await waitFor(() => { expect(screen.getByText("Agents")).toBeTruthy(); }); fireEvent.click(screen.getByTitle("Board view")); await waitFor(() => { expect(localStorage.getItem(scopedKey("kb-agent-view", projectId))).toBe("board"); }); }); it("defaults to list view when no localStorage preference exists", async () => { render(); await waitFor(() => { const listContainer = document.querySelector(".agent-list"); expect(listContainer).toBeTruthy(); }); }); it("marks board view button as active when in board mode", async () => { render(); await waitFor(() => { expect(screen.getByText("Agents")).toBeTruthy(); }); const boardBtn = screen.getByTitle("Board view"); fireEvent.click(boardBtn); await waitFor(() => { expect(boardBtn.className).toContain("active"); expect(boardBtn.getAttribute("aria-pressed")).toBe("true"); }); }); }); describe("Org Chart view", () => { const orgTree: OrgTreeNode[] = [ { agent: { id: "agent-root-1", name: "Chief Agent", role: "scheduler", state: "active", lastHeartbeatAt: new Date().toISOString(), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, children: [ { agent: { id: "agent-child-1", name: "Director One", role: "executor", state: "running", lastHeartbeatAt: new Date().toISOString(), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, children: [ { agent: { id: "agent-grandchild-1", name: "Manager Alpha", role: "reviewer", state: "idle", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, children: [], }, ], }, { agent: { id: "agent-child-2", name: "Director Two", role: "triage", state: "paused", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, children: [], }, ], }, { agent: { id: "agent-root-2", name: "Independent Lead", role: "engineer", state: "error", lastError: "Agent stalled", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, children: [], }, ]; it("renders org chart toggle with aria attributes and activates org view", async () => { mockFetchOrgTree.mockResolvedValue(orgTree); render(); const orgButton = screen.getByRole("button", { name: "Org Chart view" }); expect(orgButton.getAttribute("aria-pressed")).toBe("false"); fireEvent.click(orgButton); await waitFor(() => { expect(orgButton.className).toContain("active"); expect(orgButton.getAttribute("aria-pressed")).toBe("true"); }); await waitFor(() => { expect(mockFetchOrgTree).toHaveBeenCalledWith(projectId); }); }); it("renders org chart nodes and opens detail view when clicking a node", async () => { mockFetchOrgTree.mockResolvedValue(orgTree); render(); fireEvent.click(screen.getByRole("button", { name: "Org Chart view" })); await waitFor(() => { expect(screen.getByText("Chief Agent")).toBeTruthy(); expect(screen.getByText("Director One")).toBeTruthy(); expect(screen.getByText("Manager Alpha")).toBeTruthy(); expect(screen.getByText("Independent Lead")).toBeTruthy(); expect(screen.getAllByText(/Healthy|Idle|Paused|Unresponsive|Agent stalled/).length).toBeGreaterThan(0); }); fireEvent.click(screen.getByText("Director One")); await waitFor(() => { expect(screen.getByTestId("agent-detail-view")).toHaveTextContent("agent-child-1"); }); }); it("shows org chart empty state when API returns no nodes", async () => { mockFetchOrgTree.mockResolvedValue([]); render(); fireEvent.click(screen.getByRole("button", { name: "Org Chart view" })); await waitFor(() => { expect(screen.getByText("No agents found")).toBeTruthy(); expect(screen.getByText("Create an agent to get started")).toBeTruthy(); }); }); it("shows loading state while org chart request is in flight", async () => { let resolveOrgTree: ((value: OrgTreeNode[]) => void) | undefined; mockFetchOrgTree.mockImplementation( () => new Promise((resolve) => { resolveOrgTree = resolve; }), ); render(); fireEvent.click(screen.getByRole("button", { name: "Org Chart view" })); await waitFor(() => { expect(screen.getByText("Loading org chart...")).toBeTruthy(); }); resolveOrgTree?.([]); await waitFor(() => { expect(screen.queryByText("Loading org chart...")).toBeNull(); }); }); }); describe("filter agents by state", () => { it("renders the state filter with styled container", async () => { render(); await waitFor(() => { expect(screen.getByText("All States")).toBeTruthy(); }); // Styled filter container exists const filterContainer = document.querySelector(".agent-state-filter"); expect(filterContainer).toBeTruthy(); // Select has correct aria-label const filterSelect = screen.getByLabelText("Filter agents by state"); expect(filterSelect).toBeTruthy(); }); it("can filter agents by state", async () => { render(); await waitFor(() => { expect(screen.getByText("All States")).toBeTruthy(); }); const filterSelect = screen.getByDisplayValue("All States"); fireEvent.change(filterSelect, { target: { value: "active" } }); await waitFor(() => { expect(mockFetchAgents).toHaveBeenCalledWith({ state: "active" }, undefined); }); }); it("clears filter when selecting 'all'", async () => { render(); await waitFor(() => { expect(screen.getByText("All States")).toBeTruthy(); }); const filterSelect = screen.getByDisplayValue("All States"); fireEvent.change(filterSelect, { target: { value: "idle" } }); await waitFor(() => { expect(mockFetchAgents).toHaveBeenLastCalledWith({ state: "idle" }, undefined); }); fireEvent.change(filterSelect, { target: { value: "all" } }); await waitFor(() => { expect(mockFetchAgents).toHaveBeenLastCalledWith(undefined, undefined); }); }); }); describe("create new agent", () => { it("can create new agent via multi-step dialog", async () => { render(); await waitFor(() => { expect(screen.getByText("New Agent")).toBeTruthy(); }); // Open create dialog fireEvent.click(screen.getByText("New Agent")); // Step 0: Fill in agent name const nameInput = screen.getByPlaceholderText("e.g. Frontend Reviewer"); fireEvent.change(nameInput, { target: { value: "My Agent" } }); // Click Next to step 1 fireEvent.click(screen.getByText("Next")); // Step 1: Model selection - click Next fireEvent.click(screen.getByText("Next")); // Step 2: Review - click Create fireEvent.click(screen.getByText("Create")); await waitFor(() => { expect(mockCreateAgent).toHaveBeenCalledWith( expect.objectContaining({ name: "My Agent", role: "custom", }), undefined, ); }); }); it("shows create dialog when clicking New Agent button", async () => { render(); await waitFor(() => { expect(screen.getByText("New Agent")).toBeTruthy(); }); fireEvent.click(screen.getByText("New Agent")); expect(screen.getByPlaceholderText("e.g. Frontend Reviewer")).toBeTruthy(); }); it("does not allow proceeding with empty name", async () => { render(); await waitFor(() => { expect(screen.getByText("New Agent")).toBeTruthy(); }); fireEvent.click(screen.getByText("New Agent")); // Next button should be disabled when name is empty const nextBtn = screen.getByText("Next"); expect(nextBtn.hasAttribute("disabled")).toBe(true); }); it("handles creation error gracefully", async () => { mockCreateAgent.mockRejectedValue(new Error("Creation failed")); render(); await waitFor(() => { expect(screen.getByText("New Agent")).toBeTruthy(); }); fireEvent.click(screen.getByText("New Agent")); const nameInput = screen.getByPlaceholderText("e.g. Frontend Reviewer"); fireEvent.change(nameInput, { target: { value: "Fail Agent" } }); // Navigate through steps fireEvent.click(screen.getByText("Next")); fireEvent.click(screen.getByText("Next")); fireEvent.click(screen.getByText("Create")); await waitFor(() => { // Error should be shown somewhere (dialog or toast) const errorShown = screen.queryByText(/Creation failed/) !== null || document.body.textContent?.includes("Creation failed"); expect(errorShown).toBe(true); }); }); }); describe("change agent state", () => { it("can change agent state - activate idle agent", async () => { render(); await waitFor(() => { expect(screen.getByTitle("Activate")).toBeTruthy(); }); fireEvent.click(screen.getByTitle("Activate")); await waitFor(() => { expect(mockUpdateAgentState).toHaveBeenCalledWith("agent-001", "active", undefined); expect(mockStartAgentRun).toHaveBeenCalledWith("agent-001", undefined); }); expect(mockAddToast).toHaveBeenCalledWith( expect.stringContaining("active"), "success" ); }); it("can pause active agent", async () => { render(); await waitFor(() => { const agentCards = document.querySelectorAll(".agent-card"); expect(agentCards.length).toBeGreaterThan(0); }); // Find the active agent card const agentCards = document.querySelectorAll(".agent-card"); let activeCard: Element | null = null; agentCards.forEach(card => { if (card.textContent?.includes("agent-002")) { activeCard = card; } }); expect(activeCard).toBeTruthy(); const pauseButton = activeCard?.querySelector('[title="Pause"]') as HTMLElement; expect(pauseButton).toBeTruthy(); fireEvent.click(pauseButton); await waitFor(() => { expect(mockUpdateAgentState).toHaveBeenCalledWith("agent-002", "paused", undefined); }); }); it("can resume paused agent", async () => { render(); await waitFor(() => { expect(screen.getByTitle("Resume")).toBeTruthy(); }); fireEvent.click(screen.getByTitle("Resume")); await waitFor(() => { expect(mockUpdateAgentState).toHaveBeenCalledWith("agent-003", "active", undefined); expect(mockStartAgentRun).toHaveBeenCalledWith("agent-003", undefined); }); }); it("handles state change error gracefully", async () => { mockUpdateAgentState.mockRejectedValue(new Error("State change failed")); render(); await waitFor(() => { expect(screen.getByTitle("Activate")).toBeTruthy(); }); fireEvent.click(screen.getByTitle("Activate")); await waitFor(() => { expect(mockAddToast).toHaveBeenCalledWith( expect.stringContaining("State change failed"), "error" ); }); }); it("shows error toast when startAgentRun fails but still updates state", async () => { mockStartAgentRun.mockRejectedValue(new Error("Run failed")); render(); await waitFor(() => { expect(screen.getByTitle("Activate")).toBeTruthy(); }); fireEvent.click(screen.getByTitle("Activate")); await waitFor(() => { expect(mockUpdateAgentState).toHaveBeenCalledWith("agent-001", "active", undefined); expect(mockStartAgentRun).toHaveBeenCalledWith("agent-001", undefined); expect(mockAddToast).toHaveBeenCalledWith( expect.stringContaining("failed to start run"), "error" ); }); }); it("does not start run when pausing agent", async () => { render(); await waitFor(() => { const agentCards = document.querySelectorAll(".agent-card"); expect(agentCards.length).toBeGreaterThan(0); }); // Find the active agent card const agentCards = document.querySelectorAll(".agent-card"); let activeCard: Element | null = null; agentCards.forEach(card => { if (card.textContent?.includes("agent-002")) { activeCard = card; } }); const pauseButton = activeCard?.querySelector('[title="Pause"]') as HTMLElement; fireEvent.click(pauseButton); await waitFor(() => { expect(mockUpdateAgentState).toHaveBeenCalledWith("agent-002", "paused", undefined); }); // startAgentRun should NOT be called when pausing expect(mockStartAgentRun).not.toHaveBeenCalled(); }); }); describe("delete agent", () => { it("shows Delete button for idle agents in default view (terminated filtered out)", async () => { render(); await waitFor(() => { // In default "All States" view, only idle agent (agent-001) should show Delete button // Terminated agents (agent-004) are filtered out const deleteButtons = screen.getAllByTitle("Delete"); expect(deleteButtons.length).toBeGreaterThanOrEqual(1); }); // Verify terminated agent is not visible expect(screen.queryByText("Test Agent 4")).toBeNull(); }); it("shows Delete button for terminated agents when explicitly filtered", async () => { render(); await waitFor(() => { expect(screen.getByText("All States")).toBeTruthy(); }); // Switch to terminated filter const filterSelect = screen.getByDisplayValue("All States"); fireEvent.change(filterSelect, { target: { value: "terminated" } }); await waitFor(() => { expect(screen.getByText("Test Agent 4")).toBeTruthy(); // Now we should see the Delete button for terminated agent expect(screen.getAllByTitle("Delete").length).toBeGreaterThanOrEqual(1); }); }); it("does not show Delete button for active or paused agents", async () => { render(); await waitFor(() => { // Find the active agent card (agent-002) const agentCards = document.querySelectorAll(".agent-card"); let activeCard: Element | null = null; let pausedCard: Element | null = null; agentCards.forEach(card => { if (card.textContent?.includes("agent-002")) activeCard = card; if (card.textContent?.includes("agent-003")) pausedCard = card; }); // Active and paused agents should not have delete buttons expect(activeCard?.querySelector('[title="Delete"]')).toBeFalsy(); expect(pausedCard?.querySelector('[title="Delete"]')).toBeFalsy(); }); }); it("confirms before deleting terminated agent (from terminated filter)", async () => { const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(false); render(); await waitFor(() => { expect(screen.getByText("All States")).toBeTruthy(); }); // Switch to terminated filter to see terminated agent const filterSelect = screen.getByDisplayValue("All States"); fireEvent.change(filterSelect, { target: { value: "terminated" } }); await waitFor(() => { expect(screen.getByText("Test Agent 4")).toBeTruthy(); // Click the delete button for the terminated agent (agent-004) const agentCards = document.querySelectorAll(".agent-card"); let terminatedCard: Element | null = null; agentCards.forEach(card => { if (card.textContent?.includes("agent-004")) terminatedCard = card; }); const terminatedDeleteBtn = terminatedCard?.querySelector('[title="Delete"]') as HTMLElement; expect(terminatedDeleteBtn).toBeTruthy(); fireEvent.click(terminatedDeleteBtn); }); expect(confirmSpy).toHaveBeenCalledWith( expect.stringContaining("Test Agent 4") ); expect(mockDeleteAgent).not.toHaveBeenCalled(); confirmSpy.mockRestore(); }); it("deletes terminated agent after confirmation (from terminated filter)", async () => { vi.spyOn(window, "confirm").mockReturnValue(true); render(); await waitFor(() => { expect(screen.getByText("All States")).toBeTruthy(); }); // Switch to terminated filter to see terminated agent const filterSelect = screen.getByDisplayValue("All States"); fireEvent.change(filterSelect, { target: { value: "terminated" } }); await waitFor(() => { expect(screen.getByText("Test Agent 4")).toBeTruthy(); }); // Find the delete button for terminated agent (agent-004) const agentCards = document.querySelectorAll(".agent-card"); let terminatedCard: Element | null = null; agentCards.forEach(card => { if (card.textContent?.includes("agent-004")) terminatedCard = card; }); const terminatedDeleteBtn = terminatedCard?.querySelector('[title="Delete"]') as HTMLElement; fireEvent.click(terminatedDeleteBtn); await waitFor(() => { expect(mockDeleteAgent).toHaveBeenCalledWith("agent-004", undefined); }); expect(mockAddToast).toHaveBeenCalledWith( expect.stringContaining("deleted"), "success" ); }); it("deletes idle agent after confirmation (from default view)", async () => { vi.spyOn(window, "confirm").mockReturnValue(true); render(); await waitFor(() => { // Only idle agent (agent-001) should have delete button in default view // Terminated agent (agent-004) is filtered out const deleteButtons = screen.getAllByTitle("Delete"); expect(deleteButtons.length).toBeGreaterThanOrEqual(1); }); // Find the delete button for idle agent (agent-001) const agentCards = document.querySelectorAll(".agent-card"); let idleCard: Element | null = null; agentCards.forEach(card => { if (card.textContent?.includes("agent-001")) idleCard = card; }); const idleDeleteBtn = idleCard?.querySelector('[title="Delete"]') as HTMLElement; fireEvent.click(idleDeleteBtn); await waitFor(() => { expect(mockDeleteAgent).toHaveBeenCalledWith("agent-001", undefined); }); expect(mockAddToast).toHaveBeenCalledWith( expect.stringContaining("deleted"), "success" ); }); }); describe("refresh functionality", () => { it("refreshes agent list when clicking refresh button", async () => { render(); await waitFor(() => { expect(screen.getByTitle("Refresh")).toBeTruthy(); }); mockFetchAgents.mockClear(); fireEvent.click(screen.getByTitle("Refresh")); await waitFor(() => { expect(mockFetchAgents).toHaveBeenCalled(); }); }); }); describe("active agents panel selection", () => { it("renders active agents panel when agents are active", async () => { // agent-002 is active with taskId FN-001 render(); await waitFor(() => { expect(screen.getByText("Active Agents (1)")).toBeTruthy(); }); // Should have a live agent card for the active agent const liveAgentCards = document.querySelectorAll(".live-agent-card"); expect(liveAgentCards.length).toBe(1); }); it("opens AgentDetailView when clicking an active agent card", async () => { render(); await waitFor(() => { expect(screen.getByText("Active Agents (1)")).toBeTruthy(); }); // Find and click the live agent card const liveAgentCard = document.querySelector(".live-agent-card"); expect(liveAgentCard).toBeTruthy(); fireEvent.click(liveAgentCard!); await waitFor(() => { // Should open detail view for agent-002 (the active agent) expect(screen.getByTestId("agent-detail-view")).toHaveTextContent("agent-002"); }); }); it("opens AgentDetailView when pressing Enter on an active agent card", async () => { render(); await waitFor(() => { expect(screen.getByText("Active Agents (1)")).toBeTruthy(); }); // Find the live agent card const liveAgentCard = document.querySelector(".live-agent-card") as HTMLElement; expect(liveAgentCard).toBeTruthy(); // Focus and press Enter liveAgentCard.focus(); fireEvent.keyDown(liveAgentCard, { key: "Enter" }); await waitFor(() => { expect(screen.getByTestId("agent-detail-view")).toHaveTextContent("agent-002"); }); }); it("opens AgentDetailView when pressing Space on an active agent card", async () => { render(); await waitFor(() => { expect(screen.getByText("Active Agents (1)")).toBeTruthy(); }); // Find the live agent card const liveAgentCard = document.querySelector(".live-agent-card") as HTMLElement; expect(liveAgentCard).toBeTruthy(); // Focus and press Space liveAgentCard.focus(); fireEvent.keyDown(liveAgentCard, { key: " " }); await waitFor(() => { expect(screen.getByTestId("agent-detail-view")).toHaveTextContent("agent-002"); }); }); it("live agent cards have proper accessibility attributes", async () => { render(); await waitFor(() => { expect(screen.getByText("Active Agents (1)")).toBeTruthy(); }); const liveAgentCard = document.querySelector(".live-agent-card") as HTMLElement; expect(liveAgentCard).toBeTruthy(); // Check accessibility attributes expect(liveAgentCard.getAttribute("role")).toBe("button"); expect(liveAgentCard.getAttribute("tabIndex")).toBe("0"); expect(liveAgentCard.getAttribute("aria-label")).toBe("Select agent Test Agent 2"); }); it("does not show active agents panel when no agents are active", async () => { // Create agents with no active ones const inactiveAgents: Agent[] = [ { id: "agent-005", name: "Idle Agent", role: "executor" as AgentCapability, state: "idle" as AgentState, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, ]; mockFetchAgents.mockResolvedValue(inactiveAgents); render(); await waitFor(() => { expect(screen.queryByText("Active Agents")).toBeNull(); }); }); it("opens AgentDetailView for spawned agents in the active panel", async () => { // Simulate spawned agents by having multiple active agents const spawnedAgents: Agent[] = [ ...mockAgents, { id: "spawned-001", name: "Spawned Worker", role: "custom" as AgentCapability, state: "active" as AgentState, taskId: "FN-100", lastHeartbeatAt: new Date().toISOString(), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: {}, }, ]; mockFetchAgents.mockResolvedValue(spawnedAgents); render(); await waitFor(() => { expect(screen.getByText("Active Agents (2)")).toBeTruthy(); }); // Find and click the spawned agent card const liveAgentCards = document.querySelectorAll(".live-agent-card"); expect(liveAgentCards.length).toBe(2); // Click on the spawned agent const spawnedCard = Array.from(liveAgentCards).find( card => card.textContent?.includes("Spawned Worker") ); expect(spawnedCard).toBeTruthy(); fireEvent.click(spawnedCard!); await waitFor(() => { expect(screen.getByTestId("agent-detail-view")).toHaveTextContent("spawned-001"); }); }); }); });