fix(KB-605): add CSS variables for agent badges and AgentListModal tests
- Add missing CSS variables for agent state badges in AgentListModal - Add comprehensive test suite for AgentListModal component (754 lines) - Include changeset documenting the CSS fix
This commit is contained in:
@@ -309,6 +309,23 @@ export function AgentListModal({ isOpen, onClose, addToast }: AgentListModalProp
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
:host, .modal--wide {
|
||||
/* Agent state CSS variables - define fallback values */
|
||||
--state-idle-bg: rgba(139, 148, 158, 0.15);
|
||||
--state-idle-text: #8b949e;
|
||||
--state-idle-border: #8b949e;
|
||||
--state-active-bg: rgba(46, 160, 67, 0.15);
|
||||
--state-active-text: #3fb950;
|
||||
--state-active-border: #3fb950;
|
||||
--state-paused-bg: rgba(227, 179, 65, 0.15);
|
||||
--state-paused-text: #e3b541;
|
||||
--state-paused-border: #e3b541;
|
||||
--state-error-bg: rgba(248, 81, 73, 0.15);
|
||||
--state-error-text: #f85149;
|
||||
--state-error-border: #f85149;
|
||||
--text-secondary: var(--text-muted, #8b949e);
|
||||
}
|
||||
|
||||
.modal--wide {
|
||||
width: 90vw;
|
||||
max-width: 900px;
|
||||
|
||||
@@ -0,0 +1,754 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
import { AgentListModal } from "../AgentListModal";
|
||||
import * as apiModule from "../../api";
|
||||
import type { Agent, AgentState, AgentCapability } from "../../api";
|
||||
|
||||
// Mock the API module
|
||||
vi.mock("../../api", () => ({
|
||||
fetchAgents: vi.fn(),
|
||||
createAgent: vi.fn(),
|
||||
updateAgentState: vi.fn(),
|
||||
deleteAgent: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockFetchAgents = vi.mocked(apiModule.fetchAgents);
|
||||
const mockCreateAgent = vi.mocked(apiModule.createAgent);
|
||||
const mockUpdateAgentState = vi.mocked(apiModule.updateAgentState);
|
||||
const mockDeleteAgent = vi.mocked(apiModule.deleteAgent);
|
||||
|
||||
describe("AgentListModal", () => {
|
||||
const mockOnClose = vi.fn();
|
||||
const mockAddToast = vi.fn();
|
||||
|
||||
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: "KB-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();
|
||||
mockFetchAgents.mockResolvedValue(mockAgents);
|
||||
mockCreateAgent.mockResolvedValue(mockAgents[0]);
|
||||
mockUpdateAgentState.mockResolvedValue({ ...mockAgents[0], state: "active" });
|
||||
mockDeleteAgent.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
describe("modal visibility", () => {
|
||||
it("renders when isOpen is true", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Agents")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not render when isOpen is false", () => {
|
||||
const { container } = render(
|
||||
<AgentListModal
|
||||
isOpen={false}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it("calls onClose when clicking the overlay", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Agents")).toBeTruthy();
|
||||
});
|
||||
|
||||
const overlay = document.querySelector(".modal-overlay");
|
||||
if (overlay) {
|
||||
fireEvent.click(overlay);
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it("calls onClose when clicking the close button", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const closeButton = screen.getByTitle("Close");
|
||||
expect(closeButton).toBeTruthy();
|
||||
});
|
||||
|
||||
const closeButton = screen.getByTitle("Close");
|
||||
fireEvent.click(closeButton);
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("agent list display", () => {
|
||||
it("fetches agents on mount", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchAgents).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("displays agent names", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Test Agent 1")).toBeTruthy();
|
||||
expect(screen.getByText("Test Agent 2")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("displays agent states as badges", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("idle")).toBeTruthy();
|
||||
expect(screen.getByText("active")).toBeTruthy();
|
||||
expect(screen.getByText("paused")).toBeTruthy();
|
||||
expect(screen.getByText("terminated")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("displays agent roles", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Executor")).toBeTruthy();
|
||||
expect(screen.getByText("Triage")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("displays task ID when agent is working on a task", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("KB-001")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows empty state when no agents exist", async () => {
|
||||
mockFetchAgents.mockResolvedValue([]);
|
||||
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("No agents found")).toBeTruthy();
|
||||
expect(screen.getByText("Create an agent to get started")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows health status for agents", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
// Active agent with heartbeat should show "Healthy"
|
||||
expect(screen.getByText("Healthy")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("agent creation", () => {
|
||||
it("shows create form when clicking New Agent button", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("New Agent")).toBeTruthy();
|
||||
});
|
||||
|
||||
const newAgentButton = screen.getByText("New Agent");
|
||||
fireEvent.click(newAgentButton);
|
||||
|
||||
expect(screen.getByPlaceholderText("Agent name...")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("creates agent with name and role", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("New Agent")).toBeTruthy();
|
||||
});
|
||||
|
||||
// Open create form
|
||||
fireEvent.click(screen.getByText("New Agent"));
|
||||
|
||||
// Fill in agent name
|
||||
const nameInput = screen.getByPlaceholderText("Agent name...");
|
||||
fireEvent.change(nameInput, { target: { value: "My New Agent" } });
|
||||
|
||||
// Select role - find by class within the create form
|
||||
const roleSelect = document.querySelector(".agent-create-form .select") as HTMLSelectElement;
|
||||
expect(roleSelect).toBeTruthy();
|
||||
fireEvent.change(roleSelect, { target: { value: "executor" } });
|
||||
|
||||
// Click create button
|
||||
const createButton = screen.getByText("Create");
|
||||
fireEvent.click(createButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockCreateAgent).toHaveBeenCalledWith({
|
||||
name: "My New Agent",
|
||||
role: "executor",
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockAddToast).toHaveBeenCalledWith(
|
||||
expect.stringContaining("My New Agent"),
|
||||
"success"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not create agent with empty name", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("New Agent")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByText("New Agent"));
|
||||
|
||||
const createButton = screen.getByText("Create");
|
||||
fireEvent.click(createButton);
|
||||
|
||||
expect(mockCreateAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles creation error gracefully", async () => {
|
||||
mockCreateAgent.mockRejectedValue(new Error("Creation failed"));
|
||||
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("New Agent")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByText("New Agent"));
|
||||
|
||||
const nameInput = screen.getByPlaceholderText("Agent name...");
|
||||
fireEvent.change(nameInput, { target: { value: "Fail Agent" } });
|
||||
|
||||
fireEvent.click(screen.getByText("Create"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddToast).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Creation failed"),
|
||||
"error"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("agent state changes", () => {
|
||||
it("shows Start button for idle agents", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const startButtons = screen.getAllByTitle("Activate");
|
||||
expect(startButtons.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("transitions idle agent to active", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Activate")).toBeTruthy();
|
||||
});
|
||||
|
||||
const startButton = screen.getByTitle("Activate");
|
||||
fireEvent.click(startButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateAgentState).toHaveBeenCalledWith("agent-001", "active");
|
||||
});
|
||||
|
||||
expect(mockAddToast).toHaveBeenCalledWith(
|
||||
expect.stringContaining("active"),
|
||||
"success"
|
||||
);
|
||||
});
|
||||
|
||||
it("shows Pause and Stop buttons for active agents", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
// Get all agent cards and find the one with active state
|
||||
const agentCards = document.querySelectorAll(".agent-card");
|
||||
expect(agentCards.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// Find the agent card for the active agent (agent-002)
|
||||
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();
|
||||
|
||||
// Check for Pause and Stop buttons within the active card
|
||||
const pauseButton = activeCard?.querySelector('[title="Pause"]');
|
||||
const stopButton = activeCard?.querySelector('[title="Stop"]');
|
||||
expect(pauseButton).toBeTruthy();
|
||||
expect(stopButton).toBeTruthy();
|
||||
});
|
||||
|
||||
it("pauses active agent", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const agentCards = document.querySelectorAll(".agent-card");
|
||||
expect(agentCards.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// Find the agent card for the active agent (agent-002)
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
it("stops active agent", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const agentCards = document.querySelectorAll(".agent-card");
|
||||
expect(agentCards.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// Find the agent card for the active agent (agent-002)
|
||||
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 stopButton = activeCard?.querySelector('[title="Stop"]') as HTMLElement;
|
||||
expect(stopButton).toBeTruthy();
|
||||
fireEvent.click(stopButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateAgentState).toHaveBeenCalledWith("agent-002", "terminated");
|
||||
});
|
||||
});
|
||||
|
||||
it("shows Resume button for paused agents", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Resume")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("resumes paused agent", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Resume")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTitle("Resume"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateAgentState).toHaveBeenCalledWith("agent-003", "active");
|
||||
});
|
||||
});
|
||||
|
||||
it("handles state change errors gracefully", async () => {
|
||||
mockUpdateAgentState.mockRejectedValue(new Error("Invalid transition"));
|
||||
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Activate")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTitle("Activate"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddToast).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Invalid transition"),
|
||||
"error"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("agent deletion", () => {
|
||||
it("shows Delete button only for terminated agents", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Delete")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("confirms before deleting agent", async () => {
|
||||
const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(false);
|
||||
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Delete")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTitle("Delete"));
|
||||
|
||||
expect(confirmSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Test Agent 4")
|
||||
);
|
||||
expect(mockDeleteAgent).not.toHaveBeenCalled();
|
||||
|
||||
confirmSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("deletes agent after confirmation", async () => {
|
||||
vi.spyOn(window, "confirm").mockReturnValue(true);
|
||||
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Delete")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTitle("Delete"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteAgent).toHaveBeenCalledWith("agent-004");
|
||||
});
|
||||
|
||||
expect(mockAddToast).toHaveBeenCalledWith(
|
||||
expect.stringContaining("deleted"),
|
||||
"success"
|
||||
);
|
||||
});
|
||||
|
||||
it("handles deletion error gracefully", async () => {
|
||||
mockDeleteAgent.mockRejectedValue(new Error("Delete failed"));
|
||||
vi.spyOn(window, "confirm").mockReturnValue(true);
|
||||
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Delete")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTitle("Delete"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddToast).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Delete failed"),
|
||||
"error"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("agent filtering", () => {
|
||||
it("filters agents by state", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
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" });
|
||||
});
|
||||
});
|
||||
|
||||
it("clears filter when selecting 'all'", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
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" });
|
||||
});
|
||||
|
||||
fireEvent.change(filterSelect, { target: { value: "all" } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchAgents).toHaveBeenLastCalledWith(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("refresh functionality", () => {
|
||||
it("refreshes agent list when clicking refresh button", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Refresh")).toBeTruthy();
|
||||
});
|
||||
|
||||
mockFetchAgents.mockClear();
|
||||
|
||||
fireEvent.click(screen.getByTitle("Refresh"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchAgents).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("CSS variables for agent states", () => {
|
||||
it("has CSS variables defined for agent state badges", async () => {
|
||||
render(
|
||||
<AgentListModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
addToast={mockAddToast}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Agents")).toBeTruthy();
|
||||
});
|
||||
|
||||
// Check that style tag is present with CSS variables
|
||||
const styleTag = document.querySelector("style");
|
||||
expect(styleTag).toBeTruthy();
|
||||
expect(styleTag?.textContent).toContain("--state-idle-bg");
|
||||
expect(styleTag?.textContent).toContain("--state-active-bg");
|
||||
expect(styleTag?.textContent).toContain("--state-paused-bg");
|
||||
expect(styleTag?.textContent).toContain("--state-error-bg");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user