vitest 3.2.0-beta.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/LICENSE.md +29 -0
  2. package/dist/browser.d.ts +3 -3
  3. package/dist/browser.js +2 -2
  4. package/dist/chunks/{base.DwtwORaC.js → base.Cg0miDlQ.js} +11 -14
  5. package/dist/chunks/{benchmark.BoF7jW0Q.js → benchmark.CYdenmiT.js} +4 -6
  6. package/dist/chunks/{cac.I9MLYfT-.js → cac.6rXCxFY1.js} +76 -143
  7. package/dist/chunks/{cli-api.d6IK1pnk.js → cli-api.Cej3MBjA.js} +1460 -1344
  8. package/dist/chunks/{config.d.UqE-KR0o.d.ts → config.d.D2ROskhv.d.ts} +2 -0
  9. package/dist/chunks/{console.K1NMVOSc.js → console.CtFJOzRO.js} +25 -45
  10. package/dist/chunks/{constants.BZZyIeIE.js → constants.DnKduX2e.js} +1 -0
  11. package/dist/chunks/{coverage.0iPg4Wrz.js → coverage.DVF1vEu8.js} +4 -12
  12. package/dist/chunks/{coverage.OGU09Jbh.js → coverage.EIiagJJP.js} +578 -993
  13. package/dist/chunks/{creator.DGAdZ4Hj.js → creator.GK6I-cL4.js} +39 -83
  14. package/dist/chunks/date.Bq6ZW5rf.js +73 -0
  15. package/dist/chunks/{defaults.DSxsTG0h.js → defaults.B7q_naMc.js} +2 -1
  16. package/dist/chunks/{env.Dq0hM4Xv.js → env.D4Lgay0q.js} +1 -1
  17. package/dist/chunks/{environment.d.D8YDy2v5.d.ts → environment.d.cL3nLXbE.d.ts} +1 -0
  18. package/dist/chunks/{execute.JlGHLJZT.js → execute.B7h3T_Hc.js} +126 -217
  19. package/dist/chunks/{git.DXfdBEfR.js → git.BVQ8w_Sw.js} +1 -3
  20. package/dist/chunks/{global.d.BPa1eL3O.d.ts → global.d.MAmajcmJ.d.ts} +5 -1
  21. package/dist/chunks/{globals.CpxW8ccg.js → globals.DEHgCU4V.js} +7 -6
  22. package/dist/chunks/{index.CV36oG_L.js → index.BZ0g1JD2.js} +430 -625
  23. package/dist/chunks/{index.DswW_LEs.js → index.BbB8_kAK.js} +25 -24
  24. package/dist/chunks/{index.CmC5OK9L.js → index.CIyJn3t1.js} +38 -82
  25. package/dist/chunks/{index.CfXMNXHg.js → index.CdQS2e2Q.js} +4 -2
  26. package/dist/chunks/{index.DFXFpH3w.js → index.CmSc2RE5.js} +85 -105
  27. package/dist/chunks/index.D3XRDfWc.js +213 -0
  28. package/dist/chunks/{inspector.DbDkSkFn.js → inspector.C914Efll.js} +4 -1
  29. package/dist/chunks/{node.3xsWotC9.js → node.fjCdwEIl.js} +1 -1
  30. package/dist/chunks/{reporters.d.CLC9rhKy.d.ts → reporters.d.C1ogPriE.d.ts} +47 -9
  31. package/dist/chunks/{rpc.D9_013TY.js → rpc.Iovn4oWe.js} +10 -19
  32. package/dist/chunks/{runBaseTests.Dn2vyej_.js → runBaseTests.Dd85QTll.js} +27 -31
  33. package/dist/chunks/{setup-common.CYo3Y0dD.js → setup-common.Dd054P77.js} +16 -42
  34. package/dist/chunks/{typechecker.DnTrplSJ.js → typechecker.DRKU1-1g.js} +163 -186
  35. package/dist/chunks/{utils.BfxieIyZ.js → utils.CAioKnHs.js} +9 -14
  36. package/dist/chunks/{utils.CgTj3MsC.js → utils.XdZDrNZV.js} +6 -13
  37. package/dist/chunks/{vi.BFR5YIgu.js → vi.bdSIJ99Y.js} +137 -263
  38. package/dist/chunks/{vite.d.CBZ3M_ru.d.ts → vite.d.DqE4-hhK.d.ts} +3 -1
  39. package/dist/chunks/{vm.C1HHjtNS.js → vm.BThCzidc.js} +164 -212
  40. package/dist/chunks/{worker.d.D5Xdi-Zr.d.ts → worker.d.DvqK5Vmu.d.ts} +1 -1
  41. package/dist/chunks/{worker.d.CoCI7hzP.d.ts → worker.d.tQu2eJQy.d.ts} +5 -3
  42. package/dist/cli.js +5 -5
  43. package/dist/config.cjs +3 -1
  44. package/dist/config.d.ts +7 -6
  45. package/dist/config.js +3 -3
  46. package/dist/coverage.d.ts +4 -4
  47. package/dist/coverage.js +7 -7
  48. package/dist/environments.d.ts +6 -2
  49. package/dist/environments.js +1 -1
  50. package/dist/execute.d.ts +9 -3
  51. package/dist/execute.js +1 -1
  52. package/dist/index.d.ts +28 -15
  53. package/dist/index.js +5 -5
  54. package/dist/node.d.ts +18 -10
  55. package/dist/node.js +17 -17
  56. package/dist/reporters.d.ts +4 -4
  57. package/dist/reporters.js +4 -4
  58. package/dist/runners.d.ts +6 -3
  59. package/dist/runners.js +59 -80
  60. package/dist/snapshot.js +2 -2
  61. package/dist/suite.js +2 -2
  62. package/dist/worker.js +39 -41
  63. package/dist/workers/forks.js +6 -4
  64. package/dist/workers/runVmTests.js +20 -21
  65. package/dist/workers/threads.js +4 -4
  66. package/dist/workers/vmForks.js +6 -6
  67. package/dist/workers/vmThreads.js +6 -6
  68. package/dist/workers.d.ts +4 -4
  69. package/dist/workers.js +10 -10
  70. package/package.json +21 -19
  71. package/dist/chunks/date.CDOsz-HY.js +0 -53
  72. package/dist/chunks/index.CK1YOQaa.js +0 -143
@@ -4,8 +4,8 @@ import { slash, toArray, isPrimitive, inspect, positionToOffset, lineSplitRE } f
4
4
  import { parseStacktrace, parseErrorStacktrace } from '@vitest/utils/source-map';
5
5
  import { isAbsolute, relative, dirname, basename, resolve, normalize } from 'pathe';
6
6
  import c from 'tinyrainbow';
7
- import { i as isTTY } from './env.Dq0hM4Xv.js';
8
- import { h as hasFailedSnapshot, g as getOutputFile, T as TypeCheckError } from './typechecker.DnTrplSJ.js';
7
+ import { i as isTTY } from './env.D4Lgay0q.js';
8
+ import { h as hasFailedSnapshot, g as getOutputFile, T as TypeCheckError } from './typechecker.DRKU1-1g.js';
9
9
  import { stripVTControlCharacters } from 'node:util';
10
10
  import { existsSync, readFileSync, promises } from 'node:fs';
11
11
  import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
@@ -40,9 +40,7 @@ const labelDefaultColors = [
40
40
  ];
41
41
  function getCols(delta = 0) {
42
42
  let length = process.stdout?.columns;
43
- if (!length || Number.isNaN(length)) {
44
- length = 30;
45
- }
43
+ if (!length || Number.isNaN(length)) length = 30;
46
44
  return Math.max(length + delta, 0);
47
45
  }
