huskies: merge 806
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
import { act, render, screen } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { AgentEvent, AgentInfo } from "../api/agents";
|
||||
|
||||
vi.mock("../api/client", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("../api/client")>("../api/client");
|
||||
return {
|
||||
...actual,
|
||||
api: {
|
||||
...actual.api,
|
||||
getWorkItemContent: vi.fn(),
|
||||
getTestResults: vi.fn(),
|
||||
getTokenCost: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../api/agents", () => ({
|
||||
agentsApi: {
|
||||
listAgents: vi.fn(),
|
||||
getAgentConfig: vi.fn(),
|
||||
stopAgent: vi.fn(),
|
||||
startAgent: vi.fn(),
|
||||
},
|
||||
subscribeAgentStream: vi.fn(() => () => {}),
|
||||
}));
|
||||
|
||||
import { agentsApi, subscribeAgentStream } from "../api/agents";
|
||||
import { api } from "../api/client";
|
||||
|
||||
const { WorkItemDetailPanel } = await import("./WorkItemDetailPanel");
|
||||
|
||||
const mockedGetWorkItemContent = vi.mocked(api.getWorkItemContent);
|
||||
const mockedGetTestResults = vi.mocked(api.getTestResults);
|
||||
const mockedGetTokenCost = vi.mocked(api.getTokenCost);
|
||||
const mockedListAgents = vi.mocked(agentsApi.listAgents);
|
||||
const mockedGetAgentConfig = vi.mocked(agentsApi.getAgentConfig);
|
||||
const mockedSubscribeAgentStream = vi.mocked(subscribeAgentStream);
|
||||
|
||||
const DEFAULT_CONTENT = {
|
||||
content: "# Big Title\n\nSome content here.",
|
||||
stage: "current",
|
||||
name: "Big Title Story",
|
||||
agent: null,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockedGetWorkItemContent.mockResolvedValue(DEFAULT_CONTENT);
|
||||
mockedGetTestResults.mockResolvedValue(null);
|
||||
mockedGetTokenCost.mockResolvedValue({ total_cost_usd: 0, agents: [] });
|
||||
mockedListAgents.mockResolvedValue([]);
|
||||
mockedGetAgentConfig.mockResolvedValue([]);
|
||||
mockedSubscribeAgentStream.mockReturnValue(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("WorkItemDetailPanel - Agent Logs", () => {
|
||||
it("shows placeholder when no agent is assigned to the story", async () => {
|
||||
render(
|
||||
<WorkItemDetailPanel
|
||||
storyId="42_story_test"
|
||||
pipelineVersion={0}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
await screen.findByTestId("detail-panel-content");
|
||||
const placeholder = screen.getByTestId("placeholder-agent-logs");
|
||||
expect(placeholder).toBeInTheDocument();
|
||||
expect(placeholder).toHaveTextContent("Coming soon");
|
||||
});
|
||||
|
||||
it("shows agent name and running status when agent is running", async () => {
|
||||
const agentList: AgentInfo[] = [
|
||||
{
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
status: "running",
|
||||
session_id: null,
|
||||
worktree_path: "/tmp/wt",
|
||||
base_branch: "master",
|
||||
log_session_id: null,
|
||||
},
|
||||
];
|
||||
mockedListAgents.mockResolvedValue(agentList);
|
||||
|
||||
render(
|
||||
<WorkItemDetailPanel
|
||||
storyId="42_story_test"
|
||||
pipelineVersion={0}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const statusBadge = await screen.findByTestId("agent-status-badge");
|
||||
expect(statusBadge).toHaveTextContent("coder-1");
|
||||
expect(statusBadge).toHaveTextContent("running");
|
||||
});
|
||||
|
||||
it("shows log output when agent emits output events", async () => {
|
||||
let emitEvent: ((e: AgentEvent) => void) | null = null;
|
||||
mockedSubscribeAgentStream.mockImplementation(
|
||||
(_storyId, _agentName, onEvent) => {
|
||||
emitEvent = onEvent;
|
||||
return () => {};
|
||||
},
|
||||
);
|
||||
|
||||
const agentList: AgentInfo[] = [
|
||||
{
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
status: "running",
|
||||
session_id: null,
|
||||
worktree_path: "/tmp/wt",
|
||||
base_branch: "master",
|
||||
log_session_id: null,
|
||||
},
|
||||
];
|
||||
mockedListAgents.mockResolvedValue(agentList);
|
||||
|
||||
render(
|
||||
<WorkItemDetailPanel
|
||||
storyId="42_story_test"
|
||||
pipelineVersion={0}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await screen.findByTestId("agent-status-badge");
|
||||
|
||||
await act(async () => {
|
||||
emitEvent?.({
|
||||
type: "output",
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
text: "Writing tests...",
|
||||
});
|
||||
});
|
||||
|
||||
const logOutput = screen.getByTestId("agent-log-output");
|
||||
expect(logOutput).toHaveTextContent("Writing tests...");
|
||||
});
|
||||
|
||||
it("appends multiple output events to the log", async () => {
|
||||
let emitEvent: ((e: AgentEvent) => void) | null = null;
|
||||
mockedSubscribeAgentStream.mockImplementation(
|
||||
(_storyId, _agentName, onEvent) => {
|
||||
emitEvent = onEvent;
|
||||
return () => {};
|
||||
},
|
||||
);
|
||||
|
||||
const agentList: AgentInfo[] = [
|
||||
{
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
status: "running",
|
||||
session_id: null,
|
||||
worktree_path: "/tmp/wt",
|
||||
base_branch: "master",
|
||||
log_session_id: null,
|
||||
},
|
||||
];
|
||||
mockedListAgents.mockResolvedValue(agentList);
|
||||
|
||||
render(
|
||||
<WorkItemDetailPanel
|
||||
storyId="42_story_test"
|
||||
pipelineVersion={0}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await screen.findByTestId("agent-status-badge");
|
||||
|
||||
await act(async () => {
|
||||
emitEvent?.({
|
||||
type: "output",
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
text: "Line one\n",
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
emitEvent?.({
|
||||
type: "output",
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
text: "Line two\n",
|
||||
});
|
||||
});
|
||||
|
||||
const logOutput = screen.getByTestId("agent-log-output");
|
||||
expect(logOutput.textContent).toContain("Line one");
|
||||
expect(logOutput.textContent).toContain("Line two");
|
||||
});
|
||||
|
||||
it("updates status to completed after done event", async () => {
|
||||
let emitEvent: ((e: AgentEvent) => void) | null = null;
|
||||
mockedSubscribeAgentStream.mockImplementation(
|
||||
(_storyId, _agentName, onEvent) => {
|
||||
emitEvent = onEvent;
|
||||
return () => {};
|
||||
},
|
||||
);
|
||||
|
||||
const agentList: AgentInfo[] = [
|
||||
{
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
status: "running",
|
||||
session_id: null,
|
||||
worktree_path: "/tmp/wt",
|
||||
base_branch: "master",
|
||||
log_session_id: null,
|
||||
},
|
||||
];
|
||||
mockedListAgents.mockResolvedValue(agentList);
|
||||
|
||||
render(
|
||||
<WorkItemDetailPanel
|
||||
storyId="42_story_test"
|
||||
pipelineVersion={0}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await screen.findByTestId("agent-status-badge");
|
||||
|
||||
await act(async () => {
|
||||
emitEvent?.({
|
||||
type: "done",
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
session_id: "session-123",
|
||||
});
|
||||
});
|
||||
|
||||
const statusBadge = screen.getByTestId("agent-status-badge");
|
||||
expect(statusBadge).toHaveTextContent("completed");
|
||||
});
|
||||
|
||||
it("shows failed status after error event", async () => {
|
||||
let emitEvent: ((e: AgentEvent) => void) | null = null;
|
||||
mockedSubscribeAgentStream.mockImplementation(
|
||||
(_storyId, _agentName, onEvent) => {
|
||||
emitEvent = onEvent;
|
||||
return () => {};
|
||||
},
|
||||
);
|
||||
|
||||
const agentList: AgentInfo[] = [
|
||||
{
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
status: "running",
|
||||
session_id: null,
|
||||
worktree_path: "/tmp/wt",
|
||||
base_branch: "master",
|
||||
log_session_id: null,
|
||||
},
|
||||
];
|
||||
mockedListAgents.mockResolvedValue(agentList);
|
||||
|
||||
render(
|
||||
<WorkItemDetailPanel
|
||||
storyId="42_story_test"
|
||||
pipelineVersion={0}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await screen.findByTestId("agent-status-badge");
|
||||
|
||||
await act(async () => {
|
||||
emitEvent?.({
|
||||
type: "error",
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
message: "Process failed",
|
||||
});
|
||||
});
|
||||
|
||||
const statusBadge = screen.getByTestId("agent-status-badge");
|
||||
expect(statusBadge).toHaveTextContent("failed");
|
||||
|
||||
const logOutput = screen.getByTestId("agent-log-output");
|
||||
expect(logOutput.textContent).toContain("[ERROR] Process failed");
|
||||
});
|
||||
|
||||
it("shows completed agent status without subscribing to stream", async () => {
|
||||
const agentList: AgentInfo[] = [
|
||||
{
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
status: "completed",
|
||||
session_id: "session-123",
|
||||
worktree_path: "/tmp/wt",
|
||||
base_branch: "master",
|
||||
log_session_id: null,
|
||||
},
|
||||
];
|
||||
mockedListAgents.mockResolvedValue(agentList);
|
||||
|
||||
render(
|
||||
<WorkItemDetailPanel
|
||||
storyId="42_story_test"
|
||||
pipelineVersion={0}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const statusBadge = await screen.findByTestId("agent-status-badge");
|
||||
expect(statusBadge).toHaveTextContent("completed");
|
||||
expect(mockedSubscribeAgentStream).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows failed agent status for a failed agent without subscribing to stream", async () => {
|
||||
const agentList: AgentInfo[] = [
|
||||
{
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
status: "failed",
|
||||
session_id: null,
|
||||
worktree_path: null,
|
||||
base_branch: "master",
|
||||
log_session_id: null,
|
||||
},
|
||||
];
|
||||
mockedListAgents.mockResolvedValue(agentList);
|
||||
|
||||
render(
|
||||
<WorkItemDetailPanel
|
||||
storyId="42_story_test"
|
||||
pipelineVersion={0}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const statusBadge = await screen.findByTestId("agent-status-badge");
|
||||
expect(statusBadge).toHaveTextContent("failed");
|
||||
expect(mockedSubscribeAgentStream).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows agent logs section (not placeholder) when agent is assigned", async () => {
|
||||
const agentList: AgentInfo[] = [
|
||||
{
|
||||
story_id: "42_story_test",
|
||||
agent_name: "coder-1",
|
||||
status: "running",
|
||||
session_id: null,
|
||||
worktree_path: "/tmp/wt",
|
||||
base_branch: "master",
|
||||
log_session_id: null,
|
||||
},
|
||||
];
|
||||
mockedListAgents.mockResolvedValue(agentList);
|
||||
|
||||
render(
|
||||
<WorkItemDetailPanel
|
||||
storyId="42_story_test"
|
||||
pipelineVersion={0}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await screen.findByTestId("agent-logs-section");
|
||||
|
||||
expect(
|
||||
screen.queryByTestId("placeholder-agent-logs"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user