vitest 3.2.0-beta.2 → 3.2.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 (67) hide show
  1. package/dist/browser.d.ts +3 -3
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{base.DwtwORaC.js → base.D4119yLM.js} +4 -3
  4. package/dist/chunks/{benchmark.BoF7jW0Q.js → benchmark.Cf_PACH1.js} +1 -1
  5. package/dist/chunks/{cac.I9MLYfT-.js → cac.DWaWHIIE.js} +18 -15
  6. package/dist/chunks/{cli-api.d6IK1pnk.js → cli-api.CnmEXkxs.js} +250 -49
  7. package/dist/chunks/{config.d.UqE-KR0o.d.ts → config.d.D2ROskhv.d.ts} +2 -0
  8. package/dist/chunks/{console.K1NMVOSc.js → console.Cwr-MFPV.js} +3 -2
  9. package/dist/chunks/{constants.BZZyIeIE.js → constants.DnKduX2e.js} +1 -0
  10. package/dist/chunks/{coverage.OGU09Jbh.js → coverage.C73DaDgS.js} +116 -12
  11. package/dist/chunks/{creator.DGAdZ4Hj.js → creator.C8WKy2eW.js} +10 -7
  12. package/dist/chunks/{date.CDOsz-HY.js → date.ByMsSlOr.js} +25 -0
  13. package/dist/chunks/{defaults.DSxsTG0h.js → defaults.DpVH7vbg.js} +1 -0
  14. package/dist/chunks/{environment.d.D8YDy2v5.d.ts → environment.d.cL3nLXbE.d.ts} +1 -0
  15. package/dist/chunks/{execute.JlGHLJZT.js → execute.B3q-2LPV.js} +25 -0
  16. package/dist/chunks/{global.d.BPa1eL3O.d.ts → global.d.BNLIi6yo.d.ts} +3 -1
  17. package/dist/chunks/{globals.CpxW8ccg.js → globals.CI21aWXF.js} +7 -6
  18. package/dist/chunks/{index.DFXFpH3w.js → index.2jgTs_Q5.js} +19 -1
  19. package/dist/chunks/{index.CV36oG_L.js → index.Bter3jj9.js} +83 -16
  20. package/dist/chunks/{index.DswW_LEs.js → index.CbT4iuwc.js} +7 -4
  21. package/dist/chunks/index.D3XRDfWc.js +213 -0
  22. package/dist/chunks/{index.CfXMNXHg.js → index.DNgLEKsQ.js} +4 -2
  23. package/dist/chunks/{index.CmC5OK9L.js → index.JOzufsrU.js} +2 -1
  24. package/dist/chunks/{inspector.DbDkSkFn.js → inspector.BFsh5KO0.js} +3 -0
  25. package/dist/chunks/{node.3xsWotC9.js → node.Be-ntJnD.js} +1 -1
  26. package/dist/chunks/{reporters.d.CLC9rhKy.d.ts → reporters.d.Bt4IGtsa.d.ts} +24 -6
  27. package/dist/chunks/{rpc.D9_013TY.js → rpc.BKExFSRG.js} +2 -1
  28. package/dist/chunks/{runBaseTests.Dn2vyej_.js → runBaseTests.B_M1TTsK.js} +19 -10
  29. package/dist/chunks/{setup-common.CYo3Y0dD.js → setup-common.CF-O-dZX.js} +2 -1
  30. package/dist/chunks/{typechecker.DnTrplSJ.js → typechecker.BgzF-6iO.js} +78 -21
  31. package/dist/chunks/{utils.CgTj3MsC.js → utils.BlI4TC7Y.js} +1 -0
  32. package/dist/chunks/{utils.BfxieIyZ.js → utils.DPCq3gzW.js} +3 -0
  33. package/dist/chunks/{vi.BFR5YIgu.js → vi.pkoYCV6A.js} +25 -2
  34. package/dist/chunks/{vite.d.CBZ3M_ru.d.ts → vite.d.B-Kx3KCF.d.ts} +3 -1
  35. package/dist/chunks/{vm.C1HHjtNS.js → vm.DPYem2so.js} +72 -4
  36. package/dist/chunks/{worker.d.CoCI7hzP.d.ts → worker.d.BKbBp2ga.d.ts} +2 -2
  37. package/dist/chunks/{worker.d.D5Xdi-Zr.d.ts → worker.d.Bl1O4kuf.d.ts} +1 -1
  38. package/dist/cli.js +4 -4
  39. package/dist/config.cjs +2 -0
  40. package/dist/config.d.ts +7 -6
  41. package/dist/config.js +2 -2
  42. package/dist/coverage.d.ts +4 -4
  43. package/dist/coverage.js +5 -5
  44. package/dist/environments.d.ts +6 -2
  45. package/dist/environments.js +1 -1
  46. package/dist/execute.d.ts +9 -3
  47. package/dist/execute.js +1 -1
  48. package/dist/index.d.ts +24 -12
  49. package/dist/index.js +5 -5
  50. package/dist/node.d.ts +18 -10
  51. package/dist/node.js +14 -12
  52. package/dist/reporters.d.ts +4 -4
  53. package/dist/reporters.js +3 -3
  54. package/dist/runners.d.ts +1 -1
  55. package/dist/runners.js +13 -5
  56. package/dist/snapshot.js +2 -2
  57. package/dist/suite.js +2 -2
  58. package/dist/worker.js +9 -5
  59. package/dist/workers/forks.js +6 -4
  60. package/dist/workers/runVmTests.js +14 -9
  61. package/dist/workers/threads.js +4 -4
  62. package/dist/workers/vmForks.js +6 -6
  63. package/dist/workers/vmThreads.js +6 -6
  64. package/dist/workers.d.ts +4 -4
  65. package/dist/workers.js +10 -10
  66. package/package.json +18 -18
  67. package/dist/chunks/index.CK1YOQaa.js +0 -143
