vitest 4.0.0-beta.1 → 4.0.0-beta.11

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 (90) hide show
  1. package/LICENSE.md +83 -2
  2. package/dist/browser.d.ts +19 -16
  3. package/dist/browser.js +11 -7
  4. package/dist/chunks/{benchmark.CYdenmiT.js → benchmark.LXhJ0F0X.js} +7 -9
  5. package/dist/chunks/{benchmark.d.BwvBVTda.d.ts → benchmark.d.DAaHLpsq.d.ts} +4 -4
  6. package/dist/chunks/{browser.d.q8Z0P0q1.d.ts → browser.d.Dx7DO_Ce.d.ts} +5 -5
  7. package/dist/chunks/{cac.D3EzDDZd.js → cac.elvK37c9.js} +71 -153
  8. package/dist/chunks/{cli-api.Dn5gKePv.js → cli-api.C7plPyhs.js} +1376 -1693
  9. package/dist/chunks/{config.d.HJdfX-8k.d.ts → config.d.B_LthbQq.d.ts} +58 -63
  10. package/dist/chunks/{console.CtFJOzRO.js → console.CiTi59Jy.js} +35 -71
  11. package/dist/chunks/{constants.DnKduX2e.js → constants.D_Q9UYh-.js} +1 -9
  12. package/dist/chunks/{coverage.Cwa-XhJt.js → coverage.CG6Uhorw.js} +522 -792
  13. package/dist/chunks/{coverage.DVF1vEu8.js → coverage.D_JHT54q.js} +2 -2
  14. package/dist/chunks/{coverage.d.S9RMNXIe.d.ts → coverage.d.BZtK59WP.d.ts} +10 -8
  15. package/dist/chunks/{creator.GK6I-cL4.js → creator.08Gi-vCA.js} +93 -77
  16. package/dist/chunks/{date.Bq6ZW5rf.js → date.-jtEtIeV.js} +6 -17
  17. package/dist/chunks/{environment.d.CUq4cUgQ.d.ts → environment.d.BsToaxti.d.ts} +27 -6
  18. package/dist/chunks/{git.BVQ8w_Sw.js → git.BFNcloKD.js} +1 -2
  19. package/dist/chunks/{global.d.CVbXEflG.d.ts → global.d.BK3X7FW1.d.ts} +2 -5
  20. package/dist/chunks/{globals.Cxal6MLI.js → globals.BjvYA-AD.js} +11 -9
  21. package/dist/chunks/{index.BWf_gE5n.js → index.AZOjjqWP.js} +7 -6
  22. package/dist/chunks/{index.B521nVV-.js → index.Bgo3tNWt.js} +23 -4
  23. package/dist/chunks/{index.TfbsX-3I.js → index.BhY64fF0.js} +16 -26
  24. package/dist/chunks/{index.CZI_8rVt.js → index.BwBttQPf.js} +340 -663
  25. package/dist/chunks/{index.CmSc2RE5.js → index.DIWhzsUh.js} +72 -118
  26. package/dist/chunks/{inspector.C914Efll.js → inspector.CvQD-Nie.js} +10 -25
  27. package/dist/chunks/moduleRunner.d.BNa-CL9e.d.ts +201 -0
  28. package/dist/chunks/{node.fjCdwEIl.js → node.BsdMi6DV.js} +2 -2
  29. package/dist/chunks/{plugin.d.C2EcJUjo.d.ts → plugin.d.C5phQR6o.d.ts} +1 -1
  30. package/dist/chunks/{reporters.d.DxZg19fy.d.ts → reporters.d.CVzhsTvK.d.ts} +1233 -1293
  31. package/dist/chunks/resolveSnapshotEnvironment.DQVamkje.js +81 -0
  32. package/dist/chunks/rpc.jKGRSXIH.js +65 -0
  33. package/dist/chunks/{setup-common.D7ZqXFx-.js → setup-common.NAWRuMRP.js} +18 -30
  34. package/dist/chunks/startModuleRunner.oAuCu1yL.js +682 -0
  35. package/dist/chunks/{suite.d.FvehnV49.d.ts → suite.d.BJWk38HB.d.ts} +1 -1
  36. package/dist/chunks/test.KC5tH8hC.js +214 -0
  37. package/dist/chunks/typechecker.gXq-5P3n.js +1438 -0
  38. package/dist/chunks/{utils.XdZDrNZV.js → utils.DGKhod2J.js} +9 -28
  39. package/dist/chunks/{vi.bdSIJ99Y.js → vi.CiJ0Laa6.js} +159 -306
  40. package/dist/chunks/worker.d.B_Fd9M_w.d.ts +100 -0
  41. package/dist/chunks/worker.rPGLlbkW.js +200 -0
  42. package/dist/cli.js +8 -6
  43. package/dist/config.cjs +3 -9
  44. package/dist/config.d.ts +49 -54
  45. package/dist/config.js +1 -1
  46. package/dist/coverage.d.ts +27 -26
  47. package/dist/coverage.js +6 -8
  48. package/dist/environments.d.ts +9 -13
  49. package/dist/environments.js +1 -1
  50. package/dist/index.d.ts +38 -45
  51. package/dist/index.js +10 -10
  52. package/dist/module-evaluator.d.ts +13 -0
  53. package/dist/module-evaluator.js +276 -0
  54. package/dist/module-runner.js +15 -0
  55. package/dist/node.d.ts +44 -42
  56. package/dist/node.js +30 -36
  57. package/dist/reporters.d.ts +12 -13
  58. package/dist/reporters.js +7 -5
  59. package/dist/runners.d.ts +3 -3
  60. package/dist/runners.js +15 -232
  61. package/dist/snapshot.js +3 -3
  62. package/dist/suite.d.ts +2 -2
  63. package/dist/suite.js +4 -3
  64. package/dist/worker-base.js +203 -0
  65. package/dist/{chunks/vm.BThCzidc.js → worker-vm.js} +179 -228
  66. package/dist/workers/runVmTests.js +39 -56
  67. package/globals.d.ts +17 -17
  68. package/package.json +40 -38
  69. package/browser.d.ts +0 -1
  70. package/dist/chunks/base.Bj3pWTr1.js +0 -38
  71. package/dist/chunks/execute.B7h3T_Hc.js +0 -708
  72. package/dist/chunks/index.D-VkfKhf.js +0 -105
  73. package/dist/chunks/rpc.CsFtxqeq.js +0 -83
  74. package/dist/chunks/runBaseTests.BC7ZIH5L.js +0 -129
  75. package/dist/chunks/typechecker.CVytUJuF.js +0 -874
  76. package/dist/chunks/utils.CAioKnHs.js +0 -61
  77. package/dist/chunks/worker.d.CmvJfRGs.d.ts +0 -8
  78. package/dist/chunks/worker.d.DoNjFAiv.d.ts +0 -169
  79. package/dist/execute.d.ts +0 -148
  80. package/dist/execute.js +0 -13
  81. package/dist/worker.js +0 -124
  82. package/dist/workers/forks.js +0 -43
  83. package/dist/workers/threads.js +0 -31
  84. package/dist/workers/vmForks.js +0 -47
  85. package/dist/workers/vmThreads.js +0 -37
  86. package/dist/workers.d.ts +0 -37
  87. package/dist/workers.js +0 -30
  88. package/execute.d.ts +0 -1
  89. package/utils.d.ts +0 -1
  90. package/workers.d.ts +0 -1
@@ -1,16 +1,18 @@
1
1
  import { existsSync, readFileSync, promises } from 'node:fs';
2
2
  import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
3
3
  import { resolve, dirname, isAbsolute, relative, basename, normalize } from 'pathe';
4
- import { g as getOutputFile, h as hasFailedSnapshot, T as TypeCheckError } from './typechecker.CVytUJuF.js';
4
+ import { g as getOutputFile, h as hasFailedSnapshot, T as TypeCheckError } from './typechecker.gXq-5P3n.js';
5
5
  import { performance as performance$1 } from 'node:perf_hooks';
6
- import { getTestName, getFullName, hasFailed, getTests, getSuites, getTasks } from '@vitest/runner/utils';
7
- import { slash, toArray, isPrimitive, inspect, positionToOffset, lineSplitRE } from '@vitest/utils';
8
- import { parseStacktrace, parseErrorStacktrace } from '@vitest/utils/source-map';
6
+ import { getTestName, hasFailed, getTests, getSuites, getTasks, getFullName } from '@vitest/runner/utils';
7
+ import { slash, toArray, isPrimitive } from '@vitest/utils/helpers';
8
+ import { parseStacktrace, parseErrorStacktrace, defaultStackIgnorePatterns } from '@vitest/utils/source-map';
9
9
  import c from 'tinyrainbow';
10
10
  import { i as isTTY } from './env.D4Lgay0q.js';
11
11
  import { stripVTControlCharacters } from 'node:util';
12
12
  import { Console } from 'node:console';
13
13
  import { Writable } from 'node:stream';
14
+ import { inspect } from '@vitest/utils/display';
15
+ import { positionToOffset, lineSplitRE } from '@vitest/utils/offset';
14
16
  import { createRequire } from 'node:module';
15
17
  import { hostname } from 'node:os';
16
18
 
@@ -125,16 +127,19 @@ class BlobReporter {
125
127
  start = 0;
126
128
  ctx;
127
129
  options;
130
+ coverage;
128
131
  constructor(options) {
129
132
  this.options = options;
130
133
  }
131
134
  onInit(ctx) {
132
135
  if (ctx.config.watch) throw new Error("Blob reporter is not supported in watch mode");
133
- this.ctx = ctx;
134
- this.start = performance.now();
136
+ this.ctx = ctx, this.start = performance.now(), this.coverage = void 0;
137
+ }
138
+ onCoverage(coverage) {
139
+ this.coverage = coverage;
135
140
  }
136
- async onFinished(files = [], errors = [], coverage) {
137
- const executionTime = performance.now() - this.start;
141
+ async onTestRunEnd(testModules, unhandledErrors) {
142
+ const executionTime = performance.now() - this.start, files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], coverage = this.coverage;
138
143
  let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
139
144
  if (!outputFile) {
140
145
  const shard = this.ctx.config.shard;
@@ -142,43 +147,34 @@ class BlobReporter {
142
147
  }
143
148
  const modules = this.ctx.projects.map((project) => {
144
149
  return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
145
- if (!mod[1].file) return null;
146
- return [
150
+ return mod[1].file ? [
147
151
  mod[0],
148
152
  mod[1].file,
149
153
  mod[1].url
150
- ];
154
+ ] : null;
151
155
  }).filter((x) => x != null)];
152
- });
153
- const report = [
156
+ }), report = [
154
157
  this.ctx.version,
155
158
  files,
156
159
  errors,
157
160
  modules,
158
161
  coverage,
159
162
  executionTime
160
- ];
161
- const reportFile = resolve(this.ctx.config.root, outputFile);
162
- await writeBlob(report, reportFile);
163
- this.ctx.logger.log("blob report written to", reportFile);
163
+ ], reportFile = resolve(this.ctx.config.root, outputFile);
164
+ await writeBlob(report, reportFile), this.ctx.logger.log("blob report written to", reportFile);
164
165
  }
165
166
  }
166
167
  async function writeBlob(content, filename) {
167
- const report = stringify(content);
168
- const dir = dirname(filename);
168
+ const report = stringify(content), dir = dirname(filename);
169
169
  if (!existsSync(dir)) await mkdir(dir, { recursive: true });
170
170
  await writeFile(filename, report, "utf-8");
171
171
  }
172
172
  async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
173
173
  // using process.cwd() because --merge-reports can only be used in CLI
174
- const resolvedDir = resolve(process.cwd(), blobsDirectory);
175
- const blobsFiles = await readdir(resolvedDir);
176
- const promises = blobsFiles.map(async (filename) => {
177
- const fullPath = resolve(resolvedDir, filename);
178
- const stats = await stat(fullPath);
174
+ const resolvedDir = resolve(process.cwd(), blobsDirectory), blobsFiles = await readdir(resolvedDir), promises = blobsFiles.map(async (filename) => {
175
+ const fullPath = resolve(resolvedDir, filename), stats = await stat(fullPath);
179
176
  if (!stats.isFile()) throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`);
180
- const content = await readFile(fullPath, "utf-8");
181
- const [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
177
+ const content = await readFile(fullPath, "utf-8"), [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
182
178
  if (!version) throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a valid blob file`);
183
179
  return {
184
180
  version,
@@ -189,8 +185,7 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
189
185
  file: filename,
190
186
  executionTime
191
187
  };
192
- });
193
- const blobs = await Promise.all(promises);
188
+ }), blobs = await Promise.all(promises);
194
189
  if (!blobs.length) throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
195
190
  const versions = new Set(blobs.map((blob) => blob.version));
196
191
  if (versions.size > 1) throw new Error(`vitest.mergeReports() requires all blob files to be generated by the same Vitest version, received\n\n${blobs.map((b) => `- "${b.file}" uses v${b.version}`).join("\n")}`);
