Skip to content

Commit 0a18596

Browse files
committed
feat: scoped and mocked loggers
1 parent b9f313f commit 0a18596

File tree

6 files changed

+213
-8
lines changed

6 files changed

+213
-8
lines changed

electron/common/__mocks__/writable.mock.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { vi } from 'vitest';
33

44
type Callback = (...args: Array<unknown>) => void;
55

6-
export class WritableMock extends Writable {
6+
export class WritableMockImpl extends Writable {
77
private onCallbacksMap: Record<string, Array<Callback>> = {};
88
private onceCallbacksMap: Record<string, Array<Callback>> = {};
99

Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1-
import { WritableMock } from '../../__mocks__/writable.mock.js';
1+
import type { MockedFunction } from 'vitest';
2+
import { WritableMockImpl } from '../../__mocks__/writable.mock.js';
3+
import type { LogTransport } from '../types.js';
24

3-
export class LogTransportMock extends WritableMock {}
5+
export interface LogTransportMock extends LogTransport {
6+
writable: boolean;
7+
write: MockedFunction<WritableMockImpl['write']>;
8+
on: MockedFunction<(...args: Parameters<WritableMockImpl['on']>) => this>;
9+
once: MockedFunction<(...args: Parameters<WritableMockImpl['once']>) => this>;
10+
off: MockedFunction<(...args: Parameters<WritableMockImpl['off']>) => this>;
11+
emit: MockedFunction<WritableMockImpl['emit']>;
12+
}
13+
14+
export class LogTransportMockImpl
15+
extends WritableMockImpl
16+
implements LogTransportMock {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { MockedFunction } from 'vitest';
2+
import { vi } from 'vitest';
3+
import { AbstractLogger } from '../abstract-logger.js';
4+
import { type Logger } from '../types.js';
5+
6+
export interface LoggerMock extends Logger {
7+
error: MockedFunction<Logger['error']>;
8+
warn: MockedFunction<Logger['warn']>;
9+
info: MockedFunction<Logger['info']>;
10+
debug: MockedFunction<Logger['debug']>;
11+
log: MockedFunction<Logger['log']>;
12+
}
13+
14+
export class LoggerMockImpl extends AbstractLogger implements LoggerMock {
15+
public override error = vi
16+
.fn<Logger['error']>()
17+
.mockImplementation((...args) => {
18+
super.error(...args);
19+
});
20+
21+
public override warn = vi
22+
.fn<Logger['warn']>()
23+
.mockImplementation((...args) => {
24+
super.warn(...args);
25+
});
26+
27+
public override info = vi
28+
.fn<Logger['info']>()
29+
.mockImplementation((...args) => {
30+
super.info(...args);
31+
});
32+
33+
public override debug = vi
34+
.fn<Logger['debug']>()
35+
.mockImplementation((...args) => {
36+
super.debug(...args);
37+
});
38+
39+
public override log = vi.fn();
40+
}

electron/common/logger/__tests__/logger.test.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import type { MockInstance, Mocked } from 'vitest';
22
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
33
import type { LogFormatterMock } from '../__mocks__/log-formatter.mock.js';
44
import { mockLogFormatterFactory } from '../__mocks__/log-formatter.mock.js';
5-
import { LogTransportMock } from '../__mocks__/log-transport.mock.js';
5+
import type { LogTransportMock } from '../__mocks__/log-transport.mock.js';
6+
import { LogTransportMockImpl } from '../__mocks__/log-transport.mock.js';
67
import { LoggerImpl } from '../logger.js';
78
import type { LogFormatter, LogMessage, LogTransport } from '../types.js';
89
import { LogLevel } from '../types.js';
@@ -67,7 +68,7 @@ describe('logger', () => {
6768
};
6869
delete mockFlatLogMessage['data'];
6970

70-
mockTransport = new LogTransportMock();
71+
mockTransport = new LogTransportMockImpl();
7172
mockFormatter = mockLogFormatterFactory();
7273

7374
mockTransportConfig = {
@@ -296,7 +297,7 @@ describe('logger', () => {
296297

297298
const consoleErrorSpy = vi.spyOn(console, 'error');
298299

299-
const mockTransport2 = new LogTransportMock();
300+
const mockTransport2 = new LogTransportMockImpl();
300301
const mockFormatter2 = mockLogFormatterFactory();
301302

302303
const mockTransportConfig2 = {
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,121 @@
1-
import { describe, it } from 'vitest';
1+
import { beforeEach, describe, expect, it } from 'vitest';
2+
import type { LoggerMock } from '../__mocks__/logger.mock.js';
3+
import { LoggerMockImpl } from '../__mocks__/logger.mock.js';
4+
import { ScopedLoggerImpl } from '../scoped-logger.js';
5+
import { LogLevel } from '../types.js';
26

37
describe('scoped-logger', () => {
4-
it.todo('');
8+
let mockLogger: LoggerMock;
9+
10+
let scopedLogger: ScopedLoggerImpl;
11+
12+
beforeEach(() => {
13+
mockLogger = new LoggerMockImpl();
14+
15+
scopedLogger = new ScopedLoggerImpl({
16+
scope: 'test-scope-from-logger',
17+
delegate: mockLogger,
18+
});
19+
});
20+
21+
describe('#log', () => {
22+
it('should log with the correct scope', () => {
23+
scopedLogger.log({
24+
level: LogLevel.INFO,
25+
message: 'test-message',
26+
});
27+
28+
expect(mockLogger.log).toHaveBeenCalledWith({
29+
level: LogLevel.INFO,
30+
message: 'test-message',
31+
data: {
32+
scope: 'test-scope-from-logger',
33+
},
34+
});
35+
});
36+
37+
it('should allow scope to be overwritten by data', () => {
38+
scopedLogger.log({
39+
level: LogLevel.INFO,
40+
message: 'test-message',
41+
data: {
42+
scope: 'test-scope-from-data',
43+
foo: 'bar',
44+
},
45+
});
46+
47+
expect(mockLogger.log).toHaveBeenCalledWith({
48+
level: LogLevel.INFO,
49+
message: 'test-message',
50+
data: {
51+
scope: 'test-scope-from-data',
52+
foo: 'bar',
53+
},
54+
});
55+
});
56+
});
57+
58+
describe('#debug', () => {
59+
it('should delegate to the underlying logger', () => {
60+
scopedLogger.debug('test-debug');
61+
62+
expect(mockLogger.log).toHaveBeenCalledWith(
63+
expect.objectContaining({
64+
level: LogLevel.DEBUG,
65+
message: 'test-debug',
66+
data: {
67+
scope: 'test-scope-from-logger',
68+
},
69+
})
70+
);
71+
});
72+
});
73+
74+
describe('#info', () => {
75+
it('should delegate to the underlying logger', () => {
76+
scopedLogger.info('test-info');
77+
78+
expect(mockLogger.log).toHaveBeenCalledWith(
79+
expect.objectContaining({
80+
level: LogLevel.INFO,
81+
message: 'test-info',
82+
data: {
83+
scope: 'test-scope-from-logger',
84+
},
85+
})
86+
);
87+
});
88+
});
89+
90+
describe('#warn', () => {
91+
it('should delegate to the underlying logger', () => {
92+
scopedLogger.warn('test-warn');
93+
94+
expect(mockLogger.log).toHaveBeenCalledWith(
95+
expect.objectContaining({
96+
level: LogLevel.WARN,
97+
message: 'test-warn',
98+
data: {
99+
scope: 'test-scope-from-logger',
100+
},
101+
})
102+
);
103+
});
104+
});
105+
106+
describe('#error', () => {
107+
it('should delegate to the underlying logger', () => {
108+
scopedLogger.error('test-error');
109+
110+
expect(mockLogger.log).toHaveBeenCalledWith(
111+
expect.objectContaining({
112+
level: LogLevel.ERROR,
113+
message: 'test-error',
114+
data: {
115+
scope: 'test-scope-from-logger',
116+
},
117+
})
118+
);
119+
});
120+
});
5121
});
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { AbstractLogger } from './abstract-logger.js';
2+
import type { LogData, LogLevel, Logger } from './types.js';
3+
4+
/**
5+
* Decorates a logger to set the same scope for all log messages.
6+
* You can continue to customize the scope on a per-message basis
7+
* by setting the `scope` property in the `data` argument.
8+
*/
9+
export class ScopedLoggerImpl extends AbstractLogger {
10+
private scope: string;
11+
private delegate: Logger;
12+
13+
constructor(options: { scope: string; delegate: Logger }) {
14+
super();
15+
this.scope = options.scope;
16+
this.delegate = options.delegate;
17+
}
18+
19+
public override log(options: {
20+
level: LogLevel;
21+
message: string;
22+
data?: LogData;
23+
}): void {
24+
const { level, message, data } = options;
25+
26+
this.delegate.log({
27+
level,
28+
message,
29+
data: {
30+
scope: this.scope,
31+
...data,
32+
},
33+
});
34+
}
35+
}

0 commit comments

Comments
 (0)