import {expect, jest, test} from '@jest/globals'; import { Formatter, DEFAULT_FORMATTER } from '../../src/formatter'; import { LogRecord } from '../../src/log-record'; import { DEBUG, WARNING, INFO } from '../../src/log-level'; import { MyError } from '../../src/helper/error'; function makeRecord(level: number, msg: string): LogRecord { return new LogRecord('test.module', { level, msg }); } describe('Formatter', () => { describe('constructor', () => { test('uses default format when no options given', () => { const fmt = new Formatter(); const record = makeRecord(WARNING, 'hello'); const result = fmt.format(record); expect(result).toBe('hello'); }); test('accepts custom format string', () => { const fmt = new Formatter({ fmt: '%(levelname)s - %(message)s' }); const record = makeRecord(DEBUG, 'debug msg'); expect(fmt.format(record)).toBe('DEBUG - debug msg'); }); test('throws on invalid style', () => { expect(() => new Formatter({ style: '{' })).toThrow('style must be one of'); }); }); describe('format', () => { test('substitutes %(name)s with logger scope', () => { const fmt = new Formatter({ fmt: '[%(name)s] %(message)s' }); const record = makeRecord(INFO, 'test'); expect(fmt.format(record)).toBe('[test.module] test'); }); test('substitutes %(levelno)d with numeric level', () => { const fmt = new Formatter({ fmt: '%(levelno)d: %(message)s' }); const record = makeRecord(WARNING, 'warn'); expect(fmt.format(record)).toBe('30: warn'); }); test('substitutes %(levelname)s with level name', () => { const fmt = new Formatter({ fmt: '%(levelname)s %(message)s' }); const record = makeRecord(DEBUG, 'msg'); expect(fmt.format(record)).toBe('DEBUG msg'); }); test('handles multiple placeholders', () => { const fmt = new Formatter({ fmt: '%(levelname)s:%(name)s:%(levelno)d:%(message)s' }); const record = makeRecord(WARNING, 'multi'); expect(fmt.format(record)).toBe('WARNING:test.module:30:multi'); }); test('throws on unknown field', () => { const fmt = new Formatter({ fmt: '%(nonexistent)s' }); const record = makeRecord(DEBUG, 'test'); expect(() => fmt.format(record)).toThrow('formatting field not found'); }); test('populates asctime when format uses it', () => { const fmt = new Formatter({ fmt: '%(asctime)s %(message)s' }); const record = makeRecord(INFO, 'timed'); const result = fmt.format(record); expect(result).toContain('timed'); expect(record.asctime.length).toBeGreaterThan(0); }); test('does not populate asctime when format does not use it', () => { const fmt = new Formatter({ fmt: '%(message)s' }); const record = makeRecord(INFO, 'no time'); fmt.format(record); expect(record.asctime).toBe(''); }); }); describe('formatTime', () => { test('returns ISO8601-like string by default', () => { const fmt = new Formatter(); const record = makeRecord(INFO, 'test'); const result = fmt.formatTime(record); expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/); }); test('uses custom datefmt with strftime tokens', () => { const fmt = new Formatter(); const record = makeRecord(INFO, 'test'); const result = fmt.formatTime(record, '%Y/%m/%d'); expect(result).toMatch(/^\d{4}\/\d{2}\/\d{2}$/); }); }); describe('formatError', () => { test('returns stack trace when available', () => { const fmt = new Formatter(); const err = new MyError('test error'); const result = fmt.formatError(err); expect(result).toContain('test error'); expect(result).toContain('\n'); }); test('returns string representation when no stack', () => { const fmt = new Formatter(); const err = new MyError('no stack'); err.stack = undefined; const result = fmt.formatError(err); expect(result).toContain('no stack'); }); }); describe('usesTime', () => { test('returns true when format contains asctime', () => { const fmt = new Formatter({ fmt: '%(asctime)s %(message)s' }); expect(fmt.usesTime()).toBe(true); }); test('returns false when format does not contain asctime', () => { const fmt = new Formatter({ fmt: '%(message)s' }); expect(fmt.usesTime()).toBe(false); }); }); }); describe('LogRecord.getMessage', () => { test('returns the message string', () => { const record = makeRecord(INFO, 'plain message'); expect(record.getMessage()).toBe('plain message'); }); test('substitutes %s args into message', () => { const record = new LogRecord('test', { level: INFO, msg: 'hello %s, you have %s items', args: ['world', '5'], }); expect(record.getMessage()).toBe('hello world, you have 5 items'); }); });