Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
343
tests/core/DependencyManager.test.js
Normal file
343
tests/core/DependencyManager.test.js
Normal file
@@ -0,0 +1,343 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { DependencyManager } from '../../resources/js/core/DependencyManager.js';
|
||||
|
||||
// Mock Logger
|
||||
jest.mock('../../resources/js/core/logger.js', () => ({
|
||||
Logger: {
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
describe('DependencyManager', () => {
|
||||
let depManager;
|
||||
|
||||
beforeEach(() => {
|
||||
depManager = new DependencyManager();
|
||||
});
|
||||
|
||||
describe('Module registration', () => {
|
||||
test('should register module with valid definition', () => {
|
||||
const definition = {
|
||||
name: 'test-module',
|
||||
version: '1.0.0',
|
||||
dependencies: [],
|
||||
provides: ['test-service'],
|
||||
priority: 0
|
||||
};
|
||||
|
||||
depManager.register(definition);
|
||||
|
||||
expect(depManager.getModule('test-module')).toEqual(definition);
|
||||
});
|
||||
|
||||
test('should reject invalid module definitions', () => {
|
||||
const invalidDefinitions = [
|
||||
{ version: '1.0.0', dependencies: [] }, // No name
|
||||
{ name: 'test', dependencies: [] }, // No version
|
||||
{ name: 'test', version: '1.0.0' }, // No dependencies array
|
||||
{ name: 'test', version: '1.0.0', dependencies: [{}] } // Invalid dependency
|
||||
];
|
||||
|
||||
invalidDefinitions.forEach(def => {
|
||||
depManager.register(def);
|
||||
expect(depManager.getModule(def.name)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('should not allow duplicate registration', () => {
|
||||
const definition1 = {
|
||||
name: 'duplicate',
|
||||
version: '1.0.0',
|
||||
dependencies: [],
|
||||
provides: [],
|
||||
priority: 0
|
||||
};
|
||||
|
||||
const definition2 = {
|
||||
name: 'duplicate',
|
||||
version: '2.0.0',
|
||||
dependencies: [],
|
||||
provides: [],
|
||||
priority: 0
|
||||
};
|
||||
|
||||
depManager.register(definition1);
|
||||
depManager.register(definition2);
|
||||
|
||||
expect(depManager.getModule('duplicate').version).toBe('1.0.0');
|
||||
});
|
||||
|
||||
test('should build dependents map correctly', () => {
|
||||
const moduleA = {
|
||||
name: 'module-a',
|
||||
version: '1.0.0',
|
||||
dependencies: [],
|
||||
provides: [],
|
||||
priority: 0
|
||||
};
|
||||
|
||||
const moduleB = {
|
||||
name: 'module-b',
|
||||
version: '1.0.0',
|
||||
dependencies: [{ name: 'module-a', version: '1.0.0', optional: false }],
|
||||
provides: [],
|
||||
priority: 0
|
||||
};
|
||||
|
||||
depManager.register(moduleA);
|
||||
depManager.register(moduleB);
|
||||
|
||||
expect(depManager.getDependents('module-a')).toContain('module-b');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dependency resolution', () => {
|
||||
beforeEach(() => {
|
||||
// Register test modules
|
||||
depManager.register({
|
||||
name: 'core',
|
||||
version: '1.0.0',
|
||||
dependencies: [],
|
||||
provides: ['core-service'],
|
||||
priority: 100
|
||||
});
|
||||
|
||||
depManager.register({
|
||||
name: 'ui',
|
||||
version: '1.0.0',
|
||||
dependencies: [{ name: 'core', version: '1.0.0', optional: false }],
|
||||
provides: ['ui-service'],
|
||||
priority: 50
|
||||
});
|
||||
|
||||
depManager.register({
|
||||
name: 'advanced',
|
||||
version: '1.0.0',
|
||||
dependencies: [
|
||||
{ name: 'core', version: '1.0.0', optional: false },
|
||||
{ name: 'ui', version: '1.0.0', optional: false }
|
||||
],
|
||||
provides: ['advanced-service'],
|
||||
priority: 10
|
||||
});
|
||||
});
|
||||
|
||||
test('should calculate correct initialization order', () => {
|
||||
const order = depManager.calculateInitializationOrder();
|
||||
|
||||
expect(order).toEqual(['core', 'ui', 'advanced']);
|
||||
});
|
||||
|
||||
test('should handle optional dependencies', () => {
|
||||
depManager.register({
|
||||
name: 'optional-dep',
|
||||
version: '1.0.0',
|
||||
dependencies: [
|
||||
{ name: 'core', version: '1.0.0', optional: false },
|
||||
{ name: 'missing-optional', version: '1.0.0', optional: true }
|
||||
],
|
||||
provides: [],
|
||||
priority: 0
|
||||
});
|
||||
|
||||
const order = depManager.calculateInitializationOrder();
|
||||
|
||||
expect(order).toContain('optional-dep');
|
||||
});
|
||||
|
||||
test('should detect circular dependencies', () => {
|
||||
depManager.register({
|
||||
name: 'circular-a',
|
||||
version: '1.0.0',
|
||||
dependencies: [{ name: 'circular-b', version: '1.0.0', optional: false }],
|
||||
provides: [],
|
||||
priority: 0
|
||||
});
|
||||
|
||||
depManager.register({
|
||||
name: 'circular-b',
|
||||
version: '1.0.0',
|
||||
dependencies: [{ name: 'circular-a', version: '1.0.0', optional: false }],
|
||||
provides: [],
|
||||
priority: 0
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
depManager.calculateInitializationOrder();
|
||||
}).toThrow('Circular dependency detected');
|
||||
});
|
||||
|
||||
test('should respect priority in initialization order', () => {
|
||||
depManager.register({
|
||||
name: 'high-priority',
|
||||
version: '1.0.0',
|
||||
dependencies: [],
|
||||
provides: [],
|
||||
priority: 200
|
||||
});
|
||||
|
||||
const order = depManager.calculateInitializationOrder();
|
||||
|
||||
expect(order.indexOf('high-priority')).toBeLessThan(order.indexOf('core'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dependency checking', () => {
|
||||
beforeEach(() => {
|
||||
depManager.register({
|
||||
name: 'dependency',
|
||||
version: '1.0.0',
|
||||
dependencies: [],
|
||||
provides: [],
|
||||
priority: 0
|
||||
});
|
||||
|
||||
depManager.register({
|
||||
name: 'dependent',
|
||||
version: '1.0.0',
|
||||
dependencies: [{ name: 'dependency', version: '1.0.0', optional: false }],
|
||||
provides: [],
|
||||
priority: 0
|
||||
});
|
||||
});
|
||||
|
||||
test('should check dependencies correctly', () => {
|
||||
const check = depManager.checkDependencies('dependent');
|
||||
|
||||
expect(check.satisfied).toBe(false);
|
||||
expect(check.missing).toContain('dependency (not initialized)');
|
||||
});
|
||||
|
||||
test('should pass dependency check when dependencies are initialized', () => {
|
||||
depManager.markInitialized('dependency');
|
||||
const check = depManager.checkDependencies('dependent');
|
||||
|
||||
expect(check.satisfied).toBe(true);
|
||||
expect(check.missing).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should handle optional dependencies in check', () => {
|
||||
depManager.register({
|
||||
name: 'optional-user',
|
||||
version: '1.0.0',
|
||||
dependencies: [
|
||||
{ name: 'dependency', version: '1.0.0', optional: false },
|
||||
{ name: 'missing-optional', version: '1.0.0', optional: true }
|
||||
],
|
||||
provides: [],
|
||||
priority: 0
|
||||
});
|
||||
|
||||
depManager.markInitialized('dependency');
|
||||
const check = depManager.checkDependencies('optional-user');
|
||||
|
||||
expect(check.satisfied).toBe(true);
|
||||
expect(check.optional).toContain('missing-optional');
|
||||
});
|
||||
|
||||
test('should check readiness for initialization', () => {
|
||||
expect(depManager.isReadyToInitialize('dependency')).toBe(true);
|
||||
expect(depManager.isReadyToInitialize('dependent')).toBe(false);
|
||||
|
||||
depManager.markInitialized('dependency');
|
||||
expect(depManager.isReadyToInitialize('dependent')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Module state tracking', () => {
|
||||
beforeEach(() => {
|
||||
depManager.register({
|
||||
name: 'test-module',
|
||||
version: '1.0.0',
|
||||
dependencies: [],
|
||||
provides: [],
|
||||
priority: 0
|
||||
});
|
||||
});
|
||||
|
||||
test('should track initialization state', () => {
|
||||
expect(depManager.initialized.has('test-module')).toBe(false);
|
||||
expect(depManager.initializing.has('test-module')).toBe(false);
|
||||
|
||||
depManager.markInitializing('test-module');
|
||||
expect(depManager.initializing.has('test-module')).toBe(true);
|
||||
|
||||
depManager.markInitialized('test-module');
|
||||
expect(depManager.initialized.has('test-module')).toBe(true);
|
||||
expect(depManager.initializing.has('test-module')).toBe(false);
|
||||
});
|
||||
|
||||
test('should provide status report', () => {
|
||||
depManager.register({
|
||||
name: 'module-2',
|
||||
version: '1.0.0',
|
||||
dependencies: [],
|
||||
provides: [],
|
||||
priority: 0
|
||||
});
|
||||
|
||||
depManager.markInitialized('test-module');
|
||||
depManager.markInitializing('module-2');
|
||||
|
||||
const status = depManager.getStatus();
|
||||
|
||||
expect(status.total).toBe(2);
|
||||
expect(status.initialized).toBe(1);
|
||||
expect(status.initializing).toBe(1);
|
||||
expect(status.pending).toBe(0);
|
||||
expect(status.modules.initialized).toContain('test-module');
|
||||
expect(status.modules.initializing).toContain('module-2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Definition builder', () => {
|
||||
test('should create definition with builder pattern', () => {
|
||||
const definition = DependencyManager.createDefinition('builder-test', '2.0.0')
|
||||
.depends('dep1', '1.0.0')
|
||||
.depends('dep2', '2.0.0', true)
|
||||
.provides('service1', 'service2')
|
||||
.priority(50);
|
||||
|
||||
expect(definition.name).toBe('builder-test');
|
||||
expect(definition.version).toBe('2.0.0');
|
||||
expect(definition.dependencies).toHaveLength(2);
|
||||
expect(definition.dependencies[0]).toEqual({
|
||||
name: 'dep1',
|
||||
version: '1.0.0',
|
||||
optional: false
|
||||
});
|
||||
expect(definition.dependencies[1]).toEqual({
|
||||
name: 'dep2',
|
||||
version: '2.0.0',
|
||||
optional: true
|
||||
});
|
||||
expect(definition.provides).toEqual(['service1', 'service2']);
|
||||
expect(definition.priority).toBe(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Reset and cleanup', () => {
|
||||
test('should reset all state', () => {
|
||||
depManager.register({
|
||||
name: 'test',
|
||||
version: '1.0.0',
|
||||
dependencies: [],
|
||||
provides: [],
|
||||
priority: 0
|
||||
});
|
||||
|
||||
depManager.markInitialized('test');
|
||||
|
||||
depManager.reset();
|
||||
|
||||
expect(depManager.modules.size).toBe(0);
|
||||
expect(depManager.initialized.size).toBe(0);
|
||||
expect(depManager.dependents.size).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
173
tests/core/Logger.test.js
Normal file
173
tests/core/Logger.test.js
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { Logger } from '../../resources/js/core/logger.js';
|
||||
|
||||
// Mock the frameloop dependency
|
||||
jest.mock('../../resources/js/core/frameloop.js', () => ({
|
||||
monitor: {
|
||||
log: jest.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
import { monitor } from '../../resources/js/core/frameloop.js';
|
||||
|
||||
describe('Logger', () => {
|
||||
beforeEach(() => {
|
||||
Logger.enabled = true;
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Basic logging functionality', () => {
|
||||
test('should log messages when enabled', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
Logger.log('test message');
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('[LOG]')
|
||||
);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('test message')
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('should not log messages when disabled', () => {
|
||||
Logger.enabled = false;
|
||||
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||
|
||||
Logger.info('test message');
|
||||
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('should handle different log levels', () => {
|
||||
const methods = ['log', 'warn', 'info', 'error'];
|
||||
|
||||
methods.forEach(method => {
|
||||
const consoleSpy = jest.spyOn(console, method).mockImplementation();
|
||||
|
||||
Logger[method]('test message');
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`[${method.toUpperCase()}]`)
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
test('should format timestamps correctly', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||
|
||||
Logger.info('test message');
|
||||
|
||||
const logCall = consoleSpy.mock.calls[0][0];
|
||||
expect(logCall).toMatch(/\[\d{2}:\d{2}:\d{2}\]/);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('should stringify objects', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||
const testObj = { key: 'value', number: 42 };
|
||||
|
||||
Logger.info('Object:', testObj);
|
||||
|
||||
const logCall = consoleSpy.mock.calls[0][0];
|
||||
expect(logCall).toContain('{"key":"value","number":42}');
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('should handle mixed argument types', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
Logger.log('Message:', 42, { key: 'value' }, true);
|
||||
|
||||
const logCall = consoleSpy.mock.calls[0][0];
|
||||
expect(logCall).toContain('Message: 42 {"key":"value"} true');
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Monitor integration', () => {
|
||||
test('should call monitor.log when available', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||
|
||||
Logger.info('test message');
|
||||
|
||||
expect(monitor.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('test message')
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('should handle missing monitor gracefully', () => {
|
||||
// Temporarily remove monitor
|
||||
const originalMonitor = monitor.log;
|
||||
monitor.log = undefined;
|
||||
|
||||
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||
|
||||
expect(() => {
|
||||
Logger.info('test message');
|
||||
}).not.toThrow();
|
||||
|
||||
monitor.log = originalMonitor;
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error handling', () => {
|
||||
test('should handle console method not available', () => {
|
||||
const originalConsole = console.debug;
|
||||
console.debug = undefined;
|
||||
|
||||
expect(() => {
|
||||
Logger._write('debug', '[DEBUG]', ['test']);
|
||||
}).not.toThrow();
|
||||
|
||||
console.debug = originalConsole;
|
||||
});
|
||||
|
||||
test('should handle JSON.stringify errors', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
// Create circular reference
|
||||
const circular = {};
|
||||
circular.self = circular;
|
||||
|
||||
expect(() => {
|
||||
Logger.error('Circular:', circular);
|
||||
}).not.toThrow();
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Performance', () => {
|
||||
test('should be performant with many log calls', () => {
|
||||
Logger.enabled = false; // Disable actual logging for performance test
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
Logger.info(`Log message ${i}`);
|
||||
}
|
||||
|
||||
const end = performance.now();
|
||||
const duration = end - start;
|
||||
|
||||
// Should complete 1000 calls in under 100ms when disabled
|
||||
expect(duration).toBeLessThan(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
279
tests/core/StateManager.test.js
Normal file
279
tests/core/StateManager.test.js
Normal file
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { StateManager } from '../../resources/js/core/StateManager.js';
|
||||
|
||||
// Mock Logger
|
||||
jest.mock('../../resources/js/core/logger.js', () => ({
|
||||
Logger: {
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
describe('StateManager', () => {
|
||||
let stateManager;
|
||||
|
||||
beforeEach(() => {
|
||||
stateManager = new StateManager();
|
||||
stateManager.setContext('test-module');
|
||||
});
|
||||
|
||||
describe('State registration', () => {
|
||||
test('should register state with default value', () => {
|
||||
stateManager.register('testKey', 'defaultValue');
|
||||
|
||||
expect(stateManager.get('testKey')).toBe('defaultValue');
|
||||
});
|
||||
|
||||
test('should not allow duplicate registration', () => {
|
||||
stateManager.register('testKey', 'value1');
|
||||
stateManager.register('testKey', 'value2');
|
||||
|
||||
expect(stateManager.get('testKey')).toBe('value1');
|
||||
});
|
||||
|
||||
test('should track state ownership', () => {
|
||||
stateManager.setContext('module-a');
|
||||
stateManager.register('keyA', 'valueA');
|
||||
|
||||
stateManager.setContext('module-b');
|
||||
stateManager.register('keyB', 'valueB');
|
||||
|
||||
expect(stateManager.stateOwners.get('keyA')).toBe('module-a');
|
||||
expect(stateManager.stateOwners.get('keyB')).toBe('module-b');
|
||||
});
|
||||
});
|
||||
|
||||
describe('State access', () => {
|
||||
beforeEach(() => {
|
||||
stateManager.register('testKey', 42);
|
||||
});
|
||||
|
||||
test('should get current state value', () => {
|
||||
expect(stateManager.get('testKey')).toBe(42);
|
||||
});
|
||||
|
||||
test('should return undefined for unknown keys', () => {
|
||||
expect(stateManager.get('unknownKey')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should allow owner to set value', () => {
|
||||
const success = stateManager.set('testKey', 100);
|
||||
|
||||
expect(success).toBe(true);
|
||||
expect(stateManager.get('testKey')).toBe(100);
|
||||
});
|
||||
|
||||
test('should prevent non-owner from setting value', () => {
|
||||
stateManager.setContext('other-module');
|
||||
const success = stateManager.set('testKey', 200);
|
||||
|
||||
expect(success).toBe(false);
|
||||
expect(stateManager.get('testKey')).toBe(42);
|
||||
});
|
||||
|
||||
test('should allow forced set by non-owner', () => {
|
||||
stateManager.setContext('other-module');
|
||||
const success = stateManager.set('testKey', 300, true);
|
||||
|
||||
expect(success).toBe(true);
|
||||
expect(stateManager.get('testKey')).toBe(300);
|
||||
});
|
||||
|
||||
test('should not trigger subscribers for same value', () => {
|
||||
const callback = jest.fn();
|
||||
stateManager.subscribe('testKey', callback);
|
||||
|
||||
stateManager.set('testKey', 42); // Same value
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('State subscriptions', () => {
|
||||
beforeEach(() => {
|
||||
stateManager.register('testKey', 'initial');
|
||||
});
|
||||
|
||||
test('should subscribe to state changes', () => {
|
||||
const callback = jest.fn();
|
||||
const subscriptionId = stateManager.subscribe('testKey', callback);
|
||||
|
||||
expect(subscriptionId).toBeTruthy();
|
||||
expect(typeof subscriptionId).toBe('string');
|
||||
});
|
||||
|
||||
test('should notify subscribers on state change', () => {
|
||||
const callback = jest.fn();
|
||||
stateManager.subscribe('testKey', callback);
|
||||
|
||||
stateManager.set('testKey', 'changed');
|
||||
|
||||
expect(callback).toHaveBeenCalledWith('changed', 'initial', 'testKey');
|
||||
});
|
||||
|
||||
test('should handle multiple subscribers', () => {
|
||||
const callback1 = jest.fn();
|
||||
const callback2 = jest.fn();
|
||||
|
||||
stateManager.subscribe('testKey', callback1);
|
||||
stateManager.subscribe('testKey', callback2);
|
||||
|
||||
stateManager.set('testKey', 'updated');
|
||||
|
||||
expect(callback1).toHaveBeenCalledWith('updated', 'initial', 'testKey');
|
||||
expect(callback2).toHaveBeenCalledWith('updated', 'initial', 'testKey');
|
||||
});
|
||||
|
||||
test('should unsubscribe correctly', () => {
|
||||
const callback = jest.fn();
|
||||
const subscriptionId = stateManager.subscribe('testKey', callback);
|
||||
|
||||
stateManager.unsubscribe(subscriptionId);
|
||||
stateManager.set('testKey', 'after-unsubscribe');
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle subscriber errors gracefully', () => {
|
||||
const errorCallback = jest.fn(() => {
|
||||
throw new Error('Subscriber error');
|
||||
});
|
||||
const goodCallback = jest.fn();
|
||||
|
||||
stateManager.subscribe('testKey', errorCallback);
|
||||
stateManager.subscribe('testKey', goodCallback);
|
||||
|
||||
expect(() => {
|
||||
stateManager.set('testKey', 'trigger-error');
|
||||
}).not.toThrow();
|
||||
|
||||
expect(goodCallback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not subscribe to unknown keys', () => {
|
||||
const callback = jest.fn();
|
||||
const subscriptionId = stateManager.subscribe('unknownKey', callback);
|
||||
|
||||
expect(subscriptionId).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('State reset', () => {
|
||||
beforeEach(() => {
|
||||
stateManager.register('testKey', 'default');
|
||||
stateManager.set('testKey', 'changed');
|
||||
});
|
||||
|
||||
test('should reset to default value', () => {
|
||||
const success = stateManager.reset('testKey');
|
||||
|
||||
expect(success).toBe(true);
|
||||
expect(stateManager.get('testKey')).toBe('default');
|
||||
});
|
||||
|
||||
test('should notify subscribers on reset', () => {
|
||||
const callback = jest.fn();
|
||||
stateManager.subscribe('testKey', callback);
|
||||
|
||||
stateManager.reset('testKey');
|
||||
|
||||
expect(callback).toHaveBeenCalledWith('default', 'changed', 'testKey');
|
||||
});
|
||||
|
||||
test('should not reset unknown keys', () => {
|
||||
const success = stateManager.reset('unknownKey');
|
||||
expect(success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Module scoped interface', () => {
|
||||
test('should create scoped state manager', () => {
|
||||
const scoped = stateManager.createScope('scoped-module');
|
||||
|
||||
expect(scoped).toHaveProperty('register');
|
||||
expect(scoped).toHaveProperty('get');
|
||||
expect(scoped).toHaveProperty('set');
|
||||
expect(scoped).toHaveProperty('subscribe');
|
||||
expect(scoped).toHaveProperty('cleanup');
|
||||
});
|
||||
|
||||
test('should use module context in scoped operations', () => {
|
||||
const scoped = stateManager.createScope('scoped-module');
|
||||
|
||||
scoped.register('scopedKey', 'scopedValue');
|
||||
|
||||
expect(stateManager.stateOwners.get('scopedKey')).toBe('scoped-module');
|
||||
});
|
||||
|
||||
test('should cleanup module subscriptions', () => {
|
||||
const scoped = stateManager.createScope('cleanup-module');
|
||||
const callback = jest.fn();
|
||||
|
||||
scoped.register('cleanupKey', 'value');
|
||||
const subscriptionId = scoped.subscribe('cleanupKey', callback);
|
||||
|
||||
scoped.cleanup();
|
||||
|
||||
// Subscription should be cleaned up
|
||||
stateManager.set('cleanupKey', 'after-cleanup', true);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Debugging and introspection', () => {
|
||||
test('should provide state snapshot', () => {
|
||||
stateManager.register('key1', 'value1');
|
||||
stateManager.register('key2', 42);
|
||||
|
||||
const snapshot = stateManager.getSnapshot();
|
||||
|
||||
expect(snapshot).toHaveProperty('state');
|
||||
expect(snapshot).toHaveProperty('owners');
|
||||
expect(snapshot).toHaveProperty('subscriptions');
|
||||
expect(snapshot.state.key1).toBe('value1');
|
||||
expect(snapshot.state.key2).toBe(42);
|
||||
});
|
||||
|
||||
test('should reset entire state manager', () => {
|
||||
stateManager.register('key1', 'value1');
|
||||
stateManager.subscribe('key1', jest.fn());
|
||||
|
||||
stateManager.resetAll();
|
||||
|
||||
expect(stateManager.state.size).toBe(0);
|
||||
expect(stateManager.subscribers.size).toBe(0);
|
||||
expect(stateManager.stateOwners.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Performance', () => {
|
||||
test('should handle many state operations efficiently', () => {
|
||||
const start = performance.now();
|
||||
|
||||
// Register many state keys
|
||||
for (let i = 0; i < 100; i++) {
|
||||
stateManager.register(`key${i}`, i);
|
||||
}
|
||||
|
||||
// Subscribe to all keys
|
||||
for (let i = 0; i < 100; i++) {
|
||||
stateManager.subscribe(`key${i}`, jest.fn());
|
||||
}
|
||||
|
||||
// Update all keys
|
||||
for (let i = 0; i < 100; i++) {
|
||||
stateManager.set(`key${i}`, i * 2);
|
||||
}
|
||||
|
||||
const end = performance.now();
|
||||
const duration = end - start;
|
||||
|
||||
// Should complete in reasonable time
|
||||
expect(duration).toBeLessThan(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user