@@ -200,23 +195,19 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
200
195
  blobs.forEach((blob) => {
201
196
  blob.moduleKeys.forEach(([projectName, moduleIds]) => {
202
197
  const project = projects[projectName];
203
- if (!project) return;
204
- moduleIds.forEach(([moduleId, file, url]) => {
198
+ project && moduleIds.forEach(([moduleId, file, url]) => {
205
199
  const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
206
- moduleNode.url = url;
207
- moduleNode.id = moduleId;
208
- project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
200
+ moduleNode.url = url, moduleNode.id = moduleId, moduleNode.transformResult = {
201
+ code: " ",
202
+ map: null
203
+ }, project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
209
204
  });
210
205
  });
211
206
  });
212
207
  const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
213
- const time1 = f1.result?.startTime || 0;
214
- const time2 = f2.result?.startTime || 0;
208
+ const time1 = f1.result?.startTime || 0, time2 = f2.result?.startTime || 0;
215
209
  return time1 - time2;
216
- });
217
- const errors = blobs.flatMap((blob) => blob.errors);
218
- const coverages = blobs.map((blob) => blob.coverage);
219
- const executionTimes = blobs.map((blob) => blob.executionTime);
210
+ }), errors = blobs.flatMap((blob) => blob.errors), coverages = blobs.map((blob) => blob.coverage), executionTimes = blobs.map((blob) => blob.executionTime);
220
211
  return {
221
212
  files,
222
213
  errors,
@@ -243,6 +234,7 @@ const testPass = c.green(F_CHECK);
243
234
  const taskFail = c.red(F_CROSS);
244
235
  const suiteFail = c.red(F_POINTER);
245
236
  const pending$1 = c.gray("·");
237
+ const separator = c.dim(" > ");
246
238
  const labelDefaultColors = [
247
239
  c.bgYellow,
248
240
  c.bgCyan,
@@ -258,26 +250,18 @@ function errorBanner(message) {
258
250
  return divider(c.bold(c.bgRed(` ${message} `)), null, null, c.red);
259
251
  }
260
252
  function divider(text, left, right, color) {
261
- const cols = getCols();
262
- const c = color || ((text) => text);
253
+ const cols = getCols(), c = color || ((text) => text);
263
254
  if (text) {
264
255
  const textLength = stripVTControlCharacters(text).length;
265
256
  if (left == null && right != null) left = cols - textLength - right;
266
- else {
267
- left = left ?? Math.floor((cols - textLength) / 2);
268
- right = cols - textLength - left;
269
- }
270
- left = Math.max(0, left);
271
- right = Math.max(0, right);
272
- return `${c(F_LONG_DASH.repeat(left))}${text}${c(F_LONG_DASH.repeat(right))}`;
257
+ else left = left ?? Math.floor((cols - textLength) / 2), right = cols - textLength - left;
258
+ return left = Math.max(0, left), right = Math.max(0, right), `${c(F_LONG_DASH.repeat(left))}${text}${c(F_LONG_DASH.repeat(right))}`;
273
259
  }
274
260
  return F_LONG_DASH.repeat(cols);
275
261
  }
276
262
  function formatTestPath(root, path) {
277
263
  if (isAbsolute(path)) path = relative(root, path);
278
- const dir = dirname(path);
279
- const ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "";
280
- const base = basename(path, ext);
264
+ const dir = dirname(path), ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "", base = basename(path, ext);
281
265
  return slash(c.dim(`${dir}/`) + c.bold(base)) + c.dim(ext);
282
266
  }
283
267
  function renderSnapshotSummary(rootDir, snapshots) {
@@ -289,8 +273,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
289
273
  else summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
290
274
  if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
291
275
  const [head, ...tail] = snapshots.filesRemovedList;
292
- summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`);
293
- tail.forEach((key) => {
276
+ summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`), tail.forEach((key) => {
294
277
  summary.push(` ${c.gray(F_DOT)} ${formatTestPath(rootDir, key)}`);
295
278
  });
296
279
  }
@@ -298,8 +281,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
298
281
  if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
299
282
  else summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
300
283
  snapshots.uncheckedKeysByFile.forEach((uncheckedFile) => {
301
- summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
302
- uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
284
+ summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`), uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
303
285
  });
304
286
  }
305
287
  return summary;
@@ -309,10 +291,7 @@ function countTestErrors(tasks) {
309
291
  }
310
292
  function getStateString$1(tasks, name = "tests", showTotal = true) {
311
293
  if (tasks.length === 0) return c.dim(`no ${name}`);
312
- const passed = tasks.filter((i) => i.result?.state === "pass");
313
- const failed = tasks.filter((i) => i.result?.state === "fail");
314
- const skipped = tasks.filter((i) => i.mode === "skip");
315
- const todo = tasks.filter((i) => i.mode === "todo");
294
+ const passed = tasks.filter((i) => i.result?.state === "pass"), failed = tasks.filter((i) => i.result?.state === "fail"), skipped = tasks.filter((i) => i.mode === "skip"), todo = tasks.filter((i) => i.mode === "todo");
316
295
  return [
317
296
  failed.length ? c.bold(c.red(`${failed.length} failed`)) : null,
318
297
  passed.length ? c.bold(c.green(`${passed.length} passed`)) : null,
@@ -326,16 +305,13 @@ function getStateSymbol(task) {
326
305
  if (task.result.state === "run" || task.result.state === "queued") {
327
306
  if (task.type === "suite") return pointer;
328
307
  }
329
- if (task.result.state === "pass") return task.meta?.benchmark ? benchmarkPass : testPass;
330
- if (task.result.state === "fail") return task.type === "suite" ? suiteFail : taskFail;
331
- return " ";
308
+ return task.result.state === "pass" ? task.meta?.benchmark ? benchmarkPass : testPass : task.result.state === "fail" ? task.type === "suite" ? suiteFail : taskFail : " ";
332
309
  }
333
310
  function formatTimeString(date) {
334
311
  return date.toTimeString().split(" ")[0];
335
312
  }
336
313
  function formatTime(time) {
337
- if (time > 1e3) return `${(time / 1e3).toFixed(2)}s`;
338
- return `${Math.round(time)}ms`;
314
+ return time > 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
339
315
  }
340
316
  function formatProjectName(project, suffix = " ") {
341
317
  if (!project?.name) return "";
@@ -356,8 +332,7 @@ function padSummaryTitle(str) {
356
332
  }
357
333
  function truncateString(text, maxLength) {
358
334
  const plainText = stripVTControlCharacters(text);
359
- if (plainText.length <= maxLength) return text;
360
- return `${plainText.slice(0, maxLength - 1)}…`;
335
+ return plainText.length <= maxLength ? text : `${plainText.slice(0, maxLength - 1)}…`;
361
336
  }
362
337
  function capitalize(text) {
363
338
  return `${text[0].toUpperCase()}${text.slice(1)}`;
@@ -379,6 +354,7 @@ var utils = /*#__PURE__*/Object.freeze({
379
354
  pending: pending$1,
380
355
  pointer: pointer,
381
356
  renderSnapshotSummary: renderSnapshotSummary,
357
+ separator: separator,
382
358
  skipped: skipped,
383
359
  suiteFail: suiteFail,
384
360
  taskFail: taskFail,
@@ -403,9 +379,7 @@ class BaseReporter {
403
379
  this.isTTY = options.isTTY ?? isTTY;
404
380
  }
405
381
  onInit(ctx) {
406
- this.ctx = ctx;
407
- this.ctx.logger.printBanner();
408
- this.start = performance$1.now();
382
+ this.ctx = ctx, this.ctx.logger.printBanner(), this.start = performance$1.now();
409
383
  }
410
384
  log(...messages) {
411
385
  this.ctx.logger.log(...messages);
@@ -416,9 +390,9 @@ class BaseReporter {
416
390
  relative(path) {
417
391
  return relative(this.ctx.config.root, path);
418
392
  }
419
- onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
420
- this.end = performance$1.now();
421
- if (!files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
393
+ onTestRunEnd(testModules, unhandledErrors, _reason) {
394
+ const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors];
395
+ if (this.end = performance$1.now(), !files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
422
396
  else this.reportSummary(files, errors);
423
397
  }
424
398
  onTestCaseResult(testCase) {
@@ -437,13 +411,10 @@ class BaseReporter {
437
411
  printTestModule(testModule) {
438
412
  const moduleState = testModule.state();
439
413
  if (moduleState === "queued" || moduleState === "pending") return;
440
- let testsCount = 0;
441
- let failedCount = 0;
442
- let skippedCount = 0;
414
+ let testsCount = 0, failedCount = 0, skippedCount = 0;
443
415
  // delaying logs to calculate the test stats first
444
416
  // which minimizes the amount of for loops
445
- const logs = [];
446
- const originalLog = this.log.bind(this);
417
+ const logs = [], originalLog = this.log.bind(this);
447
418
  this.log = (msg) => logs.push(msg);
448
419
  const visit = (suiteState, children) => {
449
420
  for (const child of children) if (child.type === "suite") {
@@ -453,8 +424,7 @@ class BaseReporter {
453
424
  visit(suiteState, child.children);
454
425
  } else {
455
426
  const testResult = child.result();
456
- testsCount++;
457
- if (testResult.state === "failed") failedCount++;
427
+ if (testsCount++, testResult.state === "failed") failedCount++;
458
428
  else if (testResult.state === "skipped") skippedCount++;
459
429
  if (this.ctx.config.hideSkippedTests && suiteState === "skipped")
460
430
  // Skipped suites are hidden when --hideSkippedTests
@@ -471,26 +441,13 @@ class BaseReporter {
471
441
  tests: testsCount,
472
442
  failed: failedCount,
473
443
  skipped: skippedCount
474
- }));
475
- logs.forEach((log) => this.log(log));
444
+ })), logs.forEach((log) => this.log(log));
476
445
  }
477
446
  printTestCase(moduleState, test) {
478
- const testResult = test.result();
479
- const { duration, retryCount, repeatCount } = test.diagnostic() || {};
480
- const padding = this.getTestIndentation(test.task);
481
- let suffix = this.getDurationPrefix(test.task);
482
- if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
483
- if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
484
- if (testResult.state === "failed") {
485
- this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(" > "))}`) + suffix);
486
- // print short errors, full errors will be at the end in summary
487
- testResult.errors.forEach((error) => {
488
- const message = this.formatShortError(error);
489
- if (message) this.log(c.red(` ${padding}${message}`));
490
- });
491
- } else if (duration && duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
492
- else if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") ; else if (testResult.state === "skipped" && testResult.note) this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${c.dim(c.gray(` [${testResult.note}]`))}`);
493
- else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
447
+ const testResult = test.result(), { duration = 0 } = test.diagnostic() || {}, padding = this.getTestIndentation(test.task), suffix = this.getTestCaseSuffix(test);
448
+ if (testResult.state === "failed") this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, separator)}`) + suffix);
449
+ else if (duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, separator)} ${suffix}`);
450
+ else if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") ; else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${this.getStateSymbol(test)} ${this.getTestName(test.task, separator)}${suffix}`);
494
451
  }
