agent-ecosystem/test/renderer/features/agent-graph/activityLane.test.ts
2026-04-13 16:18:14 +03:00

102 lines
3.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
ACTIVITY_LANE,
findActivityItemAt,
getActivityAnchorScreenPlacement,
getActivityAnchorTarget,
getActivityLaneBounds,
getVisibleActivityWindow,
} from '../../../../packages/agent-graph/src/layout/activityLane';
import type { GraphActivityItem, GraphNode } from '@claude-teams/agent-graph';
function createItems(count: number): GraphActivityItem[] {
return Array.from({ length: count }, (_, index) => ({
id: `item-${index + 1}`,
kind: 'inbox_message',
timestamp: `2026-04-13T12:00:0${index}Z`,
title: `Item ${index + 1}`,
}));
}
describe('activity lane helpers', () => {
it('keeps the newest visible window in newest-first order', () => {
const window = getVisibleActivityWindow(createItems(6));
expect(window.items.map((item) => item.id)).toEqual(['item-1', 'item-2', 'item-3']);
expect(window.overflowCount).toBe(3);
});
it('places the lead lane to the left and member lane to the right', () => {
const leadTarget = getActivityAnchorTarget({ nodeX: 100, nodeY: 80, nodeKind: 'lead' });
const memberTarget = getActivityAnchorTarget({ nodeX: 100, nodeY: 80, nodeKind: 'member' });
const memberLeftOfLeadTarget = getActivityAnchorTarget({
nodeX: 80,
nodeY: 80,
nodeKind: 'member',
leadX: 100,
});
expect(leadTarget.x).toBeLessThan(100);
expect(memberTarget.x).toBeGreaterThan(100);
expect(memberLeftOfLeadTarget.x).toBeLessThan(80);
expect(leadTarget.y).toBeLessThan(80);
expect(memberTarget.y).toBeLessThan(80);
});
it('hits visible activity pills in the owner lane', () => {
const node: GraphNode = {
id: 'member:team:alice',
kind: 'member',
label: 'alice',
state: 'active',
x: 100,
y: 80,
activityItems: createItems(3),
domainRef: { kind: 'member', teamName: 'team', memberName: 'alice' },
};
const anchor = getActivityAnchorTarget({ nodeX: 100, nodeY: 80, nodeKind: 'member' });
const bounds = getActivityLaneBounds(anchor.x, anchor.y);
const hit = findActivityItemAt(
bounds.left + ACTIVITY_LANE.width / 2,
bounds.top + ACTIVITY_LANE.headerHeight + ACTIVITY_LANE.itemHeight / 2,
[node]
);
expect(hit?.ownerNodeId).toBe(node.id);
expect(hit?.item.id).toBe('item-1');
});
it('keeps activity lane at its world-space position instead of clamping to the viewport', () => {
const placement = getActivityAnchorScreenPlacement({
anchorX: 40,
anchorY: 60,
cameraX: 0,
cameraY: 0,
zoom: 1,
viewportWidth: 800,
viewportHeight: 600,
});
expect(placement.x).toBe(40 - ACTIVITY_LANE.width / 2);
expect(placement.y).toBe(60 - (ACTIVITY_LANE.headerHeight + ACTIVITY_LANE.maxVisibleItems * ACTIVITY_LANE.rowHeight + ACTIVITY_LANE.overflowHeight) / 2);
expect(placement.visible).toBe(true);
});
it('stays visible when only part of the lane is inside the viewport', () => {
const placement = getActivityAnchorScreenPlacement({
anchorX: -40,
anchorY: 40,
cameraX: 0,
cameraY: 0,
zoom: 1,
viewportWidth: 800,
viewportHeight: 600,
});
expect(placement.x).toBeLessThan(0);
expect(placement.visible).toBe(true);
});
});