From ad29eaeb261fe2e357826f25f0bfcef95c066002 Mon Sep 17 00:00:00 2001 From: Konstantin Kovalev Date: Wed, 4 Jun 2025 00:20:39 +0500 Subject: [PATCH] test: ensure unsubscription after emission --- __tests__/eventEmitter.ts | 27 +++++++++++++++++++++++++++ src/eventEmitter.ts | 7 ++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/__tests__/eventEmitter.ts b/__tests__/eventEmitter.ts index d0227d1..d56e01c 100644 --- a/__tests__/eventEmitter.ts +++ b/__tests__/eventEmitter.ts @@ -99,6 +99,33 @@ describe("eventEmitter", () => { expect(logUserLogin2).toHaveBeenCalled(); }); + it("handles unsubscription of one listener by another during emission", () => { + const logUserLogin1 = jest.fn(); + const logUserLogin2 = jest.fn(); + + // eslint-disable-next-line prefer-const + let unsubscribeSecond!: ReturnType; + + subscribe("userLogin", (eventName, data) => { + logUserLogin1(eventName, data); + unsubscribeSecond(); + }); + + unsubscribeSecond = subscribe("userLogin", logUserLogin2); + + // First emission: both listeners should be called once + emit("userLogin", { userId: "user1", timestamp: new Date() }); + + expect(logUserLogin1).toHaveBeenCalledTimes(1); + expect(logUserLogin2).toHaveBeenCalledTimes(1); + + // Second emission: only the first listener should fire + emit("userLogin", { userId: "user1", timestamp: new Date() }); + + expect(logUserLogin1).toHaveBeenCalledTimes(2); + expect(logUserLogin2).toHaveBeenCalledTimes(1); + }); + it("should not throw if emit is called without any listeners", () => { expect(() => { emit("userLogin", { userId: "user1", timestamp: new Date() }); diff --git a/src/eventEmitter.ts b/src/eventEmitter.ts index 93696be..eaac88e 100644 --- a/src/eventEmitter.ts +++ b/src/eventEmitter.ts @@ -478,12 +478,13 @@ export const createEventEmitter = < if (!listenersMapEntry) { return; } - listenersMapEntry.listeners.forEach(({ listener, predicate }) => { + const listeners = [...listenersMapEntry.listeners.values()]; + for (const { listener, predicate } of listeners) { if (predicate && !predicate(data)) { - return; + continue; } listener(eventName, data); - }); + } }; // Unsubscribe all listeners for a specific event or all events