vitest 4.0.0-beta.5 → 4.0.0-beta.7

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 (64) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/browser.d.ts +3 -3
  3. package/dist/browser.js +2 -2
  4. package/dist/chunks/{base.DMfOuRWD.js → base.BXI97p6t.js} +7 -16
  5. package/dist/chunks/{benchmark.CtuRzf-i.js → benchmark.UW6Ezvxy.js} +4 -9
  6. package/dist/chunks/{browser.d.Cawq_X_N.d.ts → browser.d.DOMmqJQx.d.ts} +1 -1
  7. package/dist/chunks/{cac.CKnbxhn2.js → cac.Dsn7ixFt.js} +38 -113
  8. package/dist/chunks/{cli-api.COn58yrl.js → cli-api.DfGJyldU.js} +829 -1232
  9. package/dist/chunks/{config.d.CKNVOKm0.d.ts → config.d._GBBbReY.d.ts} +1 -0
  10. package/dist/chunks/{console.Duv2dVIC.js → console.B0quX7yH.js} +32 -68
  11. package/dist/chunks/{coverage.B6cReEn1.js → coverage.Dvxug1RM.js} +210 -579
  12. package/dist/chunks/{creator.DUVZ6rfm.js → creator.KEg6n5IC.js} +28 -74
  13. package/dist/chunks/{date.Bq6ZW5rf.js → date.-jtEtIeV.js} +6 -17
  14. package/dist/chunks/{git.BVQ8w_Sw.js → git.BFNcloKD.js} +1 -2
  15. package/dist/chunks/{globals.CJQ63oO0.js → globals.lgsmH00r.js} +5 -5
  16. package/dist/chunks/{index.QZr3S3vQ.js → index.AR8aAkCC.js} +2 -2
  17. package/dist/chunks/{index.DQhAfQQU.js → index.C3EbxYwt.js} +276 -607
  18. package/dist/chunks/{index.oWRWx-nj.js → index.CsFXYRkW.js} +17 -36
  19. package/dist/chunks/{index.DgN0Zk9a.js → index.D2B6d2vv.js} +14 -24
  20. package/dist/chunks/{index.BRtIe7r8.js → index.DfviD7lX.js} +55 -110
  21. package/dist/chunks/{inspector.C914Efll.js → inspector.CvQD-Nie.js} +10 -25
  22. package/dist/chunks/{moduleRunner.d.mmOmOGrW.d.ts → moduleRunner.d.CX4DuqOx.d.ts} +2 -2
  23. package/dist/chunks/{node.4JV5OXkt.js → node.BOqcT2jW.js} +1 -1
  24. package/dist/chunks/{plugin.d.CvOlgjxK.d.ts → plugin.d.vcD4xbMS.d.ts} +1 -1
  25. package/dist/chunks/{reporters.d.CYE9sT5z.d.ts → reporters.d.BC86JJdB.d.ts} +799 -758
  26. package/dist/chunks/{resolver.D5bG4zy5.js → resolver.Bx6lE0iq.js} +21 -64
  27. package/dist/chunks/{rpc.DGoW_Vl-.js → rpc.RpPylpp0.js} +7 -21
  28. package/dist/chunks/{runBaseTests.B3KcKqlF.js → runBaseTests.D6sfuWBM.js} +25 -54
  29. package/dist/chunks/{setup-common.lgPs-bYv.js → setup-common.hLGRxhC8.js} +9 -22
  30. package/dist/chunks/{startModuleRunner.C8FtT_BY.js → startModuleRunner.C8TW8zTN.js} +83 -205
  31. package/dist/chunks/{typechecker.BgoW4nTA.js → typechecker.DSo_maXz.js} +97 -209
  32. package/dist/chunks/{utils.CcGm2cd1.js → utils.C2YI6McM.js} +4 -13
  33. package/dist/chunks/{utils.B9FY3b73.js → utils.C7__0Iv5.js} +5 -14
  34. package/dist/chunks/{vi.DGAfBY4R.js → vi.BfdOiD4j.js} +110 -267
  35. package/dist/chunks/{vm.BKfKvaKl.js → vm.BHBje7cC.js} +73 -177
  36. package/dist/chunks/{worker.d.Db-UVmXc.d.ts → worker.d.BKu8cnnX.d.ts} +1 -1
  37. package/dist/chunks/{worker.d.D9QWnzAe.d.ts → worker.d.DYlqbejz.d.ts} +1 -1
  38. package/dist/cli.js +3 -3
  39. package/dist/config.d.ts +7 -7
  40. package/dist/coverage.d.ts +4 -4
  41. package/dist/coverage.js +2 -2
  42. package/dist/environments.js +1 -1
  43. package/dist/index.d.ts +6 -6
  44. package/dist/index.js +5 -5
  45. package/dist/module-evaluator.d.ts +3 -3
  46. package/dist/module-evaluator.js +33 -84
  47. package/dist/module-runner.js +2 -2
  48. package/dist/node.d.ts +11 -9
  49. package/dist/node.js +16 -27
  50. package/dist/reporters.d.ts +5 -5
  51. package/dist/reporters.js +3 -3
  52. package/dist/runners.d.ts +1 -1
  53. package/dist/runners.js +23 -51
  54. package/dist/snapshot.js +2 -2
  55. package/dist/suite.js +2 -2
  56. package/dist/worker.js +18 -34
  57. package/dist/workers/forks.js +4 -4
  58. package/dist/workers/runVmTests.js +19 -37
  59. package/dist/workers/threads.js +4 -4
  60. package/dist/workers/vmForks.js +7 -7
  61. package/dist/workers/vmThreads.js +7 -7
  62. package/dist/workers.d.ts +3 -3
  63. package/dist/workers.js +11 -11
  64. package/package.json +11 -11
@@ -1,11 +1,11 @@
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.BgoW4nTA.js';
4
+ import { g as getOutputFile, h as hasFailedSnapshot, T as TypeCheckError } from './typechecker.DSo_maXz.js';
5
5
  import { performance as performance$1 } from 'node:perf_hooks';
6
6
  import { getTestName, getFullName, hasFailed, getTests, getSuites, getTasks } from '@vitest/runner/utils';
7
7
  import { slash, toArray, isPrimitive, inspect, positionToOffset, lineSplitRE } from '@vitest/utils';
8
- import { parseStacktrace, parseErrorStacktrace } from '@vitest/utils/source-map';
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';
@@ -125,16 +125,19 @@ class BlobReporter {
125
125
  start = 0;
126
126
  ctx;
127
127
  options;
128
+ coverage;
128
129
  constructor(options) {
129
130
  this.options = options;
130
131
  }
131
132
  onInit(ctx) {
132
133
  if (ctx.config.watch) throw new Error("Blob reporter is not supported in watch mode");
133
- this.ctx = ctx;
134
- this.start = performance.now();
134
+ this.ctx = ctx, this.start = performance.now(), this.coverage = void 0;
135
+ }
136
+ onCoverage(coverage) {
137
+ this.coverage = coverage;
135
138
  }
136
- async onFinished(files = [], errors = [], coverage) {
137
- const executionTime = performance.now() - this.start;
139
+ async onTestRunEnd(testModules, unhandledErrors) {
140
+ const executionTime = performance.now() - this.start, files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], coverage = this.coverage;
138
141
  let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
139
142
  if (!outputFile) {
140
143
  const shard = this.ctx.config.shard;
@@ -142,43 +145,34 @@ class BlobReporter {
142
145
  }
143
146
  const modules = this.ctx.projects.map((project) => {
144
147
  return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
145
- if (!mod[1].file) return null;
146
- return [
148
+ return mod[1].file ? [
147
149
  mod[0],
148
150
  mod[1].file,
149
151
  mod[1].url
150
- ];
152
+ ] : null;
151
153
  }).filter((x) => x != null)];
152
- });
153
- const report = [
154
+ }), report = [
154
155
  this.ctx.version,
155
156
  files,
156
157
  errors,
157
158
  modules,
158
159
  coverage,
159
160
  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);
161
+ ], reportFile = resolve(this.ctx.config.root, outputFile);
162
+ await writeBlob(report, reportFile), this.ctx.logger.log("blob report written to", reportFile);
164
163
  }
165
164
  }
166
165
  async function writeBlob(content, filename) {
167
- const report = stringify(content);
168
- const dir = dirname(filename);
166
+ const report = stringify(content), dir = dirname(filename);
169
167
  if (!existsSync(dir)) await mkdir(dir, { recursive: true });
170
168
  await writeFile(filename, report, "utf-8");
171
169
  }
172
170
  async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
173
171
  // 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);
172
+ const resolvedDir = resolve(process.cwd(), blobsDirectory), blobsFiles = await readdir(resolvedDir), promises = blobsFiles.map(async (filename) => {
173
+ const fullPath = resolve(resolvedDir, filename), stats = await stat(fullPath);
179
174
  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);
175
+ const content = await readFile(fullPath, "utf-8"), [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
182
176
  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
177
  return {
184
178
  version,
@@ -189,8 +183,7 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
189
183
  file: filename,
190
184
  executionTime
191
185
  };
192
- });
193
- const blobs = await Promise.all(promises);
186
+ }), blobs = await Promise.all(promises);
194
187
  if (!blobs.length) throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
195
188
  const versions = new Set(blobs.map((blob) => blob.version));
196
189
  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,27 +193,19 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
200
193
  blobs.forEach((blob) => {
201
194
  blob.moduleKeys.forEach(([projectName, moduleIds]) => {
202
195
  const project = projects[projectName];
203
- if (!project) return;
204
- moduleIds.forEach(([moduleId, file, url]) => {
196
+ project && moduleIds.forEach(([moduleId, file, url]) => {
205
197
  const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
206
- moduleNode.url = url;
207
- moduleNode.id = moduleId;
208
- moduleNode.transformResult = {
198
+ moduleNode.url = url, moduleNode.id = moduleId, moduleNode.transformResult = {
209
199
  code: " ",
210
200
  map: null
211
- };
212
- project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
201
+ }, project.vite.moduleGraph.idToModuleMap.set(moduleId, moduleNode);
213
202
  });
214
203
  });
215
204
  });
216
205
  const files = blobs.flatMap((blob) => blob.files).sort((f1, f2) => {
217
- const time1 = f1.result?.startTime || 0;
218
- const time2 = f2.result?.startTime || 0;
206
+ const time1 = f1.result?.startTime || 0, time2 = f2.result?.startTime || 0;
219
207
  return time1 - time2;
220
- });
221
- const errors = blobs.flatMap((blob) => blob.errors);
222
- const coverages = blobs.map((blob) => blob.coverage);
223
- const executionTimes = blobs.map((blob) => blob.executionTime);
208
+ }), errors = blobs.flatMap((blob) => blob.errors), coverages = blobs.map((blob) => blob.coverage), executionTimes = blobs.map((blob) => blob.executionTime);
224
209
  return {
225
210
  files,
226
211
  errors,
@@ -262,26 +247,18 @@ function errorBanner(message) {
262
247
  return divider(c.bold(c.bgRed(` ${message} `)), null, null, c.red);
263
248
  }
264
249
  function divider(text, left, right, color) {
265
- const cols = getCols();
266
- const c = color || ((text) => text);
250
+ const cols = getCols(), c = color || ((text) => text);
267
251
  if (text) {
268
252
  const textLength = stripVTControlCharacters(text).length;
269
253
  if (left == null && right != null) left = cols - textLength - right;
270
- else {
271
- left = left ?? Math.floor((cols - textLength) / 2);
272
- right = cols - textLength - left;
273
- }
274
- left = Math.max(0, left);
275
- right = Math.max(0, right);
276
- return `${c(F_LONG_DASH.repeat(left))}${text}${c(F_LONG_DASH.repeat(right))}`;
254
+ else left = left ?? Math.floor((cols - textLength) / 2), right = cols - textLength - left;
255
+ return left = Math.max(0, left), right = Math.max(0, right), `${c(F_LONG_DASH.repeat(left))}${text}${c(F_LONG_DASH.repeat(right))}`;
277
256
  }
278
257
  return F_LONG_DASH.repeat(cols);
279
258
  }
280
259
  function formatTestPath(root, path) {
281
260
  if (isAbsolute(path)) path = relative(root, path);
282
- const dir = dirname(path);
283
- const ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "";
284
- const base = basename(path, ext);
261
+ const dir = dirname(path), ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "", base = basename(path, ext);
285
262
  return slash(c.dim(`${dir}/`) + c.bold(base)) + c.dim(ext);
286
263
  }
287
264
  function renderSnapshotSummary(rootDir, snapshots) {
@@ -293,8 +270,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
293
270
  else summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
294
271
  if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
295
272
  const [head, ...tail] = snapshots.filesRemovedList;
296
- summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`);
297
- tail.forEach((key) => {
273
+ summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`), tail.forEach((key) => {
298
274
  summary.push(` ${c.gray(F_DOT)} ${formatTestPath(rootDir, key)}`);
299
275
  });
300
276
  }
