Files
apes-sidebar/tests/tasks-graph-layout.spec.ts
T
Menghuan1918 fdc4520080 feat(client): unified tasks view model + graph layout + api methods
- tasks-model.ts: one pre-order node list for graph AND tree modes —
  catalog walking, workflow-run attachment with member re-parenting and
  synthesis, team enrichment, per-parent settled-leaf fold aggregates.
- tasks-graph-layout.ts: dependency-free tidy-tree layered layout
  (bundle-purity: no graph library in the core bundle).
- api.ts: workflowsList/teamsView/teamsTaskCreate/teamsTaskUpdate with
  the wire result types mirrored from the host routes.
- 11 unit tests across model derivation, edge classification, fold, and
  layout geometry.
2026-09-27 22:18:14 +08:00

64 lines
2.4 KiB
TypeScript

/**
* Unit tests for the layered graph layout (layoutTasksGraph): parent
* centering over children bands, depth rows, and live-line heights.
*/
import { describe, expect, it } from 'vitest'
import {
GRAPH_GAP_Y,
GRAPH_LIVE_H,
GRAPH_NODE_H,
GRAPH_NODE_W,
GRAPH_PAD,
layoutTasksGraph,
} from '../src/client/tasks-graph-layout.ts'
import type { TasksAgentNode, TasksNode } from '../src/client/tasks-model.ts'
/** An agent node. */
function agent(id: string, parentId?: string, live = false): TasksAgentNode {
return {
kind: 'agent', id, ...(parentId === undefined ? {} : { parentId }),
label: id, state: 'running', activity: 'running', current: false,
...(live ? { live: { text: 'x' } } : {}),
}
}
describe('layoutTasksGraph', () => {
it('places the root on row 0 and children one row below', () => {
const nodes: TasksNode[] = [agent('root'), agent('a', 'root'), agent('b', 'root')]
const layout = layoutTasksGraph(nodes)
expect(layout.boxes.get('root')).toMatchObject({ y: GRAPH_PAD, h: GRAPH_NODE_H })
const rowY = GRAPH_PAD + GRAPH_NODE_H + GRAPH_LIVE_H + GRAPH_GAP_Y
expect(layout.boxes.get('a')?.y).toBe(rowY)
expect(layout.boxes.get('b')?.y).toBe(rowY)
})
it('centers a parent over its children band', () => {
const nodes: TasksNode[] = [agent('root'), agent('a', 'root'), agent('b', 'root')]
const layout = layoutTasksGraph(nodes)
const a = layout.boxes.get('a')
const b = layout.boxes.get('b')
const root = layout.boxes.get('root')
expect(a).toBeDefined(); expect(b).toBeDefined(); expect(root).toBeDefined()
const bandCenter = ((a!.x + b!.x + GRAPH_NODE_W) / 2)
expect(root!.x + GRAPH_NODE_W / 2).toBeCloseTo(bandCenter, 5)
})
it('widens the canvas for deep/wide forests and grows live rows', () => {
const nodes: TasksNode[] = [
agent('root'), agent('a', 'root', true),
agent('a1', 'a'), agent('a2', 'a'), agent('a3', 'a'),
]
const layout = layoutTasksGraph(nodes)
expect(layout.boxes.get('a')?.h).toBe(GRAPH_NODE_H + GRAPH_LIVE_H)
expect(layout.width).toBeGreaterThan(3 * GRAPH_NODE_W)
expect(layout.height).toBeGreaterThan(2 * (GRAPH_NODE_H + GRAPH_GAP_Y))
})
it('gives every node a box even with a defensive orphan', () => {
const nodes: TasksNode[] = [agent('root'), agent('ghost', 'missing')]
const layout = layoutTasksGraph(nodes)
expect(layout.boxes.get('root')).toBeDefined()
expect(layout.boxes.get('ghost')).toBeDefined()
})
})