import { beforeEach, describe, expect, it, mock } from 'bun:test'; import { SimpleEventBus } from '../src/simple-event-bus'; import type { EventHandler, EventSubscription, EventBusMessage } from '../src/types'; describe('EventBus', () => { let eventBus: SimpleEventBus; beforeEach(() => { eventBus = new SimpleEventBus(); }); describe('subscribe', () => { it('should subscribe to events', () => { const handler: EventHandler = mock(async () => {}); const subscription = eventBus.subscribe('test-event', handler); expect(subscription).toBeDefined(); expect(subscription.channel).toBe('test-event'); expect(subscription.handler).toBe(handler); }); it('should allow multiple subscribers to same event', () => { const handler1 = mock(async () => {}); const handler2 = mock(async () => {}); const sub1 = eventBus.subscribe('event', handler1); const sub2 = eventBus.subscribe('event', handler2); expect(sub1.handler).toBe(handler1); expect(sub2.handler).toBe(handler2); }); it('should support pattern subscriptions', () => { const handler = mock(async () => {}); const subscription = eventBus.subscribe('user.*', handler); expect(subscription.channel).toBe('user.*'); }); }); describe('unsubscribe', () => { it('should unsubscribe by subscription object', () => { const handler = mock(async () => {}); const subscription = eventBus.subscribe('event', handler); const result = eventBus.unsubscribe(subscription); expect(result).toBe(true); }); it('should unsubscribe by id', () => { const handler = mock(async () => {}); eventBus.subscribe('event', handler); // We'll use the subscription object method since we don't expose IDs const result = eventBus.unsubscribe('sub-1'); expect(result).toBe(true); }); it('should return false for non-existent subscription', () => { const result = eventBus.unsubscribe('non-existent'); expect(result).toBe(false); }); }); describe('publish', () => { it('should publish events to subscribers', async () => { const handler = mock(async (message: EventBusMessage) => {}); eventBus.subscribe('event', handler); await eventBus.publish('event', { data: 'test' }); expect(handler).toHaveBeenCalledTimes(1); const message = handler.mock.calls[0][0]; expect(message.type).toBe('event'); expect(message.data).toEqual({ data: 'test' }); }); it('should publish to multiple subscribers', async () => { const handler1 = mock(async () => {}); const handler2 = mock(async () => {}); eventBus.subscribe('event', handler1); eventBus.subscribe('event', handler2); await eventBus.publish('event', { data: 'test' }); expect(handler1).toHaveBeenCalledTimes(1); expect(handler2).toHaveBeenCalledTimes(1); }); it('should match pattern subscriptions', async () => { const handler = mock(async () => {}); eventBus.subscribe('user.*', handler); await eventBus.publish('user.created', { userId: '123' }); await eventBus.publish('user.updated', { userId: '123' }); await eventBus.publish('order.created', { orderId: '456' }); expect(handler).toHaveBeenCalledTimes(2); }); it('should handle errors in handlers gracefully', async () => { const errorHandler = mock(async () => { throw new Error('Handler error'); }); const successHandler = mock(async () => {}); eventBus.subscribe('event', errorHandler); eventBus.subscribe('event', successHandler); await eventBus.publish('event', { data: 'test' }); expect(errorHandler).toHaveBeenCalledTimes(1); expect(successHandler).toHaveBeenCalledTimes(1); }); }); describe('publishSync', () => { it('should publish synchronously', () => { const handler = mock((message: EventBusMessage) => {}); eventBus.subscribe('event', handler); eventBus.publishSync('event', { data: 'test' }); expect(handler).toHaveBeenCalledTimes(1); const message = handler.mock.calls[0][0]; expect(message.type).toBe('event'); expect(message.data).toEqual({ data: 'test' }); }); }); describe('once', () => { it('should subscribe for single event', async () => { const handler = mock(async () => {}); eventBus.once('event', handler); await eventBus.publish('event', { data: 'first' }); await eventBus.publish('event', { data: 'second' }); expect(handler).toHaveBeenCalledTimes(1); }); }); describe('off', () => { it('should remove all handlers for event', async () => { const handler1 = mock(async () => {}); const handler2 = mock(async () => {}); eventBus.subscribe('event', handler1); eventBus.subscribe('event', handler2); eventBus.off('event'); await eventBus.publish('event', { data: 'test' }); expect(handler1).not.toHaveBeenCalled(); expect(handler2).not.toHaveBeenCalled(); }); it('should remove specific handler', async () => { const handler1 = mock(async () => {}); const handler2 = mock(async () => {}); eventBus.subscribe('event', handler1); eventBus.subscribe('event', handler2); eventBus.off('event', handler1); await eventBus.publish('event', { data: 'test' }); expect(handler1).not.toHaveBeenCalled(); expect(handler2).toHaveBeenCalledTimes(1); }); }); describe('hasSubscribers', () => { it('should check for subscribers', () => { expect(eventBus.hasSubscribers('event')).toBe(false); const handler = mock(async () => {}); eventBus.subscribe('event', handler); expect(eventBus.hasSubscribers('event')).toBe(true); eventBus.off('event'); expect(eventBus.hasSubscribers('event')).toBe(false); }); }); describe('clear', () => { it('should clear all subscriptions', async () => { const handler1 = mock(async () => {}); const handler2 = mock(async () => {}); eventBus.subscribe('event1', handler1); eventBus.subscribe('event2', handler2); eventBus.clear(); await eventBus.publish('event1', {}); await eventBus.publish('event2', {}); expect(handler1).not.toHaveBeenCalled(); expect(handler2).not.toHaveBeenCalled(); }); }); });