48
46
  function errorBanner(message) {
@@ -53,9 +51,8 @@ function divider(text, left, right, color) {
53
51
  const c = color || ((text) => text);
54
52
  if (text) {
55
53
  const textLength = stripVTControlCharacters(text).length;
56
- if (left == null && right != null) {
57
- left = cols - textLength - right;
58
- } else {
54
+ if (left == null && right != null) left = cols - textLength - right;
55
+ else {
59
56
  left = left ?? Math.floor((cols - textLength) / 2);
60
57
  right = cols - textLength - left;
61
58
  }
@@ -66,9 +63,7 @@ function divider(text, left, right, color) {
66
63
  return F_LONG_DASH.repeat(cols);
67
64
  }
68
65
  function formatTestPath(root, path) {
69
- if (isAbsolute(path)) {
70
- path = relative(root, path);
71
- }
66
+ if (isAbsolute(path)) path = relative(root, path);
72
67
  const dir = dirname(path);
73
68
  const ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "";
74
69
  const base = basename(path, ext);
@@ -76,22 +71,11 @@ function formatTestPath(root, path) {
76
71
  }
77
72
  function renderSnapshotSummary(rootDir, snapshots) {
78
73
  const summary = [];
79
- if (snapshots.added) {
80
- summary.push(c.bold(c.green(`${snapshots.added} written`)));
81
- }
82
- if (snapshots.unmatched) {
83
- summary.push(c.bold(c.red(`${snapshots.unmatched} failed`)));
84
- }
85
- if (snapshots.updated) {
86
- summary.push(c.bold(c.green(`${snapshots.updated} updated `)));
87
- }
88
- if (snapshots.filesRemoved) {
89
- if (snapshots.didUpdate) {
90
- summary.push(c.bold(c.green(`${snapshots.filesRemoved} files removed `)));
91
- } else {
92
- summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
93
- }
94
- }
74
+ if (snapshots.added) summary.push(c.bold(c.green(`${snapshots.added} written`)));
75
+ if (snapshots.unmatched) summary.push(c.bold(c.red(`${snapshots.unmatched} failed`)));
76
+ if (snapshots.updated) summary.push(c.bold(c.green(`${snapshots.updated} updated `)));
77
+ if (snapshots.filesRemoved) if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.filesRemoved} files removed `)));
78
+ else summary.push(c.bold(c.yellow(`${snapshots.filesRemoved} files obsolete `)));
95
79
  if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
96
80
  const [head, ...tail] = snapshots.filesRemovedList;
97
81
  summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`);
@@ -100,11 +84,8 @@ function renderSnapshotSummary(rootDir, snapshots) {
100
84
  });
101
85
  }
102
86
  if (snapshots.unchecked) {
103
- if (snapshots.didUpdate) {
104
- summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
105
- } else {
106
- summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
107
- }
87
+ if (snapshots.didUpdate) summary.push(c.bold(c.green(`${snapshots.unchecked} removed`)));
88
+ else summary.push(c.bold(c.yellow(`${snapshots.unchecked} obsolete`)));
108
89
  snapshots.uncheckedKeysByFile.forEach((uncheckedFile) => {
109
90
  summary.push(`${c.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
110
91
  uncheckedFile.keys.forEach((key) => summary.push(` ${c.gray(F_DOT)} ${key}`));
@@ -116,9 +97,7 @@ function countTestErrors(tasks) {
116
97
  return tasks.reduce((c, i) => c + (i.result?.errors?.length || 0), 0);
117
98
  }
118
99
  function getStateString$1(tasks, name = "tests", showTotal = true) {
119
- if (tasks.length === 0) {
120
- return c.dim(`no ${name}`);
121
- }
100
+ if (tasks.length === 0) return c.dim(`no ${name}`);
122
101
  const passed = tasks.filter((i) => i.result?.state === "pass");
123
102
  const failed = tasks.filter((i) => i.result?.state === "fail");
124
103
  const skipped = tasks.filter((i) => i.mode === "skip");
@@ -131,41 +110,25 @@ function getStateString$1(tasks, name = "tests", showTotal = true) {
131
110
  ].filter(Boolean).join(c.dim(" | ")) + (showTotal ? c.gray(` (${tasks.length})`) : "");
132
111
  }
133
112
  function getStateSymbol(task) {
134
- if (task.mode === "skip" || task.mode === "todo") {
135
- return skipped;
136
- }
137
- if (!task.result) {
138
- return pending$1;
139
- }
113
+ if (task.mode === "skip" || task.mode === "todo") return skipped;
114
+ if (!task.result) return pending$1;
140
115
  if (task.result.state === "run" || task.result.state === "queued") {
141
- if (task.type === "suite") {
142
- return pointer;
143
- }
144
- }
145
- if (task.result.state === "pass") {
146
- return task.meta?.benchmark ? benchmarkPass : testPass;
147
- }
148
- if (task.result.state === "fail") {
149
- return task.type === "suite" ? suiteFail : taskFail;
116
+ if (task.type === "suite") return pointer;
150
117
  }
118
+ if (task.result.state === "pass") return task.meta?.benchmark ? benchmarkPass : testPass;
119
+ if (task.result.state === "fail") return task.type === "suite" ? suiteFail : taskFail;
151
120
  return " ";
152
121
  }
153
122
  function formatTimeString(date) {
154
123
  return date.toTimeString().split(" ")[0];
155
124
  }
156
125
  function formatTime(time) {
157
- if (time > 1e3) {
158
- return `${(time / 1e3).toFixed(2)}s`;
159
- }
126
+ if (time > 1e3) return `${(time / 1e3).toFixed(2)}s`;
160
127
  return `${Math.round(time)}ms`;
161
128
  }
162
129
  function formatProjectName(project, suffix = " ") {
163
- if (!project?.name) {
164
- return "";
165
- }
166
- if (!c.isColorSupported) {
167
- return `|${project.name}|${suffix}`;
168
- }
130
+ if (!project?.name) return "";
131
+ if (!c.isColorSupported) return `|${project.name}|${suffix}`;
169
132
  let background = project.color && c[`bg${capitalize(project.color)}`];
170
133
  if (!background) {
171
134
  const index = project.name.split("").reduce((acc, v, idx) => acc + v.charCodeAt(0) + idx, 0);
@@ -182,9 +145,7 @@ function padSummaryTitle(str) {
182
145
  }
183
146
  function truncateString(text, maxLength) {
184
147
  const plainText = stripVTControlCharacters(text);
185
- if (plainText.length <= maxLength) {
186
- return text;
187
- }
148
+ if (plainText.length <= maxLength) return text;
188
149
  return `${plainText.slice(0, maxLength - 1)}…`;
189
150
  }
190
151
  function capitalize(text) {
@@ -222,11 +183,11 @@ class BaseReporter {
222
183
  watchFilters;
223
184
  failedUnwatchedFiles = [];
224
185
  isTTY;
225
- ctx = undefined;
186
+ ctx = void 0;
226
187
  renderSucceed = false;
227
188
  verbose = false;
228
- _filesInWatchMode = new Map();
229
- _timeStart = formatTimeString(new Date());
189
+ _filesInWatchMode = /* @__PURE__ */ new Map();
190
+ _timeStart = formatTimeString(/* @__PURE__ */ new Date());
230
191
  constructor(options = {}) {
231
192
  this.isTTY = options.isTTY ?? isTTY;
232
193
  }
@@ -246,67 +207,48 @@ class BaseReporter {
246
207
  }
247
208
  onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
248
209
  this.end = performance$1.now();
249
- if (!files.length && !errors.length) {
250
- this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
251
- } else {
252
- this.reportSummary(files, errors);
253
- }
210
+ if (!files.length && !errors.length) this.ctx.logger.printNoTestFound(this.ctx.filenamePattern);
211
+ else this.reportSummary(files, errors);
254
212
  }
255
213
  onTestCaseResult(testCase) {
256
- if (testCase.result().state === "failed") {
257
- this.logFailedTask(testCase.task);
258
- }
214
+ if (testCase.result().state === "failed") this.logFailedTask(testCase.task);
259
215
  }
260
216
  onTestSuiteResult(testSuite) {
261
- if (testSuite.state() === "failed") {
262
- this.logFailedTask(testSuite.task);
263
- }
217
+ if (testSuite.state() === "failed") this.logFailedTask(testSuite.task);
264
218
  }
265
219
  onTestModuleEnd(testModule) {
266
- if (testModule.state() === "failed") {
267
- this.logFailedTask(testModule.task);
268
- }
220
+ if (testModule.state() === "failed") this.logFailedTask(testModule.task);
269
221
  this.printTestModule(testModule);
270
222
  }
271
223
  logFailedTask(task) {
272
- if (this.ctx.config.silent === "passed-only") {
273
- for (const log of task.logs || []) {
274
- this.onUserConsoleLog(log, "failed");
275
- }
276
- }
224
+ if (this.ctx.config.silent === "passed-only") for (const log of task.logs || []) this.onUserConsoleLog(log, "failed");
277
225
  }
278
226
  printTestModule(testModule) {
279
227
  const moduleState = testModule.state();
280
- if (moduleState === "queued" || moduleState === "pending") {
281
- return;
282
- }
228
+ if (moduleState === "queued" || moduleState === "pending") return;
283
229
  let testsCount = 0;
284
230
  let failedCount = 0;
285
231
  let skippedCount = 0;
232
+ // delaying logs to calculate the test stats first
233
+ // which minimizes the amount of for loops
286
234
  const logs = [];
287
235
  const originalLog = this.log.bind(this);
288
236
  this.log = (msg) => logs.push(msg);
289
237
  const visit = (suiteState, children) => {
290
- for (const child of children) {
291
- if (child.type === "suite") {
292
- const suiteState = child.state();
293
- if (!this.ctx.config.hideSkippedTests || suiteState !== "skipped") {
294
- this.printTestSuite(child);
295
- }
296
- visit(suiteState, child.children);
297
- } else {
298
- const testResult = child.result();
299
- testsCount++;
300
- if (testResult.state === "failed") {
301
- failedCount++;
302
- } else if (testResult.state === "skipped") {
303
- skippedCount++;
304
- }
305
- if (this.ctx.config.hideSkippedTests && suiteState === "skipped") {
306
- continue;
307
- }
308
- this.printTestCase(moduleState, child);
309
- }
238
+ for (const child of children) if (child.type === "suite") {
239
+ const suiteState = child.state();
240
+ // Skipped suites are hidden when --hideSkippedTests, print otherwise
241
+ if (!this.ctx.config.hideSkippedTests || suiteState !== "skipped") this.printTestSuite(child);
242
+ visit(suiteState, child.children);
243
+ } else {
244
+ const testResult = child.result();
245
+ testsCount++;
246
+ if (testResult.state === "failed") failedCount++;
247
+ else if (testResult.state === "skipped") skippedCount++;
248
+ if (this.ctx.config.hideSkippedTests && suiteState === "skipped")
249
+ // Skipped suites are hidden when --hideSkippedTests
250
+ continue;
251
+ this.printTestCase(moduleState, child);
310
252
  }
311
253
  };
312
254
  try {
@@ -326,51 +268,34 @@ class BaseReporter {
326
268
  const { duration, retryCount, repeatCount } = test.diagnostic() || {};
327
269
  const padding = this.getTestIndentation(test.task);
328
270
  let suffix = this.getDurationPrefix(test.task);
329
- if (retryCount != null && retryCount > 0) {
330
- suffix += c.yellow(` (retry x${retryCount})`);
331
- }
332
- if (repeatCount != null && repeatCount > 0) {
333
- suffix += c.yellow(` (repeat x${repeatCount})`);
334
- }
271
+ if (retryCount != null && retryCount > 0) suffix += c.yellow(` (retry x${retryCount})`);
272
+ if (repeatCount != null && repeatCount > 0) suffix += c.yellow(` (repeat x${repeatCount})`);
335
273
  if (testResult.state === "failed") {
336
274
  this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(" > "))}`) + suffix);
275
+ // print short errors, full errors will be at the end in summary
337
276
  testResult.errors.forEach((error) => {
338
277
  const message = this.formatShortError(error);
339
- if (message) {
340
- this.log(c.red(` ${padding}${message}`));
341
- }
278
+ if (message) this.log(c.red(` ${padding}${message}`));
342
279
  });
343
- } else if (duration && duration > this.ctx.config.slowTestThreshold) {
344
- this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
345
- } else if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") ; else if (testResult.state === "skipped" && testResult.note) {
346
- this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${c.dim(c.gray(` [${testResult.note}]`))}`);
347
- } else if (this.renderSucceed || moduleState === "failed") {
348
- this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
349
- }
280
+ } else if (duration && duration > this.ctx.config.slowTestThreshold) this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(" > "))} ${suffix}`);
281
+ 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}]`))}`);
282
+ else if (this.renderSucceed || moduleState === "failed") this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(" > "))}${suffix}`);
350
283
  }
351
284
  getModuleLog(testModule, counts) {
352
285
  let state = c.dim(`${counts.tests} test${counts.tests > 1 ? "s" : ""}`);
353
- if (counts.failed) {
354
- state += c.dim(" | ") + c.red(`${counts.failed} failed`);
355
- }
356
- if (counts.skipped) {
357
- state += c.dim(" | ") + c.yellow(`${counts.skipped} skipped`);
358
- }
286
+ if (counts.failed) state += c.dim(" | ") + c.red(`${counts.failed} failed`);
287
+ if (counts.skipped) state += c.dim(" | ") + c.yellow(`${counts.skipped} skipped`);
359
288
  let suffix = c.dim("(") + state + c.dim(")") + this.getDurationPrefix(testModule.task);
360
289
  const diagnostic = testModule.diagnostic();
361
- if (diagnostic.heap != null) {
362
- suffix += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
363
- }
290
+ if (diagnostic.heap != null) suffix += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
364
291
  let title = getStateSymbol(testModule.task);
365
- if (testModule.meta().typecheck) {
366
- title += ` ${c.bgBlue(c.bold(" TS "))}`;
367
- }
368
- if (testModule.project.name) {
369
- title += ` ${formatProjectName(testModule.project, "")}`;
370
- }
292
+ if (testModule.meta().typecheck) title += ` ${c.bgBlue(c.bold(" TS "))}`;
293
+ if (testModule.project.name) title += ` ${formatProjectName(testModule.project, "")}`;
371
294
  return ` ${title} ${testModule.task.name} ${suffix}`;
372
295
  }
373
- printTestSuite(_suite) {}
296
+ printTestSuite(_suite) {
297
+ // Suite name is included in getTestName by default
298
+ }
374
299
  getTestName(test, separator) {
375
300
  return getTestName(test, separator);
376
301
  }
@@ -380,33 +305,37 @@ class BaseReporter {
380
305
  getTestIndentation(_test) {
381
306
  return " ";
382
307
  }
308
+ printAnnotations(test, console, padding = 0) {
309
+ const annotations = test.annotations();
310
+ if (!annotations.length) return;
311
+ const PADDING = " ".repeat(padding);
312
+ annotations.forEach(({ location, type, message }) => {
313
+ if (location) {
314
+ const file = relative(test.project.config.root, location.file);
315
+ this[console](`${PADDING}${c.blue(F_POINTER)} ${c.gray(`${file}:${location.line}:${location.column}`)} ${c.bold(type)}`);
316
+ } else this[console](`${PADDING}${c.blue(F_POINTER)} ${c.bold(type)}`);
317
+ this[console](`${PADDING} ${c.blue(F_DOWN_RIGHT)} ${message}`);
318
+ });
319
+ }
383
320
  getDurationPrefix(task) {
384
- if (!task.result?.duration) {
385
- return "";
386
- }
321
+ if (!task.result?.duration) return "";
387
322
  const color = task.result.duration > this.ctx.config.slowTestThreshold ? c.yellow : c.green;
388
323
  return color(` ${Math.round(task.result.duration)}${c.dim("ms")}`);
389
324
  }
390
325
  onWatcherStart(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
391
326
  const failed = errors.length > 0 || hasFailed(files);
392
- if (failed) {
393
- this.log(withLabel("red", "FAIL", "Tests failed. Watching for file changes..."));
394
- } else if (this.ctx.isCancelling) {
395
- this.log(withLabel("red", "CANCELLED", "Test run cancelled. Watching for file changes..."));
396
- } else {
397
- this.log(withLabel("green", "PASS", "Waiting for file changes..."));
398
- }
327
+ if (failed) this.log(withLabel("red", "FAIL", "Tests failed. Watching for file changes..."));
328
+ else if (this.ctx.isCancelling) this.log(withLabel("red", "CANCELLED", "Test run cancelled. Watching for file changes..."));
329
+ else this.log(withLabel("green", "PASS", "Waiting for file changes..."));
399
330
  const hints = [c.dim("press ") + c.bold("h") + c.dim(" to show help")];
400
- if (hasFailedSnapshot(files)) {
401
- hints.unshift(c.dim("press ") + c.bold(c.yellow("u")) + c.dim(" to update snapshot"));
402
- } else {
403
- hints.push(c.dim("press ") + c.bold("q") + c.dim(" to quit"));
404
- }
331
+ if (hasFailedSnapshot(files)) hints.unshift(c.dim("press ") + c.bold(c.yellow("u")) + c.dim(" to update snapshot"));
332
+ else hints.push(c.dim("press ") + c.bold("q") + c.dim(" to quit"));
405
333
  this.log(BADGE_PADDING + hints.join(c.dim(", ")));
406
334
  }
407
335
  onWatcherRerun(files, trigger) {
408
336
  this.watchFilters = files;
409
337
  this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter((testModule) => !files.includes(testModule.task.filepath) && testModule.state() === "failed");
338
+ // Update re-run count for each file
410
339
  files.forEach((filepath) => {
411
340
  let reruns = this._filesInWatchMode.get(filepath) ?? 0;
412
341
  this._filesInWatchMode.set(filepath, ++reruns);
@@ -418,40 +347,26 @@ class BaseReporter {
418
347
  }
419
348
  this.ctx.logger.clearFullScreen();
420
349
  this.log(withLabel("blue", "RERUN", banner));
421
- if (this.ctx.configOverride.project) {
422
- this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
423
- }
424
- if (this.ctx.filenamePattern) {
425
- this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
426
- }
427
- if (this.ctx.configOverride.testNamePattern) {
428
- this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
429
- }
350
+ if (this.ctx.configOverride.project) this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
351
+ if (this.ctx.filenamePattern) this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
352
+ if (this.ctx.configOverride.testNamePattern) this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
430
353
  this.log("");
431
- for (const testModule of this.failedUnwatchedFiles) {
432
- this.printTestModule(testModule);
433
- }
434
- this._timeStart = formatTimeString(new Date());
354
+ for (const testModule of this.failedUnwatchedFiles) this.printTestModule(testModule);
355
+ this._timeStart = formatTimeString(/* @__PURE__ */ new Date());
435
356
  this.start = performance$1.now();
436
357
  }
437
358
  onUserConsoleLog(log, taskState) {
438
- if (!this.shouldLog(log, taskState)) {
439
- return;
440
- }
359
+ if (!this.shouldLog(log, taskState)) return;
441
360
  const output = log.type === "stdout" ? this.ctx.logger.outputStream : this.ctx.logger.errorStream;
442
361
  const write = (msg) => output.write(msg);
443
362
  let headerText = "unknown test";
444
- const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : undefined;
445
- if (task) {
446
- headerText = getFullName(task, c.dim(" > "));
447
- } else if (log.taskId && log.taskId !== "__vitest__unknown_test__") {
448
- headerText = log.taskId;
449
- }
363
+ const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : void 0;
364
+ if (task) headerText = getFullName(task, c.dim(" > "));
365
+ else if (log.taskId && log.taskId !== "__vitest__unknown_test__") headerText = log.taskId;
450
366
  write(c.gray(log.type + c.dim(` | ${headerText}\n`)) + log.content);
451
367
  if (log.origin) {
452
- if (log.browser) {
453
- write("\n");
454
- }
368
+ // browser logs don't have an extra end of line at the end like Node.js does
369
+ if (log.browser) write("\n");
455
370
  const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
456
371
  const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
457
372
  const highlight = task && stack.find((i) => i.file === task.file.filepath);
@@ -468,16 +383,10 @@ class BaseReporter {
468
383
  this.log(c.yellow("Test removed...") + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ""));
469
384
  }
470
385
  shouldLog(log, taskState) {
471
- if (this.ctx.config.silent === true) {
472
- return false;
473
- }
474
- if (this.ctx.config.silent === "passed-only" && taskState !== "failed") {
475
- return false;
476
- }
386
+ if (this.ctx.config.silent === true) return false;
387
+ if (this.ctx.config.silent === "passed-only" && taskState !== "failed") return false;
477
388
  const shouldLog = this.ctx.config.onConsoleLog?.(log.content, log.type);
478
- if (shouldLog === false) {
479
- return shouldLog;
480
- }
389
+ if (shouldLog === false) return shouldLog;
481
390
  return true;
482
391
  }
483
392
  onServerRestart(reason) {
@@ -485,11 +394,8 @@ class BaseReporter {
485
394
  }
486
395
  reportSummary(files, errors) {
487
396
  this.printErrorsSummary(files, errors);
488
- if (this.ctx.config.mode === "benchmark") {
489
- this.reportBenchmarkSummary(files);
490
- } else {
491
- this.reportTestSummary(files, errors);
492
- }
397
+ if (this.ctx.config.mode === "benchmark") this.reportBenchmarkSummary(files);
398
+ else this.reportTestSummary(files, errors);
493
399
  }
494
400
  reportTestSummary(files, errors) {
495
401
  this.log();
@@ -500,26 +406,22 @@ class BaseReporter {
500
406
  const title = index === 0 ? "Snapshots" : "";
501
407
  this.log(`${padSummaryTitle(title)} ${snapshot}`);
502
408
  }
503
- if (snapshotOutput.length > 1) {
504
- this.log();
505
- }
409
+ if (snapshotOutput.length > 1) this.log();
506
410
  this.log(padSummaryTitle("Test Files"), getStateString$1(affectedFiles));
507
411
  this.log(padSummaryTitle("Tests"), getStateString$1(tests));
508
412
  if (this.ctx.projects.some((c) => c.config.typecheck.enabled)) {
509
413
  const failed = tests.filter((t) => t.meta?.typecheck && t.result?.errors?.length);
510
414
  this.log(padSummaryTitle("Type Errors"), failed.length ? c.bold(c.red(`${failed.length} failed`)) : c.dim("no errors"));
511
415
  }
512
- if (errors.length) {
513
- this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
514
- }
416
+ if (errors.length) this.log(padSummaryTitle("Errors"), c.bold(c.red(`${errors.length} error${errors.length > 1 ? "s" : ""}`)));
515
417
  this.log(padSummaryTitle("Start at"), this._timeStart);
516
418
  const collectTime = sum(files, (file) => file.collectDuration);
517
419
  const testsTime = sum(files, (file) => file.result?.duration);
518
420
  const setupTime = sum(files, (file) => file.setupDuration);
519
- if (this.watchFilters) {
520
- this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
521
- } else {
421
+ if (this.watchFilters) this.log(padSummaryTitle("Duration"), formatTime(collectTime + testsTime + setupTime));
422
+ else {
522
423
  const blobs = this.ctx.state.blobs;
424
+ // Execution time is either sum of all runs of `--merge-reports` or the current run's time
523
425
  const executionTime = blobs?.executionTimes ? sum(blobs.executionTimes, (time) => time) : this.end - this.start;
524
426
  const environmentTime = sum(files, (file) => file.environmentLoad);
525
427
  const prepareTime = sum(files, (file) => file.prepareDuration);
@@ -535,9 +437,7 @@ class BaseReporter {
535
437
  typecheck && `typecheck ${formatTime(typecheck)}`
536
438
  ].filter(Boolean).join(", ");
537
439
  this.log(padSummaryTitle("Duration"), formatTime(executionTime) + c.dim(` (${timers})`));
538
- if (blobs?.executionTimes) {
539
- this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
540
- }
440
+ if (blobs?.executionTimes) this.log(padSummaryTitle("Per blob") + blobs.executionTimes.map((time) => ` ${formatTime(time)}`).join(""));
541
441
  }
542
442
  this.log();
543
443
  }
@@ -548,7 +448,7 @@ class BaseReporter {
548
448
  const failedTests = tests.filter((i) => i.result?.state === "fail");
549
449
  const failedTotal = countTestErrors(failedSuites) + countTestErrors(failedTests);
550
450
  let current = 1;
551
- const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, undefined, 1)))}\n`);
451
+ const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}\n`);
552
452
  if (failedSuites.length) {
553
453
  this.error(`\n${errorBanner(`Failed Suites ${failedSuites.length}`)}\n`);
554
454
  this.printTaskErrors(failedSuites, errorDivider);
@@ -568,9 +468,7 @@ class BaseReporter {
568
468
  this.log(`\n${withLabel("cyan", "BENCH", "Summary\n")}`);
569
469
  for (const bench of topBenches) {
570
470
  const group = bench.suite || bench.file;
571
- if (!group) {
572
- continue;
573
- }
471
+ if (!group) continue;
574
472
  const groupName = getFullName(group, c.dim(" > "));
575
473
  const project = this.ctx.projects.find((p) => p.name === bench.file.projectName);
576
474
  this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`);
