feat(frontend): repliage par défaut des background tasks par agent (#87)
WebWorkspace replie désormais par défaut la liste des background tasks par agent dans le panneau Work ; QA a ajouté les cas de test associés dans WebWorkspaceLive.test.tsx (887 tests verts, tsc --noEmit clean). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -57,6 +57,32 @@ function renderPaired(gateways: Gateways) {
|
||||
}
|
||||
|
||||
const AGENT_IDLE = { agentId: "a1", name: "Archi", profileId: "p1", busy: { state: "idle" as const }, tickets: [] };
|
||||
const AGENT_FRONT = { agentId: "a2", name: "Front", profileId: "p2", busy: { state: "idle" as const }, tickets: [] };
|
||||
|
||||
function backgroundTask(
|
||||
taskId: string,
|
||||
ownerAgentId: string,
|
||||
updatedAtMs = 1,
|
||||
): BackgroundCompletion {
|
||||
return {
|
||||
taskId,
|
||||
ownerAgentId,
|
||||
projectId: "p",
|
||||
kind: "shell",
|
||||
status: "running",
|
||||
exitCode: null,
|
||||
summary: null,
|
||||
stdoutTail: null,
|
||||
stderrTail: null,
|
||||
updatedAtMs,
|
||||
};
|
||||
}
|
||||
|
||||
async function openLivePanel(gateways: Gateways) {
|
||||
renderPaired(gateways);
|
||||
fireEvent.click(await screen.findByText("Demo"));
|
||||
return screen.findByTestId("web-workstate");
|
||||
}
|
||||
|
||||
describe("WebWorkspace live surfaces (F5)", () => {
|
||||
it("refreshes the work-state on a relevant event.domain event", async () => {
|
||||
@ -83,23 +109,14 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
});
|
||||
|
||||
it("renders background tasks and wires cancel through the gateway", async () => {
|
||||
const task: BackgroundCompletion = {
|
||||
taskId: "bt-1",
|
||||
ownerAgentId: "a1",
|
||||
projectId: "p",
|
||||
kind: "shell",
|
||||
status: "running",
|
||||
exitCode: null,
|
||||
summary: null,
|
||||
stdoutTail: null,
|
||||
stderrTail: null,
|
||||
updatedAtMs: 1,
|
||||
};
|
||||
const task = backgroundTask("bt-1", "a1");
|
||||
const { gateways } = await seeded(state([{ ...AGENT_IDLE, backgroundTasks: [task] }]));
|
||||
const cancelSpy = vi.spyOn(gateways.workState, "cancelBackgroundTask");
|
||||
renderPaired(gateways);
|
||||
await openLivePanel(gateways);
|
||||
|
||||
fireEvent.click(await screen.findByText("Demo"));
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: "Afficher les background tasks de Archi" }),
|
||||
);
|
||||
const tasks = await screen.findByLabelText("Archi background tasks");
|
||||
expect(tasks.textContent).toContain("Running");
|
||||
expect(tasks.textContent).toContain("shell");
|
||||
@ -108,6 +125,170 @@ describe("WebWorkspace live surfaces (F5)", () => {
|
||||
await waitFor(() => expect(cancelSpy).toHaveBeenCalledWith("bt-1"));
|
||||
});
|
||||
|
||||
it("keeps background task groups collapsed by default and toggles them per agent", async () => {
|
||||
const { gateways } = await seeded(state([
|
||||
{ ...AGENT_IDLE, backgroundTasks: [backgroundTask("bt-a", "a1")] },
|
||||
{ ...AGENT_FRONT, backgroundTasks: [backgroundTask("bt-f", "a2")] },
|
||||
]));
|
||||
await openLivePanel(gateways);
|
||||
|
||||
const archiToggle = await screen.findByRole("button", {
|
||||
name: "Afficher les background tasks de Archi",
|
||||
});
|
||||
const frontToggle = await screen.findByRole("button", {
|
||||
name: "Afficher les background tasks de Front",
|
||||
});
|
||||
expect(archiToggle.getAttribute("aria-expanded")).toBe("false");
|
||||
expect(frontToggle.getAttribute("aria-expanded")).toBe("false");
|
||||
expect(screen.queryByLabelText("Archi background tasks")).toBeNull();
|
||||
expect(screen.queryByLabelText("Front background tasks")).toBeNull();
|
||||
|
||||
fireEvent.click(archiToggle);
|
||||
expect(
|
||||
screen
|
||||
.getByRole("button", { name: "Masquer les background tasks de Archi" })
|
||||
.getAttribute("aria-expanded"),
|
||||
).toBe("true");
|
||||
expect(
|
||||
screen
|
||||
.getByRole("button", { name: "Afficher les background tasks de Front" })
|
||||
.getAttribute("aria-expanded"),
|
||||
).toBe("false");
|
||||
expect(await screen.findByLabelText("Archi background tasks")).toBeTruthy();
|
||||
expect(screen.queryByLabelText("Front background tasks")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Afficher les background tasks de Front" }));
|
||||
expect(screen.getByLabelText("Archi background tasks")).toBeTruthy();
|
||||
expect(screen.getByLabelText("Front background tasks")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("updates task counters without changing the current expanded state", async () => {
|
||||
const { gateways, projectId } = await seeded(state([
|
||||
{ ...AGENT_IDLE, backgroundTasks: [backgroundTask("bt-1", "a1")] },
|
||||
]));
|
||||
await openLivePanel(gateways);
|
||||
|
||||
const collapsedToggle = await screen.findByRole("button", {
|
||||
name: "Afficher les background tasks de Archi",
|
||||
});
|
||||
expect(collapsedToggle.getAttribute("aria-expanded")).toBe("false");
|
||||
expect(collapsedToggle.textContent).toContain("1");
|
||||
expect(screen.queryByLabelText("Archi background tasks")).toBeNull();
|
||||
|
||||
(gateways.workState as MockWorkStateGateway)._setProjectWorkState(
|
||||
projectId,
|
||||
state([
|
||||
{
|
||||
...AGENT_IDLE,
|
||||
backgroundTasks: [backgroundTask("bt-1", "a1", 1), backgroundTask("bt-2", "a1", 2)],
|
||||
},
|
||||
]),
|
||||
);
|
||||
act(() => {
|
||||
(gateways.system as MockSystemGateway).emit({
|
||||
type: "backgroundTaskChanged",
|
||||
projectId,
|
||||
taskId: "bt-2",
|
||||
agentId: "a1",
|
||||
state: "running",
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Afficher les background tasks de Archi" }).textContent,
|
||||
).toContain("2"),
|
||||
);
|
||||
expect(
|
||||
screen
|
||||
.getByRole("button", { name: "Afficher les background tasks de Archi" })
|
||||
.getAttribute("aria-expanded"),
|
||||
).toBe("false");
|
||||
expect(screen.queryByLabelText("Archi background tasks")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Afficher les background tasks de Archi" }));
|
||||
expect(await screen.findByLabelText("Archi background tasks")).toBeTruthy();
|
||||
|
||||
(gateways.workState as MockWorkStateGateway)._setProjectWorkState(
|
||||
projectId,
|
||||
state([{ ...AGENT_IDLE, backgroundTasks: [backgroundTask("bt-1", "a1", 3)] }]),
|
||||
);
|
||||
act(() => {
|
||||
(gateways.system as MockSystemGateway).emit({
|
||||
type: "backgroundTaskChanged",
|
||||
projectId,
|
||||
taskId: "bt-2",
|
||||
agentId: "a1",
|
||||
state: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Masquer les background tasks de Archi" }).textContent,
|
||||
).toContain("1"),
|
||||
);
|
||||
expect(
|
||||
screen
|
||||
.getByRole("button", { name: "Masquer les background tasks de Archi" })
|
||||
.getAttribute("aria-expanded"),
|
||||
).toBe("true");
|
||||
expect(screen.getByLabelText("Archi background tasks")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("hides and resets an open background task group when its task count reaches zero", async () => {
|
||||
const { gateways, projectId } = await seeded(state([
|
||||
{ ...AGENT_IDLE, backgroundTasks: [backgroundTask("bt-1", "a1")] },
|
||||
]));
|
||||
await openLivePanel(gateways);
|
||||
fireEvent.click(await screen.findByRole("button", {
|
||||
name: "Afficher les background tasks de Archi",
|
||||
}));
|
||||
expect(await screen.findByLabelText("Archi background tasks")).toBeTruthy();
|
||||
|
||||
(gateways.workState as MockWorkStateGateway)._setProjectWorkState(
|
||||
projectId,
|
||||
state([{ ...AGENT_IDLE, backgroundTasks: [] }]),
|
||||
);
|
||||
act(() => {
|
||||
(gateways.system as MockSystemGateway).emit({
|
||||
type: "backgroundTaskChanged",
|
||||
projectId,
|
||||
taskId: "bt-1",
|
||||
agentId: "a1",
|
||||
state: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole("button", { name: /background tasks de Archi/ })).toBeNull(),
|
||||
);
|
||||
expect(screen.queryByLabelText("Archi background tasks")).toBeNull();
|
||||
|
||||
(gateways.workState as MockWorkStateGateway)._setProjectWorkState(
|
||||
projectId,
|
||||
state([{ ...AGENT_IDLE, backgroundTasks: [backgroundTask("bt-2", "a1", 2)] }]),
|
||||
);
|
||||
act(() => {
|
||||
(gateways.system as MockSystemGateway).emit({
|
||||
type: "backgroundTaskChanged",
|
||||
projectId,
|
||||
taskId: "bt-2",
|
||||
agentId: "a1",
|
||||
state: "running",
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByRole("button", { name: "Afficher les background tasks de Archi" })
|
||||
.getAttribute("aria-expanded"),
|
||||
).toBe("false"),
|
||||
);
|
||||
expect(screen.queryByLabelText("Archi background tasks")).toBeNull();
|
||||
});
|
||||
|
||||
it("re-synchronises the read-model when the WS reconnects", async () => {
|
||||
const { gateways, projectId } = await seeded(state([AGENT_IDLE]));
|
||||
// Register a live client whose connection state we can drive.
|
||||
|
||||
Reference in New Issue
Block a user