@@ -2,27 +2,27 @@ import fs, { statSync, realpathSync, promises as promises$1, mkdirSync, existsSy
2
2
  import { relative, resolve, dirname, isAbsolute, join as join$1, normalize } from 'pathe';
3
3
  import pm from 'picomatch';
4
4
  import c from 'tinyrainbow';
5
- import { c as configDefaults, e as benchmarkConfigDefaults, a as coverageConfigDefaults } from './defaults.DSxsTG0h.js';
5
+ import { c as configDefaults, e as benchmarkConfigDefaults, a as coverageConfigDefaults } from './defaults.DpVH7vbg.js';
6
6
  import crypto from 'node:crypto';
7
7
  import { slash, createDefer, shuffle, toArray } from '@vitest/utils';
8
8
  import { builtinModules, createRequire } from 'node:module';
9
9
  import path, { win32, dirname as dirname$1, join, resolve as resolve$1 } from 'node:path';
10
10
  import process$1 from 'node:process';
11
- import fsPromises, { writeFile, rename, stat, unlink } from 'node:fs/promises';
11
+ import fs$1, { writeFile, rename, stat, unlink } from 'node:fs/promises';
12
12
  import { fileURLToPath as fileURLToPath$1, pathToFileURL as pathToFileURL$1, URL as URL$1 } from 'node:url';
13
13
  import assert from 'node:assert';
14
14
  import v8 from 'node:v8';
15
15
  import { format, inspect } from 'node:util';
16
- import { e as extraInlineDeps, d as defaultBrowserPort, b as defaultInspectPort, a as defaultPort } from './constants.BZZyIeIE.js';
16
+ import { version, mergeConfig } from 'vite';
17
+ import { e as extraInlineDeps, d as defaultBrowserPort, b as defaultInspectPort, a as defaultPort } from './constants.DnKduX2e.js';
17
18
  import { a as isWindows } from './env.Dq0hM4Xv.js';
18
19
  import * as nodeos from 'node:os';
19
20
  import nodeos__default from 'node:os';
20
21
  import { isatty } from 'node:tty';
21
- import { version } from 'vite';
22
22
  import EventEmitter from 'node:events';
23
23
  import { c as createBirpc } from './index.CJ0plNrh.js';
24
24
  import Tinypool$1, { Tinypool } from 'tinypool';
25
- import { w as wrapSerializableConfig, a as Typechecker } from './typechecker.DnTrplSJ.js';
25
+ import { w as wrapSerializableConfig, a as Typechecker } from './typechecker.BgzF-6iO.js';
26
26
  import { MessageChannel } from 'node:worker_threads';
27
27
  import { hasFailed } from '@vitest/runner/utils';
28
28
  import { rootDir } from '../path.js';
@@ -39,9 +39,12 @@ function groupBy(collection, iteratee) {
39
39
  }, {});
40
40
  }
41
41
  function stdout() {
42
+ // @ts-expect-error Node.js maps process.stdout to console._stdout
43
+ // eslint-disable-next-line no-console
42
44
  return console._stdout || process.stdout;
43
45
  }
44
46
  function escapeRegExp(s) {
47
+ // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
45
48
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46
49
  }