@@ -302,8 +278,7 @@ function renderSnapshotSummary(rootDir, snapshots) {
302
278
  if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
303
279
  else summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
304
280
  snapshots.uncheckedKeysByFile.forEach((uncheckedFile) => {
305
- summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
306
- uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
281
+ summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`), uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
307
282
  });
308
283
  }
309
284
  return summary;
@@ -313,10 +288,7 @@ function countTestErrors(tasks) {
313
288
  }
314
289
  function getStateString$1(tasks, name = "tests", showTotal = true) {
315
290
  if (tasks.length === 0) return c.dim(`no ${name}`);
316
- const passed = tasks.filter((i) => i.result?.state === "pass");
317
- const failed = tasks.filter((i) => i.result?.state === "fail");
318
- const skipped = tasks.filter((i) => i.mode === "skip");
319
- const todo = tasks.filter((i) => i.mode === "todo");
291
+ 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");
320
292
  return [
321
293
  failed.length ? c.bold(c.red(`${failed.length} failed`)) : null,
322
294
  passed.length ? c.bold(c.green(`${passed.length} passed`)) : null,
@@ -330,16 +302,13 @@ function getStateSymbol(task) {
330
302
  if (task.result.state === "run" || task.result.state === "queued") {
331
303
  if (task.type === "suite") return pointer;
332
304
  }
333
- if (task.result.state === "pass") return task.meta?.benchmark ? benchmarkPass : testPass;
334
- if (task.result.state === "fail") return task.type === "suite" ? suiteFail : taskFail;
335
- return " ";
305
+ return task.result.state === "pass" ? task.meta?.benchmark ? benchmarkPass : testPass : task.result.state === "fail" ? task.type === "suite" ? suiteFail : taskFail : " ";
336
306
  }
337
307
  function formatTimeString(date) {
338
308
  return date.toTimeString().split(" ")[0];
339
309
  }
340
310
  function formatTime(time) {
341
- if (time > 1e3) return `${(time / 1e3).toFixed(2)}s`;
342
- return `${Math.round(time)}ms`;
311
+ return time > 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
343
312
  }
344
313
  function formatProjectName(project, suffix = " ") {
345
314
  if (!project?.name) return "";
@@ -360,8 +329,7 @@ function padSummaryTitle(str) {
360
329
  }
361
330
  function truncateString(text, maxLength) {
362
331
  const plainText = stripVTControlCharacters(text);
363
- if (plainText.length <= maxLength) return text;
364
- return `${plainText.slice(0, maxLength - 1)}…`;
332
+ return plainText.length <= maxLength ? text : `${plainText.slice(0, maxLength - 1)}…`;
365
333
  }
366
334
  function capitalize(text) {
367
335
  return `${text[0].toUpperCase()}${text.slice(1)}`;
@@ -407,9 +375,7 @@ class BaseReporter {
407
375
  this.isTTY = options.isTTY ?? isTTY;
408
376
  }
409
377
  onInit(ctx) {
410
- this.ctx = ctx;
411
- this.ctx.logger.printBanner();
412
- this.start = performance$1.now();
378
+ this.ctx = ctx, this.ctx.logger.printBanner(), this.start = performance$1.now();
413
379
  }
414
380
  log(...messages) {
415
381
  this.ctx.logger.log(...messages);
@@ -420,9 +386,9 @@ class BaseReporter {
420
386
  relative(path) {
421
387
  return relative(this.ctx.config.root, path);
422
388
  }
423
- onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
424
- this.end = performance$1.now();
425
- if (!files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
389
+ onTestRunEnd(testModules, unhandledErrors, _reason) {
390
+ const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors];
391
+ if (this.end = performance$1.now(), !files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
426
392
  else this.reportSummary(files, errors);
427
393
  }
428
394
  onTestCaseResult(testCase) {
@@ -441,13 +407,10 @@ class BaseReporter {
441
407
  printTestModule(testModule) {
442
408
  const moduleState = testModule.state();
443
409
  if (moduleState === "queued" || moduleState === "pending") return;
444
- let testsCount = 0;
445
- let failedCount = 0;
446
- let skippedCount = 0;
410
+ let testsCount = 0, failedCount = 0, skippedCount = 0;
447
411
  // delaying logs to calculate the test stats first
448
412
  // which minimizes the amount of for loops
449
- const logs = [];
450
- const originalLog = this.log.bind(this);
413
+ const logs = [], originalLog = this.log.bind(this);
451
414
  this.log = (msg) => logs.push(msg);
452
415
  const visit = (suiteState, children) => {
453
416
  for (const child of children) if (child.type === "suite") {
@@ -457,8 +420,7 @@ class BaseReporter {
457
420
  visit(suiteState, child.children);
458
421
  } else {
459
422
  const testResult = child.result();
460
- testsCount++;
461
- if (testResult.state === "failed") failedCount++;
423
+ if (testsCount++, testResult.state === "failed") failedCount++;
462
424
  else if (testResult.state === "skipped") skippedCount++;
463
425
  if (this.ctx.config.hideSkippedTests && suiteState === "skipped")
464
426
  // Skipped suites are hidden when --hideSkippedTests
@@ -475,24 +437,20 @@ class BaseReporter {
475
437
  tests: testsCount,
476
438
  failed: failedCount,
477
439
  skipped: skippedCount
478
- }));
479
- logs.forEach((log) => this.log(log));
440
+ })), logs.forEach((log) => this.log(log));
480
441
  }
481
442
  printTestCase(moduleState, test) {
482
- const testResult = test.result();
483
- const { duration, retryCount, repeatCount } = test.diagnostic() || {};
484
- const padding = this.getTestIndentation(test.task);
443
+ const testResult = test.result(), { duration, retryCount, repeatCount } = test.diagnostic() || {}, padding = this.getTestIndentation(test.task);
485
444
  let suffix = this.getDurationPrefix(test.task);
486
445
  if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
487
446
  if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
488
- if (testResult.state === "failed") {
489
- this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(" > "))}`) + suffix);
490
- // print short errors, full errors will be at the end in summary
491
- testResult.errors.forEach((error) => {
492
- const message = this.formatShortError(error);
493
- if (message) this.log(c.red(` ${padding}${message}`));
494
- });
495
- } else if (duration && duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
447
+ if (testResult.state === "failed")
448
+ // print short errors, full errors will be at the end in summary
449
+ this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(" > "))}`) + suffix), testResult.errors.forEach((error) => {
450
+ const message = this.formatShortError(error);
451
+ if (message) this.log(c.red(` ${padding}${message}`));
452
+ });
453
+ else if (duration && duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
496
454
  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}]`))}`);
497
455
  else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
498
456
  }
@@ -551,10 +509,8 @@ class BaseReporter {
551
509
  this.log(BADGE_PADDING + hints.join(c.dim(", ")));
552
510
  }
553
511
  onWatcherRerun(files, trigger) {
554
- this.watchFilters = files;
555
- this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed");
556
512
  // Update re-run count for each file
557
- files.forEach((filepath) => {
513
+ this.watchFilters = files, this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed"), files.forEach((filepath) => {
558
514
  let reruns = this._filesInWatchMode.get(filepath) ?? 0;
559
515
  this._filesInWatchMode.set(filepath, ++reruns);
560
516
  });
@@ -563,35 +519,26 @@ class BaseReporter {
563
519
  const rerun = this._filesInWatchMode.get(files[0]) ?? 1;
564
520
  banner += c.blue(`x${rerun} `);
565
521
  }
566
- this.ctx.logger.clearFullScreen();
567
- this.log(withLabel("blue", "RERUN", banner));
568
- if (this.ctx.configOverride.project) this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
522
+ 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(", ")));
569
523
  if (this.ctx.filenamePattern) this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
570
524
  if (this.ctx.configOverride.testNamePattern) this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
571
525
  this.log("");
572
526
  for (const testModule of this.failedUnwatchedFiles) this.printTestModule(testModule);
573
- this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
574
- this.start = performance$1.now();
527
+ this._timeStart = formatTimeString(/* @__PURE__ */ new Date()), this.start = performance$1.now();
575
528
  }
576
529
  onUserConsoleLog(log, taskState) {
577
530
  if (!this.shouldLog(log, taskState)) return;
578
- const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream;
579
- const write = (msg) => output.write(msg);
531
+ const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream, write = (msg) => output.write(msg);
580
532
  let headerText = "unknown test";
581
533
  const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
582
534
  if (task) headerText = this.getFullName(task, c.dim(" > "));
583
535
  else if (log.taskId && log.taskId !== "__vitest__unknown_test__") headerText = log.taskId;
584
- write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content);
585
- if (log.origin) {
536
+ if (write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content), log.origin) {
586
537
  // browser logs don't have an extra end of line at the end like Node.js does
587
538
  if (log.browser) write("\n");
588
- const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
589
- const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
590
- const highlight = task && stack.find((i) => i.file === task.file.filepath);
539
+ 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);
591
540
  for (const frame of stack) {
592
- const color = frame === highlight ? c.cyan : c.gray;
593
- const path = relative(project.config.root, frame.file);
594
- const positions = [frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ");
541
+ 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(" ");
595
542
  write(color(` ${c.dim(F_POINTER)} ${positions}\n`));
596
543
  }
597
544
  }
@@ -601,12 +548,9 @@ class BaseReporter {
601
548
  this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ""));
602
549
  }
603
550
  shouldLog(log, taskState) {
604
- if (this.ctx.config.silent === true) return false;
605
- if (this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
551
+ if (this.ctx.config.silent === true || this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
606
552
  if (this.ctx.config.onConsoleLog) {
607
- const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
608
- const entity = task && this.ctx.state.getReportedEntity(task);
609
- const shouldLog = this.ctx.config.onConsoleLog(log.content, log.type, entity);
553
+ 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);
610
554
  if (shouldLog === false) return shouldLog;
611
555
  }
612
556
  return true;
@@ -615,41 +559,27 @@ class BaseReporter {
615
559
  this.log(c.bold(c.magenta(reason === "config" ? "\nRestarting due to config changes..." : "\nRestarting Vitest...")));
616
560
  }
617
561
  reportSummary(files, errors) {
618
- this.printErrorsSummary(files, errors);
619
- if (this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
562
+ if (this.printErrorsSummary(files, errors), this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
620
563
  else this.reportTestSummary(files, errors);
621
564
  }
622
565
  reportTestSummary(files, errors) {
623
566
  this.log();
624
- const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files];
625
- const tests = getTests(affectedFiles);
626
- const snapshotOutput = renderSnapshotSummary(this.ctx.config.root, this.ctx.snapshot.summary);
567
+ const affectedFiles = [...this.failedUnwatchedFiles.map((m) => m.task), ...files], tests = getTests(affectedFiles), snapshotOutput = renderSnapshotSummary(this.ctx.config.root, this.ctx.snapshot.summary);
627
568
  for (const [index, snapshot] of snapshotOutput.entries()) {
628
569
  const title = index === 0 ? "Snapshots" : "";
629
570
  this.log(`${padSummaryTitle(title)} ${snapshot}`);
630
571
  }
631
572
  if (snapshotOutput.length > 1) this.log();
632
- this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles));
633
- this.log(padSummaryTitle("Tests"), getStateString$1(tests));
634
- if (this.ctx.projects.some((c) => c.config.typecheck.enabled)) {
573
+ 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)) {
635
574
  const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
636
575
  this.log(padSummaryTitle("Type Errors"), failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors"));
637
576
  }
638
577
  if (errors.length) this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
639
578
  this.log(padSummaryTitle("Start at"), this._timeStart);
640
- const collectTime = sum(files, (file) => file.collectDuration);
641
- const testsTime = sum(files, (file) => file.result?.duration);
642
- const setupTime = sum(files, (file) => file.setupDuration);
579
+ const collectTime = sum(files, (file) => file.collectDuration), testsTime = sum(files, (file) => file.result?.duration), setupTime = sum(files, (file) => file.setupDuration);
643
580
  if (this.watchFilters) this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
644
581
  else {
645
- const blobs = this.ctx.state.blobs;
646
- // Execution time is either sum of all runs of `--merge-reports` or the current run's time
647
- const executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start;
648
- const environmentTime = sum(files, (file) => file.environmentLoad);
649
- const prepareTime = sum(files, (file) => file.prepareDuration);
650
- const transformTime = this.ctx.state.transformTime;
651
- const typecheck = sum(this.ctx.projects, (project) => project.typechecker?.getResult().time);
652
- const timers = [
582
+ 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 = [
653
583
  `transform ${formatTime(transformTime)}`,
654
584
  `setup ${formatTime(setupTime)}`,
655
585
  `collect ${formatTime(collectTime)}`,
@@ -658,41 +588,25 @@ class BaseReporter {
658
588
  `prepare ${formatTime(prepareTime)}`,
659
589
  typecheck && `typecheck ${formatTime(typecheck)}`
660
590
  ].filter(Boolean).join(", ");
661
- this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
662
- if (blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
591
+ if (this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`)), blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
663
592
  }
