|
1 | 1 | import { describe, it, expect, vi } from 'vitest'; |
2 | 2 |
|
3 | | -import { makeLogger } from './logger.ts'; |
| 3 | +import { |
| 4 | + consoleTransport, |
| 5 | + DEFAULT_OPTIONS, |
| 6 | + Logger, |
| 7 | + makeLogger, |
| 8 | + mergeOptions, |
| 9 | +} from './logger.ts'; |
| 10 | +import type { LoggerOptions, LogLevel } from './logger.ts'; |
4 | 11 |
|
5 | | -describe('makeLogger', () => { |
6 | | - const consoleMethod = ['log', 'debug', 'info', 'warn', 'error'] as const; |
| 12 | +const consoleMethod = ['log', 'debug', 'info', 'warn', 'error'] as const; |
7 | 13 |
|
| 14 | +describe('Logger', () => { |
8 | 15 | it.each(consoleMethod)('has method %j', (method) => { |
9 | | - const testLogger = makeLogger('test'); |
| 16 | + const testLogger = new Logger({ tags: ['test'] }); |
10 | 17 | expect(testLogger).toHaveProperty(method); |
11 | 18 | expect(testLogger[method]).toBeTypeOf('function'); |
12 | 19 | }); |
13 | 20 |
|
14 | 21 | it.each(consoleMethod)( |
15 | | - 'calls %j with the provided label followed by a single argument', |
| 22 | + 'calls %j with the provided tags followed by a single argument', |
16 | 23 | (method) => { |
17 | 24 | const methodSpy = vi.spyOn(console, method); |
18 | | - const testLogger = makeLogger('test'); |
| 25 | + const tags = ['test']; |
| 26 | + const testLogger = new Logger({ tags }); |
19 | 27 | testLogger[method]('foo'); |
20 | | - expect(methodSpy).toHaveBeenCalledWith('test', 'foo'); |
| 28 | + expect(methodSpy).toHaveBeenCalledWith(tags, 'foo'); |
21 | 29 | }, |
22 | 30 | ); |
23 | 31 |
|
24 | 32 | it.each(consoleMethod)( |
25 | | - 'calls %j with the provided label followed by multiple arguments', |
| 33 | + 'calls %j with the provided tags followed by multiple arguments', |
26 | 34 | (method) => { |
27 | 35 | const methodSpy = vi.spyOn(console, method); |
28 | | - const testLogger = makeLogger('test'); |
| 36 | + const tags = ['test']; |
| 37 | + const testLogger = new Logger({ tags }); |
29 | 38 | testLogger[method]('foo', { bar: 'bar' }); |
30 | | - expect(methodSpy).toHaveBeenCalledWith('test', 'foo', { bar: 'bar' }); |
| 39 | + expect(methodSpy).toHaveBeenCalledWith(tags, 'foo', { bar: 'bar' }); |
31 | 40 | }, |
32 | 41 | ); |
33 | 42 |
|
34 | 43 | it.each(consoleMethod)( |
35 | | - 'calls %j with the provided label when given no argument', |
| 44 | + 'calls %j with the provided tags when given no argument', |
36 | 45 | (method) => { |
37 | 46 | const methodSpy = vi.spyOn(console, method); |
38 | | - const testLogger = makeLogger('test'); |
| 47 | + const tags = ['test']; |
| 48 | + const testLogger = new Logger({ tags }); |
39 | 49 | testLogger[method](); |
40 | | - expect(methodSpy).toHaveBeenCalledWith('test'); |
| 50 | + expect(methodSpy).toHaveBeenCalledWith(tags); |
41 | 51 | }, |
42 | 52 | ); |
43 | 53 |
|
44 | 54 | it('can be nested', () => { |
45 | 55 | const consoleSpy = vi.spyOn(console, 'log'); |
46 | | - const vatLogger = makeLogger('[vat 0x01]'); |
47 | | - const subLogger = makeLogger('(process)', vatLogger); |
| 56 | + const vatLogger = new Logger({ tags: ['vat 0x01'] }); |
| 57 | + const subLogger = vatLogger.subLogger({ tags: ['(process)'] }); |
48 | 58 | subLogger.log('foo'); |
49 | | - expect(consoleSpy).toHaveBeenCalledWith('[vat 0x01]', '(process)', 'foo'); |
| 59 | + expect(consoleSpy).toHaveBeenCalledWith(['vat 0x01', '(process)'], 'foo'); |
| 60 | + }); |
| 61 | + |
| 62 | + it('omits tagline when no tags are provided', () => { |
| 63 | + const consoleSpy = vi.spyOn(console, 'log'); |
| 64 | + const logger = new Logger(); |
| 65 | + logger.log('foo'); |
| 66 | + expect(consoleSpy).toHaveBeenCalledWith('foo'); |
| 67 | + }); |
| 68 | + |
| 69 | + it('passes objects directly in the data field', () => { |
| 70 | + const consoleSpy = vi.spyOn(console, 'log'); |
| 71 | + const logger = new Logger({ tags: ['test'] }); |
| 72 | + const message = 'foo'; |
| 73 | + const data = { bar: 'bar' }; |
| 74 | + logger.log(message, data); |
| 75 | + expect(consoleSpy).toHaveBeenCalledWith(['test'], message, data); |
| 76 | + }); |
| 77 | + |
| 78 | + describe('subLogger', () => { |
| 79 | + it('creates a new logger with the merged options', () => { |
| 80 | + const consoleSpy = vi.spyOn(console, 'log'); |
| 81 | + const logger = new Logger({ tags: ['test'] }); |
| 82 | + const subLogger = logger.subLogger({ tags: ['sub'] }); |
| 83 | + expect(subLogger).toBeInstanceOf(Logger); |
| 84 | + subLogger.log('foo'); |
| 85 | + expect(consoleSpy).toHaveBeenCalledWith(['test', 'sub'], 'foo'); |
| 86 | + }); |
| 87 | + |
| 88 | + it('works with no options', () => { |
| 89 | + const consoleSpy = vi.spyOn(console, 'log'); |
| 90 | + const logger = new Logger({ tags: ['test'] }); |
| 91 | + const subLogger = logger.subLogger(); |
| 92 | + expect(subLogger).toBeInstanceOf(Logger); |
| 93 | + subLogger.log('foo'); |
| 94 | + expect(consoleSpy).toHaveBeenCalledWith(['test'], 'foo'); |
| 95 | + }); |
| 96 | + }); |
| 97 | +}); |
| 98 | + |
| 99 | +describe('consoleTransport', () => { |
| 100 | + it.each(consoleMethod)('logs to the console with method %j', (method) => { |
| 101 | + const consoleSpy = vi.spyOn(console, method); |
| 102 | + const logger = new Logger({ tags: ['test'] }); |
| 103 | + logger[method]('foo'); |
| 104 | + expect(consoleSpy).toHaveBeenCalledWith(['test'], 'foo'); |
| 105 | + }); |
| 106 | + |
| 107 | + it.each(consoleMethod)('default data is an empty array for %j', (level) => { |
| 108 | + const consoleSpy = vi.spyOn(console, level); |
| 109 | + const entry = { tags: ['test'], level, message: 'foo' }; |
| 110 | + consoleTransport(entry); |
| 111 | + expect(consoleSpy).toHaveBeenCalledWith(['test'], 'foo'); |
| 112 | + }); |
| 113 | + |
| 114 | + it('does not log when the level is silent', () => { |
| 115 | + const consoleMethodSpies = consoleMethod.map((method) => |
| 116 | + vi.spyOn(console, method), |
| 117 | + ); |
| 118 | + consoleTransport({ tags: ['test'], level: 'silent', message: 'foo' }); |
| 119 | + consoleMethodSpies.forEach((spy) => expect(spy).not.toHaveBeenCalled()); |
| 120 | + }); |
| 121 | +}); |
| 122 | + |
| 123 | +describe('mergeOptions', () => { |
| 124 | + it.each([ |
| 125 | + { left: ['test'], right: ['sub'], result: ['test', 'sub'] }, |
| 126 | + { left: ['test', 'test'], right: ['sub'], result: ['test', 'sub'] }, |
| 127 | + { |
| 128 | + left: ['test', 'fizz'], |
| 129 | + right: ['test', 'buzz'], |
| 130 | + result: ['test', 'fizz', 'buzz'], |
| 131 | + }, |
| 132 | + ])('merges tags as expected: $left and $right', ({ left, right, result }) => { |
| 133 | + const options = mergeOptions({ tags: left }, { tags: right }); |
| 134 | + expect(options.tags).toStrictEqual(result); |
| 135 | + }); |
| 136 | + |
| 137 | + it('defaults to the default options', () => { |
| 138 | + const options = mergeOptions(); |
| 139 | + expect(options).toStrictEqual(DEFAULT_OPTIONS); |
| 140 | + }); |
| 141 | + |
| 142 | + const transportA = vi.fn(); |
| 143 | + const transportB = vi.fn(); |
| 144 | + |
| 145 | + it.each([ |
| 146 | + { left: { transports: [] }, right: { transports: [] }, result: [] }, |
| 147 | + { |
| 148 | + left: { transports: [transportA] }, |
| 149 | + right: { transports: [] }, |
| 150 | + result: [transportA], |
| 151 | + }, |
| 152 | + { |
| 153 | + left: { transports: [transportA] }, |
| 154 | + right: { transports: [transportA] }, |
| 155 | + result: [transportA], |
| 156 | + }, |
| 157 | + { |
| 158 | + left: { transports: [transportA] }, |
| 159 | + right: { transports: [transportB] }, |
| 160 | + result: [transportA, transportB], |
| 161 | + }, |
| 162 | + ])( |
| 163 | + 'merges transports as expected: $left and $right', |
| 164 | + ({ left, right, result }) => { |
| 165 | + const options = mergeOptions(left, right); |
| 166 | + expect(options.transports).toStrictEqual(result); |
| 167 | + }, |
| 168 | + ); |
| 169 | + |
| 170 | + it.each([ |
| 171 | + { left: { level: 'warn' }, right: { level: 'error' }, result: 'error' }, |
| 172 | + { left: { level: undefined }, right: { level: 'warn' }, result: 'warn' }, |
| 173 | + { left: { level: 'info' }, right: {}, result: 'info' }, |
| 174 | + ] as { left: LoggerOptions; right: LoggerOptions; result: LogLevel }[])( |
| 175 | + 'merges levels as expected: $left and $right', |
| 176 | + ({ left, right, result }) => { |
| 177 | + const options = mergeOptions( |
| 178 | + { ...left, transports: [] }, |
| 179 | + { ...right, transports: [] }, |
| 180 | + ); |
| 181 | + expect(options.level).toBe(result); |
| 182 | + }, |
| 183 | + ); |
| 184 | +}); |
| 185 | + |
| 186 | +describe('makeLogger', () => { |
| 187 | + it('creates a new logger from a label and a parent logger', () => { |
| 188 | + const logger = new Logger({ tags: ['test'] }); |
| 189 | + const subLogger = makeLogger('sub', logger); |
| 190 | + expect(subLogger).toBeInstanceOf(Logger); |
| 191 | + }); |
| 192 | + |
| 193 | + it('creates a new logger from a label', () => { |
| 194 | + const logSpy = vi.spyOn(console, 'log'); |
| 195 | + const logger = makeLogger('test'); |
| 196 | + expect(logger).toBeInstanceOf(Logger); |
| 197 | + logger.log('foo'); |
| 198 | + expect(logSpy).toHaveBeenCalledWith(['test'], 'foo'); |
50 | 199 | }); |
51 | 200 | }); |
0 commit comments