vitest 3.0.0-beta.1 → 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 (41) hide show
  1. package/dist/browser.d.ts +12 -9
  2. package/dist/browser.js +2 -2
  3. package/dist/chunks/{base.CkcgFVQd.js → base.CUgXReRN.js} +1 -1
  4. package/dist/chunks/{cac.CWCZimpS.js → cac.Xzv7eNWw.js} +18 -13
  5. package/dist/chunks/{cli-api.BKUOv0Nc.js → cli-api.CETCDGgZ.js} +915 -531
  6. package/dist/chunks/{coverage.BoMDb1ip.js → coverage.BWeNbfBa.js} +4 -4
  7. package/dist/chunks/{environment.CT0jpO-1.d.ts → environment.d8YfPkTm.d.ts} +1 -3
  8. package/dist/chunks/{globals.DJTzb7B3.js → globals.BFncSRNA.js} +2 -2
  9. package/dist/chunks/{index.DKe7vK-G.js → index.9ZEBV_TJ.js} +635 -606
  10. package/dist/chunks/{index.BqHViJW9.js → index.CkWmZCXU.js} +1 -1
  11. package/dist/chunks/{index.CkOJwybT.js → index.DoV7W5gc.js} +2 -2
  12. package/dist/chunks/{reporters.BZbwTvrM.d.ts → reporters.DTtxC3KQ.d.ts} +447 -374
  13. package/dist/chunks/{resolveConfig.3rGGWga5.js → resolveConfig.BA-_OKEx.js} +5529 -5532
  14. package/dist/chunks/{runBaseTests.C6huCAng.js → runBaseTests.D0dWpzZV.js} +11 -10
  15. package/dist/chunks/{setup-common.B5ClyS48.js → setup-common.Cp_bu5q3.js} +1 -1
  16. package/dist/chunks/types.BOjykUpq.d.ts +27 -0
  17. package/dist/chunks/{vi.CZKezqeD.js → vi.S4Fq8wSo.js} +2 -1
  18. package/dist/chunks/{vite.DIfmneq0.d.ts → vite.CXaetSK3.d.ts} +1 -1
  19. package/dist/chunks/{worker.umPNbBNk.d.ts → worker.ClntunZp.d.ts} +1 -1
  20. package/dist/chunks/{worker.CmzGeuVD.d.ts → worker.o1PBoDdo.d.ts} +3 -3
  21. package/dist/cli.js +1 -1
  22. package/dist/config.d.ts +6 -8
  23. package/dist/coverage.d.ts +4 -6
  24. package/dist/coverage.js +1 -1
  25. package/dist/environments.d.ts +2 -2
  26. package/dist/execute.d.ts +2 -2
  27. package/dist/index.d.ts +11 -14
  28. package/dist/index.js +2 -2
  29. package/dist/node.d.ts +22 -17
  30. package/dist/node.js +65 -31
  31. package/dist/reporters.d.ts +4 -6
  32. package/dist/reporters.js +1 -1
  33. package/dist/runners.d.ts +2 -2
  34. package/dist/runners.js +1 -1
  35. package/dist/suite.d.ts +1 -1
  36. package/dist/workers/forks.js +1 -1
  37. package/dist/workers/runVmTests.js +7 -7
  38. package/dist/workers/threads.js +1 -1
  39. package/dist/workers.d.ts +3 -3
  40. package/dist/workers.js +1 -1
  41. package/package.json +13 -13
@@ -20,351 +20,6 @@ import require$$0$1 from 'events';
20
20
  import { createRequire } from 'node:module';
21
21
  import { hostname } from 'node:os';
22
22
 