664
593
  this.log();
665
594
  }
666
595
  printErrorsSummary(files, errors) {
667
- const suites = getSuites(files);
668
- const tests = getTests(files);
669
- const failedSuites = suites.filter((i) => i.result?.errors);
670
- const failedTests = tests.filter((i) => i.result?.state === "fail");
671
- const failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
596
+ 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);
672
597
  let current = 1;
673
598
  const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}\n`);
674
- if (failedSuites.length) {
675
- this.error(`\n${errorBanner(`Failed Suites ${failedSuites.length}`)}\n`);
676
- this.printTaskErrors(failedSuites, errorDivider);
677
- }
678
- if (failedTests.length) {
679
- this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`);
680
- this.printTaskErrors(failedTests, errorDivider);
681
- }
682
- if (errors.length) {
683
- this.ctx.logger.printUnhandledErrors(errors);
684
- this.error();
685
- }
599
+ if (failedSuites.length) this.error(`\n${errorBanner(`Failed Suites ${failedSuites.length}`)}\n`), this.printTaskErrors(failedSuites, errorDivider);
600
+ if (failedTests.length) this.error(`\n${errorBanner(`Failed Tests ${failedTests.length}`)}\n`), this.printTaskErrors(failedTests, errorDivider);
601
+ if (errors.length) this.ctx.logger.printUnhandledErrors(errors), this.error();
686
602
  }
