diff --git a/.changeset/pink-boats-type.md b/.changeset/pink-boats-type.md new file mode 100644 index 0000000000..f484fe779e --- /dev/null +++ b/.changeset/pink-boats-type.md @@ -0,0 +1,5 @@ +--- +"@medusajs/test-utils": patch +--- + +fix(test-utils): Event subscribers duplicated durin tests diff --git a/packages/medusa-test-utils/src/__tests__/events.spec.ts b/packages/medusa-test-utils/src/__tests__/events.spec.ts index 15c788dd4d..c7fa0ee4a4 100644 --- a/packages/medusa-test-utils/src/__tests__/events.spec.ts +++ b/packages/medusa-test-utils/src/__tests__/events.spec.ts @@ -307,4 +307,120 @@ describe("waitSubscribersExecution", () => { clearTimeoutSpy.mockRestore() }) }) + + describe("nested wrapper handling", () => { + it("should handle multiple consecutive waitSubscribersExecution calls on the same event", async () => { + const originalListener = jest.fn().mockResolvedValue("result") + eventBus.eventEmitter_.on(TEST_EVENT, originalListener) + + // First wait + const wait1 = waitSubscribersExecution(TEST_EVENT, eventBus as any) + eventBus.emit(TEST_EVENT, "data1") + await wait1 + + expect(originalListener).toHaveBeenCalledWith("data1") + expect(originalListener).toHaveBeenCalledTimes(1) + + // Second wait on the same event - should still restore properly + const wait2 = waitSubscribersExecution(TEST_EVENT, eventBus as any) + eventBus.emit(TEST_EVENT, "data2") + await wait2 + + expect(originalListener).toHaveBeenCalledWith("data2") + expect(originalListener).toHaveBeenCalledTimes(2) + + // Original listener should still be intact and work normally + eventBus.emit(TEST_EVENT, "data3") + expect(originalListener).toHaveBeenCalledWith("data3") + expect(originalListener).toHaveBeenCalledTimes(3) + + // Verify we still have the correct number of listeners + const listeners = eventBus.eventEmitter_.listeners(TEST_EVENT) + expect(listeners).toHaveLength(1) + expect(listeners[0]).toBe(originalListener) + }) + + it("should handle three consecutive waitSubscribersExecution calls", async () => { + const originalListener = jest.fn().mockResolvedValue("result") + eventBus.eventEmitter_.on(TEST_EVENT, originalListener) + + // First wait + const wait1 = waitSubscribersExecution(TEST_EVENT, eventBus as any) + eventBus.emit(TEST_EVENT, "data1") + await wait1 + + // Second wait + const wait2 = waitSubscribersExecution(TEST_EVENT, eventBus as any) + eventBus.emit(TEST_EVENT, "data2") + await wait2 + + // Third wait + const wait3 = waitSubscribersExecution(TEST_EVENT, eventBus as any) + eventBus.emit(TEST_EVENT, "data3") + await wait3 + + // Verify the original listener is still properly attached + const listeners = eventBus.eventEmitter_.listeners(TEST_EVENT) + expect(listeners).toHaveLength(1) + expect(listeners[0]).toBe(originalListener) + + // Verify it still works + eventBus.emit(TEST_EVENT, "data4") + expect(originalListener).toHaveBeenCalledWith("data4") + expect(originalListener).toHaveBeenCalledTimes(4) + }) + + it("should handle multiple listeners with consecutive waits", async () => { + const listener1 = jest.fn().mockResolvedValue("result1") + const listener2 = jest.fn().mockResolvedValue("result2") + + eventBus.eventEmitter_.on(TEST_EVENT, listener1) + eventBus.eventEmitter_.on(TEST_EVENT, listener2) + + // First wait + const wait1 = waitSubscribersExecution(TEST_EVENT, eventBus as any) + eventBus.emit(TEST_EVENT, "data1") + await wait1 + + expect(listener1).toHaveBeenCalledWith("data1") + expect(listener2).toHaveBeenCalledWith("data1") + + // Second wait + const wait2 = waitSubscribersExecution(TEST_EVENT, eventBus as any) + eventBus.emit(TEST_EVENT, "data2") + await wait2 + + expect(listener1).toHaveBeenCalledWith("data2") + expect(listener2).toHaveBeenCalledWith("data2") + + // Verify both original listeners are still properly attached + const listeners = eventBus.eventEmitter_.listeners(TEST_EVENT) + expect(listeners).toHaveLength(2) + expect(listeners[0]).toBe(listener1) + expect(listeners[1]).toBe(listener2) + + // Verify they still work normally + eventBus.emit(TEST_EVENT, "data3") + expect(listener1).toHaveBeenCalledWith("data3") + expect(listener2).toHaveBeenCalledWith("data3") + }) + + it("should not add undefined listener when originalListener chain ends", async () => { + // This tests the edge case where listener.originalListener might be undefined + const originalListener = jest.fn().mockResolvedValue("result") + eventBus.eventEmitter_.on(TEST_EVENT, originalListener) + + const wait1 = waitSubscribersExecution(TEST_EVENT, eventBus as any) + eventBus.emit(TEST_EVENT, "data1") + await wait1 + + // Verify we have exactly one listener (the original) + const listeners = eventBus.eventEmitter_.listeners(TEST_EVENT) + expect(listeners).toHaveLength(1) + + // Verify it's not undefined + expect(listeners[0]).toBeDefined() + expect(listeners[0]).toBe(originalListener) + }) + }) }) diff --git a/packages/medusa-test-utils/src/events.ts b/packages/medusa-test-utils/src/events.ts index 4b9cf9e04b..bd711d1d0a 100644 --- a/packages/medusa-test-utils/src/events.ts +++ b/packages/medusa-test-utils/src/events.ts @@ -99,6 +99,7 @@ const doWaitSubscribersExecution = ( }) subscriberPromises.push(promise) let res: any[] = [] + let cleanupDone = false const newListener = async (...args2: any[]) => { try { @@ -121,14 +122,20 @@ const doWaitSubscribersExecution = ( nok(error) } - if (currentCount >= triggerCount) { + if (currentCount >= triggerCount && !cleanupDone) { // As soon as the subscriber is executed the required number of times, we restore the original listener + cleanupDone = true eventEmitter.removeListener(eventName, newListener) + + // Unwrap to find the true original listener let listenerToAdd = listener - while (listenerToAdd.originalListener) { + while (listenerToAdd?.originalListener) { listenerToAdd = listenerToAdd.originalListener } - eventEmitter.on(eventName, listenerToAdd) + + if (listenerToAdd) { + eventEmitter.on(eventName, listenerToAdd) + } } }