vitest 3.2.0-beta.1 → 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 (74) hide show
  1. package/LICENSE.md +0 -232
  2. package/dist/browser.d.ts +5 -3
  3. package/dist/browser.js +3 -4
  4. package/dist/chunks/{base.SfTiRNZf.js → base.D4119yLM.js} +4 -3
  5. package/dist/chunks/{benchmark.BoF7jW0Q.js → benchmark.Cf_PACH1.js} +1 -1
  6. package/dist/chunks/{cac.TfX2-DVH.js → cac.DWaWHIIE.js} +21 -16
  7. package/dist/chunks/{cli-api.2970Nj9J.js → cli-api.CnmEXkxs.js} +292 -59
  8. package/dist/chunks/{config.d.UqE-KR0o.d.ts → config.d.D2ROskhv.d.ts} +2 -0
  9. package/dist/chunks/{console.K1NMVOSc.js → console.Cwr-MFPV.js} +3 -2
  10. package/dist/chunks/{constants.BZZyIeIE.js → constants.DnKduX2e.js} +1 -0
  11. package/dist/chunks/{coverage.z0LVMxgb.js → coverage.C73DaDgS.js} +241 -4226
  12. package/dist/chunks/{creator.CuL7xDWI.js → creator.C8WKy2eW.js} +26 -44
  13. package/dist/chunks/{date.CDOsz-HY.js → date.ByMsSlOr.js} +25 -0
  14. package/dist/chunks/{defaults.DSxsTG0h.js → defaults.DpVH7vbg.js} +1 -0
  15. package/dist/chunks/{environment.d.D8YDy2v5.d.ts → environment.d.cL3nLXbE.d.ts} +1 -0
  16. package/dist/chunks/{execute.BpmIjFTD.js → execute.B3q-2LPV.js} +28 -5
  17. package/dist/chunks/{global.d.BCOHQEpR.d.ts → global.d.BNLIi6yo.d.ts} +13 -11
  18. package/dist/chunks/{globals.Cg4NtV4P.js → globals.CI21aWXF.js} +7 -7
  19. package/dist/chunks/{index.DFXFpH3w.js → index.2jgTs_Q5.js} +19 -1
  20. package/dist/chunks/{index.CUacZlWG.js → index.Bter3jj9.js} +954 -954
  21. package/dist/chunks/{index.DbWBPwtH.js → index.CbT4iuwc.js} +7 -4
  22. package/dist/chunks/index.D3XRDfWc.js +213 -0
  23. package/dist/chunks/{index.BPc7M5ni.js → index.DNgLEKsQ.js} +5 -15
  24. package/dist/chunks/index.JOzufsrU.js +276 -0
  25. package/dist/chunks/{index.DBIGubLC.js → index.X0nbfr6-.js} +7 -7
  26. package/dist/chunks/{inspector.DbDkSkFn.js → inspector.BFsh5KO0.js} +3 -0
  27. package/dist/chunks/{node.3xsWotC9.js → node.Be-ntJnD.js} +1 -1
  28. package/dist/chunks/{reporters.d.DGm4k1Wx.d.ts → reporters.d.Bt4IGtsa.d.ts} +41 -6
  29. package/dist/chunks/{rpc.D9_013TY.js → rpc.BKExFSRG.js} +2 -1
  30. package/dist/chunks/{runBaseTests.CguliJB5.js → runBaseTests.B_M1TTsK.js} +19 -11
  31. package/dist/chunks/{setup-common.BP6KrF_Z.js → setup-common.CF-O-dZX.js} +2 -3
  32. package/dist/chunks/typechecker.BgzF-6iO.js +954 -0
  33. package/dist/chunks/{utils.CgTj3MsC.js → utils.BlI4TC7Y.js} +1 -0
  34. package/dist/chunks/{utils.BfxieIyZ.js → utils.DPCq3gzW.js} +3 -0
  35. package/dist/chunks/{vi.BFR5YIgu.js → vi.pkoYCV6A.js} +25 -2
  36. package/dist/chunks/{vite.d.DjP_ALCZ.d.ts → vite.d.B-Kx3KCF.d.ts} +3 -1
  37. package/dist/chunks/{vm.CuLHT1BG.js → vm.DPYem2so.js} +72 -4
  38. package/dist/chunks/{worker.d.CoCI7hzP.d.ts → worker.d.BKbBp2ga.d.ts} +2 -2
  39. package/dist/chunks/{worker.d.D5Xdi-Zr.d.ts → worker.d.Bl1O4kuf.d.ts} +1 -1
  40. package/dist/cli.js +21 -2
  41. package/dist/config.cjs +2 -0
  42. package/dist/config.d.ts +7 -6
  43. package/dist/config.js +2 -2
  44. package/dist/coverage.d.ts +4 -4
  45. package/dist/coverage.js +7 -10
  46. package/dist/environments.d.ts +6 -2
  47. package/dist/environments.js +1 -1
  48. package/dist/execute.d.ts +9 -3
  49. package/dist/execute.js +1 -1
  50. package/dist/index.d.ts +25 -35
  51. package/dist/index.js +5 -6
  52. package/dist/node.d.ts +18 -10
  53. package/dist/node.js +22 -22
  54. package/dist/reporters.d.ts +4 -4
  55. package/dist/reporters.js +14 -14
  56. package/dist/runners.d.ts +1 -1
  57. package/dist/runners.js +13 -5
  58. package/dist/snapshot.js +2 -2
  59. package/dist/suite.js +2 -2
  60. package/dist/worker.js +9 -5
  61. package/dist/workers/forks.js +6 -4
  62. package/dist/workers/runVmTests.js +14 -10
  63. package/dist/workers/threads.js +4 -4
  64. package/dist/workers/vmForks.js +6 -6
  65. package/dist/workers/vmThreads.js +6 -6
  66. package/dist/workers.d.ts +4 -4
  67. package/dist/workers.js +10 -10
  68. package/package.json +22 -26
  69. package/dist/chunks/index.Bw6JxgX8.js +0 -143
  70. package/dist/chunks/run-once.Dimr7O9f.js +0 -47
  71. package/dist/chunks/typechecker.DYQbn8uK.js +0 -956
  72. package/dist/chunks/utils.8gfOgtry.js +0 -207
  73. package/dist/utils.d.ts +0 -3
  74. package/dist/utils.js +0 -2
