/** * @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); }); }); });