esm-logging/tests/unit/formatter.test.ts
Tiara Rodney 3422cfb799
test(formatter): add tests for formatting, formatTime, formatError
Cover %-style substitution with %(name)s, %(levelno)d, %(levelname)s,
multiple placeholders, unknown field errors, asctime population,
ISO8601 and custom datefmt time formatting, Error.stack formatting,
usesTime detection, and LogRecord.getMessage with %s arg substitution.
2026-03-13 23:22:10 +01:00

143 lines
5.4 KiB
TypeScript

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');
});
});