vitest 2.2.0-beta.2 → 3.0.0-beta.2

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 (54) hide show
  1. package/LICENSE.md +75 -0
  2. package/dist/browser.d.ts +13 -10
  3. package/dist/browser.js +2 -2
  4. package/dist/chunks/{RandomSequencer.BPedXEug.js → RandomSequencer.gisBJ77r.js} +11 -4
  5. package/dist/chunks/{base.BS0HhLXd.js → base.CUgXReRN.js} +8 -3
  6. package/dist/chunks/{cac.Cs06pOqp.js → cac.Xzv7eNWw.js} +22 -17
  7. package/dist/chunks/{cli-api.CB-jIbYQ.js → cli-api.CETCDGgZ.js} +985 -498
  8. package/dist/chunks/{config.CPguQ7J1.d.ts → config.BTPBhmK5.d.ts} +1 -1
  9. package/dist/chunks/{coverage.BoMDb1ip.js → coverage.BWeNbfBa.js} +4 -4
  10. package/dist/chunks/{creator.IIqd8RWT.js → creator.DcAcUhMD.js} +1 -4
  11. package/dist/chunks/{environment.CT0jpO-1.d.ts → environment.d8YfPkTm.d.ts} +1 -3
  12. package/dist/chunks/{globals.BCGEw6ON.js → globals.BFncSRNA.js} +2 -2
  13. package/dist/chunks/{index.bzFpKeaq.js → index.9ZEBV_TJ.js} +992 -577
  14. package/dist/chunks/{index.DD5eTY2y.js → index.CkWmZCXU.js} +1 -1
  15. package/dist/chunks/{index.CqYx2Nsr.js → index.DQboAxJm.js} +23 -14
  16. package/dist/chunks/{index.BjjsHdBb.js → index.DoV7W5gc.js} +2 -2
  17. package/dist/chunks/{inspector.70d6emsh.js → inspector.DKLceBVD.js} +1 -1
  18. package/dist/chunks/{reporters.F9D2idOT.d.ts → reporters.DTtxC3KQ.d.ts} +588 -524
  19. package/dist/chunks/{resolveConfig.CLnvCvEs.js → resolveConfig.BA-_OKEx.js} +5599 -5570
  20. package/dist/chunks/{runBaseTests.B7hcVT-s.js → runBaseTests.D0dWpzZV.js} +13 -12
  21. package/dist/chunks/{setup-common.BfGt8K-K.js → setup-common.Cp_bu5q3.js} +1 -1
  22. package/dist/chunks/types.BOjykUpq.d.ts +27 -0
  23. package/dist/chunks/{utils.DJONn5B5.js → utils.CMUTX-p8.js} +5 -2
  24. package/dist/chunks/{vi.BlPttogV.js → vi.S4Fq8wSo.js} +20 -14
  25. package/dist/chunks/{vite.DonA4fvH.d.ts → vite.CXaetSK3.d.ts} +1 -1
  26. package/dist/chunks/{vm.Zr4qWzDJ.js → vm.DGhTouO3.js} +10 -1
  27. package/dist/chunks/{worker.Qz1UB4Fv.d.ts → worker.ClntunZp.d.ts} +1 -1
  28. package/dist/chunks/{worker.9VY11NZs.d.ts → worker.o1PBoDdo.d.ts} +4 -4
  29. package/dist/cli.js +1 -1
  30. package/dist/config.cjs +1 -10
  31. package/dist/config.d.ts +7 -10
  32. package/dist/config.js +1 -10
  33. package/dist/coverage.d.ts +5 -7
  34. package/dist/coverage.js +4 -4
  35. package/dist/environments.d.ts +2 -2
  36. package/dist/execute.d.ts +4 -4
  37. package/dist/index.d.ts +27 -22
  38. package/dist/index.js +2 -2
  39. package/dist/node.d.ts +25 -33
  40. package/dist/node.js +66 -32
  41. package/dist/reporters.d.ts +5 -7
  42. package/dist/reporters.js +5 -4
  43. package/dist/runners.d.ts +3 -4
  44. package/dist/runners.js +9 -14
  45. package/dist/suite.d.ts +1 -1
  46. package/dist/worker.js +1 -1
  47. package/dist/workers/forks.js +1 -1
  48. package/dist/workers/runVmTests.js +9 -9
  49. package/dist/workers/threads.js +1 -1
  50. package/dist/workers/vmForks.js +1 -1
  51. package/dist/workers/vmThreads.js +1 -1
  52. package/dist/workers.d.ts +4 -4
  53. package/dist/workers.js +3 -3
  54. package/package.json +23 -24
@@ -3,367 +3,23 @@ import { getTests, getTestName, hasFailed, getFullName, getSuites, getTasks } fr
3
3
  import * as pathe from 'pathe';
4
4
  import { extname, relative, normalize, resolve, dirname } from 'pathe';
5
5
  import c from 'tinyrainbow';
6
- import { d as divider, F as F_POINTER, w as withLabel, f as formatProjectName, a as formatTimeString, g as getStateSymbol, t as taskFail, b as F_RIGHT, c as F_CHECK, r as renderSnapshotSummary, p as padSummaryTitle, e as getStateString$1, h as formatTime, i as countTestErrors, j as F_TREE_NODE_END, k as F_TREE_NODE_MIDDLE, l as getCols } from './utils.DJONn5B5.js';
6
+ import { d as divider, F as F_POINTER, w as withLabel, f as formatProjectName, a as formatTimeString, g as getStateSymbol, t as taskFail, b as F_RIGHT, c as F_CHECK, r as renderSnapshotSummary, p as padSummaryTitle, e as getStateString$1, h as formatTime, i as countTestErrors, j as F_TREE_NODE_END, k as F_TREE_NODE_MIDDLE, l as getCols } from './utils.CMUTX-p8.js';
7
7
  import { stripVTControlCharacters } from 'node:util';
8
8
  import { highlight, isPrimitive, inspect, positionToOffset, lineSplitRE, toArray, notNullish } from '@vitest/utils';
9
9
  import { performance as performance$1 } from 'node:perf_hooks';
10
10
  import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
11
- import { a as TypeCheckError, R as RandomSequencer, g as getOutputFile, b as isNode, c as isDeno } from './RandomSequencer.BPedXEug.js';
11
+ import { a as TypeCheckError, R as RandomSequencer, g as getOutputFile, b as isNode, c as isDeno } from './RandomSequencer.gisBJ77r.js';
12
12
  import { isCI } from 'std-env';
13
13
  import { mkdir, writeFile, readdir, stat, readFile } from 'node:fs/promises';
14
- import restoreCursor from 'restore-cursor';
15
14
  import { Writable } from 'node:stream';
16
15
  import { Console } from 'node:console';
17
16
  import process$1 from 'node:process';
18
- import { g as getDefaultExportFromCjs } from './_commonjsHelpers.BFTU3MAI.js';
17
+ import { g as getDefaultExportFromCjs, c as commonjsGlobal } from './_commonjsHelpers.BFTU3MAI.js';
18
+ import require$$0 from 'assert';
19
+ import require$$0$1 from 'events';
19
20
  import { createRequire } from 'node:module';
20
21
  import { hostname } from 'node:os';
21
22
 