47
50
  function wildcardPatternToRegExp(pattern) {
@@ -116,7 +119,9 @@ class ResultsCache {
116
119
  }
117
120
  const resultsCache = await fs.promises.readFile(this.cachePath, "utf8");
118
121
  const { results, version } = JSON.parse(resultsCache || "[]");
119
- if (Number(version.split(".")[1]) >= 30) {
122
+ const [major, minor] = version.split(".");
123
+ // handling changed in 0.30.0
124
+ if (major > 0 || Number(minor) >= 30) {
120
125
  this.cache = new Map(results);
121
126
  this.version = version;
122
127
  results.forEach(([spec]) => {
@@ -134,6 +139,7 @@ class ResultsCache {
134
139
  return;
135
140
  }
136
141
  const duration = result.duration || 0;
142
+ // store as relative, so cache would be the same in CI and locally
137
143
  const relativePath = relative(this.root, file.filepath);
138
144
  this.cache.set(`${file.projectName || ""}:${relativePath}`, {
139
145
  duration: duration >= 0 ? duration : 0,
@@ -2363,7 +2369,7 @@ async function findUp$1(name, {
2363
2369
  while (directory) {
2364
2370
  const filePath = isAbsoluteName ? name : path.join(directory, name);
2365
2371
  try {
2366
- const stats = await fsPromises.stat(filePath); // eslint-disable-line no-await-in-loop
2372
+ const stats = await fs$1.stat(filePath); // eslint-disable-line no-await-in-loop
2367
2373
  if ((type === 'file' && stats.isFile()) || (type === 'directory' && stats.isDirectory())) {
2368
2374
  return filePath;
2369
2375
  }
@@ -2528,7 +2534,9 @@ function getTransformMode(patterns, filename) {
2528
2534
  async function groupFilesByEnv(files) {
2529
2535
  const filesWithEnv = await Promise.all(files.map(async ({ moduleId: filepath, project, testLines }) => {
2530
2536
  const code = await promises$1.readFile(filepath, "utf-8");
2537
+ // 1. Check for control comments in the file
2531
2538
  let env = code.match(/@(?:vitest|jest)-environment\s+([\w-]+)\b/)?.[1];
2539
+ // 2. Check for globals
2532
2540
  if (!env) {
2533
2541
  for (const [glob, target] of project.config.environmentMatchGlobs || []) {
2534
2542
  if (pm.isMatch(filepath, glob, { cwd: project.config.root })) {
@@ -2537,10 +2545,12 @@ async function groupFilesByEnv(files) {
2537
2545
  }
2538
2546
  }
2539
2547
  }
2548
+ // 3. Fallback to global env
2540
2549
  env ||= project.config.environment || "node";
2541
2550
  const transformMode = getTransformMode(project.config.testTransformMode, filepath);
2542
2551
  let envOptionsJson = code.match(/@(?:vitest|jest)-environment-options\s+(.+)/)?.[1];
2543
2552
  if (envOptionsJson?.endsWith("*/")) {
2553
+ // Trim closing Docblock characters the above regex might have captured
2544
2554
  envOptionsJson = envOptionsJson.slice(0, -2);
2545
2555
  }
2546
2556
  const envOptions = JSON.parse(envOptionsJson || "null");
@@ -2660,8 +2670,10 @@ function createMethodsRPC(project, options = {}) {
2660
2670
  }
2661
2671
  };
2662
2672
  }
2673
+ // serialize rollup error on server to preserve details as a test error
2663
2674
  function handleRollupError(e) {
2664
2675
  if (e instanceof Error && ("plugin" in e || "frame" in e || "id" in e)) {
2676
+ // eslint-disable-next-line no-throw-literal
2665
2677
  throw {
2666
2678
  name: e.name,
2667
2679
  message: e.message,
@@ -2790,6 +2802,7 @@ function createForksPool(ctx, { execArgv, env }) {
2790
2802
  channel
2791
2803
  });
2792
2804
  } catch (error) {
2805
+ // Worker got stuck and won't terminate - this may cause process to hang
2793
2806
  if (error instanceof Error && /Failed to terminate worker/.test(error.message)) {
2794
2807
  ctx.state.addProcessTimeoutCause(`Failed to terminate worker while running ${paths.join(", ")}.`);
2795
2808
  } else if (ctx.isCancelling && error instanceof Error && /The task has been cancelled/.test(error.message)) {
@@ -2802,6 +2815,7 @@ function createForksPool(ctx, { execArgv, env }) {
2802
2815
  }
2803
2816
  }
2804
2817
  return async (specs, invalidates) => {
2818
+ // Cancel pending tasks from pool when possible
2805
2819
  ctx.onCancel(() => pool.cancelPendingTasks());
2806
2820
  const configs = new WeakMap();
2807
2821
  const getConfig = (project) => {
@@ -2822,9 +2836,14 @@ function createForksPool(ctx, { execArgv, env }) {
2822
2836
  if (isolated) {
2823
2837
  results.push(...await Promise.allSettled(files.map(({ file, environment, project }) => runFiles(project, getConfig(project), [file], environment, invalidates))));
2824
2838
  } else {
2839
+ // When isolation is disabled, we still need to isolate environments and workspace projects from each other.
2840
+ // Tasks are still running parallel but environments are isolated between tasks.
2825
2841
  const grouped = groupBy(files, ({ project, environment }) => project.name + environment.name + JSON.stringify(environment.options));
2826
2842
  for (const group of Object.values(grouped)) {
2843
+ // Push all files to pool's queue
2827
2844
  results.push(...await Promise.allSettled(group.map(({ file, environment, project }) => runFiles(project, getConfig(project), [file], environment, invalidates))));
2845
+ // Once all tasks are running or finished, recycle worker for isolation.
2846
+ // On-going workers will run in the previous environment.
2828
2847
  await new Promise((resolve) => pool.queueSize === 0 ? resolve() : pool.once("drain", resolve));
2829
2848
  await pool.recycleWorkers();
2830
2849
  }
@@ -2844,6 +2863,7 @@ function createForksPool(ctx, { execArgv, env }) {
2844
2863
  }
2845
2864
  const filesByOptions = groupBy(files, ({ project, environment }) => project.name + JSON.stringify(environment.options));
2846
2865
  for (const files of Object.values(filesByOptions)) {
2866
+ // Always run environments isolated between each other
2847
2867
  await pool.recycleWorkers();
2848
2868
  const filenames = files.map((f) => f.file);
2849
2869
  await runFiles(files[0].project, getConfig(files[0].project), filenames, files[0].environment, invalidates);
@@ -2933,6 +2953,7 @@ function createThreadsPool(ctx, { execArgv, env }) {
2933
2953
  name
2934
2954
  });
2935
2955
  } catch (error) {
2956
+ // Worker got stuck and won't terminate - this may cause process to hang
2936
2957
  if (error instanceof Error && /Failed to terminate worker/.test(error.message)) {
2937
2958
  ctx.state.addProcessTimeoutCause(`Failed to terminate worker while running ${paths.join(", ")}. \nSee https://vitest.dev/guide/common-errors.html#failed-to-terminate-worker for troubleshooting.`);
2938
2959
  } else if (ctx.isCancelling && error instanceof Error && /The task has been cancelled/.test(error.message)) {
@@ -2946,6 +2967,7 @@ function createThreadsPool(ctx, { execArgv, env }) {
2946
2967
  }
2947
2968
  }
2948
2969
  return async (specs, invalidates) => {
2970
+ // Cancel pending tasks from pool when possible
2949
2971
  ctx.onCancel(() => pool.cancelPendingTasks());
2950
2972
  const configs = new WeakMap();
2951
2973
  const getConfig = (project) => {
@@ -2965,9 +2987,14 @@ function createThreadsPool(ctx, { execArgv, env }) {
2965
2987
  if (isolated) {
2966
2988
  results.push(...await Promise.allSettled(files.map(({ file, environment, project }) => runFiles(project, getConfig(project), [file], environment, invalidates))));
2967
2989
  } else {
2990
+ // When isolation is disabled, we still need to isolate environments and workspace projects from each other.
2991
+ // Tasks are still running parallel but environments are isolated between tasks.
2968
2992
  const grouped = groupBy(files, ({ project, environment }) => project.name + environment.name + JSON.stringify(environment.options));
2969
2993
  for (const group of Object.values(grouped)) {
2994
+ // Push all files to pool's queue
2970
2995
  results.push(...await Promise.allSettled(group.map(({ file, environment, project }) => runFiles(project, getConfig(project), [file], environment, invalidates))));
2996
+ // Once all tasks are running or finished, recycle worker for isolation.
2997
+ // On-going workers will run in the previous environment.
2971
2998
  await new Promise((resolve) => pool.queueSize === 0 ? resolve() : pool.once("drain", resolve));
2972
2999
  await pool.recycleWorkers();
2973
3000
  }
@@ -2987,6 +3014,7 @@ function createThreadsPool(ctx, { execArgv, env }) {
2987
3014
  }
2988
3015
  const filesByOptions = groupBy(files, ({ project, environment }) => project.name + JSON.stringify(environment.options));
2989
3016
  for (const files of Object.values(filesByOptions)) {
3017
+ // Always run environments isolated between each other
2990
3018
  await pool.recycleWorkers();
2991
3019
  const filenames = files.map((f) => f.file);
2992
3020
  await runFiles(files[0].project, getConfig(files[0].project), filenames, files[0].environment, invalidates);
@@ -3021,6 +3049,7 @@ function createTypecheckPool(vitest) {
3021
3049
  }
3022
3050
  promisesMap.get(project)?.resolve();
3023
3051
  rerunTriggered.delete(project);
3052
+ // triggered by TSC watcher, not Vitest watcher, so we need to emulate what Vitest does in this case
3024
3053
  if (vitest.config.watch && !vitest.runningPromise) {
3025
3054
  await vitest.report("onFinished", files, []);
3026
3055
  await vitest.report("onWatcherStart", files, [...project.config.typecheck.ignoreSourceErrors ? [] : sourceErrors, ...vitest.state.getUnhandledErrors()]);
@@ -3060,7 +3089,7 @@ function createTypecheckPool(vitest) {
3060
3089
  }
3061
3090
  async function startTypechecker(project, files) {
3062
3091
  if (project.typechecker) {
3063
- return project.typechecker;
3092
+ return;
3064
3093
  }
3065
3094
  const checker = await createWorkspaceTypechecker(project, files);
3066
3095
  await checker.collectTests();
@@ -3085,6 +3114,7 @@ function createTypecheckPool(vitest) {
3085
3114
  const project = specsByProject[name][0].project;
3086
3115
  const files = specsByProject[name].map((spec) => spec.moduleId);
3087
3116
  const promise = createDefer();
3117
+ // check that watcher actually triggered rerun
3088
3118
  const _p = new Promise((resolve) => {
3089
3119
  const _i = setInterval(() => {
3090
3120
  if (!project.typechecker || rerunTriggered.has(project)) {
@@ -3109,7 +3139,7 @@ function createTypecheckPool(vitest) {
3109
3139
  }
3110
3140
  promises.push(promise);
3111
3141
  promisesMap.set(project, promise);
3112
- startTypechecker(project, files);
3142
+ promises.push(startTypechecker(project, files));
3113
3143
  }
3114
3144
  await Promise.all(promises);
3115
3145
  }
@@ -3285,6 +3315,7 @@ function createVmForksPool(ctx, { execArgv, env }) {
3285
3315
  channel
3286
3316
  });
3287
3317
  } catch (error) {
3318
+ // Worker got stuck and won't terminate - this may cause process to hang
3288
3319
  if (error instanceof Error && /Failed to terminate worker/.test(error.message)) {
3289
3320
  ctx.state.addProcessTimeoutCause(`Failed to terminate worker while running ${paths.join(", ")}.`);
3290
3321
  } else if (ctx.isCancelling && error instanceof Error && /The task has been cancelled/.test(error.message)) {
@@ -3297,6 +3328,7 @@ function createVmForksPool(ctx, { execArgv, env }) {
3297
3328
  }
3298
3329
  }
3299
3330
  return async (specs, invalidates) => {
3331
+ // Cancel pending tasks from pool when possible
3300
3332
  ctx.onCancel(() => pool.cancelPendingTasks());
3301
3333
  const configs = new Map();
3302
3334
  const getConfig = (project) => {
@@ -3330,9 +3362,11 @@ function getMemoryLimit$1(config) {
3330
3362
  if (typeof memory === "number") {
3331
3363
  return stringToBytes(limit, config.watch ? memory / 2 : memory);
3332
3364
  }
3365
+ // If totalmem is not supported we cannot resolve percentage based values like 0.5, "50%"
3333
3366
  if (typeof limit === "number" && limit > 1 || typeof limit === "string" && limit.at(-1) !== "%") {
3334
3367
  return stringToBytes(limit);
3335
3368
  }
3369
+ // just ignore "memoryLimit" value because we cannot detect memory limit
3336
3370
  return null;
3337
3371
  }
3338
3372
 
@@ -3414,6 +3448,7 @@ function createVmThreadsPool(ctx, { execArgv, env }) {
3414
3448
  name
3415
3449
  });
3416
3450
  } catch (error) {
3451
+ // Worker got stuck and won't terminate - this may cause process to hang
3417
3452
  if (error instanceof Error && /Failed to terminate worker/.test(error.message)) {
3418
3453
  ctx.state.addProcessTimeoutCause(`Failed to terminate worker while running ${paths.join(", ")}. \nSee https://vitest.dev/guide/common-errors.html#failed-to-terminate-worker for troubleshooting.`);
3419
3454
  } else if (ctx.isCancelling && error instanceof Error && /The task has been cancelled/.test(error.message)) {
@@ -3427,6 +3462,7 @@ function createVmThreadsPool(ctx, { execArgv, env }) {
3427
3462
  }
3428
3463
  }
3429
3464
  return async (specs, invalidates) => {
3465
+ // Cancel pending tasks from pool when possible
3430
3466
  ctx.onCancel(() => pool.cancelPendingTasks());
3431
3467
  const configs = new Map();
3432
3468
  const getConfig = (project) => {
@@ -3459,9 +3495,11 @@ function getMemoryLimit(config) {
3459
3495
  if (typeof memory === "number") {
3460
3496
  return stringToBytes(limit, config.watch ? memory / 2 : memory);
3461
3497
  }
3498
+ // If totalmem is not supported we cannot resolve percentage based values like 0.5, "50%"
3462
3499
  if (typeof limit === "number" && limit > 1 || typeof limit === "string" && limit.at(-1) !== "%") {
3463
3500
  return stringToBytes(limit);
3464
3501
  }
3502
+ // just ignore "memoryLimit" value because we cannot detect memory limit
3465
3503
  return null;
3466
3504
  }
3467
3505
 
@@ -3499,6 +3537,8 @@ function createPool(ctx) {
3499
3537
  vmForks: null,
3500
3538
  typescript: null
3501
3539
  };
3540
+ // in addition to resolve.conditions Vite also adds production/development,
3541
+ // see: https://github.com/vitejs/vite/blob/af2aa09575229462635b7cbb6d248ca853057ba2/packages/vite/src/node/plugins/resolve.ts#L1056-L1080
3502
3542
  const viteMajor = Number(version.split(".")[0]);
3503
3543
  const potentialConditions = new Set(viteMajor >= 6 ? ctx.vite.config.ssr.resolve?.conditions ?? [] : [
3504
3544
  "production",
@@ -3519,6 +3559,8 @@ function createPool(ctx) {
3519
3559
  }
3520
3560
  return condition;
3521
3561
  }).flatMap((c) => ["--conditions", c]);
3562
+ // Instead of passing whole process.execArgv to the workers, pick allowed options.
3563
+ // Some options may crash worker, e.g. --prof, --title. nodejs/node#41103
3522
3564
  const execArgv = process.execArgv.filter((execArg) => execArg.startsWith("--cpu-prof") || execArg.startsWith("--heap-prof") || execArg.startsWith("--diagnostic-dir"));
3523
3565
  async function executeTests(method, files, invalidate) {
3524
3566
  const options = {
@@ -3533,6 +3575,7 @@ function createPool(ctx) {
3533
3575
  ...ctx.config.env
3534
3576
  }
3535
3577
  };
3578
+ // env are case-insensitive on Windows, but spawned processes don't support it
3536
3579
  if (isWindows) {
3537
3580
  for (const name in options.env) {
3538
3581
  options.env[name.toUpperCase()] = options.env[name];
@@ -3654,6 +3697,7 @@ class BaseSequencer {
3654
3697
  constructor(ctx) {
3655
3698
  this.ctx = ctx;
3656
3699
  }
3700
+ // async so it can be extended by other sequelizers
3657
3701
  async shard(files) {
3658
3702
  const { config } = this.ctx;
3659
3703
  const { index, count } = config.shard;
@@ -3669,6 +3713,7 @@ class BaseSequencer {
3669
3713
  };
3670
3714
  }).sort((a, b) => a.hash < b.hash ? -1 : a.hash > b.hash ? 1 : 0).slice(shardStart, shardEnd).map(({ spec }) => spec);
3671
3715
  }
3716
+ // async so it can be extended by other sequelizers
3672
3717
  async sort(files) {
3673
3718
  const cache = this.ctx.cache;
3674
3719
  return [...files].sort((a, b) => {
@@ -3679,17 +3724,21 @@ class BaseSequencer {
3679
3724
  if (!aState || !bState) {
3680
3725
  const statsA = cache.getFileStats(keyA);
3681
3726
  const statsB = cache.getFileStats(keyB);
3727
+ // run unknown first
3682
3728
  if (!statsA || !statsB) {
3683
3729
  return !statsA && statsB ? -1 : !statsB && statsA ? 1 : 0;
3684
3730
  }
3731
+ // run larger files first
3685
3732
  return statsB.size - statsA.size;
3686
3733
  }
3734
+ // run failed first
3687
3735
  if (aState.failed && !bState.failed) {
3688
3736
  return -1;
3689
3737
  }
3690
3738
  if (!aState.failed && bState.failed) {
3691
3739
  return 1;
3692
3740
  }
3741
+ // run longer first
3693
3742
  return bState.duration - aState.duration;
3694
3743
  });
3695
3744
  }
@@ -3824,9 +3873,10 @@ function resolveConfig$1(vitest, options, viteConfig) {
3824
3873
  if (resolved.minWorkers) {
3825
3874
  resolved.minWorkers = resolveInlineWorkerOption(resolved.minWorkers);
3826
3875
  }
3827
- resolved.browser ??= {};
3876
+ // run benchmark sequentially by default
3828
3877
  resolved.fileParallelism ??= mode !== "benchmark";
3829
3878
  if (!resolved.fileParallelism) {
3879
+ // ignore user config, parallelism cannot be implemented without limiting workers
3830
3880
  resolved.maxWorkers = 1;
3831
3881
  resolved.minWorkers = 1;
3832
3882
  }
@@ -3842,13 +3892,19 @@ function resolveConfig$1(vitest, options, viteConfig) {
3842
3892
  throw new Error(`You cannot use ${inspectOption} without "--no-file-parallelism", "poolOptions.threads.singleThread" or "poolOptions.forks.singleFork"`);
3843
3893
  }
3844
3894
  }
3895
+ // apply browser CLI options only if the config already has the browser config and not disabled manually
3896
+ if (vitest._cliOptions.browser && resolved.browser && (resolved.browser.enabled !== false || vitest._cliOptions.browser.enabled)) {
3897
+ resolved.browser = mergeConfig(resolved.browser, vitest._cliOptions.browser);
3898
+ }
3899
+ resolved.browser ??= {};
3845
3900
  const browser = resolved.browser;
3846
- if (browser.enabled && viteConfig.test?.browser) {
3901
+ if (browser.enabled) {
3847
3902
  if (!browser.name && !browser.instances) {
3848
3903
  throw new Error(`Vitest Browser Mode requires "browser.name" (deprecated) or "browser.instances" options, none were set.`);
3849
3904
  }
3850
3905
  const instances = browser.instances;
3851
3906
  if (browser.name && browser.instances) {
3907
+ // --browser=chromium filters configs to a single one
3852
3908
  browser.instances = browser.instances.filter((instance) => instance.browser === browser.name);
3853
3909
  }
3854
3910
  if (browser.instances && !browser.instances.length) {
@@ -3856,6 +3912,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
3856
3912
  }
3857
3913
  }
3858
3914
  const playwrightChromiumOnly = isPlaywrightChromiumOnly(vitest, resolved);
3915
+ // Browser-mode "Playwright + Chromium" only features:
3859
3916
  if (browser.enabled && !playwrightChromiumOnly) {
3860
3917
  const browserConfig = { browser: {
3861
3918
  provider: browser.provider,
@@ -3912,6 +3969,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
3912
3969
  resolved.deps.web.transformGlobPattern ??= [];
3913
3970
  resolved.setupFiles = toArray(resolved.setupFiles || []).map((file) => resolvePath(file, resolved.root));
3914
3971
  resolved.globalSetup = toArray(resolved.globalSetup || []).map((file) => resolvePath(file, resolved.root));
3972
+ // override original exclude array for cases where user re-uses same object in test.exclude
3915
3973
  resolved.coverage.exclude = [
3916
3974
  ...resolved.coverage.exclude,
3917
3975
  ...resolved.setupFiles.map((file) => `${resolved.coverage.allowExternal ? "**/" : ""}${relative(resolved.root, file)}`),
@@ -3942,6 +4000,8 @@ function resolveConfig$1(vitest, options, viteConfig) {
3942
4000
  if (resolved.cliExclude) {
3943
4001
  resolved.exclude.push(...resolved.cliExclude);
3944
4002
  }
4003
+ // vitenode will try to import such file with native node,
4004
+ // but then our mocker will not work properly
3945
4005
  if (resolved.server.deps.inline !== true) {
3946
4006
  const ssrOptions = viteConfig.ssr;
3947
4007
  if (ssrOptions?.noExternal === true && resolved.server.deps.inline == null) {
@@ -4055,6 +4115,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
4055
4115
  }
4056
4116
  }
4057
4117
  if (typeof resolved.workspace === "string") {
4118
+ // if passed down from the CLI and it's relative, resolve relative to CWD
4058
4119
  resolved.workspace = typeof options.workspace === "string" && options.workspace[0] === "." ? resolve(process.cwd(), options.workspace) : resolvePath(resolved.workspace, resolved.root);
4059
4120
  }
4060
4121
  if (!builtinPools.includes(resolved.pool)) {
@@ -4074,6 +4135,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
4074
4135
  ...benchmarkConfigDefaults,
4075
4136
  ...resolved.benchmark
4076
4137
  };
4138
+ // override test config
4077
4139
  resolved.coverage.enabled = false;
4078
4140
  resolved.typecheck.enabled = false;
4079
4141
  resolved.include = resolved.benchmark.include;
@@ -4088,6 +4150,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
4088
4150
  if (options.outputFile) {
4089
4151
  resolved.benchmark.outputFile = options.outputFile;
4090
4152
  }
4153
+ // --compare from cli
4091
4154
  if (options.compare) {
4092
4155
  resolved.benchmark.compare = options.compare;
4093
4156
  }
@@ -4099,6 +4162,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
4099
4162
  resolved.diff = resolvePath(resolved.diff, resolved.root);
4100
4163
  resolved.forceRerunTriggers.push(resolved.diff);
4101
4164
  }
4165
+ // the server has been created, we don't need to override vite.server options
4102
4166
  const api = resolveApiServerConfig(options, defaultPort);
4103
4167
  resolved.api = {
4104
4168
  ...api,
@@ -4107,8 +4171,18 @@ function resolveConfig$1(vitest, options, viteConfig) {
4107
4171
  if (options.related) {
4108
4172
  resolved.related = toArray(options.related).map((file) => resolve(resolved.root, file));
4109
4173
  }
4174
+ /*
4175
+ * Reporters can be defined in many different ways:
4176
+ * { reporter: 'json' }
4177
+ * { reporter: { onFinish() { method() } } }
4178
+ * { reporter: ['json', { onFinish() { method() } }] }
4179
+ * { reporter: [[ 'json' ]] }
4180
+ * { reporter: [[ 'json' ], 'html'] }
4181
+ * { reporter: [[ 'json', { outputFile: 'test.json' } ], 'html'] }
4182
+ */
4110
4183
  if (options.reporters) {
4111
4184
  if (!Array.isArray(options.reporters)) {
4185
+ // Reporter name, e.g. { reporters: 'json' }
4112
4186
  if (typeof options.reporters === "string") {
4113
4187
  resolved.reporters = [[options.reporters, {}]];
4114
4188
  } else {
@@ -4118,18 +4192,24 @@ function resolveConfig$1(vitest, options, viteConfig) {
4118
4192
  resolved.reporters = [];
4119
4193
  for (const reporter of options.reporters) {
4120
4194
  if (Array.isArray(reporter)) {
4195
+ // Reporter with options, e.g. { reporters: [ [ 'json', { outputFile: 'test.json' } ] ] }
4121
4196
  resolved.reporters.push([reporter[0], reporter[1] || {}]);
4122
4197
  } else if (typeof reporter === "string") {
4198
+ // Reporter name in array, e.g. { reporters: ["html", "json"]}
4123
4199
  resolved.reporters.push([reporter, {}]);
4124
4200
  } else {
4201
+ // Inline reporter, e.g. { reporter: [{ onFinish() { method() } }] }
4125
4202
  resolved.reporters.push(reporter);
4126
4203
  }
4127
4204
  }
4128
4205
  }
4129
4206
  }
4130
4207
  if (mode !== "benchmark") {
4208
+ // @ts-expect-error "reporter" is from CLI, should be absolute to the running directory
4209
+ // it is passed down as "vitest --reporter ../reporter.js"
4131
4210
  const reportersFromCLI = resolved.reporter;
4132
4211
  const cliReporters = toArray(reportersFromCLI || []).map((reporter) => {
4212
+ // ./reporter.js || ../reporter.js, but not .reporters/reporter.js
4133
4213
  if (/^\.\.?\//.test(reporter)) {
4134
4214
  return resolve(process.cwd(), reporter);
4135
4215
  }
@@ -4141,6 +4221,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
4141
4221
  }
4142
4222
  if (!resolved.reporters.length) {
4143
4223
  resolved.reporters.push(["default", {}]);
4224
+ // also enable github-actions reporter as a default
4144
4225
  if (process.env.GITHUB_ACTIONS === "true") {
4145
4226
  resolved.reporters.push(["github-actions", {}]);
4146
4227
  }
@@ -4168,6 +4249,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
4168
4249
  resolved.sequence.shuffle = tests;
4169
4250
  }
4170
4251
  if (!resolved.sequence?.sequencer) {
4252
+ // CLI flag has higher priority
4171
4253
  resolved.sequence.sequencer = resolved.sequence.shuffle ? RandomSequencer : BaseSequencer;
4172
4254
  }
4173
4255
  resolved.sequence.groupOrder ??= 0;
@@ -4188,11 +4270,11 @@ function resolveConfig$1(vitest, options, viteConfig) {
4188
4270
  if (resolved.typecheck.enabled) {
4189
4271
  logger.console.warn(c.yellow("Testing types with tsc and vue-tsc is an experimental feature.\nBreaking changes might not follow SemVer, please pin Vitest's version when using it."));
4190
4272
  }
4191
- resolved.browser ??= {};
4192
4273
  resolved.browser.enabled ??= false;
4193
4274
  resolved.browser.headless ??= isCI;
4194
4275
  resolved.browser.isolate ??= true;
4195
4276
  resolved.browser.fileParallelism ??= options.fileParallelism ?? mode !== "benchmark";
4277
+ // disable in headless mode by default, and if CI is detected
4196
4278
  resolved.browser.ui ??= resolved.browser.headless === true ? false : !isCI;
4197
4279
  if (resolved.browser.screenshotDirectory) {
4198
4280
  resolved.browser.screenshotDirectory = resolve(resolved.root, resolved.browser.screenshotDirectory);
@@ -4217,6 +4299,7 @@ function resolveConfig$1(vitest, options, viteConfig) {
4217
4299
  resolved.browser.provider = "preview";
4218
4300
  }
4219
4301
  resolved.browser.api = resolveApiServerConfig(resolved.browser, defaultBrowserPort) || { port: defaultBrowserPort };
4302
+ // enable includeTaskLocation by default in UI mode
4220
4303
  if (resolved.browser.enabled) {
4221
4304
  if (resolved.browser.ui) {
4222
4305
  resolved.includeTaskLocation ??= true;
@@ -4242,14 +4325,17 @@ function isBrowserEnabled(config) {
4242
4325
  return Boolean(config.browser?.enabled);
4243
4326
  }
4244
4327
  function resolveCoverageReporters(configReporters) {
4328
+ // E.g. { reporter: "html" }
4245
4329
  if (!Array.isArray(configReporters)) {
4246
4330
  return [[configReporters, {}]];
4247
4331
  }
4248
4332
  const resolvedReporters = [];
4249
4333
  for (const reporter of configReporters) {
4250
4334
  if (Array.isArray(reporter)) {
4335
+ // E.g. { reporter: [ ["html", { skipEmpty: true }], ["lcov"], ["json", { file: "map.json" }] ]}
4251
4336
  resolvedReporters.push([reporter[0], reporter[1] || {}]);
4252
4337
  } else {
4338
+ // E.g. { reporter: ["html", "json"]}
4253
4339
  resolvedReporters.push([reporter, {}]);
4254
4340
  }
4255
4341
  }
@@ -4268,6 +4354,7 @@ function isPlaywrightChromiumOnly(vitest, config) {
4268
4354
  }
4269
4355
  for (const instance of browser.instances) {
4270
4356
  const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser);
4357
+ // browser config is filtered out
4271
4358
  if (!vitest.matchesProjectFilter(name)) {
4272
4359
  continue;
4273
4360
  }
@@ -4375,6 +4462,7 @@ class BaseCoverageProvider {
4375
4462
  }
4376
4463
  const testFilenames = testFiles.join();
4377
4464
  const filename = resolve(this.coverageFilesDirectory, `coverage-${uniqueId++}.json`);
4465
+ // If there's a result from previous run, overwrite it
4378
4466
  entry[transformMode][testFilenames] = filename;
4379
4467
  const promise = promises$1.writeFile(filename, JSON.stringify(coverage), "utf-8");
4380
4468
  this.pendingPromises.push(promise);
@@ -4406,6 +4494,7 @@ class BaseCoverageProvider {
4406
4494
  async cleanAfterRun() {
4407
4495
  this.coverageFiles = new Map();
4408
4496
  await promises$1.rm(this.coverageFilesDirectory, { recursive: true });
4497
+ // Remove empty reports directory, e.g. when only text-reporter is used
4409
4498
  if (readdirSync(this.options.reportsDirectory).length === 0) {
4410
4499
  await promises$1.rm(this.options.reportsDirectory, { recursive: true });
4411
4500
  }
@@ -4417,6 +4506,7 @@ class BaseCoverageProvider {
4417
4506
  }
4418
4507
  async reportCoverage(coverageMap, { allTestsRun }) {
4419
4508
  await this.generateReports(coverageMap || this.createCoverageMap(), allTestsRun);
4509
+ // In watch mode we need to preserve the previous results if cleanOnRerun is disabled
4420
4510
  const keepResults = !this.options.cleanOnRerun && this.ctx.config.watch;
4421
4511
  if (!keepResults) {
4422
4512
  await this.cleanAfterRun();
@@ -4466,6 +4556,7 @@ class BaseCoverageProvider {
4466
4556
  thresholds: globThresholds
4467
4557
  });
4468
4558
  }
4559
+ // Global threshold is for all files, even if they are included by glob patterns
4469
4560
  for (const file of files) {
4470
4561
  const fileCoverage = coverageMap.fileCoverageFor(file);
4471
4562
  globalCoverageMap.addFileCoverage(fileCoverage);
@@ -4490,6 +4581,7 @@ class BaseCoverageProvider {
4490
4581
  if (thresholds.branches === undefined && thresholds.functions === undefined && thresholds.lines === undefined && thresholds.statements === undefined) {
4491
4582
  continue;
4492
4583
  }
4584
+ // Construct list of coverage summaries where thresholds are compared against
4493
4585
  const summaries = this.options.thresholds?.perFile ? coverageMap.files().map((file) => ({
4494
4586
  file,
4495
4587
  summary: coverageMap.fileCoverageFor(file).toSummary()
@@ -4497,6 +4589,7 @@ class BaseCoverageProvider {
4497
4589
  file: null,
4498
4590
  summary: coverageMap.getCoverageSummary()
4499
4591
  }];
4592
+ // Check thresholds of each summary
4500
4593
  for (const { summary, file } of summaries) {
4501
4594
  for (const thresholdKey of THRESHOLD_KEYS) {
4502
4595
  const threshold = thresholds[thresholdKey];
@@ -4568,6 +4661,7 @@ class BaseCoverageProvider {
4568
4661
  const absoluteThreshold = threshold * -1;
4569
4662
  const actual = Math.max(...summaries.map((summary) => summary[key].total - summary[key].covered));
4570
4663
  if (actual < absoluteThreshold) {
4664
+ // If everything was covered, set new threshold to 100% (since a threshold of 0 would be considered as 0%)
4571
4665
  const updatedThreshold = actual === 0 ? 100 : actual * -1;
4572
4666
  thresholdsToUpdate.push([key, updatedThreshold]);
4573
4667
  }
@@ -4627,6 +4721,7 @@ class BaseCoverageProvider {
4627
4721
  return async function transformFile(filename) {
4628
4722
  let lastError;
4629
4723
  for (const { root, vitenode, isBrowserEnabled } of servers) {
4724
+ // On Windows root doesn't start with "/" while filenames do
4630
4725
  if (!filename.startsWith(root) && !filename.startsWith(`/${root}`)) {
4631
4726
  continue;
4632
4727
  }
@@ -4642,6 +4737,7 @@ class BaseCoverageProvider {
4642
4737
  lastError = error;
4643
4738
  }
4644
4739
  }
4740
+ // All vite-node servers failed to transform the file
4645
4741
  throw lastError;
4646
4742
  };
4647
4743
  }
@@ -4670,6 +4766,7 @@ function resolveGlobThresholds(thresholds) {
4670
4766
  }
4671
4767
  function assertConfigurationModule(config) {
4672
4768
  try {
4769
+ // @ts-expect-error -- Intentional unsafe null pointer check as wrapped in try-catch
4673
4770
  if (typeof config.test.coverage.thresholds !== "object") {
4674
4771
  throw new TypeError("Expected config.test.coverage.thresholds to be an object");
4675
4772
  }
@@ -4681,13 +4778,16 @@ function assertConfigurationModule(config) {
4681
4778
  function resolveConfig(configModule) {
4682
4779
  const mod = configModule.exports.default;
4683
4780
  try {
4781
+ // Check for "export default { test: {...} }"
4684
4782
  if (mod.$type === "object") {
4685
4783
  return mod;
4686
4784
  }
4785
+ // "export default defineConfig(...)"
4687
4786
  let config = resolveDefineConfig(mod);
4688
4787
  if (config) {
4689
4788
  return config;
4690
4789
  }
4790
+ // "export default mergeConfig(..., defineConfig(...))"
4691
4791
  if (mod.$type === "function-call" && mod.$callee === "mergeConfig") {
4692
4792
  config = resolveMergeConfig(mod);
4693
4793
  if (config) {
@@ -4695,19 +4795,23 @@ function resolveConfig(configModule) {
4695
4795
  }
4696
4796
  }
4697
4797
  } catch (error) {
4798
+ // Reduce magicast's verbose errors to readable ones
4698
4799
  throw new Error(error instanceof Error ? error.message : String(error));
4699
4800
  }
4700
4801
  throw new Error("Failed to update coverage thresholds. Configuration file is too complex.");
4701
4802
  }
4702
4803
  function resolveDefineConfig(mod) {
4703
4804
  if (mod.$type === "function-call" && mod.$callee === "defineConfig") {
4805
+ // "export default defineConfig({ test: {...} })"
4704
4806
  if (mod.$args[0].$type === "object") {
4705
4807
  return mod.$args[0];
4706
4808
  }
4707
4809
  if (mod.$args[0].$type === "arrow-function-expression") {
4708
4810
  if (mod.$args[0].$body.$type === "object") {
4811
+ // "export default defineConfig(() => ({ test: {...} }))"
4709
4812
  return mod.$args[0].$body;
4710
4813
  }
4814
+ // "export default defineConfig(() => mergeConfig({...}, ...))"
4711
4815
  const config = resolveMergeConfig(mod.$args[0].$body);
4712
4816
  if (config) {
4713
4817
  return config;