vitest 5.0.0-beta.2 → 5.0.0-beta.3

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 (36) hide show
  1. package/dist/browser.d.ts +1 -1
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{base.Opc_YHkk.js → base.Bay6B1Dz.js} +4 -4
  4. package/dist/chunks/{browser.d.BUhkKcDl.d.ts → browser.d.DM1g8UNp.d.ts} +2 -2
  5. package/dist/chunks/{cac.8N4bOkkB.js → cac.DoK9yX-i.js} +8 -6
  6. package/dist/chunks/{cli-api.B0RFke2g.js → cli-api.BCY9ylNq.js} +140 -68
  7. package/dist/chunks/{config.d.D91DHYaD.d.ts → config.d.C0UMwus7.d.ts} +86 -63
  8. package/dist/chunks/{defaults.szbHWQun.js → defaults.DVfzlTkU.js} +1 -1
  9. package/dist/chunks/{env.D4Lgay0q.js → env.BKKtU2WC.js} +2 -1
  10. package/dist/chunks/{global.d.DhbKSQoV.d.ts → global.d.DZbA5YnY.d.ts} +4 -2
  11. package/dist/chunks/{globals.EHmmu0nC.js → globals.8_qjZdeE.js} +1 -1
  12. package/dist/chunks/{index.CViWo__T.js → index.PuMGMNHF.js} +2 -2
  13. package/dist/chunks/{index.D_7-4CaB.js → index.ukHtlBbI.js} +1305 -555
  14. package/dist/chunks/{init-forks.DMge3WTt.js → init-forks.OoZmDo1g.js} +1 -1
  15. package/dist/chunks/{init-threads.eIoyCTon.js → init-threads.eSHAowcx.js} +1 -1
  16. package/dist/chunks/{init.BVd7SaCA.js → init.YjNsCb-_.js} +1 -1
  17. package/dist/chunks/{plugin.d.cIKZEZ16.d.ts → plugin.d.C00LxKL6.d.ts} +35 -9
  18. package/dist/chunks/{setup-common.Hpq30zVk.js → setup-common.eQsbxe88.js} +1 -1
  19. package/dist/chunks/{vm.2okbRRME.js → vm.BE_VOfSs.js} +1 -1
  20. package/dist/chunks/{worker.d.Bu1kXGw4.d.ts → worker.d.Dv3hDCFf.d.ts} +1 -1
  21. package/dist/cli.js +2 -2
  22. package/dist/config.d.ts +6 -7
  23. package/dist/config.js +2 -2
  24. package/dist/index.d.ts +8 -8
  25. package/dist/index.js +1 -1
  26. package/dist/module-evaluator.js +1 -1
  27. package/dist/node.d.ts +7 -8
  28. package/dist/node.js +5 -5
  29. package/dist/worker.d.ts +2 -2
  30. package/dist/worker.js +5 -5
  31. package/dist/workers/forks.js +6 -6
  32. package/dist/workers/runVmTests.js +3 -3
  33. package/dist/workers/threads.js +6 -6
  34. package/dist/workers/vmForks.js +3 -3
  35. package/dist/workers/vmThreads.js +3 -3
  36. package/package.json +19 -20
package/dist/browser.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { c as SerializedCoverageConfig, a as SerializedConfig } from './chunks/config.d.D91DHYaD.js';
1
+ import { c as SerializedCoverageConfig, a as SerializedConfig } from './chunks/config.d.C0UMwus7.js';
2
2
  import { R as RuntimeCoverageModuleLoader } from './chunks/coverage.d.g2xbl2sP.js';
3
3
  import { SerializedDiffOptions } from '@vitest/utils/diff';
4
4
  export { collectTests, startTests } from '@vitest/runner';
package/dist/browser.js CHANGED
@@ -1,4 +1,4 @@
1
- export { l as loadDiffConfig, a as loadSnapshotSerializers, s as setupCommonEnv, b as startCoverageInsideWorker, c as stopCoverageInsideWorker, t as takeCoverageInsideWorker } from './chunks/setup-common.Hpq30zVk.js';
1
+ export { l as loadDiffConfig, a as loadSnapshotSerializers, s as setupCommonEnv, b as startCoverageInsideWorker, c as stopCoverageInsideWorker, t as takeCoverageInsideWorker } from './chunks/setup-common.eQsbxe88.js';
2
2
  export { collectTests, startTests } from '@vitest/runner';
3
3
  import * as spyModule from '@vitest/spy';
4
4
  export { spyModule as SpyModule };
@@ -1,15 +1,15 @@
1
1
  import { runInThisContext } from 'node:vm';
2
2
  import * as spyModule from '@vitest/spy';