@@ -584,35 +482,28 @@ class BaseReporter {
584
482
  }
585
483
  printTaskErrors(tasks, errorDivider) {
586
484
  const errorsQueue = [];
587
- for (const task of tasks) {
588
- task.result?.errors?.forEach((error) => {
589
- let previous;
590
- if (error?.stackStr) {
591
- previous = errorsQueue.find((i) => {
592
- if (i[0]?.stackStr !== error.stackStr) {
593
- return false;
594
- }
595
- const currentProjectName = task?.projectName || task.file?.projectName || "";
596
- const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
597
- return projectName === currentProjectName;
598
- });
599
- }
600
- if (previous) {
601
- previous[1].push(task);
602
- } else {
603
- errorsQueue.push([error, [task]]);
604
- }
485
+ for (const task of tasks)
486
+ // Merge identical errors
487
+ task.result?.errors?.forEach((error) => {
488
+ let previous;
489
+ if (error?.stack) previous = errorsQueue.find((i) => {
490
+ if (i[0]?.stack !== error.stack) return false;
491
+ const currentProjectName = task?.projectName || task.file?.projectName || "";
492
+ const projectName = i[1][0]?.projectName || i[1][0].file?.projectName || "";
493
+ const currentAnnotations = task.type === "test" && task.annotations;
494
+ const itemAnnotations = i[1][0].type === "test" && i[1][0].annotations;
495
+ return projectName === currentProjectName && deepEqual(currentAnnotations, itemAnnotations);
605
496
  });
606
- }
497
+ if (previous) previous[1].push(task);
498
+ else errorsQueue.push([error, [task]]);
499
+ });
607
500
  for (const [error, tasks] of errorsQueue) {
608
501
  for (const task of tasks) {
609
502
  const filepath = task?.filepath || "";
610
503
  const projectName = task?.projectName || task.file?.projectName || "";
611
504
  const project = this.ctx.projects.find((p) => p.name === projectName);
612
505
  let name = getFullName(task, c.dim(" > "));
613
- if (filepath) {
614
- name += c.dim(` [ ${this.relative(filepath)} ]`);
615
- }
506
+ if (filepath) name += c.dim(` [ ${this.relative(filepath)} ]`);
616
507
  this.ctx.logger.error(`${c.bgRed(c.bold(" FAIL "))} ${formatProjectName(project)}${name}`);
617
508
  }
618
509
  const screenshotPaths = tasks.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
@@ -622,10 +513,24 @@ class BaseReporter {
622
513
  screenshotPaths,
623
514
  task: tasks[0]
624
515
  });
516
+ if (tasks[0].type === "test" && tasks[0].annotations.length) {
517
+ const test = this.ctx.state.getReportedEntity(tasks[0]);
518
+ this.printAnnotations(test, "error", 1);
519
+ this.error();
520
+ }
625
521
  errorDivider();
626
522
  }
627
523
  }
628
524
  }
