@monochromatic-dev/module-logger 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSES/GPL-3.0-or-later.txt +674 -0
- package/LICENSES/LGPL-3.0-or-later.txt +165 -0
- package/README.md +404 -0
- package/dist/final/neutral/index.d.mts +673 -0
- package/dist/final/neutral/index.mjs +3 -0
- package/dist/final/neutral/rolldown-runtime-5duEfhBv.mjs +1 -0
- package/dist/final/node/index.d.mts +673 -0
- package/dist/final/node/index.mjs +3 -0
- package/dist/final/node/rolldown-runtime-5duEfhBv.mjs +1 -0
- package/package.json +43 -0
- package/src/create-logger.ts +494 -0
- package/src/create-logger.unit.test.ts +752 -0
- package/src/error-format.ts +43 -0
- package/src/index.ts +35 -0
- package/src/logger.ts +67 -0
- package/src/logger.unit.test.ts +190 -0
- package/src/sink/console-control-chars.ts +140 -0
- package/src/sink/console-control-chars.unit.test.ts +206 -0
- package/src/sink/console.ts +531 -0
- package/src/sink/console.unit.test.ts +542 -0
- package/src/sink/file.ts +297 -0
- package/src/sink/file.unit.test.ts +202 -0
- package/src/sink/index.ts +11 -0
- package/src/sink/indexed-db-util.ts +96 -0
- package/src/sink/indexed-db.browser.test.ts +184 -0
- package/src/sink/indexed-db.ts +324 -0
- package/src/sink/indexed-db.unit.test.ts +80 -0
- package/src/sink/local-storage-key.ts +176 -0
- package/src/sink/local-storage-key.unit.test.ts +106 -0
- package/src/sink/local-storage-quota.ts +60 -0
- package/src/sink/local-storage-quota.unit.test.ts +98 -0
- package/src/sink/local-storage-store.ts +368 -0
- package/src/sink/local-storage-store.unit.test.ts +329 -0
- package/src/sink/local-storage.browser.test.ts +125 -0
- package/src/sink/local-storage.ts +182 -0
- package/src/sink/local-storage.unit.test.ts +218 -0
- package/src/sink/noop.ts +46 -0
- package/src/sink/noop.unit.test.ts +47 -0
- package/src/sink/opfs.browser.test.ts +84 -0
- package/src/sink/opfs.ts +212 -0
- package/src/sink/opfs.unit.test.ts +81 -0
- package/src/sink/record-buffer.ts +230 -0
- package/src/sink/record-buffer.unit.test.ts +288 -0
- package/src/sink/session-storage-quota.ts +57 -0
- package/src/sink/session-storage-quota.unit.test.ts +98 -0
- package/src/sink/session-storage-store.ts +178 -0
- package/src/sink/session-storage.browser.test.ts +137 -0
- package/src/sink/session-storage.ts +128 -0
- package/src/sink/session-storage.unit.test.ts +527 -0
- package/src/sink/web-storage-quota-error.ts +43 -0
- package/src/sink/web-storage-quota-error.unit.test.ts +55 -0
- package/src/sink/web-storage-runtime.ts +49 -0
- package/src/startup.unit.test.ts +232 -0
- package/src/tagged.ts +74 -0
- package/src/tagged.unit.test.ts +211 -0
- package/src/types.ts +78 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
it,
|
|
5
|
+
} from '@monochromatic-dev/module-test/ts';
|
|
6
|
+
import {
|
|
7
|
+
sinks,
|
|
8
|
+
type LogRecord,
|
|
9
|
+
} from '@monochromatic-dev/module-logger';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sink factories under test, read from the built artifact's `sinks` namespace.
|
|
13
|
+
*/
|
|
14
|
+
const {
|
|
15
|
+
createConsoleSink,
|
|
16
|
+
} = sinks;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Awaits two microtask hops so any pending `queueMicrotask(flushBuffer)`
|
|
20
|
+
* has definitely fired and the buffer is drained before the next assertion.
|
|
21
|
+
* One hop would often be enough, but two is cheap insurance against timing
|
|
22
|
+
* skew from the harness itself.
|
|
23
|
+
*/
|
|
24
|
+
async function waitForFlush(): Promise<void> {
|
|
25
|
+
await Promise.resolve();
|
|
26
|
+
await Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Builds a `LogRecord` with a fixed timestamp so the formatted output
|
|
31
|
+
* is stable across test runs.
|
|
32
|
+
*
|
|
33
|
+
* @param level - Severity level.
|
|
34
|
+
*
|
|
35
|
+
* @param message - Message body.
|
|
36
|
+
*
|
|
37
|
+
* @returns A complete `LogRecord`.
|
|
38
|
+
*/
|
|
39
|
+
function record(
|
|
40
|
+
{
|
|
41
|
+
level,
|
|
42
|
+
message,
|
|
43
|
+
}: {
|
|
44
|
+
readonly level: LogRecord['level'];
|
|
45
|
+
readonly message: string;
|
|
46
|
+
},
|
|
47
|
+
): LogRecord {
|
|
48
|
+
return {
|
|
49
|
+
level,
|
|
50
|
+
message,
|
|
51
|
+
timestamp: 0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Appends `--verbose` to `process.argv` and removes it again when the returned
|
|
57
|
+
* value goes out of `using` scope, so a verbose-detection test cannot leak the
|
|
58
|
+
* flag into the sibling test that asserts the silenced default.
|
|
59
|
+
*
|
|
60
|
+
* @returns Disposable that restores `process.argv` on scope exit.
|
|
61
|
+
*/
|
|
62
|
+
function withVerboseArgv(): Disposable {
|
|
63
|
+
process.argv
|
|
64
|
+
.push('--verbose',);
|
|
65
|
+
return {
|
|
66
|
+
[Symbol.dispose](): void {
|
|
67
|
+
// Reassign to a flag-free copy rather than splice by index: the test
|
|
68
|
+
// environment carries no `--verbose` of its own, so filtering restores
|
|
69
|
+
// the original vector and dodges the conflicting index-check lint rules.
|
|
70
|
+
process.argv = process.argv
|
|
71
|
+
.filter(function keepNonVerbose(arg,) {
|
|
72
|
+
return arg !== '--verbose';
|
|
73
|
+
},);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await describe({
|
|
79
|
+
name: 'console sink (microtask-batched)',
|
|
80
|
+
// Sequential because every test spies a global (`console.*`); concurrent
|
|
81
|
+
// runs would clobber each other's spies. Sink state is now per-instance
|
|
82
|
+
// (each test builds its own `createConsoleSink()`), so no reset hook or
|
|
83
|
+
// shared-buffer coordination is needed.
|
|
84
|
+
concurrency: 1,
|
|
85
|
+
children: [
|
|
86
|
+
it({
|
|
87
|
+
name: 'verify resolves true in a test environment',
|
|
88
|
+
fn: async () => {
|
|
89
|
+
const sink = createConsoleSink();
|
|
90
|
+
expect(await sink.verify(),)
|
|
91
|
+
.toBe(true,);
|
|
92
|
+
},
|
|
93
|
+
},),
|
|
94
|
+
|
|
95
|
+
it({
|
|
96
|
+
name: 'single write defers until next microtask',
|
|
97
|
+
fn: async ({ sinon, },) => {
|
|
98
|
+
process.env.MONOCHROMATIC_VERBOSE = 'true';
|
|
99
|
+
const sink = createConsoleSink();
|
|
100
|
+
const spy = sinon.spy(
|
|
101
|
+
console,
|
|
102
|
+
'info',
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
void sink.write(record({
|
|
106
|
+
level: 'info',
|
|
107
|
+
message: 'deferred',
|
|
108
|
+
},),);
|
|
109
|
+
expect(spy.callCount,)
|
|
110
|
+
.toBe(0,);
|
|
111
|
+
|
|
112
|
+
await waitForFlush();
|
|
113
|
+
expect(spy.callCount,)
|
|
114
|
+
.toBe(1,);
|
|
115
|
+
},
|
|
116
|
+
},),
|
|
117
|
+
|
|
118
|
+
it({
|
|
119
|
+
// Regression: MONOCHROMATIC_WARN=false drops warn records so machine-protocol consumers
|
|
120
|
+
// (such as the toml-edit conformance codec) keep their output clean.
|
|
121
|
+
name: 'MONOCHROMATIC_WARN=false suppresses warn records',
|
|
122
|
+
fn: async ({ sinon, },) => {
|
|
123
|
+
process.env.MONOCHROMATIC_WARN = 'false';
|
|
124
|
+
const sink = createConsoleSink();
|
|
125
|
+
const spy = sinon.spy(
|
|
126
|
+
console,
|
|
127
|
+
'warn',
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
void sink.write(record({
|
|
131
|
+
level: 'warn',
|
|
132
|
+
message: 'hushed',
|
|
133
|
+
},),);
|
|
134
|
+
|
|
135
|
+
await waitForFlush();
|
|
136
|
+
expect(spy.callCount,)
|
|
137
|
+
.toBe(0,);
|
|
138
|
+
Reflect.deleteProperty(
|
|
139
|
+
process.env,
|
|
140
|
+
'MONOCHROMATIC_WARN',
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
},),
|
|
144
|
+
|
|
145
|
+
it({
|
|
146
|
+
name: 'warn records emit when MONOCHROMATIC_WARN is unset',
|
|
147
|
+
fn: async ({ sinon, },) => {
|
|
148
|
+
Reflect.deleteProperty(
|
|
149
|
+
process.env,
|
|
150
|
+
'MONOCHROMATIC_WARN',
|
|
151
|
+
);
|
|
152
|
+
const sink = createConsoleSink();
|
|
153
|
+
const spy = sinon.spy(
|
|
154
|
+
console,
|
|
155
|
+
'warn',
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
void sink.write(record({
|
|
159
|
+
level: 'warn',
|
|
160
|
+
message: 'audible',
|
|
161
|
+
},),);
|
|
162
|
+
|
|
163
|
+
await waitForFlush();
|
|
164
|
+
expect(spy.callCount,)
|
|
165
|
+
.toBe(1,);
|
|
166
|
+
},
|
|
167
|
+
},),
|
|
168
|
+
|
|
169
|
+
it({
|
|
170
|
+
name: 'contiguous same-level runs collapse to one console call',
|
|
171
|
+
fn: async ({ sinon, },) => {
|
|
172
|
+
process.env.MONOCHROMATIC_VERBOSE = 'true';
|
|
173
|
+
const sink = createConsoleSink();
|
|
174
|
+
const spy = sinon.spy(
|
|
175
|
+
console,
|
|
176
|
+
'info',
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
void sink.write(record({
|
|
180
|
+
level: 'info',
|
|
181
|
+
message: 'first',
|
|
182
|
+
},),);
|
|
183
|
+
void sink.write(record({
|
|
184
|
+
level: 'info',
|
|
185
|
+
message: 'second',
|
|
186
|
+
},),);
|
|
187
|
+
|
|
188
|
+
await waitForFlush();
|
|
189
|
+
expect(spy.callCount,)
|
|
190
|
+
.toBe(1,);
|
|
191
|
+
const emitted = spy.firstCall.args[0] as string;
|
|
192
|
+
expect(emitted.split('\n',).length,)
|
|
193
|
+
.toBe(2,);
|
|
194
|
+
expect(emitted,)
|
|
195
|
+
.toContain('first',);
|
|
196
|
+
expect(emitted,)
|
|
197
|
+
.toContain('second',);
|
|
198
|
+
},
|
|
199
|
+
},),
|
|
200
|
+
|
|
201
|
+
it({
|
|
202
|
+
name: 'debug writes to process stderr when process stderr is available',
|
|
203
|
+
fn: async ({ sinon, },) => {
|
|
204
|
+
process.env.MONOCHROMATIC_VERBOSE = 'true';
|
|
205
|
+
const sink = createConsoleSink();
|
|
206
|
+
const stderrSpy = sinon.stub(
|
|
207
|
+
process.stderr,
|
|
208
|
+
'write',
|
|
209
|
+
);
|
|
210
|
+
const consoleDebugSpy = sinon.stub(
|
|
211
|
+
console,
|
|
212
|
+
'debug',
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
void sink.write(record({
|
|
216
|
+
level: 'debug',
|
|
217
|
+
message: 'shown',
|
|
218
|
+
},),);
|
|
219
|
+
await waitForFlush();
|
|
220
|
+
|
|
221
|
+
expect(stderrSpy.callCount,)
|
|
222
|
+
.toBe(1,);
|
|
223
|
+
expect(consoleDebugSpy.callCount,)
|
|
224
|
+
.toBe(0,);
|
|
225
|
+
const emitted = stderrSpy.firstCall.args[0] as string;
|
|
226
|
+
expect(emitted,)
|
|
227
|
+
.toBe('[debug] [1970-01-01T00:00:00.000Z] shown\n',);
|
|
228
|
+
},
|
|
229
|
+
},),
|
|
230
|
+
|
|
231
|
+
it({
|
|
232
|
+
name: 'level transitions split into separate console calls',
|
|
233
|
+
fn: async ({ sinon, },) => {
|
|
234
|
+
process.env.MONOCHROMATIC_VERBOSE = 'true';
|
|
235
|
+
const sink = createConsoleSink();
|
|
236
|
+
const stderrSpy = sinon.stub(
|
|
237
|
+
process.stderr,
|
|
238
|
+
'write',
|
|
239
|
+
);
|
|
240
|
+
const consoleDebugSpy = sinon.stub(
|
|
241
|
+
console,
|
|
242
|
+
'debug',
|
|
243
|
+
);
|
|
244
|
+
const warnSpy = sinon.stub(
|
|
245
|
+
console,
|
|
246
|
+
'warn',
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
void sink.write(record({
|
|
250
|
+
level: 'debug',
|
|
251
|
+
message: 'd1',
|
|
252
|
+
},),);
|
|
253
|
+
void sink.write(record({
|
|
254
|
+
level: 'debug',
|
|
255
|
+
message: 'd2',
|
|
256
|
+
},),);
|
|
257
|
+
void sink.write(record({
|
|
258
|
+
level: 'warn',
|
|
259
|
+
message: 'w1',
|
|
260
|
+
},),);
|
|
261
|
+
void sink.write(record({
|
|
262
|
+
level: 'debug',
|
|
263
|
+
message: 'd3',
|
|
264
|
+
},),);
|
|
265
|
+
|
|
266
|
+
await waitForFlush();
|
|
267
|
+
expect(stderrSpy.callCount,)
|
|
268
|
+
.toBe(2,);
|
|
269
|
+
expect(consoleDebugSpy.callCount,)
|
|
270
|
+
.toBe(0,);
|
|
271
|
+
expect(warnSpy.callCount,)
|
|
272
|
+
.toBe(1,);
|
|
273
|
+
const firstDebug = stderrSpy.firstCall.args[0] as string;
|
|
274
|
+
const secondDebug = stderrSpy.secondCall.args[0] as string;
|
|
275
|
+
expect(firstDebug.split('\n',).length,)
|
|
276
|
+
.toBe(3,);
|
|
277
|
+
expect(secondDebug.split('\n',).length,)
|
|
278
|
+
.toBe(2,);
|
|
279
|
+
},
|
|
280
|
+
},),
|
|
281
|
+
|
|
282
|
+
it({
|
|
283
|
+
name: 'formats each record as [level] [iso] message',
|
|
284
|
+
fn: async ({ sinon, },) => {
|
|
285
|
+
process.env.MONOCHROMATIC_VERBOSE = 'true';
|
|
286
|
+
const sink = createConsoleSink();
|
|
287
|
+
const spy = sinon.spy(
|
|
288
|
+
console,
|
|
289
|
+
'info',
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
void sink.write(record({
|
|
293
|
+
level: 'info',
|
|
294
|
+
message: 'hi',
|
|
295
|
+
},),);
|
|
296
|
+
await waitForFlush();
|
|
297
|
+
|
|
298
|
+
const emitted = spy.firstCall.args[0] as string;
|
|
299
|
+
expect(emitted,)
|
|
300
|
+
.toBe('[info] [1970-01-01T00:00:00.000Z] hi',);
|
|
301
|
+
},
|
|
302
|
+
},),
|
|
303
|
+
|
|
304
|
+
it({
|
|
305
|
+
name: 'neutralizes terminal control characters before they reach console',
|
|
306
|
+
fn: async ({ sinon, },) => {
|
|
307
|
+
process.env.MONOCHROMATIC_VERBOSE = 'true';
|
|
308
|
+
const sink = createConsoleSink();
|
|
309
|
+
const spy = sinon.spy(
|
|
310
|
+
console,
|
|
311
|
+
'info',
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
void sink.write(record({
|
|
315
|
+
level: 'info',
|
|
316
|
+
message: 'title:\u001B]0;PWNED\u0007 clear:\u001B[2J\n\tkept',
|
|
317
|
+
},),);
|
|
318
|
+
await waitForFlush();
|
|
319
|
+
|
|
320
|
+
const emitted = spy.firstCall.args[0] as string;
|
|
321
|
+
expect(emitted,)
|
|
322
|
+
.toBe('[info] [1970-01-01T00:00:00.000Z] title:\\u001B]0;PWNED\\u0007 clear:\\u001B[2J\n\tkept',);
|
|
323
|
+
expect(emitted,)
|
|
324
|
+
.not
|
|
325
|
+
.toContain('\u001B',);
|
|
326
|
+
},
|
|
327
|
+
},),
|
|
328
|
+
|
|
329
|
+
it({
|
|
330
|
+
name: 'each level routes to its mapped output channel',
|
|
331
|
+
fn: async ({ sinon, },) => {
|
|
332
|
+
process.env.MONOCHROMATIC_VERBOSE = 'true';
|
|
333
|
+
const sink = createConsoleSink();
|
|
334
|
+
// Stub (not spy) console.trace: Node's Console.prototype.trace
|
|
335
|
+
// internally calls this.error(stack), so a spied trace would delegate
|
|
336
|
+
// into the spied console.error and inflate the error count by one.
|
|
337
|
+
// The stub still records the routing call (callCount) without that
|
|
338
|
+
// delegation, isolating the sink's level-to-channel mapping from Node
|
|
339
|
+
// console internals.
|
|
340
|
+
const traceSpy = sinon.stub(
|
|
341
|
+
console,
|
|
342
|
+
'trace',
|
|
343
|
+
);
|
|
344
|
+
const stderrSpy = sinon.stub(
|
|
345
|
+
process.stderr,
|
|
346
|
+
'write',
|
|
347
|
+
);
|
|
348
|
+
const consoleDebugSpy = sinon.stub(
|
|
349
|
+
console,
|
|
350
|
+
'debug',
|
|
351
|
+
);
|
|
352
|
+
const infoSpy = sinon.stub(
|
|
353
|
+
console,
|
|
354
|
+
'info',
|
|
355
|
+
);
|
|
356
|
+
const warnSpy = sinon.stub(
|
|
357
|
+
console,
|
|
358
|
+
'warn',
|
|
359
|
+
);
|
|
360
|
+
const errorSpy = sinon.stub(
|
|
361
|
+
console,
|
|
362
|
+
'error',
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
void sink.write(record({
|
|
366
|
+
level: 'trace',
|
|
367
|
+
message: 't',
|
|
368
|
+
},),);
|
|
369
|
+
await waitForFlush();
|
|
370
|
+
void sink.write(record({
|
|
371
|
+
level: 'debug',
|
|
372
|
+
message: 'd',
|
|
373
|
+
},),);
|
|
374
|
+
await waitForFlush();
|
|
375
|
+
void sink.write(record({
|
|
376
|
+
level: 'info',
|
|
377
|
+
message: 'i',
|
|
378
|
+
},),);
|
|
379
|
+
await waitForFlush();
|
|
380
|
+
void sink.write(record({
|
|
381
|
+
level: 'warn',
|
|
382
|
+
message: 'w',
|
|
383
|
+
},),);
|
|
384
|
+
await waitForFlush();
|
|
385
|
+
void sink.write(record({
|
|
386
|
+
level: 'error',
|
|
387
|
+
message: 'e',
|
|
388
|
+
},),);
|
|
389
|
+
await waitForFlush();
|
|
390
|
+
void sink.write(record({
|
|
391
|
+
level: 'fatal',
|
|
392
|
+
message: 'f',
|
|
393
|
+
},),);
|
|
394
|
+
await waitForFlush();
|
|
395
|
+
|
|
396
|
+
// Assert the whole count map at once: a future drift reports which
|
|
397
|
+
// channel's count changed (e.g. {error: 3} vs {error: 2}) instead of a
|
|
398
|
+
// bare "expected 3 to equal 2". error is 2 because console.error backs
|
|
399
|
+
// both 'error' and 'fatal' (fatal shares the error method). Keys are
|
|
400
|
+
// alphabetized to satisfy oxlint sort-keys.
|
|
401
|
+
expect({
|
|
402
|
+
debugConsole: consoleDebugSpy.callCount,
|
|
403
|
+
debugStderr: stderrSpy.callCount,
|
|
404
|
+
error: errorSpy.callCount,
|
|
405
|
+
info: infoSpy.callCount,
|
|
406
|
+
trace: traceSpy.callCount,
|
|
407
|
+
warn: warnSpy.callCount,
|
|
408
|
+
},)
|
|
409
|
+
.toEqual({
|
|
410
|
+
debugConsole: 0,
|
|
411
|
+
debugStderr: 1,
|
|
412
|
+
error: 2,
|
|
413
|
+
info: 1,
|
|
414
|
+
trace: 1,
|
|
415
|
+
warn: 1,
|
|
416
|
+
},);
|
|
417
|
+
},
|
|
418
|
+
},),
|
|
419
|
+
|
|
420
|
+
it({
|
|
421
|
+
name: 'silent gating drops debug when verbose is off',
|
|
422
|
+
fn: async ({ sinon, },) => {
|
|
423
|
+
delete process.env.MONOCHROMATIC_VERBOSE;
|
|
424
|
+
// A fresh sink recomputes verbose on its first write, so clearing
|
|
425
|
+
// MONOCHROMATIC_VERBOSE here yields verbose=false (process.argv has no --verbose in a
|
|
426
|
+
// normal test run, and 'window' is not in globalThis under Node).
|
|
427
|
+
const sink = createConsoleSink();
|
|
428
|
+
const stderrSpy = sinon.stub(
|
|
429
|
+
process.stderr,
|
|
430
|
+
'write',
|
|
431
|
+
);
|
|
432
|
+
const traceSpy = sinon.spy(
|
|
433
|
+
console,
|
|
434
|
+
'trace',
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
void sink.write(record({
|
|
438
|
+
level: 'debug',
|
|
439
|
+
message: 'hidden',
|
|
440
|
+
},),);
|
|
441
|
+
void sink.write(record({
|
|
442
|
+
level: 'trace',
|
|
443
|
+
message: 'hidden',
|
|
444
|
+
},),);
|
|
445
|
+
|
|
446
|
+
await waitForFlush();
|
|
447
|
+
expect(stderrSpy.callCount,)
|
|
448
|
+
.toBe(0,);
|
|
449
|
+
expect(traceSpy.callCount,)
|
|
450
|
+
.toBe(0,);
|
|
451
|
+
},
|
|
452
|
+
},),
|
|
453
|
+
|
|
454
|
+
it({
|
|
455
|
+
name: '--verbose in process.argv enables debug output without MONOCHROMATIC_VERBOSE',
|
|
456
|
+
fn: async ({ sinon, },) => {
|
|
457
|
+
delete process.env.MONOCHROMATIC_VERBOSE;
|
|
458
|
+
// A fresh sink recomputes verbose on its first write; with MONOCHROMATIC_VERBOSE unset
|
|
459
|
+
// and no browser `window`, the --verbose argv flag is the sole trigger.
|
|
460
|
+
using _restoreArgv = withVerboseArgv();
|
|
461
|
+
const sink = createConsoleSink();
|
|
462
|
+
const spy = sinon.stub(
|
|
463
|
+
process.stderr,
|
|
464
|
+
'write',
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
void sink.write(record({
|
|
468
|
+
level: 'debug',
|
|
469
|
+
message: 'shown',
|
|
470
|
+
},),);
|
|
471
|
+
await waitForFlush();
|
|
472
|
+
|
|
473
|
+
expect(spy.callCount,)
|
|
474
|
+
.toBe(1,);
|
|
475
|
+
},
|
|
476
|
+
},),
|
|
477
|
+
|
|
478
|
+
it({
|
|
479
|
+
name: 'cross-microtask writes produce separate console calls',
|
|
480
|
+
fn: async ({ sinon, },) => {
|
|
481
|
+
process.env.MONOCHROMATIC_VERBOSE = 'true';
|
|
482
|
+
const sink = createConsoleSink();
|
|
483
|
+
const spy = sinon.spy(
|
|
484
|
+
console,
|
|
485
|
+
'info',
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
void sink.write(record({
|
|
489
|
+
level: 'info',
|
|
490
|
+
message: 'first',
|
|
491
|
+
},),);
|
|
492
|
+
await waitForFlush();
|
|
493
|
+
void sink.write(record({
|
|
494
|
+
level: 'info',
|
|
495
|
+
message: 'second',
|
|
496
|
+
},),);
|
|
497
|
+
await waitForFlush();
|
|
498
|
+
|
|
499
|
+
expect(spy.callCount,)
|
|
500
|
+
.toBe(2,);
|
|
501
|
+
},
|
|
502
|
+
},),
|
|
503
|
+
|
|
504
|
+
it({
|
|
505
|
+
name: 'flush() drains synchronously before the await resolves',
|
|
506
|
+
fn: async ({ sinon, },) => {
|
|
507
|
+
process.env.MONOCHROMATIC_VERBOSE = 'true';
|
|
508
|
+
const sink = createConsoleSink();
|
|
509
|
+
const spy = sinon.spy(
|
|
510
|
+
console,
|
|
511
|
+
'info',
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
void sink.write(record({
|
|
515
|
+
level: 'info',
|
|
516
|
+
message: 'force-drain',
|
|
517
|
+
},),);
|
|
518
|
+
expect(spy.callCount,)
|
|
519
|
+
.toBe(0,);
|
|
520
|
+
|
|
521
|
+
await sink.flush?.();
|
|
522
|
+
expect(spy.callCount,)
|
|
523
|
+
.toBe(1,);
|
|
524
|
+
},
|
|
525
|
+
},),
|
|
526
|
+
|
|
527
|
+
it({
|
|
528
|
+
name: 'flush() on empty buffer resolves without emitting',
|
|
529
|
+
fn: async ({ sinon, },) => {
|
|
530
|
+
const sink = createConsoleSink();
|
|
531
|
+
const spy = sinon.spy(
|
|
532
|
+
console,
|
|
533
|
+
'info',
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
await sink.flush?.();
|
|
537
|
+
expect(spy.callCount,)
|
|
538
|
+
.toBe(0,);
|
|
539
|
+
},
|
|
540
|
+
},),
|
|
541
|
+
],
|
|
542
|
+
},);
|