22
- class ReportedTaskImplementation {
23
- /**
24
- * Task instance.
25
- * @experimental Public runner task API is experimental and does not follow semver.
26
- */
27
- task;
28
- /**
29
- * The project assosiacted with the test or suite.
30
- */
31
- project;
32
- /**
33
- * Unique identifier.
34
- * This ID is deterministic and will be the same for the same test across multiple runs.
35
- * The ID is based on the project name, module url and test position.
36
- */
37
- id;
38
- /**
39
- * Location in the module where the test or suite is defined.
40
- */
41
- location;
42
- constructor(task, project) {
43
- this.task = task;
44
- this.project = project;
45
- this.id = task.id;
46
- this.location = task.location;
47
- }
48
- /**
49
- * Creates a new reported task instance and stores it in the project's state for future use.
50
- */
51
- static register(task, project) {
52
- const state = new this(task, project);
53
- storeTask(project, task, state);
54
- return state;
55
- }
56
- }
57
- class TestCase extends ReportedTaskImplementation {
58
- #fullName;
59
- type = "test";
60
- /**
61
- * Direct reference to the test module where the test or suite is defined.
62
- */
63
- module;
64
- /**
65
- * Name of the test.
66
- */
67
- name;
68
- /**
69
- * Options that the test was initiated with.
70
- */
71
- options;
72
- /**
73
- * Parent suite. If the test was called directly inside the module, the parent will be the module itself.
74
- */
75
- parent;
76
- constructor(task, project) {
77
- super(task, project);
78
- this.name = task.name;
79
- this.module = getReportedTask(project, task.file);
80
- const suite = this.task.suite;
81
- if (suite) {
82
- this.parent = getReportedTask(project, suite);
83
- } else {
84
- this.parent = this.module;
85
- }
86
- this.options = buildOptions(task);
87
- }
88
- /**
89
- * Full name of the test including all parent suites separated with `>`.
90
- */
91
- get fullName() {
92
- if (this.#fullName === void 0) {
93
- if (this.parent.type !== "module") {
94
- this.#fullName = `${this.parent.fullName} > ${this.name}`;
95
- } else {
96
- this.#fullName = this.name;
97
- }
98
- }
99
- return this.#fullName;
100
- }
101
- /**
102
- * Test results. Will be `undefined` if test is not finished yet or was just collected.
103
- */
104
- result() {
105
- const result = this.task.result;
106
- if (!result || result.state === "run") {
107
- return void 0;
108
- }
109
- const state = result.state === "fail" ? "failed" : result.state === "pass" ? "passed" : "skipped";
110
- if (state === "skipped") {
111
- return {
112
- state,
113
- note: result.note,
114
- errors: void 0
115
- };
116
- }
117
- if (state === "passed") {
118
- return {
119
- state,
120
- errors: result.errors
121
- };
122
- }
123
- return {
124
- state,
125
- errors: result.errors || []
126
- };
127
- }
128
- /**
129
- * Checks if the test did not fail the suite.
130
- * If the test is not finished yet or was skipped, it will return `true`.
131
- */
132
- ok() {
133
- const result = this.result();
134
- return !result || result.state !== "failed";
135
- }
136
- /**
137
- * Custom metadata that was attached to the test during its execution.
138
- */
139
- meta() {
140
- return this.task.meta;
141
- }
142
- /**
143
- * Useful information about the test like duration, memory usage, etc.
144
- * Diagnostic is only available after the test has finished.
145
- */
146
- diagnostic() {
147
- const result = this.task.result;
148
- if (!result || result.state === "run" || !result.startTime) {
149
- return void 0;
150
- }
151
- const duration = result.duration || 0;
152
- const slow = duration > this.project.globalConfig.slowTestThreshold;
153
- return {
154
- slow,
155
- heap: result.heap,
156
- duration,
157
- startTime: result.startTime,
158
- retryCount: result.retryCount ?? 0,
159
- repeatCount: result.repeatCount ?? 0,
160
- flaky: !!result.retryCount && result.state === "pass" && result.retryCount > 0
161
- };
162
- }
163
- }
164
- class TestCollection {
165
- #task;
166
- #project;
167
- constructor(task, project) {
168
- this.#task = task;
169
- this.#project = project;
170
- }
171
- /**
172
- * Returns the test or suite at a specific index in the array.
173
- */
174
- at(index) {
175
- if (index < 0) {
176
- index = this.size + index;
177
- }
178
- return getReportedTask(this.#project, this.#task.tasks[index]);
179
- }
180
- /**
181
- * The number of tests and suites in the collection.
182
- */
183
- get size() {
184
- return this.#task.tasks.length;
185
- }
186
- /**
187
- * Returns the collection in array form for easier manipulation.
188
- */
189
- array() {
190
- return Array.from(this);
191
- }
192
- /**
193
- * Filters all tests that are part of this collection and its children.
194
- */
195
- *allTests(state) {
196
- for (const child of this) {
197
- if (child.type === "suite") {
198
- yield* child.children.allTests(state);
199
- } else if (state) {
200
- const testState = getTestState(child);
201
- if (state === testState) {
202
- yield child;
203
- }
204
- } else {
205
- yield child;
206
- }
207
- }
208
- }
209
- /**
210
- * Filters only the tests that are part of this collection.
211
- */
212
- *tests(state) {
213
- for (const child of this) {
214
- if (child.type !== "test") {
215
- continue;
216
- }
217
- if (state) {
218
- const testState = getTestState(child);
219
- if (state === testState) {
220
- yield child;
221
- }
222
- } else {
223
- yield child;
224
- }
225
- }
226
- }
227
- /**
228
- * Filters only the suites that are part of this collection.
229
- */
230
- *suites() {
231
- for (const child of this) {
232
- if (child.type === "suite") {
233
- yield child;
234
- }
235
- }
236
- }
237
- /**
238
- * Filters all suites that are part of this collection and its children.
239
- */
240
- *allSuites() {
241
- for (const child of this) {
242
- if (child.type === "suite") {
243
- yield child;
244
- yield* child.children.allSuites();
245
- }
246
- }
247
- }
248
- *[Symbol.iterator]() {
249
- for (const task of this.#task.tasks) {
250
- yield getReportedTask(this.#project, task);
251
- }
252
- }
253
- }
254
- class SuiteImplementation extends ReportedTaskImplementation {
255
- /**
256
- * Collection of suites and tests that are part of this suite.
257
- */
258
- children;
259
- constructor(task, project) {
260
- super(task, project);
261
- this.children = new TestCollection(task, project);
262
- }
263
- }
264
- class TestSuite extends SuiteImplementation {
265
- #fullName;
266
- type = "suite";
267
- /**
268
- * Name of the test or the suite.
269
- */
270
- name;
271
- /**
272
- * Direct reference to the test module where the test or suite is defined.
273
- */
274
- module;
275
- /**
276
- * Parent suite. If suite was called directly inside the module, the parent will be the module itself.
277
- */
278
- parent;
279
- /**
280
- * Options that suite was initiated with.
281
- */
282
- options;
283
- constructor(task, project) {
284
- super(task, project);
285
- this.name = task.name;
286
- this.module = getReportedTask(project, task.file);
287
- const suite = this.task.suite;
288
- if (suite) {
289
- this.parent = getReportedTask(project, suite);
290
- } else {
291
- this.parent = this.module;
292
- }
293
- this.options = buildOptions(task);
294
- }
295
- /**
296
- * Full name of the suite including all parent suites separated with `>`.
297
- */
298
- get fullName() {
299
- if (this.#fullName === void 0) {
300
- if (this.parent.type !== "module") {
301
- this.#fullName = `${this.parent.fullName} > ${this.name}`;
302
- } else {
303
- this.#fullName = this.name;
304
- }
305
- }
306
- return this.#fullName;
307
- }
308
- }
309
- class TestModule extends SuiteImplementation {
310
- type = "module";
311
- /**
312
- * This is usually an absolute UNIX file path.
313
- * It can be a virtual id if the file is not on the disk.
314
- * This value corresponds to Vite's `ModuleGraph` id.
315
- */
316
- moduleId;
317
- constructor(task, project) {
318
- super(task, project);
319
- this.moduleId = task.filepath;
320
- }
321
- /**
322
- * Useful information about the module like duration, memory usage, etc.
323
- * If the module was not executed yet, all diagnostic values will return `0`.
324
- */
325
- diagnostic() {
326
- const setupDuration = this.task.setupDuration || 0;
327
- const collectDuration = this.task.collectDuration || 0;
328
- const prepareDuration = this.task.prepareDuration || 0;
329
- const environmentSetupDuration = this.task.environmentLoad || 0;
330
- const duration = this.task.result?.duration || 0;
331
- return {
332
- environmentSetupDuration,
333
- prepareDuration,
334
- collectDuration,
335
- setupDuration,
336
- duration
337
- };
338
- }
339
- }
340
- function buildOptions(task) {
341
- return {
342
- each: task.each,
343
- concurrent: task.concurrent,
344
- shuffle: task.shuffle,
345
- retry: task.retry,
346
- repeats: task.repeats,
347
- mode: task.mode
348
- };
349
- }
350
- function getTestState(test) {
351
- const result = test.result();
352
- return result ? result.state : "running";
353
- }
354
- function storeTask(project, runnerTask, reportedTask) {
355
- project.vitest.state.reportedTasksMap.set(runnerTask, reportedTask);
356
- }
357
- function getReportedTask(project, runnerTask) {
358
- const reportedTask = project.vitest.state.getReportedEntity(runnerTask);
359
- if (!reportedTask) {
360
- throw new Error(
361
- `Task instance was not found for ${runnerTask.type} "${runnerTask.name}"`
362
- );
363
- }
364
- return reportedTask;
365
- }
366
-
367
23
  /// <reference types="../types/index.d.ts" />
368
24
 
369
25
  // (c) 2020-present Andrea Giammarchi
@@ -626,6 +282,370 @@ ansiEscapes.iTerm = {
626
282
  }
627
283
  };
628
284
 
285
+ var onetime$1 = {exports: {}};
286
+
287
+ var mimicFn = {exports: {}};
288
+
289
+ var hasRequiredMimicFn;
290
+
291
+ function requireMimicFn () {
292
+ if (hasRequiredMimicFn) return mimicFn.exports;
293
+ hasRequiredMimicFn = 1;
294
+
295
+ const mimicFn$1 = (to, from) => {
296
+ for (const prop of Reflect.ownKeys(from)) {
297
+ Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop));
298
+ }
299
+
300
+ return to;
301
+ };
302
+
303
+ mimicFn.exports = mimicFn$1;
304
+ // TODO: Remove this for the next major release
305
+ mimicFn.exports.default = mimicFn$1;
306
+ return mimicFn.exports;
307
+ }
308
+
309
+ var hasRequiredOnetime;
310
+
311
+ function requireOnetime () {
312
+ if (hasRequiredOnetime) return onetime$1.exports;
313
+ hasRequiredOnetime = 1;
314
+ const mimicFn = requireMimicFn();
315
+
316
+ const calledFunctions = new WeakMap();
317
+
318
+ const onetime = (function_, options = {}) => {
319
+ if (typeof function_ !== 'function') {
320
+ throw new TypeError('Expected a function');
321
+ }
322
+
323
+ let returnValue;
324
+ let callCount = 0;
325
+ const functionName = function_.displayName || function_.name || '<anonymous>';
326
+
327
+ const onetime = function (...arguments_) {
328
+ calledFunctions.set(onetime, ++callCount);
329
+
330
+ if (callCount === 1) {
331
+ returnValue = function_.apply(this, arguments_);
332
+ function_ = null;
333
+ } else if (options.throw === true) {
334
+ throw new Error(`Function \`${functionName}\` can only be called once`);
335
+ }
336
+
337
+ return returnValue;
338
+ };
339
+
340
+ mimicFn(onetime, function_);
341
+ calledFunctions.set(onetime, callCount);
342
+
343
+ return onetime;
344
+ };
345
+
346
+ onetime$1.exports = onetime;
347
+ // TODO: Remove this for the next major release
348
+ onetime$1.exports.default = onetime;
349
+
350
+ onetime$1.exports.callCount = function_ => {
351
+ if (!calledFunctions.has(function_)) {
352
+ throw new Error(`The given function \`${function_.name}\` is not wrapped by the \`onetime\` package`);
353
+ }
354
+
355
+ return calledFunctions.get(function_);
356
+ };
357
+ return onetime$1.exports;
358
+ }
359
+
360
+ var onetimeExports = requireOnetime();
361
+ var onetime = /*@__PURE__*/getDefaultExportFromCjs(onetimeExports);
362
+
363
+ var signalExit$1 = {exports: {}};
364
+
365
+ var signals = {exports: {}};
366
+
367
+ var hasRequiredSignals;
368
+
369
+ function requireSignals () {
370
+ if (hasRequiredSignals) return signals.exports;
371
+ hasRequiredSignals = 1;
372
+ (function (module) {
373
+ // This is not the set of all possible signals.
374
+ //
375
+ // It IS, however, the set of all signals that trigger
376
+ // an exit on either Linux or BSD systems. Linux is a
377
+ // superset of the signal names supported on BSD, and
378
+ // the unknown signals just fail to register, so we can
379
+ // catch that easily enough.
380
+ //
381
+ // Don't bother with SIGKILL. It's uncatchable, which
382
+ // means that we can't fire any callbacks anyway.
383
+ //
384
+ // If a user does happen to register a handler on a non-
385
+ // fatal signal like SIGWINCH or something, and then
386
+ // exit, it'll end up firing `process.emit('exit')`, so
387
+ // the handler will be fired anyway.
388
+ //
389
+ // SIGBUS, SIGFPE, SIGSEGV and SIGILL, when not raised
390
+ // artificially, inherently leave the process in a
391
+ // state from which it is not safe to try and enter JS
392
+ // listeners.
393
+ module.exports = [
394
+ 'SIGABRT',
395
+ 'SIGALRM',
396
+ 'SIGHUP',
397
+ 'SIGINT',
398
+ 'SIGTERM'
399
+ ];
400
+
401
+ if (process.platform !== 'win32') {
402
+ module.exports.push(
403
+ 'SIGVTALRM',
404
+ 'SIGXCPU',
405
+ 'SIGXFSZ',
406
+ 'SIGUSR2',
407
+ 'SIGTRAP',
408
+ 'SIGSYS',
409
+ 'SIGQUIT',
410
+ 'SIGIOT'
411
+ // should detect profiler and enable/disable accordingly.
412
+ // see #21
413
+ // 'SIGPROF'
414
+ );
415
+ }
416
+
417
+ if (process.platform === 'linux') {
418
+ module.exports.push(
419
+ 'SIGIO',
420
+ 'SIGPOLL',
421
+ 'SIGPWR',
422
+ 'SIGSTKFLT',
423
+ 'SIGUNUSED'
424
+ );
425
+ }
426
+ } (signals));
427
+ return signals.exports;
428
+ }
429
+
430
+ var hasRequiredSignalExit;
431
+
432
+ function requireSignalExit () {
433
+ if (hasRequiredSignalExit) return signalExit$1.exports;
434
+ hasRequiredSignalExit = 1;
435
+ // Note: since nyc uses this module to output coverage, any lines
436
+ // that are in the direct sync flow of nyc's outputCoverage are
437
+ // ignored, since we can never get coverage for them.
438
+ // grab a reference to node's real process object right away
439
+ var process = commonjsGlobal.process;
440
+
441
+ const processOk = function (process) {
442
+ return process &&
443
+ typeof process === 'object' &&
444
+ typeof process.removeListener === 'function' &&
445
+ typeof process.emit === 'function' &&
446
+ typeof process.reallyExit === 'function' &&
447
+ typeof process.listeners === 'function' &&
448
+ typeof process.kill === 'function' &&
449
+ typeof process.pid === 'number' &&
450
+ typeof process.on === 'function'
451
+ };
452
+
453
+ // some kind of non-node environment, just no-op
454
+ /* istanbul ignore if */
455
+ if (!processOk(process)) {
456
+ signalExit$1.exports = function () {
457
+ return function () {}
458
+ };
459
+ } else {
460
+ var assert = require$$0;
461
+ var signals = requireSignals();
462
+ var isWin = /^win/i.test(process.platform);
463
+
464
+ var EE = require$$0$1;
465
+ /* istanbul ignore if */
466
+ if (typeof EE !== 'function') {
467
+ EE = EE.EventEmitter;
468
+ }
469
+
470
+ var emitter;
471
+ if (process.__signal_exit_emitter__) {
472
+ emitter = process.__signal_exit_emitter__;
473
+ } else {
474
+ emitter = process.__signal_exit_emitter__ = new EE();
475
+ emitter.count = 0;
476
+ emitter.emitted = {};
477
+ }
478
+
479
+ // Because this emitter is a global, we have to check to see if a
480
+ // previous version of this library failed to enable infinite listeners.
481
+ // I know what you're about to say. But literally everything about
482
+ // signal-exit is a compromise with evil. Get used to it.
483
+ if (!emitter.infinite) {
484
+ emitter.setMaxListeners(Infinity);
485
+ emitter.infinite = true;
486
+ }
487
+
488
+ signalExit$1.exports = function (cb, opts) {
489
+ /* istanbul ignore if */
490
+ if (!processOk(commonjsGlobal.process)) {
491
+ return function () {}
492
+ }
493
+ assert.equal(typeof cb, 'function', 'a callback must be provided for exit handler');
494
+
495
+ if (loaded === false) {
496
+ load();
497
+ }
498
+
499
+ var ev = 'exit';
500
+ if (opts && opts.alwaysLast) {
501
+ ev = 'afterexit';
502
+ }
503
+
504
+ var remove = function () {
505
+ emitter.removeListener(ev, cb);
506
+ if (emitter.listeners('exit').length === 0 &&
507
+ emitter.listeners('afterexit').length === 0) {
508
+ unload();
509
+ }
510
+ };
511
+ emitter.on(ev, cb);
512
+
513
+ return remove
514
+ };
515
+
516
+ var unload = function unload () {
517
+ if (!loaded || !processOk(commonjsGlobal.process)) {
518
+ return
519
+ }
520
+ loaded = false;
521
+
522
+ signals.forEach(function (sig) {
523
+ try {
524
+ process.removeListener(sig, sigListeners[sig]);
525
+ } catch (er) {}
526
+ });
527
+ process.emit = originalProcessEmit;
528
+ process.reallyExit = originalProcessReallyExit;
529
+ emitter.count -= 1;
530
+ };
531
+ signalExit$1.exports.unload = unload;
532
+
533
+ var emit = function emit (event, code, signal) {
534
+ /* istanbul ignore if */
535
+ if (emitter.emitted[event]) {
536
+ return
537
+ }
538
+ emitter.emitted[event] = true;
539
+ emitter.emit(event, code, signal);
540
+ };
541
+
542
+ // { <signal>: <listener fn>, ... }
543
+ var sigListeners = {};
544
+ signals.forEach(function (sig) {
545
+ sigListeners[sig] = function listener () {
546
+ /* istanbul ignore if */
547
+ if (!processOk(commonjsGlobal.process)) {
548
+ return
549
+ }
550
+ // If there are no other listeners, an exit is coming!
551
+ // Simplest way: remove us and then re-send the signal.
552
+ // We know that this will kill the process, so we can
553
+ // safely emit now.
554
+ var listeners = process.listeners(sig);
555
+ if (listeners.length === emitter.count) {
556
+ unload();
557
+ emit('exit', null, sig);
558
+ /* istanbul ignore next */
559
+ emit('afterexit', null, sig);
560
+ /* istanbul ignore next */
561
+ if (isWin && sig === 'SIGHUP') {
562
+ // "SIGHUP" throws an `ENOSYS` error on Windows,
563
+ // so use a supported signal instead
564
+ sig = 'SIGINT';
565
+ }
566
+ /* istanbul ignore next */
567
+ process.kill(process.pid, sig);
568
+ }
569
+ };
570
+ });
571
+
572
+ signalExit$1.exports.signals = function () {
573
+ return signals
574
+ };
575
+
576
+ var loaded = false;
577
+
578
+ var load = function load () {
579
+ if (loaded || !processOk(commonjsGlobal.process)) {
580
+ return
581
+ }
582
+ loaded = true;
583
+
584
+ // This is the number of onSignalExit's that are in play.
585
+ // It's important so that we can count the correct number of
586
+ // listeners on signals, and don't wait for the other one to
587
+ // handle it instead of us.
588
+ emitter.count += 1;
589
+
590
+ signals = signals.filter(function (sig) {
591
+ try {
592
+ process.on(sig, sigListeners[sig]);
593
+ return true
594
+ } catch (er) {
595
+ return false
596
+ }
597
+ });
598
+
599
+ process.emit = processEmit;
600
+ process.reallyExit = processReallyExit;
601
+ };
602
+ signalExit$1.exports.load = load;
603
+
604
+ var originalProcessReallyExit = process.reallyExit;
605
+ var processReallyExit = function processReallyExit (code) {
606
+ /* istanbul ignore if */
607
+ if (!processOk(commonjsGlobal.process)) {
608
+ return
609
+ }
610
+ process.exitCode = code || /* istanbul ignore next */ 0;
611
+ emit('exit', process.exitCode, null);
612
+ /* istanbul ignore next */
613
+ emit('afterexit', process.exitCode, null);
614
+ /* istanbul ignore next */
615
+ originalProcessReallyExit.call(process, process.exitCode);
616
+ };
617
+
618
+ var originalProcessEmit = process.emit;
619
+ var processEmit = function processEmit (ev, arg) {
620
+ if (ev === 'exit' && processOk(commonjsGlobal.process)) {
621
+ /* istanbul ignore else */
622
+ if (arg !== undefined) {
623
+ process.exitCode = arg;
624
+ }
625
+ var ret = originalProcessEmit.apply(this, arguments);
626
+ /* istanbul ignore next */
627
+ emit('exit', process.exitCode, null);
628
+ /* istanbul ignore next */
629
+ emit('afterexit', process.exitCode, null);
630
+ /* istanbul ignore next */
631
+ return ret
632
+ } else {
633
+ return originalProcessEmit.apply(this, arguments)
634
+ }
635
+ };
636
+ }
637
+ return signalExit$1.exports;
638
+ }
639
+
640
+ var signalExitExports = requireSignalExit();
641
+ var signalExit = /*@__PURE__*/getDefaultExportFromCjs(signalExitExports);
642
+
643
+ const restoreCursor = onetime(() => {
644
+ signalExit(() => {
645
+ process$1.stderr.write('\u001B[?25h');
646
+ }, {alwaysLast: true});
647
+ });
648
+
629
649
  let isHidden = false;
