/** * Tests for Event Store */ import { useEventStore } from '@/lib/stores/eventStore'; import { EventType, type ProjectEvent } from '@/lib/types/events'; /** * Helper to create mock event */ function createMockEvent(overrides: Partial = {}): ProjectEvent { return { id: `event-${Math.random().toString(36).substr(2, 9)}`, type: EventType.AGENT_MESSAGE, timestamp: new Date().toISOString(), project_id: 'project-123', actor_id: 'agent-456', actor_type: 'agent', payload: { message: 'Test message' }, ...overrides, }; } describe('Event Store', () => { beforeEach(() => { // Reset store state useEventStore.setState({ eventsByProject: {}, maxEvents: 100, }); }); describe('addEvent', () => { it('should add an event to the store', () => { const event = createMockEvent(); useEventStore.getState().addEvent(event); const events = useEventStore.getState().getProjectEvents('project-123'); expect(events).toHaveLength(1); expect(events[0]).toEqual(event); }); it('should add events to correct project', () => { const event1 = createMockEvent({ project_id: 'project-1' }); const event2 = createMockEvent({ project_id: 'project-2' }); useEventStore.getState().addEvent(event1); useEventStore.getState().addEvent(event2); expect(useEventStore.getState().getProjectEvents('project-1')).toHaveLength(1); expect(useEventStore.getState().getProjectEvents('project-2')).toHaveLength(1); }); it('should skip duplicate event IDs', () => { const event = createMockEvent({ id: 'unique-id' }); useEventStore.getState().addEvent(event); useEventStore.getState().addEvent(event); const events = useEventStore.getState().getProjectEvents('project-123'); expect(events).toHaveLength(1); }); it('should trim events to maxEvents limit', () => { useEventStore.getState().setMaxEvents(5); // Add 10 events for (let i = 0; i < 10; i++) { useEventStore.getState().addEvent(createMockEvent({ id: `event-${i}` })); } const events = useEventStore.getState().getProjectEvents('project-123'); expect(events).toHaveLength(5); // Should keep the last 5 events expect(events[0].id).toBe('event-5'); expect(events[4].id).toBe('event-9'); }); }); describe('addEvents', () => { it('should add multiple events at once', () => { const events = [ createMockEvent({ id: 'event-1' }), createMockEvent({ id: 'event-2' }), createMockEvent({ id: 'event-3' }), ]; useEventStore.getState().addEvents(events); const storedEvents = useEventStore.getState().getProjectEvents('project-123'); expect(storedEvents).toHaveLength(3); }); it('should handle empty array', () => { useEventStore.getState().addEvents([]); const events = useEventStore.getState().getProjectEvents('project-123'); expect(events).toHaveLength(0); }); it('should filter out duplicate events', () => { const existingEvent = createMockEvent({ id: 'existing-event' }); useEventStore.getState().addEvent(existingEvent); const newEvents = [ createMockEvent({ id: 'existing-event' }), // Duplicate createMockEvent({ id: 'new-event-1' }), createMockEvent({ id: 'new-event-2' }), ]; useEventStore.getState().addEvents(newEvents); const storedEvents = useEventStore.getState().getProjectEvents('project-123'); expect(storedEvents).toHaveLength(3); }); it('should add events to multiple projects', () => { const events = [ createMockEvent({ id: 'event-1', project_id: 'project-1' }), createMockEvent({ id: 'event-2', project_id: 'project-2' }), createMockEvent({ id: 'event-3', project_id: 'project-1' }), ]; useEventStore.getState().addEvents(events); expect(useEventStore.getState().getProjectEvents('project-1')).toHaveLength(2); expect(useEventStore.getState().getProjectEvents('project-2')).toHaveLength(1); }); }); describe('clearProjectEvents', () => { it('should clear events for a specific project', () => { const event1 = createMockEvent({ project_id: 'project-1' }); const event2 = createMockEvent({ project_id: 'project-2' }); useEventStore.getState().addEvent(event1); useEventStore.getState().addEvent(event2); useEventStore.getState().clearProjectEvents('project-1'); expect(useEventStore.getState().getProjectEvents('project-1')).toHaveLength(0); expect(useEventStore.getState().getProjectEvents('project-2')).toHaveLength(1); }); it('should handle clearing non-existent project', () => { expect(() => { useEventStore.getState().clearProjectEvents('non-existent'); }).not.toThrow(); }); }); describe('clearAllEvents', () => { it('should clear all events from all projects', () => { const event1 = createMockEvent({ project_id: 'project-1' }); const event2 = createMockEvent({ project_id: 'project-2' }); useEventStore.getState().addEvent(event1); useEventStore.getState().addEvent(event2); useEventStore.getState().clearAllEvents(); expect(useEventStore.getState().getProjectEvents('project-1')).toHaveLength(0); expect(useEventStore.getState().getProjectEvents('project-2')).toHaveLength(0); }); }); describe('getProjectEvents', () => { it('should return empty array for non-existent project', () => { const events = useEventStore.getState().getProjectEvents('non-existent'); expect(events).toEqual([]); }); it('should return events for existing project', () => { const event = createMockEvent(); useEventStore.getState().addEvent(event); const events = useEventStore.getState().getProjectEvents('project-123'); expect(events).toHaveLength(1); expect(events[0]).toEqual(event); }); }); describe('getFilteredEvents', () => { it('should filter events by type', () => { const agentEvent = createMockEvent({ type: EventType.AGENT_MESSAGE }); const issueEvent = createMockEvent({ type: EventType.ISSUE_CREATED }); const sprintEvent = createMockEvent({ type: EventType.SPRINT_STARTED }); useEventStore.getState().addEvents([agentEvent, issueEvent, sprintEvent]); const filtered = useEventStore.getState().getFilteredEvents('project-123', [ EventType.AGENT_MESSAGE, EventType.ISSUE_CREATED, ]); expect(filtered).toHaveLength(2); expect(filtered.map((e) => e.type)).toContain(EventType.AGENT_MESSAGE); expect(filtered.map((e) => e.type)).toContain(EventType.ISSUE_CREATED); }); it('should return all events when types array is empty', () => { const event1 = createMockEvent({ type: EventType.AGENT_MESSAGE }); const event2 = createMockEvent({ type: EventType.ISSUE_CREATED }); useEventStore.getState().addEvents([event1, event2]); const filtered = useEventStore.getState().getFilteredEvents('project-123', []); expect(filtered).toHaveLength(2); }); it('should return empty array for non-existent project', () => { const filtered = useEventStore.getState().getFilteredEvents('non-existent', [ EventType.AGENT_MESSAGE, ]); expect(filtered).toEqual([]); }); }); describe('setMaxEvents', () => { it('should update maxEvents setting', () => { useEventStore.getState().setMaxEvents(50); expect(useEventStore.getState().maxEvents).toBe(50); }); it('should use default for invalid values', () => { const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); useEventStore.getState().setMaxEvents(0); expect(useEventStore.getState().maxEvents).toBe(100); // Default useEventStore.getState().setMaxEvents(-5); expect(useEventStore.getState().maxEvents).toBe(100); // Default expect(consoleWarnSpy).toHaveBeenCalled(); consoleWarnSpy.mockRestore(); }); it('should trim existing events when reducing maxEvents', () => { // Add 10 events for (let i = 0; i < 10; i++) { useEventStore.getState().addEvent(createMockEvent({ id: `event-${i}` })); } expect(useEventStore.getState().getProjectEvents('project-123')).toHaveLength(10); useEventStore.getState().setMaxEvents(5); const events = useEventStore.getState().getProjectEvents('project-123'); expect(events).toHaveLength(5); // Should keep the last 5 events expect(events[0].id).toBe('event-5'); }); }); });