import {expect, jest, test} from '@jest/globals'; import { Handler, StreamHandler, ConsoleHandler, StderrHandler, FileHandler, } from '../../src/handler'; import { Formatter, DEFAULT_FORMATTER } from '../../src/formatter'; import { LogRecord } from '../../src/log-record'; import { Writable } from '../../src/helper/stream'; import { DEBUG, INFO, WARNING, ERROR, CRITICAL, NOTSET } from '../../src/log-level'; function makeRecord(level: number, msg: string): LogRecord { return new LogRecord('test', { level, msg }); } describe('Handler', () => { describe('constructor', () => { test('defaults to NOTSET level', () => { const h = new Handler(); expect(h.level).toBe(NOTSET); }); test('accepts a level argument', () => { const h = new Handler(WARNING); expect(h.level).toBe(WARNING); }); }); describe('level setter', () => { test('sets the level without infinite recursion', () => { const h = new Handler(); h.level = ERROR; expect(h.level).toBe(ERROR); }); }); describe('formatter', () => { test('getter returns null when no formatter set', () => { const h = new Handler(); expect(h.formatter).toBeNull(); }); test('getter returns assigned formatter', () => { const h = new Handler(); const fmt = new Formatter(); h.formatter = fmt; expect(h.formatter).toBe(fmt); }); }); describe('format', () => { test('uses DEFAULT_FORMATTER when no formatter assigned', () => { const h = new Handler(); const record = makeRecord(DEBUG, 'hello'); const result = h.format(record); expect(typeof result).toBe('string'); }); test('returns a string', () => { const h = new Handler(); const record = makeRecord(WARNING, 'test message'); expect(typeof h.format(record)).toBe('string'); }); }); describe('emit', () => { test('throws NotImplementedError on base class', () => { const h = new Handler(); const record = makeRecord(DEBUG, 'hello'); expect(() => h.emit(record)).toThrow('emit must be implemented'); }); }); describe('close', () => { test('sets closed to true', () => { const h = new Handler(); expect(h.closed).toBe(false); h.close(); expect(h.closed).toBe(true); }); }); describe('handleError', () => { test('does not throw', () => { const h = new Handler(); const record = makeRecord(DEBUG, 'test'); expect(() => h.handleError(record)).not.toThrow(); }); }); }); describe('StreamHandler', () => { test('writes formatted output to the stream', () => { const written: string[] = []; const stream: Writable = { write: (data: string) => { written.push(data) } }; const h = new StreamHandler(stream); const record = makeRecord(WARNING, 'stream test'); h.emit(record); expect(written.length).toBe(1); expect(written[0]).toContain('stream test'); }); test('uses default stream when none provided', () => { const h = new StreamHandler(); const record = makeRecord(DEBUG, 'default stream'); expect(() => h.emit(record)).not.toThrow(); }); test('calls handleError on emit failure', () => { const stream: Writable = { write: () => { throw new Error('write failed') } }; const h = new StreamHandler(stream); const record = makeRecord(DEBUG, 'fail'); expect(() => h.emit(record)).not.toThrow(); }); }); describe('ConsoleHandler', () => { let origLog: typeof console.log; let origWarn: typeof console.warn; let origError: typeof console.error; let logged: string[]; let warned: string[]; let errored: string[]; beforeEach(() => { logged = []; warned = []; errored = []; origLog = console.log; origWarn = console.warn; origError = console.error; console.log = (...args: any[]) => { logged.push(args.join(' ')) }; console.warn = (...args: any[]) => { warned.push(args.join(' ')) }; console.error = (...args: any[]) => { errored.push(args.join(' ')) }; }); afterEach(() => { console.log = origLog; console.warn = origWarn; console.error = origError; }); test('uses console.error for ERROR level', () => { const h = new ConsoleHandler(); h.emit(makeRecord(ERROR, 'error msg')); expect(errored.length).toBe(1); expect(errored[0]).toContain('error msg'); }); test('uses console.error for CRITICAL level', () => { const h = new ConsoleHandler(); h.emit(makeRecord(CRITICAL, 'critical msg')); expect(errored.length).toBe(1); expect(errored[0]).toContain('critical msg'); }); test('uses console.warn for WARNING level', () => { const h = new ConsoleHandler(); h.emit(makeRecord(WARNING, 'warn msg')); expect(warned.length).toBe(1); expect(warned[0]).toContain('warn msg'); }); test('uses console.log for INFO level', () => { const h = new ConsoleHandler(); h.emit(makeRecord(INFO, 'info msg')); expect(logged.length).toBe(1); expect(logged[0]).toContain('info msg'); }); test('uses console.log for DEBUG level', () => { const h = new ConsoleHandler(); h.emit(makeRecord(DEBUG, 'debug msg')); expect(logged.length).toBe(1); expect(logged[0]).toContain('debug msg'); }); }); describe('StderrHandler', () => { test('accepts a level', () => { const h = new StderrHandler(ERROR); expect(h.level).toBe(ERROR); }); test('emits via console.error', () => { const errored: string[] = []; const origError = console.error; console.error = (...args: any[]) => { errored.push(args.join(' ')) }; try { const h = new StderrHandler(DEBUG); h.emit(makeRecord(WARNING, 'stderr test')); expect(errored.length).toBe(1); expect(errored[0]).toContain('stderr test'); } finally { console.error = origError; } }); }); describe('FileHandler', () => { test('throws NotImplementedError on construction', () => { expect(() => new FileHandler({ filename: 'test.log' })).toThrow('not available in browser'); }); });