diff --git a/packages/shared/src/__tests__/bus.test.ts b/packages/shared/src/__tests__/bus.test.ts new file mode 100644 index 000000000..f5105a793 --- /dev/null +++ b/packages/shared/src/__tests__/bus.test.ts @@ -0,0 +1,81 @@ +import { createBus } from 'bus'; + +describe('bus', () => { + it('should be a function', () => { + expect(createBus).toBeInstanceOf(Function); + }); + + it('should return a bus', () => { + const bus = createBus(); + expect(bus).toBeInstanceOf(Object); + expect(bus.emit).toBeInstanceOf(Function); + expect(bus.on).toBeInstanceOf(Function); + }); + + it('should emit events', () => { + const bus = createBus(); + const spy = jest.fn(); + bus.on('test', spy); + bus.emit('test'); + expect(spy).toHaveBeenCalled(); + }); + + it('should emit events with data', () => { + const bus = createBus(); + const spy = jest.fn(); + bus.on('test', spy); + bus.emit('test', 'testData'); + expect(spy).toHaveBeenCalledWith('testData'); + }); + + it('should emit events with multiple listeners', () => { + const bus = createBus(); + const spy1 = jest.fn(); + const spy2 = jest.fn(); + bus.on('test', spy1); + bus.on('test', spy2); + bus.emit('test'); + expect(spy1).toHaveBeenCalled(); + expect(spy2).toHaveBeenCalled(); + }); + + it('should emit events with multiple listeners and data', () => { + const bus = createBus(); + const spy1 = jest.fn(); + const spy2 = jest.fn(); + bus.on('test', spy1); + bus.on('test', spy2); + bus.emit('test', 'test1'); + expect(spy1).toHaveBeenCalledWith('test1'); + expect(spy2).toHaveBeenCalledWith('test1'); + }); + + test('on returns an object with an `off` function', () => { + const bus = createBus(); + const spy = jest.fn(); + const off = bus.on('test', spy); + expect(off).toBeInstanceOf(Object); + expect(off.off).toBeInstanceOf(Function); + }); + + test('off should remove a listener', () => { + const bus = createBus(); + const spy = jest.fn(); + const off = bus.on('test', spy); + off.off(); + bus.emit('test'); + expect(spy).not.toHaveBeenCalled(); + }); + + test('off should only remove specific handler', () => { + const bus = createBus(); + const spy1 = jest.fn(); + const spy2 = jest.fn(); + const off = bus.on('test', spy1); + bus.on('test', spy2); + off.off(); + bus.emit('test'); + expect(spy1).not.toHaveBeenCalled(); + expect(spy2).toHaveBeenCalled(); + }); +}); diff --git a/packages/shared/src/bus.ts b/packages/shared/src/bus.ts new file mode 100644 index 000000000..0a374ad79 --- /dev/null +++ b/packages/shared/src/bus.ts @@ -0,0 +1,32 @@ +export function createBus(): { + on: (event: string, handler: (...args: any[]) => void) => { off: () => void }; + emit: (event: string, ...args: any[]) => void; +} { + const listeners: Record void)[]> = {}; + + return { + emit(event: string, data: any) { + if (!listeners[event]) { + return; + } + + listeners[event].forEach(listener => { + listener(data); + }); + }, + + on(event: string, handler: (...args: any[]) => void): { off: () => void } { + if (!listeners[event]) { + listeners[event] = []; + } + + listeners[event].push(handler); + + return { + off() { + listeners[event] = listeners[event].filter(h => h !== handler); + }, + }; + }, + }; +} diff --git a/tsconfig.json b/tsconfig.json index d92cdf355..00533cb74 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -90,6 +90,7 @@ "asArray": ["./packages/shared/src/asArray.ts"], "assign": ["./packages/shared/src/assign.ts"], "bindNot": ["./packages/shared/src/bindNot.ts"], + "bus": ["./packages/shared/src/bus.ts"], "cache": ["./packages/shared/src/cache.ts"], "callEach": ["./packages/shared/src/callEach.ts"], "defaultTo": ["./packages/shared/src/defaultTo.ts"],