3
- import { r as resolveTestRunner, a as resolveSnapshotEnvironment, d as detectAsyncLeaks, s as setupChaiConfig } from './index.CViWo__T.js';
4
- import { l as loadEnvironment, e as emitModuleRunner, a as listenForErrors } from './init.BVd7SaCA.js';
3
+ import { r as resolveTestRunner, a as resolveSnapshotEnvironment, d as detectAsyncLeaks, s as setupChaiConfig } from './index.PuMGMNHF.js';
4
+ import { l as loadEnvironment, e as emitModuleRunner, a as listenForErrors } from './init.YjNsCb-_.js';
5
5
  import { N as NativeModuleRunner } from './nativeModuleRunner.BOeMnHl4.js';
6
6
  import { Traces } from '../traces.js';
7
7
  import { V as VitestEvaluatedModules } from './rpc.DFRWVnRh.js';
8
8
  import { s as startVitestModuleRunner, c as createNodeImportMeta } from './index.CbgUM9E5.js';
9
9
  import { performance as performance$1 } from 'node:perf_hooks';
10
10
  import { startTests, collectTests } from '@vitest/runner';
11
- import { s as setupCommonEnv, b as startCoverageInsideWorker, c as stopCoverageInsideWorker } from './setup-common.Hpq30zVk.js';
12
- import { i as index, g as globalExpect, v as vi } from './index.D_7-4CaB.js';
11
+ import { s as setupCommonEnv, b as startCoverageInsideWorker, c as stopCoverageInsideWorker } from './setup-common.eQsbxe88.js';
12
+ import { i as index, g as globalExpect, v as vi } from './index.ukHtlBbI.js';
13
13
  import { c as closeInspector } from './inspector.CvyFGlXm.js';
14
14
  import { createRequire } from 'node:module';
15
15
  import timers from 'node:timers';
@@ -6,7 +6,7 @@ import { Test, FileSpecification } from '@vitest/runner';
6
6
  import { ChainableFunction } from '@vitest/runner/utils';
7
7
  import { TaskResult, Bench, Options } from 'tinybench';
8
8
  import { O as OTELCarrier } from './rpc.d.7JZuxZ8u.js';
9
- import { T as TestExecutionMethod } from './worker.d.Bu1kXGw4.js';
9
+ import { T as TestExecutionMethod } from './worker.d.Dv3hDCFf.js';
10
10
 
