UNPKG

@reldens/utils

Version:
1,085 lines (905 loc) 42.3 kB
const EventsManager = require('../lib/events-manager'); class TestEventsManager { constructor() { this.testResults = []; this.testCount = 0; this.passedCount = 0; } test(name, testFn) { this.testCount++; try{ testFn(); console.log('✓ PASS:', name); this.passedCount++; this.testResults.push({name, status: 'PASS'}); } catch(error){ console.log('✗ FAIL:', name, '-', error.message); this.testResults.push({name, status: 'FAIL', error: error.message}); } } assert(condition, message) { if(!condition){ throw new Error(message || 'Assertion failed'); } } runAllTests() { console.log('Running tests for EventsManager...\n'); let methodNames = Object.getOwnPropertyNames(Object.getPrototypeOf(this)); let testMethods = methodNames.filter(name => name.startsWith('test') && 'function' === typeof this[name] && name !== 'test' ); for(let methodName of testMethods){ this[methodName](); } this.printSummary(); } testConstructorCreatesInstance() { this.test('Constructor creates instance', () => { let emitter = new EventsManager(); this.assert(emitter instanceof EventsManager, 'Should create instance'); this.assert('object' === typeof emitter._events, 'Should have _events object'); this.assert('object' === typeof emitter.eventsByRemoveKeys, 'Should have eventsByRemoveKeys object'); this.assert(false === emitter.debug, 'Should have debug false by default'); }); } testStaticTypeKeyName() { this.test('Constructor initializes typeKeyName property', () => { let emitter = new EventsManager(); this.assert('undefined' !== typeof emitter.typeKeyName, 'Should have typeKeyName property'); this.assert('string' === typeof emitter.typeKeyName || 'symbol' === typeof emitter.typeKeyName, 'typeKeyName should be string or symbol'); this.assert('undefined' !== typeof emitter.symbolString, 'Should have symbolString property'); this.assert('--[[await-event-emitter]]--' === emitter.symbolString, 'Should have correct symbolString value'); }); } testConstructorUsesShortcutsForSymbolCheck() { this.test('Constructor uses shortcuts for Symbol function check', () => { let emitter = new EventsManager(); if('function' === typeof Symbol){ this.assert('symbol' === typeof emitter.typeKeyName, 'Should create symbol when Symbol is available'); } if('function' !== typeof Symbol){ this.assert('string' === typeof emitter.typeKeyName, 'Should use string when Symbol not available'); } this.assert(emitter.symbolString === emitter.typeKeyName || Symbol.for(emitter.symbolString) === emitter.typeKeyName, 'typeKeyName should match expected value'); }); } testTypeKeyNameUsedInListenerObjects() { this.test('typeKeyName property used correctly in listener objects', () => { let emitter = new EventsManager(); let fn = () => {}; emitter.on('type-key-test', fn); let eventArray = emitter._events['type-key-test']; this.assert(eventArray && eventArray.length > 0, 'Should have event listeners'); let listenerObj = eventArray[0]; this.assert(listenerObj.hasOwnProperty(emitter.typeKeyName), 'Listener should use typeKeyName property'); this.assert('always' === listenerObj[emitter.typeKeyName], 'Should have correct listener type'); this.assert(fn === listenerObj.fn, 'Should store correct function'); }); } testOnceListenerUsesTypeKeyName() { this.test('once listener uses typeKeyName correctly', () => { let emitter = new EventsManager(); let fn = () => {}; emitter.once('once-type-key-test', fn); let eventArray = emitter._events['once-type-key-test']; let listenerObj = eventArray[0]; this.assert('once' === listenerObj[emitter.typeKeyName], 'Once listener should have correct type'); }); } testConstructorInitializesPerformanceFeatures() { this.test('Constructor initializes performance features', () => { let emitter = new EventsManager(); this.assert('object' === typeof emitter._listenersCache, 'Should have _listenersCache object'); this.assert(emitter._validationCache instanceof Map, 'Should have _validationCache Map'); this.assert(null === emitter._debugPatterns, 'Should have _debugPatterns initially null'); this.assert('string' === typeof emitter.symbolString, 'Should have symbolString property'); this.assert('--[[await-event-emitter]]--' === emitter.symbolString, 'Should have correct symbolString'); this.assert('undefined' !== typeof emitter.typeKeyName, 'Should have typeKeyName property'); }); } testListenersCachePerformance() { this.test('Listeners cache improves performance', () => { let emitter = new EventsManager(); let fn = () => {}; emitter.on('cache-test', fn); let firstCall = emitter.listeners('cache-test'); let secondCall = emitter.listeners('cache-test'); this.assert(firstCall === secondCall, 'Should return same cached array reference'); this.assert(emitter._listenersCache['cache-test'], 'Should store cache entry'); }); } testValidationCachePerformance() { this.test('Validation cache improves performance', () => { let emitter = new EventsManager(); let result1 = emitter.validateEventKey('test-key'); let result2 = emitter.validateEventKey('test-key'); this.assert(result1 === result2, 'Should return same validation result'); this.assert(emitter._validationCache.has('test-key'), 'Should cache validation result'); this.assert(emitter._validationCache.get('test-key') === result1, 'Should return cached value'); }); } testDebugPatternsOptimization() { this.test('Debug patterns optimization works', () => { let emitter = new EventsManager(); emitter.debug = 'all,test,custom'; emitter.logDebugEvent('test', 'Listen'); this.assert(emitter._debugPatterns instanceof Set, 'Should create debug patterns Set'); this.assert(emitter._debugPatterns.has('all'), 'Should contain all pattern'); this.assert(emitter._debugPatterns.has('test'), 'Should contain test pattern'); this.assert(emitter._debugPatterns.has('custom'), 'Should contain custom pattern'); }); } testCacheInvalidationOnRemoveListener() { this.test('Cache invalidation on removeListener', () => { let emitter = new EventsManager(); let fn = () => {}; emitter.on('cache-invalidate-test', fn); emitter.listeners('cache-invalidate-test'); this.assert(emitter._listenersCache['cache-invalidate-test'], 'Should have cache entry'); emitter.removeListener('cache-invalidate-test', fn); this.assert(!emitter._listenersCache['cache-invalidate-test'], 'Should clear cache on remove'); }); } testCacheInvalidationOnRemoveAllListeners() { this.test('Cache invalidation on removeAllListeners', () => { let emitter = new EventsManager(); emitter.on('clear-cache-test1', () => {}); emitter.on('clear-cache-test2', () => {}); emitter.listeners('clear-cache-test1'); emitter.listeners('clear-cache-test2'); this.assert(emitter._listenersCache['clear-cache-test1'], 'Should have cache entry 1'); this.assert(emitter._listenersCache['clear-cache-test2'], 'Should have cache entry 2'); emitter.removeAllListeners(); this.assert(0 === Object.keys(emitter._listenersCache).length, 'Should clear all cache entries'); }); } testCacheInvalidationOnOffWithKey() { this.test('Cache invalidation on offWithKey', () => { let emitter = new EventsManager(); emitter.onWithKey('off-cache-test', () => {}, 'test-key'); emitter.listeners('off-cache-test'); this.assert(emitter._listenersCache['off-cache-test'], 'Should have cache entry'); emitter.offWithKey('test-key'); this.assert(!emitter._listenersCache['off-cache-test'], 'Should clear cache on offWithKey'); }); } testCacheInvalidationOnOffByMasterKey() { this.test('Cache invalidation on offByMasterKey', () => { let emitter = new EventsManager(); emitter.onWithKey('master-cache-test1', () => {}, 'sub1', 'master'); emitter.onWithKey('master-cache-test2', () => {}, 'sub2', 'master'); emitter.listeners('master-cache-test1'); emitter.listeners('master-cache-test2'); this.assert(emitter._listenersCache['master-cache-test1'], 'Should have cache entry 1'); this.assert(emitter._listenersCache['master-cache-test2'], 'Should have cache entry 2'); emitter.offByMasterKey('master'); this.assert(!emitter._listenersCache['master-cache-test1'], 'Should clear cache 1'); this.assert(!emitter._listenersCache['master-cache-test2'], 'Should clear cache 2'); }); } testAssertTypeUsesShortcuts() { this.test('assertType uses shortcuts methods', () => { let emitter = new EventsManager(); try{ emitter.assertType(123); this.assert(false, 'Should throw error for number'); } catch(error){ this.assert(error instanceof TypeError, 'Should throw TypeError'); this.assert(error.message.includes('type is not type of string or symbol'), 'Should have correct error message'); } try{ emitter.assertType('valid-string'); this.assert(true, 'Should accept string'); } catch(error){ this.assert(false, 'Should not throw for valid string'); } try{ let sym = Symbol('test'); emitter.assertType(sym); this.assert(true, 'Should accept symbol'); } catch(error){ this.assert(false, 'Should not throw for valid symbol'); } }); } testCheckMemoryLeaksMethod() { this.test('checkMemoryLeaks method works correctly', () => { let emitter = new EventsManager(); let result = emitter.checkMemoryLeaks(); this.assert(true === result, 'Should return true for low listener count'); this.assert(false === emitter.hasLoggedMaxListeners, 'Should not have logged initially'); }); } testMaxListenersDebugLogOnce() { this.test('maxListeners debug log only once', () => { let emitter = new EventsManager(); emitter.maxListeners = 5; let loggedMessages = []; let originalDebug = console.log; console.log = (...args) => loggedMessages.push(args.join(' ')); process.env.RELDENS_LOG_LEVEL = 8; for(let i = 0; i < 10; i++){ emitter.on('test-max-'+i, () => {}); } console.log = originalDebug; delete process.env.RELDENS_LOG_LEVEL; let debugLogs = loggedMessages.filter(log => log.includes('High listener count detected')); this.assert(1 === debugLogs.length, 'Should log high listener count only once'); this.assert(true === emitter.hasLoggedMaxListeners, 'Should set flag after logging'); }); } testCheckMemoryLeaksAfterFlagSet() { this.test('checkMemoryLeaks returns early after flag set', () => { let emitter = new EventsManager(); emitter.hasLoggedMaxListeners = true; for(let i = 0; i < 20; i++){ emitter.on('test-flag-'+i, () => {}); } let result = emitter.checkMemoryLeaks(); this.assert(true === result, 'Should return true immediately when flag is set'); }); } testConstructorInitializesLogFlag() { this.test('Constructor initializes hasLoggedMaxListeners flag', () => { let emitter = new EventsManager(); this.assert(false === emitter.hasLoggedMaxListeners, 'Should initialize flag as false'); }); } testRemoveListenerUsesShortcuts() { this.test('removeListener uses shortcuts for function check', () => { let emitter = new EventsManager(); let fn = () => {}; emitter.on('shortcuts-test', fn); emitter.on('shortcuts-test', () => {}); let result = emitter.removeListener('shortcuts-test', fn); this.assert(true === result, 'Should use sc.isFunction internally and work correctly'); }); } testSanitizeEventArgsWithValidArray() { this.test('sanitizeEventArgs with valid array', () => { let emitter = new EventsManager(); let args = ['test', 123, {safe: 'data'}]; let result = emitter.sanitizeEventArgs(args); this.assert(Array.isArray(result), 'Should return array'); this.assert(3 === result.length, 'Should preserve array length'); }); } testSanitizeEventArgsWithNonArray() { this.test('sanitizeEventArgs with non-array returns empty array', () => { let emitter = new EventsManager(); let result1 = emitter.sanitizeEventArgs(null); let result2 = emitter.sanitizeEventArgs('string'); let result3 = emitter.sanitizeEventArgs(123); this.assert(Array.isArray(result1) && 0 === result1.length, 'Should return empty array for null'); this.assert(Array.isArray(result2) && 0 === result2.length, 'Should return empty array for string'); this.assert(Array.isArray(result3) && 0 === result3.length, 'Should return empty array for number'); }); } testSanitizeEventArgsExceedsMaxArgs() { this.test('sanitizeEventArgs when exceeding maxEventArgs', () => { let emitter = new EventsManager(); emitter.maxEventArgs = 3; let args = [1, 2, 3, 4, 5]; let result = emitter.sanitizeEventArgs(args); this.assert(Array.isArray(result) && 0 === result.length, 'Should return empty array when exceeding maxEventArgs'); }); } testFilterSensitiveDataWithNonObject() { this.test('filterSensitiveData with non-object returns input', () => { let emitter = new EventsManager(); let result1 = emitter.filterSensitiveData('string'); let result2 = emitter.filterSensitiveData(123); let result3 = emitter.filterSensitiveData(null); this.assert('string' === result1, 'Should return string unchanged'); this.assert(123 === result2, 'Should return number unchanged'); this.assert(null === result3, 'Should return null unchanged'); }); } testFilterSensitiveDataWithSensitiveFields() { this.test('filterSensitiveData filters sensitive fields', () => { let emitter = new EventsManager(); let obj = { username: 'test', password: 'secret123', authToken: 'token123', apiKey: 'key123', safe: 'data' }; let result = emitter.filterSensitiveData(obj); this.assert('test' === result.username, 'Should keep safe fields'); this.assert('[FILTERED]' === result.password, 'Should filter password'); this.assert('[FILTERED]' === result.authToken, 'Should filter token'); this.assert('[FILTERED]' === result.apiKey, 'Should filter key'); this.assert('data' === result.safe, 'Should keep safe data'); }); } testFilterSensitiveDataWithDangerousKeys() { this.test('filterSensitiveData removes dangerous keys', () => { let emitter = new EventsManager(); let obj = { safe: 'data', __proto__: 'dangerous', constructor: 'dangerous', prototype: 'dangerous' }; let result = emitter.filterSensitiveData(obj); this.assert('data' === result.safe, 'Should keep safe data'); this.assert(!result.hasOwnProperty('__proto__'), 'Should remove __proto__'); this.assert(!result.hasOwnProperty('constructor'), 'Should remove constructor'); this.assert(!result.hasOwnProperty('prototype'), 'Should remove prototype'); }); } testFilterSensitiveDataWithNestedObjects() { this.test('filterSensitiveData handles nested objects', () => { let emitter = new EventsManager(); let obj = { user: { name: 'test', password: 'secret' }, config: { apiKey: 'key123', safe: 'value' } }; let result = emitter.filterSensitiveData(obj); this.assert('test' === result.user.name, 'Should keep nested safe fields'); this.assert('[FILTERED]' === result.user.password, 'Should filter nested password'); this.assert('[FILTERED]' === result.config.apiKey, 'Should filter nested key'); this.assert('value' === result.config.safe, 'Should keep nested safe value'); }); } testValidateEventKeyWithLongKey() { this.test('validateEventKey rejects long keys', () => { let emitter = new EventsManager(); emitter.maxEventKeyLength = 10; let longKey = 'a'.repeat(20); let result = emitter.validateEventKey(longKey); this.assert(false === result, 'Should reject keys exceeding maxEventKeyLength'); }); } testValidateEventKeyCachesResults() { this.test('validateEventKey caches validation results', () => { let emitter = new EventsManager(); let key = 'cache-test-key'; emitter.validateEventKey(key); this.assert(emitter._validationCache.has(key), 'Should cache validation result'); let cachedResult = emitter._validationCache.get(key); let secondResult = emitter.validateEventKey(key); this.assert(cachedResult === secondResult, 'Should return cached result on second call'); }); } testLogDebugEventWithNullDebugPatterns() { this.test('logDebugEvent handles null _debugPatterns', () => { let emitter = new EventsManager(); emitter.debug = 'test,custom'; emitter._debugPatterns = null; try{ emitter.logDebugEvent('test', 'Listen'); this.assert(emitter._debugPatterns instanceof Set, 'Should create debug patterns Set'); } catch(error){ this.assert(false, 'Should not throw error when _debugPatterns is null'); } }); } testLogDebugEventWithFilteredArgs() { this.test('logDebugEvent filters sensitive data in args', () => { let emitter = new EventsManager(); emitter.debug = 'all'; let loggedMessages = []; let originalLog = console.log; console.log = (...args) => loggedMessages.push(args.join(' ')); process.env.RELDENS_LOG_LEVEL = 8; let sensitiveArgs = [{password: 'secret', safe: 'data'}]; emitter.logDebugEvent('test', 'Fire', sensitiveArgs); console.log = originalLog; delete process.env.RELDENS_LOG_LEVEL; let debugLog = loggedMessages.find(log => log.includes('Fire Event: test')); this.assert(debugLog, 'Should log debug event'); this.assert(debugLog.includes('with 1 arguments'), 'Should mention filtered arguments'); }); } testEmitWithSanitizedArgs() { this.test('emit uses sanitized arguments', async () => { let emitter = new EventsManager(); let receivedArgs = null; emitter.on('sanitize-test', (...args) => { receivedArgs = args; }); let sensitiveData = {password: 'secret', safe: 'data'}; await emitter.emit('sanitize-test', sensitiveData); this.assert(receivedArgs && 1 === receivedArgs.length, 'Should receive arguments'); this.assert('[FILTERED]' === receivedArgs[0].password, 'Should filter sensitive data'); this.assert('data' === receivedArgs[0].safe, 'Should keep safe data'); }); } testEmitSyncWithSanitizedArgs() { this.test('emitSync uses sanitized arguments', () => { let emitter = new EventsManager(); let receivedArgs = null; emitter.on('sanitize-sync-test', (...args) => { receivedArgs = args; }); let sensitiveData = {authToken: 'token123', safe: 'data'}; emitter.emitSync('sanitize-sync-test', sensitiveData); this.assert(receivedArgs && 1 === receivedArgs.length, 'Should receive arguments'); this.assert('[FILTERED]' === receivedArgs[0].authToken, 'Should filter sensitive data'); this.assert('data' === receivedArgs[0].safe, 'Should keep safe data'); }); } testCheckMemoryLeaksCalledOnAllListenerMethods() { this.test('checkMemoryLeaks called on all listener methods', () => { let emitter = new EventsManager(); let checkCount = 0; let originalCheck = emitter.checkMemoryLeaks; emitter.checkMemoryLeaks = () => { checkCount++; return originalCheck.call(emitter); }; emitter.on('check-test', () => {}); emitter.prepend('check-test', () => {}); emitter.once('check-test', () => {}); emitter.prependOnce('check-test', () => {}); this.assert(4 === checkCount, 'Should call checkMemoryLeaks on all listener methods'); }); } testDebugPatternsEarlyReturn() { this.test('logDebugEvent early return when no pattern match', () => { let emitter = new EventsManager(); emitter.debug = 'specific-pattern'; emitter._debugPatterns = new Set(['specific-pattern']); let loggedMessages = []; let originalLog = console.log; console.log = (...args) => loggedMessages.push(args.join(' ')); process.env.RELDENS_LOG_LEVEL = 8; emitter.logDebugEvent('non-matching-key', 'Listen'); console.log = originalLog; delete process.env.RELDENS_LOG_LEVEL; let debugLogs = loggedMessages.filter(log => log.includes('Listen Event:')); this.assert(0 === debugLogs.length, 'Should not log when no pattern matches'); }); } testBasicOnEmitFunctionality() { this.test('Basic on/emit functionality', async () => { let emitter = new EventsManager(); let called = false; let eventData = null; emitter.on('test-event', (data) => { called = true; eventData = data; }); await emitter.emit('test-event', 'test-data'); this.assert(called, 'Event listener should be called'); this.assert('test-data' === eventData, 'Event data should be passed correctly'); }); } testMultipleListenersForSameEvent() { this.test('Multiple listeners for same event', async () => { let emitter = new EventsManager(); let callCount = 0; emitter.on('multi-test', () => callCount++); emitter.on('multi-test', () => callCount++); emitter.on('multi-test', () => callCount++); await emitter.emit('multi-test'); this.assert(3 === callCount, 'All listeners should be called'); }); } testOnceListenerRemovesAfterFirstCall() { this.test('Once listener removes after first call', async () => { let emitter = new EventsManager(); let callCount = 0; emitter.once('once-test', () => callCount++); await emitter.emit('once-test'); await emitter.emit('once-test'); this.assert(1 === callCount, 'Once listener should only be called once'); }); } testPrependListenerAddsToBeginning() { this.test('Prepend listener adds to beginning', async () => { let emitter = new EventsManager(); let order = []; emitter.on('order-test', () => order.push('second')); emitter.prepend('order-test', () => order.push('first')); await emitter.emit('order-test'); this.assert('first' === order[0], 'Prepended listener should be first'); this.assert('second' === order[1], 'Original listener should be second'); }); } testPrependOnceListener() { this.test('PrependOnce listener', async () => { let emitter = new EventsManager(); let order = []; emitter.on('prepend-once-test', () => order.push('second')); emitter.prependOnce('prepend-once-test', () => order.push('first')); await emitter.emit('prepend-once-test'); await emitter.emit('prepend-once-test'); this.assert(3 === order.length, 'Should have 3 calls total'); this.assert('first' === order[0], 'Prepended once should be first'); this.assert('second' === order[1], 'Regular listener second'); this.assert('second' === order[2], 'Regular listener third'); }); } testListenersMethodReturnsArrayOfFunctions() { this.test('Listeners method returns array of functions', () => { let emitter = new EventsManager(); let fn1 = () => {}; let fn2 = () => {}; emitter.on('listeners-test', fn1); emitter.on('listeners-test', fn2); let listeners = emitter.listeners('listeners-test'); this.assert(Array.isArray(listeners), 'Should return array'); this.assert(2 === listeners.length, 'Should return 2 listeners'); this.assert(fn1 === listeners[0], 'Should return first function'); this.assert(fn2 === listeners[1], 'Should return second function'); }); } testRemoveListenerRemovesSpecificFunction() { this.test('RemoveListener removes specific function', async () => { let emitter = new EventsManager(); let called1 = false; let called2 = false; let fn1 = () => called1 = true; let fn2 = () => called2 = true; emitter.on('remove-test', fn1); emitter.on('remove-test', fn2); emitter.removeListener('remove-test', fn1); await emitter.emit('remove-test'); this.assert(!called1, 'Removed listener should not be called'); this.assert(called2, 'Remaining listener should be called'); }); } testRemoveListenerRemovesAllIfNoFunctionSpecified() { this.test('RemoveListener removes all if no function specified', async () => { let emitter = new EventsManager(); let called = false; emitter.on('remove-all-test', () => called = true); emitter.removeListener('remove-all-test'); await emitter.emit('remove-all-test'); this.assert(!called, 'All listeners should be removed'); }); } testRemoveAllListenersClearsAllEvents() { this.test('RemoveAllListeners clears all events', async () => { let emitter = new EventsManager(); let called1 = false; let called2 = false; emitter.on('clear-test1', () => called1 = true); emitter.on('clear-test2', () => called2 = true); emitter.removeAllListeners(); await emitter.emit('clear-test1'); await emitter.emit('clear-test2'); this.assert(!called1, 'First event should not fire'); this.assert(!called2, 'Second event should not fire'); }); } testOffAliasWorksLikeRemoveListener() { this.test('Off alias works like removeListener', async () => { let emitter = new EventsManager(); let called = false; let fn = () => called = true; emitter.on('off-test', fn); emitter.off('off-test', fn); await emitter.emit('off-test'); this.assert(!called, 'Off should remove listener'); }); } testAddListenerAliasWorksLikeOn() { this.test('AddListener alias works like on', async () => { let emitter = new EventsManager(); let called = false; emitter.addListener('add-test', () => called = true); await emitter.emit('add-test'); this.assert(called, 'AddListener should work like on'); }); } testPrependListenerAliasWorksLikePrepend() { this.test('PrependListener alias works like prepend', async () => { let emitter = new EventsManager(); let order = []; emitter.on('prepend-alias-test', () => order.push('second')); emitter.prependListener('prepend-alias-test', () => order.push('first')); await emitter.emit('prepend-alias-test'); this.assert('first' === order[0], 'PrependListener should work like prepend'); }); } testPrependOnceListenerAliasWorksLikePrependOnce() { this.test('PrependOnceListener alias works like prependOnce', async () => { let emitter = new EventsManager(); let callCount = 0; emitter.prependOnceListener('prepend-once-alias', () => callCount++); await emitter.emit('prepend-once-alias'); await emitter.emit('prepend-once-alias'); this.assert(1 === callCount, 'PrependOnceListener should work like prependOnce'); }); } testOnWithKeyBasicFunctionality() { this.test('onWithKey basic functionality', async () => { let emitter = new EventsManager(); let called = false; emitter.onWithKey('key-test', () => called = true, 'test-key'); await emitter.emit('key-test'); this.assert(called, 'onWithKey should work like on'); this.assert(emitter.eventsByRemoveKeys['test-key'], 'Should store event by key'); }); } testOffWithKeyRemovesByKey() { this.test('offWithKey removes by key', async () => { let emitter = new EventsManager(); let called = false; emitter.onWithKey('key-remove-test', () => called = true, 'remove-key'); emitter.offWithKey('remove-key'); await emitter.emit('key-remove-test'); this.assert(!called, 'offWithKey should remove listener'); this.assert(!emitter.eventsByRemoveKeys['remove-key'], 'Should remove key from storage'); }); } testOnWithKeyWithMasterKey() { this.test('onWithKey with master key', async () => { let emitter = new EventsManager(); let called = false; emitter.onWithKey('master-test', () => called = true, 'sub-key', 'master-key'); await emitter.emit('master-test'); this.assert(called, 'onWithKey with master key should work'); this.assert(emitter.eventsByRemoveKeys['master-key'], 'Should store master key'); this.assert(emitter.eventsByRemoveKeys['master-key']['sub-key'], 'Should store sub key under master'); }); } testOffWithKeyWithMasterKey() { this.test('offWithKey with master key', async () => { let emitter = new EventsManager(); let called = false; emitter.onWithKey('master-remove-test', () => called = true, 'sub-key', 'master-key'); emitter.offWithKey('sub-key', 'master-key'); await emitter.emit('master-remove-test'); this.assert(!called, 'offWithKey with master key should remove listener'); }); } testOffByMasterKeyRemovesAllEventsUnderMasterKey() { this.test('offByMasterKey removes all events under master key', async () => { let emitter = new EventsManager(); let called1 = false; let called2 = false; emitter.onWithKey('master-bulk1', () => called1 = true, 'sub1', 'bulk-master'); emitter.onWithKey('master-bulk2', () => called2 = true, 'sub2', 'bulk-master'); emitter.offByMasterKey('bulk-master'); await emitter.emit('master-bulk1'); await emitter.emit('master-bulk2'); this.assert(!called1, 'First event should be removed'); this.assert(!called2, 'Second event should be removed'); this.assert(!emitter.eventsByRemoveKeys['bulk-master'], 'Master key should be removed'); }); } testEmitSyncWorksWithoutAwait() { this.test('EmitSync works without await', () => { let emitter = new EventsManager(); let called = false; emitter.on('sync-test', () => called = true); emitter.emitSync('sync-test'); this.assert(called, 'EmitSync should call listeners immediately'); }); } testDebugFunctionalityLogsEvents() { this.test('Debug functionality logs events', async () => { let emitter = new EventsManager(); let loggedEvents = []; let originalLog = console.log; console.log = (...args) => loggedEvents.push(args.join(' ')); process.env.RELDENS_LOG_LEVEL = 8; emitter.debug = 'all'; emitter.on('debug-test', () => {}); await emitter.emit('debug-test'); console.log = originalLog; delete process.env.RELDENS_LOG_LEVEL; let hasListenLog = loggedEvents.some(log => log.includes('Listen Event:')); let hasFireLog = loggedEvents.some(log => log.includes('Fire Event:')); this.assert(hasListenLog, 'Should log listen events'); this.assert(hasFireLog, 'Should log fire events'); }); } testAsyncListenersWithPromises() { this.test('Async listeners with promises', async () => { let emitter = new EventsManager(); let asyncResult = ''; emitter.on('async-test', async () => { await new Promise(resolve => setTimeout(resolve, 10)); asyncResult = 'async-complete'; }); await emitter.emit('async-test'); this.assert('async-complete' === asyncResult, 'Should await async listeners'); }); } testInvalidEventKeyThrowsError() { this.test('Invalid event key throws error', () => { let emitter = new EventsManager(); let errorThrown = false; try{ emitter.on(123, () => {}); } catch(error){ errorThrown = true; this.assert(error instanceof TypeError, 'Should throw TypeError for invalid type'); } this.assert(errorThrown, 'Should throw error for invalid event key'); }); } testInvalidFunctionThrowsError() { this.test('Invalid function throws error', () => { let emitter = new EventsManager(); let errorThrown = false; try{ emitter.on('test', 'not-a-function'); } catch(error){ errorThrown = true; this.assert(error instanceof TypeError, 'Should throw TypeError for invalid function'); } this.assert(errorThrown, 'Should throw error for invalid function'); }); } testDangerousKeysRejectedByOnWithKey() { this.test('Dangerous keys rejected by onWithKey', () => { let emitter = new EventsManager(); let result = emitter.onWithKey('__proto__', () => {}, 'test-key'); this.assert(false === result, 'Should reject dangerous keys'); }); } testMissingParametersHandledGracefully() { this.test('Missing parameters handled gracefully', () => { let emitter = new EventsManager(); let errorThrown = false; try{ emitter.on(); } catch(error){ errorThrown = true; } this.assert(errorThrown, 'Should throw error for missing parameters'); }); } testOffWithKeyWithNonExistentKeyReturnsFalse() { this.test('offWithKey with non-existent key returns false', () => { let emitter = new EventsManager(); let result = emitter.offWithKey('non-existent-key'); this.assert(false === result, 'Should return false for non-existent key'); }); } testOffByMasterKeyWithNonExistentMasterKeyReturnsFalse() { this.test('offByMasterKey with non-existent master key returns false', () => { let emitter = new EventsManager(); let result = emitter.offByMasterKey('non-existent-master'); this.assert(false === result, 'Should return false for non-existent master key'); }); } testDuplicateKeyRegistrationReturnsFalse() { this.test('Duplicate key registration returns false', () => { let emitter = new EventsManager(); emitter.onWithKey('dup-test', () => {}, 'dup-key'); let result = emitter.onWithKey('dup-test2', () => {}, 'dup-key'); this.assert(false === result, 'Should return false for duplicate key'); }); } testEmptyEventNameHandled() { this.test('Empty event name handled', () => { let emitter = new EventsManager(); let called = false; emitter.on('', () => called = true); emitter.emit(''); this.assert(called, 'Should handle empty string event names'); }); } testSymbolEventKeysWork() { this.test('Symbol event keys work', () => { let emitter = new EventsManager(); let called = false; let sym = Symbol('test-symbol'); emitter.on(sym, () => called = true); emitter.emit(sym); this.assert(called, 'Should handle symbol event keys'); }); } testEmitWithNoListenersReturnsFalse() { this.test('Emit with no listeners returns false', async () => { let emitter = new EventsManager(); let result = await emitter.emit('no-listeners'); this.assert(false === result, 'Should return false when no listeners'); }); } testEmitWithListenersReturnsTrue() { this.test('Emit with listeners returns true', async () => { let emitter = new EventsManager(); emitter.on('has-listeners', () => {}); let result = await emitter.emit('has-listeners'); this.assert(true === result, 'Should return true when has listeners'); }); } printSummary() { console.log('\n'+'='.repeat(50)); console.log('TEST SUMMARY'); console.log('='.repeat(50)); console.log('Total tests:', this.testCount); console.log('Passed:', this.passedCount); console.log('Failed:', this.testCount - this.passedCount); console.log('Success rate:', Math.round((this.passedCount / this.testCount) * 100)+'%'); if(this.testCount - this.passedCount > 0){ console.log('\nFailed tests:'); for(let result of this.testResults){ if('FAIL' === result.status){ console.log('-', result.name, ':', result.error); } } } } } let testRunner = new TestEventsManager(); testRunner.runAllTests();