vitest 3.0.0-beta.3 → 3.0.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.
Files changed (54) hide show
  1. package/LICENSE.md +1 -315
  2. package/config.d.ts +2 -0
  3. package/dist/browser.d.ts +3 -3
  4. package/dist/browser.js +1 -1
  5. package/dist/chunks/{base.CQ2VEtuH.js → base.CUDzyU2J.js} +2 -2
  6. package/dist/chunks/{cac.e7qW4xLT.js → cac.DZC9WjGM.js} +8 -8
  7. package/dist/chunks/{cli-api.CWDlED-m.js → cli-api.CmJw5Cd_.js} +920 -84
  8. package/dist/chunks/{config.BTPBhmK5.d.ts → config.BRtC-JeT.d.ts} +6 -0
  9. package/dist/chunks/{console.BYGVloWk.js → console.CN7AiMGV.js} +16 -7
  10. package/dist/chunks/{creator.Ot9GlSGw.js → creator.DztqrnyH.js} +1 -1
  11. package/dist/chunks/{execute.2pr0rHgK.js → execute.BMOaRArH.js} +27 -16
  12. package/dist/chunks/global.CnI8_G5V.d.ts +133 -0
  13. package/dist/chunks/{globals.BFncSRNA.js → globals.C5RQxaV3.js} +2 -2
  14. package/dist/chunks/{index.CkWmZCXU.js → index.BQbxGbG9.js} +1 -1
  15. package/dist/chunks/{index.BBoOXW-l.js → index.CUcwvygK.js} +5 -5
  16. package/dist/chunks/{index.DQboAxJm.js → index.D9C26wCk.js} +1 -0
  17. package/dist/chunks/index.TKSL1HjN.js +2460 -0
  18. package/dist/chunks/{reporters.DCiyjXOg.d.ts → reporters.Y8BYiXBN.d.ts} +412 -386
  19. package/dist/chunks/{resolveConfig.C1d7TK-U.js → resolveConfig.CSLLD33d.js} +140 -55
  20. package/dist/chunks/{rpc.C3q9uwRX.js → rpc.TVf73xOu.js} +0 -1
  21. package/dist/chunks/{runBaseTests.qNWRkgHj.js → runBaseTests.C0T_TQwH.js} +9 -7
  22. package/dist/chunks/{setup-common.Cp_bu5q3.js → setup-common.D0zLenuv.js} +1 -1
  23. package/dist/chunks/{RandomSequencer.C6x84bNN.js → typechecker.BJMkWMXo.js} +84 -108
  24. package/dist/chunks/{utils.Coei4Wlj.js → utils.DJWL04yX.js} +9 -20
  25. package/dist/chunks/{vi.S4Fq8wSo.js → vi.Da_PT3Vw.js} +554 -272
  26. package/dist/chunks/{vite.CRSMFy31.d.ts → vite.CQ0dHgkN.d.ts} +1 -1
  27. package/dist/chunks/{vm.DGhTouO3.js → vm.DrFVeTXo.js} +4 -4
  28. package/dist/chunks/{worker.R-PA7DpW.d.ts → worker.B1y96qmv.d.ts} +1 -1
  29. package/dist/chunks/{worker.XbtCXEXv.d.ts → worker.CIpff8Eg.d.ts} +3 -5
  30. package/dist/cli.js +1 -1
  31. package/dist/config.d.ts +4 -4
  32. package/dist/coverage.d.ts +2 -2
  33. package/dist/coverage.js +5 -4
  34. package/dist/execute.d.ts +3 -3
  35. package/dist/execute.js +1 -1
  36. package/dist/index.d.ts +18 -119
  37. package/dist/index.js +2 -2
  38. package/dist/node.d.ts +12 -9
  39. package/dist/node.js +25 -24
  40. package/dist/reporters.d.ts +2 -2
  41. package/dist/reporters.js +4 -10
  42. package/dist/runners.d.ts +2 -1
  43. package/dist/runners.js +9 -16
  44. package/dist/worker.js +1 -1
  45. package/dist/workers/forks.js +2 -2
  46. package/dist/workers/runVmTests.js +8 -6
  47. package/dist/workers/threads.js +2 -2
  48. package/dist/workers/vmForks.js +3 -3
  49. package/dist/workers/vmThreads.js +3 -3
  50. package/dist/workers.d.ts +3 -3
  51. package/dist/workers.js +5 -5
  52. package/package.json +17 -19
  53. package/dist/chunks/index.CzkCSFCy.js +0 -5455
  54. package/dist/chunks/types.BOjykUpq.d.ts +0 -27