495
452
  getModuleLog(testModule, counts) {
496
453
  let state = c.dim(`${counts.tests} test${counts.tests > 1 ? "s" : ""}`);
@@ -499,25 +456,25 @@ class BaseReporter {
499
456
  let suffix = c.dim("(") + state + c.dim(")") + this.getDurationPrefix(testModule.task);
500
457
  const diagnostic = testModule.diagnostic();
501
458
  if (diagnostic.heap != null) suffix += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
502
- let title = getStateSymbol(testModule.task);
503
- if (testModule.meta().typecheck) title += ` ${c.bgBlue(c.bold(" TS "))}`;
504
- if (testModule.project.name) title += ` ${formatProjectName(testModule.project, "")}`;
459
+ const title = this.getEntityPrefix(testModule);
505
460
  return ` ${title} ${testModule.task.name} ${suffix}`;
506
461
  }
507
- printTestSuite(_suite) {
508
- // Suite name is included in getTestName by default
462
+ printTestSuite(testSuite) {
463
+ if (!this.renderSucceed) return;
464
+ const indentation = " ".repeat(getIndentation(testSuite.task)), tests = Array.from(testSuite.children.allTests()), state = this.getStateSymbol(testSuite);
465
+ this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`);
509
466
  }
510
- getTestName(test, separator) {
511
- return getTestName(test, separator);
467
+ getTestName(test, _separator) {
468
+ return test.name;
512
469
  }
513
470
  getFullName(test, separator) {
514
- return getFullName(test, separator);
471
+ if (test === test.file) return test.name;
472
+ let name = test.file.name;
473
+ if (test.location) name += c.dim(`:${test.location.line}:${test.location.column}`);
474
+ return name += separator, name += getTestName(test, separator), name;
515
475
  }
516
- formatShortError(error) {
517
- return `${F_RIGHT} ${error.message}`;
518
- }
519
- getTestIndentation(_test) {
520
- return " ";
476
+ getTestIndentation(test) {
477
+ return " ".repeat(getIndentation(test));
521
478
  }
522
479
  printAnnotations(test, console, padding = 0) {
523
480
  const annotations = test.annotations();
@@ -531,10 +488,29 @@ class BaseReporter {
531
488
  this[console](`${PADDING} ${c.blue(F_DOWN_RIGHT)} ${message}`);
532
489
  });
533
490
  }
491
+ getEntityPrefix(entity) {
492
+ let title = this.getStateSymbol(entity);
493
+ if (entity.project.name) title += ` ${formatProjectName(entity.project, "")}`;
494
+ if (entity.meta().typecheck) title += ` ${c.bgBlue(c.bold(" TS "))}`;
495
+ return title;
496
+ }
497
+ getTestCaseSuffix(testCase) {
498
+ const { heap, retryCount, repeatCount } = testCase.diagnostic() || {}, testResult = testCase.result();
499
+ let suffix = this.getDurationPrefix(testCase.task);
500
+ if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
501
+ if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
502
+ if (heap != null) suffix += c.magenta(` ${Math.floor(heap / 1024 / 1024)} MB heap used`);
503
+ if (testResult.state === "skipped" && testResult.note) suffix += c.dim(c.gray(` [${testResult.note}]`));
504
+ return suffix;
505
+ }
506
+ getStateSymbol(test) {
507
+ return getStateSymbol(test.task);
508
+ }
534
509
  getDurationPrefix(task) {
535
- if (!task.result?.duration) return "";
536
- const color = task.result.duration > this.ctx.config.slowTestThreshold ? c.yellow : c.green;
537
- return color(` ${Math.round(task.result.duration)}${c.dim("ms")}`);
510
+ const duration = task.result?.duration && Math.round(task.result?.duration);
511
+ if (duration == null) return "";
512
+ const color = duration > this.ctx.config.slowTestThreshold ? c.yellow : c.green;
513
+ return color(` ${duration}${c.dim("ms")}`);
538
514
  }
539
515
  onWatcherStart(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
540
516
  const failed = errors.length > 0 || hasFailed(files);
@@ -547,10 +523,8 @@ class BaseReporter {
547
523
  this.log(BADGE_PADDING + hints.join(c.dim(", ")));
548
524
  }
549
525
  onWatcherRerun(files, trigger) {
550
- this.watchFilters = files;
551
- this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed");
552
526
  // Update re-run count for each file
553
- files.forEach((filepath) => {
527
+ this.watchFilters = files, this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed"), files.forEach((filepath) => {
554
528
  let reruns = this._filesInWatchMode.get(filepath) ?? 0;
555
529
  this._filesInWatchMode.set(filepath, ++reruns);
556
530
  });
@@ -559,35 +533,26 @@ class BaseReporter {
559
533
  const rerun = this._filesInWatchMode.get(files[0]) ?? 1;
560
534
  banner += c.blue(`x${rerun} `);
561
535
  }
562
- this.ctx.logger.clearFullScreen();
563
- this.log(withLabel("blue", "RERUN", banner));
564
- if (this.ctx.configOverride.project) this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
536
+ if (this.ctx.logger.clearFullScreen(), this.log(withLabel("blue", "RERUN", banner)), this.ctx.configOverride.project) this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
565
537
  if (this.ctx.filenamePattern) this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
566
538
  if (this.ctx.configOverride.testNamePattern) this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
567
539
  this.log("");
568
540
  for (const testModule of this.failedUnwatchedFiles) this.printTestModule(testModule);
569
- this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
570
- this.start = performance$1.now();
541
+ this._timeStart = formatTimeString(/* @__PURE__ */ new Date()), this.start = performance$1.now();
571
542
  }
572
543
  onUserConsoleLog(log, taskState) {
573
544
  if (!this.shouldLog(log, taskState)) return;
574
- const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream;
575
- const write = (msg) => output.write(msg);
545
+ const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream, write = (msg) => output.write(msg);
576
546
  let headerText = "unknown test";
577
547
  const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
578
- if (task) headerText = this.getFullName(task, c.dim(" > "));
548
+ if (task) headerText = this.getFullName(task, separator);
579
549
  else if (log.taskId && log.taskId !== "__vitest__unknown_test__") headerText = log.taskId;
580
- write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content);
581
- if (log.origin) {
550
+ if (write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content), log.origin) {
582
551
  // browser logs don't have an extra end of line at the end like Node.js does
583
552
  if (log.browser) write("\n");
584
- const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
585
- const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
586
- const highlight = task && stack.find((i) => i.file === task.file.filepath);
553
+ const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject(), stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin), highlight = task && stack.find((i) => i.file === task.file.filepath);
587
554
  for (const frame of stack) {
588
- const color = frame === highlight ? c.cyan : c.gray;
589
- const path = relative(project.config.root, frame.file);
590
- const positions = [frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ");
555
+ const color = frame === highlight ? c.cyan : c.gray, path = relative(project.config.root, frame.file), positions = [frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ");
591
556
  write(color(` ${c.dim(F_POINTER)} ${positions}\n`));
592
557
  }
593
558
  }
@@ -597,13 +562,10 @@ class BaseReporter {
597
562
  this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ""));
598
563
  }
599
564
  shouldLog(log, taskState) {
600
- if (this.ctx.config.silent === true) return false;
601
- if (this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
565
+ if (this.ctx.config.silent === true || this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
602
566
  if (this.ctx.config.onConsoleLog) {
603
- const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
604
- const entity = task && this.ctx.state.getReportedEntity(task);
605
- const shouldLog = this.ctx.config.onConsoleLog(log.content, log.type, entity);
606
- if (shouldLog === false) return shouldLog;
567
+ const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0, entity = task && this.ctx.state.getReportedEntity(task), shouldLog = this.ctx.config.onConsoleLog(log.content, log.type, entity);
568
+ if (shouldLog === false) return false;
607
569
  }
608
570
  return true;
609
571
  }
@@ -611,41 +573,27 @@ class BaseReporter {
611
573
  this.log(c.bold(c.magenta(reason === "config" ? "\nRestarting due to config changes..." : "\nRestarting Vitest...")));
612
574
  }
613
575
  reportSummary(files, errors) {
614
- this.printErrorsSummary(files, errors);
615
- if (this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
576
+ if (this.printErrorsSummary(files, errors), this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
616
577
  else this.reportTestSummary(files, errors);
617
578
  }
618
579
  reportTestSummary(files, errors) {
619
580
  this.log();
620
- const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files];
621
- const tests = getTests(affectedFiles);
622
- const snapshotOutput = renderSnapshotSummary(this.ctx.config.root, this.ctx.snapshot.summary);
581
+ const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files], tests = getTests(affectedFiles), snapshotOutput = renderSnapshotSummary(this.ctx.config.root, this.ctx.snapshot.summary);
623
582
  for (const [index, snapshot] of snapshotOutput.entries()) {
624
583
  const title = index === 0 ? "Snapshots" : "";
625
584
  this.log(`${padSummaryTitle(title)} ${snapshot}`);
626
585
  }
627
586
  if (snapshotOutput.length > 1) this.log();
628
- this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles));
629
- this.log(padSummaryTitle("Tests"), getStateString$1(tests));
630
- if (this.ctx.projects.some((c) => c.config.typecheck.enabled)) {
587
+ if (this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles)), this.log(padSummaryTitle("Tests"), getStateString$1(tests)), this.ctx.projects.some((c) => c.config.typecheck.enabled)) {
631
588
  const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
632
589
  this.log(padSummaryTitle("Type Errors"), failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors"));
633
590
  }
634
591
  if (errors.length) this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
635
592
  this.log(padSummaryTitle("Start at"), this._timeStart);
636
- const collectTime = sum(files, (file) => file.collectDuration);
637
- const testsTime = sum(files, (file) => file.result?.duration);
638
- const setupTime = sum(files, (file) => file.setupDuration);
593
+ const collectTime = sum(files, (file) => file.collectDuration), testsTime = sum(files, (file) => file.result?.duration), setupTime = sum(files, (file) => file.setupDuration);
639
594
  if (this.watchFilters) this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
640
595
  else {
641
- const blobs = this.ctx.state.blobs;
642
- // Execution time is either sum of all runs of `--merge-reports` or the current run's time
643
- const executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start;
644
- const environmentTime = sum(files, (file) => file.environmentLoad);
645
- const prepareTime = sum(files, (file) => file.prepareDuration);
646
- const transformTime = sum(this.ctx.projects, (project) => project.vitenode.getTotalDuration());
647
- const typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time);
648
- const timers = [
596
+ const blobs = this.ctx.state.blobs, executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start, environmentTime = sum(files, (file) => file.environmentLoad), prepareTime = sum(files, (file) => file.prepareDuration), transformTime = this.ctx.state.transformTime, typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time), timers = [
649
597
  `transform ${formatTime(transformTime)}`,
650
598
  `setup ${formatTime(setupTime)}`,
651
599
  `collect ${formatTime(collectTime)}`,
@@ -654,41 +602,25 @@ class BaseReporter {
654
602
  `prepare ${formatTime(prepareTime)}`,
655
603
  typecheck && `typecheck ${formatTime(typecheck)}`
656
604
  ].filter(Boolean).join(", ");
657
- this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
658
- if (blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
605
+ if (this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`)), blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
659
606
  }
660
607
  this.log();
661
608
  }