23
- class ReportedTaskImplementation {
24
- /**
25
- * Task instance.
26
- * @experimental Public runner task API is experimental and does not follow semver.
27
- */
28
- task;
29
- /**
30
- * The project assosiacted with the test or suite.
31
- */
32
- project;
33
- /**
34
- * Unique identifier.
35
- * This ID is deterministic and will be the same for the same test across multiple runs.
36
- * The ID is based on the project name, module url and test position.
37
- */
38
- id;
39
- /**
40
- * Location in the module where the test or suite is defined.
41
- */
42
- location;
43
- constructor(task, project) {
44
- this.task = task;
45
- this.project = project;
46
- this.id = task.id;
47
- this.location = task.location;
48
- }
49
- /**
50
- * Creates a new reported task instance and stores it in the project's state for future use.
51
- */
52
- static register(task, project) {
53
- const state = new this(task, project);
54
- storeTask(project, task, state);
55
- return state;
56
- }
57
- }
58
- class TestCase extends ReportedTaskImplementation {
59
- #fullName;
60
- type = "test";
61
- /**
62
- * Direct reference to the test module where the test or suite is defined.
63
- */
64
- module;
65
- /**
66
- * Name of the test.
67
- */
68
- name;
69
- /**
70
- * Options that the test was initiated with.
71
- */
72
- options;
73
- /**
74
- * Parent suite. If the test was called directly inside the module, the parent will be the module itself.
75
- */
76
- parent;
77
- constructor(task, project) {
78
- super(task, project);
79
- this.name = task.name;
80
- this.module = getReportedTask(project, task.file);
81
- const suite = this.task.suite;
82
- if (suite) {
83
- this.parent = getReportedTask(project, suite);
84
- } else {
85
- this.parent = this.module;
86
- }
87
- this.options = buildOptions(task);
88
- }
89
- /**
90
- * Full name of the test including all parent suites separated with `>`.
91
- */
92
- get fullName() {
93
- if (this.#fullName === void 0) {
94
- if (this.parent.type !== "module") {
95
- this.#fullName = `${this.parent.fullName} > ${this.name}`;
96
- } else {
97
- this.#fullName = this.name;
98
- }
99
- }
100
- return this.#fullName;
101
- }
102
- /**
103
- * Test results. Will be `undefined` if test is not finished yet or was just collected.
104
- */
105
- result() {
106
- const result = this.task.result;
107
- if (!result || result.state === "run") {
108
- return void 0;
109
- }
110
- const state = result.state === "fail" ? "failed" : result.state === "pass" ? "passed" : "skipped";
111
- if (state === "skipped") {
112
- return {
113
- state,
114
- note: result.note,
115
- errors: void 0
116
- };
117
- }
118
- if (state === "passed") {
119
- return {
120
- state,
121
- errors: result.errors
122
- };
123
- }
124
- return {
125
- state,
126
- errors: result.errors || []
127
- };
128
- }
129
- /**
130
- * Checks if the test did not fail the suite.
131
- * If the test is not finished yet or was skipped, it will return `true`.
132
- */
133
- ok() {
134
- const result = this.result();
135
- return !result || result.state !== "failed";
136
- }
137
- /**
138
- * Custom metadata that was attached to the test during its execution.
139
- */
140
- meta() {
141
- return this.task.meta;
142
- }
143
- /**
144
- * Useful information about the test like duration, memory usage, etc.
145
- * Diagnostic is only available after the test has finished.
146
- */
147
- diagnostic() {
148
- const result = this.task.result;
149
- if (!result || result.state === "run" || !result.startTime) {
150
- return void 0;
151
- }
152
- const duration = result.duration || 0;
153
- const slow = duration > this.project.globalConfig.slowTestThreshold;
154
- return {
155
- slow,
156
- heap: result.heap,
157
- duration,
158
- startTime: result.startTime,
159
- retryCount: result.retryCount ?? 0,
160
- repeatCount: result.repeatCount ?? 0,
161
- flaky: !!result.retryCount && result.state === "pass" && result.retryCount > 0
162
- };
163
- }
164
- }
165
- class TestCollection {
166
- #task;
167
- #project;
168
- constructor(task, project) {
169
- this.#task = task;
170
- this.#project = project;
171
- }
172
- /**
173
- * Returns the test or suite at a specific index in the array.
174
- */
175
- at(index) {
176
- if (index < 0) {
177
- index = this.size + index;
178
- }
179
- return getReportedTask(this.#project, this.#task.tasks[index]);
180
- }
181
- /**
182
- * The number of tests and suites in the collection.
183
- */
184
- get size() {
185
- return this.#task.tasks.length;
186
- }
187
- /**
188
- * Returns the collection in array form for easier manipulation.
189
- */
190
- array() {
191
- return Array.from(this);
192
- }
193
- /**
194
- * Filters all tests that are part of this collection and its children.
195
- */
196
- *allTests(state) {
197
- for (const child of this) {
198
- if (child.type === "suite") {
199
- yield* child.children.allTests(state);
200
- } else if (state) {
201
- const testState = getTestState(child);
202
- if (state === testState) {
203
- yield child;
204
- }
205
- } else {
206
- yield child;
207
- }
208
- }
209
- }
210
- /**
211
- * Filters only the tests that are part of this collection.
212
- */
213
- *tests(state) {
214
- for (const child of this) {
215
- if (child.type !== "test") {
216
- continue;
217
- }
218
- if (state) {
219
- const testState = getTestState(child);
220
- if (state === testState) {
221
- yield child;
222
- }
223
- } else {
224
- yield child;
225
- }
226
- }
227
- }
228
- /**
229
- * Filters only the suites that are part of this collection.
230
- */
231
- *suites() {
232
- for (const child of this) {
233
- if (child.type === "suite") {
234
- yield child;
235
- }
236
- }
237
- }
238
- /**
239
- * Filters all suites that are part of this collection and its children.
240
- */
241
- *allSuites() {
242
- for (const child of this) {
243
- if (child.type === "suite") {
244
- yield child;
245
- yield* child.children.allSuites();
246
- }
247
- }
248
- }
249
- *[Symbol.iterator]() {
250
- for (const task of this.#task.tasks) {
251
- yield getReportedTask(this.#project, task);
252
- }
253
- }
254
- }
255
- class SuiteImplementation extends ReportedTaskImplementation {
256
- /**
257
- * Collection of suites and tests that are part of this suite.
258
- */
259
- children;
260
- constructor(task, project) {
261
- super(task, project);
262
- this.children = new TestCollection(task, project);
263
- }
264
- }
265
- class TestSuite extends SuiteImplementation {
266
- #fullName;
267
- type = "suite";
268
- /**
269
- * Name of the test or the suite.
270
- */
271
- name;
272
- /**
273
- * Direct reference to the test module where the test or suite is defined.
274
- */
275
- module;
276
- /**
277
- * Parent suite. If suite was called directly inside the module, the parent will be the module itself.
278
- */
279
- parent;
280
- /**
281
- * Options that suite was initiated with.
282
- */
283
- options;
284
- constructor(task, project) {
285
- super(task, project);
286
- this.name = task.name;
287
- this.module = getReportedTask(project, task.file);
288
- const suite = this.task.suite;
289
- if (suite) {
290
- this.parent = getReportedTask(project, suite);
291
- } else {
292
- this.parent = this.module;
293
- }
294
- this.options = buildOptions(task);
295
- }
296
- /**
297
- * Full name of the suite including all parent suites separated with `>`.
298
- */
299
- get fullName() {
300
- if (this.#fullName === void 0) {
301
- if (this.parent.type !== "module") {
302
- this.#fullName = `${this.parent.fullName} > ${this.name}`;
303
- } else {
304
- this.#fullName = this.name;
305
- }
306
- }
307
- return this.#fullName;
308
- }
309
- }
310
- class TestModule extends SuiteImplementation {
311
- type = "module";
312
- /**
313
- * This is usually an absolute UNIX file path.
314
- * It can be a virtual id if the file is not on the disk.
315
- * This value corresponds to Vite's `ModuleGraph` id.
316
- */
317
- moduleId;
318
- constructor(task, project) {
319
- super(task, project);
320
- this.moduleId = task.filepath;
321
- }
322
- /**
323
- * Useful information about the module like duration, memory usage, etc.
324
- * If the module was not executed yet, all diagnostic values will return `0`.
325
- */
326
- diagnostic() {
327
- const setupDuration = this.task.setupDuration || 0;
328
- const collectDuration = this.task.collectDuration || 0;
329
- const prepareDuration = this.task.prepareDuration || 0;
330
- const environmentSetupDuration = this.task.environmentLoad || 0;
331
- const duration = this.task.result?.duration || 0;
332
- return {
333
- environmentSetupDuration,
334
- prepareDuration,
335
- collectDuration,
336
- setupDuration,
337
- duration
338
- };
339
- }
340
- }
341
- function buildOptions(task) {
342
- return {
343
- each: task.each,
344
- concurrent: task.concurrent,
345
- shuffle: task.shuffle,
346
- retry: task.retry,
347
- repeats: task.repeats,
348
- mode: task.mode
349
- };
350
- }
351
- function getTestState(test) {
352
- const result = test.result();
353
- return result ? result.state : "running";
354
- }
355
- function storeTask(project, runnerTask, reportedTask) {
356
- project.vitest.state.reportedTasksMap.set(runnerTask, reportedTask);
357
- }
358
- function getReportedTask(project, runnerTask) {
359
- const reportedTask = project.vitest.state.getReportedEntity(runnerTask);
360
- if (!reportedTask) {
361
- throw new Error(
362
- `Task instance was not found for ${runnerTask.type} "${runnerTask.name}"`
363
- );
364
- }
365
- return reportedTask;
366
- }
367
-
368
23
  /// <reference types="../types/index.d.ts" />
369
24
 
370
25
  // (c) 2020-present Andrea Giammarchi
@@ -3474,7 +3129,7 @@ class BaseReporter {
3474
3129
  this.log(BADGE_PADDING + c.dim(" Project name: ") + c.blue(toArray(this.ctx.configOverride.project).join(", ")));
3475
3130
  }
3476
3131
  if (this.ctx.filenamePattern) {
3477
- 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(", ")));
3478
3133
  }
