diff --git a/packages/stdlib/src/patterns/behavioral/pubsub/index.test.ts b/packages/stdlib/src/patterns/behavioral/pubsub/index.test.ts new file mode 100644 index 0000000..629e23c --- /dev/null +++ b/packages/stdlib/src/patterns/behavioral/pubsub/index.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { PubSub } from './index'; + +describe('PubSub', () => { + let eventBus: PubSub<{ + event1: (arg: string) => void; + event2: () => void + }>; + + beforeEach(() => { + eventBus = new PubSub(); + }); + + it('add a listener and emit an event', () => { + const listener = vi.fn(); + + eventBus.on('event1', listener); + eventBus.emit('event1', 'Hello'); + + expect(listener).toHaveBeenCalledWith('Hello'); + }); + + it('add multiple listeners and emit an event', () => { + const listener1 = vi.fn(); + const listener2 = vi.fn(); + + eventBus.on('event1', listener1); + eventBus.on('event1', listener2); + eventBus.emit('event1', 'Hello'); + + expect(listener1).toHaveBeenCalledWith('Hello'); + expect(listener2).toHaveBeenCalledWith('Hello'); + }); + + it('add a one-time listener and emit an event', () => { + const listener = vi.fn(); + + eventBus.once('event1', listener); + eventBus.emit('event1', 'Hello'); + eventBus.emit('event1', 'World'); + + expect(listener).toHaveBeenCalledWith('Hello'); + expect(listener).not.toHaveBeenCalledWith('World'); + }); + + it('add once listener and emit multiple events', () => { + const listener = vi.fn(); + + eventBus.once('event1', listener); + eventBus.emit('event1', 'Hello'); + eventBus.emit('event1', 'World'); + eventBus.emit('event1', '!'); + + expect(listener).toHaveBeenCalledTimes(1); + expect(listener).toHaveBeenCalledWith('Hello'); + }); + + it('remove a listener', () => { + const listener = vi.fn(); + + eventBus.on('event1', listener); + eventBus.off('event1', listener); + eventBus.emit('event1', 'Hello'); + + expect(listener).not.toHaveBeenCalled(); + }); + + it('clear all listeners for an event', () => { + const listener1 = vi.fn(); + const listener2 = vi.fn(); + + eventBus.on('event1', listener1); + eventBus.on('event1', listener2); + eventBus.clear('event1'); + eventBus.emit('event1', 'Hello'); + + expect(listener1).not.toHaveBeenCalled(); + expect(listener2).not.toHaveBeenCalled(); + }); + + it('return true when emitting an event with listeners', () => { + const listener = vi.fn(); + + eventBus.on('event1', listener); + const result = eventBus.emit('event1', 'Hello'); + + expect(result).toBe(true); + }); + + it('return false when emitting an event without listeners', () => { + const result = eventBus.emit('event1', 'Hello'); + + expect(result).toBe(false); + }); +}); \ No newline at end of file diff --git a/packages/stdlib/src/patterns/behavioral/pubsub/index.ts b/packages/stdlib/src/patterns/behavioral/pubsub/index.ts new file mode 100644 index 0000000..5d292fa --- /dev/null +++ b/packages/stdlib/src/patterns/behavioral/pubsub/index.ts @@ -0,0 +1,58 @@ +export type Subscriber = (...args: any[]) => void; +export type EventsRecord = Record; + +export class PubSub { + private events: Map>; + + constructor() { + this.events = new Map(); + } + + public on(event: K, listener: Events[K]) { + const listeners = this.events.get(event); + + if (listeners) + listeners.add(listener); + else + this.events.set(event, new Set([listener])); + + return this; + } + + public off(event: K, listener: Events[K]) { + const listeners = this.events.get(event); + + if (listeners) + listeners.delete(listener); + + return this; + } + + public once(event: K, listener: Events[K]) { + const onceListener = (...args: Parameters) => { + this.off(event, onceListener as Events[K]); + listener(...args); + }; + + this.on(event, onceListener as Events[K]); + + return this; + } + + public emit(event: K, ...args: Parameters): boolean { + const listeners = this.events.get(event); + + if (!listeners) + return false; + + listeners.forEach((listener) => listener(...args)); + + return true; + } + + public clear(event: K) { + this.events.delete(event); + + return this; + } +}