New web/ SPA (React 18 + Vite 5 + TypeScript + React Flow + dagre, all pinned, no CDN). Dark, three-pane layout: summary top bar, filterable task list, center auto-laid pipeline map, and a right drawer showing a selected task's history through each node (timeline with timestamps/duration/cost + Q&A/verdicts/plan). Map nodes color by live state, loop-backs render dashed, clicking a node filters the list, selecting a task highlights its path. Polls /api/state every 4s; topology fetched once. dist/ is gitignored (built on the Mac, rsynced).
74 lines
2.7 KiB
TypeScript
74 lines
2.7 KiB
TypeScript
import { fireEvent, render, screen } from "@testing-library/react";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { TaskList } from "./TaskList";
|
|
import type { StateResponse, Topology } from "../api";
|
|
|
|
const TOPO: Topology = {
|
|
trees: [
|
|
{ id: "core", label: "Core" },
|
|
{ id: "sdlc", label: "SDLC" },
|
|
],
|
|
nodes: [
|
|
{ id: "intake", label: "Intake", agent: "coordinator", tree: "core", kind: "phase", gated: false },
|
|
{ id: "plan", label: "Plan", agent: "Claude", tree: "sdlc", kind: "phase", gated: false },
|
|
],
|
|
edges: [],
|
|
};
|
|
|
|
function makeState(): StateResponse {
|
|
return {
|
|
ok: true,
|
|
error: null,
|
|
generated_at: "now",
|
|
db_path: "x",
|
|
tasks: [
|
|
{ thread_id: "aaaa1111", short_id: "aaaa1111", task: "remediate CVE", current_phase: "plan", status: "active", waiting: false, waiting_since: null },
|
|
{ thread_id: "bbbb2222", short_id: "bbbb2222", task: "fix login bug", current_phase: "intake", status: "parked", waiting: false, waiting_since: null },
|
|
],
|
|
waiting: [],
|
|
question_counts: {},
|
|
summary: { active: 1, parked: 1, waiting: 0, open_questions: 0, total: 2 },
|
|
budget: { available: false },
|
|
nodes: {},
|
|
};
|
|
}
|
|
|
|
describe("TaskList", () => {
|
|
it("renders all tasks", () => {
|
|
render(
|
|
<TaskList state={makeState()} topology={TOPO} selectedId={null} nodeFilter={null}
|
|
onSelect={() => {}} onClearNodeFilter={() => {}} />,
|
|
);
|
|
expect(screen.getByText("remediate CVE")).toBeInTheDocument();
|
|
expect(screen.getByText("fix login bug")).toBeInTheDocument();
|
|
});
|
|
|
|
it("filters by status", () => {
|
|
render(
|
|
<TaskList state={makeState()} topology={TOPO} selectedId={null} nodeFilter={null}
|
|
onSelect={() => {}} onClearNodeFilter={() => {}} />,
|
|
);
|
|
fireEvent.change(screen.getByLabelText("Filter by status"), { target: { value: "parked" } });
|
|
expect(screen.queryByText("remediate CVE")).not.toBeInTheDocument();
|
|
expect(screen.getByText("fix login bug")).toBeInTheDocument();
|
|
});
|
|
|
|
it("honors a node filter (map click)", () => {
|
|
render(
|
|
<TaskList state={makeState()} topology={TOPO} selectedId={null} nodeFilter="plan"
|
|
onSelect={() => {}} onClearNodeFilter={() => {}} />,
|
|
);
|
|
expect(screen.getByText("remediate CVE")).toBeInTheDocument();
|
|
expect(screen.queryByText("fix login bug")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("fires onSelect when a row is clicked", () => {
|
|
const onSelect = vi.fn();
|
|
render(
|
|
<TaskList state={makeState()} topology={TOPO} selectedId={null} nodeFilter={null}
|
|
onSelect={onSelect} onClearNodeFilter={() => {}} />,
|
|
);
|
|
fireEvent.click(screen.getByText("remediate CVE"));
|
|
expect(onSelect).toHaveBeenCalledWith("aaaa1111");
|
|
});
|
|
});
|