MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1import { test, testDeep, summary } from './helpers.js';
2import { EventEmitter } from 'ant:events';
3import { once as nodeOnce, addAbortListener, getMaxListeners, setMaxListeners } from 'node:events';
4
5console.log('EventEmitter Tests\n');
6
7const ee = new EventEmitter();
8
9let received = null;
10ee.on('test', data => {
11 received = data;
12});
13ee.emit('test', 'hello');
14test('on and emit', received, 'hello');
15
16let count = 0;
17ee.on('count', () => count++);
18ee.on('count', () => count++);
19ee.emit('count');
20test('multiple listeners', count, 2);
21
22let onceValue = 0;
23ee.once('once', val => {
24 onceValue = val;
25});
26ee.emit('once', 42);
27ee.emit('once', 100);
28test('once only fires once', onceValue, 42);
29
30let removed = false;
31const handler = () => {
32 removed = true;
33};
34ee.on('remove', handler);
35ee.off('remove', handler);
36ee.emit('remove');
37test('off removes listener', removed, false);
38
39let allRemoved = 0;
40ee.on('all', () => allRemoved++);
41ee.on('all', () => allRemoved++);
42ee.removeAllListeners('all');
43ee.emit('all');
44test('removeAllListeners', allRemoved, 0);
45
46test('listenerCount', ee.listenerCount('count'), 2);
47
48const names = ee.eventNames();
49test('eventNames includes count', names.includes('count'), true);
50
51const ee2 = new EventEmitter();
52let args = [];
53ee2.on('multi', (a, b, c) => {
54 args = [a, b, c];
55});
56ee2.emit('multi', 1, 2, 3);
57test('multiple emit args', args.join(','), '1,2,3');
58
59const ee3 = new EventEmitter();
60test('emit returns false with no listeners', ee3.emit('none'), false);
61ee3.on('exists', () => {});
62test('emit returns true with listeners', ee3.emit('exists'), true);
63
64const ee4 = new EventEmitter();
65const chain = ee4
66 .on('a', () => {})
67 .once('b', () => {})
68 .off('b', () => {});
69test('methods return this for chaining', chain, ee4);
70
71const ee5 = new EventEmitter();
72let aliasCount = 0;
73ee5.addListener('alias', () => aliasCount++);
74ee5.emit('alias');
75test('addListener alias works', aliasCount, 1);
76
77const ee6 = new EventEmitter();
78let removeAliasVal = 0;
79const removeAliasHandler = () => {
80 removeAliasVal = 1;
81};
82ee6.on('rem', removeAliasHandler);
83ee6.removeListener('rem', removeAliasHandler);
84ee6.emit('rem');
85test('removeListener alias works', removeAliasVal, 0);
86
87const ee7 = new EventEmitter();
88let onceCount = 0;
89ee7.once('multi-once', () => onceCount++);
90ee7.once('multi-once', () => onceCount++);
91ee7.emit('multi-once');
92test('multiple once listeners fire', onceCount, 2);
93ee7.emit('multi-once');
94test('multiple once listeners only fire once', onceCount, 2);
95
96const ee8 = new EventEmitter();
97test('listenerCount for non-existent event', ee8.listenerCount('nope'), 0);
98
99const ee9 = new EventEmitter();
100const h = () => {};
101ee9.on('temp', h);
102ee9.off('temp', h);
103test('eventNames excludes removed events', ee9.eventNames().includes('temp'), false);
104
105const eeA = new EventEmitter();
106const eeB = new EventEmitter();
107let aVal = 0,
108 bVal = 0;
109eeA.on('x', () => aVal++);
110eeB.on('x', () => bVal++);
111eeA.emit('x');
112test('separate instances are isolated (A)', aVal, 1);
113test('separate instances are isolated (B)', bVal, 0);
114
115const eeC = new EventEmitter();
116eeC.on('a', () => {});
117eeC.on('b', () => {});
118eeC.removeAllListeners();
119test('removeAllListeners without event clears all listeners', eeC.listenerCount('a') + eeC.listenerCount('b'), 0);
120
121const eeD = new EventEmitter();
122let listenerThis = null;
123eeD.on('ctx', function () {
124 listenerThis = this;
125});
126eeD.emit('ctx');
127test('EventEmitter listeners receive emitter as this', listenerThis, eeD);
128
129function LegacyEmitter() {
130 EventEmitter.call(this);
131}
132LegacyEmitter.prototype = {};
133LegacyEmitter.prototype.__proto__ = EventEmitter.prototype;
134LegacyEmitter.prototype.constructor = LegacyEmitter;
135
136const legacy = new LegacyEmitter();
137let legacyCount = 0;
138legacy.on('legacy', () => legacyCount++);
139legacy.emit('legacy');
140test('EventEmitter.call(this) initializes classic inherited receivers', legacyCount, 1);
141
142console.log('\nEventTarget Tests\n');
143
144const et = new EventTarget();
145let etReceived = null;
146et.addEventListener('click', e => {
147 etReceived = e.type;
148});
149et.dispatchEvent(new Event('click'));
150test('EventTarget addEventListener and dispatchEvent', etReceived, 'click');
151
152let etEvent = null;
153const et2 = new EventTarget();
154et2.addEventListener('custom', e => {
155 etEvent = e;
156});
157et2.dispatchEvent(new CustomEvent('custom', { detail: { foo: 'bar' } }));
158test('event.type', etEvent.type, 'custom');
159test('event.target is EventTarget', etEvent.target, et2);
160test('event.detail', etEvent.detail.foo, 'bar');
161
162const et3 = new EventTarget();
163let et3Val = 0;
164const et3Handler = () => {
165 et3Val++;
166};
167et3.addEventListener('rem', et3Handler);
168et3.removeEventListener('rem', et3Handler);
169et3.dispatchEvent(new Event('rem'));
170test('EventTarget removeEventListener', et3Val, 0);
171
172const et4 = new EventTarget();
173let et4Count = 0;
174et4.addEventListener('once', () => et4Count++, { once: true });
175et4.dispatchEvent(new Event('once'));
176et4.dispatchEvent(new Event('once'));
177test('EventTarget once option', et4Count, 1);
178
179console.log('\nGlobal Event Tests\n');
180
181let globalReceived = null;
182addEventListener('global-test', e => {
183 globalReceived = e.type;
184});
185dispatchEvent(new Event('global-test'));
186test('global addEventListener and dispatchEvent', globalReceived, 'global-test');
187
188let globalRemoved = 0;
189const globalHandler = () => {
190 globalRemoved++;
191};
192addEventListener('global-rem', globalHandler);
193removeEventListener('global-rem', globalHandler);
194dispatchEvent(new Event('global-rem'));
195test('global removeEventListener', globalRemoved, 0);
196
197console.log('\nCustomEvent Tests\n');
198
199const ce1 = new CustomEvent('ping');
200test('CustomEvent type', ce1.type, 'ping');
201test('CustomEvent detail defaults to null', ce1.detail, null);
202
203const ce2 = new CustomEvent('animalfound', { detail: { name: 'cat' } });
204test('CustomEvent detail', ce2.detail.name, 'cat');
205
206let ceReceived = null;
207const et5 = new EventTarget();
208et5.addEventListener('animalfound', e => {
209 ceReceived = e.detail.name;
210});
211et5.dispatchEvent(ce2);
212test('EventTarget dispatchEvent with CustomEvent', ceReceived, 'cat');
213
214let ceGlobal = null;
215addEventListener('custom-global', e => {
216 ceGlobal = e.detail.value;
217});
218dispatchEvent(new CustomEvent('custom-global', { detail: { value: 42 } }));
219test('global dispatchEvent with CustomEvent', ceGlobal, 42);
220
221const ce3 = new CustomEvent('tagged');
222test('CustomEvent Symbol.toStringTag', Object.prototype.toString.call(ce3), '[object CustomEvent]');
223
224console.log('\nnode:events Helper Tests\n');
225
226const eeNodeOnce = new EventEmitter();
227const eeNodeOncePromise = nodeOnce(eeNodeOnce, 'ready');
228eeNodeOnce.emit('ready', 'ok', 7);
229testDeep('events.once resolves emitted args', await eeNodeOncePromise, ['ok', 7]);
230
231const eeRaw = new EventEmitter();
232function rawOnce() {}
233function rawOn() {}
234eeRaw.once('raw', rawOnce);
235eeRaw.on('raw', rawOn);
236const rawListeners = eeRaw.rawListeners('raw');
237test('rawListeners returns wrapper for once listeners', rawListeners[0] !== rawOnce, true);
238test('rawListeners wrapper exposes original listener', rawListeners[0].listener, rawOnce);
239test('rawListeners keeps non-once listeners unchanged', rawListeners[1], rawOn);
240
241const etNodeOnce = new EventTarget();
242const etNodeOncePromise = nodeOnce(etNodeOnce, 'ping');
243etNodeOnce.dispatchEvent(new Event('ping'));
244const etNodeOnceArgs = await etNodeOncePromise;
245test('events.once supports EventTarget', etNodeOnceArgs[0].type, 'ping');
246
247const abortController = new AbortController();
248const abortPromise = nodeOnce(abortController.signal, 'abort');
249abortController.abort('stop');
250const abortArgs = await abortPromise;
251test('events.once supports AbortSignal', abortArgs[0].type, 'abort');
252
253let duckOnceRejected = false;
254try {
255 await nodeOnce(
256 {
257 once(_name, listener) {
258 listener('bad');
259 }
260 },
261 'ready'
262 );
263} catch {
264 duckOnceRejected = true;
265}
266test('events.once rejects once-shaped plain objects', duckOnceRejected, true);
267
268let protoEmitterRejected = false;
269try {
270 await nodeOnce(Object.create(EventEmitter.prototype), 'ready');
271} catch {
272 protoEmitterRejected = true;
273}
274test('events.once rejects EventEmitter prototype spoofing', protoEmitterRejected, true);
275
276let duckTargetRejected = false;
277try {
278 await nodeOnce(
279 {
280 addEventListener(_name, listener) {
281 listener(new Event('bad'));
282 }
283 },
284 'ready'
285 );
286} catch {
287 duckTargetRejected = true;
288}
289test('events.once rejects EventTarget-shaped plain objects', duckTargetRejected, true);
290
291let protoTargetRejected = false;
292try {
293 await nodeOnce(Object.create(EventTarget.prototype), 'ready');
294} catch {
295 protoTargetRejected = true;
296}
297test('events.once rejects EventTarget prototype spoofing', protoTargetRejected, true);
298
299let addAbortValue = 0;
300const addAbortController = new AbortController();
301const disposable = addAbortListener(addAbortController.signal, () => {
302 addAbortValue++;
303});
304addAbortController.abort();
305test('events.addAbortListener fires on abort', addAbortValue, 1);
306test('events.addAbortListener returns disposable', typeof disposable.dispose, 'function');
307
308let disposedAbortValue = 0;
309const disposedAbortController = new AbortController();
310const disposedAbort = addAbortListener(disposedAbortController.signal, () => {
311 disposedAbortValue++;
312});
313disposedAbort.dispose();
314disposedAbortController.abort();
315test('events.addAbortListener dispose removes listener', disposedAbortValue, 0);
316
317let onceAbortCalls = 0;
318const onceAbortController = new AbortController();
319const onceAbortListener = () => {
320 onceAbortCalls++;
321};
322addAbortListener(onceAbortController.signal, onceAbortListener);
323const onceAbortEmitter = new EventEmitter();
324const onceAbortPromise = nodeOnce(onceAbortEmitter, 'done', { signal: onceAbortController.signal });
325onceAbortEmitter.emit('done', 'ok');
326await onceAbortPromise;
327onceAbortController.abort();
328test('events.once removes abort listener after resolve', onceAbortCalls, 1);
329
330test('events.getMaxListeners default', getMaxListeners(eeNodeOnce), 10);
331test('events.setMaxListeners no-op return', setMaxListeners(20, eeNodeOnce), undefined);
332
333summary();