From fa0672ee64bacc46ff98f478be68a5afa79b947f Mon Sep 17 00:00:00 2001 From: Andi Wilson Date: Thu, 6 Jun 2024 14:42:31 -0700 Subject: [PATCH 1/2] feat: add method to remove all listeners --- src/event-mixin.spec.ts | 34 ++++++++++++++++++++++++++++++++++ src/event-mixin.ts | 12 ++++++++++++ src/typed-event.ts | 7 +++++++ 3 files changed, 53 insertions(+) diff --git a/src/event-mixin.spec.ts b/src/event-mixin.spec.ts index 36a4e60..454ef5c 100644 --- a/src/event-mixin.spec.ts +++ b/src/event-mixin.spec.ts @@ -42,6 +42,22 @@ describe('a class with events', () => { expect(handlerTwoArgs).toHaveLength(1); expect(handlerTwoArgs[0]).toBe(42); }); + + it('should remove all handlers when removeAllListeners is called', () => { + expect.hasAssertions(); + const handlerOneArgs: number[] = []; + const handler = (value: number) => { + handlerOneArgs.push(value); + }; + myClass.on('eventOne', handler); + myClass.fireEventOne(); + expect(handlerOneArgs).toHaveLength(1); + expect(handlerOneArgs[0]).toBe(42); + + myClass.removeAllListeners(); + myClass.fireEventOne(); + expect(handlerOneArgs).toHaveLength(1); // Should still be 1, as all listeners should be removed + }); }); interface ChildEvents { @@ -75,4 +91,22 @@ describe('a child class', () => { expect(eventOneArgs).toHaveLength(1); expect(eventThreeArgs).toHaveLength(1); }); + + it('should remove all handlers from parent and child when removeAllListeners is called', () => { + expect.hasAssertions(); + + const eventOneArgs: number[] = []; + const eventThreeArgs: string[] = []; + + myClass.on('eventOne', (value: number) => eventOneArgs.push(value)); + myClass.on('eventThree', (value: string) => eventThreeArgs.push(value)); + + myClass.removeAllListeners(); + + myClass.fireEventOne(); + myClass.fireEventThree(); + + expect(eventOneArgs).toHaveLength(0); // Should be 0, as all listeners should be removed + expect(eventThreeArgs).toHaveLength(0); // Should be 0, as all listeners should be removed + }); }); diff --git a/src/event-mixin.ts b/src/event-mixin.ts index b3b0a72..324a044 100644 --- a/src/event-mixin.ts +++ b/src/event-mixin.ts @@ -63,6 +63,18 @@ export function AddEvents(Base: TBase) { off>(eventName: K, handler: E) { (this as any)[eventName].off(handler); } + + /** + * Remove all event listeners from all events. + */ + removeAllListeners() { + Object.keys(this).forEach((eventName) => { + const event = (this as any)[eventName]; + if (event instanceof TypedEvent) { + event.removeAllListeners(); + } + }); + } }; } diff --git a/src/typed-event.ts b/src/typed-event.ts index 3189c6d..eb2155d 100644 --- a/src/typed-event.ts +++ b/src/typed-event.ts @@ -57,4 +57,11 @@ export class TypedEvent { emit(...args: Parameters): void { this.emitter.emit('event', ...args); } + + /** + * Remove all listeners from this event. + */ + removeAllListeners(): void { + this.emitter.removeAllListeners('event'); + } } From 5d7a206bf5db2474d084b31c585942026a6f85a6 Mon Sep 17 00:00:00 2001 From: Andi Wilson Date: Mon, 10 Jun 2024 12:39:53 -0700 Subject: [PATCH 2/2] fix: ensure methods are checked on the prototype chain --- src/event-mixin.spec.ts | 13 +++++++++++++ src/event-mixin.ts | 23 ++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/event-mixin.spec.ts b/src/event-mixin.spec.ts index 454ef5c..adc22c6 100644 --- a/src/event-mixin.spec.ts +++ b/src/event-mixin.spec.ts @@ -109,4 +109,17 @@ describe('a child class', () => { expect(eventOneArgs).toHaveLength(0); // Should be 0, as all listeners should be removed expect(eventThreeArgs).toHaveLength(0); // Should be 0, as all listeners should be removed }); + + it('should remove all handlers from inherited classes when removeAllListeners is called', () => { + expect.hasAssertions(); + + const eventTwoArgs: boolean[] = []; + myClass.on('eventTwo', (value: boolean) => eventTwoArgs.push(value)); + + myClass.removeAllListeners(); + + myClass.fireEventTwo(); + + expect(eventTwoArgs).toHaveLength(0); // Should be 0, as all listeners should be removed + }); }); diff --git a/src/event-mixin.ts b/src/event-mixin.ts index 324a044..b89c625 100644 --- a/src/event-mixin.ts +++ b/src/event-mixin.ts @@ -68,12 +68,25 @@ export function AddEvents(Base: TBase) { * Remove all event listeners from all events. */ removeAllListeners() { - Object.keys(this).forEach((eventName) => { - const event = (this as any)[eventName]; - if (event instanceof TypedEvent) { - event.removeAllListeners(); + /** + * Recursively remove all listeners from the given object and its prototype chain. This is + * necessary because the events may be defined on the prototype chain. + * + * @param obj - The object to remove listeners from. + */ + function removeListeners(obj: any) { + if (!obj || obj === Object.prototype) { + return; } - }); + Object.keys(obj).forEach((eventName) => { + const event = obj[eventName]; + if (event instanceof TypedEvent) { + event.removeAllListeners(); + } + }); + removeListeners(Object.getPrototypeOf(obj)); + } + removeListeners(this); } }; }