687
603
  reportBenchmarkSummary(files) {
688
- const benches = getTests(files);
689
- const topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
604
+ const benches = getTests(files), topBenches = benches.filter((i) => i.result?.benchmark?.rank === 1);
690
605
  this.log(`\n${withLabel("cyan", "BENCH", "Summary\n")}`);
691
606
  for (const bench of topBenches) {
692
607
  const group = bench.suite || bench.file;
693
608
  if (!group) continue;
694
- const groupName = this.getFullName(group, c.dim(" > "));
695
- const project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
609
+ const groupName = this.getFullName(group, c.dim(" > ")), project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
696
610
  this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
697
611
  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);
698
612
  for (const sibling of siblings) {
@@ -710,10 +624,7 @@ class BaseReporter {
710
624
  let previous;
711
625
  if (error?.stack) previous = errorsQueue.find((i) => {
712
626
  if (i[0]?.stack !== error.stack) return false;
713
- const currentProjectName = task?.projectName || task.file?.projectName || "";
714
- const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
715
- const currentAnnotations = task.type === "test" && task.annotations;
716
- const itemAnnotations = i[1][0].type === "test" && i[1][0].annotations;
627
+ 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;
717
628
  return projectName === currentProjectName && deepEqual(currentAnnotations, itemAnnotations);
718
629
  });
719
630
  if (previous) previous[1].push(task);
@@ -721,24 +632,20 @@ class BaseReporter {
721
632
  });
722
633
  for (const [error, tasks] of errorsQueue) {
723
634
  for (const task of tasks) {
724
- const filepath = task?.filepath || "";
725
- const projectName = task?.projectName || task.file?.projectName || "";
726
- const project = this.ctx.projects.find((p) => p.name === projectName);
635
+ const filepath = task?.filepath || "", projectName = task?.projectName || task.file?.projectName || "", project = this.ctx.projects.find((p) => p.name === projectName);
727
636
  let name = this.getFullName(task, c.dim(" > "));
728
637
  if (filepath) name += c.dim(` [ ${this.relative(filepath)} ]`);
729
638
  this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
730
639
  }
731
640
  const screenshotPaths = tasks.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
732
- this.ctx.logger.printError(error, {
641
+ if (this.ctx.logger.printError(error, {
733
642
  project: this.ctx.getProjectByName(tasks[0].file.projectName || ""),
734
643
  verbose: this.verbose,
735
644
  screenshotPaths,
736
645
  task: tasks[0]
737
- });
738
- if (tasks[0].type === "test" && tasks[0].annotations.length) {
646
+ }), tasks[0].type === "test" && tasks[0].annotations.length) {
739
647
  const test = this.ctx.state.getReportedEntity(tasks[0]);
740
- this.printAnnotations(test, "error", 1);
741
- this.error();
648
+ this.printAnnotations(test, "error", 1), this.error();
742
649
  }
743
650
  errorDivider();
744
651
  }
@@ -747,8 +654,7 @@ class BaseReporter {
747
654
  function deepEqual(a, b) {
748
655
  if (a === b) return true;
749
656
  if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
750
- const keysA = Object.keys(a);
751
- const keysB = Object.keys(b);
657
+ const keysA = Object.keys(a), keysB = Object.keys(b);
752
658
  if (keysA.length !== keysB.length) return false;
753
659
  for (const key of keysA) if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
754
660
  return true;
@@ -759,12 +665,7 @@ function sum(items, cb) {
759
665
  }, 0);
760
666
  }
761
667
 
762
- const DEFAULT_RENDER_INTERVAL_MS = 1e3;
763
- const ESC = "\x1B[";
764
- const CLEAR_LINE = `${ESC}K`;
765
- const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`;
766
- const SYNC_START = `${ESC}?2026h`;
767
- const SYNC_END = `${ESC}?2026l`;
668
+ 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`;
768
669
  /**
769
670
  * Renders content of `getWindow` at the bottom of the terminal and
770
671
  * forwards all other intercepted `stdout` and `stderr` logs above it.
@@ -780,50 +681,37 @@ class WindowRenderer {
780
681
  finished = false;
781
682
  cleanups = [];
782
683
  constructor(options) {
684
+ // Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
783
685
  this.options = {
784
686
  interval: DEFAULT_RENDER_INTERVAL_MS,
785
687
  ...options
786
- };
787
- this.streams = {
688
+ }, this.streams = {
788
689
  output: options.logger.outputStream.write.bind(options.logger.outputStream),
789
690
  error: options.logger.errorStream.write.bind(options.logger.errorStream)
790
- };
791
- this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error"));
792
- // Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
793
- this.options.logger.onTerminalCleanup(() => {
794
- this.flushBuffer();
795
- this.stop();
691
+ }, this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error")), this.options.logger.onTerminalCleanup(() => {
692
+ this.flushBuffer(), this.stop();
796
693
  });
797
694
  }
798
695
  start() {
799
- this.started = true;
800
- this.finished = false;
801
- this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
696
+ this.started = true, this.finished = false, this.renderInterval = setInterval(() => this.schedule(), this.options.interval).unref();
802
697
  }
803
698
  stop() {
804
- this.cleanups.splice(0).map((fn) => fn());
805
- clearInterval(this.renderInterval);
699
+ this.cleanups.splice(0).map((fn) => fn()), clearInterval(this.renderInterval);
806
700
  }
807
701
  /**
808
702
  * Write all buffered output and stop buffering.
809
703
  * All intercepted writes are forwarded to actual write after this.
810
704
  */
811
705
  finish() {
812
- this.finished = true;
813
- this.flushBuffer();
814
- clearInterval(this.renderInterval);
706
+ this.finished = true, this.flushBuffer(), clearInterval(this.renderInterval);
815
707
  }
816
708
  /**
817
709
  * Queue new render update
818
710
  */
819
711
  schedule() {
820
- if (!this.renderScheduled) {
821
- this.renderScheduled = true;
822
- this.flushBuffer();
823
- setTimeout(() => {
824
- this.renderScheduled = false;
825
- }, 100).unref();
826
- }
712
+ if (!this.renderScheduled) this.renderScheduled = true, this.flushBuffer(), setTimeout(() => {
713
+ this.renderScheduled = false;
714
+ }, 100).unref();
827
715
  }
828
716
  flushBuffer() {
829
717
  if (this.buffer.length === 0) return this.render();
@@ -835,8 +723,7 @@ class WindowRenderer {
835
723
  continue;
836
724
  }
837
725
  if (current.type !== next.type) {
838
- this.render(current.message, current.type);
839
- current = next;
726
+ this.render(current.message, current.type), current = next;
840
727
  continue;
841
728
  }
842
729
  current.message += next.message;
@@ -844,40 +731,31 @@ class WindowRenderer {
844
731
  if (current) this.render(current?.message, current?.type);
845
732
  }
846
733
  render(message, type = "output") {
847
- if (this.finished) {
848
- this.clearWindow();
849
- return this.write(message || "", type);
850
- }
851
- const windowContent = this.options.getWindow();
852
- const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
734
+ if (this.finished) return this.clearWindow(), this.write(message || "", type);
735
+ const windowContent = this.options.getWindow(), rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
853
736
  let padding = this.windowHeight - rowCount;
854
737
  if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
855
- this.write(SYNC_START);
856
- this.clearWindow();
857
- if (message) this.write(message, type);
738
+ if (this.write(SYNC_START), this.clearWindow(), message) this.write(message, type);
858
739
  if (padding > 0) this.write("\n".repeat(padding));
859
- this.write(windowContent.join("\n"));
860
- this.write(SYNC_END);
861
- this.windowHeight = rowCount + Math.max(0, padding);
740
+ this.write(windowContent.join("\n")), this.write(SYNC_END), this.windowHeight = rowCount + Math.max(0, padding);
862
741
  }
863
742
  clearWindow() {
864
- if (this.windowHeight === 0) return;
865
- this.write(CLEAR_LINE);
866
- for (let i = 1; i < this.windowHeight; i++) this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
867
- this.windowHeight = 0;
743
+ if (this.windowHeight !== 0) {
744
+ this.write(CLEAR_LINE);
745
+ for (let i = 1; i < this.windowHeight; i++) this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
746
+ this.windowHeight = 0;
747
+ }
868
748
  }
869
749
  interceptStream(stream, type) {
870
750
  const original = stream.write;
871
- // @ts-expect-error -- not sure how 2 overloads should be typed
872
- stream.write = (chunk, _, callback) => {
751
+ return stream.write = (chunk, _, callback) => {
873
752
  if (chunk) if (this.finished || !this.started) this.write(chunk.toString(), type);
874
753
  else this.buffer.push({
875
754
  type,
876
755
  message: chunk.toString()
877
756
  });
878
757
  callback?.();
879
- };
880
- return function restore() {
758
+ }, function restore() {
881
759
  stream.write = original;
882
760
  };
883
761
  }
@@ -895,8 +773,7 @@ function getRenderedRowCount(rows, columns) {
895
773
  return count;
896
774
  }
897
775
 
898
- const DURATION_UPDATE_INTERVAL_MS = 100;
899
- const FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
776
+ const DURATION_UPDATE_INTERVAL_MS = 100, FINISHED_TEST_CLEANUP_TIME_MS = 1e3;
900
777
  /**
901
778
  * Reporter extension that renders summary and forwards all other logs above itself.
902
779
  * Intended to be used by other reporters, not as a standalone reporter.
@@ -917,34 +794,21 @@ class SummaryReporter {
917
794
  duration = 0;
918
795
  durationInterval = void 0;
919
796
  onInit(ctx, options = {}) {
920
- this.ctx = ctx;
921
- this.options = {
797
+ this.ctx = ctx, this.options = {
922
798
  verbose: false,
923
799
  ...options
924
- };
925
- this.renderer = new WindowRenderer({
800
+ }, this.renderer = new WindowRenderer({
926
801
  logger: ctx.logger,
927
802
  getWindow: () => this.createSummary()
928
- });
929
- this.ctx.onClose(() => {
930
- clearInterval(this.durationInterval);
931
- this.renderer.stop();
803
+ }), this.ctx.onClose(() => {
804
+ clearInterval(this.durationInterval), this.renderer.stop();
932
805
  });
933
806
  }
934
807
  onTestRunStart(specifications) {
935
- this.runningModules.clear();
936
- this.finishedModules.clear();
937
- this.modules = emptyCounters();
938
- this.tests = emptyCounters();
939
- this.startTimers();
940
- this.renderer.start();
941
- this.modules.total = specifications.length;
808
+ this.runningModules.clear(), this.finishedModules.clear(), this.modules = emptyCounters(), this.tests = emptyCounters(), this.startTimers(), this.renderer.start(), this.modules.total = specifications.length;
942
809
  }
943
810
  onTestRunEnd() {
944
- this.runningModules.clear();
945
- this.finishedModules.clear();
946
- this.renderer.finish();
947
- clearInterval(this.durationInterval);
811
+ this.runningModules.clear(), this.finishedModules.clear(), this.renderer.finish(), clearInterval(this.durationInterval);
948
812
  }
949
813
  onTestModuleQueued(module) {
950
814
  // When new test module starts, take the place of previously finished test module, if any
@@ -952,20 +816,13 @@ class SummaryReporter {
952
816
  const finished = this.finishedModules.keys().next().value;
953
817
  this.removeTestModule(finished);
954
818
  }
955
- this.runningModules.set(module.id, initializeStats(module));
956
- this.renderer.schedule();
819
+ this.runningModules.set(module.id, initializeStats(module)), this.renderer.schedule();
957
820
  }
958
821
  onTestModuleCollected(module) {
959
822
  let stats = this.runningModules.get(module.id);
960
- if (!stats) {
961
- stats = initializeStats(module);
962
- this.runningModules.set(module.id, stats);
963
- }
823
+ if (!stats) stats = initializeStats(module), this.runningModules.set(module.id, stats);
964
824
  const total = Array.from(module.children.allTests()).length;
965
- this.tests.total += total;
966
- stats.total = total;
967
- this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size);
968
- this.renderer.schedule();
825
+ this.tests.total += total, stats.total = total, this.maxParallelTests = Math.max(this.maxParallelTests, this.runningModules.size), this.renderer.schedule();
969
826
  }
970
827
  onHookStart(options) {
971
828
  const stats = this.getHookStats(options);
@@ -976,8 +833,7 @@ class SummaryReporter {
976
833
  startTime: performance.now(),
977
834
  onFinish: () => {}
978
835
  };
979
- stats.hook?.onFinish?.();
980
- stats.hook = hook;
836
+ stats.hook?.onFinish?.(), stats.hook = hook;
981
837
  const timeout = setTimeout(() => {
982
838
  hook.visible = true;
983
839
  }, this.ctx.config.slowTestThreshold).unref();
@@ -985,9 +841,7 @@ class SummaryReporter {
985
841
  }
986
842
  onHookEnd(options) {
987
843
  const stats = this.getHookStats(options);
988
- if (stats?.hook?.name !== options.name) return;
989
- stats.hook.onFinish();
990
- stats.hook.visible = false;
844
+ stats?.hook?.name === options.name && (stats.hook.onFinish(), stats.hook.visible = false);
991
845
  }
992
846
  onTestCaseReady(test) {
993
847
  // Track slow running tests only on verbose mode
@@ -999,22 +853,17 @@ class SummaryReporter {
999
853
  visible: false,
1000
854
  startTime: performance.now(),
1001
855
  onFinish: () => {}
1002
- };
1003
- const timeout = setTimeout(() => {
856
+ }, timeout = setTimeout(() => {
1004
857
  slowTest.visible = true;
1005
858
  }, this.ctx.config.slowTestThreshold).unref();
1006
859
  slowTest.onFinish = () => {
1007
- slowTest.hook?.onFinish();
1008
- clearTimeout(timeout);
1009
- };
1010
- stats.tests.set(test.id, slowTest);
860
+ slowTest.hook?.onFinish(), clearTimeout(timeout);
861
+ }, stats.tests.set(test.id, slowTest);
1011
862
  }
1012
863
  onTestCaseResult(test) {
1013
864
  const stats = this.runningModules.get(test.module.id);
1014
865
  if (!stats) return;
1015
- stats.tests.get(test.id)?.onFinish();
1016
- stats.tests.delete(test.id);
1017
- stats.completed++;
866
+ stats.tests.get(test.id)?.onFinish(), stats.tests.delete(test.id), stats.completed++;
1018
867
  const result = test.result();
1019
868
  if (result?.state === "passed") this.tests.passed++;
1020
869
  else if (result?.state === "failed") this.tests.failed++;
@@ -1023,8 +872,7 @@ class SummaryReporter {
1023
872
  }
1024
873
  onTestModuleEnd(module) {
1025
874
  const state = module.state();
1026
- this.modules.completed++;
1027
- if (state === "passed") this.modules.passed++;
875
+ if (this.modules.completed++, state === "passed") this.modules.passed++;
1028
876
  else if (state === "failed") this.modules.failed++;
1029
877
  else if (module.task.mode === "todo" && state === "skipped") this.modules.todo++;
1030
878
  else if (state === "skipped") this.modules.skipped++;
@@ -1044,10 +892,8 @@ class SummaryReporter {
1044
892
  getHookStats({ entity }) {
1045
893
  // Track slow running hooks only on verbose mode
1046
894
  if (!this.options.verbose) return;
1047
- const module = entity.type === "module" ? entity : entity.module;
1048
- const stats = this.runningModules.get(module.id);
1049
- if (!stats) return;
1050
- return entity.type === "test" ? stats.tests.get(entity.id) : stats;
895
+ const module = entity.type === "module" ? entity : entity.module, stats = this.runningModules.get(module.id);
896
+ if (stats) return entity.type === "test" ? stats.tests.get(entity.id) : stats;
1051
897
  }
1052
898
  createSummary() {
1053
899
  const summary = [""];
@@ -1059,36 +905,23 @@ class SummaryReporter {
1059
905
  }) + typecheck + testFile.filename + c.dim(!testFile.completed && !testFile.total ? " [queued]" : ` ${testFile.completed}/${testFile.total}`));
1060
906
  const slowTasks = [testFile.hook, ...Array.from(testFile.tests.values())].filter((t) => t != null && t.visible);
1061
907
  for (const [index, task] of slowTasks.entries()) {
1062
- const elapsed = this.currentTime - task.startTime;
1063
- const icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
1064
- summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
1065
- if (task.hook?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
908
+ const elapsed = this.currentTime - task.startTime, icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
909
+ 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);
1066
910
  }
1067
911
  }
1068
912
  if (this.runningModules.size > 0) summary.push("");
1069
- summary.push(padSummaryTitle("Test Files") + getStateString(this.modules));
1070
- summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
1071
- summary.push(padSummaryTitle("Start at") + this.startTime);
1072
- summary.push(padSummaryTitle("Duration") + formatTime(this.duration));
1073
- summary.push("");
1074
- return summary;
913
+ 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;
1075
914
  }
1076
915
  startTimers() {
1077
916
  const start = performance.now();
1078
- this.startTime = formatTimeString(/* @__PURE__ */ new Date());
1079
- this.durationInterval = setInterval(() => {
1080
- this.currentTime = performance.now();
1081
- this.duration = this.currentTime - start;
917
+ this.startTime = formatTimeString(/* @__PURE__ */ new Date()), this.durationInterval = setInterval(() => {
918
+ this.currentTime = performance.now(), this.duration = this.currentTime - start;
1082
919
  }, DURATION_UPDATE_INTERVAL_MS).unref();
1083
920
  }
1084
921
  removeTestModule(id) {
1085
922
  if (!id) return;
1086
923
  const testFile = this.runningModules.get(id);
1087
- testFile?.hook?.onFinish();
1088
- testFile?.tests?.forEach((test) => test.onFinish());
1089
- this.runningModules.delete(id);
1090
- clearTimeout(this.finishedModules.get(id));
1091
- this.finishedModules.delete(id);
924
+ testFile?.hook?.onFinish(), testFile?.tests?.forEach((test) => test.onFinish()), this.runningModules.delete(id), clearTimeout(this.finishedModules.get(id)), this.finishedModules.delete(id);
1092
925
  }
1093
926
  }
1094
927
  function emptyCounters() {
@@ -1110,9 +943,7 @@ function getStateString(entry) {
1110
943
  ].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
1111
944
  }
1112
945
  function sortRunningModules(a, b) {
1113
- if ((a.projectName || "") > (b.projectName || "")) return 1;
1114
- if ((a.projectName || "") < (b.projectName || "")) return -1;
1115
- return a.filename.localeCompare(b.filename);
946
+ return (a.projectName || "") > (b.projectName || "") ? 1 : (a.projectName || "") < (b.projectName || "") ? -1 : a.filename.localeCompare(b.filename);
1116
947
  }
1117
948
  function initializeStats(module) {
1118
949
  return {
@@ -1130,12 +961,10 @@ class DefaultReporter extends BaseReporter {
1130
961
  options;
1131
962
  summary;
1132
963
  constructor(options = {}) {
1133
- super(options);
1134
- this.options = {
964
+ if (super(options), this.options = {
1135
965
  summary: true,
1136
966
  ...options
1137
- };
1138
- if (!this.isTTY) this.options.summary = false;
967
+ }, !this.isTTY) this.options.summary = false;
1139
968
  if (this.options.summary) this.summary = new SummaryReporter();
1140
969
  }
1141
970
  onTestRunStart(specifications) {
@@ -1145,6 +974,9 @@ class DefaultReporter extends BaseReporter {
1145
974
  }
1146
975
  this.summary?.onTestRunStart(specifications);
1147
976
  }
977
+ onTestRunEnd(testModules, unhandledErrors, reason) {
978
+ super.onTestRunEnd(testModules, unhandledErrors, reason), this.summary?.onTestRunEnd();
979
+ }
1148
980
  onTestModuleQueued(file) {
1149
981
  this.summary?.onTestModuleQueued(file);
1150
982
  }
@@ -1152,15 +984,13 @@ class DefaultReporter extends BaseReporter {
1152
984
  this.summary?.onTestModuleCollected(module);
1153
985
  }
1154
986
  onTestModuleEnd(module) {
1155
- super.onTestModuleEnd(module);
1156
- this.summary?.onTestModuleEnd(module);
987
+ super.onTestModuleEnd(module), this.summary?.onTestModuleEnd(module);
1157
988
  }
1158
989
  onTestCaseReady(test) {
1159
990
  this.summary?.onTestCaseReady(test);
1160
991
  }
1161
992
  onTestCaseResult(test) {
1162
- super.onTestCaseResult(test);
1163
- this.summary?.onTestCaseResult(test);
993
+ super.onTestCaseResult(test), this.summary?.onTestCaseResult(test);
1164
994
  }
1165
995
  onHookStart(hook) {
1166
996
  this.summary?.onHookStart(hook);
@@ -1169,11 +999,7 @@ class DefaultReporter extends BaseReporter {
1169
999
  this.summary?.onHookEnd(hook);
1170
1000
  }
1171
1001
  onInit(ctx) {
1172
- super.onInit(ctx);
1173
- this.summary?.onInit(ctx, { verbose: this.verbose });
1174
- }
1175
- onTestRunEnd() {
1176
- this.summary?.onTestRunEnd();
1002
+ super.onInit(ctx), this.summary?.onInit(ctx, { verbose: this.verbose });
1177
1003
  }
1178
1004
  }
1179
1005
 
@@ -1182,30 +1008,22 @@ class DotReporter extends BaseReporter {
1182
1008
  tests = /* @__PURE__ */ new Map();
1183
1009
  finishedTests = /* @__PURE__ */ new Set();
1184
1010
  onInit(ctx) {
1185
- super.onInit(ctx);
1186
- if (this.isTTY) {
1187
- this.renderer = new WindowRenderer({
1188
- logger: ctx.logger,
1189
- getWindow: () => this.createSummary()
1190
- });
1191
- this.ctx.onClose(() => this.renderer?.stop());
1192
- }
1011
+ if (super.onInit(ctx), this.isTTY) this.renderer = new WindowRenderer({
1012
+ logger: ctx.logger,
1013
+ getWindow: () => this.createSummary()
1014
+ }), this.ctx.onClose(() => this.renderer?.stop());
1193
1015
  }
1194
1016
  // Ignore default logging of base reporter
1195
1017
  printTestModule() {}
1196
1018
  onWatcherRerun(files, trigger) {
1197
- this.tests.clear();
1198
- this.renderer?.start();
1199
- super.onWatcherRerun(files, trigger);
1019
+ this.tests.clear(), this.renderer?.start(), super.onWatcherRerun(files, trigger);
1200
1020
  }
1201
- onFinished(files, errors) {
1021
+ onTestRunEnd(testModules, unhandledErrors, reason) {
1202
1022
  if (this.isTTY) {
1203
1023
  const finalLog = formatTests(Array.from(this.tests.values()));
1204
1024
  this.ctx.logger.log(finalLog);
1205
1025
  } else this.ctx.logger.log();
1206
- this.tests.clear();
1207
- this.renderer?.finish();
1208
- super.onFinished(files, errors);
1026
+ this.tests.clear(), this.renderer?.finish(), super.onTestRunEnd(testModules, unhandledErrors, reason);
1209
1027
  }
1210
1028
  onTestModuleCollected(module) {
1211
1029
  for (const test of module.children.allTests())
@@ -1213,22 +1031,16 @@ class DotReporter extends BaseReporter {
1213
1031
  this.onTestCaseReady(test);
1214
1032
  }
1215
1033
  onTestCaseReady(test) {
1216
- if (this.finishedTests.has(test.id)) return;
1217
- this.tests.set(test.id, test.result().state || "run");
1218
- this.renderer?.schedule();
1034
+ this.finishedTests.has(test.id) || (this.tests.set(test.id, test.result().state || "run"), this.renderer?.schedule());
1219
1035
  }
1220
1036
  onTestCaseResult(test) {
1221
1037
  const result = test.result().state;
1222
1038
  // On non-TTY the finished tests are printed immediately
1223
1039
  if (!this.isTTY && result !== "pending") this.ctx.logger.outputStream.write(formatTests([result]));
1224
- super.onTestCaseResult(test);
1225
- this.finishedTests.add(test.id);
1226
- this.tests.set(test.id, result || "skipped");
1227
- this.renderer?.schedule();
1040
+ super.onTestCaseResult(test), this.finishedTests.add(test.id), this.tests.set(test.id, result || "skipped"), this.renderer?.schedule();
1228
1041
  }
1229
1042
  onTestModuleEnd(testModule) {
1230
- super.onTestModuleEnd(testModule);
1231
- if (!this.isTTY) return;
1043
+ if (super.onTestModuleEnd(testModule), !this.isTTY) return;
1232
1044
  const columns = this.ctx.logger.getColumns();
1233
1045
  if (this.tests.size < columns) return;
1234
1046
  const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
@@ -1238,11 +1050,9 @@ class DotReporter extends BaseReporter {
1238
1050
  let count = 0;
1239
1051
  for (const [id, state] of finishedTests) {
1240
1052
  if (count++ >= columns) break;
1241
- this.tests.delete(id);
1242
- states.push(state);
1053
+ this.tests.delete(id), states.push(state);
1243
1054
  }
1244
- this.ctx.logger.log(formatTests(states));
1245
- this.renderer?.schedule();
1055
+ this.ctx.logger.log(formatTests(states)), this.renderer?.schedule();
1246
1056
  }
1247
1057
  createSummary() {
1248
1058
  return [formatTests(Array.from(this.tests.values())), ""];
@@ -1252,16 +1062,13 @@ class DotReporter extends BaseReporter {
1252
1062
  const pass = {
1253
1063
  char: "·",
1254
1064
  color: c.green
1255
- };
1256
- const fail = {
1065
+ }, fail = {
1257
1066
  char: "x",
1258
1067
  color: c.red
1259
- };
1260
- const pending = {
1068
+ }, pending = {
1261
1069
  char: "*",
1262
1070
  color: c.yellow
1263
- };
1264
- const skip = {
1071
+ }, skip = {
1265
1072
  char: "-",
1266
1073
  color: (char) => c.dim(c.gray(char))
1267
1074
  };
@@ -1278,37 +1085,27 @@ function getIcon(state) {
1278
1085
  * Sibling icons with same color are merged into a single c.color() call.
1279
1086
  */
1280
1087
  function formatTests(states) {
1281
- let currentIcon = pending;
1282
- let count = 0;
1283
- let output = "";
1088
+ let currentIcon = pending, count = 0, output = "";
1284
1089
  for (const state of states) {
1285
1090
  const icon = getIcon(state);
1286
1091
  if (currentIcon === icon) {
1287
1092
  count++;
1288
1093
  continue;
1289
1094
  }
1290
- output += currentIcon.color(currentIcon.char.repeat(count));
1291
- // Start tracking new group
1292
- count = 1;
1293
- currentIcon = icon;
1095
+ output += currentIcon.color(currentIcon.char.repeat(count)), count = 1, currentIcon = icon;
1294
1096
  }
1295
- output += currentIcon.color(currentIcon.char.repeat(count));
1296
- return output;
1097
+ return output += currentIcon.color(currentIcon.char.repeat(count)), output;
1297
1098
  }
1298
1099
 
1299
1100
  // use Logger with custom Console to capture entire error printing
1300
1101
  function capturePrintError(error, ctx, options) {
1301
1102
  let output = "";
1302
1103
  const writable = new Writable({ write(chunk, _encoding, callback) {
1303
- output += String(chunk);
1304
- callback();
1305
- } });
1306
- const console = new Console(writable);
1307
- const logger = {
1104
+ output += String(chunk), callback();
1105
+ } }), console = new Console(writable), logger = {
1308
1106
  error: console.error.bind(console),
1309
1107
  highlight: ctx.logger.highlight.bind(ctx.logger)
1310
- };
1311
- const result = printError(error, ctx, logger, {
1108
+ }, result = printError(error, ctx, logger, {
1312
1109
  showCodeFrame: false,
1313
1110
  ...options
1314
1111
  });
@@ -1326,11 +1123,13 @@ function printError(error, ctx, logger, options) {
1326
1123
  screenshotPaths: options.screenshotPaths,
1327
1124
  printProperties: options.verbose,
1328
1125
  parseErrorStacktrace(error) {
1329
- // browser stack trace needs to be processed differently,
1330
- // so there is a separate method for that
1331
- if (options.task?.file.pool === "browser" && project.browser) return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : void 0 });
1332
1126
  // node.js stack trace already has correct source map locations
1333
- return parseErrorStacktrace(error, {
1127
+ return error.stacks ? options.fullStack ? error.stacks : error.stacks.filter((stack) => {
1128
+ return !defaultStackIgnorePatterns.some((p) => stack.file.match(p));
1129
+ }) : options.task?.file.pool === "browser" && project.browser ? project.browser.parseErrorStacktrace(error, {
1130
+ frameFilter: project.config.onStackTrace,
1131
+ ignoreStackEntries: options.fullStack ? [] : void 0
1132
+ }) : parseErrorStacktrace(error, {
1334
1133
  frameFilter: project.config.onStackTrace,
1335
1134
  ignoreStackEntries: options.fullStack ? [] : void 0
1336
1135
  });
@@ -1338,8 +1137,7 @@ function printError(error, ctx, logger, options) {
1338
1137
  });
1339
1138
  }
1340
1139
  function printErrorInner(error, project, options) {
1341
- const { showCodeFrame = true, type, printProperties = true } = options;
1342
- const logger = options.logger;
1140
+ const { showCodeFrame = true, type, printProperties = true } = options, logger = options.logger;
1343
1141
  let e = error;
1344
1142
  if (isPrimitive(e)) e = {
1345
1143
  message: String(error).split(/\n/g)[0],
@@ -1357,8 +1155,7 @@ function printErrorInner(error, project, options) {
1357
1155
  printErrorMessage(e, logger);
1358
1156
  return;
1359
1157
  }
1360
- const stacks = options.parseErrorStacktrace(e);
1361
- const nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
1158
+ const stacks = options.parseErrorStacktrace(e), nearest = error instanceof TypeCheckError ? error.stacks[0] : stacks.find((stack) => {
1362
1159
  try {
1363
1160
  const module = project._vite && project.getModuleById(stack.file);
1364
1161
  return (module?.transformResult || module?.ssrTransformResult) && existsSync(stack.file);
@@ -1367,12 +1164,9 @@ function printErrorInner(error, project, options) {
1367
1164
  }
1368
1165
  });
1369
1166
  if (type) printErrorType(type, project.vitest);
1370
- printErrorMessage(e, logger);
1371
- if (options.screenshotPaths?.length) {
1167
+ if (printErrorMessage(e, logger), options.screenshotPaths?.length) {
1372
1168
  const length = options.screenshotPaths.length;
1373
- logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
1374
- logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
1375
- if (!e.diff) logger.error();
1169
+ 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();
1376
1170
  }
1377
1171
  if (e.codeFrame) logger.error(`${e.codeFrame}\n`);
1378
1172
  if ("__vitest_rollup_error__" in e) {
@@ -1397,25 +1191,19 @@ function printErrorInner(error, project, options) {
1397
1191
  }
1398
1192
  });
1399
1193
  }
1400
- const testPath = e.VITEST_TEST_PATH;
1401
- const testName = e.VITEST_TEST_NAME;
1402
- const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
1194
+ const testPath = e.VITEST_TEST_PATH, testName = e.VITEST_TEST_NAME, afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
1403
1195
  // testName has testPath inside
1404
1196
  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.`));
1405
1197
  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:
1406
1198
  - The error was thrown, while Vitest was running this test.
1407
1199
  - If the error occurred after the test had been completed, this was the last documented test before it was thrown.`));
1408
1200
  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"));
1409
- if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
1410
- e.cause.name = `Caused by: ${e.cause.name}`;
1411
- printErrorInner(e.cause, project, {
1412
- showCodeFrame: false,
1413
- logger: options.logger,
1414
- parseErrorStacktrace: options.parseErrorStacktrace
1415
- });
1416
- }
1417
- handleImportOutsideModuleError(e.stack || "", logger);
1418
- return { nearest };
1201
+ if (typeof e.cause === "object" && e.cause && "name" in e.cause) e.cause.name = `Caused by: ${e.cause.name}`, printErrorInner(e.cause, project, {
1202
+ showCodeFrame: false,
1203
+ logger: options.logger,
1204
+ parseErrorStacktrace: options.parseErrorStacktrace
1205
+ });
1206
+ return handleImportOutsideModuleError(e.stack || "", logger), { nearest };
1419
1207
  }
1420
1208
  function printErrorType(type, ctx) {
1421
1209
  ctx.logger.error(`\n${errorBanner(type)}`);
@@ -1498,10 +1286,8 @@ function printErrorMessage(error, logger) {
1498
1286
  }
1499
1287
  function printStack(logger, project, stack, highlight, errorProperties, onStack) {
1500
1288
  for (const frame of stack) {
1501
- const color = frame === highlight ? c.cyan : c.gray;
1502
- const path = relative(project.config.root, frame.file);
1503
- logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
1504
- onStack?.(frame);
1289
+ const color = frame === highlight ? c.cyan : c.gray, path = relative(project.config.root, frame.file);
1290
+ logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`)), onStack?.(frame);
1505
1291
  }
1506
1292
  if (stack.length) logger.error();
1507
1293
  if (hasProperties(errorProperties)) {
@@ -1516,28 +1302,19 @@ function hasProperties(obj) {
1516
1302
  return false;
1517
1303
  }
1518
1304
  function generateCodeFrame(source, indent = 0, loc, range = 2) {
1519
- const start = typeof loc === "object" ? positionToOffset(source, loc.line, loc.column) : loc;
1520
- const end = start;
1521
- const lines = source.split(lineSplitRE);
1522
- const nl = /\r\n/.test(source) ? 2 : 1;
1523
- let count = 0;
1524
- let res = [];
1305
+ 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;
1306
+ let count = 0, res = [];
1525
1307
  const columns = process.stdout?.columns || 80;
1526
- for (let i = 0; i < lines.length; i++) {
1527
- count += lines[i].length + nl;
1528
- if (count >= start) {
1529
- for (let j = i - range; j <= i + range || end > count; j++) {
1530
- if (j < 0 || j >= lines.length) continue;
1531
- const lineLength = lines[j].length;
1532
- const strippedContent = stripVTControlCharacters(lines[j]);
1533
- if (strippedContent.startsWith("//# sourceMappingURL")) continue;
1308
+ for (let i = 0; i < lines.length; i++) if (count += lines[i].length + nl, count >= start) {
1309
+ for (let j = i - range; j <= i + range || end > count; j++) {
1310
+ if (j < 0 || j >= lines.length) continue;
1311
+ const lineLength = lines[j].length, strippedContent = stripVTControlCharacters(lines[j]);
1312
+ if (!strippedContent.startsWith("//# sourceMappingURL")) {
1534
1313
  // too long, maybe it's a minified file, skip for codeframe
1535
1314
  if (strippedContent.length > 200) return "";
1536
- res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent));
1537
- if (j === i) {
1315
+ if (res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent)), j === i) {
1538
1316
  // push underline
1539
- const pad = start - (count - lineLength) + (nl - 1);
1540
- const length = Math.max(1, end > count ? lineLength - pad : end - start);
1317
+ const pad = start - (count - lineLength) + (nl - 1), length = Math.max(1, end > count ? lineLength - pad : end - start);
1541
1318
  res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
1542
1319
  } else if (j > i) {
1543
1320
  if (end > count) {
@@ -1547,8 +1324,8 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
1547
1324
  count += lineLength + 1;
1548
1325
  }
1549
1326
  }
1550
- break;
1551
1327
  }
1328
+ break;
1552
1329
  }
1553
1330
  if (indent) res = res.map((line) => " ".repeat(indent) + line);
1554
1331
  return res.join("\n");
@@ -1568,8 +1345,7 @@ class GithubActionsReporter {
1568
1345
  }
1569
1346
  onTestCaseAnnotate(testCase, annotation) {
1570
1347
  if (!annotation.location) return;
1571
- const type = getTitle(annotation.type);
1572
- const formatted = formatMessage({
1348
+ const type = getTitle(annotation.type), formatted = formatMessage({
1573
1349
  command: getType(annotation.type),
1574
1350
  properties: {
1575
1351
  file: annotation.location.file,
@@ -1581,17 +1357,15 @@ class GithubActionsReporter {
1581
1357
  });
1582
1358
  this.ctx.logger.log(`\n${formatted}`);
1583
1359
  }
1584
- onFinished(files = [], errors = []) {
1585
- // collect all errors and associate them with projects
1586
- const projectErrors = new Array();
1360
+ onTestRunEnd(testModules, unhandledErrors) {
1361
+ const files = testModules.map((testModule) => testModule.task), errors = [...unhandledErrors], projectErrors = new Array();
1587
1362
  for (const error of errors) projectErrors.push({
1588
1363
  project: this.ctx.getRootProject(),
1589
1364
  title: "Unhandled error",
1590
1365
  error
1591
1366
  });
1592
1367
  for (const file of files) {
1593
- const tasks = getTasks(file);
1594
- const project = this.ctx.getProjectByName(file.projectName || "");
1368
+ const tasks = getTasks(file), project = this.ctx.getProjectByName(file.projectName || "");
1595
1369
  for (const task of tasks) {
1596
1370
  if (task.result?.state !== "fail") continue;
1597
1371
  const title = getFullName(task, " > ");
@@ -1609,8 +1383,7 @@ class GithubActionsReporter {
1609
1383
  const result = capturePrintError(error, this.ctx, {
1610
1384
  project,
1611
1385
  task: file
1612
- });
1613
- const stack = result?.nearest;
1386
+ }), stack = result?.nearest;
1614
1387
  if (!stack) continue;
1615
1388
  const formatted = formatMessage({
1616
1389
  command: "error",
@@ -1632,12 +1405,10 @@ const BUILT_IN_TYPES = [
1632
1405
  "warning"
1633
1406
  ];
1634
1407
  function getTitle(type) {
1635
- if (BUILT_IN_TYPES.includes(type)) return void 0;
1636
- return type;
1408
+ return BUILT_IN_TYPES.includes(type) ? void 0 : type;
1637
1409
  }
1638
1410
  function getType(type) {
1639
- if (BUILT_IN_TYPES.includes(type)) return type;
1640
- return "notice";
1411
+ return BUILT_IN_TYPES.includes(type) ? type : "notice";
1641
1412
  }
1642
1413
  function defaultOnWritePath(path) {
1643
1414
  return path;
@@ -1647,12 +1418,9 @@ function defaultOnWritePath(path) {
1647
1418
  // https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
1648
1419
  function formatMessage({ command, properties, message }) {
1649
1420
  let result = `::${command}`;
1650
- Object.entries(properties).forEach(([k, v], i) => {
1651
- result += i === 0 ? " " : ",";
1652
- result += `${k}=${escapeProperty(v)}`;
1653
- });
1654
- result += `::${escapeData(message)}`;
1655
- return result;
1421
+ return Object.entries(properties).forEach(([k, v], i) => {
1422
+ result += i === 0 ? " " : ",", result += `${k}=${escapeProperty(v)}`;
1423
+ }), result += `::${escapeData(message)}`, result;
1656
1424
  }
1657
1425
  function escapeData(s) {
1658
1426
  return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
@@ -1685,41 +1453,27 @@ class JsonReporter {
1685
1453
  start = 0;
1686
1454
  ctx;
1687
1455
  options;
1456
+ coverageMap;
1688
1457
  constructor(options) {
1689
1458
  this.options = options;
1690
1459
  }
1691
1460
  onInit(ctx) {
1692
- this.ctx = ctx;
1693
- this.start = Date.now();
1694
- }
1695
- async logTasks(files, coverageMap) {
1696
- const suites = getSuites(files);
1697
- const numTotalTestSuites = suites.length;
1698
- const tests = getTests(files);
1699
- const numTotalTests = tests.length;
1700
- const numFailedTestSuites = suites.filter((s) => s.result?.state === "fail").length;
1701
- const numPendingTestSuites = suites.filter((s) => s.result?.state === "run" || s.result?.state === "queued" || s.mode === "todo").length;
1702
- const numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites;
1703
- const numFailedTests = tests.filter((t) => t.result?.state === "fail").length;
1704
- const numPassedTests = tests.filter((t) => t.result?.state === "pass").length;
1705
- const numPendingTests = tests.filter((t) => t.result?.state === "run" || t.result?.state === "queued" || t.mode === "skip" || t.result?.state === "skip").length;
1706
- const numTodoTests = tests.filter((t) => t.mode === "todo").length;
1707
- const testResults = [];
1708
- const success = !!(files.length > 0 || this.ctx.config.passWithNoTests) && numFailedTestSuites === 0 && numFailedTests === 0;
1461
+ this.ctx = ctx, this.start = Date.now(), this.coverageMap = void 0;
1462
+ }
1463
+ onCoverage(coverageMap) {
1464
+ this.coverageMap = coverageMap;
1465
+ }
1466
+ async onTestRunEnd(testModules) {
1467
+ 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;
1709
1468
  for (const file of files) {
1710
1469
  const tests = getTests([file]);
1711
1470
  let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
1712
1471
  if (startTime === Number.POSITIVE_INFINITY) startTime = this.start;
1713
- const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime);
1714
- const assertionResults = tests.map((t) => {
1472
+ const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime), assertionResults = tests.map((t) => {
1715
1473
  const ancestorTitles = [];
1716
1474
  let iter = t.suite;
1717
- while (iter) {
1718
- ancestorTitles.push(iter.name);
1719
- iter = iter.suite;
1720
- }
1721
- ancestorTitles.reverse();
1722
- return {
1475
+ while (iter) ancestorTitles.push(iter.name), iter = iter.suite;
1476
+ return ancestorTitles.reverse(), {
1723
1477
  ancestorTitles,
1724
1478
  fullName: t.name ? [...ancestorTitles, t.name].join(" ") : ancestorTitles.join(" "),
1725
1479
  status: StatusMap[t.result?.state || t.mode] || "skipped",
@@ -1755,13 +1509,10 @@ class JsonReporter {
1755
1509
  startTime: this.start,
1756
1510
  success,
1757
1511
  testResults,
1758
- coverageMap
1512
+ coverageMap: this.coverageMap
1759
1513
  };
1760
1514
  await this.writeReport(JSON.stringify(result));
1761
1515
  }
1762
- async onFinished(files = this.ctx.state.getFiles(), _errors = [], coverageMap) {
1763
- await this.logTasks(files, coverageMap);
1764
- }
1765
1516
  /**
1766
1517
  * Writes the report to an output file if specified in the config,
1767
1518
  * or logs it to the console otherwise.
@@ -1770,11 +1521,9 @@ class JsonReporter {
1770
1521
  async writeReport(report) {
1771
1522
  const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
1772
1523
  if (outputFile) {
1773
- const reportFile = resolve(this.ctx.config.root, outputFile);
1774
- const outputDirectory = dirname(reportFile);
1524
+ const reportFile = resolve(this.ctx.config.root, outputFile), outputDirectory = dirname(reportFile);
1775
1525
  if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
1776
- await promises.writeFile(reportFile, report, "utf-8");
1777
- this.ctx.logger.log(`JSON report written to ${reportFile}`);
1526
+ await promises.writeFile(reportFile, report, "utf-8"), this.ctx.logger.log(`JSON report written to ${reportFile}`);
1778
1527
  } else this.ctx.logger.log(report);
1779
1528
  }
1780
1529
  }
@@ -1797,8 +1546,7 @@ class IndentedLogger {
1797
1546
 
1798
1547
  function flattenTasks$1(task, baseName = "") {
1799
1548
  const base = baseName ? `${baseName} > ` : "";
1800
- if (task.type === "suite") return task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`));
1801
- else return [{
1549
+ return task.type === "suite" ? task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`)) : [{
1802
1550
  ...task,
1803
1551
  name: `${base}${task.name}`
1804
1552
  }];
@@ -1806,21 +1554,16 @@ function flattenTasks$1(task, baseName = "") {
1806
1554
  // https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
1807
1555
  function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
1808
1556
  let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
1809
- value = String(value || "").replace(regex, "");
1810
- {
1811
- // remove everything discouraged by XML 1.0 specifications
1812
- regex = new RegExp(
1813
- /* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
1814
- "([\\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]))",
1815
- "g"
1816
- /* eslint-enable */
1817
- );
1818
- value = value.replace(regex, "");
1819
- }
1557
+ if (value = String(value || "").replace(regex, ""), removeDiscouragedChars) regex = new RegExp(
1558
+ /* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
1559
+ "([\\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]))",
1560
+ "g"
1561
+ /* eslint-enable */
1562
+ ), value = value.replace(regex, "");
1820
1563
  return value;
1821
1564
  }
1822
1565
  function escapeXML(value) {
1823
- return removeInvalidXMLCharacters(String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
1566
+ return removeInvalidXMLCharacters(String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), true);
1824
1567
  }
1825
1568
  function executionTime(durationMS) {
1826
1569
  return (durationMS / 1e3).toLocaleString("en-US", {
@@ -1841,8 +1584,7 @@ class JUnitReporter {
1841
1584
  fileFd;
1842
1585
  options;
1843
1586
  constructor(options) {
1844
- this.options = { ...options };
1845
- this.options.includeConsoleOutput ??= true;
1587
+ this.options = { ...options }, this.options.includeConsoleOutput ??= true;
1846
1588
  }
1847
1589
  async onInit(ctx) {
1848
1590
  this.ctx = ctx;
@@ -1852,14 +1594,12 @@ class JUnitReporter {
1852
1594
  const outputDirectory = dirname(this.reportFile);
1853
1595
  if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
1854
1596
  const fileFd = await promises.open(this.reportFile, "w+");
1855
- this.fileFd = fileFd;
1856
- this.baseLog = async (text) => {
1597
+ this.fileFd = fileFd, this.baseLog = async (text) => {
1857
1598
  if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
1858
1599
  await promises.writeFile(this.fileFd, `${text}\n`);
1859
1600
  };
1860
1601
  } else this.baseLog = async (text) => this.ctx.logger.log(text);
1861
- this._timeStart = /* @__PURE__ */ new Date();
1862
- this.logger = new IndentedLogger(this.baseLog);
1602
+ this._timeStart = /* @__PURE__ */ new Date(), this.logger = new IndentedLogger(this.baseLog);
1863
1603
  }
1864
1604
  async writeElement(name, attrs, children) {
1865
1605
  const pairs = [];
@@ -1868,18 +1608,12 @@ class JUnitReporter {
1868
1608
  if (attr === void 0) continue;
1869
1609
  pairs.push(`${key}="${escapeXML(attr)}"`);
1870
1610
  }
1871
- await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`);
1872
- this.logger.indent();
1873
- await children.call(this);
1874
- this.logger.unindent();
1875
- await this.logger.log(`</${name}>`);
1611
+ await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`), this.logger.indent(), await children.call(this), this.logger.unindent(), await this.logger.log(`</${name}>`);
1876
1612
  }
1877
1613
  async writeLogs(task, type) {
1878
1614
  if (task.logs == null || task.logs.length === 0) return;
1879
- const logType = type === "err" ? "stderr" : "stdout";
1880
- const logs = task.logs.filter((log) => log.type === logType);
1881
- if (logs.length === 0) return;
1882
- await this.writeElement(`system-${type}`, {}, async () => {
1615
+ const logType = type === "err" ? "stderr" : "stdout", logs = task.logs.filter((log) => log.type === logType);
1616
+ logs.length !== 0 && await this.writeElement(`system-${type}`, {}, async () => {
1883
1617
  for (const log of logs) await this.baseLog(escapeXML(log.content));
1884
1618
  });
1885
1619
  }
@@ -1898,20 +1632,12 @@ class JUnitReporter {
1898
1632
  name: task.name,
1899
1633
  time: getDuration(task)
1900
1634
  }, async () => {
1901
- if (this.options.includeConsoleOutput) {
1902
- await this.writeLogs(task, "out");
1903
- await this.writeLogs(task, "err");
1904
- }
1635
+ if (this.options.includeConsoleOutput) await this.writeLogs(task, "out"), await this.writeLogs(task, "err");
1905
1636
  if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
1906
1637
  if (task.type === "test" && task.annotations.length) {
1907
- await this.logger.log("<properties>");
1908
- this.logger.indent();
1909
- for (const annotation of task.annotations) {
1910
- await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`);
1911
- await this.logger.log("</property>");
1912
- }
1913
- this.logger.unindent();
1914
- await this.logger.log("</properties>");
1638
+ await this.logger.log("<properties>"), this.logger.indent();
1639
+ for (const annotation of task.annotations) await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`), await this.logger.log("</property>");
1640
+ this.logger.unindent(), await this.logger.log("</properties>");
1915
1641
  }
1916
1642
  if (task.result?.state === "fail") {
1917
1643
  const errors = task.result.errors || [];
@@ -1930,11 +1656,11 @@ class JUnitReporter {
1930
1656
  });
1931
1657
  }
1932
1658
  }
1933
- async onFinished(files = this.ctx.state.getFiles()) {
1659
+ async onTestRunEnd(testModules) {
1660
+ const files = testModules.map((testModule) => testModule.task);
1934
1661
  await this.logger.log("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
1935
1662
  const transformed = files.map((file) => {
1936
- const tasks = file.tasks.flatMap((task) => flattenTasks$1(task));
1937
- const stats = tasks.reduce((stats, task) => {
1663
+ const tasks = file.tasks.flatMap((task) => flattenTasks$1(task)), stats = tasks.reduce((stats, task) => {
1938
1664
  return {
1939
1665
  passed: stats.passed + Number(task.result?.state === "pass"),
1940
1666
  failures: stats.failures + Number(task.result?.state === "fail"),
@@ -1944,41 +1670,29 @@ class JUnitReporter {
1944
1670
  passed: 0,
1945
1671
  failures: 0,
1946
1672
  skipped: 0
1947
- });
1948
- // inject failed suites to surface errors during beforeAll/afterAll
1949
- const suites = getSuites(file);
1950
- for (const suite of suites) if (suite.result?.errors) {
1951
- tasks.push(suite);
1952
- stats.failures += 1;
1953
- }
1673
+ }), suites = getSuites(file);
1674
+ for (const suite of suites) if (suite.result?.errors) tasks.push(suite), stats.failures += 1;
1954
1675
  // If there are no tests, but the file failed to load, we still want to report it as a failure
1955
- if (tasks.length === 0 && file.result?.state === "fail") {
1956
- stats.failures = 1;
1957
- tasks.push({
1958
- id: file.id,
1959
- type: "test",
1960
- name: file.name,
1961
- mode: "run",
1962
- result: file.result,
1963
- meta: {},
1964
- timeout: 0,
1965
- context: null,
1966
- suite: null,
1967
- file: null,
1968
- annotations: []
1969
- });
1970
- }
1676
+ if (tasks.length === 0 && file.result?.state === "fail") stats.failures = 1, tasks.push({
1677
+ id: file.id,
1678
+ type: "test",
1679
+ name: file.name,
1680
+ mode: "run",
1681
+ result: file.result,
1682
+ meta: {},
1683
+ timeout: 0,
1684
+ context: null,
1685
+ suite: null,
1686
+ file: null,
1687
+ annotations: []
1688
+ });
1971
1689
  return {
1972
1690
  ...file,
1973
1691
  tasks,
1974
1692
  stats
1975
1693
  };
1976
- });
1977
- const stats = transformed.reduce((stats, file) => {
1978
- stats.tests += file.tasks.length;
1979
- stats.failures += file.stats.failures;
1980
- stats.time += file.result?.duration || 0;
1981
- return stats;
1694
+ }), stats = transformed.reduce((stats, file) => {
1695
+ return stats.tests += file.tasks.length, stats.failures += file.stats.failures, stats.time += file.result?.duration || 0, stats;
1982
1696
  }, {
1983
1697
  name: this.options.suiteName || "vitest tests",
1984
1698
  tests: 0,
@@ -1986,7 +1700,7 @@ class JUnitReporter {
1986
1700
  errors: 0,
1987
1701
  time: 0
1988
1702
  });
1989
- await this.writeElement("testsuites", {
1703
+ if (await this.writeElement("testsuites", {
1990
1704
  ...stats,
1991
1705
  time: executionTime(stats.time)
1992
1706
  }, async () => {
@@ -2005,16 +1719,13 @@ class JUnitReporter {
2005
1719
  await this.writeTasks(file.tasks, filename);
2006
1720
  });
2007
1721
  }
2008
- });
2009
- if (this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
2010
- await this.fileFd?.close();
2011
- this.fileFd = void 0;
1722
+ }), this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
1723
+ await this.fileFd?.close(), this.fileFd = void 0;
2012
1724
  }
2013
1725
  }
2014
1726
 
2015
1727
  function yamlString(str) {
2016
- if (!str) return "";
2017
- return `"${str.replace(/"/g, "\\\"")}"`;
1728
+ return str ? `"${str.replace(/"/g, "\\\"")}"` : "";
2018
1729
  }
2019
1730
  function tapString(str) {
2020
1731
  return str.replace(/\\/g, "\\\\").replace(/#/g, "\\#").replace(/\n/g, " ");
@@ -2023,77 +1734,45 @@ class TapReporter {
2023
1734
  ctx;
2024
1735
  logger;
2025
1736
  onInit(ctx) {
2026
- this.ctx = ctx;
2027
- this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
1737
+ this.ctx = ctx, this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
2028
1738
  }
2029
1739
  static getComment(task) {
2030
- if (task.mode === "skip") return " # SKIP";
2031
- else if (task.mode === "todo") return " # TODO";
2032
- else if (task.result?.duration != null) return ` # time=${task.result.duration.toFixed(2)}ms`;
2033
- else return "";
1740
+ return task.mode === "skip" ? " # SKIP" : task.mode === "todo" ? " # TODO" : task.result?.duration == null ? "" : ` # time=${task.result.duration.toFixed(2)}ms`;
2034
1741
  }
2035
1742
  logErrorDetails(error, stack) {
2036
1743
  const errorName = error.name || "Unknown Error";
2037
- this.logger.log(`name: ${yamlString(String(errorName))}`);
2038
- this.logger.log(`message: ${yamlString(String(error.message))}`);
2039
- if (stack)
1744
+ if (this.logger.log(`name: ${yamlString(String(errorName))}`), this.logger.log(`message: ${yamlString(String(error.message))}`), stack)
2040
1745
  // For compatibility with tap-mocha-reporter
2041
1746
  this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
2042
1747
  }
2043
1748
  logTasks(tasks) {
2044
1749
  this.logger.log(`1..${tasks.length}`);
2045
1750
  for (const [i, task] of tasks.entries()) {
2046
- const id = i + 1;
2047
- const ok = task.result?.state === "pass" || task.mode === "skip" || task.mode === "todo" ? "ok" : "not ok";
2048
- const comment = TapReporter.getComment(task);
2049
- if (task.type === "suite" && task.tasks.length > 0) {
2050
- this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment} {`);
2051
- this.logger.indent();
2052
- this.logTasks(task.tasks);
2053
- this.logger.unindent();
2054
- this.logger.log("}");
2055
- } else {
1751
+ const id = i + 1, ok = task.result?.state === "pass" || task.mode === "skip" || task.mode === "todo" ? "ok" : "not ok", comment = TapReporter.getComment(task);
1752
+ 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("}");
1753
+ else {
2056
1754
  this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
2057
1755
  const project = this.ctx.getProjectByName(task.file.projectName || "");
2058
- if (task.type === "test" && task.annotations) {
2059
- this.logger.indent();
2060
- task.annotations.forEach(({ type, message }) => {
2061
- this.logger.log(`# ${type}: ${message}`);
2062
- });
2063
- this.logger.unindent();
2064
- }
2065
- if (task.result?.state === "fail" && task.result.errors) {
2066
- this.logger.indent();
2067
- task.result.errors.forEach((error) => {
2068
- const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace });
2069
- const stack = stacks[0];
2070
- this.logger.log("---");
2071
- this.logger.log("error:");
2072
- this.logger.indent();
2073
- this.logErrorDetails(error);
2074
- this.logger.unindent();
2075
- if (stack) this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
2076
- if (error.showDiff) {
2077
- this.logger.log(`actual: ${yamlString(error.actual)}`);
2078
- this.logger.log(`expected: ${yamlString(error.expected)}`);
2079
- }
2080
- });
2081
- this.logger.log("...");
2082
- this.logger.unindent();
2083
- }
1756
+ if (task.type === "test" && task.annotations) this.logger.indent(), task.annotations.forEach(({ type, message }) => {
1757
+ this.logger.log(`# ${type}: ${message}`);
1758
+ }), this.logger.unindent();
1759
+ if (task.result?.state === "fail" && task.result.errors) this.logger.indent(), task.result.errors.forEach((error) => {
1760
+ const stacks = task.file.pool === "browser" ? project.browser?.parseErrorStacktrace(error) || [] : parseErrorStacktrace(error, { frameFilter: this.ctx.config.onStackTrace }), stack = stacks[0];
1761
+ 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}`)}`);
1762
+ if (error.showDiff) this.logger.log(`actual: ${yamlString(error.actual)}`), this.logger.log(`expected: ${yamlString(error.expected)}`);
1763
+ }), this.logger.log("..."), this.logger.unindent();
2084
1764
  }
2085
1765
  }
2086
1766
  }
2087
- onFinished(files = this.ctx.state.getFiles()) {
2088
- this.logger.log("TAP version 13");
2089
- this.logTasks(files);
1767
+ onTestRunEnd(testModules) {
1768
+ const files = testModules.map((testModule) => testModule.task);
1769
+ this.logger.log("TAP version 13"), this.logTasks(files);
2090
1770
  }
2091
1771
  }
2092
1772
 
2093
1773
  function flattenTasks(task, baseName = "") {
2094
1774
  const base = baseName ? `${baseName} > ` : "";
2095
- if (task.type === "suite" && task.tasks.length > 0) return task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`));
2096
- else return [{
1775
+ return task.type === "suite" && task.tasks.length > 0 ? task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`)) : [{
2097
1776
  ...task,
2098
1777
  name: `${base}${task.name}`
2099
1778
  }];
@@ -2102,9 +1781,9 @@ class TapFlatReporter extends TapReporter {
2102
1781
  onInit(ctx) {
2103
1782
  super.onInit(ctx);
2104
1783
  }
2105
- onFinished(files = this.ctx.state.getFiles()) {
1784
+ onTestRunEnd(testModules) {
2106
1785
  this.ctx.logger.log("TAP version 13");
2107
- const flatTasks = files.flatMap((task) => flattenTasks(task));
1786
+ const flatTasks = testModules.flatMap((testModule) => flattenTasks(testModule.task));
2108
1787
  this.logTasks(flatTasks);
2109
1788
  }
2110
1789
  }
@@ -2120,31 +1799,22 @@ class VerboseReporter extends DefaultReporter {
2120
1799
  if (this.isTTY) return super.printTestModule(module);
2121
1800
  }
2122
1801
  onTestCaseResult(test) {
2123
- super.onTestCaseResult(test);
2124
1802
  // don't print tests in TTY as they go, only print them
2125
1803
  // in the CLI when they finish
2126
- if (this.isTTY) return;
1804
+ if (super.onTestCaseResult(test), this.isTTY) return;
2127
1805
  const testResult = test.result();
2128
1806
  if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") return;
2129
1807
  let title = ` ${getStateSymbol(test.task)} `;
2130
1808
  if (test.project.name) title += formatProjectName(test.project);
2131
- title += getFullName(test.task, c.dim(" > "));
2132
- title += this.getDurationPrefix(test.task);
1809
+ title += getFullName(test.task, c.dim(" > ")), title += this.getDurationPrefix(test.task);
2133
1810
  const diagnostic = test.diagnostic();
2134
1811
  if (diagnostic?.heap != null) title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
2135
1812
  if (testResult.state === "skipped" && testResult.note) title += c.dim(c.gray(` [${testResult.note}]`));
2136
- this.log(title);
2137
- if (testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
2138
- if (test.annotations().length) {
2139
- this.log();
2140
- this.printAnnotations(test, "log", 3);
2141
- this.log();
2142
- }
1813
+ if (this.log(title), testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
1814
+ if (test.annotations().length) this.log(), this.printAnnotations(test, "log", 3), this.log();
2143
1815
  }
2144
1816
  printTestSuite(testSuite) {
2145
- const indentation = " ".repeat(getIndentation(testSuite.task));
2146
- const tests = Array.from(testSuite.children.allTests());
2147
- const state = getStateSymbol(testSuite.task);
1817
+ const indentation = " ".repeat(getIndentation(testSuite.task)), tests = Array.from(testSuite.children.allTests()), state = getStateSymbol(testSuite.task);
2148
1818
  this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`);
2149
1819
  }
2150
1820
  getTestName(test) {
@@ -2159,8 +1829,7 @@ class VerboseReporter extends DefaultReporter {
2159
1829
  }
2160
1830
  }
2161
1831
  function getIndentation(suite, level = 1) {
2162
- if (suite.suite && !("filepath" in suite.suite)) return getIndentation(suite.suite, level + 1);
2163
- return level;
1832
+ return suite.suite && !("filepath" in suite.suite) ? getIndentation(suite.suite, level + 1) : level;
2164
1833
  }
2165
1834
 
2166
1835
  const ReportersMap = {