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

190 lines
5.1 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { buildFocusState } from '../../../../packages/agent-graph/src/ui/buildFocusState';
import type { GraphEdge, GraphNode } from '@claude-teams/agent-graph';
const leadNode: GraphNode = {
id: 'lead:my-team',
kind: 'lead',
label: 'My Team',
state: 'active',
domainRef: { kind: 'lead', teamName: 'my-team', memberName: 'team-lead' },
};
const aliceNode: GraphNode = {
id: 'member:my-team:alice',
kind: 'member',
label: 'alice',
state: 'active',
currentTaskId: 'task-current',
domainRef: { kind: 'member', teamName: 'my-team', memberName: 'alice' },
};
const bobNode: GraphNode = {
id: 'member:my-team:bob',
kind: 'member',
label: 'bob',
state: 'idle',
currentTaskId: 'task-current',
domainRef: { kind: 'member', teamName: 'my-team', memberName: 'bob' },
};
const blockerNode: GraphNode = {
id: 'task:my-team:blocker',
kind: 'task',
label: '#1',
state: 'active',
ownerId: 'member:my-team:alice',
taskStatus: 'in_progress',
reviewState: 'none',
sublabel: 'Blocker',
domainRef: { kind: 'task', teamName: 'my-team', taskId: 'blocker' },
};
const reviewTaskNode: GraphNode = {
id: 'task:my-team:review',
kind: 'task',
label: '#2',
state: 'active',
ownerId: 'member:my-team:alice',
taskStatus: 'in_progress',
reviewState: 'review',
reviewerName: 'bob',
reviewMode: 'assigned',
sublabel: 'Review task',
domainRef: { kind: 'task', teamName: 'my-team', taskId: 'review' },
};
const overflowNode: GraphNode = {
id: 'task:my-team:overflow:alice:review',
kind: 'task',
label: '+3',
state: 'waiting',
ownerId: 'member:my-team:alice',
taskStatus: 'in_progress',
reviewState: 'review',
isOverflowStack: true,
overflowCount: 3,
overflowTaskIds: ['hidden-1', 'hidden-2', 'hidden-3'],
domainRef: {
kind: 'task_overflow',
teamName: 'my-team',
ownerMemberName: 'alice',
columnKey: 'review',
},
};
const edges: GraphEdge[] = [
{
id: 'edge:parent:lead:alice',
source: leadNode.id,
target: aliceNode.id,
type: 'parent-child',
},
{
id: 'edge:parent:lead:bob',
source: leadNode.id,
target: bobNode.id,
type: 'parent-child',
},
{
id: 'edge:own:alice:blocker',
source: aliceNode.id,
target: blockerNode.id,
type: 'ownership',
},
{
id: 'edge:own:alice:review',
source: aliceNode.id,
target: reviewTaskNode.id,
type: 'ownership',
},
{
id: 'edge:own:alice:overflow',
source: aliceNode.id,
target: overflowNode.id,
type: 'ownership',
},
{
id: 'edge:block:blocker:review',
source: blockerNode.id,
target: reviewTaskNode.id,
type: 'blocking',
},
];
const nodes = [leadNode, aliceNode, bobNode, blockerNode, reviewTaskNode, overflowNode];
describe('buildFocusState', () => {
it('focuses task selection on its owner, reviewer, direct blockers, and connecting edges', () => {
const focus = buildFocusState(reviewTaskNode.id, null, nodes, edges);
expect(Array.from(focus.focusNodeIds ?? []).sort()).toEqual(
[
leadNode.id,
aliceNode.id,
bobNode.id,
blockerNode.id,
reviewTaskNode.id,
].sort()
);
expect(focus.focusEdgeIds).toEqual(
new Set([
'edge:parent:lead:alice',
'edge:parent:lead:bob',
'edge:own:alice:blocker',
'edge:own:alice:review',
'edge:block:blocker:review',
])
);
});
it('includes review-assigned tasks and owned overflow stacks when focusing a member', () => {
const focus = buildFocusState(bobNode.id, null, nodes, edges);
expect(focus.focusNodeIds?.has(bobNode.id)).toBe(true);
expect(focus.focusNodeIds?.has(reviewTaskNode.id)).toBe(true);
expect(focus.focusNodeIds?.has(aliceNode.id)).toBe(true);
expect(focus.focusEdgeIds?.has('edge:parent:lead:bob')).toBe(true);
expect(focus.focusEdgeIds?.has('edge:own:alice:review')).toBe(true);
const aliceFocus = buildFocusState(aliceNode.id, null, nodes, edges);
expect(aliceFocus.focusNodeIds?.has(overflowNode.id)).toBe(true);
});
it('focuses a lead on direct neighbors only', () => {
const focus = buildFocusState(leadNode.id, null, nodes, edges);
expect(focus.focusNodeIds).toEqual(
new Set([leadNode.id, aliceNode.id, bobNode.id])
);
expect(focus.focusEdgeIds).toEqual(
new Set(['edge:parent:lead:alice', 'edge:parent:lead:bob'])
);
});
it('does not enable global dimming for overflow stack selections', () => {
const focus = buildFocusState(overflowNode.id, null, nodes, edges);
expect(focus.focusNodeIds).toBeNull();
expect(focus.focusEdgeIds).toBeNull();
});
it('focuses the connected blocking chain when a blocking edge is selected', () => {
const focus = buildFocusState(null, 'edge:block:blocker:review', nodes, edges);
expect(focus.focusNodeIds).toEqual(
new Set([leadNode.id, aliceNode.id, bobNode.id, blockerNode.id, reviewTaskNode.id])
);
expect(focus.focusEdgeIds).toEqual(
new Set([
'edge:block:blocker:review',
'edge:own:alice:blocker',
'edge:own:alice:review',
'edge:parent:lead:alice',
'edge:parent:lead:bob',
])
);
});
});