@@ -1,19 +1,19 @@
1
1
  import { promises, existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { extname, normalize, relative, dirname, resolve, join, basename, isAbsolute } from 'pathe';
3
3
  import { C as CoverageProviderMap } from './coverage.0iPg4Wrz.js';
4
- import p, { resolve as resolve$1 } from 'node:path';
4
+ import path, { resolve as resolve$1 } from 'node:path';
5
5
  import { noop, isPrimitive, createDefer, highlight, toArray, deepMerge, nanoid, slash, deepClone, notNullish } from '@vitest/utils';
6
- import { f as findUp, p as prompt } from './index.DBIGubLC.js';
6
+ import { f as findUp, p as prompt } from './index.X0nbfr6-.js';
7
7
  import * as vite from 'vite';
8
- import { searchForWorkspaceRoot, version, mergeConfig, createServer } from 'vite';
9
- import { A as API_PATH, c as configFiles, d as defaultBrowserPort, w as workspacesFiles, a as defaultPort } from './constants.BZZyIeIE.js';
8
+ import { searchForWorkspaceRoot, version, createServer, mergeConfig } from 'vite';
9
+ import { A as API_PATH, c as configFiles, d as defaultBrowserPort, w as workspacesFiles, a as defaultPort } from './constants.DnKduX2e.js';
10
10
  import { generateFileHash, limitConcurrency, createFileTask, hasFailed, getTasks, getTests } from '@vitest/runner/utils';
11
11
  import { SnapshotManager } from '@vitest/snapshot/manager';
12
12
  import { ViteNodeRunner } from 'vite-node/client';
13
13
  import { ViteNodeServer } from 'vite-node/server';
14
- import { v as version$1 } from './cac.TfX2-DVH.js';
14
+ import { v as version$1 } from './cac.DWaWHIIE.js';
15
15
  import { c as createBirpc } from './index.CJ0plNrh.js';
16
- import { p as parse, s as stringify, g as printError, h as generateCodeFrame, b as BenchmarkReportsMap, R as ReportersMap, i as BlobReporter, r as readBlobs, H as HangingProcessReporter } from './index.CUacZlWG.js';
16
+ import { p as parse, s as stringify, d as printError, f as formatProjectName, w as withLabel, e as errorBanner, h as divider, i as generateCodeFrame, R as ReportersMap, j as BlobReporter, r as readBlobs, H as HangingProcessReporter } from './index.Bter3jj9.js';
17
17
  import require$$0$3 from 'events';
18
18
  import require$$1$1 from 'https';
19
19
  import require$$2 from 'http';
@@ -28,21 +28,22 @@ import { g as getDefaultExportFromCjs } from './_commonjsHelpers.BFTU3MAI.js';
28
28
  import { parseErrorStacktrace } from '@vitest/utils/source-map';
29
29
  import crypto from 'node:crypto';
30
30
  import { distDir, rootDir } from '../path.js';
31
- import { R as RandomSequencer, i as isPackageExists, h as hash, V as VitestCache, g as getFilePoolName, d as isBrowserEnabled, m as mm, r as resolveConfig, e as groupBy, f as getCoverageProvider, j as createPool, w as wildcardPatternToRegExp, a as resolveApiServerConfig, s as stdout } from './coverage.z0LVMxgb.js';
32
- import { c as convertTasksToEvents } from './typechecker.DYQbn8uK.js';
31
+ import { R as RandomSequencer, i as isPackageExists, h as hash, V as VitestCache, g as getFilePoolName, d as isBrowserEnabled, r as resolveConfig, e as groupBy, f as getCoverageProvider, j as createPool, w as wildcardPatternToRegExp, a as resolveApiServerConfig, s as stdout } from './coverage.C73DaDgS.js';
32
+ import { c as convertTasksToEvents } from './typechecker.BgzF-6iO.js';
33
33
  import { Console } from 'node:console';
34
34
  import c from 'tinyrainbow';
35
- import { c as formatProjectName, w as withLabel, e as errorBanner, d as divider } from './utils.8gfOgtry.js';
36
35
  import { createRequire } from 'node:module';
37
36
  import url from 'node:url';
38
37
  import { i as isTTY, a as isWindows } from './env.Dq0hM4Xv.js';
39
38
  import { rm } from 'node:fs/promises';
40
39
  import nodeos__default, { tmpdir } from 'node:os';
40
+ import pm from 'picomatch';
41
41
  import { glob, isDynamicPattern } from 'tinyglobby';
42
42
  import { normalizeRequestId, cleanUrl } from 'vite-node/utils';
43
43
  import { hoistMocksPlugin, automockPlugin } from '@vitest/mocker/node';
44
- import { c as configDefaults } from './defaults.DSxsTG0h.js';
44
+ import { c as configDefaults } from './defaults.DpVH7vbg.js';
45
45
  import MagicString from 'magic-string';
46
+ import { a as BenchmarkReportsMap } from './index.JOzufsrU.js';
46
47
  import assert$1 from 'node:assert';
47
48
  import { serializeError } from '@vitest/utils/error';
48
49
  import readline from 'node:readline';
@@ -778,6 +779,14 @@ function requirePermessageDeflate () {
778
779
  this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
779
780
  this[kError][kStatusCode] = 1009;
780
781
  this.removeListener('data', inflateOnData);
782
+
783
+ //
784
+ // The choice to employ `zlib.reset()` over `zlib.close()` is dictated by the
785
+ // fact that in Node.js versions prior to 13.10.0, the callback for
786
+ // `zlib.flush()` is not called if `zlib.close()` is used. Utilizing
787
+ // `zlib.reset()` ensures that either the callback is invoked or an error is
788
+ // emitted.
789
+ //
781
790
  this.reset();
782
791
  }
783
792
 
@@ -793,6 +802,12 @@ function requirePermessageDeflate () {
793
802
  // closed when an error is emitted.
794
803
  //
795
804
  this[kPerMessageDeflate]._inflate = null;
805
+
806
+ if (this[kError]) {
807
+ this[kCallback](this[kError]);
808
+ return;
809
+ }
810
+
796
811
  err[kStatusCode] = 1007;
797
812
  this[kCallback](err);
798
813
  }
@@ -3510,7 +3525,7 @@ function requireWebsocket () {
3510
3525
  if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) {
3511
3526
  invalidUrlMessage =
3512
3527
  'The URL\'s protocol must be one of "ws:", "wss:", ' +
3513
- '"http:", "https", or "ws+unix:"';
3528
+ '"http:", "https:", or "ws+unix:"';
3514
3529
  } else if (isIpcUrl && !parsedUrl.pathname) {
3515
3530
  invalidUrlMessage = "The URL's pathname is empty";
3516
3531
  } else if (parsedUrl.hash) {
@@ -5034,7 +5049,10 @@ function clearId(id) {
5034
5049
  return id?.replace(/\?v=\w+$/, "") || "";
5035
5050
  }
5036
5051
 
5052
+ // Serialization support utils.
5037
5053
  function cloneByOwnProperties(value) {
5054
+ // Clones the value's properties into a new Object. The simpler approach of
5055
+ // Object.assign() won't work in the case that properties are not enumerable.
5038
5056
  return Object.getOwnPropertyNames(value).reduce((clone, prop) => ({
5039
5057
  ...clone,
5040
5058
  [prop]: value[prop]
@@ -5060,12 +5078,15 @@ function stringifyReplace(key, value) {
5060
5078
 
5061
5079
  function isValidApiRequest(config, req) {
5062
5080
  const url = new URL(req.url ?? "", "http://localhost");
5081
+ // validate token. token is injected in ui/tester/orchestrator html, which is cross origin protected.
5063
5082
  try {
5064
5083
  const token = url.searchParams.get("token");
5065
5084
  if (token && crypto.timingSafeEqual(Buffer.from(token), Buffer.from(config.api.token))) {
5066
5085
  return true;
5067
5086
  }
5068
- } catch {}
5087
+ }
5088
+ // an error is thrown when the length is incorrect
5089
+ catch {}
5069
5090
  return false;
5070
5091
  }
5071
5092
 
@@ -5266,6 +5287,7 @@ class BrowserSessions {
5266
5287
  this.sessions.delete(sessionId);
5267
5288
  }
5268
5289
  createSession(sessionId, project, pool) {
5290
+ // this promise only waits for the WS connection with the orhcestrator to be established
5269
5291
  const defer = createDefer();
5270
5292
  const timeout = setTimeout(() => {
5271
5293
  defer.reject(new Error(`Failed to connect to the browser session "${sessionId}" [${project.name}] within the timeout.`));
@@ -5488,6 +5510,7 @@ class Logger {
5488
5510
  this.log(PAD + c.dim(c.green(`UI started at http://${host}:${c.bold(port)}${base}`)));
5489
5511
  } else if (this.ctx.config.api?.port) {
5490
5512
  const resolvedUrls = this.ctx.server.resolvedUrls;
5513
+ // workaround for https://github.com/vitejs/vite/issues/15438, it was fixed in vite 5.1
5491
5514
  const fallbackUrl = `http://${this.ctx.config.api.host || "localhost"}:${this.ctx.config.api.port}`;
5492
5515
  const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0] ?? fallbackUrl;
5493
5516
  this.log(PAD + c.dim(c.green(`API started at ${new URL("/", origin)}`)));
@@ -5551,6 +5574,8 @@ class Logger {
5551
5574
  };
5552
5575
  const onExit = (signal, exitCode) => {
5553
5576
  cleanup();
5577
+ // Interrupted signals don't set exit code automatically.
5578
+ // Use same exit code as node: https://nodejs.org/api/process.html#signal-events
5554
5579
  if (process.exitCode === undefined) {
5555
5580
  process.exitCode = exitCode !== undefined ? 128 + exitCode : Number(signal);
5556
5581
  }
@@ -5606,7 +5631,7 @@ class VitestPackageInstaller {
5606
5631
  if (!isTTY) {
5607
5632
  return false;
5608
5633
  }
5609
- const prompts = await import('./index.DBIGubLC.js').then(function (n) { return n.i; });
5634
+ const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; });
5610
5635
  const { install } = await prompts.default({
5611
5636
  type: "confirm",
5612
5637
  name: "install",
@@ -5614,7 +5639,8 @@ class VitestPackageInstaller {
5614
5639
  });
5615
5640
  if (install) {
5616
5641
  const packageName = version ? `${dependency}@${version}` : dependency;
5617
- await (await import('./index.Bw6JxgX8.js')).installPackage(packageName, { dev: true });
5642
+ await (await import('./index.D3XRDfWc.js')).installPackage(packageName, { dev: true });
5643
+ // TODO: somehow it fails to load the package after installation, remove this when it's fixed
5618
5644
  process.stderr.write(c.yellow(`\nPackage ${packageName} installed, re-run the command to start.\n`));
5619
5645
  process.exit();
5620
5646
  return true;
@@ -5626,6 +5652,7 @@ class VitestPackageInstaller {
5626
5652
  function serializeConfig(config, coreConfig, viteConfig) {
5627
5653
  const optimizer = config.deps?.optimizer;
5628
5654
  const poolOptions = config.poolOptions;
5655
+ // Resolve from server.config to avoid comparing against default value
5629
5656
  const isolate = viteConfig?.test?.isolate;
5630
5657
  return {
5631
5658
  environmentOptions: config.environmentOptions,
@@ -5798,6 +5825,7 @@ function generateCssFilenameHash(filepath) {
5798
5825
  return hash("md5", filepath, "hex").slice(0, 6);
5799
5826
  }
5800
5827
  function generateScopedClassName(strategy, name, filename) {
5828
+ // should be configured by Vite defaults
5801
5829
  if (strategy === "scoped") {
5802
5830
  return null;
5803
5831
  }
@@ -5822,6 +5850,8 @@ function clearScreen(logger) {
5822
5850
  let lastType;
5823
5851
  let lastMsg;
5824
5852
  let sameCount = 0;
5853
+ // Only initialize the timeFormatter when the timestamp option is used, and
5854
+ // reuse it across all loggers
5825
5855
  let timeFormatter;
5826
5856
  function getTimeFormatter() {
5827
5857
  timeFormatter ??= new Intl.DateTimeFormat(undefined, {
@@ -5831,6 +5861,10 @@ function getTimeFormatter() {
5831
5861
  });
5832
5862
  return timeFormatter;
5833
5863
  }
5864
+ // This is copy-pasted and needs to be synced from time to time. Ideally, Vite's `createLogger` should accept a custom `console`
5865
+ // https://github.com/vitejs/vite/blob/main/packages/vite/src/node/logger.ts?rgh-link-date=2024-10-16T23%3A29%3A19Z
5866
+ // When Vitest supports only Vite 6 and above, we can use Vite's `createLogger({ console })`
5867
+ // https://github.com/vitejs/vite/pull/18379
5834
5868
  function createViteLogger(console, level = "info", options = {}) {
5835
5869
  const loggedErrors = new WeakSet();
5836
5870
  const { prefix = "[vite]", allowClearScreen = true } = options;
@@ -5911,6 +5945,7 @@ function createViteLogger(console, level = "info", options = {}) {
5911
5945
  };
5912
5946
  return logger;
5913
5947
  }
5948
+ // silence warning by Vite for statically not analyzable dynamic import
5914
5949
  function silenceImportViteIgnoreWarning(logger) {
5915
5950
  return {
5916
5951
  ...logger,
@@ -5933,6 +5968,8 @@ function isCSS(id) {
5933
5968
  function isCSSModule(id) {
5934
5969
  return cssModuleRE.test(id);
5935
5970
  }
5971
+ // inline css requests are expected to just return the
5972
+ // string content directly and not the proxy module
5936
5973
  function isInline(id) {
5937
5974
  return cssInlineRE.test(id);
5938
5975
  }
@@ -5964,6 +6001,8 @@ function CSSEnablerPlugin(ctx) {
5964
6001
  if (!isCSS(id)) {
5965
6002
  return;
5966
6003
  }
6004
+ // css plugin inside Vite won't do anything if the code is empty
6005
+ // but it will put __vite__updateStyle anyway
5967
6006
  if (!shouldProcessCSS(id)) {
5968
6007
  return { code: "" };
5969
6008
  }
@@ -5976,6 +6015,9 @@ function CSSEnablerPlugin(ctx) {
5976
6015
  return;
5977
6016
  }
5978
6017
  if (isCSSModule(id) && !isInline(id)) {
6018
+ // return proxy for css modules, so that imported module has names:
6019
+ // styles.foo returns a "foo" instead of "undefined"
6020
+ // we don't use code content to generate hash for "scoped", because it's empty
5979
6021
  const scopeStrategy = typeof ctx.config.css !== "boolean" && ctx.config.css.modules?.classNameStrategy || "stable";
5980
6022
  const proxyReturn = getCSSModuleProxyReturn(scopeStrategy, relative(ctx.config.root, id));
5981
6023
  const code = `export default new Proxy(Object.create(null), {
@@ -6476,6 +6518,10 @@ function stripLiteralDetailed(code, options) {
6476
6518
 
6477
6519
  const metaUrlLength = "import.meta.url".length;
6478
6520
  const locationString = "self.location".padEnd(metaUrlLength, " ");
6521
+ // Vite transforms new URL('./path', import.meta.url) to new URL('/path.js', import.meta.url)
6522
+ // This makes "href" equal to "http://localhost:3000/path.js" in the browser, but if we keep it like this,
6523
+ // then in tests the URL will become "file:///path.js".
6524
+ // To battle this, we replace "import.meta.url" with "self.location" in the code to keep the browser behavior.
6479
6525
  function NormalizeURLPlugin() {
6480
6526
  return {
6481
6527
  name: "vitest:normalize-url",
@@ -6489,6 +6535,7 @@ function NormalizeURLPlugin() {
6489
6535
  const assetImportMetaUrlRE = /\bnew\s+URL\s*\(\s*(?:'[^']+'|"[^"]+"|`[^`]+`)\s*,\s*(?:'' \+ )?import\.meta\.url\s*(?:,\s*)?\)/g;
6490
6536
  let updatedCode = code;
6491
6537
  let match;
6538
+ // eslint-disable-next-line no-cond-assign
6492
6539
  while (match = assetImportMetaUrlRE.exec(cleanString)) {
6493
6540
  const { 0: exp, index } = match;
6494
6541
  const metaUrlIndex = index + exp.indexOf("import.meta.url");
@@ -6542,6 +6589,8 @@ function resolveOptimizerConfig(_testOptions, viteOptions, testConfig, viteCache
6542
6589
  include
6543
6590
  };
6544
6591
  }
6592
+ // `optimizeDeps.disabled` is deprecated since v5.1.0-beta.1
6593
+ // https://github.com/vitejs/vite/pull/15184
6545
6594
  if (major >= 5 && minor >= 1 || major >= 6) {
6546
6595
  if (newConfig.optimizeDeps.disabled) {
6547
6596
  newConfig.optimizeDeps.noDiscovery = true;
@@ -6565,6 +6614,8 @@ function deleteDefineConfig(viteConfig) {
6565
6614
  try {
6566
6615
  replacement = typeof val === "string" ? JSON.parse(val) : val;
6567
6616
  } catch {
6617
+ // probably means it contains reference to some variable,
6618
+ // like this: "__VAR__": "process.env.VAR"
6568
6619
  continue;
6569
6620
  }
6570
6621
  if (key.startsWith("import.meta.env.")) {
@@ -6625,6 +6676,8 @@ function VitestOptimizer() {
6625
6676
  };
6626
6677
  }
6627
6678
 
6679
+ // so people can reassign envs at runtime
6680
+ // import.meta.env.VITE_NAME = 'app' -> process.env.VITE_NAME = 'app'
6628
6681
  function SsrReplacerPlugin() {
6629
6682
  return {
6630
6683
  name: "vitest:ssr-replacer",
@@ -6661,6 +6714,8 @@ function VitestProjectResolver(ctx) {
6661
6714
  enforce: "pre",
6662
6715
  async resolveId(id, _, { ssr }) {
6663
6716
  if (id === "vitest" || id.startsWith("@vitest/") || id.startsWith("vitest/")) {
6717
+ // always redirect the request to the root vitest plugin since
6718
+ // it will be the one used to run Vitest
6664
6719
  const resolved = await ctx.server.pluginContainer.resolveId(id, undefined, {
6665
6720
  skip: new Set([plugin]),
6666
6721
  ssr
@@ -6680,6 +6735,7 @@ function VitestCoreResolver(ctx) {
6680
6735
  return resolve(distDir, "index.js");
6681
6736
  }
6682
6737
  if (id.startsWith("@vitest/") || id.startsWith("vitest/")) {
6738
+ // ignore actual importer, we want it to be resolved relative to the root
6683
6739
  return this.resolve(id, join(ctx.config.root, "index.html"), { skipSelf: true });
6684
6740
  }
6685
6741
  }
@@ -6704,6 +6760,7 @@ function WorkspaceVitestPlugin(project, options) {
6704
6760
  };
6705
6761
  if (!name) {
6706
6762
  if (typeof options.workspacePath === "string") {
6763
+ // if there is a package.json, read the name from it
6707
6764
  const dir = options.workspacePath.endsWith("/") ? options.workspacePath.slice(0, -1) : dirname(options.workspacePath);
6708
6765
  const pkgJsonPath = resolve(dir, "package.json");
6709
6766
  if (existsSync(pkgJsonPath)) {
@@ -6744,22 +6801,27 @@ function WorkspaceVitestPlugin(project, options) {
6744
6801
  color
6745
6802
  } }
6746
6803
  };
6747
- if (project.vitest._options.browser && viteConfig.test?.browser) {
6748
- viteConfig.test.browser = mergeConfig(viteConfig.test.browser, project.vitest._options.browser);
6749
- }
6750
6804
  config.test.defines = defines;
6805
+ const isUserBrowserEnabled = viteConfig.test?.browser?.enabled;
6806
+ const isBrowserEnabled = isUserBrowserEnabled ?? (viteConfig.test?.browser && project.vitest._cliOptions.browser?.enabled);
6807
+ // keep project names to potentially filter it out
6751
6808
  const workspaceNames = [name];
6752
- if (viteConfig.test?.browser?.enabled) {
6753
- if (viteConfig.test.browser.name && !viteConfig.test.browser.instances?.length) {
6754
- const browser = viteConfig.test.browser.name;
6755
- workspaceNames.push(name ? `${name} (${browser})` : browser);
6756
- }
6757
- viteConfig.test.browser.instances?.forEach((instance) => {
6758
- instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
6759
- workspaceNames.push(instance.name);
6760
- });
6809
+ const browser = viteConfig.test.browser || {};
6810
+ if (isBrowserEnabled && browser.name && !browser.instances?.length) {
6811
+ // vitest injects `instances` in this case later on
6812
+ workspaceNames.push(name ? `${name} (${browser.name})` : browser.name);
6761
6813
  }
6814
+ viteConfig.test?.browser?.instances?.forEach((instance) => {
6815
+ // every instance is a potential project
6816
+ instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
6817
+ if (isBrowserEnabled) {
6818
+ workspaceNames.push(instance.name);
6819
+ }
6820
+ });
6762
6821
  const filters = project.vitest.config.project;
6822
+ // if there is `--project=...` filter, check if any of the potential projects match
6823
+ // if projects don't match, we ignore the test project altogether
6824
+ // if some of them match, they will later be filtered again by `resolveWorkspace`
6763
6825
  if (filters.length) {
6764
6826
  const hasProject = workspaceNames.some((name) => {
6765
6827
  return project.vitest.matchesProjectFilter(name);
@@ -6880,6 +6942,8 @@ class TestSpecification {
6880
6942
  }
6881
6943
 
6882
6944
  async function createViteServer(inlineConfig) {
6945
+ // Vite prints an error (https://github.com/vitejs/vite/issues/14328)
6946
+ // But Vitest works correctly either way
6883
6947
  const error = console.error;
6884
6948
  console.error = (...args) => {
6885
6949
  if (typeof args[0] === "string" && args[0].includes("WebSocket server error:")) {
@@ -6916,6 +6980,7 @@ class TestProject {
6916
6980
  /** @internal */ typechecker;
6917
6981
  /** @internal */ _config;
6918
6982
  /** @internal */ _vite;
6983
+ /** @internal */ _hash;
6919
6984
  runner;
6920
6985
  closingPromise;
6921
6986
  testFilesList = null;
@@ -6930,6 +6995,19 @@ class TestProject {
6930
6995
  this.globalConfig = vitest.config;
6931
6996
  }
6932
6997
  /**
6998
+ * The unique hash of this project. This value is consistent between the reruns.
6999
+ *
7000
+ * It is based on the root of the project (not consistent between OS) and its name.
7001
+ */
7002
+ get hash() {
7003
+ if (!this._hash) {
7004
+ throw new Error("The server was not set. It means that `project.hash` was called before the Vite server was established.");
7005
+ }
7006
+ return this._hash;
7007
+ }
7008
+ // "provide" is a property, not a method to keep the context when destructed in the global setup,
7009
+ // making it a method would be a breaking change, and can be done in Vitest 3 at minimum
7010
+ /**
6933
7011
  * Provide a value to the test context. This value will be available to all tests with `inject`.
6934
7012
  */
6935
7013
  provide = (key, value) => {
@@ -6938,6 +7016,7 @@ class TestProject {
6938
7016
  } catch (err) {
6939
7017
  throw new Error(`Cannot provide "${key}" because it's not serializable.`, { cause: err });
6940
7018
  }
7019
+ // casting `any` because the default type is `never` since `ProvidedContext` is empty
6941
7020
  this._provided[key] = value;
6942
7021
  };
6943
7022
  /**
@@ -6947,6 +7026,8 @@ class TestProject {
6947
7026
  if (this.isRootProject()) {
6948
7027
  return this._provided;
6949
7028
  }
7029
+ // globalSetup can run even if core workspace is not part of the test run
7030
+ // so we need to inherit its provided context
6950
7031
  return {
6951
7032
  ...this.vitest.getRootProject().getProvidedContext(),
6952
7033
  ...this._provided
@@ -6973,6 +7054,7 @@ class TestProject {
6973
7054
  if (!this._vite) {
6974
7055
  throw new Error("The server was not set. It means that `project.vite` was called before the Vite server was established.");
6975
7056
  }
7057
+ // checking it once should be enough
6976
7058
  Object.defineProperty(this, "vite", {
6977
7059
  configurable: true,
6978
7060
  writable: true,
@@ -6987,6 +7069,12 @@ class TestProject {
6987
7069
  if (!this._config) {
6988
7070
  throw new Error("The config was not set. It means that `project.config` was called before the Vite server was established.");
6989
7071
  }
7072
+ // checking it once should be enough
7073
+ // Object.defineProperty(this, 'config', {
7074
+ // configurable: true,
7075
+ // writable: true,
7076
+ // value: this._config,
7077
+ // })
6990
7078
  return this._config;
6991
7079
  }
6992
7080
  /**
@@ -7066,6 +7154,7 @@ class TestProject {
7066
7154
  get logger() {
7067
7155
  return this.vitest.logger;
7068
7156
  }
7157
+ // it's possible that file path was imported with different queries (?raw, ?url, etc)
7069
7158
  /** @deprecated use `.vite` or `.browser.vite` directly */
7070
7159
  getModulesByFilepath(file) {
7071
7160
  const set = this.server.moduleGraph.getModulesByFile(file) || this.browser?.vite.moduleGraph.getModulesByFile(file);
@@ -7159,7 +7248,10 @@ class TestProject {
7159
7248
  expandDirectories: false
7160
7249
  };
7161
7250
  const files = await glob(include, globOptions);
7162
- return files.map((file) => slash(p.resolve(cwd, file)));
7251
+ // keep the slashes consistent with Vite
7252
+ // we are not using the pathe here because it normalizes the drive letter on Windows
7253
+ // and we want to keep it the same as working dir
7254
+ return files.map((file) => slash(path.resolve(cwd, file)));
7163
7255
  }
7164
7256
  /**
7165
7257
  * Test if a file matches the test globs. This does the actual glob matching if the test is not cached, unlike `isCachedTestFile`.
@@ -7169,14 +7261,14 @@ class TestProject {
7169
7261
  return true;
7170
7262
  }
7171
7263
  const relativeId = relative(this.config.dir || this.config.root, moduleId);
7172
- if (mm.isMatch(relativeId, this.config.exclude)) {
7264
+ if (pm.isMatch(relativeId, this.config.exclude)) {
7173
7265
  return false;
7174
7266
  }
7175
- if (mm.isMatch(relativeId, this.config.include)) {
7267
+ if (pm.isMatch(relativeId, this.config.include)) {
7176
7268
  this.markTestFile(moduleId);
7177
7269
  return true;
7178
7270
  }
7179
- if (this.config.includeSource?.length && mm.isMatch(relativeId, this.config.includeSource)) {
7271
+ if (this.config.includeSource?.length && pm.isMatch(relativeId, this.config.includeSource)) {
7180
7272
  const code = source?.() || readFileSync(moduleId, "utf-8");
7181
7273
  if (this.isInSourceTestCode(code)) {
7182
7274
  this.markTestFile(moduleId);
@@ -7200,6 +7292,7 @@ class TestProject {
7200
7292
  return testFiles.filter((t) => {
7201
7293
  const testFile = relative(dir, t).toLocaleLowerCase();
7202
7294
  return filters.some((f) => {
7295
+ // if filter is a full file path, we should include it if it's in the same folder
7203
7296
  if (isAbsolute(f) && t.startsWith(f)) {
7204
7297
  return true;
7205
7298
  }
@@ -7278,14 +7371,19 @@ class TestProject {
7278
7371
  setServer(options, server) {
7279
7372
  return this._configureServer(options, server);
7280
7373
  }
7374
+ _setHash() {
7375
+ this._hash = generateHash(this._config.root + this._config.name);
7376
+ }
7281
7377
  /** @internal */
7282
7378
  async _configureServer(options, server) {
7283
7379
  this._config = resolveConfig(this.vitest, {
7284
7380
  ...options,
7285
7381
  coverage: this.vitest.config.coverage
7286
7382
  }, server.config);
7383
+ this._setHash();
7287
7384
  for (const _providedKey in this.config.provide) {
7288
7385
  const providedKey = _providedKey;
7386
+ // type is very strict here, so we cast it to any
7289
7387
  this.provide(providedKey, this.config.provide[providedKey]);
7290
7388
  }
7291
7389
  this.closingPromise = undefined;
@@ -7304,6 +7402,7 @@ class TestProject {
7304
7402
  });
7305
7403
  }
7306
7404
  _serializeOverriddenConfig() {
7405
+ // TODO: serialize the config _once_ or when needed
7307
7406
  const config = serializeConfig(this.config, this.vitest.config, this.vite.config);
7308
7407
  if (!this.vitest.configOverride) {
7309
7408
  return config;
@@ -7333,6 +7432,7 @@ class TestProject {
7333
7432
  _provideObject(context) {
7334
7433
  for (const _providedKey in context) {
7335
7434
  const providedKey = _providedKey;
7435
+ // type is very strict here, so we cast it to any
7336
7436
  this.provide(providedKey, context[providedKey]);
7337
7437
  }
7338
7438
  }
@@ -7343,6 +7443,7 @@ class TestProject {
7343
7443
  project.runner = vitest.runner;
7344
7444
  project._vite = vitest.server;
7345
7445
  project._config = vitest.config;
7446
+ project._setHash();
7346
7447
  project._provideObject(vitest.config.provide);
7347
7448
  return project;
7348
7449
  }
@@ -7353,6 +7454,7 @@ class TestProject {
7353
7454
  clone.runner = parent.runner;
7354
7455
  clone._vite = parent._vite;
7355
7456
  clone._config = config;
7457
+ clone._setHash();
7356
7458
  clone._parent = parent;
7357
7459
  clone._provideObject(config.provide);
7358
7460
  return clone;
@@ -7385,9 +7487,23 @@ async function initializeProject(workspacePath, ctx, options) {
7385
7487
  await createViteServer(config);
7386
7488
  return project;
7387
7489
  }
7490
+ function generateHash(str) {
7491
+ let hash = 0;
7492
+ if (str.length === 0) {
7493
+ return `${hash}`;
7494
+ }
7495
+ for (let i = 0; i < str.length; i++) {
7496
+ const char = str.charCodeAt(i);
7497
+ hash = (hash << 5) - hash + char;
7498
+ hash = hash & hash;
7499
+ }
7500
+ return `${hash}`;
7501
+ }
7388
7502
 
7389
7503
  async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projectsDefinition, names) {
7390
7504
  const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition);
7505
+ // cli options that affect the project config,
7506
+ // not all options are allowed to be overridden
7391
7507
  const overridesOptions = [
7392
7508
  "logHeapUsage",
7393
7509
  "allowOnly",
@@ -7419,7 +7535,10 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7419
7535
  const concurrent = limitConcurrency(nodeos__default.availableParallelism?.() || nodeos__default.cpus().length || 5);
7420
7536
  projectConfigs.forEach((options, index) => {
7421
7537
  const configRoot = workspaceConfigPath ? dirname(workspaceConfigPath) : vitest.config.root;
7538
+ // if extends a config file, resolve the file path
7422
7539
  const configFile = typeof options.extends === "string" ? resolve(configRoot, options.extends) : options.extends === true ? vitest.vite.config.configFile || false : false;
7540
+ // if `root` is configured, resolve it relative to the workspace file or vite root (like other options)
7541
+ // if `root` is not specified, inline configs use the same root as the root project
7423
7542
  const root = options.root ? resolve(configRoot, options.root) : vitest.config.root;
7424
7543
  projectPromises.push(concurrent(() => initializeProject(index, vitest, {
7425
7544
  ...options,
@@ -7432,6 +7551,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7432
7551
  })));
7433
7552
  });
7434
7553
  for (const path of fileProjects) {
7554
+ // if file leads to the root config, then we can just reuse it because we already initialized it
7435
7555
  if (vitest.vite.config.configFile === path) {
7436
7556
  const project = getDefaultTestProject(vitest);
7437
7557
  if (project) {
@@ -7447,6 +7567,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7447
7567
  test: cliOverrides
7448
7568
  })));
7449
7569
  }
7570
+ // pretty rare case - the glob didn't match anything and there are no inline configs
7450
7571
  if (!projectPromises.length) {
7451
7572
  throw new Error([
7452
7573
  "No projects were found. Make sure your configuration is correct. ",
@@ -7460,6 +7581,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7460
7581
  for (const result of resolvedProjectsPromises) {
7461
7582
  if (result.status === "rejected") {
7462
7583
  if (result.reason instanceof VitestFilteredOutProjectError) {
7584
+ // filter out filtered out projects
7463
7585
  continue;
7464
7586
  }
7465
7587
  errors.push(result.reason);
@@ -7470,6 +7592,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7470
7592
  if (errors.length) {
7471
7593
  throw new AggregateError(errors, "Failed to initialize projects. There were errors during projects setup. See below for more details.");
7472
7594
  }
7595
+ // project names are guaranteed to be unique
7473
7596
  for (const project of resolvedProjects) {
7474
7597
  const name = project.name;
7475
7598
  if (names.has(name)) {
@@ -7514,10 +7637,12 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7514
7637
  ].filter(Boolean).join("")));
7515
7638
  }
7516
7639
  const originalName = project.config.name;
7640
+ // if original name is in the --project=name filter, keep all instances
7517
7641
  const filteredInstances = vitest.matchesProjectFilter(originalName) ? instances : instances.filter((instance) => {
7518
7642
  const newName = instance.name;
7519
7643
  return vitest.matchesProjectFilter(newName);
7520
7644
  });
7645
+ // every project was filtered out
7521
7646
  if (!filteredInstances.length) {
7522
7647
  removeProjects.add(project);
7523
7648
  return;
@@ -7560,7 +7685,7 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7560
7685
  if (!isTTY) {
7561
7686
  throw new Error(`${message} Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option.`);
7562
7687
  }
7563
- const prompts = await import('./index.DBIGubLC.js').then(function (n) { return n.i; });
7688
+ const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; });
7564
7689
  const { projectName } = await prompts.default({
7565
7690
  type: "select",
7566
7691
  name: "projectName",
@@ -7597,13 +7722,19 @@ function cloneConfig(project, { browser,...config }) {
7597
7722
  }, overrideConfig);
7598
7723
  }
7599
7724
  async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition) {
7725
+ // project configurations that were specified directly
7600
7726
  const projectsOptions = [];
7727
+ // custom config files that were specified directly or resolved from a directory
7601
7728
  const projectsConfigFiles = [];
7729
+ // custom glob matches that should be resolved as directories or config files
7602
7730
  const projectsGlobMatches = [];
7731
+ // directories that don't have a config file inside, but should be treated as projects
7603
7732
  const nonConfigProjectDirectories = [];
7604
7733
  for (const definition of projectsDefinition) {
7605
7734
  if (typeof definition === "string") {
7606
7735
  const stringOption = definition.replace("<rootDir>", vitest.config.root);
7736
+ // if the string doesn't contain a glob, we can resolve it directly
7737
+ // ['./vitest.config.js']
7607
7738
  if (!isDynamicPattern(stringOption)) {
7608
7739
  const file = resolve(vitest.config.root, stringOption);
7609
7740
  if (!existsSync(file)) {
@@ -7612,6 +7743,7 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
7612
7743
  throw new Error(`${note} references a non-existing file or a directory: ${file}`);
7613
7744
  }
7614
7745
  const stats = await promises.stat(file);
7746
+ // user can specify a config file directly
7615
7747
  if (stats.isFile()) {
7616
7748
  projectsConfigFiles.push(file);
7617
7749
  } else if (stats.isDirectory()) {
@@ -7623,6 +7755,7 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
7623
7755
  nonConfigProjectDirectories.push(directory);
7624
7756
  }
7625
7757
  } else {
7758
+ // should never happen
7626
7759
  throw new TypeError(`Unexpected file type: ${file}`);
7627
7760
  }
7628
7761
  } else {
@@ -7654,6 +7787,8 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
7654
7787
  };
7655
7788
  const projectsFs = await glob(projectsGlobMatches, globOptions);
7656
7789
  await Promise.all(projectsFs.map(async (path) => {
7790
+ // directories are allowed with a glob like `packages/*`
7791
+ // in this case every directory is treated as a project
7657
7792
  if (path.endsWith("/")) {
7658
7793
  const configFile = await resolveDirectoryConfig(path);
7659
7794
  if (configFile) {
@@ -7675,6 +7810,8 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
7675
7810
  }
7676
7811
  async function resolveDirectoryConfig(directory) {
7677
7812
  const files = new Set(await promises.readdir(directory));
7813
+ // default resolution looks for vitest.config.* or vite.config.* files
7814
+ // this simulates how `findUp` works in packages/vitest/src/node/create.ts:29
7678
7815
  const configFile = configFiles.find((file) => files.has(file));
7679
7816
  if (configFile) {
7680
7817
  return resolve(directory, configFile);
@@ -7687,6 +7824,7 @@ function getDefaultTestProject(vitest) {
7687
7824
  if (!filter.length) {
7688
7825
  return project;
7689
7826
  }
7827
+ // check for the project name and browser names
7690
7828
  const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
7691
7829
  if (hasProjects) {
7692
7830
  return project;
@@ -7808,6 +7946,7 @@ class VitestSpecifications {
7808
7946
  const files = [];
7809
7947
  const dir = process.cwd();
7810
7948
  const parsedFilters = filters.map((f) => parseFilter(f));
7949
+ // Require includeTaskLocation when a location filter is passed
7811
7950
  if (!this.vitest.config.includeTaskLocation && parsedFilters.some((f) => f.lineNumber !== undefined)) {
7812
7951
  throw new IncludeTaskLocationDisabledError();
7813
7952
  }
@@ -7815,6 +7954,7 @@ class VitestSpecifications {
7815
7954
  ...f,
7816
7955
  filename: resolve(dir, f.filename)
7817
7956
  })));
7957
+ // Key is file and val specifies whether we have matched this file with testLocation
7818
7958
  const testLocHasMatch = {};
7819
7959
  await Promise.all(this.vitest.projects.map(async (project) => {
7820
7960
  const { testFiles, typecheckTestFiles } = await project.globTestFiles(parsedFilters.map((f) => f.filename));
@@ -7878,9 +8018,12 @@ class VitestSpecifications {
7878
8018
  return specs;
7879
8019
  }
7880
8020
  const forceRerunTriggers = this.vitest.config.forceRerunTriggers;
7881
- if (forceRerunTriggers.length && mm(related, forceRerunTriggers).length) {
8021
+ const matcher = forceRerunTriggers.length ? pm(forceRerunTriggers) : undefined;
8022
+ if (matcher && related.some((file) => matcher(file))) {
7882
8023
  return specs;
7883
8024
  }
8025
+ // don't run anything if no related sources are found
8026
+ // if we are in watch mode, we want to process all tests
7884
8027
  if (!this.vitest.config.watch && !related.length) {
7885
8028
  return [];
7886
8029
  }
@@ -7890,6 +8033,7 @@ class VitestSpecifications {
7890
8033
  }));
7891
8034
  const runningTests = [];
7892
8035
  for (const [specification, deps] of testGraphs) {
8036
+ // if deps or the test itself were changed
7893
8037
  if (related.some((path) => path === specification.moduleId || deps.has(path))) {
7894
8038
  runningTests.push(specification);
7895
8039
  }
@@ -8065,6 +8209,7 @@ class TestCase extends ReportedTaskImplementation {
8065
8209
  */
8066
8210
  diagnostic() {
8067
8211
  const result = this.task.result;
8212
+ // startTime should always be available if the test has properly finished
8068
8213
  if (!result || !result.startTime) {
8069
8214
  return undefined;
8070
8215
  }
@@ -8386,6 +8531,7 @@ class StateManager {
8386
8531
  return keys.map((key) => this.filesMap.get(key)).flat().filter((file) => file && !file.local);
8387
8532
  }
8388
8533
  return Array.from(this.filesMap.values()).flat().filter((file) => !file.local).sort((f1, f2) => {
8534
+ // print typecheck files first
8389
8535
  if (f1.meta?.typecheck && f2.meta?.typecheck) {
8390
8536
  return 0;
8391
8537
  }
@@ -8414,6 +8560,8 @@ class StateManager {
8414
8560
  const existing = this.filesMap.get(file.filepath) || [];
8415
8561
  const otherFiles = existing.filter((i) => i.projectName !== file.projectName || i.meta.typecheck !== file.meta.typecheck);
8416
8562
  const currentFile = existing.find((i) => i.projectName === file.projectName);
8563
+ // keep logs for the previous file because it should always be initiated before the collections phase
8564
+ // which means that all logs are collected during the collection and not inside tests
8417
8565
  if (currentFile) {
8418
8566
  file.logs = currentFile.logs;
8419
8567
  }
@@ -8434,6 +8582,7 @@ class StateManager {
8434
8582
  return;
8435
8583
  }
8436
8584
  const filtered = files.filter((file) => file.projectName !== project.config.name);
8585
+ // always keep a File task, so we can associate logs with it
8437
8586
  if (!filtered.length) {
8438
8587
  this.filesMap.set(path, [fileTask]);
8439
8588
  } else {
@@ -8468,6 +8617,7 @@ class StateManager {
8468
8617
  if (task) {
8469
8618
  task.result = result;
8470
8619
  task.meta = meta;
8620
+ // skipped with new PendingError
8471
8621
  if (result?.state === "skip") {
8472
8622
  task.mode = "skip";
8473
8623
  }
@@ -8520,6 +8670,9 @@ class TestRun {
8520
8670
  }
8521
8671
  async updated(update, events) {
8522
8672
  this.vitest.state.updateTasks(update);
8673
+ // TODO: what is the order or reports here?
8674
+ // "onTaskUpdate" in parallel with others or before all or after all?
8675
+ // TODO: error handling - what happens if custom reporter throws an error?
8523
8676
  await this.vitest.report("onTaskUpdate", update);
8524
8677
  for (const [id, event] of events) {
8525
8678
  await this.reportEvent(id, event).catch((error) => {
@@ -8528,6 +8681,7 @@ class TestRun {
8528
8681
  }
8529
8682
  }
8530
8683
  async end(specifications, errors, coverage) {
8684
+ // specification won't have the File task if they were filtered by the --shard command
8531
8685
  const modules = specifications.map((spec) => spec.testModule).filter((s) => s != null);
8532
8686
  const files = modules.map((m) => m.task);
8533
8687
  const state = this.vitest.isCancelling ? "interrupted" : process.exitCode ? "failed" : "passed";
@@ -8552,6 +8706,9 @@ class TestRun {
8552
8706
  if (event === "suite-finished") {
8553
8707
  assert$1(entity.type === "suite" || entity.type === "module", "Entity type must be suite or module");
8554
8708
  if (entity.state() === "skipped") {
8709
+ // everything inside suite or a module is skipped,
8710
+ // so we won't get any children events
8711
+ // we need to report everything manually
8555
8712
  await this.reportChildren(entity.children);
8556
8713
  }
8557
8714
  if (entity.type === "module") {
@@ -8706,6 +8863,7 @@ class VitestWatcher {
8706
8863
  this.changedTests.add(id);
8707
8864
  this.scheduleRerun(id);
8708
8865
  } else {
8866
+ // it's possible that file was already there but watcher triggered "add" event instead
8709
8867
  const needsRerun = this.handleFileChanged(id);
8710
8868
  if (needsRerun) {
8711
8869
  this.scheduleRerun(id);
@@ -8719,7 +8877,7 @@ class VitestWatcher {
8719
8877
  if (this.changedTests.has(filepath) || this.invalidates.has(filepath)) {
8720
8878
  return false;
8721
8879
  }
8722
- if (mm.isMatch(filepath, this.vitest.config.forceRerunTriggers)) {
8880
+ if (pm.isMatch(filepath, this.vitest.config.forceRerunTriggers)) {
8723
8881
  this.vitest.state.getFilepaths().forEach((file) => this.changedTests.add(file));
8724
8882
  return true;
8725
8883
  }
@@ -8728,6 +8886,8 @@ class VitestWatcher {
8728
8886
  return moduleGraph.getModulesByFile(filepath)?.size;
8729
8887
  });
8730
8888
  if (!projects.length) {
8889
+ // if there are no modules it's possible that server was restarted
8890
+ // we don't have information about importers anymore, so let's check if the file is a test file at least
8731
8891
  if (this.vitest.state.filesMap.has(filepath) || this.vitest.projects.some((project) => project._isCachedTestFile(filepath))) {
8732
8892
  this.changedTests.add(filepath);
8733
8893
  return true;
@@ -8741,6 +8901,7 @@ class VitestWatcher {
8741
8901
  continue;
8742
8902
  }
8743
8903
  this.invalidates.add(filepath);
8904
+ // one of test files that we already run, or one of test files that we can run
8744
8905
  if (this.vitest.state.filesMap.has(filepath) || project._isCachedTestFile(filepath)) {
8745
8906
  this.changedTests.add(filepath);
8746
8907
  files.push(filepath);
@@ -8812,7 +8973,7 @@ class Vitest {
8812
8973
  resolvedProjects = [];
8813
8974
  /** @internal */ _browserLastPort = defaultBrowserPort;
8814
8975
  /** @internal */ _browserSessions = new BrowserSessions();
8815
- /** @internal */ _options = {};
8976
+ /** @internal */ _cliOptions = {};
8816
8977
  /** @internal */ reporters = [];
8817
8978
  /** @internal */ vitenode = undefined;
8818
8979
  /** @internal */ runner = undefined;
@@ -8828,8 +8989,9 @@ class Vitest {
8828
8989
  _cache;
8829
8990
  _snapshot;
8830
8991
  _workspaceConfigPath;
8831
- constructor(mode, options = {}) {
8992
+ constructor(mode, cliOptions, options = {}) {
8832
8993
  this.mode = mode;
8994
+ this._cliOptions = cliOptions;
8833
8995
  this.logger = new Logger(this, options.stdout, options.stderr);
8834
8996
  this.packageInstaller = options.packageInstaller || new VitestPackageInstaller();
8835
8997
  this.specifications = new VitestSpecifications(this);
@@ -8890,12 +9052,11 @@ class Vitest {
8890
9052
  return this._cache;
8891
9053
  }
8892
9054
  /** @deprecated internal */
8893
- setServer(options, server, cliOptions) {
8894
- return this._setServer(options, server, cliOptions);
9055
+ setServer(options, server) {
9056
+ return this._setServer(options, server);
8895
9057
  }
8896
9058
  /** @internal */
8897
- async _setServer(options, server, cliOptions) {
8898
- this._options = options;
9059
+ async _setServer(options, server) {
8899
9060
  this.watcher.unregisterWatcher();
8900
9061
  clearTimeout(this._rerunTimer);
8901
9062
  this.restartsCount += 1;
@@ -8934,6 +9095,7 @@ class Vitest {
8934
9095
  }
8935
9096
  });
8936
9097
  if (this.config.watch) {
9098
+ // hijack server restart
8937
9099
  const serverRestart = server.restart;
8938
9100
  server.restart = async (...args) => {
8939
9101
  await Promise.all(this._onRestartListeners.map((fn) => fn()));
@@ -8941,6 +9103,7 @@ class Vitest {
8941
9103
  await this.close();
8942
9104
  await serverRestart(...args);
8943
9105
  };
9106
+ // since we set `server.hmr: false`, Vite does not auto restart itself
8944
9107
  server.watcher.on("change", async (file) => {
8945
9108
  file = normalize(file);
8946
9109
  const isConfig = file === server.config.configFile || this.projects.some((p) => p.vite.config.configFile === file) || file === this._workspaceConfigPath;
@@ -8956,7 +9119,7 @@ class Vitest {
8956
9119
  try {
8957
9120
  await this.cache.results.readFromCache();
8958
9121
  } catch {}
8959
- const projects = await this.resolveProjects(cliOptions);
9122
+ const projects = await this.resolveProjects(this._cliOptions);
8960
9123
  this.resolvedProjects = projects;
8961
9124
  this.projects = projects;
8962
9125
  await Promise.all(projects.flatMap((project) => {
@@ -8967,7 +9130,7 @@ class Vitest {
8967
9130
  injectTestProjects: this.injectTestProject
8968
9131
  }));
8969
9132
  }));
8970
- if (options.browser?.enabled) {
9133
+ if (this._cliOptions.browser?.enabled) {
8971
9134
  const browserProjects = this.projects.filter((p) => p.config.browser.enabled);
8972
9135
  if (!browserProjects.length) {
8973
9136
  throw new Error(`Vitest received --browser flag, but no project had a browser configuration.`);
@@ -8997,7 +9160,7 @@ class Vitest {
8997
9160
  */
8998
9161
  injectTestProject = async (config) => {
8999
9162
  const currentNames = new Set(this.projects.map((p) => p.name));
9000
- const projects = await resolveProjects(this, this._options, undefined, Array.isArray(config) ? config : [config], currentNames);
9163
+ const projects = await resolveProjects(this, this._cliOptions, undefined, Array.isArray(config) ? config : [config], currentNames);
9001
9164
  this.projects.push(...projects);
9002
9165
  return projects;
9003
9166
  };
@@ -9084,7 +9247,10 @@ class Vitest {
9084
9247
  }
9085
9248
  const workspaceConfigPath = await this.resolveWorkspaceConfigPath();
9086
9249
  this._workspaceConfigPath = workspaceConfigPath;
9250
+ // user doesn't have a workspace config, return default project
9087
9251
  if (!workspaceConfigPath) {
9252
+ // user can filter projects with --project flag, `getDefaultTestProject`
9253
+ // returns the project only if it matches the filter
9088
9254
  const project = getDefaultTestProject(this);
9089
9255
  if (!project) {
9090
9256
  return [];
@@ -9175,6 +9341,7 @@ class Vitest {
9175
9341
  }
9176
9342
  async collect(filters) {
9177
9343
  const files = await this.specifications.getRelevantTestSpecifications(filters);
9344
+ // if run with --changed, don't exit if no tests are found
9178
9345
  if (!files.length) {
9179
9346
  return {
9180
9347
  testModules: [],
@@ -9211,15 +9378,18 @@ class Vitest {
9211
9378
  }
9212
9379
  this.filenamePattern = filters && filters?.length > 0 ? filters : undefined;
9213
9380
  const files = await this.specifications.getRelevantTestSpecifications(filters);
9381
+ // if run with --changed, don't exit if no tests are found
9214
9382
  if (!files.length) {
9215
9383
  const throwAnError = !this.config.watch || !(this.config.changed || this.config.related?.length);
9216
9384
  await this._testRun.start([]);
9217
9385
  const coverage = await this.coverageProvider?.generateCoverage?.({ allTestsRun: true });
9386
+ // set exit code before calling `onTestRunEnd` so the lifecycle is consistent
9218
9387
  if (throwAnError) {
9219
9388
  const exitCode = this.config.passWithNoTests ? 0 : 1;
9220
9389
  process.exitCode = exitCode;
9221
9390
  }
9222
9391
  await this._testRun.end([], [], coverage);
9392
+ // Report coverage for uncovered files
9223
9393
  await this.reportCoverage(coverage, true);
9224
9394
  if (throwAnError) {
9225
9395
  throw new FilesNotFoundError(this.mode);
@@ -9230,6 +9400,7 @@ class Vitest {
9230
9400
  unhandledErrors: []
9231
9401
  };
9232
9402
  if (files.length) {
9403
+ // populate once, update cache on watch
9233
9404
  await this.cache.stats.populateStats(this.config.root, files);
9234
9405
  testModules = await this.runFiles(files, true);
9235
9406
  }
@@ -9249,6 +9420,7 @@ class Vitest {
9249
9420
  } finally {
9250
9421
  await this.report("onInit", this);
9251
9422
  }
9423
+ // populate test files cache so watch mode can trigger a file rerun
9252
9424
  await this.globTestSpecifications();
9253
9425
  if (this.config.watch) {
9254
9426
  await this.report("onWatcherStart");
@@ -9303,9 +9475,11 @@ class Vitest {
9303
9475
  }
9304
9476
  async runFiles(specs, allTestsRun) {
9305
9477
  await this._testRun.start(specs);
9478
+ // previous run
9306
9479
  await this.runningPromise;
9307
9480
  this._onCancelListeners = [];
9308
9481
  this.isCancelling = false;
9482
+ // schedule the new run
9309
9483
  this.runningPromise = (async () => {
9310
9484
  try {
9311
9485
  if (!this.pool) {
@@ -9337,6 +9511,7 @@ class Vitest {
9337
9511
  unhandledErrors: this.state.getUnhandledErrors()
9338
9512
  };
9339
9513
  } finally {
9514
+ // TODO: wait for coverage only if `onFinished` is defined
9340
9515
  const coverage = await this.coverageProvider?.generateCoverage({ allTestsRun });
9341
9516
  const errors = this.state.getUnhandledErrors();
9342
9517
  this._checkUnhandledErrors(errors);
@@ -9346,6 +9521,7 @@ class Vitest {
9346
9521
  })().finally(() => {
9347
9522
  this.runningPromise = undefined;
9348
9523
  this.isFirstRun = false;
9524
+ // all subsequent runs will treat this as a fresh run
9349
9525
  this.config.changed = false;
9350
9526
  this.config.related = undefined;
9351
9527
  });
@@ -9358,9 +9534,11 @@ class Vitest {
9358
9534
  async collectTests(specifications) {
9359
9535
  const filepaths = specifications.map((spec) => spec.moduleId);
9360
9536
  this.state.collectPaths(filepaths);
9537
+ // previous run
9361
9538
  await this.runningPromise;
9362
9539
  this._onCancelListeners = [];
9363
9540
  this.isCancelling = false;
9541
+ // schedule the new run
9364
9542
  this.runningPromise = (async () => {
9365
9543
  if (!this.pool) {
9366
9544
  this.pool = createPool(this);
@@ -9376,6 +9554,8 @@ class Vitest {
9376
9554
  this.state.catchError(err, "Unhandled Error");
9377
9555
  }
9378
9556
  const files = this.state.getFiles();
9557
+ // can only happen if there was a syntax error in describe block
9558
+ // or there was an error importing a file
9379
9559
  if (hasFailed(files)) {
9380
9560
  process.exitCode = 1;
9381
9561
  }
@@ -9385,6 +9565,7 @@ class Vitest {
9385
9565
  };
9386
9566
  })().finally(() => {
9387
9567
  this.runningPromise = undefined;
9568
+ // all subsequent runs will treat this as a fresh run
9388
9569
  this.config.changed = false;
9389
9570
  this.config.related = undefined;
9390
9571
  });
@@ -9447,11 +9628,13 @@ class Vitest {
9447
9628
  }
9448
9629
  /** @internal */
9449
9630
  async changeNamePattern(pattern, files = this.state.getFilepaths(), trigger) {
9631
+ // Empty test name pattern should reset filename pattern as well
9450
9632
  if (pattern === "") {
9451
9633
  this.filenamePattern = undefined;
9452
9634
  }
9453
9635
  const testNamePattern = pattern ? new RegExp(pattern) : undefined;
9454
9636
  this.configOverride.testNamePattern = testNamePattern;
9637
+ // filter only test files that have tests matching the pattern
9455
9638
  if (testNamePattern) {
9456
9639
  files = files.filter((filepath) => {
9457
9640
  const files = this.state.getFiles([filepath]);
@@ -9478,6 +9661,7 @@ class Vitest {
9478
9661
  * @param files The list of files on the file system
9479
9662
  */
9480
9663
  async updateSnapshot(files) {
9664
+ // default to failed files
9481
9665
  files = files || [...this.state.getFailedFilepaths(), ...this.snapshot.summary.uncheckedKeysByFile.map((s) => s.filePath)];
9482
9666
  this.enableSnapshotUpdate();
9483
9667
  try {
@@ -9525,11 +9709,13 @@ class Vitest {
9525
9709
  this.configOverride.testNamePattern = undefined;
9526
9710
  }
9527
9711
  _rerunTimer;
9712
+ // we can't use a single `triggerId` yet because vscode extension relies on this
9528
9713
  async scheduleRerun(triggerId) {
9529
9714
  const currentCount = this.restartsCount;
9530
9715
  clearTimeout(this._rerunTimer);
9531
9716
  await this.runningPromise;
9532
9717
  clearTimeout(this._rerunTimer);
9718
+ // server restarted
9533
9719
  if (this.restartsCount !== currentCount) {
9534
9720
  return;
9535
9721
  }
@@ -9538,6 +9724,7 @@ class Vitest {
9538
9724
  this.watcher.invalidates.clear();
9539
9725
  return;
9540
9726
  }
9727
+ // server restarted
9541
9728
  if (this.restartsCount !== currentCount) {
9542
9729
  return;
9543
9730
  }
@@ -9547,6 +9734,7 @@ class Vitest {
9547
9734
  if (this.filenamePattern) {
9548
9735
  const filteredFiles = await this.globTestSpecifications(this.filenamePattern);
9549
9736
  files = files.filter((file) => filteredFiles.some((f) => f.moduleId === file));
9737
+ // A file that does not match the current filename pattern was changed
9550
9738
  if (files.length === 0) {
9551
9739
  return;
9552
9740
  }
@@ -9554,6 +9742,7 @@ class Vitest {
9554
9742
  this.watcher.changedTests.clear();
9555
9743
  const triggerIds = new Set(triggerId.map((id) => relative(this.config.root, id)));
9556
9744
  const triggerLabel = Array.from(triggerIds).join(", ");
9745
+ // get file specifications and filter them if needed
9557
9746
  const specifications = files.flatMap((file) => this.getModuleSpecifications(file)).filter((specification) => {
9558
9747
  if (this._onFilterWatchedSpecification.length === 0) {
9559
9748
  return true;
@@ -9597,6 +9786,7 @@ class Vitest {
9597
9786
  }
9598
9787
  if (this.coverageProvider) {
9599
9788
  await this.coverageProvider.reportCoverage(coverage, { allTestsRun });
9789
+ // notify coverage iframe reload
9600
9790
  for (const reporter of this.reporters) {
9601
9791
  if (reporter instanceof WebSocketReporter) {
9602
9792
  reporter.onFinishedReportCoverage();
@@ -9615,10 +9805,13 @@ class Vitest {
9615
9805
  if (this.coreWorkspaceProject && !teardownProjects.includes(this.coreWorkspaceProject)) {
9616
9806
  teardownProjects.push(this.coreWorkspaceProject);
9617
9807
  }
9808
+ // do teardown before closing the server
9618
9809
  for (const project of teardownProjects.reverse()) {
9619
9810
  await project._teardownGlobalSetup();
9620
9811
  }
9621
9812
  const closePromises = this.projects.map((w) => w.close());
9813
+ // close the core workspace server only once
9814
+ // it's possible that it's not initialized at all because it's not running any tests
9622
9815
  if (this.coreWorkspaceProject && !this.projects.includes(this.coreWorkspaceProject)) {
9623
9816
  closePromises.push(this.coreWorkspaceProject.close().then(() => this._vite = undefined));
9624
9817
  }
@@ -9675,7 +9868,7 @@ class Vitest {
9675
9868
  await Promise.all(this.reporters.map((r) => r[name]?.(
9676
9869
  // @ts-expect-error let me go
9677
9870
  ...args
9678
- )));
9871
+ )));
9679
9872
  }
9680
9873
  /** @internal */
9681
9874
  async _globTestFilepaths() {
@@ -9698,6 +9891,7 @@ class Vitest {
9698
9891
  getModuleProjects(filepath) {
9699
9892
  return this.projects.filter((project) => {
9700
9893
  return project.getModulesByFilepath(filepath).size;
9894
+ // TODO: reevaluate || project.browser?.moduleGraph.getModulesByFile(id)?.size
9701
9895
  });
9702
9896
  }
9703
9897
  /**
@@ -9748,7 +9942,8 @@ class Vitest {
9748
9942
  * Check if the project with a given name should be included.
9749
9943
  */
9750
9944
  matchesProjectFilter(name) {
9751
- const projects = this._config?.project || this._options?.project;
9945
+ const projects = this._config?.project || this._cliOptions?.project;
9946
+ // no filters applied, any project can be included
9752
9947
  if (!projects || !projects.length) {
9753
9948
  return true;
9754
9949
  }
@@ -9764,11 +9959,11 @@ function assert(condition, property, name = property) {
9764
9959
  }
9765
9960
  }
9766
9961
 
9767
- async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9962
+ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(options))) {
9768
9963
  const userConfig = deepMerge({}, options);
9769
9964
  async function UIPlugin() {
9770
- await ctx.packageInstaller.ensureInstalled("@vitest/ui", options.root || process.cwd(), ctx.version);
9771
- return (await import('@vitest/ui')).default(ctx);
9965
+ await vitest.packageInstaller.ensureInstalled("@vitest/ui", options.root || process.cwd(), vitest.version);
9966
+ return (await import('@vitest/ui')).default(vitest);
9772
9967
  }
9773
9968
  return [
9774
9969
  {
@@ -9779,10 +9974,17 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9779
9974
  },
9780
9975
  async config(viteConfig) {
9781
9976
  if (options.watch) {
9977
+ // Earlier runs have overwritten values of the `options`.
9978
+ // Reset it back to initial user config before setting up the server again.
9782
9979
  options = deepMerge({}, userConfig);
9783
9980
  }
9981
+ // preliminary merge of options to be able to create server options for vite
9982
+ // however to allow vitest plugins to modify vitest config values
9983
+ // this is repeated in configResolved where the config is final
9784
9984
  const testConfig = deepMerge({}, configDefaults, removeUndefinedValues(viteConfig.test ?? {}), options);
9785
9985
  testConfig.api = resolveApiServerConfig(testConfig, defaultPort);
9986
+ // store defines for globalThis to make them
9987
+ // reassignable when running in worker in src/runtime/setup.ts
9786
9988
  const defines = deleteDefineConfig(viteConfig);
9787
9989
  options.defines = defines;
9788
9990
  let open = false;
@@ -9824,11 +10026,13 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9824
10026
  deps: testConfig.deps ?? viteConfig.test?.deps
9825
10027
  }
9826
10028
  };
9827
- if (ctx.configOverride.project) {
9828
- options.project = ctx.configOverride.project;
10029
+ if (vitest.configOverride.project) {
10030
+ // project filter was set by the user, so we need to filter the project
10031
+ options.project = vitest.configOverride.project;
9829
10032
  }
9830
- config.customLogger = createViteLogger(ctx.logger, viteConfig.logLevel || "warn", { allowClearScreen: false });
10033
+ config.customLogger = createViteLogger(vitest.logger, viteConfig.logLevel || "warn", { allowClearScreen: false });
9831
10034
  config.customLogger = silenceImportViteIgnoreWarning(config.customLogger);
10035
+ // we want inline dependencies to be resolved by analyser plugin so module graph is populated correctly
9832
10036
  if (viteConfig.ssr?.noExternal !== true) {
9833
10037
  const inline = testConfig.server?.deps?.inline;
9834
10038
  if (inline === true) {
@@ -9836,13 +10040,17 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9836
10040
  } else {
9837
10041
  const noExternal = viteConfig.ssr?.noExternal;
9838
10042
  const noExternalArray = typeof noExternal !== "undefined" ? toArray(noExternal) : undefined;
10043
+ // filter the same packages
9839
10044
  const uniqueInline = inline && noExternalArray ? inline.filter((dep) => !noExternalArray.includes(dep)) : inline;
9840
10045
  config.ssr = { noExternal: uniqueInline };
9841
10046
  }
9842
10047
  }
10048
+ // chokidar fsevents is unstable on macos when emitting "ready" event
9843
10049
  if (process.platform === "darwin" && process.env.VITE_TEST_WATCHER_DEBUG) {
9844
10050
  const watch = config.server.watch;
9845
10051
  if (watch) {
10052
+ // eslint-disable-next-line ts/ban-ts-comment
10053
+ // @ts-ignore Vite 6 compat
9846
10054
  watch.useFsEvents = false;
9847
10055
  watch.usePolling = false;
9848
10056
  }
@@ -9853,7 +10061,7 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9853
10061
  config.css.modules ??= {};
9854
10062
  if (config.css.modules) {
9855
10063
  config.css.modules.generateScopedName = (name, filename) => {
9856
- const root = ctx.config.root || options.root || process.cwd();
10064
+ const root = vitest.config.root || options.root || process.cwd();
9857
10065
  return generateScopedClassName(classNameStrategy, name, relative(root, filename));
9858
10066
  };
9859
10067
  }
@@ -9868,14 +10076,20 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9868
10076
  if ("alias" in viteConfigTest) {
9869
10077
  delete viteConfigTest.alias;
9870
10078
  }
10079
+ // viteConfig.test is final now, merge it for real
9871
10080
  options = deepMerge({}, configDefaults, viteConfigTest, options);
9872
10081
  options.api = resolveApiServerConfig(options, defaultPort);
10082
+ // we replace every "import.meta.env" with "process.env"
10083
+ // to allow reassigning, so we need to put all envs on process.env
9873
10084
  const { PROD, DEV,...envs } = viteConfig.env;
10085
+ // process.env can have only string values and will cast string on it if we pass other type,
10086
+ // so we are making them truthy
9874
10087
  process.env.PROD ??= PROD ? "1" : "";
9875
10088
  process.env.DEV ??= DEV ? "1" : "";
9876
10089
  for (const name in envs) {
9877
10090
  process.env[name] ??= envs[name];
9878
10091
  }
10092
+ // don't watch files in run mode
9879
10093
  if (!options.watch) {
9880
10094
  viteConfig.server.watch = null;
9881
10095
  }
@@ -9885,7 +10099,7 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9885
10099
  configurable: true
9886
10100
  });
9887
10101
  const originalName = options.name;
9888
- if (options.browser?.enabled && options.browser?.instances) {
10102
+ if (options.browser?.instances) {
9889
10103
  options.browser.instances.forEach((instance) => {
9890
10104
  instance.name ??= originalName ? `${originalName} (${instance.browser})` : instance.browser;
9891
10105
  });
@@ -9896,13 +10110,15 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9896
10110
  async handler(server) {
9897
10111
  if (options.watch && process.env.VITE_TEST_WATCHER_DEBUG) {
9898
10112
  server.watcher.on("ready", () => {
10113
+ // eslint-disable-next-line no-console
9899
10114
  console.log("[debug] watcher is ready");
9900
10115
  });
9901
10116
  }
9902
- await ctx._setServer(options, server, userConfig);
10117
+ await vitest._setServer(options, server);
9903
10118
  if (options.api && options.watch) {
9904
- (await Promise.resolve().then(function () { return setup$1; })).setup(ctx);
10119
+ (await Promise.resolve().then(function () { return setup$1; })).setup(vitest);
9905
10120
  }
10121
+ // #415, in run mode we don't need the watcher, close it would improve the performance
9906
10122
  if (!options.watch) {
9907
10123
  await server.watcher.close();
9908
10124
  }
@@ -9910,9 +10126,9 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9910
10126
  }
9911
10127
  },
9912
10128
  SsrReplacerPlugin(),
9913
- ...CSSEnablerPlugin(ctx),
9914
- CoverageTransform(ctx),
9915
- VitestCoreResolver(ctx),
10129
+ ...CSSEnablerPlugin(vitest),
10130
+ CoverageTransform(vitest),
10131
+ VitestCoreResolver(vitest),
9916
10132
  options.ui ? await UIPlugin() : null,
9917
10133
  ...MocksPlugins(),
9918
10134
  VitestOptimizer(),
@@ -9929,15 +10145,16 @@ function removeUndefinedValues(obj) {
9929
10145
  }
9930
10146
 
9931
10147
  async function createVitest(mode, options, viteOverrides = {}, vitestOptions = {}) {
9932
- const ctx = new Vitest(mode, vitestOptions);
10148
+ const ctx = new Vitest(mode, deepClone(options), vitestOptions);
9933
10149
  const root = slash(resolve$1(options.root || process.cwd()));
9934
10150
  const configPath = options.config === false ? false : options.config ? resolve$1(root, options.config) : await findUp(configFiles, { cwd: root });
9935
10151
  options.config = configPath;
10152
+ const { browser: _removeBrowser,...restOptions } = options;
9936
10153
  const config = {
9937
10154
  configFile: configPath,
9938
10155
  configLoader: options.configLoader,
9939
10156
  mode: options.mode || mode,
9940
- plugins: await VitestPlugin(options, ctx)
10157
+ plugins: await VitestPlugin(restOptions, ctx)
9941
10158
  };
9942
10159
  const server = await createViteServer(mergeConfig(config, mergeConfig(viteOverrides, { root: options.root })));
9943
10160
  if (ctx.config.api?.port) {
@@ -10068,6 +10285,7 @@ class WatchFilter {
10068
10285
  const lines = str.split(/\r?\n/);
10069
10286
  for (const line of lines) {
10070
10287
  const columns = "columns" in this.stdout ? this.stdout.columns : 80;
10288
+ // We have to take care of screen width in case of long lines
10071
10289
  rows += 1 + Math.floor(Math.max(stripVTControlCharacters(line).length - 1, 0) / columns);
10072
10290
  }
10073
10291
  this.write(`${ESC}1G`);
@@ -10122,6 +10340,8 @@ ${keys.map((i) => c.dim(" press ") + c.reset([i[0]].flat().map(c.bold).join(",
10122
10340
  function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10123
10341
  let latestFilename = "";
10124
10342
  async function _keypressHandler(str, key) {
10343
+ // Cancel run and exit when ctrl-c or esc is pressed.
10344
+ // If cancelling takes long and key is pressed multiple times, exit forcefully.
10125
10345
  if (str === "" || str === "\x1B" || key && key.ctrl && key.name === "c") {
10126
10346
  if (!ctx.isCancelling) {
10127
10347
  ctx.logger.log(c.red("Cancelling test run. Press CTRL+c again to exit forcefully.\n"));
@@ -10130,6 +10350,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10130
10350
  }
10131
10351
  return ctx.exit(true);
10132
10352
  }
10353
+ // window not support suspend
10133
10354
  if (!isWindows && key && key.ctrl && key.name === "z") {
10134
10355
  process.kill(process.ppid, "SIGTSTP");
10135
10356
  process.kill(process.pid, "SIGTSTP");
@@ -10142,31 +10363,40 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10142
10363
  }
10143
10364
  return;
10144
10365
  }
10366
+ // quit
10145
10367
  if (name === "q") {
10146
10368
  return ctx.exit(true);
10147
10369
  }
10370
+ // help
10148
10371
  if (name === "h") {
10149
10372
  return printShortcutsHelp();
10150
10373
  }
10374
+ // update snapshot
10151
10375
  if (name === "u") {
10152
10376
  return ctx.updateSnapshot();
10153
10377
  }
10378
+ // rerun all tests
10154
10379
  if (name === "a" || name === "return") {
10155
10380
  const files = await ctx._globTestFilepaths();
10156
10381
  return ctx.changeNamePattern("", files, "rerun all tests");
10157
10382
  }
10383
+ // rerun current pattern tests
10158
10384
  if (name === "r") {
10159
10385
  return ctx.rerunFiles();
10160
10386
  }
10387
+ // rerun only failed tests
10161
10388
  if (name === "f") {
10162
10389
  return ctx.rerunFailed();
10163
10390
  }
10391
+ // change project filter
10164
10392
  if (name === "w") {
10165
10393
  return inputProjectName();
10166
10394
  }
10395
+ // change testNamePattern
10167
10396
  if (name === "t") {
10168
10397
  return inputNamePattern();
10169
10398
  }
10399
+ // change fileNamePattern
10170
10400
  if (name === "p") {
10171
10401
  return inputFilePattern();
10172
10402
  }
@@ -10192,6 +10422,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10192
10422
  const reg = new RegExp(str);
10193
10423
  return tests.map((test) => test.name).filter((testName) => testName.match(reg));
10194
10424
  } catch {
10425
+ // `new RegExp` may throw error when input is invalid regexp
10195
10426
  return [];
10196
10427
  }
10197
10428
  });
@@ -10200,6 +10431,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10200
10431
  return;
10201
10432
  }
10202
10433
  const files = ctx.state.getFilepaths();
10434
+ // if running in standalone mode, Vitest instance doesn't know about any test file
10203
10435
  const cliFiles = ctx.config.standalone && !files.length ? await ctx._globTestFilepaths() : undefined;
10204
10436
  await ctx.changeNamePattern(filter?.trim() || "", cliFiles, "change pattern");
10205
10437
  }
@@ -10329,6 +10561,7 @@ async function prepareVitest(mode, options = {}, viteOverrides, vitestOptions) {
10329
10561
  if (options.run) {
10330
10562
  options.watch = false;
10331
10563
  }
10564
+ // this shouldn't affect _application root_ that can be changed inside config
10332
10565
  const root = resolve(options.root || process.cwd());
10333
10566
  const ctx = await createVitest(mode, options, viteOverrides, vitestOptions);
10334
10567
  const environmentPackage = getEnvPackageName(ctx.config.environment);