vitest 3.2.4 → 4.0.0-beta.10

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