import * as util from 'node:util'; import { codeBlock } from 'common-tags'; import { partial } from '~test/util.ts'; import * as prettyStdout from './pretty-stdout.ts'; import type { BunyanRecord } from './types.ts'; describe('logger/pretty-stdout', () => { describe('getMeta(rec)', () => { it('returns empty string if null rec', () => { expect(prettyStdout.getMeta(null as any)).toBeEmptyString(); }); it('returns empty string if empty rec', () => { expect(prettyStdout.getMeta(partial())).toBeEmptyString(); }); it('returns empty string if no meta fields', () => { const rec = partial({ foo: 'bar', }); expect(prettyStdout.getMeta(rec)).toBeEmptyString(); }); it('supports single meta', () => { const rec = partial({ foo: 'bar', repository: 'a/b', }); expect(prettyStdout.getMeta(rec)).toEqual( util.styleText('gray', ' (repository=a/b)'), ); }); it('supports multi meta', () => { const rec = partial({ foo: 'bar', branch: 'c', repository: 'a/b', module: 'test', }); expect(prettyStdout.getMeta(rec)).toEqual( util.styleText('gray', ' (repository=a/b, branch=c) [test]'), ); }); it('returns plain text when colorize is false', () => { const rec = partial({ foo: 'bar', repository: 'a/b', module: 'test', }); expect(prettyStdout.getMeta(rec, false)).toBe(' (repository=a/b) [test]'); }); it.each([ { field: 'repository' }, { field: 'baseBranch' }, { field: 'packageFile' }, { field: 'depType' }, { field: 'dependency' }, { field: 'branch' }, ])('ignores a non-string value for the $field meta field', ({ field }) => { const rec = partial({ [field]: { count: 1 }, }); expect(prettyStdout.getMeta(rec)).toBeEmptyString(); }); }); describe('getDetails(rec)', () => { it('returns empty string if null rec', () => { expect(prettyStdout.getDetails(null as any)).toBeEmptyString(); }); it('returns empty string if empty rec', () => { expect( prettyStdout.getDetails(partial()), ).toBeEmptyString(); }); it('returns empty string if all are meta fields', () => { const rec = partial({ branch: 'bar', v: 0, }); expect(prettyStdout.getDetails(rec)).toBeEmptyString(); }); it.each([ { field: 'repository' }, { field: 'baseBranch' }, { field: 'packageFile' }, { field: 'depType' }, { field: 'dependency' }, { field: 'branch' }, ])( 'expands the $field meta field when its value is not a string', ({ field }) => { const rec = partial({ v: 0, [field]: { count: 1 }, }); expect(prettyStdout.getDetails(rec)).toBe( ` "${field}": {"count": 1}\n`, ); }, ); it('supports a config', () => { const rec = partial({ v: 0, config: { a: 'b', d: ['e', 'f'], }, }); expect(prettyStdout.getDetails(rec)).toBe( ` "config": {"a": "b", "d": ["e", "f"]}\n`, ); }); it('formats err.stack as readable multi-line output', () => { const rec = partial({ v: 0, err: { message: 'something broke', stack: 'Error: something broke\n at foo (file.js:1:1)', }, }); expect(prettyStdout.getDetails(rec)).toBe( `${prettyStdout.indent( codeBlock` "err": {"message": "something broke"} Error: something broke at foo (file.js:1:1) `, true, )}\n`, ); }); it('formats err.stack without other err fields', () => { const rec = partial({ v: 0, err: { stack: 'Error: oops\n at bar (file.js:2:2)', }, }); expect(prettyStdout.getDetails(rec)).toBe( `${prettyStdout.indent( codeBlock` Error: oops at bar (file.js:2:2) `, true, )}\n`, ); }); }); describe('formatRecord(rec)', () => { it('formats record', () => { const rec: BunyanRecord = { level: 10, msg: 'test message', v: 0, config: { a: 'b', d: ['e', 'f'], }, }; // The colorized level strings are built once, when the module is // imported, so whether they carry ANSI codes depends on the colour // support of the surrounding environment. Derive the expectation the // same way instead of assuming an uncoloured terminal. expect(prettyStdout.formatRecord(rec)).toEqual( [ `${util.styleText('gray', 'TRACE')}: test message`, ` "config": {"a": "b", "d": ["e", "f"]}`, ``, ].join('\n'), ); }); it('formats record without colors', () => { const rec = partial({ level: 10, msg: 'test message', v: 0, config: { a: 'b', d: ['e', 'f'], }, }); expect(prettyStdout.formatRecord(rec, false)).toEqual( `${codeBlock` TRACE: test message "config": {"a": "b", "d": ["e", "f"]} `}\n`, ); }); }); describe('PrettyStdoutStream', () => { it('writes formatted data to stdout', () => { const stdoutSpy = vi .spyOn(process.stdout, 'write') .mockImplementation(() => true); const stream = new prettyStdout.PrettyStdoutStream(); const rec: BunyanRecord = { level: 10, msg: 'test message', v: 0, }; stream.write(rec); expect(stdoutSpy).toHaveBeenCalledOnce(); expect(stdoutSpy.mock.calls[0][0]).toContain('test message'); }); }); });