From f987f722df471e70a53c0f9aec29b67122bc70ad Mon Sep 17 00:00:00 2001 From: robonen Date: Tue, 16 Apr 2024 16:32:56 +0700 Subject: [PATCH 1/2] feat(packages/stdlib): add PubSub class and tests to patterns --- .../patterns/behavioral/pubsub/index.test.ts | 95 +++++++++++++++++++ .../src/patterns/behavioral/pubsub/index.ts | 58 +++++++++++ 2 files changed, 153 insertions(+) create mode 100644 packages/stdlib/src/patterns/behavioral/pubsub/index.test.ts create mode 100644 packages/stdlib/src/patterns/behavioral/pubsub/index.ts 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; + } +} From e3ef3a693e40fabcdd3b38cc8449d1fbffc34ca4 Mon Sep 17 00:00:00 2001 From: robonen Date: Tue, 16 Apr 2024 16:39:24 +0700 Subject: [PATCH 2/2] feat(packages/stdlib): add patterns exports --- packages/stdlib/src/index.ts | 3 ++- packages/stdlib/src/patterns/index.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 packages/stdlib/src/patterns/index.ts diff --git a/packages/stdlib/src/index.ts b/packages/stdlib/src/index.ts index cd510a8..9ba9ae6 100644 --- a/packages/stdlib/src/index.ts +++ b/packages/stdlib/src/index.ts @@ -1,2 +1,3 @@ export * from './text'; -export * from './math'; \ No newline at end of file +export * from './math'; +export * from './patterns'; \ No newline at end of file diff --git a/packages/stdlib/src/patterns/index.ts b/packages/stdlib/src/patterns/index.ts new file mode 100644 index 0000000..e509dbc --- /dev/null +++ b/packages/stdlib/src/patterns/index.ts @@ -0,0 +1 @@ +export * from './behavioral/pubsub'; \ No newline at end of file