@supercat1337/event-emitter 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,31 +1,57 @@
1
1
  {
2
2
  "name": "@supercat1337/event-emitter",
3
- "version": "2.0.0",
4
- "description": "Event Emitter",
3
+ "version": "2.0.1",
4
+ "description": "Lightweight typed event emitter with Symbol support, one-time listeners, and zero dependencies. Works in Node.js and browsers.",
5
5
  "main": "index.js",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./package.json": "./package.json"
10
+ },
11
+ "types": "./index.d.ts",
12
+ "files": [
13
+ "index.js",
14
+ "index.d.ts",
15
+ "src/**/*.js",
16
+ "src/**/*.d.ts"
17
+ ],
6
18
  "scripts": {
7
19
  "test": "c8 ava",
8
- "create_types": "npm run remove_type_files && npx -p typescript tsc --project my.tsconfig.types.json",
9
- "remove_type_files": "del /q *.d.ts *.d.ts.map && cd src && del /s /q *.d.ts *.d.ts.map && cd ../src && del /s /q *.d.ts *.d.ts.map && cd .."
20
+ "clean": "shx rm -f *.d.ts *.d.ts.map && shx rm -rf src/**/*.d.ts src/**/*.d.ts.map",
21
+ "build:types": "npm run clean && tsc --project my.tsconfig.types.json"
10
22
  },
11
- "author": "Supercat",
23
+ "keywords": [
24
+ "event",
25
+ "emitter",
26
+ "eventemitter",
27
+ "event-emitter",
28
+ "typed",
29
+ "typescript",
30
+ "symbols",
31
+ "lightweight",
32
+ "tiny",
33
+ "zero-dependencies",
34
+ "browser",
35
+ "node",
36
+ "events",
37
+ "pubsub",
38
+ "observer"
39
+ ],
40
+ "author": "Albert Bazaleev",
12
41
  "license": "MIT",
13
42
  "devDependencies": {
43
+ "@types/node": "^20.12.2",
14
44
  "ava": "^6.1.2",
15
45
  "c8": "^9.1.0",
16
- "@types/node": "^20.12.2"
46
+ "shx": "^0.4.0",
47
+ "typescript": "^5.4.3"
17
48
  },
18
- "type": "module",
19
- "keywords": [
20
- "typed event emitter",
21
- "typed event bus"
22
- ],
23
49
  "publishConfig": {
24
50
  "registry": "https://registry.npmjs.org"
25
51
  },
26
52
  "homepage": "https://github.com/supercat1337/event-emitter",
27
53
  "repository": {
28
54
  "type": "git",
29
- "url": "https://github.com/supercat1337/event-emitter.git"
55
+ "url": "git+https://github.com/supercat1337/event-emitter.git"
30
56
  }
31
57
  }
@@ -1,10 +1,10 @@
1
1
  export const ORIGINAL: unique symbol;
2
2
  /**
3
- * @template {string | Record<string, any[]>} [Events=string]
3
+ * @template {string | symbol | Record<string|symbol, any[]>} [Events=string]
4
4
  */
