EventEmitter and EventTarget assertions for bupkis.
npm install @bupkis/events
import { use } from 'bupkis';
import { eventAssertions } from '@bupkis/events';
import { EventEmitter } from 'node:events';
// Register the assertions
const { expect, expectAsync } = use(eventAssertions);
// Sync assertions for listener state
const emitter = new EventEmitter();
emitter.on('data', () => {});
expect(emitter, 'to have listener for', 'data');
// Async assertions for event emission
await expectAsync(
() => emitter.emit('ready'),
'to emit from',
emitter,
'ready',
);
Asserts that an emitter has at least one listener for the specified event.
const emitter = new EventEmitter();
emitter.on('data', () => {});
expect(emitter, 'to have listener for', 'data'); // passes
expect(emitter, 'not to have listener for', 'other'); // passes (negation)
Asserts that an emitter has listeners for all specified events.
const emitter = new EventEmitter();
emitter.on('data', () => {});
emitter.on('end', () => {});
expect(emitter, 'to have listeners for', ['data', 'end']); // passes
Asserts that an emitter has exactly the specified number of listeners for an event.
const emitter = new EventEmitter();
emitter.on('data', () => {});
emitter.on('data', () => {}); // second listener
expect(emitter, 'to have listener count', 'data', 2); // passes
Asserts that an emitter has at least one listener registered (for any event).
const emitter = new EventEmitter();
emitter.on('anything', () => {});
expect(emitter, 'to have listeners'); // passes
expect(new EventEmitter(), 'not to have listeners'); // passes (fresh emitter)
Asserts that an emitter's maxListeners is set to the specified value.
const emitter = new EventEmitter();
emitter.setMaxListeners(20);
expect(emitter, 'to have max listeners', 20); // passes
Async assertions use a trigger-based API where the first argument is a function or Promise that causes the event to be emitted.
Asserts that a trigger causes an emitter to emit the specified event.
const emitter = new EventEmitter();
// Function trigger
await expectAsync(
() => emitter.emit('ready'),
'to emit from',
emitter,
'ready',
);
// Async trigger
await expectAsync(
() => setTimeout(() => emitter.emit('ready'), 10),
'to emit from',
emitter,
'ready',
);
Asserts that a trigger causes an emitter to emit an event with specific arguments. Uses 'to satisfy' semantics, allowing partial matching for objects and expect.it() for custom assertions.
const emitter = new EventEmitter();
await expectAsync(
() => emitter.emit('data', 'hello', 42),
'to emit from',
emitter,
'data',
'with args',
['hello', 42],
);
With custom assertions:
await expectAsync(
() => emitter.emit('data', { count: 42, extra: 'ignored' }),
'to emit from',
emitter,
'data',
'with args',
[expect.it('to satisfy', { count: expect.it('to be greater than', 0) })],
);
Asserts that a trigger causes an emitter to emit the 'error' event.
const emitter = new EventEmitter();
emitter.on('error', () => {}); // Prevent unhandled error
await expectAsync(
() => emitter.emit('error', new Error('oops')),
'to emit error from',
emitter,
);
Asserts that a trigger causes an emitter to emit events in the specified order.
const emitter = new EventEmitter();
await expectAsync(
() => {
emitter.emit('start');
emitter.emit('data');
emitter.emit('end');
},
'to emit events from',
emitter,
['start', 'data', 'end'],
);
Asserts that a trigger causes an EventTarget to dispatch the specified event.
const target = new EventTarget();
await expectAsync(
() => target.dispatchEvent(new Event('click')),
'to dispatch from',
target,
'click',
);
Asserts that a trigger causes an EventTarget to dispatch a CustomEvent with specific detail. Uses 'to satisfy' semantics for objects, allowing partial matching and expect.it() for custom assertions.
const target = new EventTarget();
await expectAsync(
() =>
target.dispatchEvent(new CustomEvent('custom', { detail: { foo: 'bar' } })),
'to dispatch from',
target,
'custom',
'with detail',
{ foo: 'bar' },
);
Partial matching (extra properties allowed):
await expectAsync(
() =>
target.dispatchEvent(
new CustomEvent('custom', { detail: { foo: 'bar', extra: 'ignored' } }),
),
'to dispatch from',
target,
'custom',
'with detail',
{ foo: 'bar' },
);
With custom assertions:
await expectAsync(
() =>
target.dispatchEvent(new CustomEvent('data', { detail: { count: 42 } })),
'to dispatch from',
target,
'data',
'with detail',
expect.it('to satisfy', { count: expect.it('to be greater than', 0) }),
);
All async assertions accept an optional timeout configuration:
// Wait up to 100ms for the event
await expectAsync(
() => setTimeout(() => emitter.emit('slow'), 50),
'to emit from',
emitter,
'slow',
{ within: 100 },
);
The default timeout is 2000ms.
This package uses duck-typing to detect EventEmitter-like objects, making it compatible with:
EventEmittereventemitter3import EventEmitter3 from 'eventemitter3';
const emitter = new EventEmitter3();
emitter.on('data', () => {});
expect(emitter, 'to have listener for', 'data'); // works!
Both sync and async assertions support symbol event names:
const sym = Symbol('myEvent');
emitter.on(sym, () => {});
expect(emitter, 'to have listener for', sym);
await expectAsync(() => emitter.emit(sym), 'to emit from', emitter, sym);