525
+ function deepEqual(a, b) {
526
+ if (a === b) return true;
527
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
528
+ const keysA = Object.keys(a);
529
+ const keysB = Object.keys(b);
530
+ if (keysA.length !== keysB.length) return false;
531
+ for (const key of keysA) if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
532
+ return true;
533
+ }
629
534
  function sum(items, cb) {
630
535
  return items.reduce((total, next) => {
631
536
  return total + Math.max(cb(next) || 0, 0);
@@ -639,9 +544,10 @@ class BasicReporter extends BaseReporter {
639
544
  }
640
545
  onInit(ctx) {
641
546
  super.onInit(ctx);
642
- ctx.logger.deprecate(`'basic' reporter is deprecated and will be removed in Vitest v3.\n` + `Remove 'basic' from 'reporters' option. To match 'basic' reporter 100%, use configuration:\n${JSON.stringify({ test: { reporters: [["default", { summary: false }]] } }, null, 2)}`);
547
+ ctx.logger.deprecate(`'basic' reporter is deprecated and will be removed in Vitest v3.\nRemove 'basic' from 'reporters' option. To match 'basic' reporter 100%, use configuration:\n${JSON.stringify({ test: { reporters: [["default", { summary: false }]] } }, null, 2)}`);
643
548
  }
644
549
  reportSummary(files, errors) {
550
+ // non-tty mode doesn't add a new line
645
551
  this.ctx.logger.log();
646
552
  return super.reportSummary(files, errors);
647
553
  }
@@ -762,9 +668,7 @@ class BlobReporter {
762
668
  this.options = options;
763
669
  }
764
670
  onInit(ctx) {
765
- if (ctx.config.watch) {
766
- throw new Error("Blob reporter is not supported in watch mode");
767
- }
671
+ if (ctx.config.watch) throw new Error("Blob reporter is not supported in watch mode");
768
672
  this.ctx = ctx;
769
673
  this.start = performance.now();
770
674
  }
@@ -777,9 +681,7 @@ class BlobReporter {
777
681
  }
778
682
  const modules = this.ctx.projects.map((project) => {
779
683
  return [project.name, [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
780
- if (!mod[1].file) {
781
- return null;
782
- }
684
+ if (!mod[1].file) return null;
783
685
  return [
784
686
  mod[0],
785
687
  mod[1].file,
@@ -803,25 +705,20 @@ class BlobReporter {
803
705
  async function writeBlob(content, filename) {
804
706
  const report = stringify(content);
805
707
  const dir = dirname(filename);
806
- if (!existsSync(dir)) {
807
- await mkdir(dir, { recursive: true });
808
- }
708
+ if (!existsSync(dir)) await mkdir(dir, { recursive: true });
809
709
  await writeFile(filename, report, "utf-8");
810
710
  }
811
711
  async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
712
+ // using process.cwd() because --merge-reports can only be used in CLI
812
713
  const resolvedDir = resolve(process.cwd(), blobsDirectory);
813
714
  const blobsFiles = await readdir(resolvedDir);
814
715
  const promises = blobsFiles.map(async (filename) => {
815
716
  const fullPath = resolve(resolvedDir, filename);
816
717
  const stats = await stat(fullPath);
817
- if (!stats.isFile()) {
818
- throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`);
819
- }
718
+ 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`);
820
719
  const content = await readFile(fullPath, "utf-8");
821
720
  const [version, files, errors, moduleKeys, coverage, executionTime] = parse(content);
822
- if (!version) {
823
- 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`);
824
- }
721
+ 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`);
825
722
  return {
826
723
  version,
827
724
  files,
@@ -833,23 +730,16 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
833
730
  };
834
731
  });
835
732
  const blobs = await Promise.all(promises);
836
- if (!blobs.length) {
837
- throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
838
- }
733
+ if (!blobs.length) throw new Error(`vitest.mergeReports() requires at least one blob file in "${blobsDirectory}" directory, but none were found`);
839
734
  const versions = new Set(blobs.map((blob) => blob.version));
840
- if (versions.size > 1) {
841
- 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")}`);
842
- }
843
- if (!versions.has(currentVersion)) {
844
- throw new Error(`the blobs in "${blobsDirectory}" were generated by a different version of Vitest. Expected v${currentVersion}, but received v${blobs[0].version}`);
845
- }
735
+ 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")}`);
736
+ if (!versions.has(currentVersion)) throw new Error(`the blobs in "${blobsDirectory}" were generated by a different version of Vitest. Expected v${currentVersion}, but received v${blobs[0].version}`);
737
+ // fake module graph - it is used to check if module is imported, but we don't use values inside
846
738
  const projects = Object.fromEntries(projectsArray.map((p) => [p.name, p]));
847
739
  blobs.forEach((blob) => {
848
740
  blob.moduleKeys.forEach(([projectName, moduleIds]) => {
849
741
  const project = projects[projectName];
850
- if (!project) {
851
- return;
852
- }
742
+ if (!project) return;
853
743
  moduleIds.forEach(([moduleId, file, url]) => {
854
744
  const moduleNode = project.vite.moduleGraph.createFileOnlyEntry(file);
855
745
  moduleNode.url = url;
@@ -888,7 +778,7 @@ class WindowRenderer {
888
778
  options;
889
779
  streams;
890
780
  buffer = [];
891
- renderInterval = undefined;
781
+ renderInterval = void 0;
892
782
  renderScheduled = false;
893
783
  windowHeight = 0;
894
784
  finished = false;
@@ -903,6 +793,7 @@ class WindowRenderer {
903
793
  error: options.logger.errorStream.write.bind(options.logger.errorStream)
904
794
  };
905
795
  this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error"));
796
+ // Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
906
797
  this.options.logger.onTerminalCleanup(() => {
907
798
  this.flushBuffer();
908
799
  this.stop();
@@ -939,10 +830,9 @@ class WindowRenderer {
939
830
  }
940
831
  }
941
832
  flushBuffer() {
942
- if (this.buffer.length === 0) {
943
- return this.render();
944
- }
833
+ if (this.buffer.length === 0) return this.render();
945
834
  let current;
835
+ // Concatenate same types into a single render
946
836
  for (const next of this.buffer.splice(0)) {
947
837
  if (!current) {
948
838
  current = next;
@@ -955,9 +845,7 @@ class WindowRenderer {
955
845
  }
956
846
  current.message += next.message;
957
847
  }
958
- if (current) {
959
- this.render(current?.message, current?.type);
960
- }
848
+ if (current) this.render(current?.message, current?.type);
961
849
  }
962
850
  render(message, type = "output") {
963
851
  if (this.finished) {
@@ -967,44 +855,30 @@ class WindowRenderer {
967
855
  const windowContent = this.options.getWindow();
968
856
  const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
969
857
  let padding = this.windowHeight - rowCount;
970
- if (padding > 0 && message) {
971
- padding -= getRenderedRowCount([message], this.options.logger.getColumns());
972
- }
858
+ if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
973
859
  this.write(SYNC_START);
974
860
  this.clearWindow();
975
- if (message) {
976
- this.write(message, type);
977
- }
978
- if (padding > 0) {
979
- this.write("\n".repeat(padding));
980
- }
861
+ if (message) this.write(message, type);
862
+ if (padding > 0) this.write("\n".repeat(padding));
981
863
  this.write(windowContent.join("\n"));
982
864
  this.write(SYNC_END);
983
865
  this.windowHeight = rowCount + Math.max(0, padding);
984
866
  }
985
867
  clearWindow() {
986
- if (this.windowHeight === 0) {
987
- return;
988
- }
868
+ if (this.windowHeight === 0) return;
989
869
  this.write(CLEAR_LINE);
990
- for (let i = 1; i < this.windowHeight; i++) {
991
- this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
992
- }
870
+ for (let i = 1; i < this.windowHeight; i++) this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
993
871
  this.windowHeight = 0;
994
872
  }
995
873
  interceptStream(stream, type) {
996
874
  const original = stream.write;
875
+ // @ts-expect-error -- not sure how 2 overloads should be typed
997
876
  stream.write = (chunk, _, callback) => {
998
- if (chunk) {
999
- if (this.finished) {
1000
- this.write(chunk.toString(), type);
1001
- } else {
1002
- this.buffer.push({
1003
- type,
1004
- message: chunk.toString()
1005
- });
1006
- }
1007
- }
877
+ if (chunk) if (this.finished) this.write(chunk.toString(), type);
878
+ else this.buffer.push({
879
+ type,
880
+ message: chunk.toString()
881
+ });
1008
882
  callback?.();
1009
883
  };
1010
884
  return function restore() {
@@ -1039,13 +913,13 @@ class SummaryReporter {
1039
913
  tests = emptyCounters();
1040
914
  maxParallelTests = 0;
1041
915
  /** Currently running test modules, may include finished test modules too */
1042
- runningModules = new Map();
916
+ runningModules = /* @__PURE__ */ new Map();
1043
917
  /** ID of finished `this.runningModules` that are currently being shown */
1044
- finishedModules = new Map();
918
+ finishedModules = /* @__PURE__ */ new Map();
1045
919
  startTime = "";
1046
920
  currentTime = 0;
1047
921
  duration = 0;
1048
- durationInterval = undefined;
922
+ durationInterval = void 0;
1049
923
  onInit(ctx, options = {}) {
1050
924
  this.ctx = ctx;
1051
925
  this.options = {
@@ -1077,6 +951,7 @@ class SummaryReporter {
1077
951
  clearInterval(this.durationInterval);
1078
952
  }
1079
953
  onTestModuleQueued(module) {
954
+ // When new test module starts, take the place of previously finished test module, if any
1080
955
  if (this.finishedModules.size) {
1081
956
  const finished = this.finishedModules.keys().next().value;
1082
957
  this.removeTestModule(finished);
@@ -1098,9 +973,7 @@ class SummaryReporter {
1098
973
  }
1099
974
  onHookStart(options) {
1100
975
  const stats = this.getHookStats(options);
1101
- if (!stats) {
1102
- return;
1103
- }
976
+ if (!stats) return;
1104
977
  const hook = {
1105
978
  name: options.name,
1106
979
  visible: false,
@@ -1116,20 +989,15 @@ class SummaryReporter {
1116
989
  }
1117
990
  onHookEnd(options) {
1118
991
  const stats = this.getHookStats(options);
1119
- if (stats?.hook?.name !== options.name) {
1120
- return;
1121
- }
992
+ if (stats?.hook?.name !== options.name) return;
1122
993
  stats.hook.onFinish();
1123
994
  stats.hook.visible = false;
1124
995
  }
1125
996
  onTestCaseReady(test) {
1126
- if (!this.options.verbose) {
1127
- return;
1128
- }
997
+ // Track slow running tests only on verbose mode
998
+ if (!this.options.verbose) return;
1129
999
  const stats = this.runningModules.get(test.module.id);
1130
- if (!stats || stats.tests.has(test.id)) {
1131
- return;
1132
- }
1000
+ if (!stats || stats.tests.has(test.id)) return;
1133
1001
  const slowTest = {
1134
1002
  name: test.name,
1135
1003
  visible: false,
@@ -1147,53 +1015,42 @@ class SummaryReporter {
1147
1015
  }
1148
1016
  onTestCaseResult(test) {
1149
1017
  const stats = this.runningModules.get(test.module.id);
1150
- if (!stats) {
1151
- return;
1152
- }
1018
+ if (!stats) return;
1153
1019
  stats.tests.get(test.id)?.onFinish();
1154
1020
  stats.tests.delete(test.id);
1155
1021
  stats.completed++;
1156
1022
  const result = test.result();
1157
- if (result?.state === "passed") {
1158
- this.tests.passed++;
1159
- } else if (result?.state === "failed") {
1160
- this.tests.failed++;
1161
- } else if (!result?.state || result?.state === "skipped") {
1162
- this.tests.skipped++;
1163
- }
1023
+ if (result?.state === "passed") this.tests.passed++;
1024
+ else if (result?.state === "failed") this.tests.failed++;
1025
+ else if (!result?.state || result?.state === "skipped") this.tests.skipped++;
1164
1026
  this.renderer.schedule();
1165
1027
  }
1166
1028
  onTestModuleEnd(module) {
1167
1029
  const state = module.state();
1168
1030
  this.modules.completed++;
1169
- if (state === "passed") {
1170
- this.modules.passed++;
1171
- } else if (state === "failed") {
1172
- this.modules.failed++;
1173
- } else if (module.task.mode === "todo" && state === "skipped") {
1174
- this.modules.todo++;
1175
- } else if (state === "skipped") {
1176
- this.modules.skipped++;
1177
- }
1031
+ if (state === "passed") this.modules.passed++;
1032
+ else if (state === "failed") this.modules.failed++;
1033
+ else if (module.task.mode === "todo" && state === "skipped") this.modules.todo++;
1034
+ else if (state === "skipped") this.modules.skipped++;
1178
1035
  const left = this.modules.total - this.modules.completed;
1179
- if (left > this.maxParallelTests) {
1180
- this.finishedModules.set(module.id, setTimeout(() => {
1181
- this.removeTestModule(module.id);
1182
- }, FINISHED_TEST_CLEANUP_TIME_MS).unref());
1183
- } else {
1036
+ // Keep finished tests visible in summary for a while if there are more tests left.
1037
+ // When a new test starts in onTestModuleQueued it will take this ones place.
1038
+ // This reduces flickering by making summary more stable.
1039
+ if (left > this.maxParallelTests) this.finishedModules.set(module.id, setTimeout(() => {
1184
1040
  this.removeTestModule(module.id);
1185
- }
1041
+ }, FINISHED_TEST_CLEANUP_TIME_MS).unref());
1042
+ else
1043
+ // Run is about to end as there are less tests left than whole run had parallel at max.
1044
+ // Remove finished test immediately.
1045
+ this.removeTestModule(module.id);
1186
1046
  this.renderer.schedule();
1187
1047
  }
1188
1048
  getHookStats({ entity }) {
1189
- if (!this.options.verbose) {
1190
- return;
1191
- }
1049
+ // Track slow running hooks only on verbose mode
1050
+ if (!this.options.verbose) return;
1192
1051
  const module = entity.type === "module" ? entity : entity.module;
1193
1052
  const stats = this.runningModules.get(module.id);
1194
- if (!stats) {
1195
- return;
1196
- }
1053
+ if (!stats) return;
1197
1054
  return entity.type === "test" ? stats.tests.get(entity.id) : stats;
1198
1055
  }
1199
1056
  createSummary() {
@@ -1209,14 +1066,10 @@ class SummaryReporter {
1209
1066
  const elapsed = this.currentTime - task.startTime;
1210
1067
  const icon = index === slowTasks.length - 1 ? F_TREE_NODE_END : F_TREE_NODE_MIDDLE;
1211
1068
  summary.push(c.bold(c.yellow(` ${icon} `)) + task.name + c.bold(c.yellow(` ${formatTime(Math.max(0, elapsed))}`)));
1212
- if (task.hook?.visible) {
1213
- summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
1214
- }
1069
+ if (task.hook?.visible) summary.push(c.bold(c.yellow(` ${F_TREE_NODE_END} `)) + task.hook.name);
1215
1070
  }
1216
1071
  }
1217
- if (this.runningModules.size > 0) {
1218
- summary.push("");
1219
- }
1072
+ if (this.runningModules.size > 0) summary.push("");
1220
1073
  summary.push(padSummaryTitle("Test Files") + getStateString(this.modules));
1221
1074
  summary.push(padSummaryTitle("Tests") + getStateString(this.tests));
1222
1075
  summary.push(padSummaryTitle("Start at") + this.startTime);
@@ -1226,16 +1079,14 @@ class SummaryReporter {
1226
1079
  }
1227
1080
  startTimers() {
1228
1081
  const start = performance.now();
1229
- this.startTime = formatTimeString(new Date());
1082
+ this.startTime = formatTimeString(/* @__PURE__ */ new Date());
1230
1083
  this.durationInterval = setInterval(() => {
1231
1084
  this.currentTime = performance.now();
1232
1085
  this.duration = this.currentTime - start;
1233
1086
  }, DURATION_UPDATE_INTERVAL_MS).unref();
1234
1087
  }
1235
1088
  removeTestModule(id) {
1236
- if (!id) {
1237
- return;
1238
- }
1089
+ if (!id) return;
1239
1090
  const testFile = this.runningModules.get(id);
1240
1091
  testFile?.hook?.onFinish();
1241
1092
  testFile?.tests?.forEach((test) => test.onFinish());
@@ -1263,12 +1114,8 @@ function getStateString(entry) {
1263
1114
  ].filter(Boolean).join(c.dim(" | ")) + c.gray(` (${entry.total})`);
1264
1115
  }
1265
1116
  function sortRunningModules(a, b) {
1266
- if ((a.projectName || "") > (b.projectName || "")) {
1267
- return 1;
1268
- }
1269
- if ((a.projectName || "") < (b.projectName || "")) {
1270
- return -1;
1271
- }
1117
+ if ((a.projectName || "") > (b.projectName || "")) return 1;
1118
+ if ((a.projectName || "") < (b.projectName || "")) return -1;
1272
1119
  return a.filename.localeCompare(b.filename);
1273
1120
  }
1274
1121
  function initializeStats(module) {
@@ -1278,7 +1125,7 @@ function initializeStats(module) {
1278
1125
  filename: module.task.name,
1279
1126
  projectName: module.project.name,
1280
1127
  projectColor: module.project.color,
1281
- tests: new Map(),
1128
+ tests: /* @__PURE__ */ new Map(),
1282
1129
  typecheck: !!module.task.meta.typecheck
1283
1130
  };
1284
1131
  }
@@ -1292,14 +1139,14 @@ class DefaultReporter extends BaseReporter {
1292
1139
  summary: true,
1293
1140
  ...options
1294
1141
  };
1295
- if (!this.isTTY) {
1296
- this.options.summary = false;
1297
- }
1298
- if (this.options.summary) {
1299
- this.summary = new SummaryReporter();
1300
- }
1142
+ if (!this.isTTY) this.options.summary = false;
1143
+ if (this.options.summary) this.summary = new SummaryReporter();
1301
1144
  }
1302
1145
  onTestRunStart(specifications) {
1146
+ if (this.isTTY) {
1147
+ if (this.renderSucceed === void 0) this.renderSucceed = !!this.renderSucceed;
1148
+ if (this.renderSucceed !== true) this.renderSucceed = specifications.length <= 1;
1149
+ }
1303
1150
  this.summary?.onTestRunStart(specifications);
1304
1151
  }
1305
1152
  onTestModuleQueued(file) {
@@ -1329,16 +1176,6 @@ class DefaultReporter extends BaseReporter {
1329
1176
  super.onInit(ctx);
1330
1177
  this.summary?.onInit(ctx, { verbose: this.verbose });
1331
1178
  }
1332
- onPathsCollected(paths = []) {
1333
- if (this.isTTY) {
1334
- if (this.renderSucceed === undefined) {
1335
- this.renderSucceed = !!this.renderSucceed;
1336
- }
1337
- if (this.renderSucceed !== true) {
1338
- this.renderSucceed = paths.length <= 1;
1339
- }
1340
- }
1341
- }
1342
1179
  onTestRunEnd() {
1343
1180
  this.summary?.onTestRunEnd();
1344
1181
  }
@@ -1346,8 +1183,8 @@ class DefaultReporter extends BaseReporter {
1346
1183
 
1347
1184
  class DotReporter extends BaseReporter {
1348
1185
  renderer;
1349
- tests = new Map();
1350
- finishedTests = new Set();
1186
+ tests = /* @__PURE__ */ new Map();
1187
+ finishedTests = /* @__PURE__ */ new Set();
1351
1188
  onInit(ctx) {
1352
1189
  super.onInit(ctx);
1353
1190
  if (this.isTTY) {
@@ -1359,9 +1196,7 @@ class DotReporter extends BaseReporter {
1359
1196
  }
1360
1197
  }
1361
1198
  printTestModule(testModule) {
1362
- if (!this.isTTY) {
1363
- super.printTestModule(testModule);
1364
- }
1199
+ if (!this.isTTY) super.printTestModule(testModule);
1365
1200
  }
1366
1201
  onWatcherRerun(files, trigger) {
1367
1202
  this.tests.clear();
@@ -1378,14 +1213,12 @@ class DotReporter extends BaseReporter {
1378
1213
  super.onFinished(files, errors);
1379
1214
  }
1380
1215
  onTestModuleCollected(module) {
1381
- for (const test of module.children.allTests()) {
1382
- this.onTestCaseReady(test);
1383
- }
1216
+ for (const test of module.children.allTests())
1217
+ // Dot reporter marks pending tests as running
1218
+ this.onTestCaseReady(test);
1384
1219
  }
1385
1220
  onTestCaseReady(test) {
1386
- if (this.finishedTests.has(test.id)) {
1387
- return;
1388
- }
1221
+ if (this.finishedTests.has(test.id)) return;
1389
1222
  this.tests.set(test.id, test.result().state || "run");
1390
1223
  this.renderer?.schedule();
1391
1224
  }
@@ -1397,23 +1230,16 @@ class DotReporter extends BaseReporter {
1397
1230
  }
1398
1231
  onTestModuleEnd(testModule) {
1399
1232
  super.onTestModuleEnd(testModule);
1400
- if (!this.isTTY) {
1401
- return;
1402
- }
1233
+ if (!this.isTTY) return;
1403
1234
  const columns = this.ctx.logger.getColumns();
1404
- if (this.tests.size < columns) {
1405
- return;
1406
- }
1235
+ if (this.tests.size < columns) return;
1407
1236
  const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "pending");
1408
- if (finishedTests.length < columns) {
1409
- return;
1410
- }
1237
+ if (finishedTests.length < columns) return;
1238
+ // Remove finished tests from state and render them in static output
1411
1239
  const states = [];
1412
1240
  let count = 0;
1413
1241
  for (const [id, state] of finishedTests) {
1414
- if (count++ >= columns) {
1415
- break;
1416
- }
1242
+ if (count++ >= columns) break;
1417
1243
  this.tests.delete(id);
1418
1244
  states.push(state);
1419
1245
  }
@@ -1424,6 +1250,7 @@ class DotReporter extends BaseReporter {
1424
1250
  return [formatTests(Array.from(this.tests.values())), ""];
1425
1251
  }
1426
1252
  }
1253
+ // These are compared with reference equality in formatTests
1427
1254
  const pass = {
1428
1255
  char: "·",
1429
1256
  color: c.green
@@ -1463,6 +1290,7 @@ function formatTests(states) {
1463
1290
  continue;
1464
1291
  }
1465
1292
  output += currentIcon.color(currentIcon.char.repeat(count));
1293
+ // Start tracking new group
1466
1294
  count = 1;
1467
1295
  currentIcon = icon;
1468
1296
  }
@@ -1470,6 +1298,7 @@ function formatTests(states) {
1470
1298
  return output;
1471
1299
  }
1472
1300
 
1301
+ // use Logger with custom Console to capture entire error printing
1473
1302
  function capturePrintError(error, ctx, options) {
1474
1303
  let output = "";
1475
1304
  const writable = new Writable({ write(chunk, _encoding, callback) {
@@ -1499,12 +1328,13 @@ function printError(error, ctx, logger, options) {
1499
1328
  screenshotPaths: options.screenshotPaths,
1500
1329
  printProperties: options.verbose,
1501
1330
  parseErrorStacktrace(error) {
1502
- if (options.task?.file.pool === "browser" && project.browser) {
1503
- return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : undefined });
1504
- }
1331
+ // browser stack trace needs to be processed differently,
1332
+ // so there is a separate method for that
1333
+ if (options.task?.file.pool === "browser" && project.browser) return project.browser.parseErrorStacktrace(error, { ignoreStackEntries: options.fullStack ? [] : void 0 });
1334
+ // node.js stack trace already has correct source map locations
1505
1335
  return parseErrorStacktrace(error, {
1506
1336
  frameFilter: project.config.onStackTrace,
1507
- ignoreStackEntries: options.fullStack ? [] : undefined
1337
+ ignoreStackEntries: options.fullStack ? [] : void 0
1508
1338
  });
1509
1339
  }
1510
1340
  });
@@ -1513,12 +1343,10 @@ function printErrorInner(error, project, options) {
1513
1343
  const { showCodeFrame = true, type, printProperties = true } = options;
1514
1344
  const logger = options.logger;
1515
1345
  let e = error;
1516
- if (isPrimitive(e)) {
1517
- e = {
1518
- message: String(error).split(/\n/g)[0],
1519
- stack: String(error)
1520
- };
1521
- }
1346
+ if (isPrimitive(e)) e = {
1347
+ message: String(error).split(/\n/g)[0],
1348
+ stack: String(error)
1349
+ };
1522
1350
  if (!e) {
1523
1351
  const error = new Error("unknown error");
1524
1352
  e = {
@@ -1526,6 +1354,7 @@ function printErrorInner(error, project, options) {
1526
1354
  stack: error.stack
1527
1355
  };
1528
1356
  }
1357
+ // Error may have occurred even before the configuration was resolved
1529
1358
  if (!project) {
1530
1359
  printErrorMessage(e, logger);
1531
1360
  return;
@@ -1538,22 +1367,17 @@ function printErrorInner(error, project, options) {
1538
1367
  return false;
1539
1368
  }
1540
1369
  });
1541
- if (type) {
1542
- printErrorType(type, project.vitest);
1543
- }
1370
+ if (type) printErrorType(type, project.vitest);
1544
1371
  printErrorMessage(e, logger);
1545
1372
  if (options.screenshotPaths?.length) {
1546
1373
  const length = options.screenshotPaths.length;
1547
1374
  logger.error(`\nFailure screenshot${length > 1 ? "s" : ""}:`);
1548
1375
  logger.error(options.screenshotPaths.map((p) => ` - ${c.dim(relative(process.cwd(), p))}`).join("\n"));
1549
- if (!e.diff) {
1550
- logger.error();
1551
- }
1552
- }
1553
- if (e.codeFrame) {
1554
- logger.error(`${e.codeFrame}\n`);
1376
+ if (!e.diff) logger.error();
1555
1377
  }
1378
+ if (e.codeFrame) logger.error(`${e.codeFrame}\n`);
1556
1379
  if ("__vitest_rollup_error__" in e) {
1380
+ // https://github.com/vitejs/vite/blob/95020ab49e12d143262859e095025cf02423c1d9/packages/vite/src/node/server/middlewares/error.ts#L25-L36
1557
1381
  const err = e.__vitest_rollup_error__;
1558
1382
  logger.error([
1559
1383
  err.plugin && ` Plugin: ${c.magenta(err.plugin)}`,
@@ -1561,12 +1385,11 @@ function printErrorInner(error, project, options) {
1561
1385
  err.frame && c.yellow(err.frame.split(/\r?\n/g).map((l) => ` `.repeat(2) + l).join(`\n`))
1562
1386
  ].filter(Boolean).join("\n"));
1563
1387
  }
1564
- if (e.diff) {
1565
- logger.error(`\n${e.diff}\n`);
1566
- }
1567
- if (e.frame) {
1568
- logger.error(c.yellow(e.frame));
1569
- } else {
1388
+ // E.g. AssertionError from assert does not set showDiff but has both actual and expected properties
1389
+ if (e.diff) logger.error(`\n${e.diff}\n`);
1390
+ // if the error provide the frame
1391
+ if (e.frame) logger.error(c.yellow(e.frame));
1392
+ else {
1570
1393
  const errorProperties = printProperties ? getErrorProperties(e) : {};
1571
1394
  printStack(logger, project, stacks, nearest, errorProperties, (s) => {
1572
1395
  if (showCodeFrame && s === nearest && nearest) {
@@ -1578,15 +1401,12 @@ function printErrorInner(error, project, options) {
1578
1401
  const testPath = e.VITEST_TEST_PATH;
1579
1402
  const testName = e.VITEST_TEST_NAME;
1580
1403
  const afterEnvTeardown = e.VITEST_AFTER_ENV_TEARDOWN;
1581
- if (testPath) {
1582
- 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.`));
1583
- }
1584
- if (testName) {
1585
- logger.error(c.red(`The latest test that might've caused the error is "${c.bold(testName)}". It might mean one of the following:` + "\n- The error was thrown, while Vitest was running this test." + "\n- If the error occurred after the test had been completed, this was the last documented test before it was thrown."));
1586
- }
1587
- if (afterEnvTeardown) {
1588
- 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"));
1589
- }
1404
+ // testName has testPath inside
1405
+ 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.`));
1406
+ 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:
1407
+ - The error was thrown, while Vitest was running this test.
1408
+ - If the error occurred after the test had been completed, this was the last documented test before it was thrown.`));
1409
+ 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"));
1590
1410
  if (typeof e.cause === "object" && e.cause && "name" in e.cause) {
1591
1411
  e.cause.name = `Caused by: ${e.cause.name}`;
1592
1412
  printErrorInner(e.cause, project, {
@@ -1595,18 +1415,15 @@ function printErrorInner(error, project, options) {
1595
1415
  parseErrorStacktrace: options.parseErrorStacktrace
1596
1416
  });
1597
1417
  }
1598
- handleImportOutsideModuleError(e.stack || e.stackStr || "", logger);
1418
+ handleImportOutsideModuleError(e.stack || "", logger);
1599
1419
  return { nearest };
1600
1420
  }
1601
1421
  function printErrorType(type, ctx) {
1602
1422
  ctx.logger.error(`\n${errorBanner(type)}`);
1603
1423
  }
1604
1424
  const skipErrorProperties = new Set([
1605
- "nameStr",
1606
- "stack",
1607
1425
  "cause",
1608
1426
  "stacks",
1609
- "stackStr",
1610
1427
  "type",
1611
1428
  "showDiff",
1612
1429
  "ok",
@@ -1625,41 +1442,35 @@ const skipErrorProperties = new Set([
1625
1442
  "VITEST_TEST_NAME",
1626
1443
  "VITEST_TEST_PATH",
1627
1444
  "VITEST_AFTER_ENV_TEARDOWN",
1445
+ "__vitest_rollup_error__",
1628
1446
  ...Object.getOwnPropertyNames(Error.prototype),
1629
1447
  ...Object.getOwnPropertyNames(Object.prototype)
1630
1448
  ]);
1631
1449
  function getErrorProperties(e) {
1632
1450
  const errorObject = Object.create(null);
1633
- if (e.name === "AssertionError") {
1634
- return errorObject;
1635
- }
1636
- for (const key of Object.getOwnPropertyNames(e)) {
1637
- if (!skipErrorProperties.has(key)) {
1638
- errorObject[key] = e[key];
1639
- }
1640
- }
1451
+ if (e.name === "AssertionError") return errorObject;
1452
+ for (const key of Object.getOwnPropertyNames(e))
1453
+ // print the original stack if it was ever changed manually by the user
1454
+ if (key === "stack" && e[key] != null && typeof e[key] !== "string") errorObject[key] = e[key];
1455
+ else if (key !== "stack" && !skipErrorProperties.has(key)) errorObject[key] = e[key];
1641
1456
  return errorObject;
1642
1457
  }
1643
1458
  const esmErrors = ["Cannot use import statement outside a module", "Unexpected token 'export'"];
1644
1459
  function handleImportOutsideModuleError(stack, logger) {
1645
- if (!esmErrors.some((e) => stack.includes(e))) {
1646
- return;
1647
- }
1460
+ if (!esmErrors.some((e) => stack.includes(e))) return;
1648
1461
  const path = normalize(stack.split("\n")[0].trim());
1649
1462
  let name = path.split("/node_modules/").pop() || "";
1650
- if (name?.startsWith("@")) {
1651
- name = name.split("/").slice(0, 2).join("/");
1652
- } else {
1653
- name = name.split("/")[0];
1654
- }
1655
- if (name) {
1656
- printModuleWarningForPackage(logger, path, name);
1657
- } else {
1658
- printModuleWarningForSourceCode(logger, path);
1659
- }
1463
+ if (name?.startsWith("@")) name = name.split("/").slice(0, 2).join("/");
1464
+ else name = name.split("/")[0];
1465
+ if (name) printModuleWarningForPackage(logger, path, name);
1466
+ else printModuleWarningForSourceCode(logger, path);
1660
1467
  }
1661
1468
  function printModuleWarningForPackage(logger, path, name) {
1662
- logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. ` + `You might want to create an issue to the package ${c.bold(`"${name}"`)} asking ` + "them to ship the file in .mjs extension or add \"type\": \"module\" in their package.json." + "\n\n" + "As a temporary workaround you can try to inline the package by updating your config:" + "\n\n" + c.gray(c.dim("// vitest.config.js")) + "\n" + c.green(`export default {
1469
+ logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. You might want to create an issue to the package ${c.bold(`"${name}"`)} asking them to ship the file in .mjs extension or add "type": "module" in their package.json.
1470
+
1471
+ As a temporary workaround you can try to inline the package by updating your config:
1472
+
1473
+ ` + c.gray(c.dim("// vitest.config.js")) + "\n" + c.green(`export default {
1663
1474
  test: {
1664
1475
  server: {
1665
1476
  deps: {
@@ -1672,19 +1483,18 @@ function printModuleWarningForPackage(logger, path, name) {
1672
1483
  }\n`)));
1673
1484
  }
1674
1485
  function printModuleWarningForSourceCode(logger, path) {
1675
- logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. ` + "To fix this issue, change the file extension to .mjs or add \"type\": \"module\" in your package.json."));
1486
+ logger.error(c.yellow(`Module ${path} seems to be an ES Module but shipped in a CommonJS package. To fix this issue, change the file extension to .mjs or add "type": "module" in your package.json.`));
1676
1487
  }
1677
1488
  function printErrorMessage(error, logger) {
1678
- const errorName = error.name || error.nameStr || "Unknown Error";
1489
+ const errorName = error.name || "Unknown Error";
1679
1490
  if (!error.message) {
1680
1491
  logger.error(error);
1681
1492
  return;
1682
1493
  }
1683
- if (error.message.length > 5e3) {
1684
- logger.error(`${c.red(c.bold(errorName))}: ${error.message}`);
1685
- } else {
1686
- logger.error(c.red(`${c.bold(errorName)}: ${error.message}`));
1687
- }
1494
+ if (error.message.length > 5e3)
1495
+ // Protect against infinite stack trace in tinyrainbow
1496
+ logger.error(`${c.red(c.bold(errorName))}: ${error.message}`);
1497
+ else logger.error(c.red(`${c.bold(errorName)}: ${error.message}`));
1688
1498
  }
1689
1499
  function printStack(logger, project, stack, highlight, errorProperties, onStack) {
1690
1500
  for (const frame of stack) {
@@ -1693,9 +1503,7 @@ function printStack(logger, project, stack, highlight, errorProperties, onStack)
1693
1503
  logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, `${path}:${c.dim(`${frame.line}:${frame.column}`)}`].filter(Boolean).join(" ")}`));
1694
1504
  onStack?.(frame);
1695
1505
  }
1696
- if (stack.length) {
1697
- logger.error();
1698
- }
1506
+ if (stack.length) logger.error();
1699
1507
  if (hasProperties(errorProperties)) {
1700
1508
  logger.error(c.red(c.dim(divider())));
1701
1509
  const propertiesString = inspect(errorProperties);
@@ -1703,9 +1511,8 @@ function printStack(logger, project, stack, highlight, errorProperties, onStack)
1703
1511
  }
1704
1512
  }
1705
1513
  function hasProperties(obj) {
1706
- for (const _key in obj) {
1707
- return true;
1708
- }
1514
+ // eslint-disable-next-line no-unreachable-loop
1515
+ for (const _key in obj) return true;
1709
1516
  return false;
1710
1517
  }
1711
1518
  function generateCodeFrame(source, indent = 0, loc, range = 2) {
@@ -1720,15 +1527,13 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
1720
1527
  count += lines[i].length + nl;
1721
1528
  if (count >= start) {
1722
1529
  for (let j = i - range; j <= i + range || end > count; j++) {
1723
- if (j < 0 || j >= lines.length) {
1724
- continue;
1725
- }
1530
+ if (j < 0 || j >= lines.length) continue;
1726
1531
  const lineLength = lines[j].length;
1727
- if (stripVTControlCharacters(lines[j]).length > 200) {
1728
- return "";
1729
- }
1532
+ // too long, maybe it's a minified file, skip for codeframe
1533
+ if (stripVTControlCharacters(lines[j]).length > 200) return "";
1730
1534
  res.push(lineNo(j + 1) + truncateString(lines[j].replace(/\t/g, " "), columns - 5 - indent));
1731
1535
  if (j === i) {
1536
+ // push underline
1732
1537
  const pad = start - (count - lineLength) + (nl - 1);
1733
1538
  const length = Math.max(1, end > count ? lineLength - pad : end - start);
1734
1539
  res.push(lineNo() + " ".repeat(pad) + c.red("^".repeat(length)));
@@ -1743,9 +1548,7 @@ function generateCodeFrame(source, indent = 0, loc, range = 2) {
1743
1548
  break;
1744
1549
  }
1745
1550
  }
1746
- if (indent) {
1747
- res = res.map((line) => " ".repeat(indent) + line);
1748
- }
1551
+ if (indent) res = res.map((line) => " ".repeat(indent) + line);
1749
1552
  return res.join("\n");
1750
1553
  }
1751
1554
  function lineNo(no = "") {
@@ -1753,50 +1556,64 @@ function lineNo(no = "") {
1753
1556
  }
1754
1557
 
1755
1558
  class GithubActionsReporter {
1756
- ctx = undefined;
1559
+ ctx = void 0;
1560
+ options;
1561
+ constructor(options = {}) {
1562
+ this.options = options;
1563
+ }
1757
1564
  onInit(ctx) {
1758
1565
  this.ctx = ctx;
1759
1566
  }
1567
+ onTestCaseAnnotate(testCase, annotation) {
1568
+ if (!annotation.location) return;
1569
+ const type = getTitle(annotation.type);
1570
+ const formatted = formatMessage({
1571
+ command: getType(annotation.type),
1572
+ properties: {
1573
+ file: annotation.location.file,
1574
+ line: String(annotation.location.line),
1575
+ column: String(annotation.location.column),
1576
+ ...type && { title: type }
1577
+ },
1578
+ message: stripVTControlCharacters(annotation.message)
1579
+ });
1580
+ this.ctx.logger.log(`\n${formatted}`);
1581
+ }
1760
1582
  onFinished(files = [], errors = []) {
1583
+ // collect all errors and associate them with projects
1761
1584
  const projectErrors = new Array();
1762
- for (const error of errors) {
1763
- projectErrors.push({
1764
- project: this.ctx.getRootProject(),
1765
- title: "Unhandled error",
1766
- error
1767
- });
1768
- }
1585
+ for (const error of errors) projectErrors.push({
1586
+ project: this.ctx.getRootProject(),
1587
+ title: "Unhandled error",
1588
+ error
1589
+ });
1769
1590
  for (const file of files) {
1770
1591
  const tasks = getTasks(file);
1771
1592
  const project = this.ctx.getProjectByName(file.projectName || "");
1772
1593
  for (const task of tasks) {
1773
- if (task.result?.state !== "fail") {
1774
- continue;
1775
- }
1594
+ if (task.result?.state !== "fail") continue;
1776
1595
  const title = getFullName(task, " > ");
1777
- for (const error of task.result?.errors ?? []) {
1778
- projectErrors.push({
1779
- project,
1780
- title,
1781
- error,
1782
- file
1783
- });
1784
- }
1596
+ for (const error of task.result?.errors ?? []) projectErrors.push({
1597
+ project,
1598
+ title,
1599
+ error,
1600
+ file
1601
+ });
1785
1602
  }
1786
1603
  }
1604
+ const onWritePath = this.options.onWritePath ?? defaultOnWritePath;
1605
+ // format errors via `printError`
1787
1606
  for (const { project, title, error, file } of projectErrors) {
1788
1607
  const result = capturePrintError(error, this.ctx, {
1789
1608
  project,
1790
1609
  task: file
1791
1610
  });
1792
1611
  const stack = result?.nearest;
1793
- if (!stack) {
1794
- continue;
1795
- }
1612
+ if (!stack) continue;
1796
1613
  const formatted = formatMessage({
1797
1614
  command: "error",
1798
1615
  properties: {
1799
- file: stack.file,
1616
+ file: onWritePath(stack.file),
1800
1617
  title,
1801
1618
  line: String(stack.line),
1802
1619
  column: String(stack.column)
@@ -1807,6 +1624,25 @@ class GithubActionsReporter {
1807
1624
  }
1808
1625
  }
1809
1626
  }
1627
+ const BUILT_IN_TYPES = [
1628
+ "notice",
1629
+ "error",
1630
+ "warning"
1631
+ ];
1632
+ function getTitle(type) {
1633
+ if (BUILT_IN_TYPES.includes(type)) return void 0;
1634
+ return type;
1635
+ }
1636
+ function getType(type) {
1637
+ if (BUILT_IN_TYPES.includes(type)) return type;
1638
+ return "notice";
1639
+ }
1640
+ function defaultOnWritePath(path) {
1641
+ return path;
1642
+ }
1643
+ // workflow command formatting based on
1644
+ // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message
1645
+ // https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
1810
1646
  function formatMessage({ command, properties, message }) {
1811
1647
  let result = `::${command}`;
1812
1648
  Object.entries(properties).forEach(([k, v], i) => {
@@ -1871,9 +1707,7 @@ class JsonReporter {
1871
1707
  for (const file of files) {
1872
1708
  const tests = getTests([file]);
1873
1709
  let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
1874
- if (startTime === Number.POSITIVE_INFINITY) {
1875
- startTime = this.start;
1876
- }
1710
+ if (startTime === Number.POSITIVE_INFINITY) startTime = this.start;
1877
1711
  const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime);
1878
1712
  const assertionResults = tests.map((t) => {
1879
1713
  const ancestorTitles = [];
@@ -1894,9 +1728,7 @@ class JsonReporter {
1894
1728
  meta: t.meta
1895
1729
  };
1896
1730
  });
1897
- if (tests.some((t) => t.result?.state === "run" || t.result?.state === "queued")) {
1898
- this.ctx.logger.warn("WARNING: Some tests are still running when generating the JSON report." + "This is likely an internal bug in Vitest." + "Please report it to https://github.com/vitest-dev/vitest/issues");
1899
- }
1731
+ if (tests.some((t) => t.result?.state === "run" || t.result?.state === "queued")) this.ctx.logger.warn("WARNING: Some tests are still running when generating the JSON report.This is likely an internal bug in Vitest.Please report it to https://github.com/vitest-dev/vitest/issues");
1900
1732
  const hasFailedTests = tests.some((t) => t.result?.state === "fail");
1901
1733
  testResults.push({
1902
1734
  assertionResults,
@@ -1938,14 +1770,10 @@ class JsonReporter {
1938
1770
  if (outputFile) {
1939
1771
  const reportFile = resolve(this.ctx.config.root, outputFile);
1940
1772
  const outputDirectory = dirname(reportFile);
1941
- if (!existsSync(outputDirectory)) {
1942
- await promises.mkdir(outputDirectory, { recursive: true });
1943
- }
1773
+ if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
1944
1774
  await promises.writeFile(reportFile, report, "utf-8");
1945
1775
  this.ctx.logger.log(`JSON report written to ${reportFile}`);
1946
- } else {
1947
- this.ctx.logger.log(report);
1948
- }
1776
+ } else this.ctx.logger.log(report);
1949
1777
  }
1950
1778
  }
1951
1779
 
@@ -1967,25 +1795,24 @@ class IndentedLogger {
1967
1795
 
1968
1796
  function flattenTasks$1(task, baseName = "") {
1969
1797
  const base = baseName ? `${baseName} > ` : "";
1970
- if (task.type === "suite") {
1971
- return task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`));
1972
- } else {
1973
- return [{
1974
- ...task,
1975
- name: `${base}${task.name}`
1976
- }];
1977
- }
1798
+ if (task.type === "suite") return task.tasks.flatMap((child) => flattenTasks$1(child, `${base}${task.name}`));
1799
+ else return [{
1800
+ ...task,
1801
+ name: `${base}${task.name}`
1802
+ }];
1978
1803
  }
1804
+ // https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
1979
1805
  function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
1980
1806
  let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
1981
1807
  value = String(value || "").replace(regex, "");
1982
1808
  {
1809
+ // remove everything discouraged by XML 1.0 specifications
1983
1810
  regex = new RegExp(
1984
1811
  /* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
1985
- "([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|\\uD83F[\\uDFFE\\uDFFF]|(?:\\uD87F[\\uDF" + "FE\\uDFFF])|\\uD8BF[\\uDFFE\\uDFFF]|\\uD8FF[\\uDFFE\\uDFFF]|(?:\\uD93F[\\uDFFE\\uD" + "FFF])|\\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]))",
1812
+ "([\\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]))",
1986
1813
  "g"
1987
1814
  /* eslint-enable */
1988
- );
1815
+ );
1989
1816
  value = value.replace(regex, "");
1990
1817
  }
1991
1818
  return value;
@@ -2008,7 +1835,7 @@ class JUnitReporter {
2008
1835
  reportFile;
2009
1836
  baseLog;
2010
1837
  logger;
2011
- _timeStart = new Date();
1838
+ _timeStart = /* @__PURE__ */ new Date();
2012
1839
  fileFd;
2013
1840
  options;
2014
1841
  constructor(options) {
@@ -2021,30 +1848,22 @@ class JUnitReporter {
2021
1848
  if (outputFile) {
2022
1849
  this.reportFile = resolve(this.ctx.config.root, outputFile);
2023
1850
  const outputDirectory = dirname(this.reportFile);
2024
- if (!existsSync(outputDirectory)) {
2025
- await promises.mkdir(outputDirectory, { recursive: true });
2026
- }
1851
+ if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
2027
1852
  const fileFd = await promises.open(this.reportFile, "w+");
2028
1853
  this.fileFd = fileFd;
2029
1854
  this.baseLog = async (text) => {
2030
- if (!this.fileFd) {
2031
- this.fileFd = await promises.open(this.reportFile, "w+");
2032
- }
1855
+ if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
2033
1856
  await promises.writeFile(this.fileFd, `${text}\n`);
2034
1857
  };
2035
- } else {
2036
- this.baseLog = async (text) => this.ctx.logger.log(text);
2037
- }
2038
- this._timeStart = new Date();
1858
+ } else this.baseLog = async (text) => this.ctx.logger.log(text);
1859
+ this._timeStart = /* @__PURE__ */ new Date();
2039
1860
  this.logger = new IndentedLogger(this.baseLog);
2040
1861
  }
2041
1862
  async writeElement(name, attrs, children) {
2042
1863
  const pairs = [];
2043
1864
  for (const key in attrs) {
2044
1865
  const attr = attrs[key];
2045
- if (attr === undefined) {
2046
- continue;
2047
- }
1866
+ if (attr === void 0) continue;
2048
1867
  pairs.push(`${key}="${escapeXML(attr)}"`);
2049
1868
  }
2050
1869
  await this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`);
@@ -2054,18 +1873,12 @@ class JUnitReporter {
2054
1873
  await this.logger.log(`</${name}>`);
2055
1874
  }
2056
1875
  async writeLogs(task, type) {
2057
- if (task.logs == null || task.logs.length === 0) {
2058
- return;
2059
- }
1876
+ if (task.logs == null || task.logs.length === 0) return;
2060
1877
  const logType = type === "err" ? "stderr" : "stdout";
2061
1878
  const logs = task.logs.filter((log) => log.type === logType);
2062
- if (logs.length === 0) {
2063
- return;
2064
- }
1879
+ if (logs.length === 0) return;
2065
1880
  await this.writeElement(`system-${type}`, {}, async () => {
2066
- for (const log of logs) {
2067
- await this.baseLog(escapeXML(log.content));
2068
- }
1881
+ for (const log of logs) await this.baseLog(escapeXML(log.content));
2069
1882
  });
2070
1883
  }
2071
1884
  async writeTasks(tasks, filename) {
@@ -2075,16 +1888,12 @@ class JUnitReporter {
2075
1888
  filename: task.file.name,
2076
1889
  filepath: task.file.filepath
2077
1890
  };
2078
- if (typeof this.options.classnameTemplate === "function") {
2079
- classname = this.options.classnameTemplate(templateVars);
2080
- } else if (typeof this.options.classnameTemplate === "string") {
2081
- classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
2082
- } else if (typeof this.options.classname === "string") {
2083
- classname = this.options.classname;
2084
- }
1891
+ if (typeof this.options.classnameTemplate === "function") classname = this.options.classnameTemplate(templateVars);
1892
+ else if (typeof this.options.classnameTemplate === "string") classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
1893
+ else if (typeof this.options.classname === "string") classname = this.options.classname;
2085
1894
  await this.writeElement("testcase", {
2086
1895
  classname,
2087
- file: this.options.addFileAttribute ? filename : undefined,
1896
+ file: this.options.addFileAttribute ? filename : void 0,
2088
1897
  name: task.name,
2089
1898
  time: getDuration(task)
2090
1899
  }, async () => {
@@ -2092,26 +1901,30 @@ class JUnitReporter {
2092
1901
  await this.writeLogs(task, "out");
2093
1902
  await this.writeLogs(task, "err");
2094
1903
  }
2095
- if (task.mode === "skip" || task.mode === "todo") {
2096
- await this.logger.log("<skipped/>");
1904
+ if (task.mode === "skip" || task.mode === "todo") await this.logger.log("<skipped/>");
1905
+ if (task.type === "test" && task.annotations.length) {
1906
+ await this.logger.log("<properties>");
1907
+ this.logger.indent();
1908
+ for (const annotation of task.annotations) {
1909
+ await this.logger.log(`<property name="${escapeXML(annotation.type)}" value="${escapeXML(annotation.message)}">`);
1910
+ await this.logger.log("</property>");
1911
+ }
1912
+ this.logger.unindent();
1913
+ await this.logger.log("</properties>");
2097
1914
  }
2098
1915
  if (task.result?.state === "fail") {
2099
1916
  const errors = task.result.errors || [];
2100
- for (const error of errors) {
2101
- await this.writeElement("failure", {
2102
- message: error?.message,
2103
- type: error?.name ?? error?.nameStr
2104
- }, async () => {
2105
- if (!error) {
2106
- return;
2107
- }
2108
- const result = capturePrintError(error, this.ctx, {
2109
- project: this.ctx.getProjectByName(task.file.projectName || ""),
2110
- task
2111
- });
2112
- await this.baseLog(escapeXML(stripVTControlCharacters(result.output.trim())));
1917
+ for (const error of errors) await this.writeElement("failure", {
1918
+ message: error?.message,
1919
+ type: error?.name
1920
+ }, async () => {
1921
+ if (!error) return;
1922
+ const result = capturePrintError(error, this.ctx, {
1923
+ project: this.ctx.getProjectByName(task.file.projectName || ""),
1924
+ task
2113
1925
  });
2114
- }
1926
+ await this.baseLog(escapeXML(stripVTControlCharacters(result.output.trim())));
1927
+ });
2115
1928
  }
2116
1929
  });
2117
1930
  }
@@ -2131,13 +1944,13 @@ class JUnitReporter {
2131
1944
  failures: 0,
2132
1945
  skipped: 0
2133
1946
  });
1947
+ // inject failed suites to surface errors during beforeAll/afterAll
2134
1948
  const suites = getSuites(file);
2135
- for (const suite of suites) {
2136
- if (suite.result?.errors) {
2137
- tasks.push(suite);
2138
- stats.failures += 1;
2139
- }
1949
+ for (const suite of suites) if (suite.result?.errors) {
1950
+ tasks.push(suite);
1951
+ stats.failures += 1;
2140
1952
  }
1953
+ // If there are no tests, but the file failed to load, we still want to report it as a failure
2141
1954
  if (tasks.length === 0 && file.result?.state === "fail") {
2142
1955
  stats.failures = 1;
2143
1956
  tasks.push({
@@ -2150,7 +1963,8 @@ class JUnitReporter {
2150
1963
  timeout: 0,
2151
1964
  context: null,
2152
1965
  suite: null,
2153
- file: null
1966
+ file: null,
1967
+ annotations: []
2154
1968
  });
2155
1969
  }
2156
1970
  return {
@@ -2179,7 +1993,7 @@ class JUnitReporter {
2179
1993
  const filename = relative(this.ctx.config.root, file.filepath);
2180
1994
  await this.writeElement("testsuite", {
2181
1995
  name: filename,
2182
- timestamp: new Date().toISOString(),
1996
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2183
1997
  hostname: hostname(),
2184
1998
  tests: file.tasks.length,
2185
1999
  failures: file.stats.failures,
@@ -2191,11 +2005,9 @@ class JUnitReporter {
2191
2005
  });
2192
2006
  }
2193
2007
  });
2194
- if (this.reportFile) {
2195
- this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
2196
- }
2008
+ if (this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
2197
2009
  await this.fileFd?.close();
2198
- this.fileFd = undefined;
2010
+ this.fileFd = void 0;
2199
2011
  }
2200
2012
  }
2201
2013
 
@@ -2213,23 +2025,18 @@ class TapReporter {
2213
2025
  this.logger = new IndentedLogger(ctx.logger.log.bind(ctx.logger));
2214
2026
  }
2215
2027
  static getComment(task) {
2216
- if (task.mode === "skip") {
2217
- return " # SKIP";
2218
- } else if (task.mode === "todo") {
2219
- return " # TODO";
2220
- } else if (task.result?.duration != null) {
2221
- return ` # time=${task.result.duration.toFixed(2)}ms`;
2222
- } else {
2223
- return "";
2224
- }
2028
+ if (task.mode === "skip") return " # SKIP";
2029
+ else if (task.mode === "todo") return " # TODO";
2030
+ else if (task.result?.duration != null) return ` # time=${task.result.duration.toFixed(2)}ms`;
2031
+ else return "";
2225
2032
  }
2226
2033
  logErrorDetails(error, stack) {
2227
- const errorName = error.name || error.nameStr || "Unknown Error";
2034
+ const errorName = error.name || "Unknown Error";
2228
2035
  this.logger.log(`name: ${yamlString(String(errorName))}`);
2229
2036
  this.logger.log(`message: ${yamlString(String(error.message))}`);
2230
- if (stack) {
2231
- this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
2232
- }
2037
+ if (stack)
2038
+ // For compatibility with tap-mocha-reporter
2039
+ this.logger.log(`stack: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
2233
2040
  }
2234
2041
  logTasks(tasks) {
2235
2042
  this.logger.log(`1..${tasks.length}`);
@@ -2246,6 +2053,13 @@ class TapReporter {
2246
2053
  } else {
2247
2054
  this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
2248
2055
  const project = this.ctx.getProjectByName(task.file.projectName || "");
2056
+ if (task.type === "test" && task.annotations) {
2057
+ this.logger.indent();
2058
+ task.annotations.forEach(({ type, message }) => {
2059
+ this.logger.log(`# ${type}: ${message}`);
2060
+ });
2061
+ this.logger.unindent();
2062
+ }
2249
2063
  if (task.result?.state === "fail" && task.result.errors) {
2250
2064
  this.logger.indent();
2251
2065
  task.result.errors.forEach((error) => {
@@ -2256,9 +2070,7 @@ class TapReporter {
2256
2070
  this.logger.indent();
2257
2071
  this.logErrorDetails(error);
2258
2072
  this.logger.unindent();
2259
- if (stack) {
2260
- this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
2261
- }
2073
+ if (stack) this.logger.log(`at: ${yamlString(`${stack.file}:${stack.line}:${stack.column}`)}`);
2262
2074
  if (error.showDiff) {
2263
2075
  this.logger.log(`actual: ${yamlString(error.actual)}`);
2264
2076
  this.logger.log(`expected: ${yamlString(error.expected)}`);
@@ -2278,14 +2090,11 @@ class TapReporter {
2278
2090
 
2279
2091
  function flattenTasks(task, baseName = "") {
2280
2092
  const base = baseName ? `${baseName} > ` : "";
2281
- if (task.type === "suite" && task.tasks.length > 0) {
2282
- return task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`));
2283
- } else {
2284
- return [{
2285
- ...task,
2286
- name: `${base}${task.name}`
2287
- }];
2288
- }
2093
+ if (task.type === "suite" && task.tasks.length > 0) return task.tasks.flatMap((child) => flattenTasks(child, `${base}${task.name}`));
2094
+ else return [{
2095
+ ...task,
2096
+ name: `${base}${task.name}`
2097
+ }];
2289
2098
  }
2290
2099
  class TapFlatReporter extends TapReporter {
2291
2100
  onInit(ctx) {
@@ -2302,35 +2111,32 @@ class VerboseReporter extends DefaultReporter {
2302
2111
  verbose = true;
2303
2112
  renderSucceed = true;
2304
2113
  printTestModule(module) {
2305
- if (this.isTTY) {
2306
- return super.printTestModule(module);
2307
- }
2114
+ // still print the test module in TTY,
2115
+ // but don't print it in the CLI because we
2116
+ // print all the tests when they finish
2117
+ // instead of printing them when the test file finishes
2118
+ if (this.isTTY) return super.printTestModule(module);
2308
2119
  }
2309
2120
  onTestCaseResult(test) {
2310
2121
  super.onTestCaseResult(test);
2311
- if (this.isTTY) {
2312
- return;
2313
- }
2122
+ // don't print tests in TTY as they go, only print them
2123
+ // in the CLI when they finish
2124
+ if (this.isTTY) return;
2314
2125
  const testResult = test.result();
2315
- if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") {
2316
- return;
2317
- }
2126
+ if (this.ctx.config.hideSkippedTests && testResult.state === "skipped") return;
2318
2127
  let title = ` ${getStateSymbol(test.task)} `;
2319
- if (test.project.name) {
2320
- title += formatProjectName(test.project);
2321
- }
2128
+ if (test.project.name) title += formatProjectName(test.project);
2322
2129
  title += getFullName(test.task, c.dim(" > "));
2323
2130
  title += this.getDurationPrefix(test.task);
2324
2131
  const diagnostic = test.diagnostic();
2325
- if (diagnostic?.heap != null) {
2326
- title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
2327
- }
2328
- if (testResult.state === "skipped" && testResult.note) {
2329
- title += c.dim(c.gray(` [${testResult.note}]`));
2330
- }
2132
+ if (diagnostic?.heap != null) title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`);
2133
+ if (testResult.state === "skipped" && testResult.note) title += c.dim(c.gray(` [${testResult.note}]`));
2331
2134
  this.log(title);
2332
- if (testResult.state === "failed") {
2333
- testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
2135
+ if (testResult.state === "failed") testResult.errors.forEach((error) => this.log(c.red(` ${F_RIGHT} ${error?.message}`)));
2136
+ if (test.annotations().length) {
2137
+ this.log();
2138
+ this.printAnnotations(test, "log", 3);
2139
+ this.log();
2334
2140
  }
2335
2141
  }
2336
2142
  printTestSuite(testSuite) {
@@ -2346,13 +2152,12 @@ class VerboseReporter extends DefaultReporter {
2346
2152
  return " ".repeat(getIndentation(test));
2347
2153
  }
2348
2154
  formatShortError() {
2155
+ // Short errors are not shown in tree-view
2349
2156
  return "";
2350
2157
  }
2351
2158
  }
2352
2159
  function getIndentation(suite, level = 1) {
2353
- if (suite.suite && !("filepath" in suite.suite)) {
2354
- return getIndentation(suite.suite, level + 1);
2355
- }
2160
+ if (suite.suite && !("filepath" in suite.suite)) return getIndentation(suite.suite, level + 1);
2356
2161
  return level;
2357
2162
  }
2358
2163