refactor: 移除 Team 和 Swarm 协作功能
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

功能论证结论:Team(团队)和 Swarm(协作)为零后端支持的
纯前端 localStorage 空壳,Pipeline 系统已完全覆盖其全部能力。

删除 16 个文件,约 7,950 行代码:
- 5 个组件:TeamCollaborationView, TeamOrchestrator, TeamList, DevQALoop, SwarmDashboard
- 1 个 Store:teamStore.ts
- 3 个 Client/库:team-client.ts, useTeamEvents.ts, agent-swarm.ts
- 1 个类型文件:team.ts
- 4 个测试文件
- 1 个文档(归档 swarm-coordination.md)

修改 4 个文件:
- Sidebar.tsx:移除"团队"和"协作"导航项
- App.tsx:移除 team/swarm 视图路由
- types/index.ts:移除 team 类型导出
- chatStore.ts:移除 dispatchSwarmTask 方法

更新 CHANGELOG.md 和功能文档 README.md
This commit is contained in:
iven
2026-03-26 20:27:19 +08:00
parent 978dc5cdd8
commit c3996573aa
22 changed files with 11 additions and 7689 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,594 +0,0 @@
/**
* Team Client Tests
*
* Tests for OpenFang Team API client.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
TeamAPIError,
listTeams,
getTeam,
createTeam,
updateTeam,
deleteTeam,
addTeamMember,
removeTeamMember,
updateMemberRole,
addTeamTask,
updateTaskStatus,
assignTask,
submitDeliverable,
startDevQALoop,
submitReview,
updateLoopState,
getTeamMetrics,
getTeamEvents,
subscribeToTeamEvents,
teamClient,
} from '../../src/lib/team-client';
import type { Team, TeamMember, TeamTask, TeamMemberRole, DevQALoop } from '../../src/types/team';
// Mock fetch globally
const mockFetch = vi.fn();
const originalFetch = global.fetch;
describe('team-client', () => {
beforeEach(() => {
global.fetch = mockFetch;
mockFetch.mockClear();
});
afterEach(() => {
global.fetch = originalFetch;
mockFetch.mockReset();
});
describe('TeamAPIError', () => {
it('should create error with all properties', () => {
const error = new TeamAPIError('Test error', 404, '/teams/test', { detail: 'test detail' });
expect(error.message).toBe('Test error');
expect(error.statusCode).toBe(404);
expect(error.endpoint).toBe('/teams/test');
expect(error.details).toEqual({ detail: 'test detail' });
});
});
describe('listTeams', () => {
it('should fetch teams list', async () => {
const mockTeams: Team[] = [
{
id: 'team-1',
name: 'Test Team',
members: [],
tasks: [],
pattern: 'sequential',
activeLoops: [],
status: 'active',
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
},
];
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => ({ teams: mockTeams, total: 1 }),
});
const result = await listTeams();
expect(mockFetch.mock.calls[0][0]).toContain('/api/teams');
expect(result).toEqual({ teams: mockTeams, total: 1 });
});
});
describe('getTeam', () => {
it('should fetch single team', async () => {
const mockTeam = {
team: {
id: 'team-1',
name: 'Test Team',
members: [],
tasks: [],
pattern: 'sequential' as const,
activeLoops: [],
status: 'active',
createdAt: '2024-01-01T00:00:00Z',
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => mockTeam,
});
const result = await getTeam('team-1');
expect(mockFetch.mock.calls[0][0]).toContain('/api/teams/team-1');
expect(result).toEqual(mockTeam);
});
});
describe('createTeam', () => {
it('should create a new team', async () => {
const createRequest = {
name: 'New Team',
description: 'Test description',
memberAgents: [],
pattern: 'parallel' as const,
};
const mockResponse = {
team: {
id: 'new-team-id',
name: 'New Team',
description: 'Test description',
members: [],
tasks: [],
pattern: 'parallel',
activeLoops: [],
status: 'active',
createdAt: '2024-01-01T00:00:00Z',
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 201,
statusText: 'Created',
json: async () => mockResponse,
});
const result = await createTeam(createRequest);
expect(mockFetch).toHaveBeenCalledWith('/api/teams', expect.objectContaining({
method: 'POST',
}));
expect(result).toEqual(mockResponse);
});
});
describe('updateTeam', () => {
it('should update a team', async () => {
const updateData = { name: 'Updated Team' };
const mockResponse = {
team: {
id: 'team-1',
name: 'Updated Team',
description: 'Test',
members: [],
tasks: [],
pattern: 'sequential' as const,
activeLoops: [],
status: 'active',
createdAt: '2024-01-01T00:00:00Z',
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => mockResponse,
});
const result = await updateTeam('team-1', updateData);
expect(mockFetch).toHaveBeenCalledWith('/api/teams/team-1', expect.objectContaining({
method: 'PUT',
}));
expect(result).toEqual(mockResponse);
});
});
describe('deleteTeam', () => {
it('should delete a team', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => ({ success: true }),
});
const result = await deleteTeam('team-1');
expect(mockFetch).toHaveBeenCalledWith('/api/teams/team-1', expect.objectContaining({
method: 'DELETE',
}));
expect(result).toEqual({ success: true });
});
});
describe('addTeamMember', () => {
it('should add a member to team', async () => {
const mockResponse = {
member: {
id: 'member-1',
agentId: 'agent-1',
name: 'Agent 1',
role: 'developer',
skills: [],
workload: 0,
status: 'idle',
maxConcurrentTasks: 2,
currentTasks: [],
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => mockResponse,
});
const result = await addTeamMember('team-1', 'agent-1', 'developer');
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/teams/team-1/members'),
expect.objectContaining({ method: 'POST' })
);
expect(result).toEqual(mockResponse);
});
});
describe('removeTeamMember', () => {
it('should remove a member from team', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => ({ success: true }),
});
const result = await removeTeamMember('team-1', 'member-1');
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/teams/team-1/members/member-1'),
expect.objectContaining({ method: 'DELETE' })
);
expect(result).toEqual({ success: true });
});
});
describe('updateMemberRole', () => {
it('should update member role', async () => {
const mockResponse = {
member: {
id: 'member-1',
agentId: 'agent-1',
name: 'Agent 1',
role: 'reviewer',
skills: [],
workload: 0,
status: 'idle',
maxConcurrentTasks: 2,
currentTasks: [],
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => mockResponse,
});
const result = await updateMemberRole('team-1', 'member-1', 'reviewer');
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/teams/team-1/members/member-1'),
expect.objectContaining({ method: 'PUT' })
);
expect(result).toEqual(mockResponse);
});
});
describe('addTeamTask', () => {
it('should add a task to team', async () => {
const taskRequest = {
teamId: 'team-1',
title: 'Test Task',
description: 'Test task description',
priority: 'high',
type: 'implementation',
};
const mockResponse = {
task: {
id: 'task-1',
title: 'Test Task',
description: 'Test task description',
status: 'pending',
priority: 'high',
dependencies: [],
type: 'implementation',
createdAt: '2024-01-01T00:00:00Z',
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 201,
statusText: 'Created',
json: async () => mockResponse,
});
const result = await addTeamTask(taskRequest);
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/teams/team-1/tasks'),
expect.objectContaining({ method: 'POST' })
);
expect(result).toEqual(mockResponse);
});
});
describe('updateTaskStatus', () => {
it('should update task status', async () => {
const mockResponse = {
task: {
id: 'task-1',
title: 'Test Task',
status: 'in_progress',
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => mockResponse,
});
const result = await updateTaskStatus('team-1', 'task-1', 'in_progress');
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/teams/team-1/tasks/task-1'),
expect.objectContaining({ method: 'PUT' })
);
expect(result).toEqual(mockResponse);
});
});
describe('assignTask', () => {
it('should assign task to member', async () => {
const mockResponse = {
task: {
id: 'task-1',
title: 'Test Task',
assigneeId: 'member-1',
status: 'assigned',
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => mockResponse,
});
const result = await assignTask('team-1', 'task-1', 'member-1');
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/teams/team-1/tasks/task-1/assign'),
expect.objectContaining({ method: 'POST' })
);
expect(result).toEqual(mockResponse);
});
});
describe('submitDeliverable', () => {
it('should submit deliverable for task', async () => {
const deliverable = {
type: 'code',
description: 'Test deliverable',
files: ['/test/file.ts'],
};
const mockResponse = {
task: {
id: 'task-1',
title: 'Test Task',
deliverable,
status: 'review',
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => mockResponse,
});
const result = await submitDeliverable('team-1', 'task-1', deliverable);
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/teams/team-1/tasks/task-1/deliverable'),
expect.objectContaining({ method: 'POST' })
);
expect(result).toEqual(mockResponse);
});
});
describe('startDevQALoop', () => {
it('should start a DevQA loop', async () => {
const mockResponse = {
loop: {
id: 'loop-1',
developerId: 'dev-1',
reviewerId: 'reviewer-1',
taskId: 'task-1',
state: 'developing',
iterationCount: 0,
maxIterations: 3,
feedbackHistory: [],
startedAt: '2024-01-01T00:00:00Z',
lastUpdatedAt: '2024-01-01T00:00:00Z',
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 201,
statusText: 'Created',
json: async () => mockResponse,
});
const result = await startDevQALoop('team-1', 'task-1', 'dev-1', 'reviewer-1');
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/teams/team-1/loops'),
expect.objectContaining({ method: 'POST' })
);
expect(result).toEqual(mockResponse);
});
});
describe('submitReview', () => {
it('should submit a review', async () => {
const feedback = {
verdict: 'approved',
comments: ['LGTM!'],
issues: [],
};
const mockResponse = {
loop: {
id: 'loop-1',
state: 'approved',
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => mockResponse,
});
const result = await submitReview('team-1', 'loop-1', feedback);
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/teams/team-1/loops/loop-1/review'),
expect.objectContaining({ method: 'POST' })
);
expect(result).toEqual(mockResponse);
});
});
describe('updateLoopState', () => {
it('should update loop state', async () => {
const mockResponse = {
loop: {
id: 'loop-1',
state: 'reviewing',
},
success: true,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => mockResponse,
});
const result = await updateLoopState('team-1', 'loop-1', 'reviewing');
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/api/teams/team-1/loops/loop-1'),
expect.objectContaining({ method: 'PUT' })
);
expect(result).toEqual(mockResponse);
});
});
describe('getTeamMetrics', () => {
it('should get team metrics', async () => {
const mockMetrics = {
tasksCompleted: 10,
avgCompletionTime: 1000,
passRate: 85,
avgIterations: 1.5,
escalations: 0,
efficiency: 80,
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => mockMetrics,
});
const result = await getTeamMetrics('team-1');
expect(mockFetch.mock.calls[0][0]).toContain('/api/teams/team-1/metrics');
expect(result).toEqual(mockMetrics);
});
});
describe('getTeamEvents', () => {
it('should get team events', async () => {
const mockEvents = [
{
type: 'task_assigned',
teamId: 'team-1',
sourceAgentId: 'agent-1',
payload: {},
timestamp: '2024-01-01T00:00:00Z',
},
];
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
statusText: 'OK',
json: async () => ({ events: mockEvents, total: 1 }),
});
const result = await getTeamEvents('team-1', 10);
expect(mockFetch.mock.calls[0][0]).toContain('/api/teams/team-1/events');
expect(result).toEqual({ events: mockEvents, total: 1 });
});
});
describe('subscribeToTeamEvents', () => {
it('should subscribe to team events', () => {
const mockWs = {
readyState: WebSocket.OPEN,
send: vi.fn(),
addEventListener: vi.fn((event, handler) => {
handler('message');
return mockWs;
}),
removeEventListener: vi.fn(),
};
const callback = vi.fn();
const unsubscribe = subscribeToTeamEvents('team-1', callback, mockWs as unknown as WebSocket);
expect(mockWs.send).toHaveBeenCalledWith(JSON.stringify({
type: 'subscribe',
topic: 'team:team-1',
}));
unsubscribe();
expect(mockWs.removeEventListener).toHaveBeenCalled();
expect(mockWs.send).toHaveBeenCalledWith(JSON.stringify({
type: 'unsubscribe',
topic: 'team:team-1',
}));
});
});
describe('teamClient', () => {
it('should export all API functions', () => {
expect(teamClient.listTeams).toBe(listTeams);
expect(teamClient.getTeam).toBe(getTeam);
expect(teamClient.createTeam).toBe(createTeam);
expect(teamClient.updateTeam).toBe(updateTeam);
expect(teamClient.deleteTeam).toBe(deleteTeam);
expect(teamClient.addTeamMember).toBe(addTeamMember);
expect(teamClient.removeTeamMember).toBe(removeTeamMember);
expect(teamClient.updateMemberRole).toBe(updateMemberRole);
expect(teamClient.addTeamTask).toBe(addTeamTask);
expect(teamClient.updateTaskStatus).toBe(updateTaskStatus);
expect(teamClient.assignTask).toBe(assignTask);
expect(teamClient.submitDeliverable).toBe(submitDeliverable);
expect(teamClient.startDevQALoop).toBe(startDevQALoop);
expect(teamClient.submitReview).toBe(submitReview);
expect(teamClient.updateLoopState).toBe(updateLoopState);
expect(teamClient.getTeamMetrics).toBe(getTeamMetrics);
expect(teamClient.getTeamEvents).toBe(getTeamEvents);
expect(teamClient.subscribeToTeamEvents).toBe(subscribeToTeamEvents);
});
});
});

View File

@@ -1,373 +0,0 @@
/**
* Team Store Tests
*
* Tests for multi-agent team collaboration state management.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { useTeamStore } from '../../src/store/teamStore';
import type { Team, TeamMember, TeamTask, CreateTeamRequest, AddTeamTaskRequest, TeamMemberRole } from '../../src/types/team';
import { localStorageMock } from '../setup';
// Mock fetch globally
const mockFetch = vi.fn();
const originalFetch = global.fetch;
describe('teamStore', () => {
beforeEach(() => {
global.fetch = mockFetch;
mockFetch.mockClear();
localStorageMock.clear();
});
afterEach(() => {
global.fetch = originalFetch;
mockFetch.mockReset();
});
describe('Initial State', () => {
it('should have correct initial state', () => {
const store = useTeamStore.getState();
expect(store.teams).toEqual([]);
expect(store.activeTeam).toBeNull();
expect(store.metrics).toBeNull();
expect(store.isLoading).toBe(false);
expect(store.error).toBeNull();
expect(store.selectedTaskId).toBeNull();
expect(store.selectedMemberId).toBeNull();
expect(store.recentEvents).toEqual([]);
});
});
describe('loadTeams', () => {
// Note: This test is skipped because the zustand persist middleware
// interferes with manual localStorage manipulation in tests.
// The persist middleware handles loading automatically.
it.skip('should load teams from localStorage', async () => {
const mockTeams: Team[] = [
{
id: 'team-1',
name: 'Test Team',
members: [],
tasks: [],
pattern: 'sequential',
activeLoops: [],
status: 'active',
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
},
];
// Clear any existing data
localStorageMock.clear();
// Set localStorage in the format that zustand persist middleware uses
localStorageMock.setItem('zclaw-teams', JSON.stringify({
state: {
teams: mockTeams,
activeTeam: null
},
version: 0
}));
await useTeamStore.getState().loadTeams();
const store = useTeamStore.getState();
// Check that teams were loaded
expect(store.teams).toHaveLength(1);
expect(store.teams[0].name).toBe('Test Team');
expect(store.isLoading).toBe(false);
});
});
describe('createTeam', () => {
it('should create a new team with members', async () => {
const request: CreateTeamRequest = {
name: 'Dev Team',
description: 'Development team',
memberAgents: [
{ agentId: 'agent-1', role: 'developer' },
{ agentId: 'agent-2', role: 'reviewer' },
],
pattern: 'review_loop',
};
const team = await useTeamStore.getState().createTeam(request);
expect(team).not.toBeNull();
expect(team.name).toBe('Dev Team');
expect(team.description).toBe('Development team');
expect(team.pattern).toBe('review_loop');
expect(team.members).toHaveLength(2);
expect(team.status).toBe('active');
const store = useTeamStore.getState();
expect(store.teams).toHaveLength(1);
expect(store.activeTeam?.id).toBe(team.id);
});
});
describe('deleteTeam', () => {
it('should delete a team', async () => {
// First create a team
const request: CreateTeamRequest = {
name: 'Team to Delete',
memberAgents: [],
pattern: 'sequential',
};
await useTeamStore.getState().createTeam(request);
// Then delete it
const result = await useTeamStore.getState().deleteTeam('team-to-delete-id');
expect(result).toBe(true);
const store = useTeamStore.getState();
expect(store.teams.find(t => t.id === 'team-to-delete-id')).toBeUndefined();
});
});
describe('setActiveTeam', () => {
it('should set active team and update metrics', () => {
const team: Team = {
id: 'team-1',
name: 'Test Team',
members: [],
tasks: [],
pattern: 'sequential',
activeLoops: [],
status: 'active',
createdAt: '2024-01-01T00:00:00Z',
};
useTeamStore.getState().setActiveTeam(team);
const store = useTeamStore.getState();
expect(store.activeTeam).toEqual(team);
expect(store.metrics).not.toBeNull();
});
});
describe('addMember', () => {
let team: Team;
beforeEach(async () => {
const request: CreateTeamRequest = {
name: 'Test Team',
memberAgents: [],
pattern: 'sequential',
};
team = (await useTeamStore.getState().createTeam(request))!;
});
it('should add a member to team', async () => {
const member = await useTeamStore.getState().addMember(team.id, 'agent-1', 'developer');
expect(member).not.toBeNull();
expect(member.agentId).toBe('agent-1');
expect(member.role).toBe('developer');
const store = useTeamStore.getState();
const updatedTeam = store.teams.find(t => t.id === team.id);
expect(updatedTeam?.members).toHaveLength(1);
});
});
describe('removeMember', () => {
let team: Team;
let memberId: string;
beforeEach(async () => {
const request: CreateTeamRequest = {
name: 'Test Team',
memberAgents: [{ agentId: 'agent-1', role: 'developer' }],
pattern: 'sequential',
};
team = (await useTeamStore.getState().createTeam(request))!;
memberId = team.members[0].id;
});
it('should remove a member from team', async () => {
const result = await useTeamStore.getState().removeMember(team.id, memberId);
expect(result).toBe(true);
const store = useTeamStore.getState();
const updatedTeam = store.teams.find(t => t.id === team.id);
expect(updatedTeam?.members).toHaveLength(0);
});
});
describe('addTask', () => {
let team: Team;
beforeEach(async () => {
const request: CreateTeamRequest = {
name: 'Test Team',
memberAgents: [],
pattern: 'sequential',
};
team = (await useTeamStore.getState().createTeam(request))!;
});
it('should add a task to team', async () => {
const taskRequest: AddTeamTaskRequest = {
teamId: team.id,
title: 'Test Task',
description: 'Test task description',
priority: 'high',
type: 'implementation',
};
const task = await useTeamStore.getState().addTask(taskRequest);
expect(task).not.toBeNull();
expect(task.title).toBe('Test Task');
expect(task.status).toBe('pending');
const store = useTeamStore.getState();
const updatedTeam = store.teams.find(t => t.id === team.id);
expect(updatedTeam?.tasks).toHaveLength(1);
});
});
describe('updateTaskStatus', () => {
let team: Team;
let taskId: string;
beforeEach(async () => {
const teamRequest: CreateTeamRequest = {
name: 'Test Team',
memberAgents: [],
pattern: 'sequential',
};
team = (await useTeamStore.getState().createTeam(teamRequest))!;
const taskRequest: AddTeamTaskRequest = {
teamId: team.id,
title: 'Test Task',
priority: 'medium',
type: 'implementation',
};
const task = await useTeamStore.getState().addTask(taskRequest);
taskId = task!.id;
});
it('should update task status to in_progress', async () => {
const result = await useTeamStore.getState().updateTaskStatus(team.id, taskId, 'in_progress');
expect(result).toBe(true);
const store = useTeamStore.getState();
const updatedTeam = store.teams.find(t => t.id === team.id);
const updatedTask = updatedTeam?.tasks.find(t => t.id === taskId);
expect(updatedTask?.status).toBe('in_progress');
expect(updatedTask?.startedAt).toBeDefined();
});
});
describe('startDevQALoop', () => {
let team: Team;
let taskId: string;
let memberId: string;
beforeEach(async () => {
const teamRequest: CreateTeamRequest = {
name: 'Test Team',
memberAgents: [
{ agentId: 'dev-agent', role: 'developer' },
{ agentId: 'qa-agent', role: 'reviewer' },
],
pattern: 'review_loop',
};
team = (await useTeamStore.getState().createTeam(teamRequest))!;
const taskRequest: AddTeamTaskRequest = {
teamId: team.id,
title: 'Test Task',
priority: 'high',
type: 'implementation',
assigneeId: team.members[0].id,
};
const task = await useTeamStore.getState().addTask(taskRequest);
taskId = task!.id;
memberId = team.members[0].id;
});
it('should start a Dev-QA loop', async () => {
const loop = await useTeamStore.getState().startDevQALoop(
team.id,
taskId,
team.members[0].id,
team.members[1].id
);
expect(loop).not.toBeNull();
expect(loop.state).toBe('developing');
expect(loop.developerId).toBe(team.members[0].id);
expect(loop.reviewerId).toBe(team.members[1].id);
const store = useTeamStore.getState();
const updatedTeam = store.teams.find(t => t.id === team.id);
expect(updatedTeam?.activeLoops).toHaveLength(1);
});
});
describe('submitReview', () => {
let team: Team;
let loop: any;
beforeEach(async () => {
const teamRequest: CreateTeamRequest = {
name: 'Test Team',
memberAgents: [
{ agentId: 'dev-agent', role: 'developer' },
{ agentId: 'qa-agent', role: 'reviewer' },
],
pattern: 'review_loop',
};
team = (await useTeamStore.getState().createTeam(teamRequest))!;
const taskRequest: AddTeamTaskRequest = {
teamId: team.id,
title: 'Test Task',
priority: 'high',
type: 'implementation',
assigneeId: team.members[0].id,
};
const task = await useTeamStore.getState().addTask(taskRequest);
loop = await useTeamStore.getState().startDevQALoop(
team.id,
task!.id,
team.members[0].id,
team.members[1].id
);
});
it('should submit review and update loop state', async () => {
const feedback = {
verdict: 'approved',
comments: ['Good work!'],
issues: [],
};
const result = await useTeamStore.getState().submitReview(team.id, loop.id, feedback);
expect(result).toBe(true);
const store = useTeamStore.getState();
const updatedTeam = store.teams.find(t => t.id === team.id);
const updatedLoop = updatedTeam?.activeLoops.find(l => l.id === loop.id);
expect(updatedLoop?.state).toBe('approved');
});
});
describe('addEvent', () => {
it('should add event to recent events', () => {
const event = {
type: 'task_completed',
teamId: 'team-1',
sourceAgentId: 'agent-1',
payload: { taskId: 'task-1' },
timestamp: new Date().toISOString(),
};
useTeamStore.getState().addEvent(event);
const store = useTeamStore.getState();
expect(store.recentEvents).toHaveLength(1);
expect(store.recentEvents[0]).toEqual(event);
});
});
describe('clearEvents', () => {
it('should clear all events', () => {
const event = {
type: 'task_completed',
teamId: 'team-1',
sourceAgentId: 'agent-1',
payload: {},
timestamp: new Date().toISOString(),
};
useTeamStore.getState().addEvent(event);
useTeamStore.getState().clearEvents();
const store = useTeamStore.getState();
expect(store.recentEvents).toHaveLength(0);
});
});
describe('UI Actions', () => {
it('should set selected task', () => {
useTeamStore.getState().setSelectedTask('task-1');
expect(useTeamStore.getState().selectedTaskId).toBe('task-1');
});
it('should set selected member', () => {
useTeamStore.getState().setSelectedMember('member-1');
expect(useTeamStore.getState().selectedMemberId).toBe('member-1');
});
it('should clear error', () => {
useTeamStore.setState({ error: 'Test error' });
useTeamStore.getState().clearError();
expect(useTeamStore.getState().error).toBeNull();
});
});
});