import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { EventBus } from '../src/core/EventBus'; describe('EventBus', () => { let consoleSpy: ReturnType; beforeEach(() => { consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); }); afterEach(() => { vi.restoreAllMocks(); }); describe('on / emit', () => { it('should register a handler and call it on emit', () => { const bus = new EventBus(); const handler = vi.fn(); bus.on('test-event', handler); bus.emit('test-event', 'data1', 'data2'); expect(handler).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledWith('data1', 'data2'); }); it('should call multiple handlers on same event', () => { const bus = new EventBus(); const handler1 = vi.fn(); const handler2 = vi.fn(); bus.on('test-event', handler1); bus.on('test-event', handler2); bus.emit('test-event'); expect(handler1).toHaveBeenCalledTimes(1); expect(handler2).toHaveBeenCalledTimes(1); }); it('should not call handlers for different events', () => { const bus = new EventBus(); const handler = vi.fn(); bus.on('event-a', handler); bus.emit('event-b'); expect(handler).not.toHaveBeenCalled(); }); it('should handle emit on non-existent event', () => { const bus = new EventBus(); expect(() => bus.emit('non-existent', 'data')).not.toThrow(); }); it('should pass multiple arguments to handler', () => { const bus = new EventBus(); const handler = vi.fn(); bus.on('multi-arg', handler); bus.emit('multi-arg', 1, 'two', { three: 3 }, [4, 5]); expect(handler).toHaveBeenCalledWith(1, 'two', { three: 3 }, [4, 5]); }); it('should not throw if a handler throws', () => { const bus = new EventBus(); const handler1 = vi.fn(() => { throw new Error('Handler error'); }); const handler2 = vi.fn(); bus.on('error-event', handler1); bus.on('error-event', handler2); expect(() => bus.emit('error-event')).not.toThrow(); expect(handler2).toHaveBeenCalledTimes(1); expect(consoleSpy).toHaveBeenCalled(); }); }); describe('off', () => { it('should remove a registered handler', () => { const bus = new EventBus(); const handler = vi.fn(); bus.on('test-event', handler); bus.off('test-event', handler); bus.emit('test-event'); expect(handler).not.toHaveBeenCalled(); }); it('should not remove other handlers', () => { const bus = new EventBus(); const handler1 = vi.fn(); const handler2 = vi.fn(); bus.on('test-event', handler1); bus.on('test-event', handler2); bus.off('test-event', handler1); bus.emit('test-event'); expect(handler1).not.toHaveBeenCalled(); expect(handler2).toHaveBeenCalledTimes(1); }); it('should handle off for non-existent event', () => { const bus = new EventBus(); const handler = vi.fn(); expect(() => bus.off('non-existent', handler)).not.toThrow(); }); it('should handle off for non-existent handler', () => { const bus = new EventBus(); const handler1 = vi.fn(); const handler2 = vi.fn(); bus.on('test-event', handler1); bus.off('test-event', handler2); bus.emit('test-event'); expect(handler1).toHaveBeenCalledTimes(1); }); it('should only remove one instance if handler is registered multiple times', () => { const bus = new EventBus(); const handler = vi.fn(); bus.on('test-event', handler); bus.on('test-event', handler); bus.off('test-event', handler); bus.emit('test-event'); expect(handler).toHaveBeenCalledTimes(1); }); }); describe('once', () => { it('should call handler only once', () => { const bus = new EventBus(); const handler = vi.fn(); bus.once('once-event', handler); bus.emit('once-event'); bus.emit('once-event'); expect(handler).toHaveBeenCalledTimes(1); }); it('should pass arguments to once handler', () => { const bus = new EventBus(); const handler = vi.fn(); bus.once('once-event', handler); bus.emit('once-event', 'arg1', 'arg2'); expect(handler).toHaveBeenCalledWith('arg1', 'arg2'); }); it('should work with on and once mixed', () => { const bus = new EventBus(); const onHandler = vi.fn(); const onceHandler = vi.fn(); bus.on('mixed-event', onHandler); bus.once('mixed-event', onceHandler); bus.emit('mixed-event'); bus.emit('mixed-event'); expect(onHandler).toHaveBeenCalledTimes(2); expect(onceHandler).toHaveBeenCalledTimes(1); }); it('should not throw if once handler throws', () => { const bus = new EventBus(); const handler = vi.fn(() => { throw new Error('Once handler error'); }); bus.once('once-error', handler); expect(() => bus.emit('once-error')).not.toThrow(); expect(consoleSpy).toHaveBeenCalled(); }); }); describe('destroy', () => { it('should clear all handlers', () => { const bus = new EventBus(); const handler1 = vi.fn(); const handler2 = vi.fn(); bus.on('event-a', handler1); bus.on('event-b', handler2); bus.destroy(); bus.emit('event-a'); bus.emit('event-b'); expect(handler1).not.toHaveBeenCalled(); expect(handler2).not.toHaveBeenCalled(); }); it('should handle destroy on empty bus', () => { const bus = new EventBus(); expect(() => bus.destroy()).not.toThrow(); }); it('should work with once handlers after destroy', () => { const bus = new EventBus(); const handler = vi.fn(); bus.once('event', handler); bus.destroy(); bus.emit('event'); expect(handler).not.toHaveBeenCalled(); }); }); });