vitest 4.0.0-beta.1 → 4.0.0-beta.10

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