UNPKG

als-event-emitter

Version:

A powerful, asynchronous, promise-based event emitter with support for chaining and advanced event handling.

1,108 lines (982 loc) 37.4 kB
const { describe, it, beforeEach } = require('node:test') const assert = require('node:assert') const EventEmitter = require('./index') describe('EventEmitter with chaining enabled (new EventEmitter(true))', () => { it('should call listeners in order with chaining', () => { const emitter = new EventEmitter(true); let order = []; emitter.on('testEvent', ({ next }, _) => { order.push(1); next(); }); emitter.on('testEvent', ({ next }, _) => { order.push(2); next(); }); emitter.emit('testEvent'); assert.deepStrictEqual(order, [1, 2]); }); it('should stop execution if next is not called', () => { const emitter = new EventEmitter(true); let order = []; emitter.on('testEvent', ({ next }, _) => { order.push(1); }); emitter.on('testEvent', ({ next }, _) => { order.push(2); next(); }); emitter.emit('testEvent'); assert.deepStrictEqual(order, [1]); }); it('should execute "once" listeners only once with chaining', () => { const emitter = new EventEmitter(true); let counter = 0; emitter.once('testEvent', ({ next }, _) => { counter++; next(); }); emitter.emit('testEvent'); emitter.emit('testEvent'); assert(counter === 1); }); it('should pass arguments through chained listeners', () => { const emitter = new EventEmitter(true); let receivedArgs = []; emitter.on('testEvent', ({ next }, arg1, arg2) => { receivedArgs = [arg1, arg2]; next(); }); emitter.emit('testEvent', 'arg1', 'arg2'); assert.deepStrictEqual(receivedArgs, ['arg1', 'arg2']); }); it('should handle an empty chain gracefully', () => { const emitter = new EventEmitter(true); assert.doesNotThrow(() => emitter.emit('testEvent')); }); it('should combine regular and "any" listeners with chaining', () => { const emitter = new EventEmitter(true); let order = []; emitter.on('testEvent', ({ next }, _) => { order.push(1); next(); }); emitter.onAny(({ next }, _) => { order.push('any'); next(); }); emitter.emit('testEvent'); assert.deepStrictEqual(order, [1, 'any']); }); }); describe(`on(eventName, eventFn)`, () => { it('should invoke the listener when the event is emitted', () => { const e = new EventEmitter(); let wasCalled = false; e.on('testEvent', () => wasCalled = true); e.emit('testEvent'); assert(wasCalled === true); }); it('should invoke multiple listeners for the same event', async () => { const e = new EventEmitter(false); let counter = 0; e.on('testEvent', () => counter++); e.on('testEvent', () => counter++); const result = e.emit('testEvent'); assert(counter === 2); }); it('should pass parameters to the event listener', () => { const e = new EventEmitter(false); let receivedData; e.on('testEvent', (data) => receivedData = data); const sentData = { key: 'value' }; e.emit('testEvent', sentData); assert(receivedData === sentData); }); it('should handle non-function listener gracefully', () => { const e = new EventEmitter(); try { e.on('testEvent', 'notAFunction'); } catch (error) { assert(true); } }); it('should allow chaining of on method', () => { const e = new EventEmitter(); let counter = 0; e.on('event1', () => counter++).on('event2', () => counter++) e.emit('event1') e.emit('event2'); assert(counter === 2); }); }) describe('once(eventName, eventFn)', () => { it('should invoke the listener when the event is emitted', () => { const e = new EventEmitter(); let wasCalled = false; e.once('testEvent', () => wasCalled = true); e.emit('testEvent'); assert(wasCalled); }); it('should invoke the listener only once', () => { const e = new EventEmitter(); let counter = 0; e.once('testEvent', () => counter++); e.emit('testEvent'); e.emit('testEvent'); assert(counter === 1); }); it('should invoke multiple once listeners for the same event', () => { const e = new EventEmitter(false); let counter = 0; e.once('testEvent', () => counter++); e.once('testEvent', () => counter++); e.emit('testEvent'); e.emit('testEvent'); assert(counter === 2); }); it('should pass parameters to the once event listener', () => { const e = new EventEmitter(false); let receivedData; e.once('testEvent', (data) => receivedData = data); const sentData = { key: 'value' }; e.emit('testEvent', sentData); assert(receivedData === sentData); }); it('should allow chaining of once method with other methods', () => { const e = new EventEmitter(); let counter = 0; e.once('event1', () => counter++).on('event2', () => counter++) e.emit('event1') e.emit('event2') e.emit('event1'); assert(counter === 2); }); it('should handle non-function listener with once gracefully', () => { const e = new EventEmitter(); try { e.once('testEvent', 'notAFunction'); assert(false) } catch (error) { assert(true) } }); }) describe('off(eventName)', () => { it('should remove all listeners for a specific event', () => { const e = new EventEmitter(); let counter = 0; e.on('event1', () => counter++) .on('event1', () => counter++); e.off('event1'); e.emit('event1'); assert(counter === 0); }); it('should not affect listeners of other events', () => { const e = new EventEmitter(); let counter = 0; e.on('event1', () => counter++) .on('event2', () => counter++); e.off('event1'); e.emit('event2'); assert(counter === 1); }); it('should handle removal of non-existing event gracefully', () => { const e = new EventEmitter(); let counter = 0; let error = null; e.on('event1', () => counter++); assert.doesNotThrow(() => e.off('nonExistentEvent')) e.emit('event1'); assert(counter === 1); }); it('should be chainable with other methods', () => { const e = new EventEmitter(); let counter = 0; e.on('event1', () => counter++) .on('event2', () => counter++) .off('event1') .emit('event2'); assert(counter === 1); }); }) describe('removeListener(eventName)', () => { it('should remove a specific listener from all events', () => { const e = new EventEmitter(); let counter = 0; const incrementer = () => counter++; e.on('event1', incrementer) .on('event2', incrementer); e.removeListener(incrementer); e.emit('event1') e.emit('event2'); assert(counter === 0); }); it('should not affect other listeners of the same event', () => { const e = new EventEmitter(); let counter = 0; const incrementer = () => counter++; e.on('event1', incrementer) .on('event1', () => counter += 2); e.removeListener(incrementer); e.emit('event1'); assert(counter === 2); }); it('should handle removal of non-attached listener gracefully', () => { const e = new EventEmitter(); let counter = 0; const incrementer = () => counter++; e.on('event1', () => counter += 2); e.removeListener(incrementer); // Try to remove a listener that wasn't attached e.emit('event1'); assert(counter === 2); }); it('should be chainable with other methods', () => { const e = new EventEmitter(); let counter = 0; const incrementer = () => counter++; e.on('event1', incrementer) .on('event2', () => counter += 2) .removeListener(incrementer) .emit('event2'); assert(counter === 2); }); }) describe('removeAllListeners()', () => { it('should remove all listeners for all events', () => { const e = new EventEmitter(); let counter = 0; e.on('event1', () => counter++) .on('event2', () => counter++); e.removeAllListeners(); e.emit('event1') e.emit('event2'); assert(counter === 0); }); it('should not trigger any listeners after they are removed', () => { const e = new EventEmitter(); let counter = 0; e.on('event1', () => counter += 2); e.removeAllListeners(); e.emit('event1'); assert(counter === 0); }); it('should handle re-adding listeners after all are removed', () => { const e = new EventEmitter(); let counter = 0; e.on('event1', () => counter++); e.removeAllListeners(); e.on('event1', () => counter += 2); e.emit('event1'); assert(counter === 2); }); it('should be chainable with other methods', () => { const e = new EventEmitter(); let counter = 0; e.on('event1', () => counter++) .on('event2', () => counter += 2) .removeAllListeners() .on('event2', () => counter += 3) .emit('event2'); assert(counter === 3); }); }) describe('emit(eventName, ...values)', () => { it('should trigger the correct listeners when an event is emitted', () => { const e = new EventEmitter(); let counter = 0; e.on('event1', () => counter++); e.emit('event1'); assert(counter === 1); }); it('should pass multiple values to the listeners', () => { const e = new EventEmitter(false); let receivedValues = []; e.on('event1', (val1, val2) => { receivedValues.push(val1, val2); }); e.emit('event1', 'value1', 'value2'); assert.deepStrictEqual(receivedValues, ['value1', 'value2']); }); it('should allow chaining of multiple emits', () => { const e = new EventEmitter(); let counter = 0; e.on('event1', () => counter++) .on('event2', () => counter++); e.emit('event1') e.emit('event2'); assert(counter === 2); }); it('should execute all listeners for a given event', () => { const e = new EventEmitter(false); let counter = 0; e.on('event1', () => counter++) .on('event1', () => counter++); e.emit('event1'); assert(counter === 2); }); }) describe('has(eventName)', () => { it('should return true if listeners are registered for an event', () => { const e = new EventEmitter(); e.on('event1', () => { }); assert(e.has('event1') === true); }); it('should return false if no listeners are registered for an event', () => { const e = new EventEmitter(); assert(e.has('event1') === false); }); it('should return false after all listeners for an event are removed', () => { const e = new EventEmitter(); const listener = () => { }; e.on('event1', listener); e.removeListener(listener); assert(e.has('event1') === false); }); it('should return false after all listeners are removed using removeAllListeners', () => { const e = new EventEmitter(); e.on('event1', () => { }); e.removeAllListeners(); assert(e.has('event1') === false); }); it('should correctly report presence of listeners for multiple events', () => { const e = new EventEmitter(); e.on('event1', () => { }) .on('event2', () => { }); assert(e.has('event1') === true); assert(e.has('event2') === true); assert(e.has('event3') === false); // event3 doesn't have listeners }); }) describe('Basic tests for EventEmitter', () => { let emitter; beforeEach(() => { emitter = new EventEmitter(false); }); it('should handle multiple listeners for a single event', () => { let counter = 0; emitter.on('testEvent', () => counter++); emitter.on('testEvent', () => counter++); emitter.emit('testEvent'); assert(counter === 2) }); it('should remove a specific listener', () => { let counter = 0; const listener = () => counter++; emitter.on('testEvent', listener); emitter.emit('testEvent'); emitter.removeListener(listener); emitter.emit('testEvent'); assert(counter === 1); }); it('should remove all listeners', () => { let counter = 0; emitter.on('testEvent', () => counter++); emitter.removeAllListeners(); emitter.emit('testEvent'); assert(counter === 0); }); it('should handle multiple arguments passed to listeners', () => { let data1, data2; emitter.on('testEvent', (arg1, arg2) => { data1 = arg1; data2 = arg2; }); emitter.emit('testEvent', 'sample1', 'sample2'); assert(data1 === 'sample1'); assert(data2 === 'sample2'); }); it('should handle chainable methods correctly', () => { let counter = 0; emitter.on('testEvent', () => counter++) .on('anotherEvent', () => counter++) emitter.emit('testEvent') emitter.emit('anotherEvent'); assert(counter === 2); }); it('should not have side effects after removing an event listener', () => { let counter = 0; const listener = () => counter++; emitter.on('testEvent', listener); emitter.emit('testEvent'); emitter.removeListener(listener); emitter.on('anotherEvent', listener); emitter.emit('testEvent'); emitter.emit('anotherEvent'); assert(counter === 2); }); }); describe('onAny(listener)', () => { it('should trigger the listener for any emitted event', () => { const e = new EventEmitter(false); let counter = 0; e.on('event1', () => { }) e.once('event2', () => { }) e.onAny(() => counter++); e.emit('event1'); e.emit('event2'); assert(counter === 2); }); it('should work with regular event listeners', () => { const e = new EventEmitter(false); let counter = 0; e.onAny(() => counter++); e.on('event1', () => counter++); e.emit('event1'); assert(counter === 2); }); it('should handle multiple values passed to the listeners', () => { const e = new EventEmitter(false); let receivedValues = []; e.once('event1', () => { }) e.onAny((...values) => { receivedValues = values; }); e.emit('event1', 'value1', 'value2'); assert.deepStrictEqual(receivedValues, ['value1', 'value2']); }); }); describe('removeListener(listener)', () => { it('should remove the global listener', () => { const e = new EventEmitter(false); let counter = 0; const globalListener = () => counter++; e.on('event1', () => { }) e.onAny(globalListener); e.emit('event1'); e.removeListener(globalListener); e.emit('event1'); assert(counter === 1); }); it('should not interfere with regular event listeners', () => { const e = new EventEmitter(); let counter = 0; const globalListener = () => counter++; e.onAny(globalListener); e.on('event1', () => counter++); e.removeListener(globalListener); e.emit('event1'); assert(counter === 1); }); it('should handle removal of non-registered anyListener gracefully', () => { const e = new EventEmitter(); let counter = 0; const globalListener = () => counter++; e.removeListener(globalListener); e.emit('event1'); assert(counter === 0); // Shouldn't throw an error }); }); describe('EventEmitter advanced tests', () => { let emitter; beforeEach(() => { emitter = new EventEmitter(false); }); // Test 1: Adding a listener during event processing it('should handle adding listeners during event processing', () => { let counter = 0; emitter.on('testEvent', () => { emitter.on('testEvent', () => counter += 10); }); emitter.emit('testEvent'); emitter.emit('testEvent'); assert(counter === 10); // Should be 10, not 20 }); // Test 2: Removing a listener during event processing it('should handle removing listeners during event processing', () => { let counter = 0; const incrementer = () => counter++; emitter.on('testEvent', () => { emitter.removeListener(incrementer); }); emitter.on('testEvent', incrementer); emitter.emit('testEvent'); emitter.emit('testEvent'); assert(counter === 1); // Should be 1, since it's removed after first call }); // Test 3: Separate handling of anyListeners it('should add and call anyListeners separately', () => { let counter = 0; emitter.onAny(() => counter++); emitter.on('testEvent', () => counter++); emitter.emit('testEvent'); assert(counter === 2); // One for anyListener, one for testEvent }); // Test 4: OnceAny behavior it('should call onceAny listener only once for any event', () => { let counter = 0; emitter.onceAny(() => counter++); emitter.on('testEvent', () => { }); emitter.on('anotherEvent', () => { }); emitter.emit('testEvent'); emitter.emit('anotherEvent'); assert(counter === 1); // Should be 1, since onceAny listener should only fire once }); // Additional tests for other specific behaviors can be added here }); describe('Edge cases', () => { it('should not fail when emitting an event with no listeners', () => { const emitter = new EventEmitter(); assert.doesNotThrow(() => emitter.emit('noListenersEvent')); }); it('should handle removal of non-existent global listeners gracefully', () => { const emitter = new EventEmitter(); const fn = () => { }; assert.doesNotThrow(() => emitter.removeListener(fn)); }); }) describe('Performance tests', () => { it('should handle a large number of listeners', () => { const emitter = new EventEmitter(); for (let i = 0; i < 1000; i++) { emitter.on('event', () => { }); } assert.doesNotThrow(() => emitter.emit('event')); }); it('should handle bulk listener removal efficiently', () => { const emitter = new EventEmitter(); const listeners = []; for (let i = 0; i < 1000; i++) { const fn = () => { }; listeners.push(fn); emitter.on('event', fn); } listeners.forEach(fn => emitter.removeListener(fn)); assert.strictEqual(emitter.has('event'), false); }); it('should handle a single listener for multiple events', () => { const emitter = new EventEmitter(); let counter = 0; const listener = () => counter++; emitter.on('event1', listener); emitter.on('event2', listener); emitter.emit('event1'); emitter.emit('event2'); assert.strictEqual(counter, 2); }); }) describe('EventEmitter with Promises', () => { it('should wait for all chained listeners to complete', async () => { const emitter = new EventEmitter(true); let order = []; emitter.on('testEvent', async ({ next }) => { order.push(1); await next(); }); emitter.on('testEvent', async ({ next }) => { order.push(2); await next(); }); const result = await emitter.emit('testEvent'); assert.deepStrictEqual(order, [1, 2]); assert.strictEqual(result, undefined); }); it('should resolve with a custom value from a listener', async () => { const emitter = new EventEmitter(true); emitter.on('testEvent', ({ resolve }) => { resolve('Resolved Value'); }); const result = await emitter.emit('testEvent'); assert.strictEqual(result, 'Resolved Value'); }); it('should handle errors in chained listeners', async () => { const emitter = new EventEmitter(true); emitter.on('testEvent', () => { throw new Error('Listener Error'); }); await assert.throws(() => emitter.emit('testEvent'), { message: 'Listener Error' }); }); it('should allow resolving with different values in chained listeners', async () => { const emitter = new EventEmitter(true); emitter.on('testEvent', async ({ next }) => { await next(); }); emitter.on('testEvent', ({ resolve }) => { resolve('Final Value'); }); const result = await emitter.emit('testEvent'); assert.strictEqual(result, 'Final Value'); }); it('should stop execution when resolve is called in a listener', async () => { const emitter = new EventEmitter(true); let order = []; emitter.on('testEvent', ({ next }) => { order.push(1); next(); }); emitter.on('testEvent', ({ resolve }) => { order.push(2); resolve(); }); emitter.on('testEvent', ({ next }) => { order.push(3); next(); }); await emitter.emit('testEvent'); assert.deepStrictEqual(order, [1, 2]); }); it('should allow resolve and next to be called in the same listener', async () => { const emitter = new EventEmitter(true); let order = []; emitter.on('testEvent', ({ next, resolve }) => { order.push(1); resolve('Partial Resolve'); next(); }); emitter.on('testEvent', ({ next }) => { order.push(2); next(); }); const result = await emitter.emit('testEvent'); assert.deepStrictEqual(order, [1, 2]); assert.strictEqual(result, 'Partial Resolve'); }); it('should ignore additional resolve calls after the first in listeners', async () => { const emitter = new EventEmitter(true); let order = []; emitter.on('testEvent', ({ next, resolve }) => { order.push(1); resolve('First Resolve'); resolve('Second Resolve'); next(); }); emitter.on('testEvent', ({ next }) => { order.push(2); next(); }); const result = await emitter.emit('testEvent'); assert.deepStrictEqual(order, [1, 2]); assert.strictEqual(result, 'First Resolve'); }); it('should handle multiple resolve calls in listeners gracefully', async () => { const emitter = new EventEmitter(true); let resolveCalls = []; emitter.on('testEvent', ({ resolve }) => { resolveCalls.push('First Resolve'); resolve('First Value'); }); emitter.on('testEvent', ({ resolve }) => { resolveCalls.push('Second Resolve'); resolve('Second Value'); }); const result = await emitter.emit('testEvent'); assert.deepStrictEqual(resolveCalls, ['First Resolve']); assert.strictEqual(result, 'First Value'); }); }); describe('New Features in EventEmitter', () => { let emitter; beforeEach(() => { EventEmitter.removeAllListeners() emitter = new EventEmitter(true); // Enable chaining }); describe('Static Methods', () => { it('should register a global listener with once()', async () => { let counter = 0; EventEmitter.once(() => counter++); const e = new EventEmitter(false); await e.emit('anyEvent'); await e.emit('anyEvent'); assert.strictEqual(counter, 1); }); it('should register a global listener with onceLast()', async () => { let counter = 0; EventEmitter.onceLast(() => counter++); const e = new EventEmitter(false); await e.emit('anyEvent'); await e.emit('anyEvent'); assert.strictEqual(counter, 1); }); it('should register a global listener with onLast()', async () => { let order = []; EventEmitter.onLast(() => order.push('last')); const e = new EventEmitter(false); await e.emit('anyEvent'); assert.deepStrictEqual(order, ['last']); }); it('should register a global listener with on()', async () => { let counter = 0; EventEmitter.on(() => counter++); const e = new EventEmitter(false); await e.emit('anyEvent'); await e.emit('anyEvent'); assert.strictEqual(counter, 2); }); }); describe('Instance Methods', () => { it('should add a listener with onLast()', async () => { let order = []; emitter.onLast('testEvent', ({ next }) => { order.push('last'); next(); }); emitter.on('testEvent', ({ next }) => { order.push('normal'); next(); }); await emitter.emit('testEvent'); assert.deepStrictEqual(order, ['normal', 'last']); }); it('should add a listener with onceLast()', async () => { let counter = 0; emitter.onceLast('testEvent', () => counter++); await emitter.emit('testEvent'); await emitter.emit('testEvent'); assert.strictEqual(counter, 1); }); it('should register a global listener for all events with onAnyLast()', async () => { let order = []; emitter.onAnyLast(({ next }) => { order.push('any-last'); next(); }); emitter.on('testEvent', ({ next }) => { order.push('normal'); next(); }); await emitter.emit('testEvent'); assert.deepStrictEqual(order, ['normal', 'any-last']); }); it('should register a global listener for all events with onceAnyLast()', async () => { let counter = 0; emitter.onceAnyLast(() => counter++); await emitter.emit('testEvent'); await emitter.emit('testEvent'); assert.strictEqual(counter, 1); }); }); describe('Advanced Emit Handling', () => { it('should resolve the chain with resolve() from a listener', async () => { emitter.on('testEvent', ({ resolve }) => resolve('resolvedValue')); const result = await emitter.emit('testEvent'); assert.strictEqual(result, 'resolvedValue'); }); it('should reject the chain with reject() from a listener', async () => { emitter.on('testEvent', async ({ reject }) => reject(new Error('Rejected'))); await assert.rejects(() => emitter.emit('testEvent'), { message: 'Rejected' }); }); it('should continue execution with next() after resolve()', async () => { let order = []; emitter.on('testEvent', ({ resolve, next }) => { order.push(1); resolve(); next(); }); emitter.on('testEvent', ({ next }) => { order.push(2); next(); }); await emitter.emit('testEvent'); assert.deepStrictEqual(order, [1, 2]); }); it('should stop execution when resolve() is called', async () => { let order = []; emitter.on('testEvent', ({ resolve }) => { order.push(1); resolve(); }); emitter.on('testEvent', ({ next }) => { order.push(2); next(); }); await emitter.emit('testEvent'); assert.deepStrictEqual(order, [1]); }); it('should process linked chains in order', async () => { const linkedEmitter = new EventEmitter(true); let order = []; linkedEmitter.on('linkedEvent', ({ next }) => { order.push('linked1'); next(); }); emitter.getChain('testEvent').link(linkedEmitter.getChain('linkedEvent')); emitter.on('testEvent', ({ next }) => { order.push('main'); next(); }); await emitter.emit('testEvent'); assert.deepStrictEqual(order, ['linked1', 'main']); }); }); }); describe('Additional Tests for EventEmitter', () => { // 1. Global listeners + event does not exist describe('Global listeners with unregistered events', () => { it('should trigger global (onAny) listeners even if the event is not registered', () => { const emitter = new EventEmitter(false); let counter = 0; // Add a global listener emitter.onAny(() => counter++); // Emit an event that does NOT have specific listeners emitter.emit('unregisteredEvent'); // We expect the global onAny listener to fire assert.strictEqual(counter, 1); }); it('should trigger both the specific event listener and the global listener', () => { const emitter = new EventEmitter(false); let order = []; emitter.on('someEvent', () => order.push('eventListener')); emitter.onAny(() => order.push('global')); emitter.emit('someEvent'); assert.deepStrictEqual(order, ['eventListener', 'global']); }); }); // 2. Static clearing after removeAllListeners describe('Static clearing with removeAllListeners', () => { it('should remove globally registered listeners when removeAllListeners is called (for the instance)', () => { // Register a global listener using static method let globalCounter = 0; EventEmitter.on(() => globalCounter++); const emitter = new EventEmitter(false); // Fire an event to verify it triggers the global listener emitter.emit('test'); assert.strictEqual(globalCounter, 1); // Now remove all listeners from the static chain EventEmitter.removeAllListeners(); // Fire again emitter.emit('test'); // We expect no additional increments, because static chain was cleared assert.strictEqual(globalCounter, 1); }); }); // 3. Interference between multiple emitter instances describe('Multiple Emitter Interference', () => { it('should share global (static) listeners across different emitters', () => { // Add a static global listener let globalCount = 0; EventEmitter.once(() => globalCount++); const emitter1 = new EventEmitter(false); const emitter2 = new EventEmitter(false); emitter1.emit('someEvent'); emitter2.emit('someOtherEvent'); // The global listener should have fired only once in total assert.strictEqual(globalCount, 1); }); it('should not share instance-level listeners between different emitters', () => { const emitter1 = new EventEmitter(false); const emitter2 = new EventEmitter(false); let counter1 = 0; let counter2 = 0; emitter1.on('test', () => counter1++); emitter2.on('test', () => counter2++); emitter1.emit('test'); emitter2.emit('test'); assert.strictEqual(counter1, 1); assert.strictEqual(counter2, 1); }); }); // 4. Multiple emit calls (concurrent or sequential) describe('Multiple concurrent/sequential emit calls', () => { it('should handle multiple sequential emit calls (async mode)', async () => { const emitter = new EventEmitter(true); let order = []; emitter.on('test', async ({ next }, val) => { order.push(`listener1-${val}`); await next(); }); emitter.on('test', ({ next }, val) => { order.push(`listener2-${val}`); next(); }); await emitter.emit('test', 'A'); await emitter.emit('test', 'B'); // We expect the order of calls to be correct for each emit assert.deepStrictEqual(order, [ 'listener1-A', 'listener2-A', 'listener1-B', 'listener2-B' ]); }); it('should not mix results if two emits happen simultaneously', async () => { const emitter = new EventEmitter(true); let results = []; emitter.on('test', async ({ next, resolve }, val) => { // simulate small async operation await new Promise(r => setTimeout(r, 50)); resolve(`Done-${val}`); }); // Fire 2 emits at the same time const [res1, res2] = await Promise.all([ emitter.emit('test', 'X'), emitter.emit('test', 'Y') ]); // Each emit should complete with the correct result results.push(res1, res2); assert.deepStrictEqual(results, ['Done-X', 'Done-Y']); }); }); }); describe('name and group tests', () => { let emitter; beforeEach(() => { emitter = new EventEmitter(); }); describe('Event name in context', () => { it('should pass the event name in context when emitting an event', () => { const eventName = 'testEvent'; emitter.on(eventName, ({ name }) => { assert.strictEqual(name, eventName); }); emitter.emit(eventName); }); it('should pass the correct event name to anyChain', () => { const eventName = 'testEvent'; emitter.on(eventName, ({ name, next }) => { assert.strictEqual(name, eventName); next() }); emitter.onAny(({ name, next }) => { assert.strictEqual(name, eventName); next() }); emitter.emit(eventName); }); }); describe('Group functionality', () => { it('should link group listeners to an event', () => { const groupName = 'group1'; const eventName = 'testEvent'; let groupCalled = false; let eventCalled = false; emitter.on(groupName, ({next}) => { groupCalled = true; next() }); emitter.on(eventName, () => { eventCalled = true; }, { group: groupName }); emitter.emit(eventName); assert.strictEqual(groupCalled, true, 'Group listener should be called'); assert.strictEqual(eventCalled, true, 'Event listener should be called'); }); it('should not link group listeners for unrelated events', () => { const groupName = 'group1'; const unrelatedEventName = 'unrelatedEvent'; let groupCalled = false; emitter.on(groupName, ({next}) => { groupCalled = true; next() }); emitter.on(unrelatedEventName,({next}) => next()) emitter.emit(unrelatedEventName); assert.strictEqual(groupCalled, false, 'Group listener should not be called for unrelated events'); }); it('should handle multiple listeners in the same group', () => { const groupName = 'group1'; const eventName = 'testEvent'; let callCount = 0; emitter.on(groupName, ({next}) => { callCount++; next() }); emitter.on(eventName, ({next}) => { callCount++; next() }, { group: groupName }); emitter.on(eventName, ({next}) => { callCount++; next() }); emitter.emit(eventName); assert.strictEqual(callCount, 3, 'All listeners in the group and event should be called'); }); }); describe('Chaining and execution', () => { it('should execute listeners in the correct order', () => { const results = []; emitter.on('test', ({next}) => {results.push(1); next()}); emitter.on('test', ({next}) => {results.push(2); next()}, { last: true }); emitter.on('test', ({next}) => {results.push(3); next()}); emitter.emit('test'); assert.deepStrictEqual(results, [1, 3, 2], 'Listeners should be executed in the correct order'); }); it('should execute async listeners correctly', async () => { const results = []; emitter.on('test', async ({ next }) => { await new Promise(resolve => setTimeout(resolve, 50)); results.push(1); next(); }); emitter.on('test', async () => { results.push(2); }); await emitter.emit('test'); assert.deepStrictEqual(results, [1, 2], 'Async listeners should be executed in the correct order'); }); }); });