feat(workstate): UI des délégations en file par agent (Lot B frontend)
Affiche les tickets en attente dans `ProjectWorkStatePanel` à partir du snapshot de file exposé par le read-model : type de domaine et adaptateur mock alignés sur le DTO `camelCase`, panneau enrichi (requester, aperçu de tâche, position FIFO), hook de lecture mis à jour. Tests verts : workstate.test.tsx + projects.test.tsx (2 fichiers / 17), `tsc --noEmit` OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { render, screen, waitFor, within } from "@testing-library/react";
|
||||
|
||||
import { DIProvider } from "@/app/di";
|
||||
import { MockSystemGateway, MockWorkStateGateway } from "@/adapters/mock";
|
||||
@ -49,6 +49,7 @@ describe("ProjectWorkStatePanel", () => {
|
||||
expect(await screen.findByText("Planner")).toBeTruthy();
|
||||
expect(screen.getByText("Offline")).toBeTruthy();
|
||||
expect(screen.getByText("Idle")).toBeTruthy();
|
||||
expect(screen.queryByLabelText("Planner tickets")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders a live busy agent with a short ticket", async () => {
|
||||
@ -81,6 +82,116 @@ describe("ProjectWorkStatePanel", () => {
|
||||
expect(screen.getByLabelText("busy ticket 4c65d981")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders an in-progress human ticket", async () => {
|
||||
const workState = new MockWorkStateGateway();
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
agents: [
|
||||
{
|
||||
agentId: "agent-4",
|
||||
name: "Implementer",
|
||||
profileId: "codex",
|
||||
busy: {
|
||||
state: "busy",
|
||||
ticket: "ticket-in-progress-abcdef",
|
||||
sinceMs: 789,
|
||||
},
|
||||
tickets: [
|
||||
{
|
||||
ticketId: "ticket-in-progress-abcdef",
|
||||
conversationId: "conversation-a",
|
||||
position: 0,
|
||||
status: "inProgress",
|
||||
source: { kind: "human" },
|
||||
requesterLabel: "Anthony",
|
||||
taskPreview: "Wire the workstate queue UI",
|
||||
taskLen: 36,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
renderPanel(workState);
|
||||
|
||||
const list = await screen.findByLabelText("Implementer tickets");
|
||||
expect(within(list).getByText("#1 In progress")).toBeTruthy();
|
||||
expect(within(list).getByText("Human")).toBeTruthy();
|
||||
expect(within(list).getByText("Wire the workstate queue UI")).toBeTruthy();
|
||||
expect(within(list).getByLabelText("ticket ticket-i")).toBeTruthy();
|
||||
expect(within(list).getByLabelText("9 more characters")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders two tickets in FIFO order", async () => {
|
||||
const workState = new MockWorkStateGateway();
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
agents: [
|
||||
{
|
||||
agentId: "agent-5",
|
||||
name: "Reviewer",
|
||||
profileId: "claude",
|
||||
busy: { state: "idle" },
|
||||
tickets: [
|
||||
{
|
||||
ticketId: "ticket-second-abcdef",
|
||||
conversationId: "conversation-b",
|
||||
position: 1,
|
||||
status: "queued",
|
||||
source: { kind: "agent", agentId: "agent-main-123456" },
|
||||
requesterLabel: "Main",
|
||||
taskPreview: "Then review the result",
|
||||
taskLen: 22,
|
||||
},
|
||||
{
|
||||
ticketId: "ticket-first-abcdef",
|
||||
conversationId: "conversation-a",
|
||||
position: 0,
|
||||
status: "inProgress",
|
||||
source: { kind: "agent", agentId: "agent-qa-123456" },
|
||||
requesterLabel: "QA",
|
||||
taskPreview: "Check behavior",
|
||||
taskLen: 14,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
renderPanel(workState);
|
||||
|
||||
const list = await screen.findByLabelText("Reviewer tickets");
|
||||
expect(within(list).getByText("QA (agent-qa)")).toBeTruthy();
|
||||
expect(within(list).getByText("Main (agent-ma)")).toBeTruthy();
|
||||
expect(list.textContent?.indexOf("#1 In progress")).toBeLessThan(
|
||||
list.textContent?.indexOf("#2 Queued") ?? -1,
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes mock work state without tickets", async () => {
|
||||
const workState = new MockWorkStateGateway();
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
agents: [
|
||||
{
|
||||
agentId: "agent-6",
|
||||
name: "Legacy",
|
||||
profileId: "gemini",
|
||||
busy: { state: "idle" },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await expect(workState.getProjectWorkState(PROJECT_ID)).resolves.toEqual({
|
||||
agents: [
|
||||
{
|
||||
agentId: "agent-6",
|
||||
name: "Legacy",
|
||||
profileId: "gemini",
|
||||
busy: { state: "idle" },
|
||||
tickets: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("refreshes when a relevant domain event fires", async () => {
|
||||
const workState = new MockWorkStateGateway();
|
||||
const system = new MockSystemGateway();
|
||||
@ -108,4 +219,46 @@ describe("ProjectWorkStatePanel", () => {
|
||||
expect(await screen.findByText("Responder")).toBeTruthy();
|
||||
await waitFor(() => expect(spy).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it("refreshes when delegationReady fires", async () => {
|
||||
const workState = new MockWorkStateGateway();
|
||||
const system = new MockSystemGateway();
|
||||
renderPanel(workState, system);
|
||||
|
||||
await screen.findByText("No agent work state.");
|
||||
const spy = vi.spyOn(workState, "getProjectWorkState");
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
agents: [
|
||||
{
|
||||
agentId: "agent-7",
|
||||
name: "Delegatee",
|
||||
profileId: "codex",
|
||||
busy: { state: "busy", ticket: "ticket-ready-123456", sinceMs: 123 },
|
||||
tickets: [
|
||||
{
|
||||
ticketId: "ticket-ready-123456",
|
||||
conversationId: "conversation-ready",
|
||||
position: 0,
|
||||
status: "inProgress",
|
||||
source: { kind: "agent", agentId: "agent-main-123456" },
|
||||
requesterLabel: "Main",
|
||||
taskPreview: "Handle this delegated task",
|
||||
taskLen: 26,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
system.emit({
|
||||
type: "delegationReady",
|
||||
agentId: "agent-7",
|
||||
ticket: "ticket-ready-123456",
|
||||
text: "Handle this delegated task",
|
||||
});
|
||||
|
||||
expect(await screen.findByText("Delegatee")).toBeTruthy();
|
||||
expect(screen.getByText("#1 In progress")).toBeTruthy();
|
||||
await waitFor(() => expect(spy).toHaveBeenCalled());
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user