Files
pikasTech-HWLAB/web/hwlab-cloud-web/scripts/agent-observer-ui-contract.test.ts
T

112 lines
5.7 KiB
TypeScript

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, /<StatusStrip :items="statusItems"/);
assert.match(viewSource, /import VirtualGrid/);
assert.match(viewSource, /<VirtualGrid/);
assert.match(viewSource, /<AgentRunInspector v-if="observer\.selectedRun" embedded/);
assert.match(inspectorSource, /import ContentViewer/);
assert.match(inspectorSource, /mode="log"/);
assert.match(inspectorSource, /mode="json"/);
assert.doesNotMatch(inspectorSource, /<pre/);
assert.match(inspectorPanelSource, /embedded\?: boolean/);
assert.match(inspectorPanelSource, /v-if="!embedded"/);
});
test("AgentRun command bar preserves compact desktop and mobile primary controls", () => {
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, /<Handle v-if="data\.row\.parentId" type="target" :position="Position\.Left" \/>/);
assert.match(mindMapSource, /<Handle v-if="data\.row\.childCount > 0" type="source" :position="Position\.Right" \/>/);
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");
}