/** * Team Store Tests * * Unit tests for multi-agent team collaboration state management. * * @module tests/desktop/teamStore.test */ import { beforeEach, describe, expect, it, vi } from 'vitest'; // Mock localStorage const localStorageMock = (() => { let store: Record = {}; return { getItem: vi.fn((key: string) => store[key] || null), setItem: vi.fn((key: string, value: string) => { store[key] = value; }), removeItem: vi.fn((key: string) => { delete store[key]; }), clear: vi.fn(() => { store = {}; }), }; })(); Object.defineProperty(global, 'localStorage', { value: localStorageMock, }); // Import store after mocking import { useTeamStore } from '../../desktop/src/store/teamStore'; import type { Team, TeamMember, TeamTask, DevQALoop } from '../../desktop/src/types/team'; describe('TeamStore', () => { beforeEach(() => { // Reset store state and localStorage localStorageMock.clear(); useTeamStore.setState({ teams: [], activeTeam: null, metrics: null, isLoading: false, error: null, selectedTaskId: null, selectedMemberId: null, recentEvents: [], }); }); describe('loadTeams', () => { it('should load teams from localStorage', async () => { const mockTeams: Team[] = [ { id: 'team-1', name: 'Test Team', description: 'A test team', status: 'active', pattern: 'sequential', members: [], tasks: [], loops: [], createdAt: '2026-03-15T00:00:00Z', }, ]; localStorageMock.getItem.mockReturnValueOnce(JSON.stringify(mockTeams)); const { loadTeams } = useTeamStore.getState(); await loadTeams(); const state = useTeamStore.getState(); expect(state.teams).toHaveLength(1); expect(state.teams[0].name).toBe('Test Team'); }); it('should handle empty localStorage', async () => { localStorageMock.getItem.mockReturnValueOnce(null); const { loadTeams } = useTeamStore.getState(); await loadTeams(); const state = useTeamStore.getState(); expect(state.teams).toHaveLength(0); }); }); describe('createTeam', () => { it('should create a new team with members', async () => { const request = { name: 'New Team', description: 'A new team for testing', pattern: 'parallel' as const, memberAgents: [ { agentId: 'agent-1', role: 'orchestrator' as const }, { agentId: 'agent-2', role: 'developer' as const }, ], }; const { createTeam } = useTeamStore.getState(); const team = await createTeam(request); expect(team).not.toBeNull(); expect(team?.name).toBe('New Team'); expect(team?.pattern).toBe('parallel'); expect(team?.members).toHaveLength(2); expect(team?.members[0].role).toBe('orchestrator'); const state = useTeamStore.getState(); expect(state.teams).toHaveLength(1); }); }); describe('setActiveTeam', () => { it('should set active team and calculate metrics', () => { const mockTeam: Team = { id: 'team-1', name: 'Test Team', description: 'A test team', status: 'active', pattern: 'sequential', members: [ { id: 'member-1', agentId: 'agent-1', name: 'Developer 1', role: 'developer', status: 'idle', skills: [], workload: 0, currentTasks: [], maxConcurrentTasks: 2, }, ], tasks: [ { id: 'task-1', title: 'Completed Task', description: 'A completed task', status: 'completed', priority: 'medium', type: 'feature', assigneeId: 'member-1', createdAt: '2026-03-15T00:00:00Z', updatedAt: '2026-03-15T01:00:00Z', }, ], activeLoops: [], createdAt: '2026-03-15T00:00:00Z', }; const { setActiveTeam } = useTeamStore.getState(); setActiveTeam(mockTeam); const state = useTeamStore.getState(); expect(state.activeTeam).toEqual(mockTeam); expect(state.metrics).not.toBeNull(); expect(state.metrics?.tasksCompleted).toBe(1); }); it('should clear active team when passed null', () => { const { setActiveTeam } = useTeamStore.getState(); // First set a team setActiveTeam({ id: 'team-1', name: 'Test', description: '', status: 'active', pattern: 'sequential', members: [], tasks: [], activeLoops: [], createdAt: '2026-03-15T00:00:00Z', }); // Then clear it setActiveTeam(null); const state = useTeamStore.getState(); expect(state.activeTeam).toBeNull(); expect(state.metrics).toBeNull(); }); }); describe('addMember', () => { it('should add a member to a team', async () => { // Setup: create a team first const { createTeam, addMember } = useTeamStore.getState(); await createTeam({ name: 'Test Team', description: '', pattern: 'sequential', memberAgents: [{ agentId: 'agent-1', role: 'orchestrator' }], }); const teamId = useTeamStore.getState().teams[0].id; const member = await addMember(teamId, 'agent-2', 'developer'); expect(member).not.toBeNull(); expect(member?.role).toBe('developer'); expect(member?.agentId).toBe('agent-2'); const state = useTeamStore.getState(); expect(state.teams[0].members).toHaveLength(2); }); }); describe('addTask', () => { it('should add a task to a team', async () => { const { createTeam, addTask } = useTeamStore.getState(); await createTeam({ name: 'Test Team', description: '', pattern: 'sequential', memberAgents: [{ agentId: 'agent-1', role: 'orchestrator' }], }); const teamId = useTeamStore.getState().teams[0].id; const task = await addTask({ teamId, title: 'New Task', description: 'A new task for testing', priority: 'high', type: 'feature', }); expect(task).not.toBeNull(); expect(task?.title).toBe('New Task'); expect(task?.status).toBe('pending'); const state = useTeamStore.getState(); expect(state.teams[0].tasks).toHaveLength(1); }); }); describe('updateTaskStatus', () => { it('should update task status', async () => { const { createTeam, addTask, updateTaskStatus } = useTeamStore.getState(); await createTeam({ name: 'Test Team', description: '', pattern: 'sequential', memberAgents: [{ agentId: 'agent-1', role: 'orchestrator' }], }); const teamId = useTeamStore.getState().teams[0].id; await addTask({ teamId, title: 'Test Task', description: '', priority: 'medium', type: 'feature', }); const taskId = useTeamStore.getState().teams[0].tasks[0].id; await updateTaskStatus(teamId, taskId, 'in_progress'); const state = useTeamStore.getState(); expect(state.teams[0].tasks[0].status).toBe('in_progress'); }); }); describe('assignTask', () => { it('should assign task to a member', async () => { const { createTeam, addTask, assignTask } = useTeamStore.getState(); await createTeam({ name: 'Test Team', description: '', pattern: 'sequential', memberAgents: [ { agentId: 'agent-1', role: 'orchestrator' }, { agentId: 'agent-2', role: 'developer' }, ], }); const state1 = useTeamStore.getState(); const teamId = state1.teams[0].id; const memberId = state1.teams[0].members[1].id; await addTask({ teamId, title: 'Test Task', description: '', priority: 'medium', type: 'feature', }); const taskId = useTeamStore.getState().teams[0].tasks[0].id; await assignTask(teamId, taskId, memberId); const state2 = useTeamStore.getState(); expect(state2.teams[0].tasks[0].assigneeId).toBe(memberId); expect(state2.teams[0].tasks[0].status).toBe('assigned'); }); }); describe('startDevQALoop', () => { it('should start a Dev↔QA loop', async () => { const { createTeam, addTask, startDevQALoop } = useTeamStore.getState(); await createTeam({ name: 'Test Team', description: '', pattern: 'review_loop', memberAgents: [ { agentId: 'agent-1', role: 'orchestrator' }, { agentId: 'agent-2', role: 'developer' }, { agentId: 'agent-3', role: 'reviewer' }, ], }); const state1 = useTeamStore.getState(); const teamId = state1.teams[0].id; const developerId = state1.teams[0].members[1].id; const reviewerId = state1.teams[0].members[2].id; await addTask({ teamId, title: 'Code Task', description: '', priority: 'high', type: 'feature', }); const taskId = useTeamStore.getState().teams[0].tasks[0].id; const loop = await startDevQALoop(teamId, taskId, developerId, reviewerId); expect(loop).not.toBeNull(); expect(loop?.state).toBe('developing'); expect(loop?.developerId).toBe(developerId); expect(loop?.reviewerId).toBe(reviewerId); const state2 = useTeamStore.getState(); expect(state2.teams[0].activeLoops).toHaveLength(1); }); }); describe('submitReview', () => { it('should submit review feedback', async () => { const { createTeam, addTask, startDevQALoop, submitReview } = useTeamStore.getState(); await createTeam({ name: 'Test Team', description: '', pattern: 'review_loop', memberAgents: [ { agentId: 'agent-1', role: 'orchestrator' }, { agentId: 'agent-2', role: 'developer' }, { agentId: 'agent-3', role: 'reviewer' }, ], }); const state1 = useTeamStore.getState(); const teamId = state1.teams[0].id; const developerId = state1.teams[0].members[1].id; const reviewerId = state1.teams[0].members[2].id; await addTask({ teamId, title: 'Code Task', description: '', priority: 'high', type: 'feature', }); const taskId = useTeamStore.getState().teams[0].tasks[0].id; await startDevQALoop(teamId, taskId, developerId, reviewerId); const loopId = useTeamStore.getState().teams[0].activeLoops[0].id; await submitReview(teamId, loopId, { verdict: 'needs_work', comments: ['Please fix the bug'], issues: [ { severity: 'major', description: 'Null pointer exception', file: 'main.ts', line: 42 }, ], }); const state2 = useTeamStore.getState(); expect(state2.teams[0].activeLoops[0].state).toBe('revising'); expect(state2.teams[0].activeLoops[0].feedbackHistory).toHaveLength(1); }); it('should approve task when verdict is approved', async () => { const { createTeam, addTask, startDevQALoop, submitReview } = useTeamStore.getState(); await createTeam({ name: 'Test Team', description: '', pattern: 'review_loop', memberAgents: [ { agentId: 'agent-1', role: 'orchestrator' }, { agentId: 'agent-2', role: 'developer' }, { agentId: 'agent-3', role: 'reviewer' }, ], }); const state1 = useTeamStore.getState(); const teamId = state1.teams[0].id; const developerId = state1.teams[0].members[1].id; const reviewerId = state1.teams[0].members[2].id; await addTask({ teamId, title: 'Code Task', description: '', priority: 'high', type: 'feature', }); const taskId = useTeamStore.getState().teams[0].tasks[0].id; await startDevQALoop(teamId, taskId, developerId, reviewerId); const loopId = useTeamStore.getState().teams[0].activeLoops[0].id; await submitReview(teamId, loopId, { verdict: 'approved', comments: ['Great work!'], issues: [], }); const state2 = useTeamStore.getState(); expect(state2.teams[0].activeLoops[0].state).toBe('approved'); }); }); describe('addEvent', () => { it('should add collaboration event', () => { const { addEvent } = useTeamStore.getState(); addEvent({ type: 'task_assigned', teamId: 'team-1', sourceAgentId: 'agent-1', payload: { taskId: 'task-1' }, timestamp: new Date().toISOString(), }); const state = useTeamStore.getState(); expect(state.recentEvents).toHaveLength(1); expect(state.recentEvents[0].type).toBe('task_assigned'); }); it('should limit events to max 100', () => { const { addEvent } = useTeamStore.getState(); // Add 105 events for (let i = 0; i < 105; i++) { addEvent({ type: 'task_assigned', teamId: 'team-1', sourceAgentId: 'agent-1', payload: { index: i }, timestamp: new Date().toISOString(), }); } const state = useTeamStore.getState(); expect(state.recentEvents).toHaveLength(100); // Most recent event should be at index 0 expect(state.recentEvents[0].payload.index).toBe(104); // Oldest kept event should be at index 99 (events 0-4 are discarded) expect(state.recentEvents[99].payload.index).toBe(5); }); }); describe('deleteTeam', () => { it('should delete a team', async () => { const { createTeam, deleteTeam } = useTeamStore.getState(); await createTeam({ name: 'Team to Delete', description: '', pattern: 'sequential', memberAgents: [{ agentId: 'agent-1', role: 'orchestrator' }], }); const teamId = useTeamStore.getState().teams[0].id; await deleteTeam(teamId); const state = useTeamStore.getState(); expect(state.teams).toHaveLength(0); }); it('should clear active team if deleted', async () => { const { createTeam, setActiveTeam, deleteTeam } = useTeamStore.getState(); await createTeam({ name: 'Team to Delete', description: '', pattern: 'sequential', memberAgents: [{ agentId: 'agent-1', role: 'orchestrator' }], }); const teamId = useTeamStore.getState().teams[0].id; const team = useTeamStore.getState().teams[0]; setActiveTeam(team); await deleteTeam(teamId); const state = useTeamStore.getState(); expect(state.activeTeam).toBeNull(); }); }); });