630
650
 
631
651
  const cliCursor = {};
@@ -2804,15 +2824,15 @@ Vitest caught ${errors.length} unhandled error${errors.length > 1 ? "s" : ""} du
2804
2824
  This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.`
2805
2825
  )
2806
2826
  );
2807
- this.log(c.red(divider(c.bold(c.inverse(" Unhandled Errors ")))));
2808
- this.log(errorMessage);
2827
+ this.error(c.red(divider(c.bold(c.inverse(" Unhandled Errors ")))));
2828
+ this.error(errorMessage);
2809
2829
  errors.forEach((err) => {
2810
2830
  this.printError(err, {
2811
2831
  fullStack: true,
2812
2832
  type: err.type || "Unhandled Error"
2813
2833
  });
2814
2834
  });
2815
- this.log(c.red(divider()));
2835
+ this.error(c.red(divider()));
2816
2836
  }
2817
2837
  printSourceTypeErrors(errors) {
2818
2838
  const errorMessage = c.red(
@@ -2866,7 +2886,7 @@ class BlobReporter {
2866
2886
  const modules = this.ctx.projects.map(
2867
2887
  (project) => {
2868
2888
  return [
2869
- project.getName(),
2889
+ project.name,
2870
2890
  [...project.vite.moduleGraph.idToModuleMap.entries()].map((mod) => {
2871
2891
  if (!mod[1].file) {
2872
2892
  return null;
@@ -2934,7 +2954,7 @@ ${blobs.map((b) => `- "${b.file}" uses v${b.version}`).join("\n")}`
2934
2954
  );
