import {expect, jest, test} from '@jest/globals'; import { Manager } from '../../src/manager'; import { Logger, RootLogger } from '../../src/logger'; import { Handler } from '../../src/handler'; import { LogRecord } from '../../src/log-record'; import { WARNING, DEBUG, INFO, NOTSET } from '../../src/log-level'; function makeManager(): Manager { return new Manager(new RootLogger(WARNING)); } describe('Manager', () => { describe('constructor', () => { test('stores the root logger', () => { const root = new RootLogger(WARNING); const manager = new Manager(root); expect(manager.root).toBe(root); }); }); describe('getLogger', () => { test('returns a Logger instance', () => { const manager = makeManager(); const logger = manager.getLogger('app'); expect(logger).toBeInstanceOf(Logger); expect(logger.scope).toBe('app'); }); test('returns the same logger for the same scope', () => { const manager = makeManager(); const a = manager.getLogger('app'); const b = manager.getLogger('app'); expect(a).toBe(b); }); test('returns different loggers for different scopes', () => { const manager = makeManager(); const a = manager.getLogger('app'); const b = manager.getLogger('lib'); expect(a).not.toBe(b); }); test('sets parent to root for top-level loggers', () => { const manager = makeManager(); const logger = manager.getLogger('app'); expect(logger.parent).toBe(manager.root); }); test('sets parent to existing parent logger', () => { const manager = makeManager(); const parent = manager.getLogger('app'); const child = manager.getLogger('app.module'); expect(child.parent).toBe(parent); }); test('establishes hierarchy for deeply nested loggers', () => { const manager = makeManager(); const top = manager.getLogger('a'); const mid = manager.getLogger('a.b'); const leaf = manager.getLogger('a.b.c'); expect(leaf.parent).toBe(mid); expect(mid.parent).toBe(top); expect(top.parent).toBe(manager.root); }); test('creates placeholders for intermediate loggers', () => { const manager = makeManager(); const leaf = manager.getLogger('a.b.c'); expect(leaf.parent).toBe(manager.root); const mid = manager.getLogger('a.b'); expect(leaf.parent).toBe(mid); expect(mid.parent).toBe(manager.root); }); test('fixes up children when intermediate logger is created', () => { const manager = makeManager(); const leaf = manager.getLogger('a.b.c'); const top = manager.getLogger('a'); expect(top.parent).toBe(manager.root); const mid = manager.getLogger('a.b'); expect(leaf.parent).toBe(mid); }); }); describe('disable', () => { test('defaults to 0', () => { const manager = makeManager(); expect(manager.disable).toBe(0); }); test('can be set to a level', () => { const manager = makeManager(); manager.disable = WARNING; expect(manager.disable).toBe(WARNING); }); }); describe('loggerClass', () => { test('accepts Logger subclass', () => { const manager = makeManager(); class CustomLogger extends Logger {} expect(() => { manager.loggerClass = CustomLogger as any }).not.toThrow(); }); test('rejects non-Logger class', () => { const manager = makeManager(); class NotALogger {} expect(() => { manager.loggerClass = NotALogger as any; }).toThrow(TypeError); }); test('accepts Logger itself', () => { const manager = makeManager(); expect(() => { manager.loggerClass = Logger as any }).not.toThrow(); }); }); describe('getLogger - manager assignment', () => { test('sets manager on newly created loggers', () => { const manager = makeManager(); const logger = manager.getLogger('app'); expect(logger.manager).toBe(manager); }); test('sets manager on loggers created from placeholders', () => { const manager = makeManager(); manager.getLogger('a.b.c'); const mid = manager.getLogger('a.b'); expect(mid.manager).toBe(manager); }); test('isEnabledFor respects manager.disable via getLogger', () => { const manager = makeManager(); const logger = manager.getLogger('app'); logger.setLevel(DEBUG); expect(logger.isEnabledFor(DEBUG)).toBe(true); manager.disable = WARNING; logger.clear(); expect(logger.isEnabledFor(DEBUG)).toBe(false); expect(logger.isEnabledFor(WARNING)).toBe(false); }); }); describe('propagation through hierarchy', () => { test('child logger propagates records to parent handler', () => { const manager = makeManager(); const parent = manager.getLogger('app'); const child = manager.getLogger('app.module'); parent.setLevel(DEBUG); child.setLevel(DEBUG); const emitted: string[] = []; const handler = new (class extends Handler { emit(record: LogRecord) { emitted.push(record.msg); } })(DEBUG); parent.addHandler(handler); child.info('propagated message'); expect(emitted).toContain('propagated message'); }); test('child does not propagate to unrelated logger', () => { const manager = makeManager(); const unrelated = manager.getLogger('other'); const child = manager.getLogger('app.module'); unrelated.setLevel(DEBUG); child.setLevel(DEBUG); const emitted: string[] = []; const handler = new (class extends Handler { emit(record: LogRecord) { emitted.push(record.msg); } })(DEBUG); unrelated.addHandler(handler); child.info('should not reach'); expect(emitted).not.toContain('should not reach'); }); }); describe('clear', () => { test('clears logger caches', () => { const manager = makeManager(); const logger = manager.getLogger('test'); logger.isEnabledFor(DEBUG); manager.clear(); }); test('does not throw when placeholders exist', () => { const manager = makeManager(); manager.getLogger('a.b.c'); expect(() => manager.clear()).not.toThrow(); }); }); });