11
11
  type SerializedTestSpecification = [project: {
12
12
  name: string | undefined;
@@ -200,7 +200,7 @@ interface CustomMatcher {
200
200
  * expect('foo').toBeOneOf([expect.any(String)])
201
201
  * expect({ a: 1 }).toEqual({ a: expect.toBeOneOf(['1', '2', '3']) })
202
202
  */
203
- toBeOneOf: <T>(sample: Array<T> | Set<T>) => any;
203
+ toBeOneOf: <T>(sample: ReadonlyArray<T> | ReadonlySet<T>) => any;
204
204
  }
205
205
  interface AsymmetricMatchersContaining extends CustomMatcher {
206
206
  /**
@@ -2,7 +2,7 @@ import { toArray } from '@vitest/utils/helpers';
2
2
  import { EventEmitter } from 'events';
3
3
  import { normalize } from 'pathe';
4
4
  import c$2, { disableDefaultColors } from 'tinyrainbow';
5
- import './env.D4Lgay0q.js';
5
+ import { i as isForceColor } from './env.BKKtU2WC.js';
6
6
  import { a as defaultPort, d as defaultBrowserPort } from './constants.-juJ8b_4.js';
7
7
  import assert from 'node:assert';
8
8
  import { isAgent } from 'std-env';
@@ -621,7 +621,7 @@ class CAC extends EventEmitter {
621
621
 
622
622
  const cac = (name = "") => new CAC(name);
623
623
 
624
- var version = "5.0.0-beta.2";
624
+ var version = "5.0.0-beta.3";
625
625
 
626
626
  const apiConfig = (port) => ({
627
627
  port: {
@@ -907,7 +907,8 @@ const cliOptionsConfig = {
907
907
  argument: "<options>",
908
908
  subcommands: {
909
909
  testIdAttribute: null,
910
- exact: { description: "Should locators match the text exactly by default (default: `false`)" }
910
+ exact: { description: "Should locators match the text exactly by default (default: `false`)" },
911
+ errorFormat: null
911
912
  },
912
913
  transform(val) {
913
914
  if (typeof val !== "object" || val == null) return {};
@@ -1109,6 +1110,7 @@ const cliOptionsConfig = {
1109
1110
  },
1110
1111
  allowJs: { description: "Allow JavaScript files to be typechecked. By default takes the value from tsconfig.json" },
1111
1112
  ignoreSourceErrors: { description: "Ignore type errors from source files" },
1113
+ build: { description: "Use TypeScript build mode" },
1112
1114
  tsconfig: {
1113
1115
  description: "Path to a custom tsconfig file",
1114
1116
  argument: "<path>",
@@ -2225,7 +2227,7 @@ function addCliOptions(cli, options) {
2225
2227
  for (const [optionName, option] of Object.entries(options)) if (option) addCommand(cli, optionName, option);
2226
2228
  }
2227
2229
  function createCLI(options = {}) {
2228
- if (isAgent) disableDefaultColors();
2230
+ if (isAgent && !isForceColor()) disableDefaultColors();
2229
2231
  const cli = cac("vitest");
2230
2232
  cli.version(version);
2231
2233
  addCliOptions(cli, cliOptionsConfig);
@@ -2348,7 +2350,7 @@ function normalizeCliOptions(cliFilters, argv) {
2348
2350
  }
2349
2351
  async function start(mode, cliFilters, options) {
2350
2352
  try {
2351
- const { startVitest } = await import('./cli-api.B0RFke2g.js').then(function (n) { return n.K; });
2353
+ const { startVitest } = await import('./cli-api.BCY9ylNq.js').then(function (n) { return n.K; });
2352
2354
  const ctx = await startVitest(mode, cliFilters.map(normalize), normalizeCliOptions(cliFilters, options));
2353
2355
  if (!ctx.shouldKeepServer()) await ctx.exit();
2354
2356
  } catch (e) {
@@ -2370,7 +2372,7 @@ async function init(project) {
2370
2372
  }
2371
2373
  async function collect(mode, cliFilters, options) {
2372
2374
  try {
2373
- const { prepareVitest, processCollected, outputFileList } = await import('./cli-api.B0RFke2g.js').then(function (n) { return n.K; });
2375
+ const { prepareVitest, processCollected, outputFileList } = await import('./cli-api.BCY9ylNq.js').then(function (n) { return n.K; });
2374
2376
  const ctx = await prepareVitest(mode, {
2375
2377
  ...normalizeCliOptions(cliFilters, options),
2376
2378
  watch: false,
@@ -14,7 +14,7 @@ import * as nodeos from 'node:os';
14
14
  import nodeos__default, { tmpdir, hostname } from 'node:os';
15
15
  import { getTests, createFileTask as createFileTask$1, createTaskName, validateTags, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, hasFailed, generateFileHash, limitConcurrency, getTestName, getSuites, getTasks, getFullName, createTagsFilter, isTestCase } from '@vitest/runner/utils';
16
16
  import { serializeValue } from '@vitest/utils/serialize';
17
- import { v as version$1 } from './cac.8N4bOkkB.js';
17
+ import { v as version$1 } from './cac.DoK9yX-i.js';
18
18
  import { distDir, rootDir } from '../path.js';
19
19
  import { Traces } from '../traces.js';
20
20
  import { createDebug } from 'obug';
@@ -23,8 +23,8 @@ import { rm, readFile, writeFile, rename, stat, unlink, mkdir, readdir, copyFile
23
23
  import c from 'tinyrainbow';
24
24
  import url, { pathToFileURL, fileURLToPath } from 'node:url';
25
25
  import { isDynamicPattern, glob } from 'tinyglobby';
26
- import { c as configDefaults, e as benchmarkConfigDefaults, a as coverageConfigDefaults } from './defaults.szbHWQun.js';
27
- import { i as isTTY, a as isWindows } from './env.D4Lgay0q.js';
26
+ import { c as configDefaults, e as benchmarkConfigDefaults, a as coverageConfigDefaults } from './defaults.DVfzlTkU.js';
27
+ import { a as isTTY, b as isWindows, i as isForceColor } from './env.BKKtU2WC.js';
28
28
  import { w as withLabel, t as truncateString, e as errorBanner, F as F_POINTER, d as divider, f as formatProjectName, a as formatTimeString, b as taskFail, s as separator, c as F_CHECK, g as F_DOWN_RIGHT, h as getStateSymbol, r as renderSnapshotSummary, p as padSummaryTitle, i as getStateString$1, j as formatTime, k as countTestErrors, l as F_TREE_NODE_END, m as F_TREE_NODE_MIDDLE, n as noun, o as F_RIGHT } from './utils.DKODp04v.js';
29
29
  import { isAgent, isCI, provider } from 'std-env';
30
30
  import module$1, { createRequire, builtinModules, isBuiltin as isBuiltin$1 } from 'node:module';
@@ -1544,7 +1544,7 @@ class BrowserSessions {
1544
1544
  destroySession(sessionId) {
1545
1545
  this.sessions.delete(sessionId);
1546
1546
  }
1547
- createSession(sessionId, project, pool) {
1547
+ createSession(sessionId, project, pool, options) {
1548
1548
  // this promise only waits for the WS connection with the orchestrator to be established
1549
1549
  const defer = createDefer();
1550
1550
  const timeout = setTimeout(() => {
@@ -1552,6 +1552,7 @@ class BrowserSessions {
1552
1552
  }, project.vitest.config.browser.connectTimeout ?? 6e4).unref();
1553
1553
  this.sessions.set(sessionId, {
1554
1554
  project,
1555
+ otelCarrier: options?.otelCarrier,
1555
1556
  connected: () => {
1556
1557
  defer.resolve();
1557
1558
  clearTimeout(timeout);
@@ -2457,7 +2458,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
2457
2458
  resolved.coverage.exclude = [
2458
2459
  ...resolved.coverage.exclude,
2459
2460
  ...resolved.setupFiles.map((file) => `${resolved.coverage.allowExternal ? "**/" : ""}${relative(resolved.root, file)}`),
2460
- ...resolved.include,
2461
+ ...resolved.include.filter((pattern) => !pattern.startsWith("!")),
2461
2462
  resolved.config && slash(resolved.config),
2462
2463
  ...configFiles,
2463
2464
  "**/virtual:*",
@@ -2617,6 +2618,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
2617
2618
  resolved.browser.locators ??= {};
2618
2619
  resolved.browser.locators.testIdAttribute ??= "data-testid";
2619
2620
  resolved.browser.locators.exact ??= false;
2621
+ resolved.browser.locators.errorFormat ??= "all";
2620
2622
  if (typeof resolved.browser.provider === "string") {
2621
2623
  const source = `@vitest/browser-${resolved.browser.provider}`;
2622
2624
  throw new TypeError(`The \`browser.provider\` configuration was changed to accept a factory instead of a string. Add an import of "${resolved.browser.provider}" from "${source}" instead. See: https://vitest.dev/config/browser/provider`);
@@ -4504,18 +4506,16 @@ Possible solutions:
4504
4506
  }
4505
4507
  async spawn() {
4506
4508
  const { root, watch, typecheck } = this.project.config;
4507
- const args = [
4508
- "--noEmit",
4509
- "--pretty",
4510
- "false",
4511
- "--incremental",
4512
- "--tsBuildInfoFile",
4513
- join(process.versions.pnp ? join(nodeos__default.tmpdir(), this.project.hash) : distDir, "tsconfig.tmp.tsbuildinfo")
4514
- ];
4509
+ const args = ["--pretty", "false"];
4510
+ if (typecheck.build) args.unshift("--build");
4511
+ else args.push("--noEmit", "--incremental", "--tsBuildInfoFile", join(process.versions.pnp ? join(nodeos__default.tmpdir(), this.project.hash) : distDir, "tsconfig.tmp.tsbuildinfo"));
4515
4512
  // use builtin watcher because it's faster
4516
4513
  if (watch) args.push("--watch");
4517
4514
  if (typecheck.allowJs) args.push("--allowJs", "--checkJs");
4518
- if (typecheck.tsconfig) args.push("-p", resolve$1(root, typecheck.tsconfig));
4515
+ if (typecheck.tsconfig) {
4516
+ if (!typecheck.build) args.push("-p");
4517
+ args.push(resolve$1(root, typecheck.tsconfig));
4518
+ }
4519
4519
  this._output = "";
4520
4520
  this._startTime = performance$1.now();
4521
4521
  const child = x(typecheck.checker, args, {
@@ -12343,7 +12343,8 @@ function serializeConfig(project) {
12343
12343
  screenshotFailures: browser.screenshotFailures,
12344
12344
  locators: {
12345
12345
  testIdAttribute: browser.locators.testIdAttribute,
12346
- exact: browser.locators.exact
12346
+ exact: browser.locators.exact,
12347
+ errorFormat: browser.locators.errorFormat
12347
12348
  },
12348
12349
  providerOptions: provider?.name === "playwright" ? { actionTimeout: provider?.options?.actionTimeout } : {},
12349
12350
  trackUnhandledErrors: browser.trackUnhandledErrors ?? true,
@@ -12367,7 +12368,7 @@ function serializeConfig(project) {
12367
12368
  strictTags: config.strictTags ?? true,
12368
12369
  mergeReportsLabel: config.mergeReportsLabel,
12369
12370
  slowTestThreshold: config.slowTestThreshold ?? globalConfig.slowTestThreshold ?? configDefaults.slowTestThreshold,
12370
- isAgent
12371
+ disableColors: isAgent && !isForceColor()
12371
12372
  };
12372
12373
  }
12373
12374
 
@@ -13660,9 +13661,9 @@ class TestSpecification {
13660
13661
  * This class represents a test suite for a test module within a single project.
13661
13662
  * @internal
13662
13663
  */
13663
- constructor(project, moduleId, pool, testLinesOrOptions, metaOverride) {
13664
+ constructor(project, moduleId, pool, testLinesOrOptions, taskIdOverride) {
13664
13665
  const projectName = project.config.name;
13665
- this.taskId = generateFileHash(relative(project.config.root, moduleId), projectName, metaOverride ?? {
13666
+ this.taskId = taskIdOverride ?? generateFileHash(relative(project.config.root, moduleId), projectName, {
13666
13667
  typecheck: pool === "typescript",
13667
13668
  __vitest_label__: project.config.mergeReportsLabel
13668
13669
  });
@@ -13779,8 +13780,8 @@ class TestProject {
13779
13780
  * Creates a new test specification. Specifications describe how to run tests.
13780
13781
  * @param moduleId The file path
13781
13782
  */
13782
- createSpecification(moduleId, locationsOrOptions, pool, metaOverride) {
13783
- return new TestSpecification(this, moduleId, pool || getFilePoolName(this), locationsOrOptions, metaOverride);
13783
+ createSpecification(moduleId, locationsOrOptions, pool, taskIdOverride) {
13784
+ return new TestSpecification(this, moduleId, pool || getFilePoolName(this), locationsOrOptions, taskIdOverride);
13784
13785
  }
13785
13786
  toJSON() {
13786
13787
  return {
@@ -14029,7 +14030,8 @@ class TestProject {
14029
14030
  async _configureServer(options, server) {
14030
14031
  this._config = resolveConfig$1(this.vitest, {
14031
14032
  ...options,
14032
- coverage: this.vitest.config.coverage
14033
+ coverage: this.vitest.config.coverage,
14034
+ attachmentsDir: this.vitest.config.attachmentsDir
14033
14035
  }, server.config);
14034
14036
  this._config.api.token = this.vitest.config.api.token;
14035
14037
  this._config.mergeReportsLabel = this.vitest.config.mergeReportsLabel;
@@ -14069,9 +14071,8 @@ class TestProject {
14069
14071
  const url = new URL("/__vitest_test__/", origin);
14070
14072
  url.searchParams.set("sessionId", sessionId);
14071
14073
  const otelCarrier = this.vitest._traces.getContextCarrier();
14072
- if (otelCarrier) url.searchParams.set("otelCarrier", JSON.stringify(otelCarrier));
14073
14074
  this.vitest._browserSessions.sessionIds.add(sessionId);
14074
- const sessionPromise = this.vitest._browserSessions.createSession(sessionId, this, pool);
14075
+ const sessionPromise = this.vitest._browserSessions.createSession(sessionId, this, pool, { otelCarrier });
14075
14076
  const pagePromise = this.browser.provider.openPage(sessionId, url.toString(), { parallel: pool.parallel ?? false });
14076
14077
  await Promise.all([sessionPromise, pagePromise]);
14077
14078
  }
@@ -14345,7 +14346,8 @@ function cloneConfig(project, { browser, ...config }) {
14345
14346
  ...project.config.browser,
14346
14347
  locators: locators ? {
14347
14348
  testIdAttribute: locators.testIdAttribute ?? currentConfig.locators.testIdAttribute,
14348
- exact: locators.exact ?? currentConfig.locators.exact
14349
+ exact: locators.exact ?? currentConfig.locators.exact,
14350
+ errorFormat: locators.errorFormat ?? currentConfig.locators.errorFormat
14349
14351
  } : project.config.browser.locators,
14350
14352
  viewport: viewport ?? currentConfig.viewport,
14351
14353
  testerHtmlPath: testerHtmlPath ?? currentConfig.testerHtmlPath,
@@ -14512,8 +14514,8 @@ class BlobReporter {
14512
14514
  outputFile = [
14513
14515
  "blob",
14514
14516
  this.ctx.config.mergeReportsLabel,
14515
- shard ? `-${shard.index}-${shard.count}` : ""
14516
- ].join("");
14517
+ shard ? `${shard.index}-${shard.count}` : ""
14518
+ ].filter(Boolean).join("-");
14517
14519
  outputFile = `${sanitizeFilePath(outputFile)}.json`;
14518
14520
  await report.writeFile(outputFile, content, "utf-8");
14519
14521
  outputFile = resolve$1(report.root, outputFile);
@@ -14587,9 +14589,11 @@ function serializeEnvironmentModuleGraph(environment) {
14587
14589
  };
14588
14590
  const modules = [];
14589
14591
  for (const [id, mod] of environment.moduleGraph.idToModuleMap.entries()) {
14590
- if (!mod.file) continue;
14592
+ // Vite can generate module with `file = ""` for module id "#..."
14593
+ // when the actual module doesn't exist (e.g. resolve failure or mocked module)
14594
+ if (mod.file == null) continue;
14591
14595
  const importedIds = [];
14592
- for (const importedNode of mod.importedModules) if (importedNode.id) importedIds.push(getIdIndex(importedNode.id));
14596
+ for (const importedNode of mod.importedModules) if (importedNode.id !== null) importedIds.push(getIdIndex(importedNode.id));
14593
14597
  modules.push([
14594
14598
  getIdIndex(id),
14595
14599
  getIdIndex(mod.file),
@@ -14608,6 +14612,10 @@ function deserializeEnvironmentModuleGraph(environment, serialized) {
14608
14612
  const moduleId = serialized.idTable[id];
14609
14613
  const filePath = serialized.idTable[file];
14610
14614
  const urlPath = serialized.idTable[url];
14615
+ // `createFileOnlyEntry('')` normalizes the file to ".". This keeps
14616
+ // the graph usable, but doesn't perfectly round-trip Vite's `file = ""`
14617
+ // nodes for ids like "#...".
14618
+ // We may just do moduleNode.file = filePath in the future.
14611
14619
  const moduleNode = environment.moduleGraph.createFileOnlyEntry(filePath);
14612
14620
  moduleNode.url = urlPath;
14613
14621
  moduleNode.id = moduleId;
@@ -15208,6 +15216,9 @@ function getIndentation(suite, level = 1) {
15208
15216
  return level;
15209
15217
  }
15210
15218
 
15219
+ /** Minimum time between two renders, no matter how many scheduled renderes were called */
15220
+ const DEFAULT_RENDER_THRESHOLD_MS = 100;
15221
+ /** Interval between automatic renders. If no test state changes happened, this will increase just duration field */
15211
15222
  const DEFAULT_RENDER_INTERVAL_MS = 1e3;
15212
15223
  const ESC$1 = "\x1B[";
15213
15224
  const CLEAR_LINE = `${ESC$1}K`;
@@ -15230,14 +15241,19 @@ class WindowRenderer {
15230
15241
  cleanups = [];
15231
15242
  constructor(options) {
15232
15243
  this.options = {
15233
- interval: DEFAULT_RENDER_INTERVAL_MS,
15234
- ...options
15244
+ ...options,
15245
+ threshold: options.threshold ?? DEFAULT_RENDER_THRESHOLD_MS,
15246
+ interval: options.interval ?? DEFAULT_RENDER_INTERVAL_MS
15235
15247
  };
15248
+ // Capture the original write methods early, before intercepting these
15236
15249
  this.streams = {
15237
15250
  output: options.logger.outputStream.write.bind(options.logger.outputStream),
15238
15251
  error: options.logger.errorStream.write.bind(options.logger.errorStream)
15239
15252
  };
15240
15253
  this.cleanups.push(this.interceptStream(process.stdout, "output"), this.interceptStream(process.stderr, "error"));
15254
+ // Intercept calls to custom VitestOptions.stdout and stderr streams
15255
+ if (options.logger.outputStream !== process.stdout) this.cleanups.push(this.interceptStream(options.logger.outputStream, "output"));
15256
+ if (options.logger.errorStream !== process.stderr) this.cleanups.push(this.interceptStream(options.logger.errorStream, "error"));
15241
15257
  // Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
15242
15258
  this.options.logger.onTerminalCleanup(() => {
15243
15259
  this.flushBuffer();
@@ -15269,9 +15285,10 @@ class WindowRenderer {
15269
15285
  if (!this.renderScheduled) {
15270
15286
  this.renderScheduled = true;
15271
15287
  this.flushBuffer();
15272
- setTimeout(() => {
15288
+ if (this.options.threshold) setTimeout(() => {
15273
15289
  this.renderScheduled = false;
15274
- }, 100).unref();
15290
+ }, this.options.threshold).unref();
15291
+ else this.renderScheduled = false;
15275
15292
  }
15276
15293
  }
15277
15294
  flushBuffer() {
@@ -15293,15 +15310,16 @@ class WindowRenderer {
15293
15310
  if (current) this.render(current?.message, current?.type);
15294
15311
  }
15295
15312
  render(message, type = "output") {
15313
+ this.write(SYNC_START);
15296
15314
  if (this.finished) {
15297
15315
  this.clearWindow();
15298
- return this.write(message || "", type);
15316
+ this.write(message || "", type);
15317
+ return this.write(SYNC_END);
15299
15318
  }
15300
15319
  const windowContent = this.options.getWindow();
15301
15320
  const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns());
15302
15321
  let padding = this.windowHeight - rowCount;
15303
15322
  if (padding > 0 && message) padding -= getRenderedRowCount([message], this.options.logger.getColumns());
15304
- this.write(SYNC_START);
15305
15323
  this.clearWindow();
15306
15324
  if (message) this.write(message, type);
15307
15325
  if (padding > 0) this.write("\n".repeat(padding));
@@ -15373,7 +15391,9 @@ class SummaryReporter {
15373
15391
  };
15374
15392
  this.renderer = new WindowRenderer({
15375
15393
  logger: ctx.logger,
15376
- getWindow: () => this.createSummary()
15394
+ getWindow: () => this.createSummary(),
15395
+ interval: this.options.interval,
15396
+ threshold: this.options.threshold
15377
15397
  });
15378
15398
  this.ctx.onClose(() => {
15379
15399
  clearInterval(this.durationInterval);
@@ -15427,6 +15447,7 @@ class SummaryReporter {
15427
15447
  };
15428
15448
  stats.hook?.onFinish?.();
15429
15449
  stats.hook = hook;
15450
+ if (!Number.isFinite(this.ctx.config.slowTestThreshold)) return;
15430
15451
  const timeout = setTimeout(() => {
15431
15452
  hook.visible = true;
15432
15453
  }, this.ctx.config.slowTestThreshold).unref();
@@ -15449,9 +15470,9 @@ class SummaryReporter {
15449
15470
  startTime: performance.now(),
15450
15471
  onFinish: () => {}
15451
15472
  };
15452
- const timeout = setTimeout(() => {
15473
+ const timeout = Number.isFinite(this.ctx.config.slowTestThreshold) ? setTimeout(() => {
15453
15474
  slowTest.visible = true;
15454
- }, this.ctx.config.slowTestThreshold).unref();
15475
+ }, this.ctx.config.slowTestThreshold).unref() : void 0;
15455
15476
  slowTest.onFinish = () => {
15456
15477
  slowTest.hook?.onFinish();
15457
15478
  clearTimeout(timeout);
@@ -15630,7 +15651,10 @@ class DefaultReporter extends BaseReporter {
15630
15651
  }
15631
15652
  onInit(ctx) {
15632
15653
  super.onInit(ctx);
15633
- this.summary?.onInit(ctx, { verbose: this.verbose });
15654
+ this.summary?.onInit(ctx, {
15655
+ verbose: this.verbose,
15656
+ ...this.options.summaryOptions
15657
+ });
15634
15658
  }
15635
15659
  }
15636
15660
 
@@ -16277,14 +16301,7 @@ class JUnitReporter {
16277
16301
  }
16278
16302
  if (task.result?.state === "fail") {
16279
16303
  const errors = task.result.errors || [];
16280
- for (const error of errors) await this.writeElement("failure", {
16281
- message: error?.message,
16282
- type: error?.name
16283
- }, async () => {
16284
- if (!error || !this.options.stackTrace) return;
16285
- const result = this.ctx.logger.formatError(error, { project: this.ctx.getProjectByName(task.file?.projectName ?? "") });
16286
- await this.baseLog(escapeXML(stripVTControlCharacters(result.output.trim())));
16287
- });
16304
+ for (const error of errors) await this.writeErrorElement("failure", error, { project: this.ctx.getProjectByName(task.file?.projectName ?? "") });
16288
16305
  }
16289
16306
  });
16290
16307
  }
@@ -16303,7 +16320,52 @@ class JUnitReporter {
16303
16320
  if (typeof this.options.suiteNameTemplate === "function") return this.options.suiteNameTemplate(vars);
16304
16321
  return this.options.suiteNameTemplate.replace(/\{filepath\}/g, () => vars.filepath).replace(/\{filename\}/g, () => vars.filename).replace(/\{basename\}/g, () => vars.basename).replace(/\{displayName\}/g, () => vars.displayName).replace(/\{title\}/g, () => vars.title);
16305
16322
  }
16306
- async onTestRunEnd(testModules) {
16323
+ async writeErrorElement(elementName, error, errorOptions) {
16324
+ await this.writeElement(elementName, {
16325
+ message: error?.message,
16326
+ type: error?.name
16327
+ }, async () => {
16328
+ if (!error || !this.options.stackTrace) return;
16329
+ const result = this.ctx.logger.formatError(error, errorOptions);
16330
+ await this.baseLog(escapeXML(stripVTControlCharacters(result.output.trim())));
16331
+ });
16332
+ }
16333
+ async writeUnhandledErrorsTestsuite(unhandledErrors, testModules) {
16334
+ await this.writeElement("testsuite", {
16335
+ name: "vitest unhandled errors",
16336
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
16337
+ hostname: this.options.hostname || hostname(),
16338
+ tests: unhandledErrors.length,
16339
+ failures: 0,
16340
+ errors: unhandledErrors.length,
16341
+ skipped: 0,
16342
+ time: "0"
16343
+ }, async () => {
16344
+ // Stable order across runs — workers/projects report errors concurrently.
16345
+ const sortedErrors = [...unhandledErrors].sort((a, b) => {
16346
+ const ka = `${a.VITEST_TEST_PATH ?? ""}\0${a.type ?? ""}\0${a.name ?? ""}\0${a.message ?? ""}`;
16347
+ const kb = `${b.VITEST_TEST_PATH ?? ""}\0${b.type ?? ""}\0${b.name ?? ""}\0${b.message ?? ""}`;
16348
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
16349
+ });
16350
+ for (const error of sortedErrors) {
16351
+ const errorTitle = error.type || error.name || "Unhandled Error";
16352
+ // Only attribute when the path resolves to exactly one module — when
16353
+ // multiple projects share a file, errors lack a project identifier and
16354
+ // we can't disambiguate without one (tracked for follow-up).
16355
+ const matches = error.VITEST_TEST_PATH ? testModules.filter((m) => m.task.filepath === error.VITEST_TEST_PATH) : [];
16356
+ const owningModule = matches.length === 1 ? matches[0] : void 0;
16357
+ await this.writeElement("testcase", {
16358
+ classname: "vitest unhandled errors",
16359
+ file: this.options.addFileAttribute && error.VITEST_TEST_PATH ? relative(this.ctx.config.root, error.VITEST_TEST_PATH) : void 0,
16360
+ name: error.message ? `${errorTitle}: ${error.message}` : errorTitle,
16361
+ time: "0"
16362
+ }, async () => {
16363
+ await this.writeErrorElement("error", error, { project: owningModule?.project });
16364
+ });
16365
+ }
16366
+ });
16367
+ }
16368
+ async onTestRunEnd(testModules, unhandledErrors = []) {
16307
16369
  const files = testModules.map((testModule) => testModule.task);
16308
16370
  const separator = this.options.ancestorSeparator ?? " > ";
16309
16371
  await this.logger.log("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
@@ -16361,32 +16423,36 @@ class JUnitReporter {
16361
16423
  name: this.options.suiteName || "vitest tests",
16362
16424
  tests: 0,
16363
16425
  failures: 0,
16364
- errors: 0,
16426
+ errors: unhandledErrors.length,
16365
16427
  time: 0
16366
16428
  });
16429
+ stats.tests += unhandledErrors.length;
16430
+ // Plain byte compare (not localeCompare) so output is identical across machines and ICU versions.
16431
+ const orderedSuites = transformed.map((file, i) => {
16432
+ const filename = relative(this.ctx.config.root, file.filepath);
16433
+ return {
16434
+ file,
16435
+ filename,
16436
+ suiteName: this.resolveSuiteNameTemplate(files[i], filename)
16437
+ };
16438
+ }).sort((a, b) => a.suiteName < b.suiteName ? -1 : a.suiteName > b.suiteName ? 1 : 0);
16367
16439
  await this.writeElement("testsuites", {
16368
16440
  ...stats,
16369
16441
  time: executionTime(stats.time)
16370
16442
  }, async () => {
16371
- for (let i = 0; i < transformed.length; i++) {
16372
- const file = transformed[i];
16373
- const filename = relative(this.ctx.config.root, file.filepath);
16374
- // resolveSuiteNameTemplate needs the original file (before task flattening) to
16375
- // search for top-level describe blocks, so pass files[i] directly.
16376
- const suiteName = this.resolveSuiteNameTemplate(files[i], filename);
16377
- await this.writeElement("testsuite", {
16378
- name: suiteName,
16379
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
16380
- hostname: this.options.hostname || hostname(),
16381
- tests: file.tasks.length,
16382
- failures: file.stats.failures,
16383
- errors: 0,
16384
- skipped: file.stats.skipped,
16385
- time: getDuration(file)
16386
- }, async () => {
16387
- await this.writeTasks(file.tasks, filename, file.filepath);
16388
- });
16389
- }
16443
+ for (const { file, filename, suiteName } of orderedSuites) await this.writeElement("testsuite", {
16444
+ name: suiteName,
16445
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
16446
+ hostname: this.options.hostname || hostname(),
16447
+ tests: file.tasks.length,
16448
+ failures: file.stats.failures,
16449
+ errors: 0,
16450
+ skipped: file.stats.skipped,
16451
+ time: getDuration(file)
16452
+ }, async () => {
16453
+ await this.writeTasks(file.tasks, filename, file.filepath);
16454
+ });
16455
+ if (unhandledErrors.length) await this.writeUnhandledErrorsTestsuite(unhandledErrors, testModules);
16390
16456
  });
16391
16457
  if (this.reportFile) this.ctx.logger.log(`JUNIT report written to ${this.reportFile}`);
16392
16458
  await this.fileFd?.close();
@@ -16994,6 +17060,12 @@ class ReportedTaskImplementation {
16994
17060
  return this.task.meta;
16995
17061
  }
16996
17062
  /**
17063
+ * Console logs recorded during the test execution.
17064
+ */
17065
+ logs() {
17066
+ return [...this.task.logs || []];
17067
+ }
17068
+ /**
16997
17069
  * Creates a new reported task instance and stores it in the project's state for future use.
16998
17070
  * @internal
16999
17071
  */
@@ -18767,7 +18839,7 @@ class Vitest {
18767
18839
  await this.report("onInit", this);
18768
18840
  const specifications = [];
18769
18841
  for (const file of files) {
18770
- const specification = this.getProjectByName(file.projectName || "").createSpecification(file.filepath, void 0, file.pool, file.meta);
18842
+ const specification = this.getProjectByName(file.projectName || "").createSpecification(file.filepath, void 0, file.pool, file.id);
18771
18843
  specifications.push(specification);
18772
18844
  }
18773
18845
  await this._testRun.start(specifications);