@reldens/utils 0.49.0 → 0.51.0
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/lib/events-manager.js +324 -64
- package/lib/logger.js +13 -8
- package/lib/shortcuts.js +10 -0
- package/package.json +5 -4
- package/tests/events-manager-test.js +1084 -0
- package/tests/run.js +13 -0
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
const EventsManager = require('../lib/events-manager');
|
|
2
|
+
|
|
3
|
+
class TestEventsManager
|
|
4
|
+
{
|
|
5
|
+
|
|
6
|
+
constructor()
|
|
7
|
+
{
|
|
8
|
+
this.testResults = [];
|
|
9
|
+
this.testCount = 0;
|
|
10
|
+
this.passedCount = 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test(name, testFn)
|
|
14
|
+
{
|
|
15
|
+
this.testCount++;
|
|
16
|
+
try{
|
|
17
|
+
testFn();
|
|
18
|
+
console.log('✓ PASS:', name);
|
|
19
|
+
this.passedCount++;
|
|
20
|
+
this.testResults.push({name, status: 'PASS'});
|
|
21
|
+
} catch(error){
|
|
22
|
+
console.log('✗ FAIL:', name, '-', error.message);
|
|
23
|
+
this.testResults.push({name, status: 'FAIL', error: error.message});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
assert(condition, message)
|
|
28
|
+
{
|
|
29
|
+
if(!condition){
|
|
30
|
+
throw new Error(message || 'Assertion failed');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
runAllTests()
|
|
35
|
+
{
|
|
36
|
+
console.log('Running tests for EventsManager...\n');
|
|
37
|
+
|
|
38
|
+
let methodNames = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
|
|
39
|
+
let testMethods = methodNames.filter(name =>
|
|
40
|
+
name.startsWith('test') &&
|
|
41
|
+
'function' === typeof this[name] &&
|
|
42
|
+
name !== 'test'
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
for(let methodName of testMethods){
|
|
46
|
+
this[methodName]();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.printSummary();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
testConstructorCreatesInstance()
|
|
53
|
+
{
|
|
54
|
+
this.test('Constructor creates instance', () => {
|
|
55
|
+
let emitter = new EventsManager();
|
|
56
|
+
this.assert(emitter instanceof EventsManager, 'Should create instance');
|
|
57
|
+
this.assert('object' === typeof emitter._events, 'Should have _events object');
|
|
58
|
+
this.assert('object' === typeof emitter.eventsByRemoveKeys, 'Should have eventsByRemoveKeys object');
|
|
59
|
+
this.assert(false === emitter.debug, 'Should have debug false by default');
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
testStaticTypeKeyName()
|
|
64
|
+
{
|
|
65
|
+
this.test('Constructor initializes typeKeyName property', () => {
|
|
66
|
+
let emitter = new EventsManager();
|
|
67
|
+
this.assert('undefined' !== typeof emitter.typeKeyName, 'Should have typeKeyName property');
|
|
68
|
+
this.assert('string' === typeof emitter.typeKeyName || 'symbol' === typeof emitter.typeKeyName, 'typeKeyName should be string or symbol');
|
|
69
|
+
this.assert('undefined' !== typeof emitter.symbolString, 'Should have symbolString property');
|
|
70
|
+
this.assert('--[[await-event-emitter]]--' === emitter.symbolString, 'Should have correct symbolString value');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
testConstructorUsesShortcutsForSymbolCheck()
|
|
75
|
+
{
|
|
76
|
+
this.test('Constructor uses shortcuts for Symbol function check', () => {
|
|
77
|
+
let emitter = new EventsManager();
|
|
78
|
+
if('function' === typeof Symbol){
|
|
79
|
+
this.assert('symbol' === typeof emitter.typeKeyName, 'Should create symbol when Symbol is available');
|
|
80
|
+
}
|
|
81
|
+
if('function' !== typeof Symbol){
|
|
82
|
+
this.assert('string' === typeof emitter.typeKeyName, 'Should use string when Symbol not available');
|
|
83
|
+
}
|
|
84
|
+
this.assert(emitter.symbolString === emitter.typeKeyName || Symbol.for(emitter.symbolString) === emitter.typeKeyName, 'typeKeyName should match expected value');
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
testTypeKeyNameUsedInListenerObjects()
|
|
89
|
+
{
|
|
90
|
+
this.test('typeKeyName property used correctly in listener objects', () => {
|
|
91
|
+
let emitter = new EventsManager();
|
|
92
|
+
let fn = () => {};
|
|
93
|
+
|
|
94
|
+
emitter.on('type-key-test', fn);
|
|
95
|
+
|
|
96
|
+
let eventArray = emitter._events['type-key-test'];
|
|
97
|
+
this.assert(eventArray && eventArray.length > 0, 'Should have event listeners');
|
|
98
|
+
|
|
99
|
+
let listenerObj = eventArray[0];
|
|
100
|
+
this.assert(listenerObj.hasOwnProperty(emitter.typeKeyName), 'Listener should use typeKeyName property');
|
|
101
|
+
this.assert('always' === listenerObj[emitter.typeKeyName], 'Should have correct listener type');
|
|
102
|
+
this.assert(fn === listenerObj.fn, 'Should store correct function');
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
testOnceListenerUsesTypeKeyName()
|
|
107
|
+
{
|
|
108
|
+
this.test('once listener uses typeKeyName correctly', () => {
|
|
109
|
+
let emitter = new EventsManager();
|
|
110
|
+
let fn = () => {};
|
|
111
|
+
|
|
112
|
+
emitter.once('once-type-key-test', fn);
|
|
113
|
+
|
|
114
|
+
let eventArray = emitter._events['once-type-key-test'];
|
|
115
|
+
let listenerObj = eventArray[0];
|
|
116
|
+
this.assert('once' === listenerObj[emitter.typeKeyName], 'Once listener should have correct type');
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
testConstructorInitializesPerformanceFeatures()
|
|
121
|
+
{
|
|
122
|
+
this.test('Constructor initializes performance features', () => {
|
|
123
|
+
let emitter = new EventsManager();
|
|
124
|
+
this.assert('object' === typeof emitter._listenersCache, 'Should have _listenersCache object');
|
|
125
|
+
this.assert(emitter._validationCache instanceof Map, 'Should have _validationCache Map');
|
|
126
|
+
this.assert(null === emitter._debugPatterns, 'Should have _debugPatterns initially null');
|
|
127
|
+
this.assert('string' === typeof emitter.symbolString, 'Should have symbolString property');
|
|
128
|
+
this.assert('--[[await-event-emitter]]--' === emitter.symbolString, 'Should have correct symbolString');
|
|
129
|
+
this.assert('undefined' !== typeof emitter.typeKeyName, 'Should have typeKeyName property');
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
testListenersCachePerformance()
|
|
134
|
+
{
|
|
135
|
+
this.test('Listeners cache improves performance', () => {
|
|
136
|
+
let emitter = new EventsManager();
|
|
137
|
+
let fn = () => {};
|
|
138
|
+
|
|
139
|
+
emitter.on('cache-test', fn);
|
|
140
|
+
|
|
141
|
+
let firstCall = emitter.listeners('cache-test');
|
|
142
|
+
let secondCall = emitter.listeners('cache-test');
|
|
143
|
+
|
|
144
|
+
this.assert(firstCall === secondCall, 'Should return same cached array reference');
|
|
145
|
+
this.assert(emitter._listenersCache['cache-test'], 'Should store cache entry');
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
testValidationCachePerformance()
|
|
150
|
+
{
|
|
151
|
+
this.test('Validation cache improves performance', () => {
|
|
152
|
+
let emitter = new EventsManager();
|
|
153
|
+
|
|
154
|
+
let result1 = emitter.validateEventKey('test-key');
|
|
155
|
+
let result2 = emitter.validateEventKey('test-key');
|
|
156
|
+
|
|
157
|
+
this.assert(result1 === result2, 'Should return same validation result');
|
|
158
|
+
this.assert(emitter._validationCache.has('test-key'), 'Should cache validation result');
|
|
159
|
+
this.assert(emitter._validationCache.get('test-key') === result1, 'Should return cached value');
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
testDebugPatternsOptimization()
|
|
164
|
+
{
|
|
165
|
+
this.test('Debug patterns optimization works', () => {
|
|
166
|
+
let emitter = new EventsManager();
|
|
167
|
+
emitter.debug = 'all,test,custom';
|
|
168
|
+
|
|
169
|
+
emitter.logDebugEvent('test', 'Listen');
|
|
170
|
+
|
|
171
|
+
this.assert(emitter._debugPatterns instanceof Set, 'Should create debug patterns Set');
|
|
172
|
+
this.assert(emitter._debugPatterns.has('all'), 'Should contain all pattern');
|
|
173
|
+
this.assert(emitter._debugPatterns.has('test'), 'Should contain test pattern');
|
|
174
|
+
this.assert(emitter._debugPatterns.has('custom'), 'Should contain custom pattern');
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
testCacheInvalidationOnRemoveListener()
|
|
179
|
+
{
|
|
180
|
+
this.test('Cache invalidation on removeListener', () => {
|
|
181
|
+
let emitter = new EventsManager();
|
|
182
|
+
let fn = () => {};
|
|
183
|
+
|
|
184
|
+
emitter.on('cache-invalidate-test', fn);
|
|
185
|
+
emitter.listeners('cache-invalidate-test');
|
|
186
|
+
this.assert(emitter._listenersCache['cache-invalidate-test'], 'Should have cache entry');
|
|
187
|
+
|
|
188
|
+
emitter.removeListener('cache-invalidate-test', fn);
|
|
189
|
+
this.assert(!emitter._listenersCache['cache-invalidate-test'], 'Should clear cache on remove');
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
testCacheInvalidationOnRemoveAllListeners()
|
|
194
|
+
{
|
|
195
|
+
this.test('Cache invalidation on removeAllListeners', () => {
|
|
196
|
+
let emitter = new EventsManager();
|
|
197
|
+
|
|
198
|
+
emitter.on('clear-cache-test1', () => {});
|
|
199
|
+
emitter.on('clear-cache-test2', () => {});
|
|
200
|
+
emitter.listeners('clear-cache-test1');
|
|
201
|
+
emitter.listeners('clear-cache-test2');
|
|
202
|
+
|
|
203
|
+
this.assert(emitter._listenersCache['clear-cache-test1'], 'Should have cache entry 1');
|
|
204
|
+
this.assert(emitter._listenersCache['clear-cache-test2'], 'Should have cache entry 2');
|
|
205
|
+
|
|
206
|
+
emitter.removeAllListeners();
|
|
207
|
+
this.assert(0 === Object.keys(emitter._listenersCache).length, 'Should clear all cache entries');
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
testCacheInvalidationOnOffWithKey()
|
|
212
|
+
{
|
|
213
|
+
this.test('Cache invalidation on offWithKey', () => {
|
|
214
|
+
let emitter = new EventsManager();
|
|
215
|
+
|
|
216
|
+
emitter.onWithKey('off-cache-test', () => {}, 'test-key');
|
|
217
|
+
emitter.listeners('off-cache-test');
|
|
218
|
+
this.assert(emitter._listenersCache['off-cache-test'], 'Should have cache entry');
|
|
219
|
+
|
|
220
|
+
emitter.offWithKey('test-key');
|
|
221
|
+
this.assert(!emitter._listenersCache['off-cache-test'], 'Should clear cache on offWithKey');
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
testCacheInvalidationOnOffByMasterKey()
|
|
226
|
+
{
|
|
227
|
+
this.test('Cache invalidation on offByMasterKey', () => {
|
|
228
|
+
let emitter = new EventsManager();
|
|
229
|
+
|
|
230
|
+
emitter.onWithKey('master-cache-test1', () => {}, 'sub1', 'master');
|
|
231
|
+
emitter.onWithKey('master-cache-test2', () => {}, 'sub2', 'master');
|
|
232
|
+
emitter.listeners('master-cache-test1');
|
|
233
|
+
emitter.listeners('master-cache-test2');
|
|
234
|
+
|
|
235
|
+
this.assert(emitter._listenersCache['master-cache-test1'], 'Should have cache entry 1');
|
|
236
|
+
this.assert(emitter._listenersCache['master-cache-test2'], 'Should have cache entry 2');
|
|
237
|
+
|
|
238
|
+
emitter.offByMasterKey('master');
|
|
239
|
+
this.assert(!emitter._listenersCache['master-cache-test1'], 'Should clear cache 1');
|
|
240
|
+
this.assert(!emitter._listenersCache['master-cache-test2'], 'Should clear cache 2');
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
testAssertTypeUsesShortcuts()
|
|
245
|
+
{
|
|
246
|
+
this.test('assertType uses shortcuts methods', () => {
|
|
247
|
+
let emitter = new EventsManager();
|
|
248
|
+
|
|
249
|
+
try{
|
|
250
|
+
emitter.assertType(123);
|
|
251
|
+
this.assert(false, 'Should throw error for number');
|
|
252
|
+
} catch(error){
|
|
253
|
+
this.assert(error instanceof TypeError, 'Should throw TypeError');
|
|
254
|
+
this.assert(error.message.includes('type is not type of string or symbol'), 'Should have correct error message');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try{
|
|
258
|
+
emitter.assertType('valid-string');
|
|
259
|
+
this.assert(true, 'Should accept string');
|
|
260
|
+
} catch(error){
|
|
261
|
+
this.assert(false, 'Should not throw for valid string');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try{
|
|
265
|
+
let sym = Symbol('test');
|
|
266
|
+
emitter.assertType(sym);
|
|
267
|
+
this.assert(true, 'Should accept symbol');
|
|
268
|
+
} catch(error){
|
|
269
|
+
this.assert(false, 'Should not throw for valid symbol');
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
testCheckMemoryLeaksMethod()
|
|
275
|
+
{
|
|
276
|
+
this.test('checkMemoryLeaks method works correctly', () => {
|
|
277
|
+
let emitter = new EventsManager();
|
|
278
|
+
let result = emitter.checkMemoryLeaks();
|
|
279
|
+
this.assert(true === result, 'Should return true for low listener count');
|
|
280
|
+
this.assert(false === emitter.hasLoggedMaxListeners, 'Should not have logged initially');
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
testMaxListenersDebugLogOnce()
|
|
285
|
+
{
|
|
286
|
+
this.test('maxListeners debug log only once', () => {
|
|
287
|
+
let emitter = new EventsManager();
|
|
288
|
+
emitter.maxListeners = 5;
|
|
289
|
+
let loggedMessages = [];
|
|
290
|
+
let originalDebug = console.log;
|
|
291
|
+
|
|
292
|
+
console.log = (...args) => loggedMessages.push(args.join(' '));
|
|
293
|
+
process.env.RELDENS_LOG_LEVEL = 8;
|
|
294
|
+
|
|
295
|
+
for(let i = 0; i < 10; i++){
|
|
296
|
+
emitter.on('test-max-'+i, () => {});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
console.log = originalDebug;
|
|
300
|
+
delete process.env.RELDENS_LOG_LEVEL;
|
|
301
|
+
|
|
302
|
+
let debugLogs = loggedMessages.filter(log => log.includes('High listener count detected'));
|
|
303
|
+
this.assert(1 === debugLogs.length, 'Should log high listener count only once');
|
|
304
|
+
this.assert(true === emitter.hasLoggedMaxListeners, 'Should set flag after logging');
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
testCheckMemoryLeaksAfterFlagSet()
|
|
309
|
+
{
|
|
310
|
+
this.test('checkMemoryLeaks returns early after flag set', () => {
|
|
311
|
+
let emitter = new EventsManager();
|
|
312
|
+
emitter.hasLoggedMaxListeners = true;
|
|
313
|
+
|
|
314
|
+
for(let i = 0; i < 20; i++){
|
|
315
|
+
emitter.on('test-flag-'+i, () => {});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let result = emitter.checkMemoryLeaks();
|
|
319
|
+
this.assert(true === result, 'Should return true immediately when flag is set');
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
testConstructorInitializesLogFlag()
|
|
324
|
+
{
|
|
325
|
+
this.test('Constructor initializes hasLoggedMaxListeners flag', () => {
|
|
326
|
+
let emitter = new EventsManager();
|
|
327
|
+
this.assert(false === emitter.hasLoggedMaxListeners, 'Should initialize flag as false');
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
testRemoveListenerUsesShortcuts()
|
|
332
|
+
{
|
|
333
|
+
this.test('removeListener uses shortcuts for function check', () => {
|
|
334
|
+
let emitter = new EventsManager();
|
|
335
|
+
let fn = () => {};
|
|
336
|
+
|
|
337
|
+
emitter.on('shortcuts-test', fn);
|
|
338
|
+
emitter.on('shortcuts-test', () => {});
|
|
339
|
+
|
|
340
|
+
let result = emitter.removeListener('shortcuts-test', fn);
|
|
341
|
+
this.assert(true === result, 'Should use sc.isFunction internally and work correctly');
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
testSanitizeEventArgsWithValidArray()
|
|
346
|
+
{
|
|
347
|
+
this.test('sanitizeEventArgs with valid array', () => {
|
|
348
|
+
let emitter = new EventsManager();
|
|
349
|
+
let args = ['test', 123, {safe: 'data'}];
|
|
350
|
+
let result = emitter.sanitizeEventArgs(args);
|
|
351
|
+
this.assert(Array.isArray(result), 'Should return array');
|
|
352
|
+
this.assert(3 === result.length, 'Should preserve array length');
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
testSanitizeEventArgsWithNonArray()
|
|
357
|
+
{
|
|
358
|
+
this.test('sanitizeEventArgs with non-array returns empty array', () => {
|
|
359
|
+
let emitter = new EventsManager();
|
|
360
|
+
let result1 = emitter.sanitizeEventArgs(null);
|
|
361
|
+
let result2 = emitter.sanitizeEventArgs('string');
|
|
362
|
+
let result3 = emitter.sanitizeEventArgs(123);
|
|
363
|
+
this.assert(Array.isArray(result1) && 0 === result1.length, 'Should return empty array for null');
|
|
364
|
+
this.assert(Array.isArray(result2) && 0 === result2.length, 'Should return empty array for string');
|
|
365
|
+
this.assert(Array.isArray(result3) && 0 === result3.length, 'Should return empty array for number');
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
testSanitizeEventArgsExceedsMaxArgs()
|
|
370
|
+
{
|
|
371
|
+
this.test('sanitizeEventArgs when exceeding maxEventArgs', () => {
|
|
372
|
+
let emitter = new EventsManager();
|
|
373
|
+
emitter.maxEventArgs = 3;
|
|
374
|
+
let args = [1, 2, 3, 4, 5];
|
|
375
|
+
let result = emitter.sanitizeEventArgs(args);
|
|
376
|
+
this.assert(Array.isArray(result) && 0 === result.length, 'Should return empty array when exceeding maxEventArgs');
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
testFilterSensitiveDataWithNonObject()
|
|
381
|
+
{
|
|
382
|
+
this.test('filterSensitiveData with non-object returns input', () => {
|
|
383
|
+
let emitter = new EventsManager();
|
|
384
|
+
let result1 = emitter.filterSensitiveData('string');
|
|
385
|
+
let result2 = emitter.filterSensitiveData(123);
|
|
386
|
+
let result3 = emitter.filterSensitiveData(null);
|
|
387
|
+
this.assert('string' === result1, 'Should return string unchanged');
|
|
388
|
+
this.assert(123 === result2, 'Should return number unchanged');
|
|
389
|
+
this.assert(null === result3, 'Should return null unchanged');
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
testFilterSensitiveDataWithSensitiveFields()
|
|
394
|
+
{
|
|
395
|
+
this.test('filterSensitiveData filters sensitive fields', () => {
|
|
396
|
+
let emitter = new EventsManager();
|
|
397
|
+
let obj = {
|
|
398
|
+
username: 'test',
|
|
399
|
+
password: 'secret123',
|
|
400
|
+
authToken: 'token123',
|
|
401
|
+
apiKey: 'key123',
|
|
402
|
+
safe: 'data'
|
|
403
|
+
};
|
|
404
|
+
let result = emitter.filterSensitiveData(obj);
|
|
405
|
+
this.assert('test' === result.username, 'Should keep safe fields');
|
|
406
|
+
this.assert('[FILTERED]' === result.password, 'Should filter password');
|
|
407
|
+
this.assert('[FILTERED]' === result.authToken, 'Should filter token');
|
|
408
|
+
this.assert('[FILTERED]' === result.apiKey, 'Should filter key');
|
|
409
|
+
this.assert('data' === result.safe, 'Should keep safe data');
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
testFilterSensitiveDataWithDangerousKeys()
|
|
414
|
+
{
|
|
415
|
+
this.test('filterSensitiveData removes dangerous keys', () => {
|
|
416
|
+
let emitter = new EventsManager();
|
|
417
|
+
let obj = {
|
|
418
|
+
safe: 'data',
|
|
419
|
+
__proto__: 'dangerous',
|
|
420
|
+
constructor: 'dangerous',
|
|
421
|
+
prototype: 'dangerous'
|
|
422
|
+
};
|
|
423
|
+
let result = emitter.filterSensitiveData(obj);
|
|
424
|
+
this.assert('data' === result.safe, 'Should keep safe data');
|
|
425
|
+
this.assert(!result.hasOwnProperty('__proto__'), 'Should remove __proto__');
|
|
426
|
+
this.assert(!result.hasOwnProperty('constructor'), 'Should remove constructor');
|
|
427
|
+
this.assert(!result.hasOwnProperty('prototype'), 'Should remove prototype');
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
testFilterSensitiveDataWithNestedObjects()
|
|
432
|
+
{
|
|
433
|
+
this.test('filterSensitiveData handles nested objects', () => {
|
|
434
|
+
let emitter = new EventsManager();
|
|
435
|
+
let obj = {
|
|
436
|
+
user: {
|
|
437
|
+
name: 'test',
|
|
438
|
+
password: 'secret'
|
|
439
|
+
},
|
|
440
|
+
config: {
|
|
441
|
+
apiKey: 'key123',
|
|
442
|
+
safe: 'value'
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
let result = emitter.filterSensitiveData(obj);
|
|
446
|
+
this.assert('test' === result.user.name, 'Should keep nested safe fields');
|
|
447
|
+
this.assert('[FILTERED]' === result.user.password, 'Should filter nested password');
|
|
448
|
+
this.assert('[FILTERED]' === result.config.apiKey, 'Should filter nested key');
|
|
449
|
+
this.assert('value' === result.config.safe, 'Should keep nested safe value');
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
testValidateEventKeyWithLongKey()
|
|
454
|
+
{
|
|
455
|
+
this.test('validateEventKey rejects long keys', () => {
|
|
456
|
+
let emitter = new EventsManager();
|
|
457
|
+
emitter.maxEventKeyLength = 10;
|
|
458
|
+
let longKey = 'a'.repeat(20);
|
|
459
|
+
let result = emitter.validateEventKey(longKey);
|
|
460
|
+
this.assert(false === result, 'Should reject keys exceeding maxEventKeyLength');
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
testValidateEventKeyCachesResults()
|
|
465
|
+
{
|
|
466
|
+
this.test('validateEventKey caches validation results', () => {
|
|
467
|
+
let emitter = new EventsManager();
|
|
468
|
+
let key = 'cache-test-key';
|
|
469
|
+
emitter.validateEventKey(key);
|
|
470
|
+
this.assert(emitter._validationCache.has(key), 'Should cache validation result');
|
|
471
|
+
let cachedResult = emitter._validationCache.get(key);
|
|
472
|
+
let secondResult = emitter.validateEventKey(key);
|
|
473
|
+
this.assert(cachedResult === secondResult, 'Should return cached result on second call');
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
testLogDebugEventWithNullDebugPatterns()
|
|
478
|
+
{
|
|
479
|
+
this.test('logDebugEvent handles null _debugPatterns', () => {
|
|
480
|
+
let emitter = new EventsManager();
|
|
481
|
+
emitter.debug = 'test,custom';
|
|
482
|
+
emitter._debugPatterns = null;
|
|
483
|
+
|
|
484
|
+
try{
|
|
485
|
+
emitter.logDebugEvent('test', 'Listen');
|
|
486
|
+
this.assert(emitter._debugPatterns instanceof Set, 'Should create debug patterns Set');
|
|
487
|
+
} catch(error){
|
|
488
|
+
this.assert(false, 'Should not throw error when _debugPatterns is null');
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
testLogDebugEventWithFilteredArgs()
|
|
494
|
+
{
|
|
495
|
+
this.test('logDebugEvent filters sensitive data in args', () => {
|
|
496
|
+
let emitter = new EventsManager();
|
|
497
|
+
emitter.debug = 'all';
|
|
498
|
+
let loggedMessages = [];
|
|
499
|
+
let originalLog = console.log;
|
|
500
|
+
|
|
501
|
+
console.log = (...args) => loggedMessages.push(args.join(' '));
|
|
502
|
+
process.env.RELDENS_LOG_LEVEL = 8;
|
|
503
|
+
|
|
504
|
+
let sensitiveArgs = [{password: 'secret', safe: 'data'}];
|
|
505
|
+
emitter.logDebugEvent('test', 'Fire', sensitiveArgs);
|
|
506
|
+
|
|
507
|
+
console.log = originalLog;
|
|
508
|
+
delete process.env.RELDENS_LOG_LEVEL;
|
|
509
|
+
|
|
510
|
+
let debugLog = loggedMessages.find(log => log.includes('Fire Event: test'));
|
|
511
|
+
this.assert(debugLog, 'Should log debug event');
|
|
512
|
+
this.assert(debugLog.includes('with 1 arguments'), 'Should mention filtered arguments');
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
testEmitWithSanitizedArgs()
|
|
517
|
+
{
|
|
518
|
+
this.test('emit uses sanitized arguments', async () => {
|
|
519
|
+
let emitter = new EventsManager();
|
|
520
|
+
let receivedArgs = null;
|
|
521
|
+
|
|
522
|
+
emitter.on('sanitize-test', (...args) => {
|
|
523
|
+
receivedArgs = args;
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
let sensitiveData = {password: 'secret', safe: 'data'};
|
|
527
|
+
await emitter.emit('sanitize-test', sensitiveData);
|
|
528
|
+
|
|
529
|
+
this.assert(receivedArgs && 1 === receivedArgs.length, 'Should receive arguments');
|
|
530
|
+
this.assert('[FILTERED]' === receivedArgs[0].password, 'Should filter sensitive data');
|
|
531
|
+
this.assert('data' === receivedArgs[0].safe, 'Should keep safe data');
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
testEmitSyncWithSanitizedArgs()
|
|
536
|
+
{
|
|
537
|
+
this.test('emitSync uses sanitized arguments', () => {
|
|
538
|
+
let emitter = new EventsManager();
|
|
539
|
+
let receivedArgs = null;
|
|
540
|
+
|
|
541
|
+
emitter.on('sanitize-sync-test', (...args) => {
|
|
542
|
+
receivedArgs = args;
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
let sensitiveData = {authToken: 'token123', safe: 'data'};
|
|
546
|
+
emitter.emitSync('sanitize-sync-test', sensitiveData);
|
|
547
|
+
|
|
548
|
+
this.assert(receivedArgs && 1 === receivedArgs.length, 'Should receive arguments');
|
|
549
|
+
this.assert('[FILTERED]' === receivedArgs[0].authToken, 'Should filter sensitive data');
|
|
550
|
+
this.assert('data' === receivedArgs[0].safe, 'Should keep safe data');
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
testCheckMemoryLeaksCalledOnAllListenerMethods()
|
|
555
|
+
{
|
|
556
|
+
this.test('checkMemoryLeaks called on all listener methods', () => {
|
|
557
|
+
let emitter = new EventsManager();
|
|
558
|
+
let checkCount = 0;
|
|
559
|
+
let originalCheck = emitter.checkMemoryLeaks;
|
|
560
|
+
emitter.checkMemoryLeaks = () => {
|
|
561
|
+
checkCount++;
|
|
562
|
+
return originalCheck.call(emitter);
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
emitter.on('check-test', () => {});
|
|
566
|
+
emitter.prepend('check-test', () => {});
|
|
567
|
+
emitter.once('check-test', () => {});
|
|
568
|
+
emitter.prependOnce('check-test', () => {});
|
|
569
|
+
|
|
570
|
+
this.assert(4 === checkCount, 'Should call checkMemoryLeaks on all listener methods');
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
testDebugPatternsEarlyReturn()
|
|
575
|
+
{
|
|
576
|
+
this.test('logDebugEvent early return when no pattern match', () => {
|
|
577
|
+
let emitter = new EventsManager();
|
|
578
|
+
emitter.debug = 'specific-pattern';
|
|
579
|
+
emitter._debugPatterns = new Set(['specific-pattern']);
|
|
580
|
+
|
|
581
|
+
let loggedMessages = [];
|
|
582
|
+
let originalLog = console.log;
|
|
583
|
+
console.log = (...args) => loggedMessages.push(args.join(' '));
|
|
584
|
+
process.env.RELDENS_LOG_LEVEL = 8;
|
|
585
|
+
|
|
586
|
+
emitter.logDebugEvent('non-matching-key', 'Listen');
|
|
587
|
+
|
|
588
|
+
console.log = originalLog;
|
|
589
|
+
delete process.env.RELDENS_LOG_LEVEL;
|
|
590
|
+
|
|
591
|
+
let debugLogs = loggedMessages.filter(log => log.includes('Listen Event:'));
|
|
592
|
+
this.assert(0 === debugLogs.length, 'Should not log when no pattern matches');
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
testBasicOnEmitFunctionality()
|
|
597
|
+
{
|
|
598
|
+
this.test('Basic on/emit functionality', async () => {
|
|
599
|
+
let emitter = new EventsManager();
|
|
600
|
+
let called = false;
|
|
601
|
+
let eventData = null;
|
|
602
|
+
|
|
603
|
+
emitter.on('test-event', (data) => {
|
|
604
|
+
called = true;
|
|
605
|
+
eventData = data;
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
await emitter.emit('test-event', 'test-data');
|
|
609
|
+
this.assert(called, 'Event listener should be called');
|
|
610
|
+
this.assert('test-data' === eventData, 'Event data should be passed correctly');
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
testMultipleListenersForSameEvent()
|
|
615
|
+
{
|
|
616
|
+
this.test('Multiple listeners for same event', async () => {
|
|
617
|
+
let emitter = new EventsManager();
|
|
618
|
+
let callCount = 0;
|
|
619
|
+
|
|
620
|
+
emitter.on('multi-test', () => callCount++);
|
|
621
|
+
emitter.on('multi-test', () => callCount++);
|
|
622
|
+
emitter.on('multi-test', () => callCount++);
|
|
623
|
+
|
|
624
|
+
await emitter.emit('multi-test');
|
|
625
|
+
this.assert(3 === callCount, 'All listeners should be called');
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
testOnceListenerRemovesAfterFirstCall()
|
|
630
|
+
{
|
|
631
|
+
this.test('Once listener removes after first call', async () => {
|
|
632
|
+
let emitter = new EventsManager();
|
|
633
|
+
let callCount = 0;
|
|
634
|
+
|
|
635
|
+
emitter.once('once-test', () => callCount++);
|
|
636
|
+
|
|
637
|
+
await emitter.emit('once-test');
|
|
638
|
+
await emitter.emit('once-test');
|
|
639
|
+
this.assert(1 === callCount, 'Once listener should only be called once');
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
testPrependListenerAddsToBeginning()
|
|
644
|
+
{
|
|
645
|
+
this.test('Prepend listener adds to beginning', async () => {
|
|
646
|
+
let emitter = new EventsManager();
|
|
647
|
+
let order = [];
|
|
648
|
+
|
|
649
|
+
emitter.on('order-test', () => order.push('second'));
|
|
650
|
+
emitter.prepend('order-test', () => order.push('first'));
|
|
651
|
+
|
|
652
|
+
await emitter.emit('order-test');
|
|
653
|
+
this.assert('first' === order[0], 'Prepended listener should be first');
|
|
654
|
+
this.assert('second' === order[1], 'Original listener should be second');
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
testPrependOnceListener()
|
|
659
|
+
{
|
|
660
|
+
this.test('PrependOnce listener', async () => {
|
|
661
|
+
let emitter = new EventsManager();
|
|
662
|
+
let order = [];
|
|
663
|
+
|
|
664
|
+
emitter.on('prepend-once-test', () => order.push('second'));
|
|
665
|
+
emitter.prependOnce('prepend-once-test', () => order.push('first'));
|
|
666
|
+
|
|
667
|
+
await emitter.emit('prepend-once-test');
|
|
668
|
+
await emitter.emit('prepend-once-test');
|
|
669
|
+
this.assert(3 === order.length, 'Should have 3 calls total');
|
|
670
|
+
this.assert('first' === order[0], 'Prepended once should be first');
|
|
671
|
+
this.assert('second' === order[1], 'Regular listener second');
|
|
672
|
+
this.assert('second' === order[2], 'Regular listener third');
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
testListenersMethodReturnsArrayOfFunctions()
|
|
677
|
+
{
|
|
678
|
+
this.test('Listeners method returns array of functions', () => {
|
|
679
|
+
let emitter = new EventsManager();
|
|
680
|
+
let fn1 = () => {};
|
|
681
|
+
let fn2 = () => {};
|
|
682
|
+
|
|
683
|
+
emitter.on('listeners-test', fn1);
|
|
684
|
+
emitter.on('listeners-test', fn2);
|
|
685
|
+
|
|
686
|
+
let listeners = emitter.listeners('listeners-test');
|
|
687
|
+
this.assert(Array.isArray(listeners), 'Should return array');
|
|
688
|
+
this.assert(2 === listeners.length, 'Should return 2 listeners');
|
|
689
|
+
this.assert(fn1 === listeners[0], 'Should return first function');
|
|
690
|
+
this.assert(fn2 === listeners[1], 'Should return second function');
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
testRemoveListenerRemovesSpecificFunction()
|
|
695
|
+
{
|
|
696
|
+
this.test('RemoveListener removes specific function', async () => {
|
|
697
|
+
let emitter = new EventsManager();
|
|
698
|
+
let called1 = false;
|
|
699
|
+
let called2 = false;
|
|
700
|
+
let fn1 = () => called1 = true;
|
|
701
|
+
let fn2 = () => called2 = true;
|
|
702
|
+
|
|
703
|
+
emitter.on('remove-test', fn1);
|
|
704
|
+
emitter.on('remove-test', fn2);
|
|
705
|
+
emitter.removeListener('remove-test', fn1);
|
|
706
|
+
|
|
707
|
+
await emitter.emit('remove-test');
|
|
708
|
+
this.assert(!called1, 'Removed listener should not be called');
|
|
709
|
+
this.assert(called2, 'Remaining listener should be called');
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
testRemoveListenerRemovesAllIfNoFunctionSpecified()
|
|
714
|
+
{
|
|
715
|
+
this.test('RemoveListener removes all if no function specified', async () => {
|
|
716
|
+
let emitter = new EventsManager();
|
|
717
|
+
let called = false;
|
|
718
|
+
|
|
719
|
+
emitter.on('remove-all-test', () => called = true);
|
|
720
|
+
emitter.removeListener('remove-all-test');
|
|
721
|
+
|
|
722
|
+
await emitter.emit('remove-all-test');
|
|
723
|
+
this.assert(!called, 'All listeners should be removed');
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
testRemoveAllListenersClearsAllEvents()
|
|
728
|
+
{
|
|
729
|
+
this.test('RemoveAllListeners clears all events', async () => {
|
|
730
|
+
let emitter = new EventsManager();
|
|
731
|
+
let called1 = false;
|
|
732
|
+
let called2 = false;
|
|
733
|
+
|
|
734
|
+
emitter.on('clear-test1', () => called1 = true);
|
|
735
|
+
emitter.on('clear-test2', () => called2 = true);
|
|
736
|
+
emitter.removeAllListeners();
|
|
737
|
+
|
|
738
|
+
await emitter.emit('clear-test1');
|
|
739
|
+
await emitter.emit('clear-test2');
|
|
740
|
+
this.assert(!called1, 'First event should not fire');
|
|
741
|
+
this.assert(!called2, 'Second event should not fire');
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
testOffAliasWorksLikeRemoveListener()
|
|
746
|
+
{
|
|
747
|
+
this.test('Off alias works like removeListener', async () => {
|
|
748
|
+
let emitter = new EventsManager();
|
|
749
|
+
let called = false;
|
|
750
|
+
let fn = () => called = true;
|
|
751
|
+
|
|
752
|
+
emitter.on('off-test', fn);
|
|
753
|
+
emitter.off('off-test', fn);
|
|
754
|
+
|
|
755
|
+
await emitter.emit('off-test');
|
|
756
|
+
this.assert(!called, 'Off should remove listener');
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
testAddListenerAliasWorksLikeOn()
|
|
761
|
+
{
|
|
762
|
+
this.test('AddListener alias works like on', async () => {
|
|
763
|
+
let emitter = new EventsManager();
|
|
764
|
+
let called = false;
|
|
765
|
+
|
|
766
|
+
emitter.addListener('add-test', () => called = true);
|
|
767
|
+
|
|
768
|
+
await emitter.emit('add-test');
|
|
769
|
+
this.assert(called, 'AddListener should work like on');
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
testPrependListenerAliasWorksLikePrepend()
|
|
774
|
+
{
|
|
775
|
+
this.test('PrependListener alias works like prepend', async () => {
|
|
776
|
+
let emitter = new EventsManager();
|
|
777
|
+
let order = [];
|
|
778
|
+
|
|
779
|
+
emitter.on('prepend-alias-test', () => order.push('second'));
|
|
780
|
+
emitter.prependListener('prepend-alias-test', () => order.push('first'));
|
|
781
|
+
|
|
782
|
+
await emitter.emit('prepend-alias-test');
|
|
783
|
+
this.assert('first' === order[0], 'PrependListener should work like prepend');
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
testPrependOnceListenerAliasWorksLikePrependOnce()
|
|
788
|
+
{
|
|
789
|
+
this.test('PrependOnceListener alias works like prependOnce', async () => {
|
|
790
|
+
let emitter = new EventsManager();
|
|
791
|
+
let callCount = 0;
|
|
792
|
+
|
|
793
|
+
emitter.prependOnceListener('prepend-once-alias', () => callCount++);
|
|
794
|
+
|
|
795
|
+
await emitter.emit('prepend-once-alias');
|
|
796
|
+
await emitter.emit('prepend-once-alias');
|
|
797
|
+
this.assert(1 === callCount, 'PrependOnceListener should work like prependOnce');
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
testOnWithKeyBasicFunctionality()
|
|
802
|
+
{
|
|
803
|
+
this.test('onWithKey basic functionality', async () => {
|
|
804
|
+
let emitter = new EventsManager();
|
|
805
|
+
let called = false;
|
|
806
|
+
|
|
807
|
+
emitter.onWithKey('key-test', () => called = true, 'test-key');
|
|
808
|
+
|
|
809
|
+
await emitter.emit('key-test');
|
|
810
|
+
this.assert(called, 'onWithKey should work like on');
|
|
811
|
+
this.assert(emitter.eventsByRemoveKeys['test-key'], 'Should store event by key');
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
testOffWithKeyRemovesByKey()
|
|
816
|
+
{
|
|
817
|
+
this.test('offWithKey removes by key', async () => {
|
|
818
|
+
let emitter = new EventsManager();
|
|
819
|
+
let called = false;
|
|
820
|
+
|
|
821
|
+
emitter.onWithKey('key-remove-test', () => called = true, 'remove-key');
|
|
822
|
+
emitter.offWithKey('remove-key');
|
|
823
|
+
|
|
824
|
+
await emitter.emit('key-remove-test');
|
|
825
|
+
this.assert(!called, 'offWithKey should remove listener');
|
|
826
|
+
this.assert(!emitter.eventsByRemoveKeys['remove-key'], 'Should remove key from storage');
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
testOnWithKeyWithMasterKey()
|
|
831
|
+
{
|
|
832
|
+
this.test('onWithKey with master key', async () => {
|
|
833
|
+
let emitter = new EventsManager();
|
|
834
|
+
let called = false;
|
|
835
|
+
|
|
836
|
+
emitter.onWithKey('master-test', () => called = true, 'sub-key', 'master-key');
|
|
837
|
+
|
|
838
|
+
await emitter.emit('master-test');
|
|
839
|
+
this.assert(called, 'onWithKey with master key should work');
|
|
840
|
+
this.assert(emitter.eventsByRemoveKeys['master-key'], 'Should store master key');
|
|
841
|
+
this.assert(emitter.eventsByRemoveKeys['master-key']['sub-key'], 'Should store sub key under master');
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
testOffWithKeyWithMasterKey()
|
|
846
|
+
{
|
|
847
|
+
this.test('offWithKey with master key', async () => {
|
|
848
|
+
let emitter = new EventsManager();
|
|
849
|
+
let called = false;
|
|
850
|
+
|
|
851
|
+
emitter.onWithKey('master-remove-test', () => called = true, 'sub-key', 'master-key');
|
|
852
|
+
emitter.offWithKey('sub-key', 'master-key');
|
|
853
|
+
|
|
854
|
+
await emitter.emit('master-remove-test');
|
|
855
|
+
this.assert(!called, 'offWithKey with master key should remove listener');
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
testOffByMasterKeyRemovesAllEventsUnderMasterKey()
|
|
860
|
+
{
|
|
861
|
+
this.test('offByMasterKey removes all events under master key', async () => {
|
|
862
|
+
let emitter = new EventsManager();
|
|
863
|
+
let called1 = false;
|
|
864
|
+
let called2 = false;
|
|
865
|
+
|
|
866
|
+
emitter.onWithKey('master-bulk1', () => called1 = true, 'sub1', 'bulk-master');
|
|
867
|
+
emitter.onWithKey('master-bulk2', () => called2 = true, 'sub2', 'bulk-master');
|
|
868
|
+
emitter.offByMasterKey('bulk-master');
|
|
869
|
+
|
|
870
|
+
await emitter.emit('master-bulk1');
|
|
871
|
+
await emitter.emit('master-bulk2');
|
|
872
|
+
this.assert(!called1, 'First event should be removed');
|
|
873
|
+
this.assert(!called2, 'Second event should be removed');
|
|
874
|
+
this.assert(!emitter.eventsByRemoveKeys['bulk-master'], 'Master key should be removed');
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
testEmitSyncWorksWithoutAwait()
|
|
879
|
+
{
|
|
880
|
+
this.test('EmitSync works without await', () => {
|
|
881
|
+
let emitter = new EventsManager();
|
|
882
|
+
let called = false;
|
|
883
|
+
|
|
884
|
+
emitter.on('sync-test', () => called = true);
|
|
885
|
+
emitter.emitSync('sync-test');
|
|
886
|
+
|
|
887
|
+
this.assert(called, 'EmitSync should call listeners immediately');
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
testDebugFunctionalityLogsEvents()
|
|
892
|
+
{
|
|
893
|
+
this.test('Debug functionality logs events', async () => {
|
|
894
|
+
let emitter = new EventsManager();
|
|
895
|
+
let loggedEvents = [];
|
|
896
|
+
let originalLog = console.log;
|
|
897
|
+
|
|
898
|
+
console.log = (...args) => loggedEvents.push(args.join(' '));
|
|
899
|
+
process.env.RELDENS_LOG_LEVEL = 8;
|
|
900
|
+
emitter.debug = 'all';
|
|
901
|
+
|
|
902
|
+
emitter.on('debug-test', () => {});
|
|
903
|
+
await emitter.emit('debug-test');
|
|
904
|
+
|
|
905
|
+
console.log = originalLog;
|
|
906
|
+
delete process.env.RELDENS_LOG_LEVEL;
|
|
907
|
+
|
|
908
|
+
let hasListenLog = loggedEvents.some(log => log.includes('Listen Event:'));
|
|
909
|
+
let hasFireLog = loggedEvents.some(log => log.includes('Fire Event:'));
|
|
910
|
+
this.assert(hasListenLog, 'Should log listen events');
|
|
911
|
+
this.assert(hasFireLog, 'Should log fire events');
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
testAsyncListenersWithPromises()
|
|
916
|
+
{
|
|
917
|
+
this.test('Async listeners with promises', async () => {
|
|
918
|
+
let emitter = new EventsManager();
|
|
919
|
+
let asyncResult = '';
|
|
920
|
+
|
|
921
|
+
emitter.on('async-test', async () => {
|
|
922
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
923
|
+
asyncResult = 'async-complete';
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
await emitter.emit('async-test');
|
|
927
|
+
this.assert('async-complete' === asyncResult, 'Should await async listeners');
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
testInvalidEventKeyThrowsError()
|
|
932
|
+
{
|
|
933
|
+
this.test('Invalid event key throws error', () => {
|
|
934
|
+
let emitter = new EventsManager();
|
|
935
|
+
let errorThrown = false;
|
|
936
|
+
|
|
937
|
+
try{
|
|
938
|
+
emitter.on(123, () => {});
|
|
939
|
+
} catch(error){
|
|
940
|
+
errorThrown = true;
|
|
941
|
+
this.assert(error instanceof TypeError, 'Should throw TypeError for invalid type');
|
|
942
|
+
}
|
|
943
|
+
this.assert(errorThrown, 'Should throw error for invalid event key');
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
testInvalidFunctionThrowsError()
|
|
948
|
+
{
|
|
949
|
+
this.test('Invalid function throws error', () => {
|
|
950
|
+
let emitter = new EventsManager();
|
|
951
|
+
let errorThrown = false;
|
|
952
|
+
|
|
953
|
+
try{
|
|
954
|
+
emitter.on('test', 'not-a-function');
|
|
955
|
+
} catch(error){
|
|
956
|
+
errorThrown = true;
|
|
957
|
+
this.assert(error instanceof TypeError, 'Should throw TypeError for invalid function');
|
|
958
|
+
}
|
|
959
|
+
this.assert(errorThrown, 'Should throw error for invalid function');
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
testDangerousKeysRejectedByOnWithKey()
|
|
964
|
+
{
|
|
965
|
+
this.test('Dangerous keys rejected by onWithKey', () => {
|
|
966
|
+
let emitter = new EventsManager();
|
|
967
|
+
let result = emitter.onWithKey('__proto__', () => {}, 'test-key');
|
|
968
|
+
this.assert(false === result, 'Should reject dangerous keys');
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
testMissingParametersHandledGracefully()
|
|
973
|
+
{
|
|
974
|
+
this.test('Missing parameters handled gracefully', () => {
|
|
975
|
+
let emitter = new EventsManager();
|
|
976
|
+
let errorThrown = false;
|
|
977
|
+
|
|
978
|
+
try{
|
|
979
|
+
emitter.on();
|
|
980
|
+
} catch(error){
|
|
981
|
+
errorThrown = true;
|
|
982
|
+
}
|
|
983
|
+
this.assert(errorThrown, 'Should throw error for missing parameters');
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
testOffWithKeyWithNonExistentKeyReturnsFalse()
|
|
988
|
+
{
|
|
989
|
+
this.test('offWithKey with non-existent key returns false', () => {
|
|
990
|
+
let emitter = new EventsManager();
|
|
991
|
+
let result = emitter.offWithKey('non-existent-key');
|
|
992
|
+
this.assert(false === result, 'Should return false for non-existent key');
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
testOffByMasterKeyWithNonExistentMasterKeyReturnsFalse()
|
|
997
|
+
{
|
|
998
|
+
this.test('offByMasterKey with non-existent master key returns false', () => {
|
|
999
|
+
let emitter = new EventsManager();
|
|
1000
|
+
let result = emitter.offByMasterKey('non-existent-master');
|
|
1001
|
+
this.assert(false === result, 'Should return false for non-existent master key');
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
testDuplicateKeyRegistrationReturnsFalse()
|
|
1006
|
+
{
|
|
1007
|
+
this.test('Duplicate key registration returns false', () => {
|
|
1008
|
+
let emitter = new EventsManager();
|
|
1009
|
+
emitter.onWithKey('dup-test', () => {}, 'dup-key');
|
|
1010
|
+
let result = emitter.onWithKey('dup-test2', () => {}, 'dup-key');
|
|
1011
|
+
this.assert(false === result, 'Should return false for duplicate key');
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
testEmptyEventNameHandled()
|
|
1016
|
+
{
|
|
1017
|
+
this.test('Empty event name handled', () => {
|
|
1018
|
+
let emitter = new EventsManager();
|
|
1019
|
+
let called = false;
|
|
1020
|
+
|
|
1021
|
+
emitter.on('', () => called = true);
|
|
1022
|
+
emitter.emit('');
|
|
1023
|
+
|
|
1024
|
+
this.assert(called, 'Should handle empty string event names');
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
testSymbolEventKeysWork()
|
|
1029
|
+
{
|
|
1030
|
+
this.test('Symbol event keys work', () => {
|
|
1031
|
+
let emitter = new EventsManager();
|
|
1032
|
+
let called = false;
|
|
1033
|
+
let sym = Symbol('test-symbol');
|
|
1034
|
+
|
|
1035
|
+
emitter.on(sym, () => called = true);
|
|
1036
|
+
emitter.emit(sym);
|
|
1037
|
+
|
|
1038
|
+
this.assert(called, 'Should handle symbol event keys');
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
testEmitWithNoListenersReturnsFalse()
|
|
1043
|
+
{
|
|
1044
|
+
this.test('Emit with no listeners returns false', async () => {
|
|
1045
|
+
let emitter = new EventsManager();
|
|
1046
|
+
let result = await emitter.emit('no-listeners');
|
|
1047
|
+
this.assert(false === result, 'Should return false when no listeners');
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
testEmitWithListenersReturnsTrue()
|
|
1052
|
+
{
|
|
1053
|
+
this.test('Emit with listeners returns true', async () => {
|
|
1054
|
+
let emitter = new EventsManager();
|
|
1055
|
+
emitter.on('has-listeners', () => {});
|
|
1056
|
+
let result = await emitter.emit('has-listeners');
|
|
1057
|
+
this.assert(true === result, 'Should return true when has listeners');
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
printSummary()
|
|
1062
|
+
{
|
|
1063
|
+
console.log('\n'+'='.repeat(50));
|
|
1064
|
+
console.log('TEST SUMMARY');
|
|
1065
|
+
console.log('='.repeat(50));
|
|
1066
|
+
console.log('Total tests:', this.testCount);
|
|
1067
|
+
console.log('Passed:', this.passedCount);
|
|
1068
|
+
console.log('Failed:', this.testCount - this.passedCount);
|
|
1069
|
+
console.log('Success rate:', Math.round((this.passedCount / this.testCount) * 100)+'%');
|
|
1070
|
+
|
|
1071
|
+
if(this.testCount - this.passedCount > 0){
|
|
1072
|
+
console.log('\nFailed tests:');
|
|
1073
|
+
for(let result of this.testResults){
|
|
1074
|
+
if('FAIL' === result.status){
|
|
1075
|
+
console.log('-', result.name, ':', result.error);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
let testRunner = new TestEventsManager();
|
|
1084
|
+
testRunner.runAllTests();
|