3479
3134
  if (this.ctx.configOverride.testNamePattern) {
3480
3135
  this.log(BADGE_PADDING + c.dim(" Test name pattern: ") + c.blue(String(this.ctx.configOverride.testNamePattern)));
@@ -3505,7 +3160,7 @@ class BaseReporter {
3505
3160
  if (log.browser) {
3506
3161
  write("\n");
3507
3162
  }
3508
- 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();
3509
3164
  const stack = log.browser ? project.browser?.parseStacktrace(log.origin) || [] : parseStacktrace(log.origin);
3510
3165
  const highlight = task && stack.find((i) => i.file === task.file.filepath);
3511
3166
  for (const frame of stack) {
@@ -3687,7 +3342,7 @@ ${errorBanner(`Failed Tests ${failedTests.length}`)}
3687
3342
  }
3688
3343
  const screenshotPaths = tasks2.map((t) => t.meta?.failScreenshotPath).filter((screenshot) => screenshot != null);
3689
3344
  this.ctx.logger.printError(error, {
3690
- project: this.ctx.getProjectByTaskId(tasks2[0].id),
3345
+ project: this.ctx.getProjectByName(tasks2[0].file.projectName || ""),
3691
3346
  verbose: this.verbose,
3692
3347
  screenshotPaths,
3693
3348
  task: tasks2[0]
@@ -4383,14 +4038,14 @@ class GithubActionsReporter {
4383
4038
  const projectErrors = new Array();
4384
4039
  for (const error of errors) {
4385
4040
  projectErrors.push({
4386
- project: this.ctx.getRootTestProject(),
4041
+ project: this.ctx.getRootProject(),
4387
4042
  title: "Unhandled error",
4388
4043
  error
4389
4044
  });
4390
4045
  }
4391
4046
  for (const file of files) {
4392
4047
  const tasks = getTasks(file);
4393
- const project = this.ctx.getProjectByTaskId(file.id);
4048
+ const project = this.ctx.getProjectByName(file.projectName || "");
4394
4049
  for (const task of tasks) {
4395
4050
  if (task.result?.state !== "fail") {
4396
4051
  continue;
@@ -4581,293 +4236,667 @@ class JsonReporter {
4581
4236
  if (!existsSync(outputDirectory)) {
4582
4237
  await promises.mkdir(outputDirectory, { recursive: true });
4583
4238
  }
4584
- await promises.writeFile(reportFile, report, "utf-8");
4585
- this.ctx.logger.log(`JSON report written to ${reportFile}`);
4239
+ await promises.writeFile(reportFile, report, "utf-8");
4240
+ this.ctx.logger.log(`JSON report written to ${reportFile}`);
4241
+ } else {
4242
+ this.ctx.logger.log(report);
4243
+ }
4244
+ }
4245
+ }
4246
+
4247
+ class IndentedLogger {
4248
+ constructor(baseLog) {
4249
+ this.baseLog = baseLog;
4250
+ }
4251
+ currentIndent = "";
4252
+ indent() {
4253
+ this.currentIndent += " ";
4254
+ }
4255
+ unindent() {
4256
+ this.currentIndent = this.currentIndent.substring(
4257
+ 0,
4258
+ this.currentIndent.length - 4
4259
+ );
4260
+ }
4261
+ log(text) {
4262
+ return this.baseLog(this.currentIndent + text);
4263
+ }
4264
+ }
4265
+
4266
+ function flattenTasks$1(task, baseName = "") {
4267
+ const base = baseName ? `${baseName} > ` : "";
4268
+ if (task.type === "suite") {
4269
+ return task.tasks.flatMap(
4270
+ (child) => flattenTasks$1(child, `${base}${task.name}`)
4271
+ );
4272
+ } else {
4273
+ return [
4274
+ {
4275
+ ...task,
4276
+ name: `${base}${task.name}`
4277
+ }
4278
+ ];
4279
+ }
4280
+ }
4281
+ function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
4282
+ let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
4283
+ value = String(value || "").replace(regex, "");
4284
+ {
4285
+ regex = new RegExp(
4286
+ /* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
4287
+ "([\\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]))",
4288
+ "g"
4289
+ /* eslint-enable */
4290
+ );
4291
+ value = value.replace(regex, "");
4292
+ }
4293
+ return value;
4294
+ }
4295
+ function escapeXML(value) {
4296
+ return removeInvalidXMLCharacters(
4297
+ String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
4298
+ }
4299
+ function executionTime(durationMS) {
4300
+ return (durationMS / 1e3).toLocaleString("en-US", {
4301
+ useGrouping: false,
4302
+ maximumFractionDigits: 10
4303
+ });
4304
+ }
4305
+ function getDuration(task) {
4306
+ const duration = task.result?.duration ?? 0;
4307
+ return executionTime(duration);
4308
+ }
4309
+ class JUnitReporter {
4310
+ ctx;
4311
+ reportFile;
4312
+ baseLog;
4313
+ logger;
4314
+ _timeStart = /* @__PURE__ */ new Date();
4315
+ fileFd;
4316
+ options;
4317
+ constructor(options) {
4318
+ this.options = { ...options };
4319
+ this.options.includeConsoleOutput ??= true;
4320
+ }
4321
+ async onInit(ctx) {
4322
+ this.ctx = ctx;
4323
+ const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "junit");
4324
+ if (outputFile) {
4325
+ this.reportFile = resolve(this.ctx.config.root, outputFile);
4326
+ const outputDirectory = dirname(this.reportFile);
4327
+ if (!existsSync(outputDirectory)) {
4328
+ await promises.mkdir(outputDirectory, { recursive: true });
4329
+ }
4330
+ const fileFd = await promises.open(this.reportFile, "w+");
4331
+ this.fileFd = fileFd;
4332
+ this.baseLog = async (text) => {
4333
+ if (!this.fileFd) {
4334
+ this.fileFd = await promises.open(this.reportFile, "w+");
4335
+ }
4336
+ await promises.writeFile(this.fileFd, `${text}
4337
+ `);
4338
+ };
4586
4339
  } else {
4587
- this.ctx.logger.log(report);
4340
+ this.baseLog = async (text) => this.ctx.logger.log(text);
4588
4341
  }
4342
+ this._timeStart = /* @__PURE__ */ new Date();
4343
+ this.logger = new IndentedLogger(this.baseLog);
4589
4344
  }
4590
- }
4591
-
4592
- class IndentedLogger {
4593
- constructor(baseLog) {
4594
- this.baseLog = baseLog;
4595
- }
4596
- currentIndent = "";
4597
- indent() {
4598
- this.currentIndent += " ";
4599
- }
4600
- unindent() {
4601
- this.currentIndent = this.currentIndent.substring(
4602
- 0,
4603
- this.currentIndent.length - 4
4345
+ async writeElement(name, attrs, children) {
4346
+ const pairs = [];
4347
+ for (const key in attrs) {
4348
+ const attr = attrs[key];
4349
+ if (attr === void 0) {
4350
+ continue;
4351
+ }
4352
+ pairs.push(`${key}="${escapeXML(attr)}"`);
4353
+ }
4354
+ await this.logger.log(
4355
+ `<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`
4604
4356
  );
4357
+ this.logger.indent();
4358
+ await children.call(this);
4359
+ this.logger.unindent();
4360
+ await this.logger.log(`</${name}>`);
4605
4361
  }
4606
- log(text) {
4607
- return this.baseLog(this.currentIndent + text);
4362
+ async writeLogs(task, type) {
4363
+ if (task.logs == null || task.logs.length === 0) {
4364
+ return;
4365
+ }
4366
+ const logType = type === "err" ? "stderr" : "stdout";
4367
+ const logs = task.logs.filter((log) => log.type === logType);
4368
+ if (logs.length === 0) {
4369
+ return;
4370
+ }
4371
+ await this.writeElement(`system-${type}`, {}, async () => {
4372
+ for (const log of logs) {
4373
+ await this.baseLog(escapeXML(log.content));
4374
+ }
4375
+ });
4608
4376
  }
4609
- }
4610
-
4611
- function flattenTasks$1(task, baseName = "") {
4612
- const base = baseName ? `${baseName} > ` : "";
4613
- if (task.type === "suite") {
4614
- return task.tasks.flatMap(
4615
- (child) => flattenTasks$1(child, `${base}${task.name}`)
4616
- );
4617
- } else {
4618
- return [
4377
+ async writeTasks(tasks, filename) {
4378
+ for (const task of tasks) {
4379
+ let classname = filename;
4380
+ const templateVars = {
4381
+ filename: task.file.name,
4382
+ filepath: task.file.filepath
4383
+ };
4384
+ if (typeof this.options.classnameTemplate === "function") {
4385
+ classname = this.options.classnameTemplate(templateVars);
4386
+ } else if (typeof this.options.classnameTemplate === "string") {
4387
+ classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
4388
+ } else if (typeof this.options.classname === "string") {
4389
+ classname = this.options.classname;
4390
+ }
4391
+ await this.writeElement(
4392
+ "testcase",
4393
+ {
4394
+ classname,
4395
+ file: this.options.addFileAttribute ? filename : void 0,
4396
+ name: task.name,
4397
+ time: getDuration(task)
4398
+ },
4399
+ async () => {
4400
+ if (this.options.includeConsoleOutput) {
4401
+ await this.writeLogs(task, "out");
4402
+ await this.writeLogs(task, "err");
4403
+ }
4404
+ if (task.mode === "skip" || task.mode === "todo") {
4405
+ await this.logger.log("<skipped/>");
4406
+ }
4407
+ if (task.result?.state === "fail") {
4408
+ const errors = task.result.errors || [];
4409
+ for (const error of errors) {
4410
+ await this.writeElement(
4411
+ "failure",
4412
+ {
4413
+ message: error?.message,
4414
+ type: error?.name ?? error?.nameStr
4415
+ },
4416
+ async () => {
4417
+ if (!error) {
4418
+ return;
4419
+ }
4420
+ const result = capturePrintError(
4421
+ error,
4422
+ this.ctx,
4423
+ { project: this.ctx.getProjectByName(task.file.projectName || ""), task }
4424
+ );
4425
+ await this.baseLog(
4426
+ escapeXML(stripVTControlCharacters(result.output.trim()))
4427
+ );
4428
+ }
4429
+ );
4430
+ }
4431
+ }
4432
+ }
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
+ },
4619
4489
  {
4620
- ...task,
4621
- name: `${base}${task.name}`
4490
+ name: this.options.suiteName || "vitest tests",
4491
+ tests: 0,
4492
+ failures: 0,
4493
+ errors: 0,
4494
+ // we cannot detect those
4495
+ time: 0
4622
4496
  }
4623
- ];
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;
4624
4525
  }
4625
4526
  }
4626
- function removeInvalidXMLCharacters(value, removeDiscouragedChars) {
4627
- let regex = /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
4628
- value = String(value || "").replace(regex, "");
4629
- {
4630
- regex = new RegExp(
4631
- /* eslint-disable regexp/prefer-character-class, regexp/no-obscure-range, regexp/no-useless-non-capturing-group */
4632
- "([\\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]))",
4633
- "g"
4634
- /* eslint-enable */
4635
- );
4636
- value = value.replace(regex, "");
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;
4637
4571
  }
4638
- return value;
4639
- }
4640
- function escapeXML(value) {
4641
- return removeInvalidXMLCharacters(
4642
- String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
4643
- }
4644
- function executionTime(durationMS) {
4645
- return (durationMS / 1e3).toLocaleString("en-US", {
4646
- useGrouping: false,
4647
- maximumFractionDigits: 10
4648
- });
4649
- }
4650
- function getDuration(task) {
4651
- const duration = task.result?.duration ?? 0;
4652
- return executionTime(duration);
4653
4572
  }
4654
- class JUnitReporter {
4655
- ctx;
4656
- reportFile;
4657
- baseLog;
4658
- logger;
4659
- _timeStart = /* @__PURE__ */ new Date();
4660
- fileFd;
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
+ */
4661
4587
  options;
4662
- constructor(options) {
4663
- this.options = { ...options };
4664
- this.options.includeConsoleOutput ??= true;
4665
- }
4666
- async onInit(ctx) {
4667
- this.ctx = ctx;
4668
- const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "junit");
4669
- if (outputFile) {
4670
- this.reportFile = resolve(this.ctx.config.root, outputFile);
4671
- const outputDirectory = dirname(this.reportFile);
4672
- if (!existsSync(outputDirectory)) {
4673
- await promises.mkdir(outputDirectory, { recursive: true });
4674
- }
4675
- const fileFd = await promises.open(this.reportFile, "w+");
4676
- this.fileFd = fileFd;
4677
- this.baseLog = async (text) => {
4678
- if (!this.fileFd) {
4679
- this.fileFd = await promises.open(this.reportFile, "w+");
4680
- }
4681
- await promises.writeFile(this.fileFd, `${text}
4682
- `);
4683
- };
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);
4684
4600
  } else {
4685
- this.baseLog = async (text) => this.ctx.logger.log(text);
4601
+ this.parent = this.module;
4686
4602
  }
4687
- this._timeStart = /* @__PURE__ */ new Date();
4688
- this.logger = new IndentedLogger(this.baseLog);
4603
+ this.options = buildOptions(task);
4689
4604
  }
4690
- async writeElement(name, attrs, children) {
4691
- const pairs = [];
4692
- for (const key in attrs) {
4693
- const attr = attrs[key];
4694
- if (attr === void 0) {
4695
- continue;
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;
4696
4614
  }
4697
- pairs.push(`${key}="${escapeXML(attr)}"`);
4698
4615
  }
4699
- await this.logger.log(
4700
- `<${name}${pairs.length ? ` ${pairs.join(" ")}` : ""}>`
4701
- );
4702
- this.logger.indent();
4703
- await children.call(this);
4704
- this.logger.unindent();
4705
- await this.logger.log(`</${name}>`);
4616
+ return this.#fullName;
4706
4617
  }
4707
- async writeLogs(task, type) {
4708
- if (task.logs == null || task.logs.length === 0) {
4709
- return;
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;
4710
4625
  }
4711
- const logType = type === "err" ? "stderr" : "stdout";
4712
- const logs = task.logs.filter((log) => log.type === logType);
4713
- if (logs.length === 0) {
4714
- return;
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
+ };
4715
4633
  }
4716
- await this.writeElement(`system-${type}`, {}, async () => {
4717
- for (const log of logs) {
4718
- await this.baseLog(escapeXML(log.content));
4719
- }
4720
- });
4721
- }
4722
- async writeTasks(tasks, filename) {
4723
- for (const task of tasks) {
4724
- let classname = filename;
4725
- const templateVars = {
4726
- filename: task.file.name,
4727
- filepath: task.file.filepath
4634
+ if (state === "passed") {
4635
+ return {
4636
+ state,
4637
+ errors: result.errors
4728
4638
  };
4729
- if (typeof this.options.classnameTemplate === "function") {
4730
- classname = this.options.classnameTemplate(templateVars);
4731
- } else if (typeof this.options.classnameTemplate === "string") {
4732
- classname = this.options.classnameTemplate.replace(/\{filename\}/g, templateVars.filename).replace(/\{filepath\}/g, templateVars.filepath);
4733
- } else if (typeof this.options.classname === "string") {
4734
- classname = this.options.classname;
4735
- }
4736
- await this.writeElement(
4737
- "testcase",
4738
- {
4739
- classname,
4740
- file: this.options.addFileAttribute ? filename : void 0,
4741
- name: task.name,
4742
- time: getDuration(task)
4743
- },
4744
- async () => {
4745
- if (this.options.includeConsoleOutput) {
4746
- await this.writeLogs(task, "out");
4747
- await this.writeLogs(task, "err");
4748
- }
4749
- if (task.mode === "skip" || task.mode === "todo") {
4750
- await this.logger.log("<skipped/>");
4751
- }
4752
- if (task.result?.state === "fail") {
4753
- const errors = task.result.errors || [];
4754
- for (const error of errors) {
4755
- await this.writeElement(
4756
- "failure",
4757
- {
4758
- message: error?.message,
4759
- type: error?.name ?? error?.nameStr
4760
- },
4761
- async () => {
4762
- if (!error) {
4763
- return;
4764
- }
4765
- const result = capturePrintError(
4766
- error,
4767
- this.ctx,
4768
- { project: this.ctx.getProjectByTaskId(task.id), task }
4769
- );
4770
- await this.baseLog(
4771
- escapeXML(stripVTControlCharacters(result.output.trim()))
4772
- );
4773
- }
4774
- );
4775
- }
4776
- }
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;
4777
4719
  }
4778
- );
4720
+ } else {
4721
+ yield child;
4722
+ }
4779
4723
  }
4780
4724
  }
4781
- async onFinished(files = this.ctx.state.getFiles()) {
4782
- await this.logger.log('<?xml version="1.0" encoding="UTF-8" ?>');
4783
- const transformed = files.map((file) => {
4784
- const tasks = file.tasks.flatMap((task) => flattenTasks$1(task));
4785
- const stats2 = tasks.reduce(
4786
- (stats3, task) => {
4787
- return {
4788
- passed: stats3.passed + Number(task.result?.state === "pass"),
4789
- failures: stats3.failures + Number(task.result?.state === "fail"),
4790
- skipped: stats3.skipped + Number(task.mode === "skip" || task.mode === "todo")
4791
- };
4792
- },
4793
- {
4794
- passed: 0,
4795
- failures: 0,
4796
- skipped: 0
4797
- }
4798
- );
4799
- const suites = getSuites(file);
4800
- for (const suite of suites) {
4801
- if (suite.result?.errors) {
4802
- tasks.push(suite);
4803
- 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;
4804
4737
  }
4738
+ } else {
4739
+ yield child;
4805
4740
  }
4806
- if (tasks.length === 0 && file.result?.state === "fail") {
4807
- stats2.failures = 1;
4808
- tasks.push({
4809
- id: file.id,
4810
- type: "test",
4811
- name: file.name,
4812
- mode: "run",
4813
- result: file.result,
4814
- meta: {},
4815
- // NOTE: not used in JUnitReporter
4816
- context: null,
4817
- suite: null,
4818
- file: null
4819
- });
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;
4820
4750
  }
4821
- return {
4822
- ...file,
4823
- tasks,
4824
- stats: stats2
4825
- };
4826
- });
4827
- const stats = transformed.reduce(
4828
- (stats2, file) => {
4829
- stats2.tests += file.tasks.length;
4830
- stats2.failures += file.stats.failures;
4831
- stats2.time += file.result?.duration || 0;
4832
- return stats2;
4833
- },
4834
- {
4835
- name: this.options.suiteName || "vitest tests",
4836
- tests: 0,
4837
- failures: 0,
4838
- errors: 0,
4839
- // we cannot detect those
4840
- time: 0
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();
4841
4761
  }
4842
- );
4843
- await this.writeElement("testsuites", { ...stats, time: executionTime(stats.time) }, async () => {
4844
- for (const file of transformed) {
4845
- const filename = relative(this.ctx.config.root, file.filepath);
4846
- await this.writeElement(
4847
- "testsuite",
4848
- {
4849
- name: filename,
4850
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4851
- hostname: hostname(),
4852
- tests: file.tasks.length,
4853
- failures: file.stats.failures,
4854
- errors: 0,
4855
- // An errored test is one that had an unanticipated problem. We cannot detect those.
4856
- skipped: file.stats.skipped,
4857
- time: getDuration(file)
4858
- },
4859
- async () => {
4860
- await this.writeTasks(file.tasks, filename);
4861
- }
4862
- );
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;
4863
4835
  }
4864
- });
4865
- if (this.reportFile) {
4866
- this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
4867
4836
  }
4868
- await this.fileFd?.close();
4869
- 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
+ );
4870
4898
  }
4899
+ return reportedTask;
4871
4900
  }
4872
4901
 
4873
4902
  function yamlString(str) {
@@ -4918,7 +4947,7 @@ class TapReporter {
4918
4947
  this.logger.log("}");
4919
4948
  } else {
4920
4949
  this.logger.log(`${ok} ${id} - ${tapString(task.name)}${comment}`);
4921
- const project = this.ctx.getProjectByTaskId(task.id);
4950
+ const project = this.ctx.getProjectByName(task.file.projectName || "");
4922
4951
  if (task.result?.state === "fail" && task.result.errors) {
4923
4952
  this.logger.indent();
4924
4953
  task.result.errors.forEach((error) => {