662
609
  printErrorsSummary(files, errors) {
663
- const suites = getSuites(files);
664
- const tests = getTests(files);
665
- const failedSuites = suites.filter((i) => i.result?.errors);
666
- const failedTests = tests.filter((i) => i.result?.state === "fail");
667
- const failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
610
+ const suites = getSuites(files), tests = getTests(files), failedSuites = suites.filter((i) => i.result?.errors), failedTests = tests.filter((i) => i.result?.state === "fail"), failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
668
611
  let current = 1;
669
612
  const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}\n`);
670
- if (failedSuites.length) {
671
- this.error(`\n${errorBanner(`Failed Suites ${failedSuites.length}`)}\n`);
672
- this.printTaskErrors(failedSuites, errorDivider);
673
- }
674
- if (failedTests.length) {
675
- this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`);
676
- this.printTaskErrors(failedTests, errorDivider);
677
- }
678
- if (errors.length) {
679
- this.ctx.logger.printUnhandledErrors(errors);
680
- this.error();
681
- }
613
+ if (failedSuites.length) this.error(`\n${errorBanner(`Failed Suites ${failedSuites.length}`)}\n`), this.printTaskErrors(failedSuites, errorDivider);
614
+ if (failedTests.length) this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`), this.printTaskErrors(failedTests, errorDivider);
615
+ if (errors.length) this.ctx.logger.printUnhandledErrors(errors), this.error();
682
616
  }
683
617
  reportBenchmarkSummary(files) {
684
- const benches = getTests(files);
685
- const topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
618
+ const benches = getTests(files), topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
686
619
  this.log(`\n${withLabel("cyan", "BENCH", "Summary\n")}`);
687
620
  for (const bench of topBenches) {
688
621
  const group = bench.suite || bench.file;
689
622
  if (!group) continue;
690
- const groupName = this.getFullName(group, c.dim(" > "));
691
- const project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
623
+ const groupName = this.getFullName(group, separator), project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
692
624
  this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
693
625
  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);
694
626
  for (const sibling of siblings) {
@@ -706,10 +638,7 @@ class BaseReporter {
706
638
  let previous;
707
639
  if (error?.stack) previous = errorsQueue.find((i) => {
708
640
  if (i[0]?.stack !== error.stack) return false;
709
- const currentProjectName = task?.projectName || task.file?.projectName || "";
710
- const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
711
- const currentAnnotations = task.type === "test" && task.annotations;
712
- const itemAnnotations = i[1][0].type === "test" && i[1][0].annotations;
641
+ const currentProjectName = task?.projectName || task.file?.projectName || "", projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "", currentAnnotations = task.type === "test" && task.annotations, itemAnnotations = i[1][0].type === "test" && i[1][0].annotations;
713
642
  return projectName === currentProjectName && deepEqual(currentAnnotations, itemAnnotations);
714
643
  });
715
644
  if (previous) previous[1].push(task);
@@ -717,24 +646,20 @@ class BaseReporter {
717
646
  });
718
647
  for (const [error, tasks] of errorsQueue) {
719
648
  for (const task of tasks) {
720
- const filepath = task?.filepath || "";
721
- const projectName = task?.projectName || task.file?.projectName || "";
722
- const project = this.ctx.projects.find((p) => p.name === projectName);
723
- let name = this.getFullName(task, c.dim(" > "));
649
+ const filepath = task?.filepath || "", projectName = task?.projectName || task.file?.projectName || "", project = this.ctx.projects.find((p) => p.name === projectName);
650
+ let name = this.getFullName(task, separator);
724
651
  if (filepath) name += c.dim(` [ ${this.relative(filepath)} ]`);
725
652
  this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
726
653
  }
727
654
  const screenshotPaths = tasks.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
728
- this.ctx.logger.printError(error, {
655
+ if (this.ctx.logger.printError(error, {
729
656
  project: this.ctx.getProjectByName(tasks[0].file.projectName || ""),
730
657
  verbose: this.verbose,
731
658
  screenshotPaths,
732
659
  task: tasks[0]
733
- });
734
- if (tasks[0].type === "test" && tasks[0].annotations.length) {
660
+ }), tasks[0].type === "test" && tasks[0].annotations.length) {
735
661
  const test = this.ctx.state.getReportedEntity(tasks[0]);
736
- this.printAnnotations(test, "error", 1);
737
- this.error();
662
+ this.printAnnotations(test, "error", 1), this.error();
738
663
  }
739
664
  errorDivider();
740
665
  }
@@ -743,8 +668,7 @@ class BaseReporter {
743
668
  function deepEqual(a, b) {
744
669
  if (a === b) return true;
745
670
  if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
746
- const keysA = Object.keys(a);
747
- const keysB = Object.keys(b);
671
+ const keysA = Object.keys(a), keysB = Object.keys(b);
748
672
  if (keysA.length !== keysB.length) return false;
749
673
  for (const key of keysA) if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
750
674
  return true;
@@ -754,13 +678,11 @@ function sum(items, cb) {
754
678
  return total + Math.max(cb(next) || 0, 0);
755
679
  }, 0);
756
680
  }
681
+ function getIndentation(suite, level = 1) {
682
+ return suite.suite && !("filepath" in suite.suite) ? getIndentation(suite.suite, level + 1) : level;
683
+ }
757
684
 
758
- const DEFAULT_RENDER_INTERVAL_MS = 1e3;
759
- const ESC = "\x1B[";
760
- const CLEAR_LINE = `${ESC}K`;
761
- const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`;
762
- const SYNC_START = `${ESC}?2026h`;
763
- const SYNC_END = `${ESC}?2026l`;
685
+ const DEFAULT_RENDER_INTERVAL_MS = 1e3, ESC = "\x1B[", CLEAR_LINE = `${ESC}K`, MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`, SYNC_START = `${ESC}?2026h`, SYNC_END = `${ESC}?2026l`;
764
686
  /**
765
687
  * Renders content of `getWindow` at the bottom of the terminal and
766
688
  * forwards all other intercepted `stdout` and `stderr` logs above it.
@@ -772,53 +694,41 @@ class WindowRenderer {
772
694
  renderInterval = void 0;
773
695
  renderScheduled = false;
774
696
  windowHeight = 0;
697
+ started = false;
775
698
  finished = false;
776
699
  cleanups = [];
777
700
  constructor(options) {
701
+ // Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
778
702
  this.options = {
779
703
  interval: DEFAULT_RENDER_INTERVAL_MS,
780
704
  ...options
781
- };
782
- this.streams = {
705
+ }, this.streams = {
783
706
  output: options.logger.outputStream.write.bind(options.logger.outputStream),
784
707
  error: options.logger.errorStream.write.bind(options.logger.errorStream)
785
- };
786
- this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error"));
787
- // Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
788
- this.options.logger.onTerminalCleanup(() => {
789
- this.flushBuffer();
790
- this.stop();
708
+ }, this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error")), this.options.logger.onTerminalCleanup(() => {
709
+ this.flushBuffer(), this.stop();
791
710
  });
792
- this.start();
793
711
  }
794
712
  start() {
795
- this.finished = false;
796
- this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
713
+ this.started = true, this.finished = false, this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
797
714
  }
798
715
  stop() {
799
- this.cleanups.splice(0).map((fn) => fn());
800
- clearInterval(this.renderInterval);
716
+ this.cleanups.splice(0).map((fn) => fn()), clearInterval(this.renderInterval);
801
717
  }
802
718
  /**
803
719
  * Write all buffered output and stop buffering.
804
720
  * All intercepted writes are forwarded to actual write after this.
805
721
  */
806
722
  finish() {
807
- this.finished = true;
808
- this.flushBuffer();
809
- clearInterval(this.renderInterval);
723
+ this.finished = true, this.flushBuffer(), clearInterval(this.renderInterval);
810
724
  }
811
725
  /**
812
726
  * Queue new render update
813
727
  */
814
728
  schedule() {
815
- if (!this.renderScheduled) {
816
- this.renderScheduled = true;
817
- this.flushBuffer();
818
- setTimeout(() => {
819
- this.renderScheduled = false;
820
- }, 100).unref();
821
- }
729
+ if (!this.renderScheduled) this.renderScheduled = true, this.flushBuffer(), setTimeout(() => {
730
+ this.renderScheduled = false;
731
+ }, 100).unref();
822
732
  }
823
733
  flushBuffer() {
824
734
  if (this.buffer.length === 0) return this.render();
@@ -830,8 +740,7 @@ class WindowRenderer {
830
740
  continue;
831
741
  }
832
742
  if (current.type !== next.type) {
833
- this.render(current.message, current.type);
834
- current = next;
743
+ this.render(current.message, current.type), current = next;
835
744
  continue;
836
745
  }
837
746
  current.message += next.message;
@@ -839,40 +748,31 @@ class WindowRenderer {
839
748
  if (current) this.render(current?.message, current?.type);
840
749
  }
841
750
  render(message, type = "output") {
842
- if (this.finished) {
843
- this.clearWindow();
844
- return this.write(message || "", type);
845
- }
846
- const windowContent = this.options.getWindow();
847
- const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
751
+ if (this.finished) return this.clearWindow(), this.write(message || "", type);
752
+ const windowContent = this.options.getWindow(), rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
848
753
  let padding = this.windowHeight - rowCount;
849
754
  if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
850
- this.write(SYNC_START);
851
- this.clearWindow();
852
- if (message) this.write(message, type);
755
+ if (this.write(SYNC_START), this.clearWindow(), message) this.write(message, type);
853
756
  if (padding > 0) this.write("\n".repeat(padding));
854
- this.write(windowContent.join("\n"));
855
- this.write(SYNC_END);
856
- this.windowHeight = rowCount + Math.max(0, padding);
757
+ this.write(windowContent.join("\n")), this.write(SYNC_END), this.windowHeight = rowCount + Math.max(0, padding);
857
758
  }
858
759
  clearWindow() {
859
- if (this.windowHeight === 0) return;
860
- this.write(CLEAR_LINE);
861
- for (let i = 1; i < this.windowHeight; i++) this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
862
- this.windowHeight = 0;
760
+ if (this.windowHeight !== 0) {
761
+ this.write(CLEAR_LINE);
762
+ for (let i = 1; i < this.windowHeight; i++) this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
763
+ this.windowHeight = 0;
764
+ }
863
765
  }
864
766
  interceptStream(stream, type) {
865
767
  const original = stream.write;
866
- // @ts-expect-error -- not sure how 2 overloads should be typed
867
- stream.write = (chunk, _, callback) => {
868
- if (chunk) if (this.finished) this.write(chunk.toString(), type);
768
+ return stream.write = (chunk, _, callback) => {
769
+ if (chunk) if (this.finished || !this.started) this.write(chunk.toString(), type);
869
770
  else this.buffer.push({
870
771
  type,
871
772
  message: chunk.toString()
872
773
  });
873
774
  callback?.();
874
- };
875
- return function restore() {
775
+ }, function restore() {
876
776
  stream.write = original;
877
777
  };
878
778
  }
@@ -890,8 +790,7 @@ function getRenderedRowCount(rows, columns) {
890
790
  return count;
891
791
  }
892
792
 
893
- const DURATION_UPDATE_INTERVAL_MS = 100;
894
- const FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
793
+ const DURATION_UPDATE_INTERVAL_MS = 100, FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
895
794
  /**
896
795
  * Reporter extension that renders summary and forwards all other logs above itself.
897
796
  * Intended to be used by other reporters, not as a standalone reporter.
@@ -912,34 +811,21 @@ class SummaryReporter {
912
811
  duration = 0;
913
812
  durationInterval = void 0;
914
813
  onInit(ctx, options = {}) {
915
- this.ctx = ctx;
916
- this.options = {
814
+ this.ctx = ctx, this.options = {
917
815
  verbose: false,
918
816
  ...options
919
- };
920
- this.renderer = new WindowRenderer({
817
+ }, this.renderer = new WindowRenderer({
921
818
  logger: ctx.logger,
922
819
  getWindow: () => this.createSummary()
923
- });
924
- this.ctx.onClose(() => {
925
- clearInterval(this.durationInterval);
926
- this.renderer.stop();
820
+ }), this.ctx.onClose(() => {
821
+ clearInterval(this.durationInterval), this.renderer.stop();
927
822
  });
928
823
  }
929
824
  onTestRunStart(specifications) {
930
- this.runningModules.clear();
931
- this.finishedModules.clear();
932
- this.modules = emptyCounters();
933
- this.tests = emptyCounters();
934
- this.startTimers();
935
- this.renderer.start();
936
- this.modules.total = specifications.length;
825
+ this.runningModules.clear(), this.finishedModules.clear(), this.modules = emptyCounters(), this.tests = emptyCounters(), this.startTimers(), this.renderer.start(), this.modules.total = specifications.length;
937
826
  }
938
827
  onTestRunEnd() {
939
- this.runningModules.clear();
940
- this.finishedModules.clear();
941
- this.renderer.finish();
942
- clearInterval(this.durationInterval);
828
+ this.runningModules.clear(), this.finishedModules.clear(), this.renderer.finish(), clearInterval(this.durationInterval);
943
829
  }
944
830
  onTestModuleQueued(module) {
945
831
  // When new test module starts, take the place of previously finished test module, if any
@@ -947,20 +833,13 @@ class SummaryReporter {
947
833
  const finished = this.finishedModules.keys().next().value;
948
834
  this.removeTestModule(finished);
949
835
  }
950
- this.runningModules.set(module.id, initializeStats(module));
951
- this.renderer.schedule();
836
+ this.runningModules.set(module.id, initializeStats(module)), this.renderer.schedule();
952
837
  }
953
838
  onTestModuleCollected(module) {
954
839
  let stats = this.runningModules.get(module.id);
955
- if (!stats) {
956
- stats = initializeStats(module);
957
- this.runningModules.set(module.id, stats);
958
- }
840
+ if (!stats) stats = initializeStats(module), this.runningModules.set(module.id, stats);
959
841
  const total = Array.from(module.children.allTests()).length;
960
- this.tests.total += total;
961
- stats.total = total;
962
- this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size);
963
- this.renderer.schedule();
842
+ this.tests.total += total, stats.total = total, this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size), this.renderer.schedule();
964
843
  }
965
844
  onHookStart(options) {
966
845
  const stats = this.getHookStats(options);
@@ -971,8 +850,7 @@ class SummaryReporter {
971
850
  startTime: performance.now(),
972
851
  onFinish: () => {}
973
852
  };
974
- stats.hook?.onFinish?.();
975
- stats.hook = hook;
853
+ stats.hook?.onFinish?.(), stats.hook = hook;
976
854
  const timeout = setTimeout(() => {
977
855
  hook.visible = true;
978
856
  }, this.ctx.config.slowTestThreshold).unref();
@@ -980,9 +858,7 @@ class SummaryReporter {
980
858
  }
981
859
  onHookEnd(options) {
982
860
  const stats = this.getHookStats(options);
983
- if (stats?.hook?.name !== options.name) return;
984
- stats.hook.onFinish();
985
- stats.hook.visible = false;
861
+ stats?.hook?.name === options.name && (stats.hook.onFinish(), stats.hook.visible = false);
986
862
  }
987
863
  onTestCaseReady(test) {
988
864
  // Track slow running tests only on verbose mode
@@ -994,22 +870,17 @@ class SummaryReporter {
994
870
  visible: false,
995
871
  startTime: performance.now(),
996
872
  onFinish: () => {}
997
- };
998
- const timeout = setTimeout(() => {
873
+ }, timeout = setTimeout(() => {
999
874
  slowTest.visible = true;
1000
875
  }, this.ctx.config.slowTestThreshold).unref();
1001
876
  slowTest.onFinish = () => {
1002
- slowTest.hook?.onFinish();
1003
- clearTimeout(timeout);
1004
- };
1005
- stats.tests.set(test.id, slowTest);
877
+ slowTest.hook?.onFinish(), clearTimeout(timeout);
878
+ }, stats.tests.set(test.id, slowTest);
1006
879
  }
1007
880
  onTestCaseResult(test) {
1008
881
  const stats = this.runningModules.get(test.module.id);
1009
882
  if (!stats) return;
1010
- stats.tests.get(test.id)?.onFinish();
1011
- stats.tests.delete(test.id);
1012
- stats.completed++;
883
+ stats.tests.get(test.id)?.onFinish(), stats.tests.delete(test.id), stats.completed++;
1013
884
  const result = test.result();
1014
885
  if (result?.state === "passed") this.tests.passed++;
1015
886
  else if (result?.state === "failed") this.tests.failed++;
@@ -1018,8 +889,7 @@ class SummaryReporter {
1018
889
  }
1019
890
  onTestModuleEnd(module) {
1020
891
  const state = module.state();
1021
- this.modules.completed++;
1022
- if (state === "passed") this.modules.passed++;
892
+ if (this.modules.completed++, state === "passed") this.modules.passed++;
1023
893
  else if (state === "failed") this.modules.failed++;
1024
894
  else if (module.task.mode === "todo" && state === "skipped") this.modules.todo++;
1025
895
  else if (state === "skipped") this.modules.skipped++;
@@ -1039,10 +909,8 @@ class SummaryReporter {
1039
909
  getHookStats({ entity }) {
1040
910
  // Track slow running hooks only on verbose mode
1041
911
  if (!this.options.verbose) return;
1042
- const module = entity.type === "module" ? entity : entity.module;
1043
- const stats = this.runningModules.get(module.id);
1044
- if (!stats) return;
1045
- return entity.type === "test" ? stats.tests.get(entity.id) : stats;
912
+ const module = entity.type === "module" ? entity : entity.module, stats = this.runningModules.get(module.id);
913
+ if (stats) return entity.type === "test" ? stats.tests.get(entity.id) : stats;
1046
914
  }
1047
915
  createSummary() {
1048
916
  const summary = [""];
@@ -1054,36 +922,23 @@ class SummaryReporter {
1054
922
  }) + typecheck + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
1055
923
  const slowTasks = [testFile.hook, ...Array.from(testFile.tests.values())].filter((t) => t != null && t.visible);
1056
924
  for (const [index, task] of slowTasks.entries()) {
1057
- const elapsed = this.currentTime - task.startTime;
1058
- const icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
1059
- summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
1060
- if (task.hook?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
925
+ const elapsed = this.currentTime - task.startTime, icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
926
+ if (summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`))), task.hook?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
1061
927
  }