@@ -0,0 +1,2460 @@
1
+ import fs, { existsSync, readFileSync, promises } from 'node:fs';
2
+ import { getTests, getTestName, hasFailed, getFullName, getSuites, getTasks } from '@vitest/runner/utils';
3
+ import * as pathe from 'pathe';
4
+ import { relative, normalize, resolve, dirname } from 'pathe';
5
+ import c from 'tinyrainbow';
6
+ import { d as divider, F as F_POINTER, t as truncateString, f as formatTimeString, g as getStateSymbol, a as formatProjectName, b as taskFail, c as F_RIGHT, e as F_CHECK, w as withLabel, r as renderSnapshotSummary, p as padSummaryTitle, h as getStateString$1, i as formatTime, j as countTestErrors, k as F_TREE_NODE_END, l as F_TREE_NODE_MIDDLE } from './utils.DJWL04yX.js';
7
+ import { stripVTControlCharacters } from 'node:util';
8
+ import { isPrimitive, inspect, positionToOffset, lineSplitRE, toArray, notNullish } from '@vitest/utils';
9
+ import { performance as performance$1 } from 'node:perf_hooks';
10
+ import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
11
+ import { T as TypeCheckError, g as getOutputFile, i as isTTY, h as hasFailedSnapshot } from './typechecker.BJMkWMXo.js';
12
+ import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
13
+ import { Console } from 'node:console';
14
+ import { Writable } from 'node:stream';
15
+ import { createRequire } from 'node:module';
16
+ import { hostname } from 'node:os';
17
+
18
+ /// <reference types="../types/index.d.ts" />
19
+
20
+ // (c) 2020-present Andrea Giammarchi
21
+
22
+ const {parse: $parse, stringify: $stringify} = JSON;
23
+ const {keys} = Object;
24
+
25
+ const Primitive = String; // it could be Number
26
+ const primitive = 'string'; // it could be 'number'
27
+
28
+ const ignore = {};
29
+ const object = 'object';
30
+
31
+ const noop = (_, value) => value;
32
+
33
+ const primitives = value => (
34
+ value instanceof Primitive ? Primitive(value) : value
35
+ );
36
+
37
+ const Primitives = (_, value) => (
38
+ typeof value === primitive ? new Primitive(value) : value
39
+ );
40
+
41
+ const revive = (input, parsed, output, $) => {
42
+ const lazy = [];
43
+ for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
44
+ const k = ke[y];
45
+ const value = output[k];
46
+ if (value instanceof Primitive) {
47
+ const tmp = input[value];
48
+ if (typeof tmp === object && !parsed.has(tmp)) {
49
+ parsed.add(tmp);
50
+ output[k] = ignore;
51
+ lazy.push({k, a: [input, parsed, tmp, $]});
52
+ }
53
+ else
54
+ output[k] = $.call(output, k, tmp);
55
+ }
56
+ else if (output[k] !== ignore)
57
+ output[k] = $.call(output, k, value);
58
+ }
59
+ for (let {length} = lazy, i = 0; i < length; i++) {
60
+ const {k, a} = lazy[i];
61
+ output[k] = $.call(output, k, revive.apply(null, a));
62
+ }
63
+ return output;
64
+ };
65
+
66
+ const set = (known, input, value) => {
67
+ const index = Primitive(input.push(value) - 1);
68
+ known.set(value, index);
69
+ return index;
70
+ };
71
+
72
+ /**
73
+ * Converts a specialized flatted string into a JS value.
74
+ * @param {string} text
75
+ * @param {(this: any, key: string, value: any) => any} [reviver]
76
+ * @returns {any}
77
+ */
78
+ const parse = (text, reviver) => {
79
+ const input = $parse(text, Primitives).map(primitives);
80
+ const value = input[0];
81
+ const $ = reviver || noop;
82
+ const tmp = typeof value === object && value ?
83
+ revive(input, new Set, value, $) :
84
+ value;
85
+ return $.call({'': tmp}, '', tmp);
86
+ };
87
+
88
+ /**
89
+ * Converts a JS value into a specialized flatted string.
90
+ * @param {any} value
91
+ * @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
92
+ * @param {string | number | undefined} [space]
93
+ * @returns {string}
94
+ */
95
+ const stringify = (value, replacer, space) => {
96
+ const $ = replacer && typeof replacer === object ?
97
+ (k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
98
+ (replacer || noop);
99
+ const known = new Map;
100
+ const input = [];
101
+ const output = [];
102
+ let i = +set(known, input, $.call({'': value}, '', value));
103
+ let firstRun = !i;
104
+ while (i < input.length) {
105
+ firstRun = true;
106
+ output[i] = $stringify(input[i++], replace, space);
107
+ }
108
+ return '[' + output.join(',') + ']';
109
+ function replace(key, value) {
110
+ if (firstRun) {
111
+ firstRun = !firstRun;
112
+ return value;
113
+ }
114
+ const after = $.call(this, key, value);
115
+ switch (typeof after) {
116
+ case object:
117
+ if (after === null) return after;
118
+ case primitive:
119
+ return known.get(after) || set(known, input, after);
120
+ }
121
+ return after;
122
+ }
123
+ };
124
+
125
+ function capturePrintError(error, ctx, options) {
126
+ let output = "";
127
+ const writable = new Writable({
128
+ write(chunk, _encoding, callback) {
129
+ output += String(chunk);
130
+ callback();
131
+ }
132
+ });
133
+ const console = new Console(writable);
134
+ const logger = {
135
+ error: console.error.bind(console),
136
+ highlight: ctx.logger.highlight.bind(ctx.logger)
137
+ };
138
+ const result = printError(error, ctx, logger, {
139
+ showCodeFrame: false,
140
+ ...options
141
+ });
142
+ return { nearest: result?.nearest, output };
143
+ }
144
+ function printError(error, ctx, logger, options) {
145
+ const project = options.project ?? ctx.coreWorkspaceProject ?? ctx.projects[0];
146
+ return printErrorInner(error, project, {
147
+ logger,
148
+ type: options.type,
149
+ showCodeFrame: options.showCodeFrame,
150
+ screenshotPaths: options.screenshotPaths,
151
+ printProperties: options.verbose,
152
+ parseErrorStacktrace(error2) {
153
+ if (options.task?.file.pool === "browser" && project.browser) {
154
+ return project.browser.parseErrorStacktrace(error2, {
155
+ ignoreStackEntries: options.fullStack ? [] : void 0
156
+ });
157
+ }
158
+ return parseErrorStacktrace(error2, {
159
+ frameFilter: project.config.onStackTrace,
160
+ ignoreStackEntries: options.fullStack ? [] : void 0
161
+ });
162
+ }
163
+ });
164
+ }
165
+ function printErrorInner(error, project, options) {
166
+ const { showCodeFrame = true, type, printProperties = true } = options;
167
+ const logger = options.logger;
168
+ let e = error;
169
+ if (isPrimitive(e)) {
170
+ e = {
171
+ message: String(error).split(/\n/g)[0],
172
+ stack: String(error)
173
+ };
174
+ }
175
+ if (!e) {
176
+ const error2 = new Error("unknown error");
177
+ e = {
178
+ message: e ?? error2.message,
179
+ stack: error2.stack
180
+ };
181
+ }
182
+ if (!project) {
183
+ printErrorMessage(e, logger);
184
+ return;
185
+ }
186
+ const stacks = options.parseErrorStacktrace(e);
187
+ const nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
188
+ try {
189
+ return project.server && project.getModuleById(stack.file) && existsSync(stack.file);
190
+ } catch {
191
+ return false;
192
+ }
193
+ });
194
+ if (type) {
195
+ printErrorType(type, project.ctx);
196
+ }
197
+ printErrorMessage(e, logger);
198
+ if (options.screenshotPaths?.length) {
199
+ const length = options.screenshotPaths.length;
200
+ logger.error(`
201
+ Failure screenshot${length > 1 ? "s" : ""}:`);
202
+ logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
203
+ if (!e.diff) {
204
+ logger.error();
205
+ }
206
+ }
207
+ if (e.codeFrame) {
208
+ logger.error(`${e.codeFrame}
209
+ `);
210
+ }
211
+ if ("__vitest_rollup_error__" in e) {
212
+ const err = e.__vitest_rollup_error__;
213
+ logger.error([
214
+ err.plugin && ` Plugin: ${c.magenta(err.plugin)}`,
215
+ err.id && ` File: ${c.cyan(err.id)}${err.loc ? `:${err.loc.line}:${err.loc.column}` : ""}`,
216
+ err.frame && c.yellow(err.frame.split(/\r?\n/g).map((l) => ` `.repeat(2) + l).join(`
217
+ `))
218
+ ].filter(Boolean).join("\n"));
219
+ }
220
+ if (e.diff) {
221
+ logger.error(`
222
+ ${e.diff}
223
+ `);
224
+ }
225
+ if (e.frame) {
226
+ logger.error(c.yellow(e.frame));
227
+ } else {
228
+ const errorProperties = printProperties ? getErrorProperties(e) : {};
229
+ printStack(logger, project, stacks, nearest, errorProperties, (s) => {
230
+ if (showCodeFrame && s === nearest && nearest) {
231
+ const sourceCode = readFileSync(nearest.file, "utf-8");
232
+ logger.error(
233
+ generateCodeFrame(
234
+ sourceCode.length > 1e5 ? sourceCode : logger.highlight(nearest.file, sourceCode),
235
+ 4,
236
+ s
237
+ )
238
+ );
239
+ }
240
+ });
241
+ }
242
+ const testPath = e.VITEST_TEST_PATH;
243
+ const testName = e.VITEST_TEST_NAME;
244
+ const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
245
+ if (testPath) {
246
+ logger.error(
247
+ c.red(
248
+ `This error originated in "${c.bold(
249
+ testPath
250
+ )}" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.`
251
+ )
252
+ );
253
+ }
254
+ if (testName) {
255
+ logger.error(
256
+ c.red(
257
+ `The latest test that might've caused the error is "${c.bold(
258
+ testName
259
+ )}". It might mean one of the following:
260
+ - The error was thrown, while Vitest was running this test.
261
+ - If the error occurred after the test had been completed, this was the last documented test before it was thrown.`
262
+ )
263
+ );
264
+ }
265
+ if (afterEnvTeardown) {
266
+ logger.error(
267
+ c.red(
268
+ "This error was caught after test environment was torn down. Make sure to cancel any running tasks before test finishes:\n- cancel timeouts using clearTimeout and clearInterval\n- wait for promises to resolve using the await keyword"
269
+ )
270
+ );
271
+ }
272
+ if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
273
+ e.cause.name = `Caused by: ${e.cause.name}`;
274
+ printErrorInner(e.cause, project, {
275
+ showCodeFrame: false,
276
+ logger: options.logger,
277
+ parseErrorStacktrace: options.parseErrorStacktrace
278
+ });
279
+ }
280
+ handleImportOutsideModuleError(e.stack || e.stackStr || "", logger);
281
+ return { nearest };
282
+ }
283
+ function printErrorType(type, ctx) {
284
+ ctx.logger.error(`
285
+ ${c.red(divider(c.bold(c.inverse(` ${type} `))))}`);
286
+ }
287
+ const skipErrorProperties = /* @__PURE__ */ new Set([
288
+ "nameStr",
289
+ "stack",
290
+ "cause",
291
+ "stacks",
292
+ "stackStr",
293
+ "type",
294
+ "showDiff",
295
+ "ok",
296
+ "operator",
297
+ "diff",
298
+ "codeFrame",
299
+ "actual",
300
+ "expected",
301
+ "diffOptions",
302
+ "VITEST_TEST_NAME",
303
+ "VITEST_TEST_PATH",
304
+ "VITEST_AFTER_ENV_TEARDOWN",
305
+ ...Object.getOwnPropertyNames(Error.prototype),
306
+ ...Object.getOwnPropertyNames(Object.prototype)
307
+ ]);
308
+ function getErrorProperties(e) {
309
+ const errorObject = /* @__PURE__ */ Object.create(null);
310
+ if (e.name === "AssertionError") {
311
+ return errorObject;
312
+ }
313
+ for (const key of Object.getOwnPropertyNames(e)) {
314
+ if (!skipErrorProperties.has(key)) {
315
+ errorObject[key] = e[key];
316
+ }
317
+ }
318
+ return errorObject;
319
+ }
320
+ const esmErrors = [
321
+ "Cannot use import statement outside a module",
322
+ "Unexpected token 'export'"
323
+ ];
324
+ function handleImportOutsideModuleError(stack, logger) {
325
+ if (!esmErrors.some((e) => stack.includes(e))) {
326
+ return;
327
+ }
328
+ const path = normalize(stack.split("\n")[0].trim());
329
+ let name = path.split("/node_modules/").pop() || "";
330
+ if (name?.startsWith("@")) {
331
+ name = name.split("/").slice(0, 2).join("/");
332
+ } else {
333
+ name = name.split("/")[0];
334
+ }
335
+ if (name) {
336
+ printModuleWarningForPackage(logger, path, name);
337
+ } else {
338
+ printModuleWarningForSourceCode(logger, path);
339
+ }
340
+ }
341
+ function printModuleWarningForPackage(logger, path, name) {
342
+ logger.error(
343
+ c.yellow(
344
+ `Module ${path} seems to be an ES Module but shipped in a CommonJS package. You might want to create an issue to the package ${c.bold(
345
+ `"${name}"`
346
+ )} asking them to ship the file in .mjs extension or add "type": "module" in their package.json.
347
+
348
+ As a temporary workaround you can try to inline the package by updating your config:
349
+
350
+ ` + c.gray(c.dim("// vitest.config.js")) + "\n" + c.green(`export default {
351
+ test: {
352
+ server: {
353
+ deps: {
354
+ inline: [
355
+ ${c.yellow(c.bold(`"${name}"`))}
356
+ ]
357
+ }
358
+ }
359
+ }
360
+ }
361
+ `)
362
+ )
363
+ );
364
+ }
365
+ function printModuleWarningForSourceCode(logger, path) {
366
+ logger.error(
367
+ c.yellow(
368
+ `Module ${path} seems to be an ES Module but shipped in a CommonJS package. To fix this issue, change the file extension to .mjs or add "type": "module" in your package.json.`
369
+ )
370
+ );
371
+ }
372
+ function printErrorMessage(error, logger) {
373
+ const errorName = error.name || error.nameStr || "Unknown Error";
374
+ if (!error.message) {
375
+ logger.error(error);
376
+ return;
377
+ }
378
+ if (error.message.length > 5e3) {
379
+ logger.error(`${c.red(c.bold(errorName))}: ${error.message}`);
380
+ } else {
381
+ logger.error(c.red(`${c.bold(errorName)}: ${error.message}`));
382
+ }
383
+ }
384
+ function printStack(logger, project, stack, highlight, errorProperties, onStack) {
385
+ for (const frame of stack) {
386
+ const color = frame === highlight ? c.cyan : c.gray;
387
+ const path = relative(project.config.root, frame.file);
388
+ logger.error(
389
+ color(
390
+ ` ${c.dim(F_POINTER)} ${[
391
+ frame.method,
392
+ `${path}:${c.dim(`${frame.line}:${frame.column}`)}`
393
+ ].filter(Boolean).join(" ")}`
394
+ )
395
+ );
396
+ onStack?.(frame);
397
+ }
398
+ if (stack.length) {
399
+ logger.error();
400
+ }
401
+ if (hasProperties(errorProperties)) {
402
+ logger.error(c.red(c.dim(divider())));
403
+ const propertiesString = inspect(errorProperties);
404
+ logger.error(c.red(c.bold("Serialized Error:")), c.gray(propertiesString));
405
+ }
406
+ }
407
+ function hasProperties(obj) {
408
+ for (const _key in obj) {
409
+ return true;
410
+ }
411
+ return false;
412
+ }
413
+ function generateCodeFrame(source, indent = 0, loc, range = 2) {
414
+ const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
415
+ const end = start;
416
+ const lines = source.split(lineSplitRE);
417
+ const nl = /\r\n/.test(source) ? 2 : 1;
418
+ let count = 0;
419
+ let res = [];
420
+ const columns = process.stdout?.columns || 80;
421
+ for (let i = 0; i < lines.length; i++) {
422
+ count += lines[i].length + nl;
423
+ if (count >= start) {
424
+ for (let j = i - range; j <= i + range || end > count; j++) {
425
+ if (j < 0 || j >= lines.length) {
426
+ continue;
427
+ }
428
+ const lineLength = lines[j].length;
429
+ if (stripVTControlCharacters(lines[j]).length > 200) {
430
+ return "";
431
+ }
432
+ res.push(
433
+ lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent)
434
+ );
435
+ if (j === i) {
436
+ const pad = start - (count - lineLength) + (nl - 1);
437
+ const length = Math.max(
438
+ 1,
439
+ end > count ? lineLength - pad : end - start
440
+ );
441
+ res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
442
+ } else if (j > i) {
443
+ if (end > count) {
444
+ const length = Math.max(1, Math.min(end - count, lineLength));
445
+ res.push(lineNo() + c.red("^".repeat(length)));
446
+ }
447
+ count += lineLength + 1;
448
+ }
449
+ }
450
+ break;
451
+ }
452
+ }
453
+ if (indent) {
454
+ res = res.map((line) => " ".repeat(indent) + line);
455
+ }
456
+ return res.join("\n");
457
+ }
458
+ function lineNo(no = "") {
459
+ return c.gray(`${String(no).padStart(3, " ")}| `);
460
+ }
461
+
462
+ class BlobReporter {
463
+ ctx;
464
+ options;
465
+ constructor(options) {
466
+ this.options = options;
467
+ }
468
+ onInit(ctx) {
469
+ if (ctx.config.watch) {
470
+ throw new Error("Blob reporter is not supported in watch mode");
471
+ }
472
+ this.ctx = ctx;
473
+ }
474
+ async onFinished(files = [], errors = [], coverage) {
475
+ let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
476
+ if (!outputFile) {
477
+ const shard = this.ctx.config.shard;
478
+ outputFile = shard ? `.vitest-reports/blob-${shard.index}-${shard.count}.json` : ".vitest-reports/blob.json";
479
+ }
480
+ const modules = this.ctx.projects.map(
481
+ (project) => {
482
+ return [
483
+ project.name,
484
+ [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
485
+ if (!mod[1].file) {
486
+ return null;
487
+ }
488
+ return [mod[0], mod[1].file, mod[1].url];
489
+ }).filter((x) => x != null)
490
+ ];
491
+ }
492
+ );
493
+ const report = stringify([
494
+ this.ctx.version,
495
+ files,
496
+ errors,
497
+ modules,
498
+ coverage
499
+ ]);
500
+ const reportFile = resolve(this.ctx.config.root, outputFile);
501
+ const dir = dirname(reportFile);
502
+ if (!existsSync(dir)) {
503
+ await mkdir(dir, { recursive: true });
504
+ }
505
+ await writeFile(reportFile, report, "utf-8");
506
+ this.ctx.logger.log("blob report written to", reportFile);
507
+ }
508
+ }
509
+ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
510
+ const resolvedDir = resolve(process.cwd(), blobsDirectory);
511
+ const blobsFiles = await readdir(resolvedDir);
512
+ const promises = blobsFiles.map(async (filename) => {
513
+ const fullPath = resolve(resolvedDir, filename);
514
+ const stats = await stat(fullPath);
515
+ if (!stats.isFile()) {
516
+ throw new TypeError(
517
+ `vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`
518
+ );
519
+ }
520
+ const content = await readFile(fullPath, "utf-8");
521
+ const [version, files2, errors2, moduleKeys, coverage] = parse(
522
+ content
523
+ );
524
+ if (!version) {
525
+ throw new TypeError(
526
+ `vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a valid blob file`
527
+ );
528
+ }
529
+ return { version, files: files2, errors: errors2, moduleKeys, coverage, file: filename };
530
+ });
531
+ const blobs = await Promise.all(promises);
532
+ if (!blobs.length) {
533
+ throw new Error(
534
+ `vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`
535
+ );
536
+ }
537
+ const versions = new Set(blobs.map((blob) => blob.version));
538
+ if (versions.size > 1) {
539
+ throw new Error(
540
+ `vitest.mergeReports() requires all blob files to be generated by the same Vitest version, received
541
+
542
+ ${blobs.map((b) => `- "${b.file}" uses v${b.version}`).join("\n")}`
543
+ );
544
+ }
545
+ if (!versions.has(currentVersion)) {
546
+ throw new Error(
547
+ `the blobs in "${blobsDirectory}" were generated by a different version of Vitest. Expected v${currentVersion}, but received v${blobs[0].version}`
548
+ );
549
+ }
550
+ const projects = Object.fromEntries(
551
+ projectsArray.map((p) => [p.name, p])
552
+ );
553
+ blobs.forEach((blob) => {
554
+ blob.moduleKeys.forEach(([projectName, moduleIds]) => {
555
+ const project = projects[projectName];
556
+ if (!project) {
557
+ return;
558
+ }
559
+ moduleIds.forEach(([moduleId, file, url]) => {
560
+ const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
561
+ moduleNode.url = url;
562
+ moduleNode.id = moduleId;
563
+ project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
564
+ });
565
+ });
566
+ });
567
+ const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
568
+ const time1 = f1.result?.startTime || 0;
569
+ const time2 = f2.result?.startTime || 0;
570
+ return time1 - time2;
571
+ });
572
+ const errors = blobs.flatMap((blob) => blob.errors);
573
+ const coverages = blobs.map((blob) => blob.coverage);
574
+ return {
575
+ files,
576
+ errors,
577
+ coverages
578
+ };
579
+ }
580
+
581
+ class HangingProcessReporter {
582
+ whyRunning;
583
+ onInit() {
584
+ const _require = createRequire(import.meta.url);
585
+ this.whyRunning = _require("why-is-node-running");
586
+ }
587
+ onProcessTimeout() {
588
+ this.whyRunning?.();
589
+ }
590
+ }
591
+
592
+ const BADGE_PADDING = " ";
593
+ class BaseReporter {
594
+ start = 0;
595
+ end = 0;
596
+ watchFilters;
597
+ failedUnwatchedFiles = [];
598
+ isTTY;
599
+ ctx = void 0;
600
+ renderSucceed = false;
601
+ verbose = false;
602
+ _filesInWatchMode = /* @__PURE__ */ new Map();
603
+ _timeStart = formatTimeString(/* @__PURE__ */ new Date());
604
+ constructor(options = {}) {
605
+ this.isTTY = options.isTTY ?? isTTY;
606
+ }
607
+ onInit(ctx) {
608
+ this.ctx = ctx;
609
+ this.ctx.logger.printBanner();
610
+ this.start = performance$1.now();
611
+ }
612
+ log(...messages) {
613
+ this.ctx.logger.log(...messages);
614
+ }
615
+ error(...messages) {
616
+ this.ctx.logger.error(...messages);
617
+ }
618
+ relative(path) {
619
+ return relative(this.ctx.config.root, path);
620
+ }
621
+ onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
622
+ this.end = performance$1.now();
623
+ if (!files.length && !errors.length) {
624
+ this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
625
+ } else {
626
+ this.reportSummary(files, errors);
627
+ }
628
+ }
629
+ onTaskUpdate(packs) {
630
+ for (const pack of packs) {
631
+ const task = this.ctx.state.idMap.get(pack[0]);
632
+ if (task) {
633
+ this.printTask(task);
634
+ }
635
+ }
636
+ }
637
+ /**
638
+ * Callback invoked with a single `Task` from `onTaskUpdate`
639
+ */
640
+ printTask(task) {
641
+ if (!("filepath" in task) || !task.result?.state || task.result?.state === "run" || task.result?.state === "queued") {
642
+ return;
643
+ }
644
+ const tests = getTests(task);
645
+ const failed = tests.filter((t) => t.result?.state === "fail");
646
+ const skipped = tests.filter((t) => t.mode === "skip" || t.mode === "todo");
647
+ let state = c.dim(`${tests.length} test${tests.length > 1 ? "s" : ""}`);
648
+ if (failed.length) {
649
+ state += c.dim(" | ") + c.red(`${failed.length} failed`);
650
+ }
651
+ if (skipped.length) {
652
+ state += c.dim(" | ") + c.yellow(`${skipped.length} skipped`);
653
+ }
654
+ let suffix = c.dim("(") + state + c.dim(")") + this.getDurationPrefix(task);
655
+ if (this.ctx.config.logHeapUsage && task.result.heap != null) {
656
+ suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`);
657
+ }
658
+ let title = getStateSymbol(task);
659
+ if (task.meta.typecheck) {
660
+ title += ` ${c.bgBlue(c.bold(" TS "))}`;
661
+ }
662
+ if (task.projectName) {
663
+ title += ` ${formatProjectName(task.projectName, "")}`;
664
+ }
665
+ this.log(` ${title} ${task.name} ${suffix}`);
666
+ const anyFailed = tests.some((test) => test.result?.state === "fail");
667
+ for (const test of tests) {
668
+ const { duration, retryCount, repeatCount } = test.result || {};
669
+ let suffix2 = "";
670
+ if (retryCount != null && retryCount > 0) {
671
+ suffix2 += c.yellow(` (retry x${retryCount})`);
672
+ }
673
+ if (repeatCount != null && repeatCount > 0) {
674
+ suffix2 += c.yellow(` (repeat x${repeatCount})`);
675
+ }
676
+ if (test.result?.state === "fail") {
677
+ this.log(c.red(` ${taskFail} ${getTestName(test, c.dim(" > "))}${this.getDurationPrefix(test)}`) + suffix2);
678
+ test.result?.errors?.forEach((e) => {
679
+ this.log(c.red(` ${F_RIGHT} ${e?.message}`));
680
+ });
681
+ } else if (duration && duration > this.ctx.config.slowTestThreshold) {
682
+ this.log(
683
+ ` ${c.yellow(c.dim(F_CHECK))} ${getTestName(test, c.dim(" > "))} ${c.yellow(Math.round(duration) + c.dim("ms"))}${suffix2}`
684
+ );
685
+ } else if (this.ctx.config.hideSkippedTests && (test.mode === "skip" || test.result?.state === "skip")) ; else if (test.result?.state === "skip" && test.result.note) {
686
+ this.log(` ${getStateSymbol(test)} ${getTestName(test)}${c.dim(c.gray(` [${test.result.note}]`))}`);
687
+ } else if (this.renderSucceed || anyFailed) {
688
+ this.log(` ${getStateSymbol(test)} ${getTestName(test, c.dim(" > "))}${suffix2}`);
689
+ }
690
+ }
691
+ }
692
+ getDurationPrefix(task) {
693
+ if (!task.result?.duration) {
694
+ return "";
695
+ }
696
+ const color = task.result.duration > this.ctx.config.slowTestThreshold ? c.yellow : c.gray;
697
+ return color(` ${Math.round(task.result.duration)}${c.dim("ms")}`);
698
+ }
699
+ onWatcherStart(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
700
+ const failed = errors.length > 0 || hasFailed(files);
701
+ if (failed) {
702
+ this.log(withLabel("red", "FAIL", "Tests failed. Watching for file changes..."));
703
+ } else if (this.ctx.isCancelling) {
704
+ this.log(withLabel("red", "CANCELLED", "Test run cancelled. Watching for file changes..."));
705
+ } else {
706
+ this.log(withLabel("green", "PASS", "Waiting for file changes..."));
707
+ }
708
+ const hints = [c.dim("press ") + c.bold("h") + c.dim(" to show help")];
709
+ if (hasFailedSnapshot(files)) {
710
+ hints.unshift(c.dim("press ") + c.bold(c.yellow("u")) + c.dim(" to update snapshot"));
711
+ } else {
712
+ hints.push(c.dim("press ") + c.bold("q") + c.dim(" to quit"));
713
+ }
714
+ this.log(BADGE_PADDING + hints.join(c.dim(", ")));
715
+ }
716
+ onWatcherRerun(files, trigger) {
717
+ this.watchFilters = files;
718
+ this.failedUnwatchedFiles = this.ctx.state.getFiles().filter(
719
+ (file) => !files.includes(file.filepath) && hasFailed(file)
720
+ );
721
+ files.forEach((filepath) => {
722
+ let reruns = this._filesInWatchMode.get(filepath) ?? 0;
723
+ this._filesInWatchMode.set(filepath, ++reruns);
724
+ });
725
+ let banner = trigger ? c.dim(`${this.relative(trigger)} `) : "";
726
+ if (files.length === 1) {
727
+ const rerun = this._filesInWatchMode.get(files[0]) ?? 1;
728
+ banner += c.blue(`x${rerun} `);
729
+ }
730
+ this.ctx.logger.clearFullScreen();
731
+ this.log(withLabel("blue", "RERUN", banner));
732
+ if (this.ctx.configOverride.project) {
733
+ this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
734
+ }
735
+ if (this.ctx.filenamePattern) {
736
+ this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
737
+ }
738
+ if (this.ctx.configOverride.testNamePattern) {
739
+ this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
740
+ }
741
+ this.log("");
742
+ for (const task of this.failedUnwatchedFiles) {
743
+ this.printTask(task);
744
+ }
745
+ this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
746
+ this.start = performance$1.now();
747
+ }
748
+ onUserConsoleLog(log) {
749
+ if (!this.shouldLog(log)) {
750
+ return;
751
+ }
752
+ const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream;
753
+ const write = (msg) => output.write(msg);
754
+ let headerText = "unknown test";
755
+ const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
756
+ if (task) {
757
+ headerText = getFullName(task, c.dim(" > "));
758
+ } else if (log.taskId && log.taskId !== "__vitest__unknown_test__") {
759
+ headerText = log.taskId;
760
+ }
761
+ write(c.gray(log.type + c.dim(` | ${headerText}
762
+ `)) + log.content);
763
+ if (log.origin) {
764
+ if (log.browser) {
765
+ write("\n");
766
+ }
767
+ const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
768
+ const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
769
+ const highlight = task && stack.find((i) => i.file === task.file.filepath);
770
+ for (const frame of stack) {
771
+ const color = frame === highlight ? c.cyan : c.gray;
772
+ const path = relative(project.config.root, frame.file);
773
+ const positions = [
774
+ frame.method,
775
+ `${path}:${c.dim(`${frame.line}:${frame.column}`)}`
776
+ ].filter(Boolean).join(" ");
777
+ write(color(` ${c.dim(F_POINTER)} ${positions}
778
+ `));
779
+ }
780
+ }
781
+ write("\n");
782
+ }
783
+ onTestRemoved(trigger) {
784
+ this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]
785
+ `) : ""));
786
+ }
787
+ shouldLog(log) {
788
+ if (this.ctx.config.silent) {
789
+ return false;
790
+ }
791
+ const shouldLog = this.ctx.config.onConsoleLog?.(log.content, log.type);
792
+ if (shouldLog === false) {
793
+ return shouldLog;
794
+ }
795
+ return true;
796
+ }
797
+ onServerRestart(reason) {
798
+ this.log(c.bold(c.magenta(
799
+ reason === "config" ? "\nRestarting due to config changes..." : "\nRestarting Vitest..."
800
+ )));
801
+ }
802
+ reportSummary(files, errors) {
803
+ this.printErrorsSummary(files, errors);
804
+ if (this.ctx.config.mode === "benchmark") {
805
+ this.reportBenchmarkSummary(files);
806
+ } else {
807
+ this.reportTestSummary(files, errors);
808
+ }
809
+ }
810
+ reportTestSummary(files, errors) {
811
+ this.log();
812
+ const affectedFiles = [
813
+ ...this.failedUnwatchedFiles,
814
+ ...files
815
+ ];
816
+ const tests = getTests(affectedFiles);
817
+ const snapshotOutput = renderSnapshotSummary(
818
+ this.ctx.config.root,
819
+ this.ctx.snapshot.summary
820
+ );
821
+ for (const [index, snapshot] of snapshotOutput.entries()) {
822
+ const title = index === 0 ? "Snapshots" : "";
823
+ this.log(`${padSummaryTitle(title)} ${snapshot}`);
824
+ }
825
+ if (snapshotOutput.length > 1) {
826
+ this.log();
827
+ }
828
+ this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles));
829
+ this.log(padSummaryTitle("Tests"), getStateString$1(tests));
830
+ if (this.ctx.projects.some((c2) => c2.config.typecheck.enabled)) {
831
+ const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
832
+ this.log(
833
+ padSummaryTitle("Type Errors"),
834
+ failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors")
835
+ );
836
+ }
837
+ if (errors.length) {
838
+ this.log(
839
+ padSummaryTitle("Errors"),
840
+ c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`))
841
+ );
842
+ }
843
+ this.log(padSummaryTitle("Start at"), this._timeStart);
844
+ const collectTime = sum(files, (file) => file.collectDuration);
845
+ const testsTime = sum(files, (file) => file.result?.duration);
846
+ const setupTime = sum(files, (file) => file.setupDuration);
847
+ if (this.watchFilters) {
848
+ this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
849
+ } else {
850
+ const executionTime = this.end - this.start;
851
+ const environmentTime = sum(files, (file) => file.environmentLoad);
852
+ const prepareTime = sum(files, (file) => file.prepareDuration);
853
+ const transformTime = sum(this.ctx.projects, (project) => project.vitenode.getTotalDuration());
854
+ const typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time);
855
+ const timers = [
856
+ `transform ${formatTime(transformTime)}`,
857
+ `setup ${formatTime(setupTime)}`,
858
+ `collect ${formatTime(collectTime)}`,
859
+ `tests ${formatTime(testsTime)}`,
860
+ `environment ${formatTime(environmentTime)}`,
861
+ `prepare ${formatTime(prepareTime)}`,
862
+ typecheck && `typecheck ${formatTime(typecheck)}`
863
+ ].filter(Boolean).join(", ");
864
+ this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
865
+ }
866
+ this.log();
867
+ }
868
+ printErrorsSummary(files, errors) {
869
+ const suites = getSuites(files);
870
+ const tests = getTests(files);
871
+ const failedSuites = suites.filter((i) => i.result?.errors);
872
+ const failedTests = tests.filter((i) => i.result?.state === "fail");
873
+ const failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
874
+ let current = 1;
875
+ const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}
876
+ `);
877
+ if (failedSuites.length) {
878
+ this.error(`
879
+ ${errorBanner(`Failed Suites ${failedSuites.length}`)}
880
+ `);
881
+ this.printTaskErrors(failedSuites, errorDivider);
882
+ }
883
+ if (failedTests.length) {
884
+ this.error(`
885
+ ${errorBanner(`Failed Tests ${failedTests.length}`)}
886
+ `);
887
+ this.printTaskErrors(failedTests, errorDivider);
888
+ }
889
+ if (errors.length) {
890
+ this.ctx.logger.printUnhandledErrors(errors);
891
+ this.error();
892
+ }
893
+ }
894
+ reportBenchmarkSummary(files) {
895
+ const benches = getTests(files);
896
+ const topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
897
+ this.log(`
898
+ ${withLabel("cyan", "BENCH", "Summary\n")}`);
899
+ for (const bench of topBenches) {
900
+ const group = bench.suite || bench.file;
901
+ if (!group) {
902
+ continue;
903
+ }
904
+ const groupName = getFullName(group, c.dim(" > "));
905
+ this.log(` ${formatProjectName(bench.file.projectName)}${bench.name}${c.dim(` - ${groupName}`)}`);
906
+ const siblings = group.tasks.filter((i) => i.meta.benchmark && i.result?.benchmark && i !== bench).sort((a, b) => a.result.benchmark.rank - b.result.benchmark.rank);
907
+ for (const sibling of siblings) {
908
+ const number = (sibling.result.benchmark.mean / bench.result.benchmark.mean).toFixed(2);
909
+ this.log(c.green(` ${number}x `) + c.gray("faster than ") + sibling.name);
910
+ }
911
+ this.log("");
912
+ }
913
+ }
914
+ printTaskErrors(tasks, errorDivider) {
915
+ const errorsQueue = [];
916
+ for (const task of tasks) {
917
+ task.result?.errors?.forEach((error) => {
918
+ let previous;
919
+ if (error?.stackStr) {
920
+ previous = errorsQueue.find((i) => {
921
+ if (i[0]?.stackStr !== error.stackStr) {
922
+ return false;
923
+ }
924
+ const currentProjectName = task?.projectName || task.file?.projectName || "";
925
+ const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
926
+ return projectName === currentProjectName;
927
+ });
928
+ }
929
+ if (previous) {
930
+ previous[1].push(task);
931
+ } else {
932
+ errorsQueue.push([error, [task]]);
933
+ }
934
+ });
935
+ }
936
+ for (const [error, tasks2] of errorsQueue) {
937
+ for (const task of tasks2) {
938
+ const filepath = task?.filepath || "";
939
+ const projectName = task?.projectName || task.file?.projectName || "";
940
+ let name = getFullName(task, c.dim(" > "));
941
+ if (filepath) {
942
+ name += c.dim(` [ ${this.relative(filepath)} ]`);
943
+ }
944
+ this.ctx.logger.error(
945
+ `${c.red(c.bold(c.inverse(" FAIL ")))} ${formatProjectName(projectName)}${name}`
946
+ );
947
+ }
948
+ const screenshotPaths = tasks2.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
949
+ this.ctx.logger.printError(error, {
950
+ project: this.ctx.getProjectByName(tasks2[0].file.projectName || ""),
951
+ verbose: this.verbose,
952
+ screenshotPaths,
953
+ task: tasks2[0]
954
+ });
955
+ errorDivider();
956
+ }
957
+ }
958
+ }
959
+ function errorBanner(message) {
960
+ return c.red(divider(c.bold(c.inverse(` ${message} `))));
961
+ }
962
+ function sum(items, cb) {
963
+ return items.reduce((total, next) => {
964
+ return total + Math.max(cb(next) || 0, 0);
965
+ }, 0);
966
+ }
967
+
968
+ class BasicReporter extends BaseReporter {
969
+ constructor() {
970
+ super();
971
+ this.isTTY = false;
972
+ }
973
+ onInit(ctx) {
974
+ super.onInit(ctx);
975
+ ctx.logger.log(c.inverse(c.bold(c.yellow(" DEPRECATED "))), c.yellow(
976
+ `'basic' reporter is deprecated and will be removed in Vitest v3.
977
+ Remove 'basic' from 'reporters' option. To match 'basic' reporter 100%, use configuration:
978
+ ${JSON.stringify({ test: { reporters: [["default", { summary: false }]] } }, null, 2)}`
979
+ ));
980
+ }
981
+ reportSummary(files, errors) {
982
+ this.ctx.logger.log();
983
+ return super.reportSummary(files, errors);
984
+ }
985
+ }
986
+
987
+ const DEFAULT_RENDER_INTERVAL = 16;
988
+ const ESC = "\x1B[";
989
+ const CLEAR_LINE = `${ESC}K`;
990
+ const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`;
991
+ const SYNC_START = `${ESC}?2026h`;
992
+ const SYNC_END = `${ESC}?2026l`;
993
+ class WindowRenderer {
994
+ options;
995
+ streams;
996
+ buffer = [];
997
+ renderInterval = void 0;
998
+ windowHeight = 0;
999
+ finished = false;
1000
+ cleanups = [];
1001
+ constructor(options) {
1002
+ this.options = {
1003
+ interval: DEFAULT_RENDER_INTERVAL,
1004
+ ...options
1005
+ };
1006
+ this.streams = {
1007
+ output: options.logger.outputStream.write.bind(options.logger.outputStream),
1008
+ error: options.logger.errorStream.write.bind(options.logger.errorStream)
1009
+ };
1010
+ this.cleanups.push(
1011
+ this.interceptStream(process.stdout, "output"),
1012
+ this.interceptStream(process.stderr, "error")
1013
+ );
1014
+ this.options.logger.onTerminalCleanup(() => {
1015
+ this.flushBuffer();
1016
+ this.stop();
1017
+ });
1018
+ this.start();
1019
+ }
1020
+ start() {
1021
+ this.finished = false;
1022
+ this.renderInterval = setInterval(() => this.flushBuffer(), this.options.interval).unref();
1023
+ }
1024
+ stop() {
1025
+ this.cleanups.splice(0).map((fn) => fn());
1026
+ clearInterval(this.renderInterval);
1027
+ }
1028
+ /**
1029
+ * Write all buffered output and stop buffering.
1030
+ * All intercepted writes are forwarded to actual write after this.
1031
+ */
1032
+ finish() {
1033
+ this.finished = true;
1034
+ this.flushBuffer();
1035
+ clearInterval(this.renderInterval);
1036
+ }
1037
+ flushBuffer() {
1038
+ if (this.buffer.length === 0) {
1039
+ return this.render();
1040
+ }
1041
+ let current;
1042
+ for (const next of this.buffer.splice(0)) {
1043
+ if (!current) {
1044
+ current = next;
1045
+ continue;
1046
+ }
1047
+ if (current.type !== next.type) {
1048
+ this.render(current.message, current.type);
1049
+ current = next;
1050
+ continue;
1051
+ }
1052
+ current.message += next.message;
1053
+ }
1054
+ if (current) {
1055
+ this.render(current?.message, current?.type);
1056
+ }
1057
+ }
1058
+ render(message, type = "output") {
1059
+ if (this.finished) {
1060
+ this.clearWindow();
1061
+ return this.write(message || "", type);
1062
+ }
1063
+ const windowContent = this.options.getWindow();
1064
+ const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
1065
+ let padding = this.windowHeight - rowCount;
1066
+ if (padding > 0 && message) {
1067
+ padding -= getRenderedRowCount([message], this.options.logger.getColumns());
1068
+ }
1069
+ this.write(SYNC_START);
1070
+ this.clearWindow();
1071
+ if (message) {
1072
+ this.write(message, type);
1073
+ }
1074
+ if (padding > 0) {
1075
+ this.write("\n".repeat(padding));
1076
+ }
1077
+ this.write(windowContent.join("\n"));
1078
+ this.write(SYNC_END);
1079
+ this.windowHeight = rowCount + Math.max(0, padding);
1080
+ }
1081
+ clearWindow() {
1082
+ if (this.windowHeight === 0) {
1083
+ return;
1084
+ }
1085
+ this.write(CLEAR_LINE);
1086
+ for (let i = 1; i < this.windowHeight; i++) {
1087
+ this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
1088
+ }
1089
+ this.windowHeight = 0;
1090
+ }
1091
+ interceptStream(stream, type) {
1092
+ const original = stream.write;
1093
+ stream.write = (chunk, _, callback) => {
1094
+ if (chunk) {
1095
+ if (this.finished) {
1096
+ this.write(chunk.toString(), type);
1097
+ } else {
1098
+ this.buffer.push({ type, message: chunk.toString() });
1099
+ }
1100
+ }
1101
+ callback?.();
1102
+ };
1103
+ return function restore() {
1104
+ stream.write = original;
1105
+ };
1106
+ }
1107
+ write(message, type = "output") {
1108
+ this.streams[type](message);
1109
+ }
1110
+ }
1111
+ function getRenderedRowCount(rows, columns) {
1112
+ let count = 0;
1113
+ for (const row of rows) {
1114
+ const text = stripVTControlCharacters(row);
1115
+ count += Math.max(1, Math.ceil(text.length / columns));
1116
+ }
1117
+ return count;
1118
+ }
1119
+
1120
+ const DURATION_UPDATE_INTERVAL_MS = 100;
1121
+ const FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
1122
+ class SummaryReporter {
1123
+ ctx;
1124
+ options;
1125
+ renderer;
1126
+ modules = emptyCounters();
1127
+ tests = emptyCounters();
1128
+ maxParallelTests = 0;
1129
+ /** Currently running test modules, may include finished test modules too */
1130
+ runningModules = /* @__PURE__ */ new Map();
1131
+ /** ID of finished `this.runningModules` that are currently being shown */
1132
+ finishedModules = /* @__PURE__ */ new Map();
1133
+ startTime = "";
1134
+ currentTime = 0;
1135
+ duration = 0;
1136
+ durationInterval = void 0;
1137
+ onInit(ctx, options = {}) {
1138
+ this.ctx = ctx;
1139
+ this.options = {
1140
+ verbose: false,
1141
+ ...options
1142
+ };
1143
+ this.renderer = new WindowRenderer({
1144
+ logger: ctx.logger,
1145
+ getWindow: () => this.createSummary()
1146
+ });
1147
+ this.ctx.onClose(() => {
1148
+ clearInterval(this.durationInterval);
1149
+ this.renderer.stop();
1150
+ });
1151
+ }
1152
+ onTestRunStart(specifications) {
1153
+ this.runningModules.clear();
1154
+ this.finishedModules.clear();
1155
+ this.modules = emptyCounters();
1156
+ this.tests = emptyCounters();
1157
+ this.startTimers();
1158
+ this.renderer.start();
1159
+ this.modules.total = specifications.length;
1160
+ }
1161
+ onTestRunEnd() {
1162
+ this.runningModules.clear();
1163
+ this.finishedModules.clear();
1164
+ this.renderer.finish();
1165
+ clearInterval(this.durationInterval);
1166
+ }
1167
+ onTestModuleQueued(module) {
1168
+ if (this.finishedModules.size) {
1169
+ const finished = this.finishedModules.keys().next().value;
1170
+ this.removeTestModule(finished);
1171
+ }
1172
+ this.runningModules.set(module.id, initializeStats(module));
1173
+ }
1174
+ onTestModuleCollected(module) {
1175
+ let stats = this.runningModules.get(module.id);
1176
+ if (!stats) {
1177
+ stats = initializeStats(module);
1178
+ this.runningModules.set(module.id, stats);
1179
+ }
1180
+ const total = Array.from(module.children.allTests()).length;
1181
+ this.tests.total += total;
1182
+ stats.total = total;
1183
+ this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size);
1184
+ }
1185
+ onHookStart(options) {
1186
+ const stats = this.getHookStats(options);
1187
+ if (!stats) {
1188
+ return;
1189
+ }
1190
+ const hook = {
1191
+ name: options.name,
1192
+ visible: false,
1193
+ startTime: performance.now(),
1194
+ onFinish: () => {
1195
+ }
1196
+ };
1197
+ stats.hook?.onFinish?.();
1198
+ stats.hook = hook;
1199
+ const timeout = setTimeout(() => {
1200
+ hook.visible = true;
1201
+ }, this.ctx.config.slowTestThreshold).unref();
1202
+ hook.onFinish = () => clearTimeout(timeout);
1203
+ }
1204
+ onHookEnd(options) {
1205
+ const stats = this.getHookStats(options);
1206
+ if (stats?.hook?.name !== options.name) {
1207
+ return;
1208
+ }
1209
+ stats.hook.onFinish();
1210
+ stats.hook.visible = false;
1211
+ }
1212
+ onTestCaseReady(test) {
1213
+ if (!this.options.verbose) {
1214
+ return;
1215
+ }
1216
+ const stats = this.runningModules.get(test.module.id);
1217
+ if (!stats || stats.tests.has(test.id)) {
1218
+ return;
1219
+ }
1220
+ const slowTest = {
1221
+ name: test.name,
1222
+ visible: false,
1223
+ startTime: performance.now(),
1224
+ onFinish: () => {
1225
+ }
1226
+ };
1227
+ const timeout = setTimeout(() => {
1228
+ slowTest.visible = true;
1229
+ }, this.ctx.config.slowTestThreshold).unref();
1230
+ slowTest.onFinish = () => {
1231
+ slowTest.hook?.onFinish();
1232
+ clearTimeout(timeout);
1233
+ };
1234
+ stats.tests.set(test.id, slowTest);
1235
+ }
1236
+ onTestCaseResult(test) {
1237
+ const stats = this.runningModules.get(test.module.id);
1238
+ if (!stats) {
1239
+ return;
1240
+ }
1241
+ stats.tests.get(test.id)?.onFinish();
1242
+ stats.tests.delete(test.id);
1243
+ stats.completed++;
1244
+ const result = test.result();
1245
+ if (result?.state === "passed") {
1246
+ this.tests.passed++;
1247
+ } else if (result?.state === "failed") {
1248
+ this.tests.failed++;
1249
+ } else if (!result?.state || result?.state === "skipped") {
1250
+ this.tests.skipped++;
1251
+ }
1252
+ }
1253
+ onTestModuleEnd(module) {
1254
+ const state = module.state();
1255
+ this.modules.completed++;
1256
+ if (state === "passed") {
1257
+ this.modules.passed++;
1258
+ } else if (state === "failed") {
1259
+ this.modules.failed++;
1260
+ } else if (module.task.mode === "todo" && state === "skipped") {
1261
+ this.modules.todo++;
1262
+ } else if (state === "skipped") {
1263
+ this.modules.skipped++;
1264
+ }
1265
+ const left = this.modules.total - this.modules.completed;
1266
+ if (left > this.maxParallelTests) {
1267
+ this.finishedModules.set(module.id, setTimeout(() => {
1268
+ this.removeTestModule(module.id);
1269
+ }, FINISHED_TEST_CLEANUP_TIME_MS).unref());
1270
+ } else {
1271
+ this.removeTestModule(module.id);
1272
+ }
1273
+ }
1274
+ getHookStats({ entity }) {
1275
+ if (!this.options.verbose) {
1276
+ return;
1277
+ }
1278
+ const module = entity.type === "module" ? entity : entity.module;
1279
+ const stats = this.runningModules.get(module.id);
1280
+ if (!stats) {
1281
+ return;
1282
+ }
1283
+ return entity.type === "test" ? stats.tests.get(entity.id) : stats;
1284
+ }
1285
+ createSummary() {
1286
+ const summary = [""];
1287
+ for (const testFile of Array.from(this.runningModules.values()).sort(sortRunningModules)) {
1288
+ const typecheck = testFile.typecheck ? `${c.bgBlue(c.bold(" TS "))} ` : "";
1289
+ summary.push(
1290
+ c.bold(c.yellow(` ${F_POINTER} `)) + formatProjectName(testFile.projectName) + typecheck + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`)
1291
+ );
1292
+ const slowTasks = [
1293
+ testFile.hook,
1294
+ ...Array.from(testFile.tests.values())
1295
+ ].filter((t) => t != null && t.visible);
1296
+ for (const [index, task] of slowTasks.entries()) {
1297
+ const elapsed = this.currentTime - task.startTime;
1298
+ const icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
1299
+ summary.push(
1300
+ c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`))
1301
+ );
1302
+ if (task.hook?.visible) {
1303
+ summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
1304
+ }
1305
+ }
1306
+ }
1307
+ if (this.runningModules.size > 0) {
1308
+ summary.push("");
1309
+ }
1310
+ summary.push(padSummaryTitle("Test Files") + getStateString(this.modules));
1311
+ summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
1312
+ summary.push(padSummaryTitle("Start at") + this.startTime);
1313
+ summary.push(padSummaryTitle("Duration") + formatTime(this.duration));
1314
+ summary.push("");
1315
+ return summary;
1316
+ }
1317
+ startTimers() {
1318
+ const start = performance.now();
1319
+ this.startTime = formatTimeString(/* @__PURE__ */ new Date());
1320
+ this.durationInterval = setInterval(() => {
1321
+ this.currentTime = performance.now();
1322
+ this.duration = this.currentTime - start;
1323
+ }, DURATION_UPDATE_INTERVAL_MS).unref();
1324
+ }
1325
+ removeTestModule(id) {
1326
+ if (!id) {
1327
+ return;
1328
+ }
1329
+ const testFile = this.runningModules.get(id);
1330
+ testFile?.hook?.onFinish();
1331
+ testFile?.tests?.forEach((test) => test.onFinish());
1332
+ this.runningModules.delete(id);
1333
+ clearTimeout(this.finishedModules.get(id));
1334
+ this.finishedModules.delete(id);
1335
+ }
1336
+ }
1337
+ function emptyCounters() {
1338
+ return { completed: 0, passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
1339
+ }
1340
+ function getStateString(entry) {
1341
+ return [
1342
+ entry.failed ? c.bold(c.red(`${entry.failed} failed`)) : null,
1343
+ c.bold(c.green(`${entry.passed} passed`)),
1344
+ entry.skipped ? c.yellow(`${entry.skipped} skipped`) : null,
1345
+ entry.todo ? c.gray(`${entry.todo} todo`) : null
1346
+ ].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
1347
+ }
1348
+ function sortRunningModules(a, b) {
1349
+ if ((a.projectName || "") > (b.projectName || "")) {
1350
+ return 1;
1351
+ }
1352
+ if ((a.projectName || "") < (b.projectName || "")) {
1353
+ return -1;
1354
+ }
1355
+ return a.filename.localeCompare(b.filename);
1356
+ }
1357
+ function initializeStats(module) {
1358
+ return {
1359
+ total: 0,
1360
+ completed: 0,
1361
+ filename: module.task.name,
1362
+ projectName: module.project.name,
1363
+ tests: /* @__PURE__ */ new Map(),
1364
+ typecheck: !!module.task.meta.typecheck
1365
+ };
1366
+ }
1367
+
1368
+ class DefaultReporter extends BaseReporter {
1369
+ options;
1370
+ summary;
1371
+ constructor(options = {}) {
1372
+ super(options);
1373
+ this.options = {
1374
+ summary: true,
1375
+ ...options
1376
+ };
1377
+ if (!this.isTTY) {
1378
+ this.options.summary = false;
1379
+ }
1380
+ if (this.options.summary) {
1381
+ this.summary = new SummaryReporter();
1382
+ }
1383
+ }
1384
+ onTestRunStart(specifications) {
1385
+ this.summary?.onTestRunStart(specifications);
1386
+ }
1387
+ onTestModuleQueued(file) {
1388
+ this.summary?.onTestModuleQueued(file);
1389
+ }
1390
+ onTestModuleCollected(module) {
1391
+ this.summary?.onTestModuleCollected(module);
1392
+ }
1393
+ onTestModuleEnd(module) {
1394
+ this.summary?.onTestModuleEnd(module);
1395
+ }
1396
+ onTestCaseReady(test) {
1397
+ this.summary?.onTestCaseReady(test);
1398
+ }
1399
+ onTestCaseResult(test) {
1400
+ this.summary?.onTestCaseResult(test);
1401
+ }
1402
+ onHookStart(hook) {
1403
+ this.summary?.onHookStart(hook);
1404
+ }
1405
+ onHookEnd(hook) {
1406
+ this.summary?.onHookEnd(hook);
1407
+ }
1408
+ onInit(ctx) {
1409
+ super.onInit(ctx);
1410
+ this.summary?.onInit(ctx, { verbose: this.verbose });
1411
+ }
1412
+ onPathsCollected(paths = []) {
1413
+ if (this.isTTY) {
1414
+ if (this.renderSucceed === void 0) {
1415
+ this.renderSucceed = !!this.renderSucceed;
1416
+ }
1417
+ if (this.renderSucceed !== true) {
1418
+ this.renderSucceed = paths.length <= 1;
1419
+ }
1420
+ }
1421
+ }
1422
+ onTestRunEnd() {
1423
+ this.summary?.onTestRunEnd();
1424
+ }
1425
+ }
1426
+
1427
+ class DotReporter extends BaseReporter {
1428
+ renderer;
1429
+ tests = /* @__PURE__ */ new Map();
1430
+ finishedTests = /* @__PURE__ */ new Set();
1431
+ onInit(ctx) {
1432
+ super.onInit(ctx);
1433
+ if (this.isTTY) {
1434
+ this.renderer = new WindowRenderer({
1435
+ logger: ctx.logger,
1436
+ getWindow: () => this.createSummary()
1437
+ });
1438
+ this.ctx.onClose(() => this.renderer?.stop());
1439
+ }
1440
+ }
1441
+ printTask(task) {
1442
+ if (!this.isTTY) {
1443
+ super.printTask(task);
1444
+ }
1445
+ }
1446
+ onWatcherRerun(files, trigger) {
1447
+ this.tests.clear();
1448
+ this.renderer?.start();
1449
+ super.onWatcherRerun(files, trigger);
1450
+ }
1451
+ onFinished(files, errors) {
1452
+ if (this.isTTY) {
1453
+ const finalLog = formatTests(Array.from(this.tests.values()));
1454
+ this.ctx.logger.log(finalLog);
1455
+ }
1456
+ this.tests.clear();
1457
+ this.renderer?.finish();
1458
+ super.onFinished(files, errors);
1459
+ }
1460
+ onTestModuleCollected(module) {
1461
+ for (const test of module.children.allTests()) {
1462
+ this.onTestCaseReady(test);
1463
+ }
1464
+ }
1465
+ onTestCaseReady(test) {
1466
+ if (this.finishedTests.has(test.id)) {
1467
+ return;
1468
+ }
1469
+ this.tests.set(test.id, test.result().state || "run");
1470
+ }
1471
+ onTestCaseResult(test) {
1472
+ this.finishedTests.add(test.id);
1473
+ this.tests.set(test.id, test.result().state || "skipped");
1474
+ }
1475
+ onTestModuleEnd() {
1476
+ if (!this.isTTY) {
1477
+ return;
1478
+ }
1479
+ const columns = this.ctx.logger.getColumns();
1480
+ if (this.tests.size < columns) {
1481
+ return;
1482
+ }
1483
+ const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
1484
+ if (finishedTests.length < columns) {
1485
+ return;
1486
+ }
1487
+ const states = [];
1488
+ let count = 0;
1489
+ for (const [id, state] of finishedTests) {
1490
+ if (count++ >= columns) {
1491
+ break;
1492
+ }
1493
+ this.tests.delete(id);
1494
+ states.push(state);
1495
+ }
1496
+ this.ctx.logger.log(formatTests(states));
1497
+ }
1498
+ createSummary() {
1499
+ return [
1500
+ formatTests(Array.from(this.tests.values())),
1501
+ ""
1502
+ ];
1503
+ }
1504
+ }
1505
+ const pass = { char: "\xB7", color: c.green };
1506
+ const fail = { char: "x", color: c.red };
1507
+ const pending = { char: "*", color: c.yellow };
1508
+ const skip = { char: "-", color: (char) => c.dim(c.gray(char)) };
1509
+ function getIcon(state) {
1510
+ switch (state) {
1511
+ case "passed":
1512
+ return pass;
1513
+ case "failed":
1514
+ return fail;
1515
+ case "skipped":
1516
+ return skip;
1517
+ default:
1518
+ return pending;
1519
+ }
1520
+ }
1521
+ function formatTests(states) {
1522
+ let currentIcon = pending;
1523
+ let count = 0;
1524
+ let output = "";
1525
+ for (const state of states) {
1526
+ const icon = getIcon(state);
1527
+ if (currentIcon === icon) {
1528
+ count++;
1529
+ continue;
1530
+ }
1531
+ output += currentIcon.color(currentIcon.char.repeat(count));
1532
+ count = 1;
1533
+ currentIcon = icon;
1534
+ }
1535
+ output += currentIcon.color(currentIcon.char.repeat(count));
1536
+ return output;
1537
+ }
1538
+
1539
+ class GithubActionsReporter {
1540
+ ctx = void 0;
1541
+ onInit(ctx) {
1542
+ this.ctx = ctx;
1543
+ }
1544
+ onFinished(files = [], errors = []) {
1545
+ const projectErrors = new Array();
1546
+ for (const error of errors) {
1547
+ projectErrors.push({
1548
+ project: this.ctx.getRootProject(),
1549
+ title: "Unhandled error",
1550
+ error
1551
+ });
1552
+ }
1553
+ for (const file of files) {
1554
+ const tasks = getTasks(file);
1555
+ const project = this.ctx.getProjectByName(file.projectName || "");
1556
+ for (const task of tasks) {
1557
+ if (task.result?.state !== "fail") {
1558
+ continue;
1559
+ }
1560
+ const title = getFullName(task, " > ");
1561
+ for (const error of task.result?.errors ?? []) {
1562
+ projectErrors.push({
1563
+ project,
1564
+ title,
1565
+ error,
1566
+ file
1567
+ });
1568
+ }
1569
+ }
1570
+ }
1571
+ for (const { project, title, error, file } of projectErrors) {
1572
+ const result = capturePrintError(error, this.ctx, { project, task: file });
1573
+ const stack = result?.nearest;
1574
+ if (!stack) {
1575
+ continue;
1576
+ }
1577
+ const formatted = formatMessage({
1578
+ command: "error",
1579
+ properties: {
1580
+ file: stack.file,
1581
+ title,
1582
+ line: String(stack.line),
1583
+ column: String(stack.column)
1584
+ },
1585
+ message: stripVTControlCharacters(result.output)
1586
+ });
1587
+ this.ctx.logger.log(`
1588
+ ${formatted}`);
1589
+ }
1590
+ }
1591
+ }
1592
+ function formatMessage({
1593
+ command,
1594
+ properties,
1595
+ message
1596
+ }) {
1597
+ let result = `::${command}`;
1598
+ Object.entries(properties).forEach(([k, v], i) => {
1599
+ result += i === 0 ? " " : ",";
1600
+ result += `${k}=${escapeProperty(v)}`;
1601
+ });
1602
+ result += `::${escapeData(message)}`;
1603
+ return result;
1604
+ }
1605
+ function escapeData(s) {
1606
+ return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
1607
+ }
1608
+ function escapeProperty(s) {
1609
+ return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
1610
+ }
1611
+
1612
+ const StatusMap = {
1613
+ fail: "failed",
1614
+ only: "pending",
1615
+ pass: "passed",
1616
+ run: "pending",
1617
+ skip: "skipped",
1618
+ todo: "todo",
1619
+ queued: "pending"
1620
+ };
1621
+ class JsonReporter {
1622
+ start = 0;
1623
+ ctx;
1624
+ options;
1625
+ constructor(options) {
1626
+ this.options = options;
1627
+ }
1628
+ onInit(ctx) {
1629
+ this.ctx = ctx;
1630
+ this.start = Date.now();
1631
+ }
1632
+ async logTasks(files, coverageMap) {
1633
+ const suites = getSuites(files);
1634
+ const numTotalTestSuites = suites.length;
1635
+ const tests = getTests(files);
1636
+ const numTotalTests = tests.length;
1637
+ const numFailedTestSuites = suites.filter((s) => s.result?.state === "fail").length;
1638
+ const numPendingTestSuites = suites.filter(
1639
+ (s) => s.result?.state === "run" || s.result?.state === "queued" || s.mode === "todo"
1640
+ ).length;
1641
+ const numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites;
1642
+ const numFailedTests = tests.filter(
1643
+ (t) => t.result?.state === "fail"
1644
+ ).length;
1645
+ const numPassedTests = tests.filter((t) => t.result?.state === "pass").length;
1646
+ const numPendingTests = tests.filter(
1647
+ (t) => t.result?.state === "run" || t.result?.state === "queued" || t.mode === "skip" || t.result?.state === "skip"
1648
+ ).length;
1649
+ const numTodoTests = tests.filter((t) => t.mode === "todo").length;
1650
+ const testResults = [];
1651
+ const success = !!(files.length > 0 || this.ctx.config.passWithNoTests) && numFailedTestSuites === 0 && numFailedTests === 0;
1652
+ for (const file of files) {
1653
+ const tests2 = getTests([file]);
1654
+ let startTime = tests2.reduce(
1655
+ (prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY),
1656
+ Number.POSITIVE_INFINITY
1657
+ );
1658
+ if (startTime === Number.POSITIVE_INFINITY) {
1659
+ startTime = this.start;
1660
+ }
1661
+ const endTime = tests2.reduce(
1662
+ (prev, next) => Math.max(
1663
+ prev,
1664
+ (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)
1665
+ ),
1666
+ startTime
1667
+ );
1668
+ const assertionResults = tests2.map((t) => {
1669
+ const ancestorTitles = [];
1670
+ let iter = t.suite;
1671
+ while (iter) {
1672
+ ancestorTitles.push(iter.name);
1673
+ iter = iter.suite;
1674
+ }
1675
+ ancestorTitles.reverse();
1676
+ return {
1677
+ ancestorTitles,
1678
+ fullName: t.name ? [...ancestorTitles, t.name].join(" ") : ancestorTitles.join(" "),
1679
+ status: StatusMap[t.result?.state || t.mode] || "skipped",
1680
+ title: t.name,
1681
+ duration: t.result?.duration,
1682
+ failureMessages: t.result?.errors?.map((e) => e.stack || e.message) || [],
1683
+ location: t.location,
1684
+ meta: t.meta
1685
+ };
1686
+ });
1687
+ if (tests2.some((t) => t.result?.state === "run" || t.result?.state === "queued")) {
1688
+ this.ctx.logger.warn(
1689
+ "WARNING: Some tests are still running when generating the JSON report.This is likely an internal bug in Vitest.Please report it to https://github.com/vitest-dev/vitest/issues"
1690
+ );
1691
+ }
1692
+ const hasFailedTests = tests2.some((t) => t.result?.state === "fail");
1693
+ testResults.push({
1694
+ assertionResults,
1695
+ startTime,
1696
+ endTime,
1697
+ status: file.result?.state === "fail" || hasFailedTests ? "failed" : "passed",
1698
+ message: file.result?.errors?.[0]?.message ?? "",
1699
+ name: file.filepath
1700
+ });
1701
+ }
1702
+ const result = {
1703
+ numTotalTestSuites,
1704
+ numPassedTestSuites,
1705
+ numFailedTestSuites,
1706
+ numPendingTestSuites,
1707
+ numTotalTests,
1708
+ numPassedTests,
1709
+ numFailedTests,
1710
+ numPendingTests,
1711
+ numTodoTests,
1712
+ snapshot: this.ctx.snapshot.summary,
1713
+ startTime: this.start,
1714
+ success,
1715
+ testResults,
1716
+ coverageMap
1717
+ };
1718
+ await this.writeReport(JSON.stringify(result));
1719
+ }
1720
+ async onFinished(files = this.ctx.state.getFiles(), _errors = [], coverageMap) {
1721
+ await this.logTasks(files, coverageMap);
1722
+ }
1723
+ /**
1724
+ * Writes the report to an output file if specified in the config,
1725
+ * or logs it to the console otherwise.
1726
+ * @param report
1727
+ */
1728
+ async writeReport(report) {
1729
+ const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
1730
+ if (outputFile) {
1731
+ const reportFile = resolve(this.ctx.config.root, outputFile);
1732
+ const outputDirectory = dirname(reportFile);
1733
+ if (!existsSync(outputDirectory)) {
1734
+ await promises.mkdir(outputDirectory, { recursive: true });
1735
+ }
1736
+ await promises.writeFile(reportFile, report, "utf-8");
1737
+ this.ctx.logger.log(`JSON report written to ${reportFile}`);
1738
+ } else {
1739
+ this.ctx.logger.log(report);
1740
+ }
1741
+ }
1742
+ }
1743
+
1744
+ class IndentedLogger {
1745
+ constructor(baseLog) {
1746
+ this.baseLog = baseLog;
1747
+ }
1748
+ currentIndent = "";
1749
+ indent() {
1750
+ this.currentIndent += " ";
1751
+ }
1752
+ unindent() {
1753
+ this.currentIndent = this.currentIndent.substring(
1754
+ 0,
1755
+ this.currentIndent.length - 4
1756
+ );
1757
+ }
1758
+ log(text) {
1759
+ return this.baseLog(this.currentIndent + text);
1760
+ }
1761
+ }
1762
+
1763
+ function flattenTasks$1(task, baseName = "") {
1764
+ const base = baseName ? `${baseName} > ` : "";
1765
+ if (task.type === "suite") {
1766
+ return task.tasks.flatMap(
1767
+ (child) => flattenTasks$1(child, `${base}${task.name}`)
1768
+ );
1769
+ } else {
1770
+ return [
1771
+ {
1772
+ ...task,
1773
+ name: `${base}${task.name}`
1774
+ }
1775
+ ];
1776
+ }
1777
+ }
1778
+ function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
1779
+ let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
1780
+ value = String(value || "").replace(regex, "");
1781
+ {
1782
+ regex = new RegExp(
1783
+ /* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
1784
+ "([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|\\uD83F[\\uDFFE\\uDFFF]|(?:\\uD87F[\\uDFFE\\uDFFF])|\\uD8BF[\\uDFFE\\uDFFF]|\\uD8FF[\\uDFFE\\uDFFF]|(?:\\uD93F[\\uDFFE\\uDFFF])|\\uD97F[\\uDFFE\\uDFFF]|\\uD9BF[\\uDFFE\\uDFFF]|\\uD9FF[\\uDFFE\\uDFFF]|\\uDA3F[\\uDFFE\\uDFFF]|\\uDA7F[\\uDFFE\\uDFFF]|\\uDABF[\\uDFFE\\uDFFF]|(?:\\uDAFF[\\uDFFE\\uDFFF])|\\uDB3F[\\uDFFE\\uDFFF]|\\uDB7F[\\uDFFE\\uDFFF]|(?:\\uDBBF[\\uDFFE\\uDFFF])|\\uDBFF[\\uDFFE\\uDFFF](?:[\\0-\\t\\v\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))",
1785
+ "g"
1786
+ /* eslint-enable */
1787
+ );
1788
+ value = value.replace(regex, "");
1789
+ }
1790
+ return value;
1791
+ }
1792
+ function escapeXML(value) {
1793
+ return removeInvalidXMLCharacters(
1794
+ String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
1795
+ }
1796
+ function executionTime(durationMS) {
1797
+ return (durationMS / 1e3).toLocaleString("en-US", {
1798
+ useGrouping: false,
1799
+ maximumFractionDigits: 10
1800
+ });
1801
+ }
1802
+ function getDuration(task) {
1803
+ const duration = task.result?.duration ?? 0;
1804
+ return executionTime(duration);
1805
+ }
1806
+ class JUnitReporter {
1807
+ ctx;
1808
+ reportFile;
1809
+ baseLog;
1810
+ logger;
1811
+ _timeStart = /* @__PURE__ */ new Date();
1812
+ fileFd;
1813
+ options;
1814
+ constructor(options) {
1815
+ this.options = { ...options };
1816
+ this.options.includeConsoleOutput ??= true;
1817
+ }
1818
+ async onInit(ctx) {
1819
+ this.ctx = ctx;
1820
+ const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "junit");
1821
+ if (outputFile) {
1822
+ this.reportFile = resolve(this.ctx.config.root, outputFile);
1823
+ const outputDirectory = dirname(this.reportFile);
1824
+ if (!existsSync(outputDirectory)) {
1825
+ await promises.mkdir(outputDirectory, { recursive: true });
1826
+ }
1827
+ const fileFd = await promises.open(this.reportFile, "w+");
1828
+ this.fileFd = fileFd;
1829
+ this.baseLog = async (text) => {
1830
+ if (!this.fileFd) {
1831
+ this.fileFd = await promises.open(this.reportFile, "w+");
1832
+ }
1833
+ await promises.writeFile(this.fileFd, `${text}
1834
+ `);
1835
+ };
1836
+ } else {
1837
+ this.baseLog = async (text) => this.ctx.logger.log(text);
1838
+ }
1839
+ this._timeStart = /* @__PURE__ */ new Date();
1840
+ this.logger = new IndentedLogger(this.baseLog);
1841
+ }
1842
+ async writeElement(name, attrs, children) {
1843
+ const pairs = [];
1844
+ for (const key in attrs) {
1845
+ const attr = attrs[key];
1846
+ if (attr === void 0) {
1847
+ continue;
1848
+ }
1849
+ pairs.push(`${key}="${escapeXML(attr)}"`);
1850
+ }
1851
+ await this.logger.log(
1852
+ `<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`
1853
+ );
1854
+ this.logger.indent();
1855
+ await children.call(this);
1856
+ this.logger.unindent();
1857
+ await this.logger.log(`</${name}>`);
1858
+ }
1859
+ async writeLogs(task, type) {
1860
+ if (task.logs == null || task.logs.length === 0) {
1861
+ return;
1862
+ }
1863
+ const logType = type === "err" ? "stderr" : "stdout";
1864
+ const logs = task.logs.filter((log) => log.type === logType);
1865
+ if (logs.length === 0) {
1866
+ return;
1867
+ }
1868
+ await this.writeElement(`system-${type}`, {}, async () => {
1869
+ for (const log of logs) {
1870
+ await this.baseLog(escapeXML(log.content));
1871
+ }
1872
+ });
1873
+ }
1874
+ async writeTasks(tasks, filename) {
1875
+ for (const task of tasks) {
1876
+ let classname = filename;
1877
+ const templateVars = {
1878
+ filename: task.file.name,
1879
+ filepath: task.file.filepath
1880
+ };
1881
+ if (typeof this.options.classnameTemplate === "function") {
1882
+ classname = this.options.classnameTemplate(templateVars);
1883
+ } else if (typeof this.options.classnameTemplate === "string") {
1884
+ classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
1885
+ } else if (typeof this.options.classname === "string") {
1886
+ classname = this.options.classname;
1887
+ }
1888
+ await this.writeElement(
1889
+ "testcase",
1890
+ {
1891
+ classname,
1892
+ file: this.options.addFileAttribute ? filename : void 0,
1893
+ name: task.name,
1894
+ time: getDuration(task)
1895
+ },
1896
+ async () => {
1897
+ if (this.options.includeConsoleOutput) {
1898
+ await this.writeLogs(task, "out");
1899
+ await this.writeLogs(task, "err");
1900
+ }
1901
+ if (task.mode === "skip" || task.mode === "todo") {
1902
+ await this.logger.log("<skipped/>");
1903
+ }
1904
+ if (task.result?.state === "fail") {
1905
+ const errors = task.result.errors || [];
1906
+ for (const error of errors) {
1907
+ await this.writeElement(
1908
+ "failure",
1909
+ {
1910
+ message: error?.message,
1911
+ type: error?.name ?? error?.nameStr
1912
+ },
1913
+ async () => {
1914
+ if (!error) {
1915
+ return;
1916
+ }
1917
+ const result = capturePrintError(
1918
+ error,
1919
+ this.ctx,
1920
+ { project: this.ctx.getProjectByName(task.file.projectName || ""), task }
1921
+ );
1922
+ await this.baseLog(
1923
+ escapeXML(stripVTControlCharacters(result.output.trim()))
1924
+ );
1925
+ }
1926
+ );
1927
+ }
1928
+ }
1929
+ }
1930
+ );
1931
+ }
1932
+ }
1933
+ async onFinished(files = this.ctx.state.getFiles()) {
1934
+ await this.logger.log('<?xml version="1.0" encoding="UTF-8" ?>');
1935
+ const transformed = files.map((file) => {
1936
+ const tasks = file.tasks.flatMap((task) => flattenTasks$1(task));
1937
+ const stats2 = tasks.reduce(
1938
+ (stats3, task) => {
1939
+ return {
1940
+ passed: stats3.passed + Number(task.result?.state === "pass"),
1941
+ failures: stats3.failures + Number(task.result?.state === "fail"),
1942
+ skipped: stats3.skipped + Number(task.mode === "skip" || task.mode === "todo")
1943
+ };
1944
+ },
1945
+ {
1946
+ passed: 0,
1947
+ failures: 0,
1948
+ skipped: 0
1949
+ }
1950
+ );
1951
+ const suites = getSuites(file);
1952
+ for (const suite of suites) {
1953
+ if (suite.result?.errors) {
1954
+ tasks.push(suite);
1955
+ stats2.failures += 1;
1956
+ }
1957
+ }
1958
+ if (tasks.length === 0 && file.result?.state === "fail") {
1959
+ stats2.failures = 1;
1960
+ tasks.push({
1961
+ id: file.id,
1962
+ type: "test",
1963
+ name: file.name,
1964
+ mode: "run",
1965
+ result: file.result,
1966
+ meta: {},
1967
+ // NOTE: not used in JUnitReporter
1968
+ context: null,
1969
+ suite: null,
1970
+ file: null
1971
+ });
1972
+ }
1973
+ return {
1974
+ ...file,
1975
+ tasks,
1976
+ stats: stats2
1977
+ };
1978
+ });
1979
+ const stats = transformed.reduce(
1980
+ (stats2, file) => {
1981
+ stats2.tests += file.tasks.length;
1982
+ stats2.failures += file.stats.failures;
1983
+ stats2.time += file.result?.duration || 0;
1984
+ return stats2;
1985
+ },
1986
+ {
1987
+ name: this.options.suiteName || "vitest tests",
1988
+ tests: 0,
1989
+ failures: 0,
1990
+ errors: 0,
1991
+ // we cannot detect those
1992
+ time: 0
1993
+ }
1994
+ );
1995
+ await this.writeElement("testsuites", { ...stats, time: executionTime(stats.time) }, async () => {
1996
+ for (const file of transformed) {
1997
+ const filename = relative(this.ctx.config.root, file.filepath);
1998
+ await this.writeElement(
1999
+ "testsuite",
2000
+ {
2001
+ name: filename,
2002
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2003
+ hostname: hostname(),
2004
+ tests: file.tasks.length,
2005
+ failures: file.stats.failures,
2006
+ errors: 0,
2007
+ // An errored test is one that had an unanticipated problem. We cannot detect those.
2008
+ skipped: file.stats.skipped,
2009
+ time: getDuration(file)
2010
+ },
2011
+ async () => {
2012
+ await this.writeTasks(file.tasks, filename);
2013
+ }
2014
+ );
2015
+ }
2016
+ });
2017
+ if (this.reportFile) {
2018
+ this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
2019
+ }
2020
+ await this.fileFd?.close();
2021
+ this.fileFd = void 0;
2022
+ }
2023
+ }
2024
+
2025
+ function yamlString(str) {
2026
+ return `"${str.replace(/"/g, '\\"')}"`;
2027
+ }
2028
+ function tapString(str) {
2029
+ return str.replace(/\\/g, "\\\\").replace(/#/g, "\\#").replace(/\n/g, " ");
2030
+ }
2031
+ class TapReporter {
2032
+ ctx;
2033
+ logger;
2034
+ onInit(ctx) {
2035
+ this.ctx = ctx;
2036
+ this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
2037
+ }
2038
+ static getComment(task) {
2039
+ if (task.mode === "skip") {
2040
+ return " # SKIP";
2041
+ } else if (task.mode === "todo") {
2042
+ return " # TODO";
2043
+ } else if (task.result?.duration != null) {
2044
+ return ` # time=${task.result.duration.toFixed(2)}ms`;
2045
+ } else {
2046
+ return "";
2047
+ }
2048
+ }
2049
+ logErrorDetails(error, stack) {
2050
+ const errorName = error.name || error.nameStr || "Unknown Error";
2051
+ this.logger.log(`name: ${yamlString(String(errorName))}`);
2052
+ this.logger.log(`message: ${yamlString(String(error.message))}`);
2053
+ if (stack) {
2054
+ this.logger.log(
2055
+ `stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`
2056
+ );
2057
+ }
2058
+ }
2059
+ logTasks(tasks) {
2060
+ this.logger.log(`1..${tasks.length}`);
2061
+ for (const [i, task] of tasks.entries()) {
2062
+ const id = i + 1;
2063
+ const ok = task.result?.state === "pass" || task.mode === "skip" || task.mode === "todo" ? "ok" : "not ok";
2064
+ const comment = TapReporter.getComment(task);
2065
+ if (task.type === "suite" && task.tasks.length > 0) {
2066
+ this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment} {`);
2067
+ this.logger.indent();
2068
+ this.logTasks(task.tasks);
2069
+ this.logger.unindent();
2070
+ this.logger.log("}");
2071
+ } else {
2072
+ this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
2073
+ const project = this.ctx.getProjectByName(task.file.projectName || "");
2074
+ if (task.result?.state === "fail" && task.result.errors) {
2075
+ this.logger.indent();
2076
+ task.result.errors.forEach((error) => {
2077
+ const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, {
2078
+ frameFilter: this.ctx.config.onStackTrace
2079
+ });
2080
+ const stack = stacks[0];
2081
+ this.logger.log("---");
2082
+ this.logger.log("error:");
2083
+ this.logger.indent();
2084
+ this.logErrorDetails(error);
2085
+ this.logger.unindent();
2086
+ if (stack) {
2087
+ this.logger.log(
2088
+ `at: ${yamlString(
2089
+ `${stack.file}:${stack.line}:${stack.column}`
2090
+ )}`
2091
+ );
2092
+ }
2093
+ if (error.showDiff) {
2094
+ this.logger.log(`actual: ${yamlString(error.actual)}`);
2095
+ this.logger.log(`expected: ${yamlString(error.expected)}`);
2096
+ }
2097
+ });
2098
+ this.logger.log("...");
2099
+ this.logger.unindent();
2100
+ }
2101
+ }
2102
+ }
2103
+ }
2104
+ onFinished(files = this.ctx.state.getFiles()) {
2105
+ this.logger.log("TAP version 13");
2106
+ this.logTasks(files);
2107
+ }
2108
+ }
2109
+
2110
+ function flattenTasks(task, baseName = "") {
2111
+ const base = baseName ? `${baseName} > ` : "";
2112
+ if (task.type === "suite" && task.tasks.length > 0) {
2113
+ return task.tasks.flatMap(
2114
+ (child) => flattenTasks(child, `${base}${task.name}`)
2115
+ );
2116
+ } else {
2117
+ return [
2118
+ {
2119
+ ...task,
2120
+ name: `${base}${task.name}`
2121
+ }
2122
+ ];
2123
+ }
2124
+ }
2125
+ class TapFlatReporter extends TapReporter {
2126
+ onInit(ctx) {
2127
+ super.onInit(ctx);
2128
+ }
2129
+ onFinished(files = this.ctx.state.getFiles()) {
2130
+ this.ctx.logger.log("TAP version 13");
2131
+ const flatTasks = files.flatMap((task) => flattenTasks(task));
2132
+ this.logTasks(flatTasks);
2133
+ }
2134
+ }
2135
+
2136
+ class VerboseReporter extends DefaultReporter {
2137
+ verbose = true;
2138
+ renderSucceed = true;
2139
+ printTask(task) {
2140
+ if (this.isTTY) {
2141
+ return super.printTask(task);
2142
+ }
2143
+ if (task.type !== "test" || !task.result?.state || task.result?.state === "run" || task.result?.state === "queued") {
2144
+ return;
2145
+ }
2146
+ const duration = task.result.duration;
2147
+ let title = ` ${getStateSymbol(task)} `;
2148
+ if (task.file.projectName) {
2149
+ title += formatProjectName(task.file.projectName);
2150
+ }
2151
+ title += getFullName(task, c.dim(" > "));
2152
+ if (duration != null && duration > this.ctx.config.slowTestThreshold) {
2153
+ title += c.yellow(` ${Math.round(duration)}${c.dim("ms")}`);
2154
+ }
2155
+ if (this.ctx.config.logHeapUsage && task.result.heap != null) {
2156
+ title += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`);
2157
+ }
2158
+ if (task.result?.note) {
2159
+ title += c.dim(c.gray(` [${task.result.note}]`));
2160
+ }
2161
+ this.ctx.logger.log(title);
2162
+ if (task.result.state === "fail") {
2163
+ task.result.errors?.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
2164
+ }
2165
+ }
2166
+ }
2167
+
2168
+ function createBenchmarkJsonReport(files) {
2169
+ const report = { files: [] };
2170
+ for (const file of files) {
2171
+ const groups = [];
2172
+ for (const task of getTasks(file)) {
2173
+ if (task?.type === "suite") {
2174
+ const benchmarks = [];
2175
+ for (const t of task.tasks) {
2176
+ const benchmark = t.meta.benchmark && t.result?.benchmark;
2177
+ if (benchmark) {
2178
+ benchmarks.push({ id: t.id, ...benchmark, samples: [] });
2179
+ }
2180
+ }
2181
+ if (benchmarks.length) {
2182
+ groups.push({
2183
+ fullName: getFullName(task, " > "),
2184
+ benchmarks
2185
+ });
2186
+ }
2187
+ }
2188
+ }
2189
+ report.files.push({
2190
+ filepath: file.filepath,
2191
+ groups
2192
+ });
2193
+ }
2194
+ return report;
2195
+ }
2196
+ function flattenFormattedBenchmarkReport(report) {
2197
+ const flat = {};
2198
+ for (const file of report.files) {
2199
+ for (const group of file.groups) {
2200
+ for (const t of group.benchmarks) {
2201
+ flat[t.id] = t;
2202
+ }
2203
+ }
2204
+ }
2205
+ return flat;
2206
+ }
2207
+
2208
+ const outputMap = /* @__PURE__ */ new WeakMap();
2209
+ function formatNumber(number) {
2210
+ const res = String(number.toFixed(number < 100 ? 4 : 2)).split(".");
2211
+ return res[0].replace(/(?=(?:\d{3})+$)\B/g, ",") + (res[1] ? `.${res[1]}` : "");
2212
+ }
2213
+ const tableHead = [
2214
+ "name",
2215
+ "hz",
2216
+ "min",
2217
+ "max",
2218
+ "mean",
2219
+ "p75",
2220
+ "p99",
2221
+ "p995",
2222
+ "p999",
2223
+ "rme",
2224
+ "samples"
2225
+ ];
2226
+ function renderBenchmarkItems(result) {
2227
+ return [
2228
+ result.name,
2229
+ formatNumber(result.hz || 0),
2230
+ formatNumber(result.min || 0),
2231
+ formatNumber(result.max || 0),
2232
+ formatNumber(result.mean || 0),
2233
+ formatNumber(result.p75 || 0),
2234
+ formatNumber(result.p99 || 0),
2235
+ formatNumber(result.p995 || 0),
2236
+ formatNumber(result.p999 || 0),
2237
+ `\xB1${(result.rme || 0).toFixed(2)}%`,
2238
+ (result.sampleCount || 0).toString()
2239
+ ];
2240
+ }
2241
+ function computeColumnWidths(results) {
2242
+ const rows = [tableHead, ...results.map((v) => renderBenchmarkItems(v))];
2243
+ return Array.from(tableHead, (_, i) => Math.max(...rows.map((row) => stripVTControlCharacters(row[i]).length)));
2244
+ }
2245
+ function padRow(row, widths) {
2246
+ return row.map(
2247
+ (v, i) => i ? v.padStart(widths[i], " ") : v.padEnd(widths[i], " ")
2248
+ // name
2249
+ );
2250
+ }
2251
+ function renderTableHead(widths) {
2252
+ return " ".repeat(3) + padRow(tableHead, widths).map(c.bold).join(" ");
2253
+ }
2254
+ function renderBenchmark(result, widths) {
2255
+ const padded = padRow(renderBenchmarkItems(result), widths);
2256
+ return [
2257
+ padded[0],
2258
+ // name
2259
+ c.blue(padded[1]),
2260
+ // hz
2261
+ c.cyan(padded[2]),
2262
+ // min
2263
+ c.cyan(padded[3]),
2264
+ // max
2265
+ c.cyan(padded[4]),
2266
+ // mean
2267
+ c.cyan(padded[5]),
2268
+ // p75
2269
+ c.cyan(padded[6]),
2270
+ // p99
2271
+ c.cyan(padded[7]),
2272
+ // p995
2273
+ c.cyan(padded[8]),
2274
+ // p999
2275
+ c.dim(padded[9]),
2276
+ // rem
2277
+ c.dim(padded[10])
2278
+ // sample
2279
+ ].join(" ");
2280
+ }
2281
+ function renderTable(options) {
2282
+ const output = [];
2283
+ const benchMap = {};
2284
+ for (const task of options.tasks) {
2285
+ if (task.meta.benchmark && task.result?.benchmark) {
2286
+ benchMap[task.id] = {
2287
+ current: task.result.benchmark,
2288
+ baseline: options.compare?.[task.id]
2289
+ };
2290
+ }
2291
+ }
2292
+ const benchCount = Object.entries(benchMap).length;
2293
+ const columnWidths = computeColumnWidths(
2294
+ Object.values(benchMap).flatMap((v) => [v.current, v.baseline]).filter(notNullish)
2295
+ );
2296
+ let idx = 0;
2297
+ const padding = " ".repeat(1 );
2298
+ for (const task of options.tasks) {
2299
+ const duration = task.result?.duration;
2300
+ const bench = benchMap[task.id];
2301
+ let prefix = "";
2302
+ if (idx === 0 && task.meta?.benchmark) {
2303
+ prefix += `${renderTableHead(columnWidths)}
2304
+ ${padding}`;
2305
+ }
2306
+ prefix += ` ${getStateSymbol(task)} `;
2307
+ let suffix = "";
2308
+ if (task.type === "suite") {
2309
+ suffix += c.dim(` (${getTests(task).length})`);
2310
+ }
2311
+ if (task.mode === "skip" || task.mode === "todo") {
2312
+ suffix += c.dim(c.gray(" [skipped]"));
2313
+ }
2314
+ if (duration != null && duration > options.slowTestThreshold) {
2315
+ suffix += c.yellow(` ${Math.round(duration)}${c.dim("ms")}`);
2316
+ }
2317
+ if (options.showHeap && task.result?.heap != null) {
2318
+ suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`);
2319
+ }
2320
+ if (bench) {
2321
+ let body = renderBenchmark(bench.current, columnWidths);
2322
+ if (options.compare && bench.baseline) {
2323
+ if (bench.current.hz) {
2324
+ const diff = bench.current.hz / bench.baseline.hz;
2325
+ const diffFixed = diff.toFixed(2);
2326
+ if (diffFixed === "1.0.0") {
2327
+ body += c.gray(` [${diffFixed}x]`);
2328
+ }
2329
+ if (diff > 1) {
2330
+ body += c.blue(` [${diffFixed}x] \u21D1`);
2331
+ } else {
2332
+ body += c.red(` [${diffFixed}x] \u21D3`);
2333
+ }
2334
+ }
2335
+ output.push(padding + prefix + body + suffix);
2336
+ const bodyBaseline = renderBenchmark(bench.baseline, columnWidths);
2337
+ output.push(`${padding} ${bodyBaseline} ${c.dim("(baseline)")}`);
2338
+ } else {
2339
+ if (bench.current.rank === 1 && benchCount > 1) {
2340
+ body += c.bold(c.green(" fastest"));
2341
+ }
2342
+ if (bench.current.rank === benchCount && benchCount > 2) {
2343
+ body += c.bold(c.gray(" slowest"));
2344
+ }
2345
+ output.push(padding + prefix + body + suffix);
2346
+ }
2347
+ } else {
2348
+ output.push(padding + prefix + task.name + suffix);
2349
+ }
2350
+ if (task.result?.state !== "pass" && outputMap.get(task) != null) {
2351
+ let data = outputMap.get(task);
2352
+ if (typeof data === "string") {
2353
+ data = stripVTControlCharacters(data.trim().split("\n").filter(Boolean).pop());
2354
+ if (data === "") {
2355
+ data = void 0;
2356
+ }
2357
+ }
2358
+ if (data != null) {
2359
+ const out = ` ${" ".repeat(options.level)}${F_RIGHT} ${data}`;
2360
+ output.push(c.gray(truncateString(out, options.columns)));
2361
+ }
2362
+ }
2363
+ idx++;
2364
+ }
2365
+ return output.filter(Boolean).join("\n");
2366
+ }
2367
+
2368
+ class BenchmarkReporter extends DefaultReporter {
2369
+ compare;
2370
+ async onInit(ctx) {
2371
+ super.onInit(ctx);
2372
+ if (this.ctx.config.benchmark?.compare) {
2373
+ const compareFile = pathe.resolve(
2374
+ this.ctx.config.root,
2375
+ this.ctx.config.benchmark?.compare
2376
+ );
2377
+ try {
2378
+ this.compare = flattenFormattedBenchmarkReport(
2379
+ JSON.parse(await fs.promises.readFile(compareFile, "utf-8"))
2380
+ );
2381
+ } catch (e) {
2382
+ this.error(`Failed to read '${compareFile}'`, e);
2383
+ }
2384
+ }
2385
+ }
2386
+ onTaskUpdate(packs) {
2387
+ for (const pack of packs) {
2388
+ const task = this.ctx.state.idMap.get(pack[0]);
2389
+ if (task?.type === "suite" && task.result?.state !== "run") {
2390
+ task.tasks.filter((task2) => task2.result?.benchmark).sort((benchA, benchB) => benchA.result.benchmark.mean - benchB.result.benchmark.mean).forEach((bench, idx) => {
2391
+ bench.result.benchmark.rank = Number(idx) + 1;
2392
+ });
2393
+ }
2394
+ }
2395
+ super.onTaskUpdate(packs);
2396
+ }
2397
+ printTask(task) {
2398
+ if (task?.type !== "suite" || !task.result?.state || task.result?.state === "run" || task.result?.state === "queued") {
2399
+ return;
2400
+ }
2401
+ const benches = task.tasks.filter((t) => t.meta.benchmark);
2402
+ const duration = task.result.duration;
2403
+ if (benches.length > 0 && benches.every((t) => t.result?.state !== "run" && t.result?.state !== "queued")) {
2404
+ let title = `
2405
+ ${getStateSymbol(task)} ${formatProjectName(task.file.projectName)}${getFullName(task, c.dim(" > "))}`;
2406
+ if (duration != null && duration > this.ctx.config.slowTestThreshold) {
2407
+ title += c.yellow(` ${Math.round(duration)}${c.dim("ms")}`);
2408
+ }
2409
+ this.log(title);
2410
+ this.log(renderTable({
2411
+ tasks: benches,
2412
+ level: 1,
2413
+ shallow: true,
2414
+ columns: this.ctx.logger.getColumns(),
2415
+ compare: this.compare,
2416
+ showHeap: this.ctx.config.logHeapUsage,
2417
+ slowTestThreshold: this.ctx.config.slowTestThreshold
2418
+ }));
2419
+ }
2420
+ }
2421
+ async onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
2422
+ super.onFinished(files, errors);
2423
+ let outputFile = this.ctx.config.benchmark?.outputJson;
2424
+ if (outputFile) {
2425
+ outputFile = pathe.resolve(this.ctx.config.root, outputFile);
2426
+ const outputDirectory = pathe.dirname(outputFile);
2427
+ if (!fs.existsSync(outputDirectory)) {
2428
+ await fs.promises.mkdir(outputDirectory, { recursive: true });
2429
+ }
2430
+ const output = createBenchmarkJsonReport(files);
2431
+ await fs.promises.writeFile(outputFile, JSON.stringify(output, null, 2));
2432
+ this.log(`Benchmark report written to ${outputFile}`);
2433
+ }
2434
+ }
2435
+ }
2436
+
2437
+ class VerboseBenchmarkReporter extends BenchmarkReporter {
2438
+ verbose = true;
2439
+ }
2440
+
2441
+ const BenchmarkReportsMap = {
2442
+ default: BenchmarkReporter,
2443
+ verbose: VerboseBenchmarkReporter
2444
+ };
2445
+
2446
+ const ReportersMap = {
2447
+ "default": DefaultReporter,
2448
+ "basic": BasicReporter,
2449
+ "blob": BlobReporter,
2450
+ "verbose": VerboseReporter,
2451
+ "dot": DotReporter,
2452
+ "json": JsonReporter,
2453
+ "tap": TapReporter,
2454
+ "tap-flat": TapFlatReporter,
2455
+ "junit": JUnitReporter,
2456
+ "hanging-process": HangingProcessReporter,
2457
+ "github-actions": GithubActionsReporter
2458
+ };
2459
+
2460
+ export { BasicReporter as B, DefaultReporter as D, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, ReportersMap as R, TapFlatReporter as T, VerboseBenchmarkReporter as V, BenchmarkReporter as a, BenchmarkReportsMap as b, DotReporter as c, JUnitReporter as d, TapReporter as e, VerboseReporter as f, printError as g, generateCodeFrame as h, BlobReporter as i, parse as p, readBlobs as r, stringify as s };