import { describe, it, expect, vi } from 'vitest'; import { createEventEmitter, type EventType } from '@/lib/events.js'; describe('events.ts', () => { describe('createEventEmitter', () => { it('should emit events to single subscriber', () => { const emitter = createEventEmitter(); const callback = vi.fn(); emitter.subscribe(callback); emitter.emit('agent:stream', { message: 'test' }); expect(callback).toHaveBeenCalledOnce(); expect(callback).toHaveBeenCalledWith('agent:stream', { message: 'test' }); }); it('should emit events to multiple subscribers', () => { const emitter = createEventEmitter(); const callback1 = vi.fn(); const callback2 = vi.fn(); const callback3 = vi.fn(); emitter.subscribe(callback1); emitter.subscribe(callback2); emitter.subscribe(callback3); emitter.emit('feature:started', { id: '123' }); expect(callback1).toHaveBeenCalledOnce(); expect(callback2).toHaveBeenCalledOnce(); expect(callback3).toHaveBeenCalledOnce(); expect(callback1).toHaveBeenCalledWith('feature:started', { id: '123' }); }); it('should support unsubscribe functionality', () => { const emitter = createEventEmitter(); const callback = vi.fn(); const unsubscribe = emitter.subscribe(callback); emitter.emit('agent:stream', { test: 1 }); expect(callback).toHaveBeenCalledOnce(); unsubscribe(); emitter.emit('agent:stream', { test: 2 }); expect(callback).toHaveBeenCalledOnce(); // Still called only once }); it('should handle errors in subscribers without crashing', () => { const emitter = createEventEmitter(); const errorCallback = vi.fn(() => { throw new Error('Subscriber error'); }); const normalCallback = vi.fn(); const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); emitter.subscribe(errorCallback); emitter.subscribe(normalCallback); expect(() => { emitter.emit('feature:error', { error: 'test' }); }).not.toThrow(); expect(errorCallback).toHaveBeenCalledOnce(); expect(normalCallback).toHaveBeenCalledOnce(); expect(consoleSpy).toHaveBeenCalled(); consoleSpy.mockRestore(); }); it('should emit different event types', () => { const emitter = createEventEmitter(); const callback = vi.fn(); emitter.subscribe(callback); const eventTypes: EventType[] = [ 'agent:stream', 'auto-mode:started', 'feature:completed', 'project:analysis-progress', ]; eventTypes.forEach((type) => { emitter.emit(type, { type }); }); expect(callback).toHaveBeenCalledTimes(4); }); it('should handle emitting without subscribers', () => { const emitter = createEventEmitter(); expect(() => { emitter.emit('agent:stream', { test: true }); }).not.toThrow(); }); it('should allow multiple subscriptions and unsubscriptions', () => { const emitter = createEventEmitter(); const callback1 = vi.fn(); const callback2 = vi.fn(); const callback3 = vi.fn(); const unsub1 = emitter.subscribe(callback1); const unsub2 = emitter.subscribe(callback2); const unsub3 = emitter.subscribe(callback3); emitter.emit('feature:started', { test: 1 }); expect(callback1).toHaveBeenCalledOnce(); expect(callback2).toHaveBeenCalledOnce(); expect(callback3).toHaveBeenCalledOnce(); unsub2(); emitter.emit('feature:started', { test: 2 }); expect(callback1).toHaveBeenCalledTimes(2); expect(callback2).toHaveBeenCalledOnce(); // Still just once expect(callback3).toHaveBeenCalledTimes(2); unsub1(); unsub3(); emitter.emit('feature:started', { test: 3 }); expect(callback1).toHaveBeenCalledTimes(2); expect(callback2).toHaveBeenCalledOnce(); expect(callback3).toHaveBeenCalledTimes(2); }); }); });