5
- export class EventEmitterLite<Events extends string | Record<string, any[]> = string> {
5
+ export class EventEmitterLite<Events extends string | symbol | Record<string | symbol, any[]> = string> {
6
6
  /**
7
- * @type {Object.<Events extends string ? Events : keyof Events, Function[]>}
7
+ * @type {Object.<Events extends string | symbol ? Events : keyof Events, Function[]>}
8
8
  */
9
9
  events: any;
10
10
  /**
@@ -14,39 +14,39 @@ export class EventEmitterLite<Events extends string | Record<string, any[]> = st
14
14
  logErrors: boolean;
15
15
  /**
16
16
  * on is used to add a callback function that's going to be executed when the event is triggered
17
- * @template {Events extends string ? Events : keyof Events} K
17
+ * @template {Events extends string | symbol ? Events : keyof Events} K
18
18
  * @param {K} event
19
19
  * @param {Function} listener
20
20
  * @returns {() => void}
21
21
  */
22
- on<K extends Events extends string ? Events : keyof Events>(event: K, listener: Function): () => void;
22
+ on<K extends Events extends string | symbol ? Events : keyof Events>(event: K, listener: Function): () => void;
23
23
  /**
24
24
  * Add a one-time listener
25
- * @template {Events extends string ? Events : keyof Events} K
25
+ * @template {Events extends string | symbol ? Events : keyof Events} K
26
26
  * @param {K} event
27
27
  * @param {Function} listener
28
28
  * @returns {()=>void}
29
29
  */
30
- once<K extends Events extends string ? Events : keyof Events>(event: K, listener: Function): () => void;
30
+ once<K extends Events extends string | symbol ? Events : keyof Events>(event: K, listener: Function): () => void;
31
31
  /**
32
32
  * off is an alias for removeListener
33
- * @template {Events extends string ? Events : keyof Events} K
33
+ * @template {Events extends string | symbol ? Events : keyof Events} K
34
34
  * @param {K} event
35
35
  * @param {Function} listener
36
36
  */
37
- off<K extends Events extends string ? Events : keyof Events>(event: K, listener: Function): void;
37
+ off<K extends Events extends string | symbol ? Events : keyof Events>(event: K, listener: Function): void;
38
38
  /**
39
39
  * Remove an event listener from an event
40
- * @template {Events extends string ? Events : keyof Events} K
40
+ * @template {Events extends string | symbol ? Events : keyof Events} K
41
41
  * @param {K} event
42
42
  * @param {Function} listener
43
43
  */
44
- removeListener<K extends Events extends string ? Events : keyof Events>(event: K, listener: Function): void;
44
+ removeListener<K extends Events extends string | symbol ? Events : keyof Events>(event: K, listener: Function): void;
45
45
  /**
46
46
  * emit is used to trigger an event
47
- * @template {Events extends string ? Events : keyof Events} K
47
+ * @template {Events extends string | symbol ? Events : keyof Events} K
48
48
  * @param {K} event
49
49
  * @param {...any} args
50
50
  */
51
- emit<K extends Events extends string ? Events : keyof Events>(event: K, ...args: any[]): void;
51
+ emit<K extends Events extends string | symbol ? Events : keyof Events>(event: K, ...args: any[]): void;
52
52
  }
@@ -3,11 +3,11 @@
3
3
  export const ORIGINAL = Symbol('original');
4
4
 
5
5
  /**
6
- * @template {string | Record<string, any[]>} [Events=string]
6
+ * @template {string | symbol | Record<string|symbol, any[]>} [Events=string]
7
7
  */
8
8
  export class EventEmitterLite {
9
9
  /**
10
- * @type {Object.<Events extends string ? Events : keyof Events, Function[]>}
10
+ * @type {Object.<Events extends string | symbol ? Events : keyof Events, Function[]>}
11
11
  */
12
12
  events = Object.create(null);
13
13
 
@@ -19,7 +19,7 @@ export class EventEmitterLite {
19
19
 
20
20
  /**
21
21
  * on is used to add a callback function that's going to be executed when the event is triggered
22
- * @template {Events extends string ? Events : keyof Events} K
22
+ * @template {Events extends string | symbol ? Events : keyof Events} K
23
23
  * @param {K} event
24
24
  * @param {Function} listener
25
25
  * @returns {() => void}
@@ -34,7 +34,7 @@ export class EventEmitterLite {
34
34
 
35
35
  /**
36
36
  * Add a one-time listener
37
- * @template {Events extends string ? Events : keyof Events} K
37
+ * @template {Events extends string | symbol ? Events : keyof Events} K
38
38
  * @param {K} event
39
39
  * @param {Function} listener
40
40
  * @returns {()=>void}
@@ -50,7 +50,7 @@ export class EventEmitterLite {
50
50
 
51
51
  /**
52
52
  * off is an alias for removeListener
53
- * @template {Events extends string ? Events : keyof Events} K
53
+ * @template {Events extends string | symbol ? Events : keyof Events} K
54
54
  * @param {K} event
55
55
  * @param {Function} listener
56
56
  */
@@ -60,7 +60,7 @@ export class EventEmitterLite {
60
60
 
61
61
  /**
62
62
  * Remove an event listener from an event
63
- * @template {Events extends string ? Events : keyof Events} K
63
+ * @template {Events extends string | symbol ? Events : keyof Events} K
64
64
  * @param {K} event
65
65
  * @param {Function} listener
66
66
  */
@@ -80,7 +80,7 @@ export class EventEmitterLite {
80
80
 
81
81
  /**
82
82
  * emit is used to trigger an event
83
- * @template {Events extends string ? Events : keyof Events} K
83
+ * @template {Events extends string | symbol ? Events : keyof Events} K
84
84
  * @param {K} event
85
85
  * @param {...any} args
86
86
  */
@@ -1,8 +1,8 @@
1
1
  /**
2
- * @template {string | Record<string, any[]>} [Events=string]
2
+ * @template {string | symbol | Record<string|symbol, any[]>} [Events=string]
3
3
  * @extends {EventEmitterLite<Events>}
4
4
  */
5
- export class EventEmitter<Events extends string | Record<string, any[]> = string> extends EventEmitterLite<Events> {
5
+ export class EventEmitter<Events extends string | symbol | Record<string | symbol, any[]> = string> extends EventEmitterLite<Events> {
6
6
  /**
7
7
  * Is the event emitter destroyed?
8
8
  * @type {boolean}
@@ -10,20 +10,20 @@ export class EventEmitter<Events extends string | Record<string, any[]> = string
10
10
  get isDestroyed(): boolean;
11
11
  /**
12
12
  * Wait for a specific event to be emitted.
13
- * @template {Events extends string ? Events : keyof Events} K
13
+ * @template {Events extends string | symbol? Events : keyof Events} K
14
14
  * @param {K} event - The event to wait for.
15
15
  * @param {number} [max_wait_ms=0] - Maximum time to wait in ms. If 0, waits indefinitely.
16
16
  * @returns {Promise<boolean>} - Resolves with true if event emitted, false on timeout.
17
17
  */
18
- waitForEvent<K extends Events extends string ? Events : keyof Events>(event: K, max_wait_ms?: number): Promise<boolean>;
18
+ waitForEvent<K extends Events extends string | symbol ? Events : keyof Events>(event: K, max_wait_ms?: number): Promise<boolean>;
19
19
  /**
20
20
  * Wait for any of the specified events to be emitted.
21
- * @template {Events extends string ? Events : keyof Events} K
21
+ * @template {Events extends string | symbol? Events : keyof Events} K
22
22
  * @param {K[]} events - Array of event names.
23
23
  * @param {number} [max_wait_ms=0] - Maximum time to wait in ms.
24
24
  * @returns {Promise<boolean>} - Resolves with true if any event emitted, false on timeout.
25
25
  */
26
- waitForAnyEvent<K extends Events extends string ? Events : keyof Events>(events: K[], max_wait_ms?: number): Promise<boolean>;
26
+ waitForAnyEvent<K extends Events extends string | symbol ? Events : keyof Events>(events: K[], max_wait_ms?: number): Promise<boolean>;
27
27
  /**
28
28
  * Clear all events
29
29
  */
@@ -34,10 +34,10 @@ export class EventEmitter<Events extends string | Record<string, any[]> = string
34
34
  destroy(): void;
35
35
  /**
36
36
  * Clears all listeners for a specified event.
37
- * @template {Events extends string ? Events : keyof Events} K
37
+ * @template {Events extends string | symbol? Events : keyof Events} K
38
38
  * @param {K} event
39
39
  */
40
- clearEventListeners<K extends Events extends string ? Events : keyof Events>(event: K): void;
40
+ clearEventListeners<K extends Events extends string | symbol ? Events : keyof Events>(event: K): void;
41
41
  /**
42
42
  * onHasEventListeners() is used to subscribe to the "#has-listeners" event. This event is emitted when the number of listeners for any event (except "#has-listeners" and "#no-listeners") goes from 0 to 1.
43
43
  * @param {Function} callback
@@ -2,7 +2,7 @@
2
2
  import { EventEmitterLite } from './event-emitter-lite.js';
3
3
 
4
4
  /**
5
- * @template {string | Record<string, any[]>} [Events=string]
5
+ * @template {string | symbol | Record<string|symbol, any[]>} [Events=string]
6
6
  * @extends {EventEmitterLite<Events>}
7
7
  */
8
8
  export class EventEmitter extends EventEmitterLite {
@@ -26,7 +26,7 @@ export class EventEmitter extends EventEmitterLite {
26
26
 
27
27
  /**
28
28
  * on is used to add a callback function that's going to be executed when the event is triggered
29
- * @template {Events extends string ? Events : keyof Events} K
29
+ * @template {Events extends string | symbol ? Events : keyof Events} K
30
30
  * @param {K} event
31
31
  * @param {Function} listener
32
32
  */
@@ -47,7 +47,7 @@ export class EventEmitter extends EventEmitterLite {
47
47
 
48
48
  /**
49
49
  * Remove an event listener from an event
50
- * @template {Events extends string ? Events : keyof Events} K
50
+ * @template {Events extends string | symbol ? Events : keyof Events} K
51
51
  * @param {K} event
52
52
  * @param {Function} listener
53
53
  */
@@ -96,7 +96,7 @@ export class EventEmitter extends EventEmitterLite {
96
96
 
97
97
  /**
98
98
  * emit is used to trigger an event
99
- * @template {Events extends string ? Events : keyof Events} K
99
+ * @template {Events extends string | symbol ? Events : keyof Events} K
100
100
  * @param {K} event
101
101
  * @param {...any} args
102
102
  */
@@ -157,7 +157,7 @@ export class EventEmitter extends EventEmitterLite {
157
157
 
158
158
  /**
159
159
  * Wait for a specific event to be emitted.
160
- * @template {Events extends string ? Events : keyof Events} K
160
+ * @template {Events extends string | symbol? Events : keyof Events} K
161
161
  * @param {K} event - The event to wait for.
162
162
  * @param {number} [max_wait_ms=0] - Maximum time to wait in ms. If 0, waits indefinitely.
163
163
  * @returns {Promise<boolean>} - Resolves with true if event emitted, false on timeout.
@@ -168,7 +168,7 @@ export class EventEmitter extends EventEmitterLite {
168
168
 
169
169
  /**
170
170
  * Wait for any of the specified events to be emitted.
171
- * @template {Events extends string ? Events : keyof Events} K
171
+ * @template {Events extends string | symbol? Events : keyof Events} K
172
172
  * @param {K[]} events - Array of event names.
173
173
  * @param {number} [max_wait_ms=0] - Maximum time to wait in ms.
174
174
  * @returns {Promise<boolean>} - Resolves with true if any event emitted, false on timeout.
@@ -212,7 +212,7 @@ export class EventEmitter extends EventEmitterLite {
212
212
  clear() {
213
213
  if (this.#isDestroyed) return;
214
214
 
215
- /** @type {(Events extends string ? Events : keyof Events)[]} */
215
+ /** @type {(Events extends string | symbol? Events : keyof Events)[]} */
216
216
  // @ts-ignore
217
217
  const eventNames = Object.keys(this.events);
218
218
 
@@ -238,7 +238,7 @@ export class EventEmitter extends EventEmitterLite {
238
238
 
239
239
  /**
240
240
  * Clears all listeners for a specified event.
241
- * @template {Events extends string ? Events : keyof Events} K
241
+ * @template {Events extends string | symbol? Events : keyof Events} K
242
242
  * @param {K} event
243
243
  */
244
244
  clearEventListeners(event) {
package/.prettierrc DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "semi": true,
3
- "tabWidth": 4,
4
- "printWidth": 100,
5
- "singleQuote": true,
6
- "trailingComma": "es5",
7
- "bracketSpacing": true,
8
- "arrowParens": "avoid",
9
- "endOfLine": "auto"
10
- }
package/jsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "baseUrl": ".",
4
- "target": "ESNext",
5
- "module": "NodeNext",
6
- "noEmit": true,
7
- "lib": ["ES2020", "es5", "es6", "dom", "dom.iterable"],
8
- "moduleResolution": "NodeNext"
9
- },
10
- "exclude": ["node_modules"],
11
- "include": ["index.js", "modules/**/*.d.ts"]
12
- }
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "allowJs": true,
4
- "target": "ESNext",
5
- "module": "NodeNext",
6
- "emitDeclarationOnly": true,
7
- "declaration": true,
8
- "outDir": "./",
9
- "rootDir": ".",
10
- "declarationMap": false,
11
- "lib": ["ESNext", "DOM", "ES2023"],
12
- "moduleResolution": "NodeNext",
13
- "stripInternal": true
14
- },
15
- "include": [
16
- "./index.js"
17
- ],
18
- "exclude": []
19
- }
@@ -1,256 +0,0 @@
1
- // @ts-check
2
-
3
- import ava from 'ava';
4
- import { EventEmitterLite } from '../src/event-emitter-lite.js';
5
-
6
- /** @typedef {{ emitter: EventEmitterLite }} MyContext */
7
- /** @typedef {import('ava').TestFn<MyContext>} TestFn */
8
-
9
- const test = /** @type {TestFn} */ (ava);
10
-
11
- test.beforeEach(t => {
12
- t.context.emitter = new EventEmitterLite();
13
- });
14
-
15
- test('on() should add listener and emit should call it', t => {
16
- const { emitter } = t.context;
17
- let called = false;
18
- emitter.on('test', () => {
19
- called = true;
20
- });
21
- emitter.emit('test');
22
- t.true(called);
23
- });
24
-
25
- test('emit() should pass arguments to listener', t => {
26
- const { emitter } = t.context;
27
- emitter.on(
28
- 'test',
29
- /** @param {number} a @param {string} b */ (a, b) => {
30
- t.is(a, 1);
31
- t.is(b, 'two');
32
- }
33
- );
34
- emitter.emit('test', 1, 'two');
35
- });
36
-
37
- test('once() should call listener only once', t => {
38
- const { emitter } = t.context;
39
- let count = 0;
40
- emitter.once('test', () => count++);
41
- emitter.emit('test');
42
- emitter.emit('test');
43
- t.is(count, 1);
44
- });
45
-
46
- test('once() should be removable via off() with original listener', t => {
47
- const { emitter } = t.context;
48
- let count = 0;
49
- function listener() {
50
- count++;
51
- }
52
- emitter.once('test', listener);
53
- emitter.off('test', listener); // remove listener
54
- emitter.emit('test');
55
- t.is(count, 0);
56
- });
57
-
58
- test('off() alias for removeListener should remove listener', t => {
59
- const { emitter } = t.context;
60
- let called = false;
61
- const fn = () => {
62
- called = true;
63
- };
64
- emitter.on('test', fn);
65
- emitter.off('test', fn);
66
- emitter.emit('test');
67
- t.false(called);
68
- });
69
-
70
- test('removeListener() should remove listener added via on()', t => {
71
- const { emitter } = t.context;
72
- let called = false;
73
- const fn = () => {
74
- called = true;
75
- };
76
- emitter.on('test', fn);
77
- emitter.removeListener('test', fn);
78
- emitter.emit('test');
79
- t.false(called);
80
- });
81
-
82
- test('unsubscriber returned by on() should remove listener', t => {
83
- const { emitter } = t.context;
84
- let called = false;
85
- const unsub = emitter.on('test', () => {
86
- called = true;
87
- });
88
- unsub();
89
- emitter.emit('test');
90
- t.false(called);
91
- });
92
-
93
- test('removeListener() should do nothing if listener not found', t => {
94
- const { emitter } = t.context;
95
- t.notThrows(() => {
96
- emitter.removeListener('test', () => {});
97
- });
98
- });
99
-
100
- test('emit() with no listeners should do nothing', t => {
101
- const { emitter } = t.context;
102
- t.notThrows(() => {
103
- emitter.emit('test');
104
- });
105
- });
106
-
107
- test('multiple listeners should all be called', t => {
108
- const { emitter } = t.context;
109
- let count = 0;
110
- const inc = () => count++;
111
- emitter.on('test', inc);
112
- emitter.on('test', inc);
113
- emitter.emit('test');
114
- t.is(count, 2);
115
- });
116
-
117
- test('listeners should be called in order of addition', t => {
118
- const { emitter } = t.context;
119
- /** @type {number[]} */
120
- const order = [];
121
- emitter.on('test', () => order.push(1));
122
- emitter.on('test', () => order.push(2));
123
- emitter.emit('test');
124
- t.deepEqual(order, [1, 2]);
125
- });
126
-
127
- test('removing listener during emit does not affect current iteration', t => {
128
- const { emitter } = t.context;
129
- let called = 0;
130
- const fn1 = () => {
131
- called++;
132
- emitter.removeListener('test', fn2); // remove fn2
133
- };
134
- const fn2 = () => {
135
- called++;
136
- };
137
- emitter.on('test', fn1);
138
- emitter.on('test', fn2);
139
- emitter.emit('test');
140
- t.is(called, 2); // fn2 called twice
141
- });
142
-
143
- test('adding listener during emit does not affect current iteration', t => {
144
- const { emitter } = t.context;
145
- let called = 0;
146
- const fn1 = () => {
147
- emitter.on('test', () => called++);
148
- };
149
- emitter.on('test', fn1);
150
- emitter.emit('test');
151
- t.is(called, 0); // fn1 not called
152
- });
153
-
154
- test('logErrors: true should log to console', t => {
155
- const { emitter } = t.context;
156
- const originalError = console.error;
157
- let logged = false;
158
- console.error = (...args) => {
159
- logged = true;
160
- };
161
- t.teardown(() => {
162
- console.error = originalError;
163
- });
164
-
165
- emitter.logErrors = true;
166
- emitter.on('test', () => {
167
- throw new Error('boom');
168
- });
169
- t.notThrows(() => emitter.emit('test'));
170
- t.true(logged);
171
- });
172
-
173
- test('logErrors: false should not log', t => {
174
- const { emitter } = t.context;
175
- const originalError = console.error;
176
- let logged = false;
177
- console.error = (...args) => {
178
- logged = true;
179
- };
180
- t.teardown(() => {
181
- console.error = originalError;
182
- });
183
-
184
- emitter.logErrors = false;
185
- emitter.on('test', () => {
186
- throw new Error('boom');
187
- });
188
- t.notThrows(() => emitter.emit('test'));
189
- t.false(logged);
190
- });
191
-
192
- test('error in one listener does not prevent others', t => {
193
- const { emitter } = t.context;
194
- let called = false;
195
- emitter.on('test', () => {
196
- throw new Error('err');
197
- });
198
- emitter.on('test', () => {
199
- called = true;
200
- });
201
- t.notThrows(() => emitter.emit('test'));
202
- t.true(called);
203
- });
204
-
205
- test('this context in listener should be the emitter', t => {
206
- const { emitter } = t.context;
207
- emitter.on('test', function () {
208
- // @ts-ignore
209
- t.is(this, emitter);
210
- });
211
- emitter.emit('test');
212
- });
213
-
214
- test('event key is removed from events object when last listener removed', t => {
215
- const { emitter } = t.context;
216
- const fn = () => {};
217
- emitter.on('test', fn);
218
- t.true('test' in emitter.events); // or Object.prototype.hasOwnProperty.call(emitter.events, 'test')
219
- emitter.removeListener('test', fn);
220
- t.false('test' in emitter.events);
221
- });
222
-
223
- test('off with non-function listener should not throw', t => {
224
- const { emitter } = t.context;
225
- t.notThrows(() => {
226
- // @ts-expect-error
227
- emitter.off('test', 'not a function');
228
- });
229
- });
230
-
231
- test('same listener added twice should be called twice', t => {
232
- const { emitter } = t.context;
233
- let count = 0;
234
- function fn() {
235
- count++;
236
- }
237
- emitter.on('test', fn);
238
- emitter.on('test', fn);
239
- emitter.emit('test');
240
- t.is(count, 2);
241
- emitter.off('test', fn);
242
- emitter.emit('test');
243
- t.is(count, 3);
244
- });
245
-
246
- test('once listener should be removable via off with original', t => {
247
- const { emitter } = t.context;
248
- let called = false;
249
- function listener() {
250
- called = true;
251
- }
252
- emitter.once('test', listener);
253
- emitter.off('test', listener);
254
- emitter.emit('test');
255
- t.false(called);
256
- });
@@ -1,471 +0,0 @@
1
- // @ts-check
2
- import ava from 'ava';
3
- import { EventEmitter } from '../src/event-emitter.js';
4
-
5
- /** @typedef {{ emitter: EventEmitter<any> }} MyContext */
6
- /** @typedef {import('ava').TestFn<MyContext>} TestFn */
7
-
8
- const test = /** @type {TestFn} */ (ava);
9
-
10
- test.beforeEach(t => {
11
- t.context = { emitter: new EventEmitter() };
12
- });
13
-
14
- // ----------------------------------------------------------------------
15
- // Basic inheritance (quick check that Lite works)
16
- // ----------------------------------------------------------------------
17
- test('should inherit from EventEmitterLite: on and emit work', t => {
18
- const { emitter } = t.context;
19
- let called = false;
20
- emitter.on('test', () => {
21
- called = true;
22
- });
23
- emitter.emit('test');
24
- t.true(called);
25
- });
26
-
27
- test('once works as expected', t => {
28
- const { emitter } = t.context;
29
- let count = 0;
30
- emitter.once('test', () => count++);
31
- emitter.emit('test');
32
- emitter.emit('test');
33
- t.is(count, 1);
34
- });
35
-
36
- test('off removes listener', t => {
37
- const { emitter } = t.context;
38
- let called = false;
39
- const fn = () => {
40
- called = true;
41
- };
42
- emitter.on('test', fn);
43
- emitter.off('test', fn);
44
- emitter.emit('test');
45
- t.false(called);
46
- });
47
-
48
- // ----------------------------------------------------------------------
49
- // Internal events (#has-listeners, #no-listeners, #listener-error)
50
- // ----------------------------------------------------------------------
51
- test('#has-listeners emitted when first listener added', t => {
52
- const { emitter } = t.context;
53
- /** @type {string[]} */
54
- const events = [];
55
-
56
- emitter.onHasEventListeners(
57
- /** @param {string} event */ event => {
58
- events.push(event);
59
- }
60
- );
61
-
62
- emitter.on('foo', () => {});
63
- emitter.on('foo', () => {}); // second should not trigger
64
- emitter.on('bar', () => {}); // should trigger for bar
65
-
66
- t.deepEqual(events, ['foo', 'bar']);
67
- });
68
-
69
- test('#no-listeners emitted when last listener removed', t => {
70
- const { emitter } = t.context;
71
- /** @type {string[]} */
72
- const events = [];
73
-
74
- emitter.onNoEventListeners(
75
- /** @param {string} event */ event => {
76
- events.push(event);
77
- }
78
- );
79
-
80
- const fn1 = () => {};
81
- const fn2 = () => {};
82
- const fnBar = () => {}; // keep reference for bar
83
-
84
- emitter.on('foo', fn1);
85
- emitter.on('foo', fn2);
86
- emitter.on('bar', fnBar);
87
-
88
- emitter.off('foo', fn1); // not last for foo yet
89
- emitter.off('foo', fn2); // last for foo -> #no-listeners('foo')
90
- emitter.off('bar', fnBar); // last for bar -> #no-listeners('bar')
91
-
92
- t.deepEqual(events, ['foo', 'bar']);
93
- });
94
-
95
- test('#listener-error emitted when a listener throws', t => {
96
- const { emitter } = t.context;
97
- /** @type {Array<{ error: Error; event: string; args: any[] }>} */
98
- const errors = [];
99
-
100
- emitter.onListenerError(
101
- /** @type {(error: Error, event: string, ...args: any[]) => void} */ (
102
- error,
103
- event,
104
- ...args
105
- ) => {
106
- errors.push({ error, event, args });
107
- }
108
- );
109
-
110
- emitter.on('fail', () => {
111
- throw new Error('boom 1');
112
- });
113
- emitter.on(
114
- 'fail',
115
- /** @type {(x: number) => void} */ x => {
116
- throw new Error(`boom ${x}`);
117
- }
118
- );
119
- emitter.emit('fail', 42);
120
-
121
- t.is(errors.length, 2);
122
- t.is(errors[0].error.message, 'boom 1');
123
- t.is(errors[0].event, 'fail');
124
- t.deepEqual(errors[0].args, [42]);
125
-
126
- t.is(errors[1].error.message, 'boom 42');
127
- t.is(errors[1].event, 'fail');
128
- t.deepEqual(errors[1].args, [42]);
129
- });
130
-
131
- test('internal events do not trigger #has-listeners/#no-listeners for themselves', t => {
132
- const { emitter } = t.context;
133
- let hasCount = 0;
134
- let noCount = 0;
135
-
136
- emitter.onHasEventListeners(() => hasCount++);
137
- emitter.onNoEventListeners(() => noCount++);
138
-
139
- // Subscribing to internal events should not increase counters for 'has-listeners' etc.
140
- emitter.onHasEventListeners(() => {});
141
- emitter.onNoEventListeners(() => {});
142
- emitter.onListenerError(() => {});
143
-
144
- t.is(hasCount, 0);
145
- t.is(noCount, 0);
146
-
147
- // Adding a regular event should trigger
148
- emitter.on('test', () => {});
149
- t.is(hasCount, 1);
150
-
151
- // Removing the last one should trigger no
152
- emitter.off('test', () => {}); // but need to remove the same listener that was added
153
- // For test purity, better to keep a reference
154
- });
155
-
156
- // ----------------------------------------------------------------------
157
- // waitForEvent / waitForAnyEvent
158
- // ----------------------------------------------------------------------
159
- test('waitForEvent resolves true when event emitted', async t => {
160
- const { emitter } = t.context;
161
- const promise = emitter.waitForEvent('ready');
162
- emitter.emit('ready');
163
- const result = await promise;
164
- t.true(result);
165
- });
166
-
167
- test('waitForEvent resolves false on timeout', async t => {
168
- const { emitter } = t.context;
169
- const promise = emitter.waitForEvent('ready', 10);
170
- const result = await promise;
171
- t.false(result);
172
- });
173
-
174
- test('waitForAnyEvent resolves true when any event emitted', async t => {
175
- const { emitter } = t.context;
176
- const promise = emitter.waitForAnyEvent(['start', 'stop'], 100);
177
- emitter.emit('stop');
178
- const result = await promise;
179
- t.true(result);
180
- });
181
-
182
- test('waitForAnyEvent resolves false on timeout', async t => {
183
- const { emitter } = t.context;
184
- const promise = emitter.waitForAnyEvent(['start', 'stop'], 10);
185
- const result = await promise;
186
- t.false(result);
187
- });
188
-
189
- test('waitForAnyEvent unsubscribes after first event', async t => {
190
- const { emitter } = t.context;
191
- let count = 0;
192
- emitter.on('event1', () => count++);
193
- emitter.on('event2', () => count++);
194
-
195
- await emitter.waitForAnyEvent(['event1', 'event2'], 100);
196
- emitter.emit('event1');
197
- emitter.emit('event2');
198
-
199
- t.is(count, 2); // both events should fire after waiting
200
- });
201
-
202
- test('waitForEvent throws if emitter destroyed', t => {
203
- const { emitter } = t.context;
204
- emitter.destroy();
205
- t.throws(() => emitter.waitForEvent('test'), { message: /destroyed/ });
206
- });
207
-
208
- test('waitForAnyEvent throws if emitter destroyed', t => {
209
- const { emitter } = t.context;
210
- emitter.destroy();
211
- t.throws(() => emitter.waitForAnyEvent(['test']), { message: /destroyed/ });
212
- });
213
-
214
- // ----------------------------------------------------------------------
215
- // destroy
216
- // ----------------------------------------------------------------------
217
- test('destroy sets isDestroyed = true and clears events', t => {
218
- const { emitter } = t.context;
219
- emitter.on('test', () => {});
220
- t.false(emitter.isDestroyed);
221
- emitter.destroy();
222
- t.true(emitter.isDestroyed);
223
- t.is(Object.keys(emitter.events).length, 0);
224
-
225
- // After destroy, trying to subscribe to internal events should throw
226
- t.throws(() => emitter.onHasEventListeners(() => {}), { message: /destroyed/ });
227
- t.throws(() => emitter.onNoEventListeners(() => {}), { message: /destroyed/ });
228
- t.throws(() => emitter.onListenerError(() => {}), { message: /destroyed/ });
229
- });
230
-
231
- test('after destroy, on throws', t => {
232
- const { emitter } = t.context;
233
- emitter.destroy();
234
- t.throws(() => emitter.on('test', () => {}), { message: /destroyed/ });
235
- });
236
-
237
- test('after destroy, emit does nothing (no throw)', t => {
238
- const { emitter } = t.context;
239
- emitter.destroy();
240
- t.notThrows(() => emitter.emit('test'));
241
- });
242
-
243
- test('after destroy, removeListener does nothing', t => {
244
- const { emitter } = t.context;
245
- emitter.destroy();
246
- t.notThrows(() => emitter.removeListener('test', () => {}));
247
- });
248
-
249
- test('destroy can be called multiple times without error', t => {
250
- const { emitter } = t.context;
251
- emitter.destroy();
252
- t.notThrows(() => emitter.destroy());
253
- });
254
-
255
- // ----------------------------------------------------------------------
256
- // clear and clearEventListeners
257
- // ----------------------------------------------------------------------
258
- test('clear removes all listeners and emits #no-listeners for each', t => {
259
- const { emitter } = t.context;
260
- /** @type {string[]} */
261
- const noEvents = [];
262
-
263
- emitter.onNoEventListeners(/** @param {string} event */ event => noEvents.push(event));
264
- emitter.on('a', () => {});
265
- emitter.on('b', () => {});
266
- emitter.on('c', () => {});
267
-
268
- emitter.clear();
269
-
270
- t.deepEqual(noEvents.sort(), ['a', 'b', 'c']);
271
- t.is(Object.keys(emitter.events).length, 0);
272
- });
273
-
274
- test('clearEventListeners removes all listeners for one event', t => {
275
- const { emitter } = t.context;
276
- let noCalled = false;
277
-
278
- emitter.onNoEventListeners(
279
- /** @param {string} event */ event => {
280
- if (event === 'test') noCalled = true;
281
- }
282
- );
283
-
284
- emitter.on('test', () => {});
285
- emitter.on('test', () => {});
286
- emitter.on('other', () => {});
287
-
288
- emitter.clearEventListeners('test');
289
-
290
- t.true(noCalled);
291
- t.false('test' in emitter.events); // key removed
292
- t.true('other' in emitter.events); // other remains
293
- t.is(emitter.events['other'].length, 1);
294
- });
295
-
296
- // ----------------------------------------------------------------------
297
- // logErrors and interaction with #listener-error
298
- // ----------------------------------------------------------------------
299
- test('when logErrors is true, errors are logged and #listener-error emitted', t => {
300
- const { emitter } = t.context;
301
- const originalError = console.error;
302
- /** @type {any[][]} */
303
- const logged = [];
304
- console.error = (...args) => logged.push(args);
305
- t.teardown(() => {
306
- console.error = originalError;
307
- });
308
-
309
- let internalErrorCaught = false;
310
- emitter.onListenerError(() => {
311
- internalErrorCaught = true;
312
- });
313
-
314
- emitter.logErrors = true;
315
- emitter.on('boom', () => {
316
- throw new Error('kaboom');
317
- });
318
- emitter.emit('boom');
319
-
320
- t.true(internalErrorCaught);
321
- t.true(logged.length > 0);
322
- t.regex(logged[0][0], /Error in listener for event "boom"/);
323
- });
324
-
325
- test('when logErrors is false, errors are not logged but #listener-error still emitted', t => {
326
- const { emitter } = t.context;
327
- const originalError = console.error;
328
- let logged = false;
329
- console.error = () => (logged = true);
330
- t.teardown(() => {
331
- console.error = originalError;
332
- });
333
-
334
- let internalErrorCaught = false;
335
- emitter.onListenerError(() => {
336
- internalErrorCaught = true;
337
- });
338
-
339
- emitter.logErrors = false;
340
- emitter.on('boom', () => {
341
- throw new Error('kaboom');
342
- });
343
- emitter.emit('boom');
344
-
345
- t.true(internalErrorCaught);
346
- t.false(logged);
347
- });
348
-
349
- // ----------------------------------------------------------------------
350
- // Event key removal when no listeners (inherited from Lite)
351
- // ----------------------------------------------------------------------
352
- test('event key removed when last listener removed (inherited)', t => {
353
- const { emitter } = t.context;
354
- const fn = () => {};
355
- emitter.on('test', fn);
356
- t.true('test' in emitter.events);
357
- emitter.removeListener('test', fn);
358
- t.false('test' in emitter.events);
359
- });
360
-
361
- // ----------------------------------------------------------------------
362
- // Getter isDestroyed
363
- // ----------------------------------------------------------------------
364
- test('isDestroyed reflects destroyed state', t => {
365
- const { emitter } = t.context;
366
- t.false(emitter.isDestroyed);
367
- emitter.destroy();
368
- t.true(emitter.isDestroyed);
369
- });
370
-
371
- // test/event-emitter-extra.test.js (or add to existing file)
372
-
373
- // ----------------------------------------------------------------------
374
- // Additional tests to cover uncovered lines
375
- // ----------------------------------------------------------------------
376
-
377
- test('emit with no listeners should not throw and not call internal events', t => {
378
- const { emitter } = t.context;
379
- let internalErrorCalled = false;
380
- emitter.onListenerError(() => {
381
- internalErrorCalled = true;
382
- });
383
- t.notThrows(() => emitter.emit('nonexistent'));
384
- t.false(internalErrorCalled); // covers line where listeners existence is checked
385
- });
386
-
387
- test('error in #listener-error handler does not cause infinite loop', t => {
388
- const { emitter } = t.context;
389
- const originalConsoleError = console.error;
390
- let consoleCalled = false;
391
- console.error = () => {
392
- consoleCalled = true;
393
- };
394
- t.teardown(() => {
395
- console.error = originalConsoleError;
396
- });
397
-
398
- // Subscriber to #listener-error itself throws
399
- emitter.onListenerError(() => {
400
- throw new Error('error in error handler');
401
- });
402
-
403
- // Regular listener that triggers #listener-error
404
- emitter.on('boom', () => {
405
- throw new Error('original error');
406
- });
407
-
408
- t.notThrows(() => emitter.emit('boom'));
409
- t.true(consoleCalled); // critical error should be logged
410
- });
411
-
412
- test('waitForEvent with zero timeout waits indefinitely', async t => {
413
- const { emitter } = t.context;
414
- let resolved = false;
415
- const promise = emitter.waitForEvent('ready', 0).then(() => {
416
- resolved = true;
417
- });
418
-
419
- await new Promise(r => setTimeout(r, 50));
420
- t.false(resolved); // not resolved yet
421
-
422
- emitter.emit('ready');
423
- await promise;
424
- t.true(resolved);
425
- });
426
-
427
- test('waitForEvent with negative timeout waits indefinitely until event', async t => {
428
- const { emitter } = t.context;
429
- const promise = emitter.waitForEvent('ready', -10);
430
- setTimeout(() => emitter.emit('ready'), 20);
431
- const result = await promise;
432
- t.true(result);
433
- });
434
-
435
- test('clearEventListeners on non-existent event does nothing', t => {
436
- const { emitter } = t.context;
437
- t.notThrows(() => emitter.clearEventListeners('absent'));
438
- t.false('absent' in emitter.events);
439
- });
440
-
441
- test('calling destroy multiple times does nothing', t => {
442
- const { emitter } = t.context;
443
- emitter.destroy();
444
- t.notThrows(() => emitter.destroy());
445
- t.true(emitter.isDestroyed);
446
- });
447
-
448
- test('after destroy, onHasEventListeners throws', t => {
449
- const { emitter } = t.context;
450
- emitter.destroy();
451
- t.throws(() => emitter.onHasEventListeners(() => {}), { message: /destroyed/ });
452
- });
453
-
454
- test('error in #listener-error handler does not cause infinite loop and flag resets', t => {
455
- const { emitter } = t.context;
456
- let calls = 0;
457
-
458
- // Subscribe to #listener-error and throw
459
- emitter.onListenerError(() => {
460
- calls++;
461
- throw new Error('error in error handler');
462
- });
463
-
464
- // Regular listener that triggers the first error
465
- emitter.on('boom', () => {
466
- throw new Error('original error');
467
- });
468
-
469
- t.notThrows(() => emitter.emit('boom'));
470
- t.is(calls, 1); // #listener-error handler called once
471
- });