import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { calculateVirtualGridColumnCount, calculateVirtualGridRowWindowLimit, chunkVirtualGridItems, } from "../src/components/common/virtual-grid.ts"; const viewSource = readSource("../src/views/agents/AgentRunsView.vue"); const inspectorSource = readSource("../src/components/agents/AgentRunInspector.vue"); const inspectorPanelSource = readSource("../src/components/console/InspectorPanel.vue"); const mindMapSource = readSource("../src/components/agents/AgentRunMindMap.vue"); const virtualGridSource = readSource("../src/components/common/VirtualGrid.vue"); const styleSource = readSource("../src/styles/agent-observer.css"); test("VirtualGrid calculates three responsive column tiers", () => { assert.equal(calculateVirtualGridColumnCount(1920, 360, 3, 10), 3); assert.equal(calculateVirtualGridColumnCount(960, 360, 3, 10), 2); assert.equal(calculateVirtualGridColumnCount(390, 360, 3, 10), 1); }); test("VirtualGrid chunks stable rows and bounds the rendered row window", () => { const items = Array.from({ length: 10 }, (_, index) => ({ id: `run-${index}` })); const rows = chunkVirtualGridItems(items, 3, (item) => item.id); assert.deepEqual(rows.map((row) => row.items.length), [3, 3, 3, 1]); assert.deepEqual(rows.map((row) => row.startIndex), [0, 3, 6, 9]); assert.equal(rows[0]?.key, "0:run-0|run-1|run-2"); assert.equal(calculateVirtualGridRowWindowLimit(12, 3), 4); assert.equal(calculateVirtualGridRowWindowLimit(12, 1), 12); assert.match(virtualGridSource, /ResizeObserver/); assert.match(virtualGridSource, /:window-limit="rowWindowLimit"/); assert.match(virtualGridSource, /role="listitem"/); }); test("AgentRun table exposes the eight-column product contract", () => { const headers = [ "状态", "Run / Task / Command identity", "Project / Workspace", "Agent / Profile", "阶段", "活动 / 耗时", "Runner / Node / Lane", "结果 / 错误", ]; let previousIndex = -1; for (const header of headers) { const index = viewSource.indexOf(`role="columnheader">${header}`); assert.ok(index > previousIndex, `missing or unordered table header: ${header}`); previousIndex = index; } }); test("AgentRun page reuses shared status, viewer and embedded inspector contracts", () => { assert.match(viewSource, /import StatusStrip/); assert.match(viewSource, / { assert.match(styleSource, /@media \(max-width: 1100px\)/); assert.match(styleSource, /agent-observer-secondary-filters \{ order: 5; flex: 1 0 100%; \}/); assert.match(styleSource, /@media \(max-width: 600px\)/); assert.match(styleSource, /agent-observer-secondary-filters,[\s\S]*agent-observer-density \{ display: none; \}/); assert.match(styleSource, /agent-observer-filter-trigger \{ grid-column: 2; display: inline-flex; \}/); assert.match(styleSource, /agent-observer-segment \{ grid-column: 1 \/ -1; width: 100%; \}/); assert.match(styleSource, /@media \(prefers-reduced-motion: reduce\)/); assert.match(viewSource, /class="agent-observer-search"/); assert.match(viewSource, /class="agent-observer-primary-status"/); assert.match(viewSource, /class="agent-observer-segment" role="group" aria-label="视图切换"/); assert.match(viewSource, /class="agent-observer-stream"/); }); test("AgentRun workspace always owns the remaining grid row", () => { assert.match(styleSource, /agent-observer-page \{[\s\S]*?height: 100%;/); assert.match(styleSource, /agent-observer-workspace \{ grid-row: -2 \/ -1;/); }); test("AgentRun mind map always owns the flexible tree row", () => { assert.match(styleSource, /agent-observer-tree > \.agent-mind-map \{ grid-row: -2 \/ -1; \}/); }); test("AgentRun mind map connects custom nodes through Vue Flow handles", () => { assert.match(mindMapSource, /import \{ Handle, MarkerType, Position, VueFlow/); assert.match(mindMapSource, //); assert.match(mindMapSource, //); assert.match(styleSource, /agent-mind-map-flow \.vue-flow__handle/); assert.match(styleSource, /agent-mind-map-flow \.vue-flow__edge-path \{[^}]*vector-effect: non-scaling-stroke;/); assert.match(styleSource, /agent-mind-map-flow \.vue-flow__edge-path \{[^}]*stroke: var\(--console-graphite-500\);/); assert.match(styleSource, /agent-mind-map-flow \.vue-flow__arrowhead \{ fill: var\(--console-graphite-500\); \}/); }); test("AgentRun inspector keeps chrome fixed and scrolls only its body", () => { assert.match(styleSource, /agent-observer-inspector \{[^}]*overflow: hidden;/); assert.match(styleSource, /agent-observer-inspector > \.operations-inspector-panel \{[^}]*grid-template-rows: auto auto minmax\(0, 1fr\) auto;/); assert.match(styleSource, /agent-observer-inspector \.operations-inspector-body \{[^}]*overflow: auto;/); }); function readSource(relativePath: string): string { return readFileSync(new URL(relativePath, import.meta.url), "utf8"); }