Files
storkit/frontend/src/components/AgentPanel.test.tsx
2026-02-23 21:39:40 +00:00

172 lines
4.9 KiB
TypeScript

import { render, screen } from "@testing-library/react";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { AgentConfigInfo, AgentInfo } from "../api/agents";
import { agentsApi } from "../api/agents";
vi.mock("../api/agents", () => {
const agentsApi = {
listAgents: vi.fn(),
getAgentConfig: vi.fn(),
startAgent: vi.fn(),
stopAgent: vi.fn(),
reloadConfig: vi.fn(),
};
return { agentsApi, subscribeAgentStream: vi.fn(() => () => {}) };
});
// Dynamic import so the mock is in place before the module loads
const { AgentPanel } = await import("./AgentPanel");
const mockedAgents = {
listAgents: vi.mocked(agentsApi.listAgents),
getAgentConfig: vi.mocked(agentsApi.getAgentConfig),
startAgent: vi.mocked(agentsApi.startAgent),
};
const ROSTER: AgentConfigInfo[] = [
{
name: "coder-1",
role: "Full-stack engineer",
model: "sonnet",
allowed_tools: null,
max_turns: 50,
max_budget_usd: 5.0,
},
];
describe("AgentPanel active work list removed", () => {
beforeAll(() => {
Element.prototype.scrollIntoView = vi.fn();
});
beforeEach(() => {
mockedAgents.getAgentConfig.mockResolvedValue(ROSTER);
mockedAgents.listAgents.mockResolvedValue([]);
});
it("does not render active agent entries even when agents are running", async () => {
const agentList: AgentInfo[] = [
{
story_id: "83_active",
agent_name: "coder-1",
status: "running",
session_id: null,
worktree_path: "/tmp/wt",
base_branch: "master",
log_session_id: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
const { container } = render(<AgentPanel />);
// Roster badge should still be visible
await screen.findByTestId("roster-badge-coder-1");
// No agent entry divs should exist
expect(
container.querySelector('[data-testid^="agent-entry-"]'),
).not.toBeInTheDocument();
});
});
describe("RosterBadge availability state", () => {
beforeAll(() => {
Element.prototype.scrollIntoView = vi.fn();
});
beforeEach(() => {
mockedAgents.getAgentConfig.mockResolvedValue(ROSTER);
mockedAgents.listAgents.mockResolvedValue([]);
});
it("shows a green dot for an idle agent", async () => {
render(<AgentPanel />);
const dot = await screen.findByTestId("roster-dot-coder-1");
// JSDOM normalizes #3fb950 to rgb(63, 185, 80)
expect(dot.style.background).toBe("rgb(63, 185, 80)");
expect(dot.style.animation).toBe("");
});
it("shows grey badge styling for an idle agent", async () => {
render(<AgentPanel />);
const badge = await screen.findByTestId("roster-badge-coder-1");
// JSDOM normalizes #aaa18 to rgba(170, 170, 170, 0.094) and #aaa to rgb(170, 170, 170)
expect(badge.style.background).toBe("rgba(170, 170, 170, 0.094)");
expect(badge.style.color).toBe("rgb(170, 170, 170)");
});
// AC1: roster badge always shows idle (grey) even when agent is running
it("shows a static green dot for a running agent (roster always idle)", async () => {
const agentList: AgentInfo[] = [
{
story_id: "81_active",
agent_name: "coder-1",
status: "running",
session_id: null,
worktree_path: null,
base_branch: null,
log_session_id: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
render(<AgentPanel />);
const dot = await screen.findByTestId("roster-dot-coder-1");
expect(dot.style.background).toBe("rgb(63, 185, 80)");
// Roster is always idle — no pulsing animation
expect(dot.style.animation).toBe("");
});
// AC1: roster badge always shows idle (grey) even when agent is running
it("shows grey (idle) badge styling for a running agent", async () => {
const agentList: AgentInfo[] = [
{
story_id: "81_active",
agent_name: "coder-1",
status: "running",
session_id: null,
worktree_path: null,
base_branch: null,
log_session_id: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
render(<AgentPanel />);
const badge = await screen.findByTestId("roster-badge-coder-1");
// Always idle: grey background and grey text
expect(badge.style.background).toBe("rgba(170, 170, 170, 0.094)");
expect(badge.style.color).toBe("rgb(170, 170, 170)");
});
// AC2: after agent completes and returns to roster, badge shows idle
it("shows idle state after agent status changes from running to completed", async () => {
const agentList: AgentInfo[] = [
{
story_id: "81_completed",
agent_name: "coder-1",
status: "completed",
session_id: null,
worktree_path: null,
base_branch: null,
log_session_id: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
render(<AgentPanel />);
const badge = await screen.findByTestId("roster-badge-coder-1");
const dot = screen.getByTestId("roster-dot-coder-1");
// Completed agent: badge is idle
expect(badge.style.background).toBe("rgba(170, 170, 170, 0.094)");
expect(badge.style.color).toBe("rgb(170, 170, 170)");
expect(dot.style.animation).toBe("");
});
});