agent-ecosystem/test/renderer/features/agent-graph/edgeHitDetection.test.ts

85 lines
2.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
collectInteractiveEdgesInViewport,
findEdgeAt,
getEdgeMidpoint,
} from '../../../../packages/agent-graph/src/canvas/hit-detection';
import type { GraphEdge, GraphNode } from '@claude-teams/agent-graph';
function makeNode(id: string, x: number, y: number): GraphNode {
return {
id,
kind: id.startsWith('task') ? 'task' : 'member',
label: id,
state: 'idle',
x,
y,
domainRef:
id.startsWith('task')
? { kind: 'task', teamName: 'my-team', taskId: id }
: { kind: 'member', teamName: 'my-team', memberName: id },
} as GraphNode;
}
describe('edge hit detection', () => {
it('detects blocking edges near the curve midpoint', () => {
const nodes = [
makeNode('member:alice', 0, 0),
makeNode('task:1', 160, 90),
];
const nodeMap = new Map(nodes.map((node) => [node.id, node] as const));
const edge: GraphEdge = {
id: 'edge:blocking',
source: 'member:alice',
target: 'task:1',
type: 'blocking',
};
const midpoint = getEdgeMidpoint(edge, nodeMap);
expect(midpoint).not.toBeNull();
expect(findEdgeAt(midpoint!.x, midpoint!.y, [edge], nodeMap)).toBe('edge:blocking');
});
it('prefers the closest edge when multiple curves overlap', () => {
const nodes = [
makeNode('member:alice', 0, 0),
makeNode('task:1', 160, 90),
makeNode('task:2', 160, 150),
];
const nodeMap = new Map(nodes.map((node) => [node.id, node] as const));
const edges: GraphEdge[] = [
{ id: 'edge:1', source: 'member:alice', target: 'task:1', type: 'ownership' },
{ id: 'edge:2', source: 'member:alice', target: 'task:2', type: 'ownership' },
];
const midpoint = getEdgeMidpoint(edges[0], nodeMap);
expect(midpoint).not.toBeNull();
expect(findEdgeAt(midpoint!.x, midpoint!.y, edges, nodeMap)).toBe('edge:1');
});
it('only keeps visible blocking edges as interactive hit-test candidates', () => {
const nodes = [
makeNode('task:blocker', 0, 0),
makeNode('task:blocked', 180, 90),
makeNode('task:offscreen-a', 1200, 1200),
makeNode('task:offscreen-b', 1360, 1280),
];
const nodeMap = new Map(nodes.map((node) => [node.id, node] as const));
const edges: GraphEdge[] = [
{ id: 'edge:blocking:visible', source: 'task:blocker', target: 'task:blocked', type: 'blocking' },
{ id: 'edge:blocking:hidden', source: 'task:offscreen-a', target: 'task:offscreen-b', type: 'blocking' },
{ id: 'edge:ownership', source: 'task:blocker', target: 'task:blocked', type: 'ownership' },
];
const interactive = collectInteractiveEdgesInViewport(edges, nodeMap, {
left: -200,
top: -200,
right: 400,
bottom: 260,
});
expect(interactive.map((edge) => edge.id)).toEqual(['edge:blocking:visible']);
});
});