- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
279 lines
8.3 KiB
JavaScript
279 lines
8.3 KiB
JavaScript
/**
|
|
* @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);
|
|
});
|
|
});
|
|
}); |