mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 10:54:44 +00:00
refactor: change separate tools by category
This commit is contained in:
118
core/stdlib/src/patterns/behavioral/pubsub/index.test.ts
Normal file
118
core/stdlib/src/patterns/behavioral/pubsub/index.test.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { PubSub } from '.';
|
||||
|
||||
describe('pubsub', () => {
|
||||
const event3 = Symbol('event3');
|
||||
|
||||
let eventBus: PubSub<{
|
||||
event1: (arg: string) => void;
|
||||
event2: () => void;
|
||||
[event3]: () => 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('emit symbol event', () => {
|
||||
const listener = vi.fn();
|
||||
|
||||
eventBus.on(event3, listener);
|
||||
eventBus.emit(event3);
|
||||
|
||||
expect(listener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('calls listener only once when the same function is registered multiple times', () => {
|
||||
const listener = vi.fn();
|
||||
|
||||
eventBus.on('event1', listener);
|
||||
eventBus.on('event1', listener);
|
||||
eventBus.on('event1', listener);
|
||||
eventBus.emit('event1', 'Hello');
|
||||
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
118
core/stdlib/src/patterns/behavioral/pubsub/index.ts
Normal file
118
core/stdlib/src/patterns/behavioral/pubsub/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import type { AnyFunction } from '../../../types';
|
||||
|
||||
export type Subscriber = AnyFunction;
|
||||
|
||||
export type EventHandlerMap = Record<PropertyKey, Subscriber>;
|
||||
|
||||
/**
|
||||
* @name PubSub
|
||||
* @category Patterns
|
||||
* @description Simple PubSub implementation
|
||||
*
|
||||
* @since 0.0.2
|
||||
*
|
||||
* @template Events - Event map where all values are function types
|
||||
*/
|
||||
export class PubSub<Events extends EventHandlerMap> {
|
||||
/**
|
||||
* Events map
|
||||
*
|
||||
* @private
|
||||
* @type {Map<keyof Events, Set<Events[keyof Events]>>}
|
||||
*/
|
||||
private events: Map<keyof Events, Set<Events[keyof Events]>>;
|
||||
|
||||
/**
|
||||
* Creates an instance of PubSub
|
||||
*/
|
||||
constructor() {
|
||||
this.events = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to an event
|
||||
*
|
||||
* @template {keyof Events} K
|
||||
* @param {K} event Name of the event
|
||||
* @param {Events[K]} listener Listener function
|
||||
* @returns {this}
|
||||
*/
|
||||
public on<K extends keyof Events>(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from an event
|
||||
*
|
||||
* @template {keyof Events} K
|
||||
* @param {K} event Name of the event
|
||||
* @param {Events[K]} listener Listener function
|
||||
* @returns {this}
|
||||
*/
|
||||
public off<K extends keyof Events>(event: K, listener: Events[K]) {
|
||||
const listeners = this.events.get(event);
|
||||
|
||||
if (listeners)
|
||||
listeners.delete(listener);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to an event only once
|
||||
*
|
||||
* @template {keyof Events} K
|
||||
* @param {K} event Name of the event
|
||||
* @param {Events[K]} listener Listener function
|
||||
* @returns {this}
|
||||
*/
|
||||
public once<K extends keyof Events>(event: K, listener: Events[K]) {
|
||||
const onceListener = (...args: Parameters<Events[K]>) => {
|
||||
this.off(event, onceListener as Events[K]);
|
||||
listener(...args);
|
||||
};
|
||||
|
||||
this.on(event, onceListener as Events[K]);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event
|
||||
*
|
||||
* @template {keyof Events} K
|
||||
* @param {K} event Name of the event
|
||||
* @param {...Parameters<Events[K]>} args Arguments for the listener
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>) {
|
||||
const listeners = this.events.get(event);
|
||||
|
||||
if (!listeners)
|
||||
return false;
|
||||
|
||||
listeners.forEach((listener) => listener(...args));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all listeners for an event
|
||||
*
|
||||
* @template {keyof Events} K
|
||||
* @param {K} event Name of the event
|
||||
* @returns {this}
|
||||
*/
|
||||
public clear<K extends keyof Events>(event: K) {
|
||||
this.events.delete(event);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
1
core/stdlib/src/patterns/index.ts
Normal file
1
core/stdlib/src/patterns/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './behavioral/pubsub';
|
||||
Reference in New Issue
Block a user