removed old tests, created new ones and format

This commit is contained in:
Boki 2025-06-25 07:46:59 -04:00
parent 7579afa3c3
commit b03231b849
57 changed files with 4092 additions and 5901 deletions

View file

@ -0,0 +1,206 @@
import { beforeEach, describe, expect, it, mock } from 'bun:test';
import { SimpleEventBus } from './simple-event-bus';
import type { EventHandler, EventSubscription } from './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.id).toBeDefined();
expect(subscription.event).toBe('test-event');
});
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.id).not.toBe(sub2.id);
});
it('should support pattern subscriptions', () => {
const handler = mock(async () => {});
const subscription = eventBus.subscribe('user.*', handler);
expect(subscription.event).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 () => {});
const subscription = eventBus.subscribe('event', handler);
const result = eventBus.unsubscribe(subscription.id);
expect(result).toBe(true);
});
it('should return false for non-existent subscription', () => {
const result = eventBus.unsubscribe('non-existent-id');
expect(result).toBe(false);
});
});
describe('publish', () => {
it('should publish events to subscribers', async () => {
const handler = mock(async (data: any) => {});
eventBus.subscribe('test-event', handler);
await eventBus.publish('test-event', { message: 'hello' });
expect(handler).toHaveBeenCalledWith({ message: 'hello' }, 'test-event');
});
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).toHaveBeenCalledWith({ data: 'test' }, 'event');
expect(handler2).toHaveBeenCalledWith({ data: 'test' }, 'event');
});
it('should match pattern subscriptions', async () => {
const handler = mock(async () => {});
eventBus.subscribe('user.*', handler);
await eventBus.publish('user.created', { id: 1 });
await eventBus.publish('user.updated', { id: 2 });
await eventBus.publish('order.created', { id: 3 });
expect(handler).toHaveBeenCalledTimes(2);
expect(handler).toHaveBeenCalledWith({ id: 1 }, 'user.created');
expect(handler).toHaveBeenCalledWith({ id: 2 }, 'user.updated');
});
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', {});
expect(successHandler).toHaveBeenCalled();
});
});
describe('publishSync', () => {
it('should publish synchronously', () => {
const results: any[] = [];
const handler = (data: any) => {
results.push(data);
};
eventBus.subscribe('sync-event', handler as any);
eventBus.publishSync('sync-event', { value: 42 });
expect(results).toEqual([{ value: 42 }]);
});
});
describe('once', () => {
it('should subscribe for single event', async () => {
const handler = mock(async () => {});
eventBus.once('once-event', handler);
await eventBus.publish('once-event', { first: true });
await eventBus.publish('once-event', { second: true });
expect(handler).toHaveBeenCalledTimes(1);
expect(handler).toHaveBeenCalledWith({ first: true }, 'once-event');
});
});
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', {});
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', {});
expect(handler1).not.toHaveBeenCalled();
expect(handler2).toHaveBeenCalled();
});
});
describe('hasSubscribers', () => {
it('should check for subscribers', () => {
expect(eventBus.hasSubscribers('event')).toBe(false);
const sub = eventBus.subscribe('event', async () => {});
expect(eventBus.hasSubscribers('event')).toBe(true);
eventBus.unsubscribe(sub);
expect(eventBus.hasSubscribers('event')).toBe(false);
});
});
describe('clear', () => {
it('should clear all subscriptions', async () => {
const handler = mock(async () => {});
eventBus.subscribe('event1', handler);
eventBus.subscribe('event2', handler);
eventBus.clear();
await eventBus.publish('event1', {});
await eventBus.publish('event2', {});
expect(handler).not.toHaveBeenCalled();
});
});
});

View file

@ -0,0 +1,150 @@
import type { EventHandler, EventSubscription } from './types';
/**
* Simple in-memory event bus for testing
*/
export class SimpleEventBus {
private subscriptions = new Map<string, Set<EventSubscription>>();
private subscriptionById = new Map<string, EventSubscription>();
private nextId = 1;
subscribe(event: string, handler: EventHandler): EventSubscription {
const subscription: EventSubscription = {
id: `sub-${this.nextId++}`,
event,
handler,
pattern: event.includes('*'),
};
if (!this.subscriptions.has(event)) {
this.subscriptions.set(event, new Set());
}
this.subscriptions.get(event)!.add(subscription);
this.subscriptionById.set(subscription.id, subscription);
return subscription;
}
unsubscribe(idOrSubscription: string | EventSubscription): boolean {
const id = typeof idOrSubscription === 'string' ? idOrSubscription : idOrSubscription.id;
const subscription = this.subscriptionById.get(id);
if (!subscription) {
return false;
}
const eventSubs = this.subscriptions.get(subscription.event);
if (eventSubs) {
eventSubs.delete(subscription);
if (eventSubs.size === 0) {
this.subscriptions.delete(subscription.event);
}
}
this.subscriptionById.delete(id);
return true;
}
async publish(event: string, data: any): Promise<void> {
const handlers: EventHandler[] = [];
// Direct matches
const directSubs = this.subscriptions.get(event);
if (directSubs) {
handlers.push(...Array.from(directSubs).map(s => s.handler));
}
// Pattern matches
for (const [pattern, subs] of this.subscriptions) {
if (pattern.includes('*') && this.matchPattern(pattern, event)) {
handlers.push(...Array.from(subs).map(s => s.handler));
}
}
// Execute all handlers
await Promise.all(
handlers.map(handler =>
handler(data, event).catch(err => {
// Silently catch errors
})
)
);
}
publishSync(event: string, data: any): void {
const handlers: EventHandler[] = [];
// Direct matches
const directSubs = this.subscriptions.get(event);
if (directSubs) {
handlers.push(...Array.from(directSubs).map(s => s.handler));
}
// Pattern matches
for (const [pattern, subs] of this.subscriptions) {
if (pattern.includes('*') && this.matchPattern(pattern, event)) {
handlers.push(...Array.from(subs).map(s => s.handler));
}
}
// Execute all handlers synchronously
handlers.forEach(handler => {
try {
handler(data, event);
} catch {
// Silently catch errors
}
});
}
once(event: string, handler: EventHandler): EventSubscription {
const wrappedHandler: EventHandler = async (data, evt) => {
await handler(data, evt);
this.unsubscribe(subscription.id);
};
const subscription = this.subscribe(event, wrappedHandler);
return subscription;
}
off(event: string, handler?: EventHandler): void {
if (!handler) {
// Remove all handlers for this event
const subs = this.subscriptions.get(event);
if (subs) {
for (const sub of subs) {
this.subscriptionById.delete(sub.id);
}
this.subscriptions.delete(event);
}
} else {
// Remove specific handler
const subs = this.subscriptions.get(event);
if (subs) {
const toRemove = Array.from(subs).filter(s => s.handler === handler);
toRemove.forEach(sub => {
subs.delete(sub);
this.subscriptionById.delete(sub.id);
});
if (subs.size === 0) {
this.subscriptions.delete(event);
}
}
}
}
hasSubscribers(event: string): boolean {
return this.subscriptions.has(event) && this.subscriptions.get(event)!.size > 0;
}
clear(): void {
this.subscriptions.clear();
this.subscriptionById.clear();
}
private matchPattern(pattern: string, event: string): boolean {
// Simple pattern matching: user.* matches user.created, user.updated, etc.
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
return regex.test(event);
}
}