/** * 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 '../../tests/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', () => { it('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', }, ]; localStorageMock.setItem('zclaw-teams', JSON.stringify(mockTeams)); await useTeamStore.getState().loadTeams(); const store = useTeamStore.getState(); expect(store.teams).toEqual(mockTeams); 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); // Check localStorage was updated const stored = localStorageMock.getItem('zclaw-teams'); expect(stored).toBeDefined(); const parsed = JSON.parse(stored!); expect(parsed).toHaveLength(1); }); }); 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 () => { 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 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(); }); }); });