2935
2955
  }
2936
2956
  const projects = Object.fromEntries(
2937
- projectsArray.map((p) => [p.getName(), p])
2957
+ projectsArray.map((p) => [p.name, p])
2938
2958
  );
2939
2959
  blobs.forEach((blob) => {
2940
2960
  blob.moduleKeys.forEach(([projectName, moduleIds]) => {
@@ -3041,21 +3061,27 @@ class BaseReporter {
3041
3061
  this.log(` ${title} ${task.name} ${suffix}`);
3042
3062
  const anyFailed = tests.some((test) => test.result?.state === "fail");
3043
3063
  for (const test of tests) {
3044
- const duration = test.result?.duration;
3064
+ const { duration, retryCount, repeatCount } = test.result || {};
3065
+ let suffix2 = "";
3066
+ if (retryCount != null && retryCount > 0) {
3067
+ suffix2 += c.yellow(` (retry x${retryCount})`);
3068
+ }
3069
+ if (repeatCount != null && repeatCount > 0) {
3070
+ suffix2 += c.yellow(` (repeat x${repeatCount})`);
3071
+ }
3045
3072
  if (test.result?.state === "fail") {
3046
- const suffix2 = this.getDurationPrefix(test);
3047
- this.log(c.red(` ${taskFail} ${getTestName(test, c.dim(" > "))}${suffix2}`));
3073
+ this.log(c.red(` ${taskFail} ${getTestName(test, c.dim(" > "))}${this.getDurationPrefix(test)}`) + suffix2);
3048
3074
  test.result?.errors?.forEach((e) => {
3049
3075
  this.log(c.red(` ${F_RIGHT} ${e?.message}`));
3050
3076
  });
3051
3077
  } else if (duration && duration > this.ctx.config.slowTestThreshold) {
3052
3078
  this.log(
3053
- ` ${c.yellow(c.dim(F_CHECK))} ${getTestName(test, c.dim(" > "))} ${c.yellow(Math.round(duration) + c.dim("ms"))}`
3079
+ ` ${c.yellow(c.dim(F_CHECK))} ${getTestName(test, c.dim(" > "))} ${c.yellow(Math.round(duration) + c.dim("ms"))}${suffix2}`
3054
3080
  );
3055
- } else if (test.result?.state === "skip" && test.result.note) {
3081
+ } else if (this.ctx.config.hideSkippedTests && (test.mode === "skip" || test.result?.state === "skip")) ; else if (test.result?.state === "skip" && test.result.note) {
3056
3082
  this.log(` ${getStateSymbol(test)} ${getTestName(test)}${c.dim(c.gray(` [${test.result.note}]`))}`);
3057
3083
  } else if (this.renderSucceed || anyFailed) {
3058
- this.log(` ${c.green(c.dim(F_CHECK))} ${getTestName(test, c.dim(" > "))}`);
3084
+ this.log(` ${c.dim(getStateSymbol(test))} ${getTestName(test, c.dim(" > "))}${suffix2}`);
3059
3085
  }
3060
3086
  }
3061
3087
  }
@@ -3103,7 +3129,7 @@ class BaseReporter {
3103
3129
  this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
3104
3130
  }
3105
3131
  if (this.ctx.filenamePattern) {
3106
- this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern));
3132
+ this.log(BADGE_PADDING + c.dim(" Filename pattern: ") + c.blue(this.ctx.filenamePattern.join(", ")));
3107
3133
  }
3108
3134
  if (this.ctx.configOverride.testNamePattern) {
3109
3135
  this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
@@ -3134,7 +3160,7 @@ class BaseReporter {
3134
3160
  if (log.browser) {
3135
3161
  write("\n");
3136
3162
  }
3137
- const project = log.taskId ? this.ctx.getProjectByTaskId(log.taskId) : this.ctx.getRootTestProject();
3163
+ const project = task ? this.ctx.getProjectByName(task.file.projectName || "") : this.ctx.getRootProject();
3138
3164
  const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
3139
3165
  const highlight = task && stack.find((i) => i.file === task.file.filepath);
3140
3166
  for (const frame of stack) {
@@ -3245,12 +3271,14 @@ class BaseReporter {
3245
3271
  const errorDivider = () => this.error(`${c.red(c.dim(divider(`[${current++}/${failedTotal}]`, void 0, 1)))}
3246
3272
  `);
3247
3273
  if (failedSuites.length) {
3248
- this.error(`${errorBanner(`Failed Suites ${failedSuites.length}`)}
3274
+ this.error(`
3275
+ ${errorBanner(`Failed Suites ${failedSuites.length}`)}
3249
3276
  `);
3250
3277
  this.printTaskErrors(failedSuites, errorDivider);
3251
3278
  }
3252
3279
  if (failedTests.length) {
3253
- this.error(`${errorBanner(`Failed Tests ${failedTests.length}`)}
3280
+ this.error(`
3281
+ ${errorBanner(`Failed Tests ${failedTests.length}`)}
3254
3282
  `);
3255
3283
  this.printTaskErrors(failedTests, errorDivider);
3256
3284
  }
@@ -3309,12 +3337,12 @@ class BaseReporter {
3309
3337
  name += c.dim(` [ ${this.relative(filepath)} ]`);
3310
3338
  }
3311
3339
  this.ctx.logger.error(
3312
- `${c.red(c.bold(c.inverse(" FAIL ")))}${formatProjectName(projectName)} ${name}`
3340
+ `${c.red(c.bold(c.inverse(" FAIL ")))} ${formatProjectName(projectName)}${name}`
3313
3341
  );
3314
3342
  }
3315
3343
  const screenshotPaths = tasks2.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
3316
3344
  this.ctx.logger.printError(error, {
3317
- project: this.ctx.getProjectByTaskId(tasks2[0].id),
3345
+ project: this.ctx.getProjectByName(tasks2[0].file.projectName || ""),
3318
3346
  verbose: this.verbose,
3319
3347
  screenshotPaths,
3320
3348
  task: tasks2[0]
@@ -3378,9 +3406,9 @@ class WindowRenderer {
3378
3406
  };
3379
3407
  this.cleanups.push(
3380
3408
  this.interceptStream(process.stdout, "output"),
3381
- this.interceptStream(process.stderr, "error")
3409
+ this.interceptStream(process.stderr, "error"),
3410
+ this.addProcessExitListeners()
3382
3411
  );
3383
- restoreCursor();
3384
3412
  this.write(HIDE_CURSOR, "output");
3385
3413
  this.start();
3386
3414
  }
@@ -3402,6 +3430,9 @@ class WindowRenderer {
3402
3430
  this.flushBuffer();
3403
3431
  clearInterval(this.renderInterval);
3404
3432
  }
3433
+ getColumns() {
3434
+ return "columns" in this.options.logger.outputStream ? this.options.logger.outputStream.columns : 80;
3435
+ }
3405
3436
  flushBuffer() {
3406
3437
  if (this.buffer.length === 0) {
3407
3438
  return this.render();
@@ -3429,10 +3460,10 @@ class WindowRenderer {
3429
3460
  return this.write(message || "", type);
3430
3461
  }
3431
3462
  const windowContent = this.options.getWindow();
3432
- const rowCount = getRenderedRowCount(windowContent, this.options.logger.outputStream);
3463
+ const rowCount = getRenderedRowCount(windowContent, this.getColumns());
3433
3464
  let padding = this.windowHeight - rowCount;
3434
3465
  if (padding > 0 && message) {
3435
- padding -= getRenderedRowCount([message], this.options.logger.outputStream);
3466
+ padding -= getRenderedRowCount([message], this.getColumns());
3436
3467
  }
3437
3468
  this.write(SYNC_START);
3438
3469
  this.clearWindow();
@@ -3475,10 +3506,27 @@ class WindowRenderer {
3475
3506
  write(message, type = "output") {
3476
3507
  this.streams[type](message);
3477
3508
  }
3509
+ addProcessExitListeners() {
3510
+ const onExit = (signal, exitCode) => {
3511
+ this.flushBuffer();
3512
+ this.stop();
3513
+ if (process.exitCode === void 0) {
3514
+ process.exitCode = exitCode !== void 0 ? 128 + exitCode : Number(signal);
3515
+ }
3516
+ process.exit();
3517
+ };
3518
+ process.once("SIGINT", onExit);
3519
+ process.once("SIGTERM", onExit);
3520
+ process.once("exit", onExit);
3521
+ return function cleanup() {
3522
+ process.off("SIGINT", onExit);
3523
+ process.off("SIGTERM", onExit);
3524
+ process.off("exit", onExit);
3525
+ };
3526
+ }
3478
3527
  }
3479
- function getRenderedRowCount(rows, stream) {
3528
+ function getRenderedRowCount(rows, columns) {
3480
3529
  let count = 0;
3481
- const columns = "columns" in stream ? stream.columns : 80;
3482
3530
  for (const row of rows) {
3483
3531
  const text = stripVTControlCharacters(row);
3484
3532
  count += Math.max(1, Math.ceil(text.length / columns));
@@ -3524,7 +3572,7 @@ class TaskParser {
3524
3572
  finishedTestFiles.push(task.file);
3525
3573
  }
3526
3574
  }
3527
- if (task?.type === "test" || task?.type === "custom") {
3575
+ if (task?.type === "test") {
3528
3576
  if (task.result?.state === "run") {
3529
3577
  startingTests.push(task);
3530
3578
  } else if (task.result?.hooks?.afterEach !== "run") {
@@ -3855,139 +3903,131 @@ class DefaultReporter extends BaseReporter {
3855
3903
  }
3856
3904
  }
3857
3905
 
3858
- const check = { char: "\xB7", color: c.green };
3859
- const cross = { char: "x", color: c.red };
3906
+ class DotReporter extends BaseReporter {
3907
+ summary;
3908
+ onInit(ctx) {
3909
+ super.onInit(ctx);
3910
+ if (this.isTTY) {
3911
+ this.summary = new DotSummary();
3912
+ this.summary.onInit(ctx);
3913
+ }
3914
+ }
3915
+ onTaskUpdate(packs) {
3916
+ this.summary?.onTaskUpdate(packs);
3917
+ if (!this.isTTY) {
3918
+ super.onTaskUpdate(packs);
3919
+ }
3920
+ }
3921
+ onWatcherRerun(files, trigger) {
3922
+ this.summary?.onWatcherRerun();
3923
+ super.onWatcherRerun(files, trigger);
3924
+ }
3925
+ onFinished(files, errors) {
3926
+ this.summary?.onFinished();
3927
+ super.onFinished(files, errors);
3928
+ }
3929
+ }
3930
+ class DotSummary extends TaskParser {
3931
+ renderer;
3932
+ tests = /* @__PURE__ */ new Map();
3933
+ finishedTests = /* @__PURE__ */ new Set();
3934
+ onInit(ctx) {
3935
+ this.ctx = ctx;
3936
+ this.renderer = new WindowRenderer({
3937
+ logger: ctx.logger,
3938
+ getWindow: () => this.createSummary()
3939
+ });
3940
+ this.ctx.onClose(() => this.renderer.stop());
3941
+ }
3942
+ onWatcherRerun() {
3943
+ this.tests.clear();
3944
+ this.renderer.start();
3945
+ }
3946
+ onFinished() {
3947
+ const finalLog = formatTests(Array.from(this.tests.values()));
3948
+ this.ctx.logger.log(finalLog);
3949
+ this.tests.clear();
3950
+ this.renderer.finish();
3951
+ }
3952
+ onTestFilePrepare(file) {
3953
+ for (const test of getTests(file)) {
3954
+ this.onTestStart(test);
3955
+ }
3956
+ }
3957
+ onTestStart(test) {
3958
+ if (this.finishedTests.has(test.id)) {
3959
+ return;
3960
+ }
3961
+ this.tests.set(test.id, test.mode || "run");
3962
+ }
3963
+ onTestFinished(test) {
3964
+ if (this.finishedTests.has(test.id)) {
3965
+ return;
3966
+ }
3967
+ this.finishedTests.add(test.id);
3968
+ this.tests.set(test.id, test.result?.state || "skip");
3969
+ }
3970
+ onTestFileFinished() {
3971
+ const columns = this.renderer.getColumns();
3972
+ if (this.tests.size < columns) {
3973
+ return;
3974
+ }
3975
+ const finishedTests = Array.from(this.tests).filter((entry) => entry[1] !== "run");
3976
+ if (finishedTests.length < columns) {
3977
+ return;
3978
+ }
3979
+ const states = [];
3980
+ let count = 0;
3981
+ for (const [id, state] of finishedTests) {
3982
+ if (count++ >= columns) {
3983
+ break;
3984
+ }
3985
+ this.tests.delete(id);
3986
+ states.push(state);
3987
+ }
3988
+ this.ctx.logger.log(formatTests(states));
3989
+ }
3990
+ createSummary() {
3991
+ return [
3992
+ formatTests(Array.from(this.tests.values())),
3993
+ ""
3994
+ ];
3995
+ }
3996
+ }
3997
+ const pass = { char: "\xB7", color: c.green };
3998
+ const fail = { char: "x", color: c.red };
3860
3999
  const pending = { char: "*", color: c.yellow };
3861
4000
  const skip = { char: "-", color: (char) => c.dim(c.gray(char)) };
3862
- function getIcon(task) {
3863
- if (task.mode === "skip" || task.mode === "todo") {
3864
- return skip;
3865
- }
3866
- switch (task.result?.state) {
4001
+ function getIcon(state) {
4002
+ switch (state) {
3867
4003
  case "pass":
3868
- return check;
4004
+ return pass;
3869
4005
  case "fail":
3870
- return cross;
4006
+ return fail;
4007
+ case "skip":
4008
+ case "todo":
4009
+ return skip;
3871
4010
  default:
3872
4011
  return pending;
3873
4012
  }
3874
4013
  }
3875
- function render(tasks, width) {
3876
- const all = getTests(tasks);
4014
+ function formatTests(states) {
3877
4015
  let currentIcon = pending;
3878
- let currentTasks = 0;
3879
- let previousLineWidth = 0;
4016
+ let count = 0;
3880
4017
  let output = "";
3881
- const addOutput = () => {
3882
- const { char, color } = currentIcon;
3883
- const availableWidth = width - previousLineWidth;
3884
- if (availableWidth > currentTasks) {
3885
- output += color(char.repeat(currentTasks));
3886
- previousLineWidth += currentTasks;
3887
- } else {
3888
- let buf = `${char.repeat(availableWidth)}
3889
- `;
3890
- const remaining = currentTasks - availableWidth;
3891
- const fullRows = Math.floor(remaining / width);
3892
- buf += `${char.repeat(width)}
3893
- `.repeat(fullRows);
3894
- const partialRow = remaining % width;
3895
- if (partialRow > 0) {
3896
- buf += char.repeat(partialRow);
3897
- previousLineWidth = partialRow;
3898
- } else {
3899
- previousLineWidth = 0;
3900
- }
3901
- output += color(buf);
3902
- }
3903
- };
3904
- for (const task of all) {
3905
- const icon = getIcon(task);
3906
- if (icon === currentIcon) {
3907
- currentTasks++;
4018
+ for (const state of states) {
4019
+ const icon = getIcon(state);
4020
+ if (currentIcon === icon) {
4021
+ count++;
3908
4022
  continue;
3909
4023
  }
3910
- addOutput();
3911
- currentTasks = 1;
4024
+ output += currentIcon.color(currentIcon.char.repeat(count));
4025
+ count = 1;
3912
4026
  currentIcon = icon;
3913
4027
  }
3914
- addOutput();
4028
+ output += currentIcon.color(currentIcon.char.repeat(count));
3915
4029
  return output;
3916
4030
  }
3917
- function createDotRenderer(_tasks, options) {
3918
- let tasks = _tasks;
3919
- let timer;
3920
- const { logUpdate: log, outputStream } = options.logger;
3921
- const columns = "columns" in outputStream ? outputStream.columns : 80;
3922
- function update() {
3923
- log(render(tasks, columns));
3924
- }
3925
- return {
3926
- start() {
3927
- if (timer) {
3928
- return this;
3929
- }
3930
- timer = setInterval(update, 16);
3931
- return this;
3932
- },
3933
- update(_tasks2) {
3934
- tasks = _tasks2;
3935
- return this;
3936
- },
3937
- async stop() {
3938
- if (timer) {
3939
- clearInterval(timer);
3940
- timer = void 0;
3941
- }
3942
- log.clear();
3943
- options.logger.log(render(tasks, columns));
3944
- return this;
3945
- },
3946
- clear() {
3947
- log.clear();
3948
- }
3949
- };
3950
- }
3951
-
3952
- class DotReporter extends BaseReporter {
3953
- renderer;
3954
- onTaskUpdate() {
3955
- }
3956
- onCollected() {
3957
- if (this.isTTY) {
3958
- const files = this.ctx.state.getFiles(this.watchFilters);
3959
- if (!this.renderer) {
3960
- this.renderer = createDotRenderer(files, {
3961
- logger: this.ctx.logger
3962
- }).start();
3963
- } else {
3964
- this.renderer.update(files);
3965
- }
3966
- }
3967
- }
3968
- async onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
3969
- await this.stopListRender();
3970
- this.ctx.logger.log();
3971
- super.onFinished(files, errors);
3972
- }
3973
- async onWatcherStart() {
3974
- await this.stopListRender();
3975
- super.onWatcherStart();
3976
- }
3977
- async stopListRender() {
3978
- this.renderer?.stop();
3979
- this.renderer = void 0;
3980
- await new Promise((resolve) => setTimeout(resolve, 10));
3981
- }
3982
- async onWatcherRerun(files, trigger) {
3983
- await this.stopListRender();
3984
- super.onWatcherRerun(files, trigger);
3985
- }
3986
- onUserConsoleLog(log) {
3987
- this.renderer?.clear();
3988
- super.onUserConsoleLog(log);
3989
- }
3990
- }
3991
4031
 
3992
4032
  class GithubActionsReporter {
3993
4033
  ctx = void 0;
@@ -3998,14 +4038,14 @@ class GithubActionsReporter {
3998
4038
  const projectErrors = new Array();
3999
4039
  for (const error of errors) {
4000
4040
  projectErrors.push({
4001
- project: this.ctx.getRootTestProject(),
4041
+ project: this.ctx.getRootProject(),
4002
4042
  title: "Unhandled error",
4003
4043
  error
4004
4044
  });
4005
4045
  }
4006
4046
  for (const file of files) {
4007
4047
  const tasks = getTasks(file);
4008
- const project = this.ctx.getProjectByTaskId(file.id);
4048
+ const project = this.ctx.getProjectByName(file.projectName || "");
4009
4049
  for (const task of tasks) {
4010
4050
  if (task.result?.state !== "fail") {
4011
4051
  continue;
@@ -4380,7 +4420,7 @@ class JUnitReporter {
4380
4420
  const result = capturePrintError(
4381
4421
  error,
4382
4422
  this.ctx,
4383
- { project: this.ctx.getProjectByTaskId(task.id), task }
4423
+ { project: this.ctx.getProjectByName(task.file.projectName || ""), task }
4384
4424
  );
4385
4425
  await this.baseLog(
4386
4426
  escapeXML(stripVTControlCharacters(result.output.trim()))
@@ -4390,98 +4430,473 @@ class JUnitReporter {
4390
4430
  }
4391
4431
  }
4392
4432
  }
4393
- );
4433
+ );
4434
+ }
4435
+ }
4436
+ async onFinished(files = this.ctx.state.getFiles()) {
4437
+ await this.logger.log('<?xml version="1.0" encoding="UTF-8" ?>');
4438
+ const transformed = files.map((file) => {
4439
+ const tasks = file.tasks.flatMap((task) => flattenTasks$1(task));
4440
+ const stats2 = tasks.reduce(
4441
+ (stats3, task) => {
4442
+ return {
4443
+ passed: stats3.passed + Number(task.result?.state === "pass"),
4444
+ failures: stats3.failures + Number(task.result?.state === "fail"),
4445
+ skipped: stats3.skipped + Number(task.mode === "skip" || task.mode === "todo")
4446
+ };
4447
+ },
4448
+ {
4449
+ passed: 0,
4450
+ failures: 0,
4451
+ skipped: 0
4452
+ }
4453
+ );
4454
+ const suites = getSuites(file);
4455
+ for (const suite of suites) {
4456
+ if (suite.result?.errors) {
4457
+ tasks.push(suite);
4458
+ stats2.failures += 1;
4459
+ }
4460
+ }
4461
+ if (tasks.length === 0 && file.result?.state === "fail") {
4462
+ stats2.failures = 1;
4463
+ tasks.push({
4464
+ id: file.id,
4465
+ type: "test",
4466
+ name: file.name,
4467
+ mode: "run",
4468
+ result: file.result,
4469
+ meta: {},
4470
+ // NOTE: not used in JUnitReporter
4471
+ context: null,
4472
+ suite: null,
4473
+ file: null
4474
+ });
4475
+ }
4476
+ return {
4477
+ ...file,
4478
+ tasks,
4479
+ stats: stats2
4480
+ };
4481
+ });
4482
+ const stats = transformed.reduce(
4483
+ (stats2, file) => {
4484
+ stats2.tests += file.tasks.length;
4485
+ stats2.failures += file.stats.failures;
4486
+ stats2.time += file.result?.duration || 0;
4487
+ return stats2;
4488
+ },
4489
+ {
4490
+ name: this.options.suiteName || "vitest tests",
4491
+ tests: 0,
4492
+ failures: 0,
4493
+ errors: 0,
4494
+ // we cannot detect those
4495
+ time: 0
4496
+ }
4497
+ );
4498
+ await this.writeElement("testsuites", { ...stats, time: executionTime(stats.time) }, async () => {
4499
+ for (const file of transformed) {
4500
+ const filename = relative(this.ctx.config.root, file.filepath);
4501
+ await this.writeElement(
4502
+ "testsuite",
4503
+ {
4504
+ name: filename,
4505
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4506
+ hostname: hostname(),
4507
+ tests: file.tasks.length,
4508
+ failures: file.stats.failures,
4509
+ errors: 0,
4510
+ // An errored test is one that had an unanticipated problem. We cannot detect those.
4511
+ skipped: file.stats.skipped,
4512
+ time: getDuration(file)
4513
+ },
4514
+ async () => {
4515
+ await this.writeTasks(file.tasks, filename);
4516
+ }
4517
+ );
4518
+ }
4519
+ });
4520
+ if (this.reportFile) {
4521
+ this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
4522
+ }
4523
+ await this.fileFd?.close();
4524
+ this.fileFd = void 0;
4525
+ }
4526
+ }
4527
+
4528
+ class ReportedTaskImplementation {
4529
+ /**
4530
+ * Task instance.
4531
+ * @internal
4532
+ */
4533
+ task;
4534
+ /**
4535
+ * The project assosiacted with the test or suite.
4536
+ */
4537
+ project;
4538
+ /**
4539
+ * Unique identifier.
4540
+ * This ID is deterministic and will be the same for the same test across multiple runs.
4541
+ * The ID is based on the project name, module url and test order.
4542
+ */
4543
+ id;
4544
+ /**
4545
+ * Location in the module where the test or suite is defined.
4546
+ */
4547
+ location;
4548
+ /** @internal */
4549
+ constructor(task, project) {
4550
+ this.task = task;
4551
+ this.project = project;
4552
+ this.id = task.id;
4553
+ this.location = task.location;
4554
+ }
4555
+ /**
4556
+ * Checks if the test did not fail the suite.
4557
+ * If the test is not finished yet or was skipped, it will return `true`.
4558
+ */
4559
+ ok() {
4560
+ const result = this.task.result;
4561
+ return !result || result.state !== "fail";
4562
+ }
4563
+ /**
4564
+ * Creates a new reported task instance and stores it in the project's state for future use.
4565
+ * @internal
4566
+ */
4567
+ static register(task, project) {
4568
+ const state = new this(task, project);
4569
+ storeTask(project, task, state);
4570
+ return state;
4571
+ }
4572
+ }
4573
+ class TestCase extends ReportedTaskImplementation {
4574
+ #fullName;
4575
+ type = "test";
4576
+ /**
4577
+ * Direct reference to the test module where the test or suite is defined.
4578
+ */
4579
+ module;
4580
+ /**
4581
+ * Name of the test.
4582
+ */
4583
+ name;
4584
+ /**
4585
+ * Options that the test was initiated with.
4586
+ */
4587
+ options;
4588
+ /**
4589
+ * Parent suite. If the test was called directly inside the module, the parent will be the module itself.
4590
+ */
4591
+ parent;
4592
+ /** @internal */
4593
+ constructor(task, project) {
4594
+ super(task, project);
4595
+ this.name = task.name;
4596
+ this.module = getReportedTask(project, task.file);
4597
+ const suite = this.task.suite;
4598
+ if (suite) {
4599
+ this.parent = getReportedTask(project, suite);
4600
+ } else {
4601
+ this.parent = this.module;
4602
+ }
4603
+ this.options = buildOptions(task);
4604
+ }
4605
+ /**
4606
+ * Full name of the test including all parent suites separated with `>`.
4607
+ */
4608
+ get fullName() {
4609
+ if (this.#fullName === void 0) {
4610
+ if (this.parent.type !== "module") {
4611
+ this.#fullName = `${this.parent.fullName} > ${this.name}`;
4612
+ } else {
4613
+ this.#fullName = this.name;
4614
+ }
4615
+ }
4616
+ return this.#fullName;
4617
+ }
4618
+ /**
4619
+ * Test results. Will be `undefined` if test is skipped, not finished yet or was just collected.
4620
+ */
4621
+ result() {
4622
+ const result = this.task.result;
4623
+ if (!result || result.state === "run") {
4624
+ return void 0;
4625
+ }
4626
+ const state = result.state === "fail" ? "failed" : result.state === "pass" ? "passed" : "skipped";
4627
+ if (state === "skipped") {
4628
+ return {
4629
+ state,
4630
+ note: result.note,
4631
+ errors: void 0
4632
+ };
4633
+ }
4634
+ if (state === "passed") {
4635
+ return {
4636
+ state,
4637
+ errors: result.errors
4638
+ };
4639
+ }
4640
+ return {
4641
+ state,
4642
+ errors: result.errors || []
4643
+ };
4644
+ }
4645
+ /**
4646
+ * Checks if the test was skipped during collection or dynamically with `ctx.skip()`.
4647
+ */
4648
+ skipped() {
4649
+ const mode = this.task.result?.state || this.task.mode;
4650
+ return mode === "skip" || mode === "todo";
4651
+ }
4652
+ /**
4653
+ * Custom metadata that was attached to the test during its execution.
4654
+ */
4655
+ meta() {
4656
+ return this.task.meta;
4657
+ }
4658
+ /**
4659
+ * Useful information about the test like duration, memory usage, etc.
4660
+ * Diagnostic is only available after the test has finished.
4661
+ */
4662
+ diagnostic() {
4663
+ const result = this.task.result;
4664
+ if (!result || result.state === "run" || !result.startTime) {
4665
+ return void 0;
4666
+ }
4667
+ const duration = result.duration || 0;
4668
+ const slow = duration > this.project.globalConfig.slowTestThreshold;
4669
+ return {
4670
+ slow,
4671
+ heap: result.heap,
4672
+ duration,
4673
+ startTime: result.startTime,
4674
+ retryCount: result.retryCount ?? 0,
4675
+ repeatCount: result.repeatCount ?? 0,
4676
+ flaky: !!result.retryCount && result.state === "pass" && result.retryCount > 0
4677
+ };
4678
+ }
4679
+ }
4680
+ class TestCollection {
4681
+ #task;
4682
+ #project;
4683
+ constructor(task, project) {
4684
+ this.#task = task;
4685
+ this.#project = project;
4686
+ }
4687
+ /**
4688
+ * Returns the test or suite at a specific index.
4689
+ */
4690
+ at(index) {
4691
+ if (index < 0) {
4692
+ index = this.size + index;
4693
+ }
4694
+ return getReportedTask(this.#project, this.#task.tasks[index]);
4695
+ }
4696
+ /**
4697
+ * The number of tests and suites in the collection.
4698
+ */
4699
+ get size() {
4700
+ return this.#task.tasks.length;
4701
+ }
4702
+ /**
4703
+ * Returns the collection in array form for easier manipulation.
4704
+ */
4705
+ array() {
4706
+ return Array.from(this);
4707
+ }
4708
+ /**
4709
+ * Filters all tests that are part of this collection and its children.
4710
+ */
4711
+ *allTests(state) {
4712
+ for (const child of this) {
4713
+ if (child.type === "suite") {
4714
+ yield* child.children.allTests(state);
4715
+ } else if (state) {
4716
+ const testState = getTestState(child);
4717
+ if (state === testState) {
4718
+ yield child;
4719
+ }
4720
+ } else {
4721
+ yield child;
4722
+ }
4394
4723
  }
4395
4724
  }
4396
- async onFinished(files = this.ctx.state.getFiles()) {
4397
- await this.logger.log('<?xml version="1.0" encoding="UTF-8" ?>');
4398
- const transformed = files.map((file) => {
4399
- const tasks = file.tasks.flatMap((task) => flattenTasks$1(task));
4400
- const stats2 = tasks.reduce(
4401
- (stats3, task) => {
4402
- return {
4403
- passed: stats3.passed + Number(task.result?.state === "pass"),
4404
- failures: stats3.failures + Number(task.result?.state === "fail"),
4405
- skipped: stats3.skipped + Number(task.mode === "skip" || task.mode === "todo")
4406
- };
4407
- },
4408
- {
4409
- passed: 0,
4410
- failures: 0,
4411
- skipped: 0
4412
- }
4413
- );
4414
- const suites = getSuites(file);
4415
- for (const suite of suites) {
4416
- if (suite.result?.errors) {
4417
- tasks.push(suite);
4418
- stats2.failures += 1;
4725
+ /**
4726
+ * Filters only the tests that are part of this collection.
4727
+ */
4728
+ *tests(state) {
4729
+ for (const child of this) {
4730
+ if (child.type !== "test") {
4731
+ continue;
4732
+ }
4733
+ if (state) {
4734
+ const testState = getTestState(child);
4735
+ if (state === testState) {
4736
+ yield child;
4419
4737
  }
4738
+ } else {
4739
+ yield child;
4420
4740
  }
4421
- if (tasks.length === 0 && file.result?.state === "fail") {
4422
- stats2.failures = 1;
4423
- tasks.push({
4424
- id: file.id,
4425
- type: "test",
4426
- name: file.name,
4427
- mode: "run",
4428
- result: file.result,
4429
- meta: {},
4430
- // NOTE: not used in JUnitReporter
4431
- context: null,
4432
- suite: null,
4433
- file: null
4434
- });
4741
+ }
4742
+ }
4743
+ /**
4744
+ * Filters only the suites that are part of this collection.
4745
+ */
4746
+ *suites() {
4747
+ for (const child of this) {
4748
+ if (child.type === "suite") {
4749
+ yield child;
4435
4750
  }
4436
- return {
4437
- ...file,
4438
- tasks,
4439
- stats: stats2
4440
- };
4441
- });
4442
- const stats = transformed.reduce(
4443
- (stats2, file) => {
4444
- stats2.tests += file.tasks.length;
4445
- stats2.failures += file.stats.failures;
4446
- return stats2;
4447
- },
4448
- {
4449
- name: this.options.suiteName || "vitest tests",
4450
- tests: 0,
4451
- failures: 0,
4452
- errors: 0,
4453
- // we cannot detect those
4454
- time: executionTime((/* @__PURE__ */ new Date()).getTime() - this._timeStart.getTime())
4751
+ }
4752
+ }
4753
+ /**
4754
+ * Filters all suites that are part of this collection and its children.
4755
+ */
4756
+ *allSuites() {
4757
+ for (const child of this) {
4758
+ if (child.type === "suite") {
4759
+ yield child;
4760
+ yield* child.children.allSuites();
4455
4761
  }
4456
- );
4457
- await this.writeElement("testsuites", stats, async () => {
4458
- for (const file of transformed) {
4459
- const filename = relative(this.ctx.config.root, file.filepath);
4460
- await this.writeElement(
4461
- "testsuite",
4462
- {
4463
- name: filename,
4464
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4465
- hostname: hostname(),
4466
- tests: file.tasks.length,
4467
- failures: file.stats.failures,
4468
- errors: 0,
4469
- // An errored test is one that had an unanticipated problem. We cannot detect those.
4470
- skipped: file.stats.skipped,
4471
- time: getDuration(file)
4472
- },
4473
- async () => {
4474
- await this.writeTasks(file.tasks, filename);
4475
- }
4476
- );
4762
+ }
4763
+ }
4764
+ *[Symbol.iterator]() {
4765
+ for (const task of this.#task.tasks) {
4766
+ yield getReportedTask(this.#project, task);
4767
+ }
4768
+ }
4769
+ }
4770
+ class SuiteImplementation extends ReportedTaskImplementation {
4771
+ /**
4772
+ * Collection of suites and tests that are part of this suite.
4773
+ */
4774
+ children;
4775
+ /** @internal */
4776
+ constructor(task, project) {
4777
+ super(task, project);
4778
+ this.children = new TestCollection(task, project);
4779
+ }
4780
+ /**
4781
+ * Checks if the suite was skipped during collection.
4782
+ */
4783
+ skipped() {
4784
+ const mode = this.task.mode;
4785
+ return mode === "skip" || mode === "todo";
4786
+ }
4787
+ /**
4788
+ * Errors that happened outside of the test run during collection, like syntax errors.
4789
+ */
4790
+ errors() {
4791
+ return this.task.result?.errors || [];
4792
+ }
4793
+ }
4794
+ class TestSuite extends SuiteImplementation {
4795
+ #fullName;
4796
+ type = "suite";
4797
+ /**
4798
+ * Name of the test or the suite.
4799
+ */
4800
+ name;
4801
+ /**
4802
+ * Direct reference to the test module where the test or suite is defined.
4803
+ */
4804
+ module;
4805
+ /**
4806
+ * Parent suite. If suite was called directly inside the module, the parent will be the module itself.
4807
+ */
4808
+ parent;
4809
+ /**
4810
+ * Options that suite was initiated with.
4811
+ */
4812
+ options;
4813
+ /** @internal */
4814
+ constructor(task, project) {
4815
+ super(task, project);
4816
+ this.name = task.name;
4817
+ this.module = getReportedTask(project, task.file);
4818
+ const suite = this.task.suite;
4819
+ if (suite) {
4820
+ this.parent = getReportedTask(project, suite);
4821
+ } else {
4822
+ this.parent = this.module;
4823
+ }
4824
+ this.options = buildOptions(task);
4825
+ }
4826
+ /**
4827
+ * Full name of the suite including all parent suites separated with `>`.
4828
+ */
4829
+ get fullName() {
4830
+ if (this.#fullName === void 0) {
4831
+ if (this.parent.type !== "module") {
4832
+ this.#fullName = `${this.parent.fullName} > ${this.name}`;
4833
+ } else {
4834
+ this.#fullName = this.name;
4477
4835
  }
4478
- });
4479
- if (this.reportFile) {
4480
- this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
4481
4836
  }
4482
- await this.fileFd?.close();
4483
- this.fileFd = void 0;
4837
+ return this.#fullName;
4838
+ }
4839
+ }
4840
+ class TestModule extends SuiteImplementation {
4841
+ type = "module";
4842
+ /**
4843
+ * This is usually an absolute UNIX file path.
4844
+ * It can be a virtual id if the file is not on the disk.
4845
+ * This value corresponds to Vite's `ModuleGraph` id.
4846
+ */
4847
+ moduleId;
4848
+ /** @internal */
4849
+ constructor(task, project) {
4850
+ super(task, project);
4851
+ this.moduleId = task.filepath;
4852
+ }
4853
+ /**
4854
+ * Useful information about the module like duration, memory usage, etc.
4855
+ * If the module was not executed yet, all diagnostic values will return `0`.
4856
+ */
4857
+ diagnostic() {
4858
+ const setupDuration = this.task.setupDuration || 0;
4859
+ const collectDuration = this.task.collectDuration || 0;
4860
+ const prepareDuration = this.task.prepareDuration || 0;
4861
+ const environmentSetupDuration = this.task.environmentLoad || 0;
4862
+ const duration = this.task.result?.duration || 0;
4863
+ return {
4864
+ environmentSetupDuration,
4865
+ prepareDuration,
4866
+ collectDuration,
4867
+ setupDuration,
4868
+ duration
4869
+ };
4870
+ }
4871
+ }
4872
+ function buildOptions(task) {
4873
+ return {
4874
+ each: task.each,
4875
+ concurrent: task.concurrent,
4876
+ shuffle: task.shuffle,
4877
+ retry: task.retry,
4878
+ repeats: task.repeats,
4879
+ mode: task.mode
4880
+ };
4881
+ }
4882
+ function getTestState(test) {
4883
+ if (test.skipped()) {
4884
+ return "skipped";
4885
+ }
4886
+ const result = test.result();
4887
+ return result ? result.state : "running";
4888
+ }
4889
+ function storeTask(project, runnerTask, reportedTask) {
4890
+ project.vitest.state.reportedTasksMap.set(runnerTask, reportedTask);
4891
+ }
4892
+ function getReportedTask(project, runnerTask) {
4893
+ const reportedTask = project.vitest.state.getReportedEntity(runnerTask);
4894
+ if (!reportedTask) {
4895
+ throw new Error(
4896
+ `Task instance was not found for ${runnerTask.type} "${runnerTask.name}"`
4897
+ );
4484
4898
  }
4899
+ return reportedTask;
4485
4900
  }
4486
4901
 
4487
4902
  function yamlString(str) {
@@ -4532,7 +4947,7 @@ class TapReporter {
4532
4947
  this.logger.log("}");
4533
4948
  } else {
4534
4949
  this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
4535
- const project = this.ctx.getProjectByTaskId(task.id);
4950
+ const project = this.ctx.getProjectByName(task.file.projectName || "");
4536
4951
  if (task.result?.state === "fail" && task.result.errors) {
4537
4952
  this.logger.indent();
4538
4953
  task.result.errors.forEach((error) => {