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
JavaScript
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');
});
});
});