From 3fc856c1abc8a41a7c3d7e0db743d94140466a13 Mon Sep 17 00:00:00 2001 From: krutoo Date: Thu, 21 Mar 2024 10:22:11 +0500 Subject: [PATCH] #38 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - preset/node: добавлен адаптер EventEmitter в EventTarget (minor) --- .../utils/__test__/emitter-as-target.test.ts | 99 +++++++++++++++++++ src/preset/node/utils/emitter-as-target.ts | 54 ++++++++++ 2 files changed, 153 insertions(+) create mode 100644 src/preset/node/utils/__test__/emitter-as-target.test.ts create mode 100644 src/preset/node/utils/emitter-as-target.ts diff --git a/src/preset/node/utils/__test__/emitter-as-target.test.ts b/src/preset/node/utils/__test__/emitter-as-target.test.ts new file mode 100644 index 0000000..9f7b717 --- /dev/null +++ b/src/preset/node/utils/__test__/emitter-as-target.test.ts @@ -0,0 +1,99 @@ +import { EventEmitter } from 'node:events'; +import { EmitterAsTarget } from '../emitter-as-target'; + +describe('EmitterAsTarget', () => { + it('should works as regular event target', () => { + const emitter = new EventEmitter(); + const target = new EmitterAsTarget(emitter); + + const spy = jest.fn(); + + target.addEventListener('test', spy); + expect(spy).toHaveBeenCalledTimes(0); + + target.dispatchEvent(new Event('test')); + expect(spy).toHaveBeenCalledTimes(1); + + target.dispatchEvent(new Event('test')); + expect(spy).toHaveBeenCalledTimes(2); + + target.removeEventListener('test', spy); + expect(spy).toHaveBeenCalledTimes(2); + + target.dispatchEvent(new Event('test')); + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('should callback as object', () => { + const emitter = new EventEmitter(); + const target = new EmitterAsTarget(emitter); + + const spy = jest.fn(); + + target.addEventListener('test', { handleEvent: spy }); + expect(spy).toHaveBeenCalledTimes(0); + + target.dispatchEvent(new Event('test')); + expect(spy).toHaveBeenCalledTimes(1); + + target.dispatchEvent(new Event('test')); + expect(spy).toHaveBeenCalledTimes(2); + + target.removeEventListener('test', { handleEvent: spy }); + expect(spy).toHaveBeenCalledTimes(2); + + target.dispatchEvent(new Event('test')); + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('should handle callback as null', () => { + const emitter = new EventEmitter(); + const target = new EmitterAsTarget(emitter); + + expect(() => { + target.addEventListener('foo', null); + }).not.toThrow(); + + expect(() => { + target.removeEventListener('foo', null); + }).not.toThrow(); + }); + + it('should work through emitter', () => { + const emitter = new EventEmitter(); + const target = new EmitterAsTarget(emitter); + + const spy = jest.fn(); + + emitter.on('test', spy); + expect(spy).toHaveBeenCalledTimes(0); + + target.dispatchEvent(new Event('test')); + expect(spy).toHaveBeenCalledTimes(1); + + target.dispatchEvent(new Event('test')); + expect(spy).toHaveBeenCalledTimes(2); + + emitter.removeListener('test', spy); + expect(spy).toHaveBeenCalledTimes(2); + + target.dispatchEvent(new Event('test')); + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('should handle options.once', () => { + const emitter = new EventEmitter(); + const target = new EmitterAsTarget(emitter); + + const spy = jest.fn(); + + target.addEventListener('foo', spy, { once: true }); + expect(spy).toHaveBeenCalledTimes(0); + + target.dispatchEvent(new Event('foo')); + expect(spy).toHaveBeenCalledTimes(1); + + target.dispatchEvent(new Event('foo')); + expect(spy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/preset/node/utils/emitter-as-target.ts b/src/preset/node/utils/emitter-as-target.ts new file mode 100644 index 0000000..bfce184 --- /dev/null +++ b/src/preset/node/utils/emitter-as-target.ts @@ -0,0 +1,54 @@ +import type { EventEmitter } from 'node:events'; + +/** + * Наивная реализация обёртки, превращающей EventEmitter в EventTarget. + */ +export class EmitterAsTarget extends EventTarget { + private emitter: EventEmitter; + + /** @inheritdoc */ + constructor(emitter: EventEmitter) { + super(); + this.emitter = emitter; + } + + /** @inheritdoc */ + addEventListener( + type: string, + callback: EventListenerOrEventListenerObject | null, + options?: AddEventListenerOptions | boolean, + ) { + if (!callback) { + return; + } + + const listener = typeof callback === 'function' ? callback : callback.handleEvent; + + switch (true) { + case typeof options === 'object' && options !== null && options.once: { + this.emitter.once(type, listener as (...args: any[]) => void); + break; + } + default: { + this.emitter.on(type, listener as (...args: any[]) => void); + break; + } + } + } + + /** @inheritdoc */ + removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null) { + if (!callback) { + return; + } + + const listener = typeof callback === 'function' ? callback : callback.handleEvent; + + this.emitter.removeListener(type, listener as (...args: any[]) => void); + } + + /** @inheritdoc */ + dispatchEvent(event: Event) { + return this.emitter.emit(event.type, event); + } +}