1062
928
  }
1063
929
  if (this.runningModules.size > 0) summary.push("");
1064
- summary.push(padSummaryTitle("Test Files") + getStateString(this.modules));
1065
- summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
1066
- summary.push(padSummaryTitle("Start at") + this.startTime);
1067
- summary.push(padSummaryTitle("Duration") + formatTime(this.duration));
1068
- summary.push("");
1069
- return summary;
930
+ return summary.push(padSummaryTitle("Test Files") + getStateString(this.modules)), summary.push(padSummaryTitle("Tests") + getStateString(this.tests)), summary.push(padSummaryTitle("Start at") + this.startTime), summary.push(padSummaryTitle("Duration") + formatTime(this.duration)), summary.push(""), summary;
1070
931
  }
1071
932
  startTimers() {
1072
933
  const start = performance.now();
1073
- this.startTime = formatTimeString(/* @__PURE__ */ new Date());
1074
- this.durationInterval = setInterval(() => {
1075
- this.currentTime = performance.now();
1076
- this.duration = this.currentTime - start;
934
+ this.startTime = formatTimeString(/* @__PURE__ */ new Date()), this.durationInterval = setInterval(() => {
935
+ this.currentTime = performance.now(), this.duration = this.currentTime - start;
1077
936
  }, DURATION_UPDATE_INTERVAL_MS).unref();
1078
937
  }
1079
938
  removeTestModule(id) {
1080
939
  if (!id) return;
1081
940
  const testFile = this.runningModules.get(id);
1082
- testFile?.hook?.onFinish();
1083
- testFile?.tests?.forEach((test) => test.onFinish());
1084
- this.runningModules.delete(id);
1085
- clearTimeout(this.finishedModules.get(id));
1086
- this.finishedModules.delete(id);
941
+ testFile?.hook?.onFinish(), testFile?.tests?.forEach((test) => test.onFinish()), this.runningModules.delete(id), clearTimeout(this.finishedModules.get(id)), this.finishedModules.delete(id);
1087
942
  }
1088
943
  }
1089
944
  function emptyCounters() {
@@ -1105,9 +960,7 @@ function getStateString(entry) {
1105
960
  ].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
1106
961
  }
1107
962
  function sortRunningModules(a, b) {
1108
- if ((a.projectName || "") > (b.projectName || "")) return 1;
1109
- if ((a.projectName || "") < (b.projectName || "")) return -1;
1110
- return a.filename.localeCompare(b.filename);
963
+ return (a.projectName || "") > (b.projectName || "") ? 1 : (a.projectName || "") < (b.projectName || "") ? -1 : a.filename.localeCompare(b.filename);
1111
964
  }
1112
965
  function initializeStats(module) {
1113
966
  return {
@@ -1125,12 +978,10 @@ class DefaultReporter extends BaseReporter {
1125
978
  options;
1126
979
  summary;
1127
980
  constructor(options = {}) {
1128
- super(options);
1129
- this.options = {
981
+ if (super(options), this.options = {
1130
982
  summary: true,
1131
983
  ...options
1132
- };
1133
- if (!this.isTTY) this.options.summary = false;
984
+ }, !this.isTTY) this.options.summary = false;
1134
985
  if (this.options.summary) this.summary = new SummaryReporter();
1135
986
  }
1136
987
  onTestRunStart(specifications) {
@@ -1140,6 +991,9 @@ class DefaultReporter extends BaseReporter {
1140
991
  }
1141
992
  this.summary?.onTestRunStart(specifications);
1142
993
  }
994
+ onTestRunEnd(testModules, unhandledErrors, reason) {
995
+ super.onTestRunEnd(testModules, unhandledErrors, reason), this.summary?.onTestRunEnd();
996
+ }
1143
997
  onTestModuleQueued(file) {
1144
998
  this.summary?.onTestModuleQueued(file);
1145
999
  }
@@ -1147,15 +1001,13 @@ class DefaultReporter extends BaseReporter {
1147
1001
  this.summary?.onTestModuleCollected(module);
1148
1002
  }
1149
1003
  onTestModuleEnd(module) {
1150
- super.onTestModuleEnd(module);
1151
- this.summary?.onTestModuleEnd(module);
1004
+ super.onTestModuleEnd(module), this.summary?.onTestModuleEnd(module);
1152
1005
  }
1153
1006
  onTestCaseReady(test) {
1154
1007
  this.summary?.onTestCaseReady(test);
1155
1008
  }
1156
1009
  onTestCaseResult(test) {
1157
- super.onTestCaseResult(test);
1158
- this.summary?.onTestCaseResult(test);
1010
+ super.onTestCaseResult(test), this.summary?.onTestCaseResult(test);
1159
1011
  }
1160
1012
  onHookStart(hook) {
1161
1013
  this.summary?.onHookStart(hook);
@@ -1164,11 +1016,7 @@ class DefaultReporter extends BaseReporter {
1164
1016
  this.summary?.onHookEnd(hook);
1165
1017
  }
1166
1018
  onInit(ctx) {
1167
- super.onInit(ctx);
1168
- this.summary?.onInit(ctx, { verbose: this.verbose });
1169
- }
1170
- onTestRunEnd() {
1171
- this.summary?.onTestRunEnd();
1019
+ super.onInit(ctx), this.summary?.onInit(ctx, { verbose: this.verbose });
1172
1020
  }
1173
1021
  }
1174
1022
 
@@ -1177,30 +1025,22 @@ class DotReporter extends BaseReporter {
1177
1025
  tests = /* @__PURE__ */ new Map();
1178
1026
  finishedTests = /* @__PURE__ */ new Set();
1179
1027
  onInit(ctx) {
1180
- super.onInit(ctx);
1181
- if (this.isTTY) {
1182
- this.renderer = new WindowRenderer({
1183
- logger: ctx.logger,
1184
- getWindow: () => this.createSummary()
1185
- });
1186
- this.ctx.onClose(() => this.renderer?.stop());
1187
- }
1028
+ if (super.onInit(ctx), this.isTTY) this.renderer = new WindowRenderer({
1029
+ logger: ctx.logger,
1030
+ getWindow: () => this.createSummary()
1031
+ }), this.ctx.onClose(() => this.renderer?.stop());
1188
1032
  }
1189
1033
  // Ignore default logging of base reporter
1190
1034
  printTestModule() {}
1191
1035
  onWatcherRerun(files, trigger) {
1192
- this.tests.clear();
1193
- this.renderer?.start();
1194
- super.onWatcherRerun(files, trigger);
1036
+ this.tests.clear(), this.renderer?.start(), super.onWatcherRerun(files, trigger);
1195
1037
  }
1196
- onFinished(files, errors) {
1038
+ onTestRunEnd(testModules, unhandledErrors, reason) {
1197
1039
  if (this.isTTY) {
1198
1040
  const finalLog = formatTests(Array.from(this.tests.values()));
1199
1041
  this.ctx.logger.log(finalLog);
1200
1042
  } else this.ctx.logger.log();
1201
- this.tests.clear();
1202
- this.renderer?.finish();
1203
- super.onFinished(files, errors);
1043
+ this.tests.clear(), this.renderer?.finish(), super.onTestRunEnd(testModules, unhandledErrors, reason);
1204
1044
  }
1205
1045
  onTestModuleCollected(module) {
1206
1046
  for (const test of module.children.allTests())
@@ -1208,22 +1048,16 @@ class DotReporter extends BaseReporter {
1208
1048
  this.onTestCaseReady(test);
1209
1049
  }
1210
1050
  onTestCaseReady(test) {
1211
- if (this.finishedTests.has(test.id)) return;
1212
- this.tests.set(test.id, test.result().state || "run");
1213
- this.renderer?.schedule();
1051
+ this.finishedTests.has(test.id) || (this.tests.set(test.id, test.result().state || "run"), this.renderer?.schedule());
1214
1052
  }
1215
1053
  onTestCaseResult(test) {
1216
1054
  const result = test.result().state;
1217
1055
  // On non-TTY the finished tests are printed immediately
1218
1056
  if (!this.isTTY && result !== "pending") this.ctx.logger.outputStream.write(formatTests([result]));
1219
- super.onTestCaseResult(test);
1220
- this.finishedTests.add(test.id);
1221
- this.tests.set(test.id, result || "skipped");
1222
- this.renderer?.schedule();
1057
+ super.onTestCaseResult(test), this.finishedTests.add(test.id), this.tests.set(test.id, result || "skipped"), this.renderer?.schedule();
1223
1058
  }
1224
1059
  onTestModuleEnd(testModule) {
1225
- super.onTestModuleEnd(testModule);
1226
- if (!this.isTTY) return;
1060
+ if (super.onTestModuleEnd(testModule), !this.isTTY) return;
1227
1061
  const columns = this.ctx.logger.getColumns();
1228
1062
  if (this.tests.size < columns) return;
1229
1063
  const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
@@ -1233,11 +1067,9 @@ class DotReporter extends BaseReporter {
1233
1067
  let count = 0;
1234
1068
  for (const [id, state] of finishedTests) {
1235
1069
  if (count++ >= columns) break;
1236
- this.tests.delete(id);
1237
- states.push(state);
1070
+ this.tests.delete(id), states.push(state);
1238
1071
  }
1239
- this.ctx.logger.log(formatTests(states));
1240
- this.renderer?.schedule();
1072
+ this.ctx.logger.log(formatTests(states)), this.renderer?.schedule();
1241
1073
  }
1242
1074
  createSummary() {
1243
1075
  return [formatTests(Array.from(this.tests.values())), ""];
@@ -1247,16 +1079,13 @@ class DotReporter extends BaseReporter {
1247
1079
  const pass = {
1248
1080
  char: "·",
1249
1081
  color: c.green
1250
- };
1251
- const fail = {
1082
+ }, fail = {
1252
1083
  char: "x",
1253
1084
  color: c.red
1254
- };
1255
- const pending = {
1085
+ }, pending = {
1256
1086
  char: "*",
1257
1087
  color: c.yellow
1258
- };
1259
- const skip = {
1088
+ }, skip = {
1260
1089
  char: "-",
1261
1090
  color: (char) => c.dim(c.gray(char))
1262
1091
  };
@@ -1273,37 +1102,27 @@ function getIcon(state) {
1273
1102
  * Sibling icons with same color are merged into a single c.color() call.
1274
1103
  */
1275
1104
  function formatTests(states) {
1276
- let currentIcon = pending;
1277
- let count = 0;
1278
- let output = "";
1105
+ let currentIcon = pending, count = 0, output = "";
1279
1106
  for (const state of states) {
1280
1107
  const icon = getIcon(state);
1281
1108
  if (currentIcon === icon) {
1282
1109
  count++;
1283
1110
  continue;
1284
1111
  }
1285
- output += currentIcon.color(currentIcon.char.repeat(count));
1286
- // Start tracking new group
1287
- count = 1;
1288
- currentIcon = icon;
1112
+ output += currentIcon.color(currentIcon.char.repeat(count)), count = 1, currentIcon = icon;
1289
1113
  }
1290
- output += currentIcon.color(currentIcon.char.repeat(count));
1291
- return output;
1114
+ return output += currentIcon.color(currentIcon.char.repeat(count)), output;
1292
1115
  }
1293
1116
 
1294
1117
  // use Logger with custom Console to capture entire error printing
1295
1118
  function capturePrintError(error, ctx, options) {
1296
1119
  let output = "";
1297
1120
  const writable = new Writable({ write(chunk, _encoding, callback) {
1298
- output += String(chunk);
1299
- callback();
1300
- } });
1301
- const console = new Console(writable);
1302
- const logger = {
1121
+ output += String(chunk), callback();
1122
+ } }), console = new Console(writable), logger = {
1303
1123
  error: console.error.bind(console),
1304
1124
  highlight: ctx.logger.highlight.bind(ctx.logger)
1305
- };
1306
- const result = printError(error, ctx, logger, {
1125
+ }, result = printError(error, ctx, logger, {
1307
1126
  showCodeFrame: false,
1308
1127
  ...options
1309
1128
  });
@@ -1321,11 +1140,13 @@ function printError(error, ctx, logger, options) {
1321
1140
  screenshotPaths: options.screenshotPaths,
1322
1141
  printProperties: options.verbose,
1323
1142
  parseErrorStacktrace(error) {
1324
- // browser stack trace needs to be processed differently,
1325
- // so there is a separate method for that
1326
- if (options.task?.file.pool === "browser" && project.browser) return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : void 0 });
1327
1143
  // node.js stack trace already has correct source map locations
1328
- return parseErrorStacktrace(error, {
1144
+ return error.stacks ? options.fullStack ? error.stacks : error.stacks.filter((stack) => {
1145
+ return !defaultStackIgnorePatterns.some((p) => stack.file.match(p));
1146
+ }) : options.task?.file.pool === "browser" && project.browser ? project.browser.parseErrorStacktrace(error, {
1147
+ frameFilter: project.config.onStackTrace,
1148
+ ignoreStackEntries: options.fullStack ? [] : void 0
1149
+ }) : parseErrorStacktrace(error, {
1329
1150
  frameFilter: project.config.onStackTrace,
1330
1151
  ignoreStackEntries: options.fullStack ? [] : void 0
1331
1152
  });
@@ -1333,15 +1154,14 @@ function printError(error, ctx, logger, options) {
1333
1154
  });
1334
1155
  }
1335
1156
  function printErrorInner(error, project, options) {
1336
- const { showCodeFrame = true, type, printProperties = true } = options;
1337
- const logger = options.logger;
1157
+ const { showCodeFrame = true, type, printProperties = true } = options, logger = options.logger;
1338
1158
  let e = error;
1339
1159
  if (isPrimitive(e)) e = {
1340
1160
  message: String(error).split(/\n/g)[0],
1341
1161
  stack: String(error)
1342
1162
  };
1343
1163
  if (!e) {
1344
- const error = new Error("unknown error");
1164
+ const error = /* @__PURE__ */ new Error("unknown error");
1345
1165
  e = {
1346
1166
  message: e ?? error.message,
1347
1167
  stack: error.stack
@@ -1352,21 +1172,22 @@ function printErrorInner(error, project, options) {
1352
1172
  printErrorMessage(e, logger);
1353
1173
  return;
1354
1174
  }
1355
- const stacks = options.parseErrorStacktrace(e);
1356
- const nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
1175
+ const stacks = options.parseErrorStacktrace(e), nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
1176
+ // we are checking that this module was processed by us at one point
1357
1177
  try {
1358
- return project._vite && project.getModuleById(stack.file) && existsSync(stack.file);
1178
+ const environments = [...Object.values(project._vite?.environments || {}), ...Object.values(project.browser?.vite.environments || {})], hasResult = environments.some((environment) => {
1179
+ const modules = environment.moduleGraph.getModulesByFile(stack.file);
1180
+ return [...modules?.values() || []].some((module) => !!module.transformResult);
1181
+ });
1182
+ return hasResult && existsSync(stack.file);
1359
1183
  } catch {
1360
1184
  return false;
1361
1185
  }
1362
1186
  });
1363
1187
  if (type) printErrorType(type, project.vitest);
1364
- printErrorMessage(e, logger);
1365
- if (options.screenshotPaths?.length) {
1188
+ if (printErrorMessage(e, logger), options.screenshotPaths?.length) {
1366
1189
  const length = options.screenshotPaths.length;
1367
- logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
1368
- logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
1369
- if (!e.diff) logger.error();
1190
+ if (logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`), logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n")), !e.diff) logger.error();
1370
1191
  }
1371
1192
  if (e.codeFrame) logger.error(`${e.codeFrame}\n`);
1372
1193
  if ("__vitest_rollup_error__" in e) {
@@ -1391,25 +1212,19 @@ function printErrorInner(error, project, options) {
1391
1212
  }
1392
1213
  });
1393
1214
  }
1394
- const testPath = e.VITEST_TEST_PATH;
1395
- const testName = e.VITEST_TEST_NAME;
1396
- const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
1215
+ const testPath = e.VITEST_TEST_PATH, testName = e.VITEST_TEST_NAME, afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
1397
1216
  // testName has testPath inside
1398
1217
  if (testPath) logger.error(c.red(`This error originated in "${c.bold(relative(project.config.root, testPath))}" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.`));
1399
1218
  if (testName) logger.error(c.red(`The latest test that might've caused the error is "${c.bold(testName)}". It might mean one of the following:
1400
1219
  - The error was thrown, while Vitest was running this test.
1401
1220
  - If the error occurred after the test had been completed, this was the last documented test before it was thrown.`));
1402
1221
  if (afterEnvTeardown) logger.error(c.red("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"));
1403
- if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
1404
- e.cause.name = `Caused by: ${e.cause.name}`;
1405
- printErrorInner(e.cause, project, {
1406
- showCodeFrame: false,
1407
- logger: options.logger,
1408
- parseErrorStacktrace: options.parseErrorStacktrace
1409
- });
1410
- }
1411
- handleImportOutsideModuleError(e.stack || "", logger);
1412
- return { nearest };
1222
+ if (typeof e.cause === "object" && e.cause && "name" in e.cause) e.cause.name = `Caused by: ${e.cause.name}`, printErrorInner(e.cause, project, {
1223
+ showCodeFrame: false,
1224
+ logger: options.logger,
1225
+ parseErrorStacktrace: options.parseErrorStacktrace
1226
+ });
1227
+ return handleImportOutsideModuleError(e.stack || "", logger), { nearest };
1413
1228
  }
1414
1229
  function printErrorType(type, ctx) {
1415
1230
  ctx.logger.error(`\n${errorBanner(type)}`);
@@ -1426,6 +1241,7 @@ const skipErrorProperties = new Set([
1426
1241
  "actual",
1427
1242
  "expected",
1428
1243
  "diffOptions",
1244
+ "runnerError",
1429
1245
  "sourceURL",
1430
1246
  "column",
1431
1247
  "line",
@@ -1453,7 +1269,7 @@ function handleImportOutsideModuleError(stack, logger) {
1453
1269
  if (!esmErrors.some((e) => stack.includes(e))) return;
1454
1270
  const path = normalize(stack.split("\n")[0].trim());
1455
1271
  let name = path.split("/node_modules/").pop() || "";
1456
- if (name?.startsWith("@")) name = name.split("/").slice(0, 2).join("/");
1272
+ if (name[0] === "@") name = name.split("/").slice(0, 2).join("/");
1457
1273
  else name = name.split("/")[0];
1458
1274
  if (name) printModuleWarningForPackage(logger, path, name);
1459
1275
  else printModuleWarningForSourceCode(logger, path);
@@ -1491,10 +1307,8 @@ function printErrorMessage(error, logger) {
1491
1307
  }
1492
1308
  function printStack(logger, project, stack, highlight, errorProperties, onStack) {
1493
1309
  for (const frame of stack) {
1494
- const color = frame === highlight ? c.cyan : c.gray;
1495
- const path = relative(project.config.root, frame.file);
1496
- logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
1497
- onStack?.(frame);
1310
+ const color = frame === highlight ? c.cyan : c.gray, path = relative(project.config.root, frame.file);
1311
+ logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`)), onStack?.(frame);
1498
1312
  }
1499
1313
  if (stack.length) logger.error();
1500
1314
  if (hasProperties(errorProperties)) {
@@ -1509,26 +1323,19 @@ function hasProperties(obj) {
1509
1323
  return false;
1510
1324
  }
1511
1325
  function generateCodeFrame(source, indent = 0, loc, range = 2) {
1512
- const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
1513
- const end = start;
1514
- const lines = source.split(lineSplitRE);
1515
- const nl = /\r\n/.test(source) ? 2 : 1;
1516
- let count = 0;
1517
- let res = [];
1326
+ const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc, end = start, lines = source.split(lineSplitRE), nl = /\r\n/.test(source) ? 2 : 1;
1327
+ let count = 0, res = [];
1518
1328
  const columns = process.stdout?.columns || 80;
1519
- for (let i = 0; i < lines.length; i++) {
1520
- count += lines[i].length + nl;
1521
- if (count >= start) {
1522
- for (let j = i - range; j <= i + range || end > count; j++) {
1523
- if (j < 0 || j >= lines.length) continue;
1524
- const lineLength = lines[j].length;
1329
+ for (let i = 0; i < lines.length; i++) if (count += lines[i].length + nl, count >= start) {
1330
+ for (let j = i - range; j <= i + range || end > count; j++) {
1331
+ if (j < 0 || j >= lines.length) continue;
1332
+ const lineLength = lines[j].length, strippedContent = stripVTControlCharacters(lines[j]);
1333
+ if (!strippedContent.startsWith("//# sourceMappingURL")) {
1525
1334
  // too long, maybe it's a minified file, skip for codeframe
1526
- if (stripVTControlCharacters(lines[j]).length > 200) return "";
1527
- res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent));
1528
- if (j === i) {
1335
+ if (strippedContent.length > 200) return "";
1336
+ if (res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent)), j === i) {
1529
1337
  // push underline
1530
- const pad = start - (count - lineLength) + (nl - 1);
1531
- const length = Math.max(1, end > count ? lineLength - pad : end - start);
1338
+ const pad = start - (count - lineLength) + (nl - 1), length = Math.max(1, end > count ? lineLength - pad : end - start);
1532
1339
  res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
1533
1340
  } else if (j > i) {
1534
1341
  if (end > count) {
@@ -1538,8 +1345,8 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
1538
1345
  count += lineLength + 1;
1539
1346
  }
1540
1347
  }
1541
- break;
1542
1348
  }
1349
+ break;
1543
1350
  }
1544
1351
  if (indent) res = res.map((line) => " ".repeat(indent) + line);
1545
1352
  return res.join("\n");
@@ -1559,8 +1366,7 @@ class GithubActionsReporter {
1559
1366
  }
1560
1367
  onTestCaseAnnotate(testCase, annotation) {
1561
1368
  if (!annotation.location) return;
1562
- const type = getTitle(annotation.type);
1563
- const formatted = formatMessage({
1369
+ const type = getTitle(annotation.type), formatted = formatMessage({
1564
1370
  command: getType(annotation.type),
1565
1371
  properties: {
1566
1372
  file: annotation.location.file,
@@ -1572,17 +1378,15 @@ class GithubActionsReporter {
1572
1378
  });
1573
1379
  this.ctx.logger.log(`\n${formatted}`);
1574
1380
  }
1575
- onFinished(files = [], errors = []) {
1576
- // collect all errors and associate them with projects
1577
- const projectErrors = new Array();
1381
+ onTestRunEnd(testModules, unhandledErrors) {
1382
+ const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], projectErrors = new Array();
1578
1383
  for (const error of errors) projectErrors.push({
1579
1384
  project: this.ctx.getRootProject(),
1580
1385
  title: "Unhandled error",
1581
1386
  error
1582
1387
  });
1583
1388
  for (const file of files) {
1584
- const tasks = getTasks(file);
1585
- const project = this.ctx.getProjectByName(file.projectName || "");
1389
+ const tasks = getTasks(file), project = this.ctx.getProjectByName(file.projectName || "");
1586
1390
  for (const task of tasks) {
1587
1391
  if (task.result?.state !== "fail") continue;
1588
1392
  const title = getFullName(task, " > ");
@@ -1600,8 +1404,7 @@ class GithubActionsReporter {
1600
1404
  const result = capturePrintError(error, this.ctx, {
1601
1405
  project,
1602
1406
  task: file
1603
- });
1604
- const stack = result?.nearest;
1407
+ }), stack = result?.nearest;
1605
1408
  if (!stack) continue;
1606
1409
  const formatted = formatMessage({
1607
1410
  command: "error",
@@ -1623,12 +1426,10 @@ const BUILT_IN_TYPES = [
1623
1426
  "warning"
1624
1427
  ];
1625
1428
  function getTitle(type) {
1626
- if (BUILT_IN_TYPES.includes(type)) return void 0;
1627
- return type;
1429
+ return BUILT_IN_TYPES.includes(type) ? void 0 : type;
1628
1430
  }
1629
1431
  function getType(type) {
1630
- if (BUILT_IN_TYPES.includes(type)) return type;
1631
- return "notice";
1432
+ return BUILT_IN_TYPES.includes(type) ? type : "notice";
1632
1433
  }
1633
1434
  function defaultOnWritePath(path) {
1634
1435
  return path;
@@ -1638,12 +1439,9 @@ function defaultOnWritePath(path) {
1638
1439
  // https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
1639
1440
  function formatMessage({ command, properties, message }) {
1640
1441
  let result = `::${command}`;
1641
- Object.entries(properties).forEach(([k, v], i) => {
1642
- result += i === 0 ? " " : ",";
1643
- result += `${k}=${escapeProperty(v)}`;
1644
- });
1645
- result += `::${escapeData(message)}`;
1646
- return result;
1442
+ return Object.entries(properties).forEach(([k, v], i) => {
1443
+ result += i === 0 ? " " : ",", result += `${k}=${escapeProperty(v)}`;
1444
+ }), result += `::${escapeData(message)}`, result;
1647
1445
  }
1648
1446
  function escapeData(s) {
1649
1447
  return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
@@ -1676,41 +1474,27 @@ class JsonReporter {
1676
1474
  start = 0;
1677
1475
  ctx;
1678
1476
  options;
1477
+ coverageMap;
1679
1478
  constructor(options) {
1680
1479
  this.options = options;
1681
1480
  }
1682
1481
  onInit(ctx) {
1683
- this.ctx = ctx;
1684
- this.start = Date.now();
1685
- }
1686
- async logTasks(files, coverageMap) {
1687
- const suites = getSuites(files);
1688
- const numTotalTestSuites = suites.length;
1689
- const tests = getTests(files);
1690
- const numTotalTests = tests.length;
1691
- const numFailedTestSuites = suites.filter((s) => s.result?.state === "fail").length;
1692
- const numPendingTestSuites = suites.filter((s) => s.result?.state === "run" || s.result?.state === "queued" || s.mode === "todo").length;
1693
- const numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites;
1694
- const numFailedTests = tests.filter((t) => t.result?.state === "fail").length;
1695
- const numPassedTests = tests.filter((t) => t.result?.state === "pass").length;
1696
- const numPendingTests = tests.filter((t) => t.result?.state === "run" || t.result?.state === "queued" || t.mode === "skip" || t.result?.state === "skip").length;
1697
- const numTodoTests = tests.filter((t) => t.mode === "todo").length;
1698
- const testResults = [];
1699
- const success = !!(files.length > 0 || this.ctx.config.passWithNoTests) && numFailedTestSuites === 0 && numFailedTests === 0;
1482
+ this.ctx = ctx, this.start = Date.now(), this.coverageMap = void 0;
1483
+ }
1484
+ onCoverage(coverageMap) {
1485
+ this.coverageMap = coverageMap;
1486
+ }
1487
+ async onTestRunEnd(testModules) {
1488
+ const files = testModules.map((testModule) => testModule.task), suites = getSuites(files), numTotalTestSuites = suites.length, tests = getTests(files), numTotalTests = tests.length, numFailedTestSuites = suites.filter((s) => s.result?.state === "fail").length, numPendingTestSuites = suites.filter((s) => s.result?.state === "run" || s.result?.state === "queued" || s.mode === "todo").length, numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites, numFailedTests = tests.filter((t) => t.result?.state === "fail").length, numPassedTests = tests.filter((t) => t.result?.state === "pass").length, numPendingTests = tests.filter((t) => t.result?.state === "run" || t.result?.state === "queued" || t.mode === "skip" || t.result?.state === "skip").length, numTodoTests = tests.filter((t) => t.mode === "todo").length, testResults = [], success = !!(files.length > 0 || this.ctx.config.passWithNoTests) && numFailedTestSuites === 0 && numFailedTests === 0;
1700
1489
  for (const file of files) {
1701
1490
  const tests = getTests([file]);
1702
1491
  let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
1703
1492
  if (startTime === Number.POSITIVE_INFINITY) startTime = this.start;
1704
- const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime);
1705
- const assertionResults = tests.map((t) => {
1493
+ const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime), assertionResults = tests.map((t) => {
1706
1494
  const ancestorTitles = [];
1707
1495
  let iter = t.suite;
1708
- while (iter) {
1709
- ancestorTitles.push(iter.name);
1710
- iter = iter.suite;
1711
- }
1712
- ancestorTitles.reverse();
1713
- return {
1496
+ while (iter) ancestorTitles.push(iter.name), iter = iter.suite;
1497
+ return ancestorTitles.reverse(), {
1714
1498
  ancestorTitles,
1715
1499
  fullName: t.name ? [...ancestorTitles, t.name].join(" ") : ancestorTitles.join(" "),
1716
1500
  status: StatusMap[t.result?.state || t.mode] || "skipped",
@@ -1746,13 +1530,10 @@ class JsonReporter {
1746
1530
  startTime: this.start,
1747
1531
  success,
1748
1532
  testResults,
1749
- coverageMap
1533
+ coverageMap: this.coverageMap
1750
1534
  };
1751
1535
  await this.writeReport(JSON.stringify(result));
1752
1536
  }
1753
- async onFinished(files = this.ctx.state.getFiles(), _errors = [], coverageMap) {
1754
- await this.logTasks(files, coverageMap);
1755
- }
1756
1537
  /**
1757
1538
  * Writes the report to an output file if specified in the config,
1758
1539
  * or logs it to the console otherwise.
@@ -1761,11 +1542,9 @@ class JsonReporter {
1761
1542
  async writeReport(report) {
1762
1543
  const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
1763
1544
  if (outputFile) {
1764
- const reportFile = resolve(this.ctx.config.root, outputFile);
1765
- const outputDirectory = dirname(reportFile);
1545
+ const reportFile = resolve(this.ctx.config.root, outputFile), outputDirectory = dirname(reportFile);
1766
1546
  if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
1767
- await promises.writeFile(reportFile, report, "utf-8");
1768
- this.ctx.logger.log(`JSON report written to ${reportFile}`);
1547
+ await promises.writeFile(reportFile, report, "utf-8"), this.ctx.logger.log(`JSON report written to ${reportFile}`);
1769
1548
  } else this.ctx.logger.log(report);
1770
1549
  }
1771
1550
  }
@@ -1788,8 +1567,7 @@ class IndentedLogger {
1788
1567
 
1789
1568
  function flattenTasks$1(task, baseName = "") {
1790
1569
  const base = baseName ? `${baseName} > ` : "";
1791
- if (task.type === "suite") return task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`));
1792
- else return [{
1570
+ return task.type === "suite" ? task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`)) : [{
1793
1571
  ...task,
1794
1572
  name: `${base}${task.name}`
1795
1573
  }];
@@ -1797,21 +1575,16 @@ function flattenTasks$1(task, baseName = "") {
1797
1575
  // https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
1798
1576
  function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
1799
1577
  let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
1800
- value = String(value || "").replace(regex, "");
1801
- {
1802
- // remove everything discouraged by XML 1.0 specifications
1803
- regex = new RegExp(
1804
- /* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
1805
- "([\\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]))",
1806
- "g"
1807
- /* eslint-enable */
1808
- );
1809
- value = value.replace(regex, "");
1810
- }
1578
+ if (value = String(value || "").replace(regex, ""), removeDiscouragedChars) regex = new RegExp(
1579
+ /* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
1580
+ "([\\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]))",
1581
+ "g"
1582
+ /* eslint-enable */
1583
+ ), value = value.replace(regex, "");
1811
1584
  return value;
1812
1585
  }
1813
1586
  function escapeXML(value) {
1814
- return removeInvalidXMLCharacters(String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
1587
+ return removeInvalidXMLCharacters(String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), true);
1815
1588
  }
1816
1589
  function executionTime(durationMS) {
1817
1590
  return (durationMS / 1e3).toLocaleString("en-US", {
@@ -1832,8 +1605,7 @@ class JUnitReporter {
1832
1605
  fileFd;
1833
1606
  options;
1834
1607
  constructor(options) {
1835
- this.options = { ...options };
1836
- this.options.includeConsoleOutput ??= true;
1608
+ this.options = { ...options }, this.options.includeConsoleOutput ??= true;
1837
1609
  }
1838
1610
  async onInit(ctx) {
1839
1611
  this.ctx = ctx;
@@ -1843,14 +1615,12 @@ class JUnitReporter {
1843
1615
  const outputDirectory = dirname(this.reportFile);
1844
1616
  if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
1845
1617
  const fileFd = await promises.open(this.reportFile, "w+");
1846
- this.fileFd = fileFd;
1847
- this.baseLog = async (text) => {
1618
+ this.fileFd = fileFd, this.baseLog = async (text) => {
1848
1619
  if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
1849
1620
  await promises.writeFile(this.fileFd, `${text}\n`);
1850
1621
  };
1851
1622
  } else this.baseLog = async (text) => this.ctx.logger.log(text);
1852
- this._timeStart = /* @__PURE__ */ new Date();
1853
- this.logger = new IndentedLogger(this.baseLog);
1623
+ this._timeStart = /* @__PURE__ */ new Date(), this.logger = new IndentedLogger(this.baseLog);
1854
1624
  }
1855
1625
  async writeElement(name, attrs, children) {
1856
1626
  const pairs = [];
@@ -1859,18 +1629,12 @@ class JUnitReporter {
1859
1629
  if (attr === void 0) continue;
1860
1630
  pairs.push(`${key}="${escapeXML(attr)}"`);
1861
1631
  }
1862
- await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`);
1863
- this.logger.indent();
1864
- await children.call(this);
1865
- this.logger.unindent();
1866
- await this.logger.log(`</${name}>`);
1632
+ await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`), this.logger.indent(), await children.call(this), this.logger.unindent(), await this.logger.log(`</${name}>`);
1867
1633
  }
1868
1634
  async writeLogs(task, type) {
1869
1635
  if (task.logs == null || task.logs.length === 0) return;
1870
- const logType = type === "err" ? "stderr" : "stdout";
1871
- const logs = task.logs.filter((log) => log.type === logType);
1872
- if (logs.length === 0) return;
1873
- await this.writeElement(`system-${type}`, {}, async () => {
1636
+ const logType = type === "err" ? "stderr" : "stdout", logs = task.logs.filter((log) => log.type === logType);
1637
+ logs.length !== 0 && await this.writeElement(`system-${type}`, {}, async () => {
1874
1638
  for (const log of logs) await this.baseLog(escapeXML(log.content));
1875
1639
  });
1876
1640
  }
@@ -1883,27 +1647,18 @@ class JUnitReporter {
1883
1647
  };
1884
1648
  if (typeof this.options.classnameTemplate === "function") classname = this.options.classnameTemplate(templateVars);
1885
1649
  else if (typeof this.options.classnameTemplate === "string") classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
1886
- else if (typeof this.options.classname === "string") classname = this.options.classname;
1887
1650
  await this.writeElement("testcase", {
1888
1651
  classname,
1889
1652
  file: this.options.addFileAttribute ? filename : void 0,
1890
1653
  name: task.name,
1891
1654
  time: getDuration(task)
1892
1655
  }, async () => {
1893
- if (this.options.includeConsoleOutput) {
1894
- await this.writeLogs(task, "out");
1895
- await this.writeLogs(task, "err");
1896
- }
1656
+ if (this.options.includeConsoleOutput) await this.writeLogs(task, "out"), await this.writeLogs(task, "err");
1897
1657
  if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
1898
1658
  if (task.type === "test" && task.annotations.length) {
1899
- await this.logger.log("<properties>");
1900
- this.logger.indent();
1901
- for (const annotation of task.annotations) {
1902
- await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`);
1903
- await this.logger.log("</property>");
1904
- }
1905
- this.logger.unindent();
1906
- await this.logger.log("</properties>");
1659
+ await this.logger.log("<properties>"), this.logger.indent();
1660
+ for (const annotation of task.annotations) await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`), await this.logger.log("</property>");
1661
+ this.logger.unindent(), await this.logger.log("</properties>");
1907
1662
  }
1908
1663
  if (task.result?.state === "fail") {
1909
1664
  const errors = task.result.errors || [];
@@ -1922,11 +1677,11 @@ class JUnitReporter {
1922
1677
  });
1923
1678
  }
1924
1679
  }
1925
- async onFinished(files = this.ctx.state.getFiles()) {
1680
+ async onTestRunEnd(testModules) {
1681
+ const files = testModules.map((testModule) => testModule.task);
1926
1682
  await this.logger.log("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
1927
1683
  const transformed = files.map((file) => {
1928
- const tasks = file.tasks.flatMap((task) => flattenTasks$1(task));
1929
- const stats = tasks.reduce((stats, task) => {
1684
+ const tasks = file.tasks.flatMap((task) => flattenTasks$1(task)), stats = tasks.reduce((stats, task) => {
1930
1685
  return {
1931
1686
  passed: stats.passed + Number(task.result?.state === "pass"),
1932
1687
  failures: stats.failures + Number(task.result?.state === "fail"),
@@ -1936,41 +1691,29 @@ class JUnitReporter {
1936
1691
  passed: 0,
1937
1692
  failures: 0,
1938
1693
  skipped: 0
1939
- });
1940
- // inject failed suites to surface errors during beforeAll/afterAll
1941
- const suites = getSuites(file);
1942
- for (const suite of suites) if (suite.result?.errors) {
1943
- tasks.push(suite);
1944
- stats.failures += 1;
1945
- }
1694
+ }), suites = getSuites(file);
1695
+ for (const suite of suites) if (suite.result?.errors) tasks.push(suite), stats.failures += 1;
1946
1696
  // If there are no tests, but the file failed to load, we still want to report it as a failure
1947
- if (tasks.length === 0 && file.result?.state === "fail") {
1948
- stats.failures = 1;
1949
- tasks.push({
1950
- id: file.id,
1951
- type: "test",
1952
- name: file.name,
1953
- mode: "run",
1954
- result: file.result,
1955
- meta: {},
1956
- timeout: 0,
1957
- context: null,
1958
- suite: null,
1959
- file: null,
1960
- annotations: []
1961
- });
1962
- }
1697
+ if (tasks.length === 0 && file.result?.state === "fail") stats.failures = 1, tasks.push({
1698
+ id: file.id,
1699
+ type: "test",
1700
+ name: file.name,
1701
+ mode: "run",
1702
+ result: file.result,
1703
+ meta: {},
1704
+ timeout: 0,
1705
+ context: null,
1706
+ suite: null,
1707
+ file: null,
1708
+ annotations: []
1709
+ });
1963
1710
  return {
1964
1711
  ...file,
1965
1712
  tasks,
1966
1713
  stats
1967
1714
  };
1968
- });
1969
- const stats = transformed.reduce((stats, file) => {
1970
- stats.tests += file.tasks.length;
1971
- stats.failures += file.stats.failures;
1972
- stats.time += file.result?.duration || 0;
1973
- return stats;
1715
+ }), stats = transformed.reduce((stats, file) => {
1716
+ return stats.tests += file.tasks.length, stats.failures += file.stats.failures, stats.time += file.result?.duration || 0, stats;
1974
1717
  }, {
1975
1718
  name: this.options.suiteName || "vitest tests",
1976
1719
  tests: 0,
@@ -1978,7 +1721,7 @@ class JUnitReporter {
1978
1721
  errors: 0,
1979
1722
  time: 0
1980
1723
  });
1981
- await this.writeElement("testsuites", {
1724
+ if (await this.writeElement("testsuites", {
1982
1725
  ...stats,
1983
1726
  time: executionTime(stats.time)
1984
1727
  }, async () => {
@@ -1997,16 +1740,13 @@ class JUnitReporter {
1997
1740
  await this.writeTasks(file.tasks, filename);
1998
1741
  });
1999
1742
  }
2000
- });
2001
- if (this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
2002
- await this.fileFd?.close();
2003
- this.fileFd = void 0;
1743
+ }), this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
1744
+ await this.fileFd?.close(), this.fileFd = void 0;
2004
1745
  }
2005
1746
  }
2006
1747
 
2007
1748
  function yamlString(str) {
2008
- if (!str) return "";
2009
- return `"${str.replace(/"/g, "\\\"")}"`;
1749
+ return str ? `"${str.replace(/"/g, "\\\"")}"` : "";
2010
1750
  }
2011
1751
  function tapString(str) {
2012
1752
  return str.replace(/\\/g, "\\\\").replace(/#/g, "\\#").replace(/\n/g, " ");
@@ -2015,77 +1755,45 @@ class TapReporter {
2015
1755
  ctx;
2016
1756
  logger;
2017
1757
  onInit(ctx) {
2018
- this.ctx = ctx;
2019
- this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
1758
+ this.ctx = ctx, this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
2020
1759
  }
2021
1760
  static getComment(task) {
2022
- if (task.mode === "skip") return " # SKIP";
2023
- else if (task.mode === "todo") return " # TODO";
2024
- else if (task.result?.duration != null) return ` # time=${task.result.duration.toFixed(2)}ms`;
2025
- else return "";
1761
+ return task.mode === "skip" ? " # SKIP" : task.mode === "todo" ? " # TODO" : task.result?.duration == null ? "" : ` # time=${task.result.duration.toFixed(2)}ms`;
2026
1762
  }
2027
1763
  logErrorDetails(error, stack) {
2028
1764
  const errorName = error.name || "Unknown Error";
2029
- this.logger.log(`name: ${yamlString(String(errorName))}`);
2030
- this.logger.log(`message: ${yamlString(String(error.message))}`);
2031
- if (stack)
1765
+ if (this.logger.log(`name: ${yamlString(String(errorName))}`), this.logger.log(`message: ${yamlString(String(error.message))}`), stack)
2032
1766
  // For compatibility with tap-mocha-reporter
2033
1767
  this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
2034
1768
  }
2035
1769
  logTasks(tasks) {
2036
1770
  this.logger.log(`1..${tasks.length}`);
2037
1771
  for (const [i, task] of tasks.entries()) {
2038
- const id = i + 1;
2039
- const ok = task.result?.state === "pass" || task.mode === "skip" || task.mode === "todo" ? "ok" : "not ok";
2040
- const comment = TapReporter.getComment(task);
2041
- if (task.type === "suite" && task.tasks.length > 0) {
2042
- this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment} {`);
2043
- this.logger.indent();
2044
- this.logTasks(task.tasks);
2045
- this.logger.unindent();
2046
- this.logger.log("}");
2047
- } else {
1772
+ const id = i + 1, ok = task.result?.state === "pass" || task.mode === "skip" || task.mode === "todo" ? "ok" : "not ok", comment = TapReporter.getComment(task);
1773
+ if (task.type === "suite" && task.tasks.length > 0) this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment} {`), this.logger.indent(), this.logTasks(task.tasks), this.logger.unindent(), this.logger.log("}");
1774
+ else {
2048
1775
  this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
2049
1776
  const project = this.ctx.getProjectByName(task.file.projectName || "");
2050
- if (task.type === "test" && task.annotations) {
2051
- this.logger.indent();
2052
- task.annotations.forEach(({ type, message }) => {
2053
- this.logger.log(`# ${type}: ${message}`);
2054
- });
2055
- this.logger.unindent();
2056
- }
2057
- if (task.result?.state === "fail" && task.result.errors) {
2058
- this.logger.indent();
2059
- task.result.errors.forEach((error) => {
2060
- const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace });
2061
- const stack = stacks[0];
2062
- this.logger.log("---");
2063
- this.logger.log("error:");
2064
- this.logger.indent();
2065
- this.logErrorDetails(error);
2066
- this.logger.unindent();
2067
- if (stack) this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
2068
- if (error.showDiff) {
2069
- this.logger.log(`actual: ${yamlString(error.actual)}`);
2070
- this.logger.log(`expected: ${yamlString(error.expected)}`);
2071
- }
2072
- });
2073
- this.logger.log("...");
2074
- this.logger.unindent();
2075
- }
1777
+ if (task.type === "test" && task.annotations) this.logger.indent(), task.annotations.forEach(({ type, message }) => {
1778
+ this.logger.log(`# ${type}: ${message}`);
1779
+ }), this.logger.unindent();
1780
+ if (task.result?.state === "fail" && task.result.errors) this.logger.indent(), task.result.errors.forEach((error) => {
1781
+ const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace }), stack = stacks[0];
1782
+ if (this.logger.log("---"), this.logger.log("error:"), this.logger.indent(), this.logErrorDetails(error), this.logger.unindent(), stack) this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
1783
+ if (error.showDiff) this.logger.log(`actual: ${yamlString(error.actual)}`), this.logger.log(`expected: ${yamlString(error.expected)}`);
1784
+ }), this.logger.log("..."), this.logger.unindent();
2076
1785
  }
2077
1786
  }
2078
1787
  }
2079
- onFinished(files = this.ctx.state.getFiles()) {
2080
- this.logger.log("TAP version 13");
2081
- this.logTasks(files);
1788
+ onTestRunEnd(testModules) {
1789
+ const files = testModules.map((testModule) => testModule.task);
1790
+ this.logger.log("TAP version 13"), this.logTasks(files);
2082
1791
  }
2083
1792
  }
2084
1793
 
2085
1794
  function flattenTasks(task, baseName = "") {
2086
1795
  const base = baseName ? `${baseName} > ` : "";
2087
- if (task.type === "suite" && task.tasks.length > 0) return task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`));
2088
- else return [{
1796
+ return task.type === "suite" && task.tasks.length > 0 ? task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`)) : [{
2089
1797
  ...task,
2090
1798
  name: `${base}${task.name}`
2091
1799
  }];
@@ -2094,65 +1802,33 @@ class TapFlatReporter extends TapReporter {
2094
1802
  onInit(ctx) {
2095
1803
  super.onInit(ctx);
2096
1804
  }
2097
- onFinished(files = this.ctx.state.getFiles()) {
1805
+ onTestRunEnd(testModules) {
2098
1806
  this.ctx.logger.log("TAP version 13");
2099
- const flatTasks = files.flatMap((task) => flattenTasks(task));
1807
+ const flatTasks = testModules.flatMap((testModule) => flattenTasks(testModule.task));
2100
1808
  this.logTasks(flatTasks);
2101
1809
  }
2102
1810
  }
2103
1811
 
1812
+ class TreeReporter extends DefaultReporter {
1813
+ verbose = true;
1814
+ renderSucceed = true;
1815
+ }
1816
+
2104
1817
  class VerboseReporter extends DefaultReporter {
2105
1818
  verbose = true;
2106
1819
  renderSucceed = true;
2107
- printTestModule(module) {
2108
- // still print the test module in TTY,
2109
- // but don't print it in the CLI because we
2110
- // print all the tests when they finish
2111
- // instead of printing them when the test file finishes
2112
- if (this.isTTY) return super.printTestModule(module);
1820
+ printTestModule(_module) {
1821
+ // don't print test module, only print tests
2113
1822
  }
2114
1823
  onTestCaseResult(test) {
2115
1824
  super.onTestCaseResult(test);
2116
- // don't print tests in TTY as they go, only print them
2117
- // in the CLI when they finish
2118
- if (this.isTTY) return;
2119
1825
  const testResult = test.result();
2120
1826
  if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") return;
2121
- let title = ` ${getStateSymbol(test.task)} `;
2122
- if (test.project.name) title += formatProjectName(test.project);
2123
- title += getFullName(test.task, c.dim(" > "));
2124
- title += this.getDurationPrefix(test.task);
2125
- const diagnostic = test.diagnostic();
2126
- if (diagnostic?.heap != null) title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
2127
- if (testResult.state === "skipped" && testResult.note) title += c.dim(c.gray(` [${testResult.note}]`));
2128
- this.log(title);
2129
- if (testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
2130
- if (test.annotations().length) {
2131
- this.log();
2132
- this.printAnnotations(test, "log", 3);
2133
- this.log();
2134
- }
2135
- }
2136
- printTestSuite(testSuite) {
2137
- const indentation = " ".repeat(getIndentation(testSuite.task));
2138
- const tests = Array.from(testSuite.children.allTests());
2139
- const state = getStateSymbol(testSuite.task);
2140
- this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`);
2141
- }
2142
- getTestName(test) {
2143
- return test.name;
2144
- }
2145
- getTestIndentation(test) {
2146
- return " ".repeat(getIndentation(test));
1827
+ let title = ` ${this.getEntityPrefix(test)} `;
1828
+ if (title += test.module.task.name, test.location) title += c.dim(`:${test.location.line}:${test.location.column}`);
1829
+ if (title += separator, title += getTestName(test.task, separator), title += this.getTestCaseSuffix(test), this.log(title), testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error.message}`)));
1830
+ if (test.annotations().length) this.log(), this.printAnnotations(test, "log", 3), this.log();
2147
1831
  }
2148
- formatShortError() {
2149
- // Short errors are not shown in tree-view
2150
- return "";
2151
- }
2152
- }
2153
- function getIndentation(suite, level = 1) {
2154
- if (suite.suite && !("filepath" in suite.suite)) return getIndentation(suite.suite, level + 1);
2155
- return level;
2156
1832
  }
2157
1833
 
2158
1834
  const ReportersMap = {
@@ -2164,8 +1840,9 @@ const ReportersMap = {
2164
1840
  "tap": TapReporter,
2165
1841
  "tap-flat": TapFlatReporter,
2166
1842
  "junit": JUnitReporter,
1843
+ "tree": TreeReporter,
2167
1844
  "hanging-process": HangingProcessReporter,
2168
1845
  "github-actions": GithubActionsReporter
2169
1846
  };
2170
1847
 
2171
- export { BlobReporter as B, DefaultReporter as D, F_RIGHT as F, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, ReportersMap as R, TapFlatReporter as T, VerboseReporter as V, DotReporter as a, JUnitReporter as b, TapReporter as c, printError as d, errorBanner as e, formatProjectName as f, getStateSymbol as g, divider as h, generateCodeFrame as i, parse as p, readBlobs as r, stringify as s, truncateString as t, utils as u, withLabel as w };
1848
+ export { BlobReporter as B, DefaultReporter as D, F_RIGHT as F, GithubActionsReporter as G, HangingProcessReporter as H, JsonReporter as J, ReportersMap as R, TapFlatReporter as T, VerboseReporter as V, DotReporter as a, JUnitReporter as b, TapReporter as c, stringify as d, printError as e, formatProjectName as f, getStateSymbol as g, errorBanner as h, divider as i, generateCodeFrame as j, parse as p, readBlobs as r, separator as s, truncateString as t, utils as u, withLabel as w };