story-kit: start 83_story_remove_active_work_list_from_agents_panel

This commit is contained in:
Dave
2026-02-23 16:04:02 +00:00
parent c42be5d12c
commit 29eff51182
4 changed files with 33 additions and 749 deletions

View File

@@ -1,14 +1,5 @@
import { act, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from "vitest";
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";
@@ -43,7 +34,7 @@ const ROSTER: AgentConfigInfo[] = [
},
];
describe("AgentPanel diff command", () => {
describe("AgentPanel active work list removed", () => {
beforeAll(() => {
Element.prototype.scrollIntoView = vi.fn();
});
@@ -53,325 +44,28 @@ describe("AgentPanel diff command", () => {
mockedAgents.listAgents.mockResolvedValue([]);
});
it("shows diff command when an agent has a worktree path", async () => {
it("does not render active agent entries even when agents are running", async () => {
const agentList: AgentInfo[] = [
{
story_id: "33_diff_commands",
story_id: "83_active",
agent_name: "coder-1",
status: "running",
session_id: null,
worktree_path: "/tmp/project-story-33",
worktree_path: "/tmp/wt",
base_branch: "master",
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
render(<AgentPanel />);
const { container } = render(<AgentPanel />);
// Expand the agent detail by clicking the expand button
const expandButton = await screen.findByText("");
await userEvent.click(expandButton);
// Roster badge should still be visible
await screen.findByTestId("roster-badge-coder-1");
// The diff command should be rendered
// No agent entry divs should exist
expect(
await screen.findByText(
'cd "/tmp/project-story-33" && git difftool master...HEAD',
),
).toBeInTheDocument();
// A copy button should exist
expect(screen.getByText("Copy")).toBeInTheDocument();
});
it("copies diff command to clipboard on click", async () => {
const writeText = vi.fn().mockResolvedValue(undefined);
Object.assign(navigator, {
clipboard: { writeText },
});
const agentList: AgentInfo[] = [
{
story_id: "33_diff_commands",
agent_name: "coder-1",
status: "completed",
session_id: null,
worktree_path: "/home/user/my-project-story-33",
base_branch: "main",
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
render(<AgentPanel />);
const expandButton = await screen.findByText("▶");
await userEvent.click(expandButton);
const copyButton = await screen.findByText("Copy");
await userEvent.click(copyButton);
await waitFor(() => {
expect(writeText).toHaveBeenCalledWith(
'cd "/home/user/my-project-story-33" && git difftool main...HEAD',
);
});
expect(await screen.findByText("Copied")).toBeInTheDocument();
});
it("uses base_branch from the server in the diff command", async () => {
const agentList: AgentInfo[] = [
{
story_id: "33_diff_commands",
agent_name: "coder-1",
status: "running",
session_id: null,
worktree_path: "/tmp/wt",
base_branch: "develop",
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
render(<AgentPanel />);
const expandButton = await screen.findByText("▶");
await userEvent.click(expandButton);
expect(
await screen.findByText('cd "/tmp/wt" && git difftool develop...HEAD'),
).toBeInTheDocument();
});
it("defaults to master when base_branch is null", async () => {
const agentList: AgentInfo[] = [
{
story_id: "33_diff_commands",
agent_name: "coder-1",
status: "running",
session_id: null,
worktree_path: "/tmp/wt",
base_branch: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
render(<AgentPanel />);
const expandButton = await screen.findByText("▶");
await userEvent.click(expandButton);
expect(
await screen.findByText('cd "/tmp/wt" && git difftool master...HEAD'),
).toBeInTheDocument();
});
});
describe("AgentPanel fade-out", () => {
beforeAll(() => {
Element.prototype.scrollIntoView = vi.fn();
});
beforeEach(() => {
mockedAgents.getAgentConfig.mockResolvedValue(ROSTER);
mockedAgents.listAgents.mockResolvedValue([]);
});
it("applies fade animation to a completed agent", async () => {
const agentList: AgentInfo[] = [
{
story_id: "73_fade_test",
agent_name: "coder-1",
status: "completed",
session_id: null,
worktree_path: null,
base_branch: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
const { container } = render(<AgentPanel />);
const entry = await waitFor(() => {
const el = container.querySelector(
'[data-testid="agent-entry-73_fade_test:coder-1"]',
);
expect(el).toBeInTheDocument();
return el as HTMLElement;
});
expect(entry.style.animationName).toBe("agentFadeOut");
});
it("applies fade animation to a failed agent", async () => {
const agentList: AgentInfo[] = [
{
story_id: "73_fade_fail",
agent_name: "coder-1",
status: "failed",
session_id: null,
worktree_path: null,
base_branch: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
const { container } = render(<AgentPanel />);
const entry = await waitFor(() => {
const el = container.querySelector(
'[data-testid="agent-entry-73_fade_fail:coder-1"]',
);
expect(el).toBeInTheDocument();
return el as HTMLElement;
});
expect(entry.style.animationName).toBe("agentFadeOut");
});
it("does not apply fade animation to a running agent", async () => {
const agentList: AgentInfo[] = [
{
story_id: "73_running",
agent_name: "coder-1",
status: "running",
session_id: null,
worktree_path: null,
base_branch: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
const { container } = render(<AgentPanel />);
const entry = await waitFor(() => {
const el = container.querySelector(
'[data-testid="agent-entry-73_running:coder-1"]',
);
expect(el).toBeInTheDocument();
return el as HTMLElement;
});
expect(entry.style.animationName).not.toBe("agentFadeOut");
});
it("pauses the fade when the entry is expanded", async () => {
const agentList: AgentInfo[] = [
{
story_id: "73_pause_test",
agent_name: "coder-1",
status: "completed",
session_id: null,
worktree_path: null,
base_branch: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
const { container } = render(<AgentPanel />);
const entry = await waitFor(() => {
const el = container.querySelector(
'[data-testid="agent-entry-73_pause_test:coder-1"]',
);
expect(el).toBeInTheDocument();
return el as HTMLElement;
});
// Initially running (not paused)
expect(entry.style.animationPlayState).toBe("running");
// Expand the agent
const expandButton = screen.getByRole("button", { name: "▶" });
await userEvent.click(expandButton);
// Animation should be paused
expect(entry.style.animationPlayState).toBe("paused");
});
it("resumes the fade when the entry is collapsed", async () => {
const agentList: AgentInfo[] = [
{
story_id: "73_resume_test",
agent_name: "coder-1",
status: "completed",
session_id: null,
worktree_path: null,
base_branch: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
const { container } = render(<AgentPanel />);
const entry = await waitFor(() => {
const el = container.querySelector(
'[data-testid="agent-entry-73_resume_test:coder-1"]',
);
expect(el).toBeInTheDocument();
return el as HTMLElement;
});
const expandButton = screen.getByRole("button", { name: "▶" });
// Expand
await userEvent.click(expandButton);
expect(entry.style.animationPlayState).toBe("paused");
// Collapse
await userEvent.click(expandButton);
expect(entry.style.animationPlayState).toBe("running");
});
describe("removes the agent entry after 60s", () => {
beforeEach(() => {
vi.useFakeTimers({ shouldAdvanceTime: true });
});
afterEach(() => {
vi.useRealTimers();
});
it("removes the agent entry after the 60-second fade completes", async () => {
const agentList: AgentInfo[] = [
{
story_id: "73_remove_test",
agent_name: "coder-1",
status: "completed",
session_id: null,
worktree_path: null,
base_branch: null,
},
];
mockedAgents.listAgents.mockResolvedValue(agentList);
const { container } = render(<AgentPanel />);
// With fake timers active, waitFor's polling setInterval never fires.
// Use act to flush pending promises and React state updates instead.
await act(async () => {
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
});
expect(
container.querySelector(
'[data-testid="agent-entry-73_remove_test:coder-1"]',
),
).toBeInTheDocument();
// Advance timers by 60 seconds and flush React state updates
await act(async () => {
vi.advanceTimersByTime(60_000);
});
// Entry should be removed
expect(
container.querySelector(
'[data-testid="agent-entry-73_remove_test:coder-1"]',
),
).not.toBeInTheDocument();
});
container.querySelector('[data-testid^="agent-entry-"]'),
).not.toBeInTheDocument();
});
});