195 lines
5.4 KiB
TypeScript
195 lines
5.4 KiB
TypeScript
import React, { act } from 'react';
|
|
import { createRoot } from 'react-dom/client';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { GraphBlockingEdgePopover } from '@features/agent-graph/renderer/ui/GraphBlockingEdgePopover';
|
|
import { useStore } from '@renderer/store';
|
|
|
|
import type { GraphEdge, GraphNode } from '@claude-teams/agent-graph';
|
|
|
|
vi.mock('@renderer/components/ui/badge', () => ({
|
|
Badge: ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement('span', null, children),
|
|
}));
|
|
|
|
vi.mock('@renderer/components/ui/button', () => ({
|
|
Button: ({
|
|
children,
|
|
onClick,
|
|
}: {
|
|
children: React.ReactNode;
|
|
onClick?: () => void;
|
|
}) => React.createElement('button', { type: 'button', onClick }, children),
|
|
}));
|
|
|
|
const sourceNode: GraphNode = {
|
|
id: 'task:my-team:overflow:alice:todo',
|
|
kind: 'task',
|
|
label: '+2',
|
|
state: 'waiting',
|
|
ownerId: 'member:my-team:alice',
|
|
taskStatus: 'pending',
|
|
reviewState: 'none',
|
|
isOverflowStack: true,
|
|
overflowCount: 2,
|
|
overflowTaskIds: ['task-hidden-1', 'task-hidden-2'],
|
|
domainRef: {
|
|
kind: 'task_overflow',
|
|
teamName: 'my-team',
|
|
ownerMemberName: 'alice',
|
|
columnKey: 'todo',
|
|
},
|
|
};
|
|
|
|
const targetNode: GraphNode = {
|
|
id: 'task:my-team:task-visible',
|
|
kind: 'task',
|
|
label: '#8',
|
|
displayId: '#8',
|
|
sublabel: 'Visible blocked task',
|
|
state: 'waiting',
|
|
ownerId: 'member:my-team:bob',
|
|
taskStatus: 'pending',
|
|
reviewState: 'none',
|
|
domainRef: { kind: 'task', teamName: 'my-team', taskId: 'task-visible' },
|
|
};
|
|
|
|
const edge: GraphEdge = {
|
|
id: 'edge:block:test',
|
|
source: sourceNode.id,
|
|
target: targetNode.id,
|
|
type: 'blocking',
|
|
aggregateCount: 2,
|
|
sourceTaskIds: ['task-hidden-1', 'task-hidden-2'],
|
|
targetTaskIds: ['task-visible'],
|
|
};
|
|
|
|
describe('GraphBlockingEdgePopover', () => {
|
|
afterEach(() => {
|
|
document.body.innerHTML = '';
|
|
useStore.setState({
|
|
selectedTeamName: null,
|
|
selectedTeamData: null,
|
|
teamDataCacheByName: {},
|
|
} as never);
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it('renders the participating hidden tasks for aggregated overflow blockers', async () => {
|
|
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
|
useStore.setState({
|
|
selectedTeamName: 'my-team',
|
|
selectedTeamData: {
|
|
teamName: 'my-team',
|
|
config: { name: 'My Team', members: [], projectPath: '/repo' },
|
|
tasks: [
|
|
{
|
|
id: 'task-hidden-1',
|
|
displayId: '#1',
|
|
subject: 'Hidden blocker one',
|
|
owner: 'alice',
|
|
status: 'pending',
|
|
reviewState: 'none',
|
|
},
|
|
{
|
|
id: 'task-hidden-2',
|
|
displayId: '#2',
|
|
subject: 'Hidden blocker two',
|
|
owner: 'alice',
|
|
status: 'pending',
|
|
reviewState: 'none',
|
|
},
|
|
{
|
|
id: 'task-visible',
|
|
displayId: '#8',
|
|
subject: 'Visible blocked task',
|
|
owner: 'bob',
|
|
status: 'pending',
|
|
reviewState: 'none',
|
|
},
|
|
],
|
|
members: [],
|
|
messages: [],
|
|
kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
|
|
processes: [],
|
|
},
|
|
teamDataCacheByName: {
|
|
'my-team': {
|
|
teamName: 'my-team',
|
|
config: { name: 'My Team', members: [], projectPath: '/repo' },
|
|
tasks: [
|
|
{
|
|
id: 'task-hidden-1',
|
|
displayId: '#1',
|
|
subject: 'Hidden blocker one',
|
|
owner: 'alice',
|
|
status: 'pending',
|
|
reviewState: 'none',
|
|
},
|
|
{
|
|
id: 'task-hidden-2',
|
|
displayId: '#2',
|
|
subject: 'Hidden blocker two',
|
|
owner: 'alice',
|
|
status: 'pending',
|
|
reviewState: 'none',
|
|
},
|
|
{
|
|
id: 'task-visible',
|
|
displayId: '#8',
|
|
subject: 'Visible blocked task',
|
|
owner: 'bob',
|
|
status: 'pending',
|
|
reviewState: 'none',
|
|
},
|
|
],
|
|
members: [],
|
|
messages: [],
|
|
kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
|
|
processes: [],
|
|
},
|
|
},
|
|
} as never);
|
|
|
|
const onOpenTaskDetail = vi.fn();
|
|
const host = document.createElement('div');
|
|
document.body.appendChild(host);
|
|
const root = createRoot(host);
|
|
|
|
await act(async () => {
|
|
root.render(
|
|
React.createElement(GraphBlockingEdgePopover, {
|
|
teamName: 'my-team',
|
|
edge,
|
|
sourceNode,
|
|
targetNode,
|
|
onClose: vi.fn(),
|
|
onSelectNode: vi.fn(),
|
|
onOpenTaskDetail,
|
|
})
|
|
);
|
|
await Promise.resolve();
|
|
});
|
|
|
|
expect(host.textContent).toContain('Blocking hidden tasks');
|
|
expect(host.textContent).toContain('#1 - Hidden blocker one');
|
|
expect(host.textContent).toContain('#2 - Hidden blocker two');
|
|
|
|
const hiddenTaskButton = Array.from(host.querySelectorAll('button')).find((button) =>
|
|
button.textContent?.includes('#1 - Hidden blocker one')
|
|
);
|
|
expect(hiddenTaskButton).toBeTruthy();
|
|
|
|
await act(async () => {
|
|
hiddenTaskButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
await Promise.resolve();
|
|
});
|
|
|
|
expect(onOpenTaskDetail).toHaveBeenCalledWith('task-hidden-1');
|
|
|
|
await act(async () => {
|
|
root.unmount();
|
|
await Promise.resolve();
|
|
});
|
|
});
|
|
});
|