vitest 3.2.0-beta.2 → 3.2.0

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 (72) hide show
  1. package/LICENSE.md +29 -0
  2. package/dist/browser.d.ts +3 -3
  3. package/dist/browser.js +2 -2
  4. package/dist/chunks/{base.DwtwORaC.js → base.Cg0miDlQ.js} +11 -14
  5. package/dist/chunks/{benchmark.BoF7jW0Q.js → benchmark.CYdenmiT.js} +4 -6
  6. package/dist/chunks/{cac.I9MLYfT-.js → cac.6rXCxFY1.js} +76 -143
  7. package/dist/chunks/{cli-api.d6IK1pnk.js → cli-api.Cej3MBjA.js} +1460 -1344
  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.CtFJOzRO.js} +25 -45
  10. package/dist/chunks/{constants.BZZyIeIE.js → constants.DnKduX2e.js} +1 -0
  11. package/dist/chunks/{coverage.0iPg4Wrz.js → coverage.DVF1vEu8.js} +4 -12
  12. package/dist/chunks/{coverage.OGU09Jbh.js → coverage.EIiagJJP.js} +578 -993
  13. package/dist/chunks/{creator.DGAdZ4Hj.js → creator.GK6I-cL4.js} +39 -83
  14. package/dist/chunks/date.Bq6ZW5rf.js +73 -0
  15. package/dist/chunks/{defaults.DSxsTG0h.js → defaults.B7q_naMc.js} +2 -1
  16. package/dist/chunks/{env.Dq0hM4Xv.js → env.D4Lgay0q.js} +1 -1
  17. package/dist/chunks/{environment.d.D8YDy2v5.d.ts → environment.d.cL3nLXbE.d.ts} +1 -0
  18. package/dist/chunks/{execute.JlGHLJZT.js → execute.B7h3T_Hc.js} +126 -217
  19. package/dist/chunks/{git.DXfdBEfR.js → git.BVQ8w_Sw.js} +1 -3
  20. package/dist/chunks/{global.d.BPa1eL3O.d.ts → global.d.MAmajcmJ.d.ts} +5 -1
  21. package/dist/chunks/{globals.CpxW8ccg.js → globals.DEHgCU4V.js} +7 -6
  22. package/dist/chunks/{index.CV36oG_L.js → index.BZ0g1JD2.js} +430 -625
  23. package/dist/chunks/{index.DswW_LEs.js → index.BbB8_kAK.js} +25 -24
  24. package/dist/chunks/{index.CmC5OK9L.js → index.CIyJn3t1.js} +38 -82
  25. package/dist/chunks/{index.CfXMNXHg.js → index.CdQS2e2Q.js} +4 -2
  26. package/dist/chunks/{index.DFXFpH3w.js → index.CmSc2RE5.js} +85 -105
  27. package/dist/chunks/index.D3XRDfWc.js +213 -0
  28. package/dist/chunks/{inspector.DbDkSkFn.js → inspector.C914Efll.js} +4 -1
  29. package/dist/chunks/{node.3xsWotC9.js → node.fjCdwEIl.js} +1 -1
  30. package/dist/chunks/{reporters.d.CLC9rhKy.d.ts → reporters.d.C1ogPriE.d.ts} +47 -9
  31. package/dist/chunks/{rpc.D9_013TY.js → rpc.Iovn4oWe.js} +10 -19
  32. package/dist/chunks/{runBaseTests.Dn2vyej_.js → runBaseTests.Dd85QTll.js} +27 -31
  33. package/dist/chunks/{setup-common.CYo3Y0dD.js → setup-common.Dd054P77.js} +16 -42
  34. package/dist/chunks/{typechecker.DnTrplSJ.js → typechecker.DRKU1-1g.js} +163 -186
  35. package/dist/chunks/{utils.BfxieIyZ.js → utils.CAioKnHs.js} +9 -14
  36. package/dist/chunks/{utils.CgTj3MsC.js → utils.XdZDrNZV.js} +6 -13
  37. package/dist/chunks/{vi.BFR5YIgu.js → vi.bdSIJ99Y.js} +137 -263
  38. package/dist/chunks/{vite.d.CBZ3M_ru.d.ts → vite.d.DqE4-hhK.d.ts} +3 -1
  39. package/dist/chunks/{vm.C1HHjtNS.js → vm.BThCzidc.js} +164 -212
  40. package/dist/chunks/{worker.d.D5Xdi-Zr.d.ts → worker.d.DvqK5Vmu.d.ts} +1 -1
  41. package/dist/chunks/{worker.d.CoCI7hzP.d.ts → worker.d.tQu2eJQy.d.ts} +5 -3
  42. package/dist/cli.js +5 -5
  43. package/dist/config.cjs +3 -1
  44. package/dist/config.d.ts +7 -6
  45. package/dist/config.js +3 -3
  46. package/dist/coverage.d.ts +4 -4
  47. package/dist/coverage.js +7 -7
  48. package/dist/environments.d.ts +6 -2
  49. package/dist/environments.js +1 -1
  50. package/dist/execute.d.ts +9 -3
  51. package/dist/execute.js +1 -1
  52. package/dist/index.d.ts +28 -15
  53. package/dist/index.js +5 -5
  54. package/dist/node.d.ts +18 -10
  55. package/dist/node.js +17 -17
  56. package/dist/reporters.d.ts +4 -4
  57. package/dist/reporters.js +4 -4
  58. package/dist/runners.d.ts +6 -3
  59. package/dist/runners.js +59 -80
  60. package/dist/snapshot.js +2 -2
  61. package/dist/suite.js +2 -2
  62. package/dist/worker.js +39 -41
  63. package/dist/workers/forks.js +6 -4
  64. package/dist/workers/runVmTests.js +20 -21
  65. package/dist/workers/threads.js +4 -4
  66. package/dist/workers/vmForks.js +6 -6
  67. package/dist/workers/vmThreads.js +6 -6
  68. package/dist/workers.d.ts +4 -4
  69. package/dist/workers.js +10 -10
  70. package/package.json +21 -19
  71. package/dist/chunks/date.CDOsz-HY.js +0 -53
  72. package/dist/chunks/index.CK1YOQaa.js +0 -143
@@ -1,19 +1,19 @@
1
- import { promises, existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
2
- import { extname, normalize, relative, dirname, resolve, join, basename, isAbsolute } from 'pathe';
3
- import { C as CoverageProviderMap } from './coverage.0iPg4Wrz.js';
1
+ import fs, { promises, existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { relative, resolve, dirname, extname, normalize, join, basename, isAbsolute } from 'pathe';
3
+ import { C as CoverageProviderMap } from './coverage.DVF1vEu8.js';
4
4
  import path, { resolve as resolve$1 } from 'node:path';
5
- import { noop, isPrimitive, createDefer, highlight, toArray, deepMerge, nanoid, slash, deepClone, notNullish } from '@vitest/utils';
5
+ import { noop, isPrimitive, createDefer, slash, highlight, toArray, deepMerge, nanoid, deepClone, notNullish } from '@vitest/utils';
6
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.I9MLYfT-.js';
14
+ import { v as version$1 } from './cac.6rXCxFY1.js';
15
15
  import { c as createBirpc } from './index.CJ0plNrh.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.CV36oG_L.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.BZ0g1JD2.js';
17
17
  import require$$0$3 from 'events';
18
18
  import require$$1$1 from 'https';
19
19
  import require$$2 from 'http';
@@ -26,24 +26,24 @@ import require$$0 from 'zlib';
26
26
  import require$$0$1 from 'buffer';
27
27
  import { g as getDefaultExportFromCjs } from './_commonjsHelpers.BFTU3MAI.js';
28
28
  import { parseErrorStacktrace } from '@vitest/utils/source-map';
29
- import crypto from 'node:crypto';
29
+ import crypto, { createHash } 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, r as resolveConfig, e as groupBy, f as getCoverageProvider, j as createPool, w as wildcardPatternToRegExp, a as resolveApiServerConfig, s as stdout } from './coverage.OGU09Jbh.js';
32
- import { c as convertTasksToEvents } from './typechecker.DnTrplSJ.js';
31
+ import { h as hash, R as RandomSequencer, i as isPackageExists, 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.EIiagJJP.js';
32
+ import { c as convertTasksToEvents } from './typechecker.DRKU1-1g.js';
33
33
  import { Console } from 'node:console';
34
34
  import c from 'tinyrainbow';
35
35
  import { createRequire } from 'node:module';
36
36
  import url from 'node:url';
37
- import { i as isTTY, a as isWindows } from './env.Dq0hM4Xv.js';
38
- import { rm } from 'node:fs/promises';
37
+ import { i as isTTY, a as isWindows } from './env.D4Lgay0q.js';
38
+ import { rm, mkdir, copyFile } from 'node:fs/promises';
39
39
  import nodeos__default, { tmpdir } from 'node:os';
40
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.B7q_naMc.js';
45
45
  import MagicString from 'magic-string';
46
- import { a as BenchmarkReportsMap } from './index.CmC5OK9L.js';
46
+ import { a as BenchmarkReportsMap } from './index.CIyJn3t1.js';
47
47
  import assert$1 from 'node:assert';
48
48
  import { serializeError } from '@vitest/utils/error';
49
49
  import readline from 'node:readline';
@@ -779,6 +779,14 @@ function requirePermessageDeflate () {
779
779
  this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
780
780
  this[kError][kStatusCode] = 1009;
781
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
+ //
782
790
  this.reset();
783
791
  }
784
792
 
@@ -794,6 +802,12 @@ function requirePermessageDeflate () {
794
802
  // closed when an error is emitted.
795
803
  //
796
804
  this[kPerMessageDeflate]._inflate = null;
805
+
806
+ if (this[kError]) {
807
+ this[kCallback](this[kError]);
808
+ return;
809
+ }
810
+
797
811
  err[kStatusCode] = 1007;
798
812
  this[kCallback](err);
799
813
  }
@@ -3511,7 +3525,7 @@ function requireWebsocket () {
3511
3525
  if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) {
3512
3526
  invalidUrlMessage =
3513
3527
  'The URL\'s protocol must be one of "ws:", "wss:", ' +
3514
- '"http:", "https", or "ws+unix:"';
3528
+ '"http:", "https:", or "ws+unix:"';
3515
3529
  } else if (isIpcUrl && !parsedUrl.pathname) {
3516
3530
  invalidUrlMessage = "The URL's pathname is empty";
3517
3531
  } else if (parsedUrl.hash) {
@@ -4993,19 +5007,13 @@ var WebSocketServer = /*@__PURE__*/getDefaultExportFromCjs(websocketServerExport
4993
5007
 
4994
5008
  async function getModuleGraph(ctx, projectName, id, browser = false) {
4995
5009
  const graph = {};
4996
- const externalized = new Set();
4997
- const inlined = new Set();
5010
+ const externalized = /* @__PURE__ */ new Set();
5011
+ const inlined = /* @__PURE__ */ new Set();
4998
5012
  const project = ctx.getProjectByName(projectName);
4999
- async function get(mod, seen = new Map()) {
5000
- if (!mod || !mod.id) {
5001
- return;
5002
- }
5003
- if (mod.id === "\0@vitest/browser/context") {
5004
- return;
5005
- }
5006
- if (seen.has(mod)) {
5007
- return seen.get(mod);
5008
- }
5013
+ async function get(mod, seen = /* @__PURE__ */ new Map()) {
5014
+ if (!mod || !mod.id) return;
5015
+ if (mod.id === "\0@vitest/browser/context") return;
5016
+ if (seen.has(mod)) return seen.get(mod);
5009
5017
  let id = clearId(mod.id);
5010
5018
  seen.set(mod, id);
5011
5019
  const rewrote = browser ? mod.file?.includes(project.browser.vite.config.cacheDir) ? mod.id : false : await project.vitenode.shouldExternalize(id);
@@ -5013,18 +5021,13 @@ async function getModuleGraph(ctx, projectName, id, browser = false) {
5013
5021
  id = rewrote;
5014
5022
  externalized.add(id);
5015
5023
  seen.set(mod, id);
5016
- } else {
5017
- inlined.add(id);
5018
- }
5024
+ } else inlined.add(id);
5019
5025
  const mods = Array.from(mod.importedModules).filter((i) => i.id && !i.id.includes("/vitest/dist/"));
5020
5026
  graph[id] = (await Promise.all(mods.map((m) => get(m, seen)))).filter(Boolean);
5021
5027
  return id;
5022
5028
  }
5023
- if (browser && project.browser) {
5024
- await get(project.browser.vite.moduleGraph.getModuleById(id));
5025
- } else {
5026
- await get(project.vite.moduleGraph.getModuleById(id));
5027
- }
5029
+ if (browser && project.browser) await get(project.browser.vite.moduleGraph.getModuleById(id));
5030
+ else await get(project.vite.moduleGraph.getModuleById(id));
5028
5031
  return {
5029
5032
  graph,
5030
5033
  externalized: Array.from(externalized),
@@ -5035,7 +5038,10 @@ function clearId(id) {
5035
5038
  return id?.replace(/\?v=\w+$/, "") || "";
5036
5039
  }
5037
5040
 
5041
+ // Serialization support utils.
5038
5042
  function cloneByOwnProperties(value) {
5043
+ // Clones the value's properties into a new Object. The simpler approach of
5044
+ // Object.assign() won't work in the case that properties are not enumerable.
5039
5045
  return Object.getOwnPropertyNames(value).reduce((clone, prop) => ({
5040
5046
  ...clone,
5041
5047
  [prop]: value[prop]
@@ -5054,34 +5060,29 @@ function stringifyReplace(key, value) {
5054
5060
  stack: value.stack,
5055
5061
  ...cloned
5056
5062
  };
5057
- } else {
5058
- return value;
5059
- }
5063
+ } else return value;
5060
5064
  }
5061
5065
 
5062
5066
  function isValidApiRequest(config, req) {
5063
5067
  const url = new URL(req.url ?? "", "http://localhost");
5068
+ // validate token. token is injected in ui/tester/orchestrator html, which is cross origin protected.
5064
5069
  try {
5065
5070
  const token = url.searchParams.get("token");
5066
- if (token && crypto.timingSafeEqual(Buffer.from(token), Buffer.from(config.api.token))) {
5067
- return true;
5068
- }
5069
- } catch {}
5071
+ if (token && crypto.timingSafeEqual(Buffer.from(token), Buffer.from(config.api.token))) return true;
5072
+ }
5073
+ // an error is thrown when the length is incorrect
5074
+ catch {}
5070
5075
  return false;
5071
5076
  }
5072
5077
 
5073
5078
  function setup(ctx, _server) {
5074
5079
  const wss = new WebSocketServer({ noServer: true });
5075
- const clients = new Map();
5080
+ const clients = /* @__PURE__ */ new Map();
5076
5081
  const server = _server || ctx.server;
5077
5082
  server.httpServer?.on("upgrade", (request, socket, head) => {
5078
- if (!request.url) {
5079
- return;
5080
- }
5083
+ if (!request.url) return;
5081
5084
  const { pathname } = new URL(request.url, "http://localhost");
5082
- if (pathname !== API_PATH) {
5083
- return;
5084
- }
5085
+ if (pathname !== API_PATH) return;
5085
5086
  if (!isValidApiRequest(ctx.config, request)) {
5086
5087
  socket.destroy();
5087
5088
  return;
@@ -5103,19 +5104,15 @@ function setup(ctx, _server) {
5103
5104
  return ctx.state.getPaths();
5104
5105
  },
5105
5106
  async readTestFile(id) {
5106
- if (!ctx.state.filesMap.has(id) || !existsSync(id)) {
5107
- return null;
5108
- }
5107
+ if (!ctx.state.filesMap.has(id) || !existsSync(id)) return null;
5109
5108
  return promises.readFile(id, "utf-8");
5110
5109
  },
5111
5110
  async saveTestFile(id, content) {
5112
- if (!ctx.state.filesMap.has(id) || !existsSync(id)) {
5113
- throw new Error(`Test file "${id}" was not registered, so it cannot be updated using the API.`);
5114
- }
5111
+ if (!ctx.state.filesMap.has(id) || !existsSync(id)) throw new Error(`Test file "${id}" was not registered, so it cannot be updated using the API.`);
5115
5112
  return promises.writeFile(id, content, "utf-8");
5116
5113
  },
5117
5114
  async rerun(files, resetTestNamePattern) {
5118
- await ctx.rerunFiles(files, undefined, true, resetTestNamePattern);
5115
+ await ctx.rerunFiles(files, void 0, true, resetTestNamePattern);
5119
5116
  },
5120
5117
  async rerunTask(id) {
5121
5118
  await ctx.rerunTask(id);
@@ -5146,11 +5143,8 @@ function setup(ctx, _server) {
5146
5143
  return getModuleGraph(ctx, project, id, browser);
5147
5144
  },
5148
5145
  async updateSnapshot(file) {
5149
- if (!file) {
5150
- await ctx.updateSnapshot();
5151
- } else {
5152
- await ctx.updateSnapshot([file.filepath]);
5153
- }
5146
+ if (!file) await ctx.updateSnapshot();
5147
+ else await ctx.updateSnapshot([file.filepath]);
5154
5148
  },
5155
5149
  getUnhandledErrors() {
5156
5150
  return ctx.state.getUnhandledErrors();
@@ -5196,42 +5190,38 @@ class WebSocketReporter {
5196
5190
  this.clients = clients;
5197
5191
  }
5198
5192
  onCollected(files) {
5199
- if (this.clients.size === 0) {
5200
- return;
5201
- }
5193
+ if (this.clients.size === 0) return;
5202
5194
  this.clients.forEach((client) => {
5203
5195
  client.onCollected?.(files)?.catch?.(noop);
5204
5196
  });
5205
5197
  }
5206
5198
  onSpecsCollected(specs) {
5207
- if (this.clients.size === 0) {
5208
- return;
5209
- }
5199
+ if (this.clients.size === 0) return;
5210
5200
  this.clients.forEach((client) => {
5211
5201
  client.onSpecsCollected?.(specs)?.catch?.(noop);
5212
5202
  });
5213
5203
  }
5214
- async onTaskUpdate(packs) {
5215
- if (this.clients.size === 0) {
5216
- return;
5217
- }
5204
+ async onTestCaseAnnotate(testCase, annotation) {
5205
+ if (this.clients.size === 0) return;
5206
+ this.clients.forEach((client) => {
5207
+ client.onTestAnnotate?.(testCase.id, annotation)?.catch?.(noop);
5208
+ });
5209
+ }
5210
+ async onTaskUpdate(packs, events) {
5211
+ if (this.clients.size === 0) return;
5218
5212
  packs.forEach(([taskId, result]) => {
5219
5213
  const task = this.ctx.state.idMap.get(taskId);
5220
5214
  const isBrowser = task && task.file.pool === "browser";
5221
5215
  result?.errors?.forEach((error) => {
5222
- if (isPrimitive(error)) {
5223
- return;
5224
- }
5216
+ if (isPrimitive(error)) return;
5225
5217
  if (isBrowser) {
5226
5218
  const project = this.ctx.getProjectByName(task.file.projectName || "");
5227
5219
  error.stacks = project.browser?.parseErrorStacktrace(error);
5228
- } else {
5229
- error.stacks = parseErrorStacktrace(error);
5230
- }
5220
+ } else error.stacks = parseErrorStacktrace(error);
5231
5221
  });
5232
5222
  });
5233
5223
  this.clients.forEach((client) => {
5234
- client.onTaskUpdate?.(packs)?.catch?.(noop);
5224
+ client.onTaskUpdate?.(packs, events)?.catch?.(noop);
5235
5225
  });
5236
5226
  }
5237
5227
  onFinished(files, errors) {
@@ -5258,8 +5248,8 @@ var setup$1 = /*#__PURE__*/Object.freeze({
5258
5248
  });
5259
5249
 
5260
5250
  class BrowserSessions {
5261
- sessions = new Map();
5262
- sessionIds = new Set();
5251
+ sessions = /* @__PURE__ */ new Map();
5252
+ sessionIds = /* @__PURE__ */ new Set();
5263
5253
  getSession(sessionId) {
5264
5254
  return this.sessions.get(sessionId);
5265
5255
  }
@@ -5267,6 +5257,7 @@ class BrowserSessions {
5267
5257
  this.sessions.delete(sessionId);
5268
5258
  }
5269
5259
  createSession(sessionId, project, pool) {
5260
+ // this promise only waits for the WS connection with the orhcestrator to be established
5270
5261
  const defer = createDefer();
5271
5262
  const timeout = setTimeout(() => {
5272
5263
  defer.reject(new Error(`Failed to connect to the browser session "${sessionId}" [${project.name}] within the timeout.`));
@@ -5287,6 +5278,116 @@ class BrowserSessions {
5287
5278
  }
5288
5279
  }
5289
5280
 
5281
+ class FilesStatsCache {
5282
+ cache = /* @__PURE__ */ new Map();
5283
+ getStats(key) {
5284
+ return this.cache.get(key);
5285
+ }
5286
+ async populateStats(root, specs) {
5287
+ const promises = specs.map((spec) => {
5288
+ const key = `${spec[0].name}:${relative(root, spec.moduleId)}`;
5289
+ return this.updateStats(spec.moduleId, key);
5290
+ });
5291
+ await Promise.all(promises);
5292
+ }
5293
+ async updateStats(fsPath, key) {
5294
+ if (!fs.existsSync(fsPath)) return;
5295
+ const stats = await fs.promises.stat(fsPath);
5296
+ this.cache.set(key, { size: stats.size });
5297
+ }
5298
+ removeStats(fsPath) {
5299
+ this.cache.forEach((_, key) => {
5300
+ if (key.endsWith(fsPath)) this.cache.delete(key);
5301
+ });
5302
+ }
5303
+ }
5304
+
5305
+ class ResultsCache {
5306
+ cache = /* @__PURE__ */ new Map();
5307
+ workspacesKeyMap = /* @__PURE__ */ new Map();
5308
+ cachePath = null;
5309
+ version;
5310
+ root = "/";
5311
+ constructor(version) {
5312
+ this.version = version;
5313
+ }
5314
+ getCachePath() {
5315
+ return this.cachePath;
5316
+ }
5317
+ setConfig(root, config) {
5318
+ this.root = root;
5319
+ if (config) this.cachePath = resolve(config.dir, "results.json");
5320
+ }
5321
+ getResults(key) {
5322
+ return this.cache.get(key);
5323
+ }
5324
+ async readFromCache() {
5325
+ if (!this.cachePath) return;
5326
+ if (!fs.existsSync(this.cachePath)) return;
5327
+ const resultsCache = await fs.promises.readFile(this.cachePath, "utf8");
5328
+ const { results, version } = JSON.parse(resultsCache || "[]");
5329
+ const [major, minor] = version.split(".");
5330
+ // handling changed in 0.30.0
5331
+ if (major > 0 || Number(minor) >= 30) {
5332
+ this.cache = new Map(results);
5333
+ this.version = version;
5334
+ results.forEach(([spec]) => {
5335
+ const [projectName, relativePath] = spec.split(":");
5336
+ const keyMap = this.workspacesKeyMap.get(relativePath) || [];
5337
+ keyMap.push(projectName);
5338
+ this.workspacesKeyMap.set(relativePath, keyMap);
5339
+ });
5340
+ }
5341
+ }
5342
+ updateResults(files) {
5343
+ files.forEach((file) => {
5344
+ const result = file.result;
5345
+ if (!result) return;
5346
+ const duration = result.duration || 0;
5347
+ // store as relative, so cache would be the same in CI and locally
5348
+ const relativePath = relative(this.root, file.filepath);
5349
+ this.cache.set(`${file.projectName || ""}:${relativePath}`, {
5350
+ duration: duration >= 0 ? duration : 0,
5351
+ failed: result.state === "fail"
5352
+ });
5353
+ });
5354
+ }
5355
+ removeFromCache(filepath) {
5356
+ this.cache.forEach((_, key) => {
5357
+ if (key.endsWith(filepath)) this.cache.delete(key);
5358
+ });
5359
+ }
5360
+ async writeToCache() {
5361
+ if (!this.cachePath) return;
5362
+ const results = Array.from(this.cache.entries());
5363
+ const cacheDirname = dirname(this.cachePath);
5364
+ if (!fs.existsSync(cacheDirname)) await fs.promises.mkdir(cacheDirname, { recursive: true });
5365
+ const cache = JSON.stringify({
5366
+ version: this.version,
5367
+ results
5368
+ });
5369
+ await fs.promises.writeFile(this.cachePath, cache);
5370
+ }
5371
+ }
5372
+
5373
+ class VitestCache {
5374
+ results;
5375
+ stats = new FilesStatsCache();
5376
+ constructor(version) {
5377
+ this.results = new ResultsCache(version);
5378
+ }
5379
+ getFileTestResults(key) {
5380
+ return this.results.getResults(key);
5381
+ }
5382
+ getFileStats(key) {
5383
+ return this.stats.getStats(key);
5384
+ }
5385
+ static resolveCacheDir(root, dir, projectName) {
5386
+ const baseDir = slash(dir || "node_modules/.vite");
5387
+ return resolve(root, baseDir, "vitest", hash("md5", projectName || "", "hex"));
5388
+ }
5389
+ }
5390
+
5290
5391
  class FilesNotFoundError extends Error {
5291
5392
  code = "VITEST_FILES_NOT_FOUND";
5292
5393
  constructor(mode) {
@@ -5302,7 +5403,7 @@ class GitNotFoundError extends Error {
5302
5403
  class LocationFilterFileNotFoundError extends Error {
5303
5404
  code = "VITEST_LOCATION_FILTER_FILE_NOT_FOUND";
5304
5405
  constructor(filename) {
5305
- super(`Couldn\'t find file ${filename}. Note when specifying the test ` + "location you have to specify the full test filename.");
5406
+ super(`Couldn\'t find file ${filename}. Note when specifying the test location you have to specify the full test filename.`);
5306
5407
  }
5307
5408
  }
5308
5409
  class IncludeTaskLocationDisabledError extends Error {
@@ -5314,7 +5415,7 @@ class IncludeTaskLocationDisabledError extends Error {
5314
5415
  class RangeLocationFilterProvidedError extends Error {
5315
5416
  code = "VITEST_RANGE_LOCATION_FILTER_PROVIDED";
5316
5417
  constructor(filter) {
5317
- super(`Found "-" in location filter ${filter}. Note that range location filters ` + `are not supported. Consider specifying the exact line numbers of your tests.`);
5418
+ super(`Found "-" in location filter ${filter}. Note that range location filters are not supported. Consider specifying the exact line numbers of your tests.`);
5318
5419
  }
5319
5420
  }
5320
5421
  class VitestFilteredOutProjectError extends Error {
@@ -5334,9 +5435,7 @@ const HIGHLIGHT_SUPPORTED_EXTS = new Set(["js", "ts"].flatMap((lang) => [
5334
5435
  ]));
5335
5436
  function highlightCode(id, source, colors) {
5336
5437
  const ext = extname(id);
5337
- if (!HIGHLIGHT_SUPPORTED_EXTS.has(ext)) {
5338
- return source;
5339
- }
5438
+ if (!HIGHLIGHT_SUPPORTED_EXTS.has(ext)) return source;
5340
5439
  const isJsx = ext.endsWith("x");
5341
5440
  return highlight(source, {
5342
5441
  jsx: isJsx,
@@ -5354,7 +5453,7 @@ const SHOW_CURSOR = `${ESC$1}?25h`;
5354
5453
  const CLEAR_SCREEN = "\x1Bc";
5355
5454
  class Logger {
5356
5455
  _clearScreenPending;
5357
- _highlights = new Map();
5456
+ _highlights = /* @__PURE__ */ new Map();
5358
5457
  cleanupListeners = [];
5359
5458
  console;
5360
5459
  constructor(ctx, outputStream = process.stdout, errorStream = process.stderr) {
@@ -5368,9 +5467,7 @@ class Logger {
5368
5467
  this._highlights.clear();
5369
5468
  this.addCleanupListeners();
5370
5469
  this.registerUnhandledRejection();
5371
- if (this.outputStream.isTTY) {
5372
- this.outputStream.write(HIDE_CURSOR);
5373
- }
5470
+ if (this.outputStream.isTTY) this.outputStream.write(HIDE_CURSOR);
5374
5471
  }
5375
5472
  log(...args) {
5376
5473
  this._clearScreen();
@@ -5389,11 +5486,8 @@ class Logger {
5389
5486
  this.console.log(message);
5390
5487
  return;
5391
5488
  }
5392
- if (message) {
5393
- this.console.log(`${CLEAR_SCREEN}${ERASE_SCROLLBACK}${message}`);
5394
- } else {
5395
- this.outputStream.write(`${CLEAR_SCREEN}${ERASE_SCROLLBACK}`);
5396
- }
5489
+ if (message) this.console.log(`${CLEAR_SCREEN}${ERASE_SCROLLBACK}${message}`);
5490
+ else this.outputStream.write(`${CLEAR_SCREEN}${ERASE_SCROLLBACK}`);
5397
5491
  }
5398
5492
  clearScreen(message, force = false) {
5399
5493
  if (!this.ctx.config.clearScreen) {
@@ -5401,72 +5495,46 @@ class Logger {
5401
5495
  return;
5402
5496
  }
5403
5497
  this._clearScreenPending = message;
5404
- if (force) {
5405
- this._clearScreen();
5406
- }
5498
+ if (force) this._clearScreen();
5407
5499
  }
5408
5500
  _clearScreen() {
5409
- if (this._clearScreenPending == null) {
5410
- return;
5411
- }
5501
+ if (this._clearScreenPending == null) return;
5412
5502
  const log = this._clearScreenPending;
5413
- this._clearScreenPending = undefined;
5503
+ this._clearScreenPending = void 0;
5414
5504
  this.console.log(`${CURSOR_TO_START}${ERASE_DOWN}${log}`);
5415
5505
  }
5416
5506
  printError(err, options = {}) {
5417
5507
  printError(err, this.ctx, this, options);
5418
5508
  }
5419
5509
  deprecate(message) {
5420
- this.log(c.bold(c.bgYellow(" DEPRECATED ")), c.yellow(message));
5510
+ this.error(c.bold(c.bgYellow(" DEPRECATED ")), c.yellow(message));
5421
5511
  }
5422
5512
  clearHighlightCache(filename) {
5423
- if (filename) {
5424
- this._highlights.delete(filename);
5425
- } else {
5426
- this._highlights.clear();
5427
- }
5513
+ if (filename) this._highlights.delete(filename);
5514
+ else this._highlights.clear();
5428
5515
  }
5429
5516
  highlight(filename, source) {
5430
- if (this._highlights.has(filename)) {
5431
- return this._highlights.get(filename);
5432
- }
5517
+ if (this._highlights.has(filename)) return this._highlights.get(filename);
5433
5518
  const code = highlightCode(filename, source);
5434
5519
  this._highlights.set(filename, code);
5435
5520
  return code;
5436
5521
  }
5437
5522
  printNoTestFound(filters) {
5438
5523
  const config = this.ctx.config;
5439
- if (config.watch && (config.changed || config.related?.length)) {
5440
- this.log(`No affected ${config.mode} files found\n`);
5441
- } else if (config.watch) {
5442
- this.log(c.red(`No ${config.mode} files found. You can change the file name pattern by pressing "p"\n`));
5443
- } else {
5444
- if (config.passWithNoTests) {
5445
- this.log(`No ${config.mode} files found, exiting with code 0\n`);
5446
- } else {
5447
- this.error(c.red(`No ${config.mode} files found, exiting with code 1\n`));
5448
- }
5449
- }
5524
+ if (config.watch && (config.changed || config.related?.length)) this.log(`No affected ${config.mode} files found\n`);
5525
+ else if (config.watch) this.log(c.red(`No ${config.mode} files found. You can change the file name pattern by pressing "p"\n`));
5526
+ else if (config.passWithNoTests) this.log(`No ${config.mode} files found, exiting with code 0\n`);
5527
+ else this.error(c.red(`No ${config.mode} files found, exiting with code 1\n`));
5450
5528
  const comma = c.dim(", ");
5451
- if (filters?.length) {
5452
- this.console.error(c.dim("filter: ") + c.yellow(filters.join(comma)));
5453
- }
5529
+ if (filters?.length) this.console.error(c.dim("filter: ") + c.yellow(filters.join(comma)));
5454
5530
  const projectsFilter = toArray(config.project);
5455
- if (projectsFilter.length) {
5456
- this.console.error(c.dim("projects: ") + c.yellow(projectsFilter.join(comma)));
5457
- }
5531
+ if (projectsFilter.length) this.console.error(c.dim("projects: ") + c.yellow(projectsFilter.join(comma)));
5458
5532
  this.ctx.projects.forEach((project) => {
5459
5533
  const config = project.config;
5460
5534
  const printConfig = !project.isRootProject() && project.name;
5461
- if (printConfig) {
5462
- this.console.error(`\n${formatProjectName(project)}\n`);
5463
- }
5464
- if (config.include) {
5465
- this.console.error(c.dim("include: ") + c.yellow(config.include.join(comma)));
5466
- }
5467
- if (config.exclude) {
5468
- this.console.error(c.dim("exclude: ") + c.yellow(config.exclude.join(comma)));
5469
- }
5535
+ if (printConfig) this.console.error(`\n${formatProjectName(project)}\n`);
5536
+ if (config.include) this.console.error(c.dim("include: ") + c.yellow(config.include.join(comma)));
5537
+ if (config.exclude) this.console.error(c.dim("exclude: ") + c.yellow(config.exclude.join(comma)));
5470
5538
  if (config.typecheck.enabled) {
5471
5539
  this.console.error(c.dim("typecheck include: ") + c.yellow(config.typecheck.include.join(comma)));
5472
5540
  this.console.error(c.dim("typecheck exclude: ") + c.yellow(config.typecheck.exclude.join(comma)));
@@ -5479,9 +5547,7 @@ class Logger {
5479
5547
  const color = this.ctx.config.watch ? "blue" : "cyan";
5480
5548
  const mode = this.ctx.config.watch ? "DEV" : "RUN";
5481
5549
  this.log(withLabel(color, mode, `v${this.ctx.version} `) + c.gray(this.ctx.config.root));
5482
- if (this.ctx.config.sequence.sequencer === RandomSequencer) {
5483
- this.log(PAD + c.gray(`Running tests with seed "${this.ctx.config.sequence.seed}"`));
5484
- }
5550
+ if (this.ctx.config.sequence.sequencer === RandomSequencer) this.log(PAD + c.gray(`Running tests with seed "${this.ctx.config.sequence.seed}"`));
5485
5551
  if (this.ctx.config.ui) {
5486
5552
  const host = this.ctx.config.api?.host || "localhost";
5487
5553
  const port = this.ctx.server.config.server.port;
@@ -5489,35 +5555,28 @@ class Logger {
5489
5555
  this.log(PAD + c.dim(c.green(`UI started at http://${host}:${c.bold(port)}${base}`)));
5490
5556
  } else if (this.ctx.config.api?.port) {
5491
5557
  const resolvedUrls = this.ctx.server.resolvedUrls;
5558
+ // workaround for https://github.com/vitejs/vite/issues/15438, it was fixed in vite 5.1
5492
5559
  const fallbackUrl = `http://${this.ctx.config.api.host || "localhost"}:${this.ctx.config.api.port}`;
5493
5560
  const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0] ?? fallbackUrl;
5494
5561
  this.log(PAD + c.dim(c.green(`API started at ${new URL("/", origin)}`)));
5495
5562
  }
5496
- if (this.ctx.coverageProvider) {
5497
- this.log(PAD + c.dim("Coverage enabled with ") + c.yellow(this.ctx.coverageProvider.name));
5498
- }
5499
- if (this.ctx.config.standalone) {
5500
- this.log(c.yellow(`\nVitest is running in standalone mode. Edit a test file to rerun tests.`));
5501
- } else {
5502
- this.log();
5503
- }
5563
+ if (this.ctx.coverageProvider) this.log(PAD + c.dim("Coverage enabled with ") + c.yellow(this.ctx.coverageProvider.name));
5564
+ if (this.ctx.config.standalone) this.log(c.yellow(`\nVitest is running in standalone mode. Edit a test file to rerun tests.`));
5565
+ else this.log();
5504
5566
  }
5505
5567
  printBrowserBanner(project) {
5506
- if (!project.browser) {
5507
- return;
5508
- }
5568
+ if (!project.browser) return;
5509
5569
  const resolvedUrls = project.browser.vite.resolvedUrls;
5510
5570
  const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
5511
- if (!origin) {
5512
- return;
5513
- }
5571
+ if (!origin) return;
5514
5572
  const output = project.isRootProject() ? "" : formatProjectName(project);
5515
5573
  const provider = project.browser.provider.name;
5516
5574
  const providerString = provider === "preview" ? "" : ` by ${c.reset(c.bold(provider))}`;
5517
5575
  this.log(c.dim(`${output}Browser runner started${providerString} ${c.dim("at")} ${c.blue(new URL("/", origin))}\n`));
5518
5576
  }
5519
5577
  printUnhandledErrors(errors) {
5520
- const errorMessage = c.red(c.bold(`\nVitest caught ${errors.length} unhandled error${errors.length > 1 ? "s" : ""} during the test run.` + "\nThis might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected."));
5578
+ const errorMessage = c.red(c.bold(`\nVitest caught ${errors.length} unhandled error${errors.length > 1 ? "s" : ""} during the test run.
5579
+ This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.`));
5521
5580
  this.error(errorBanner("Unhandled Errors"));
5522
5581
  this.error(errorMessage);
5523
5582
  errors.forEach((err) => {
@@ -5546,15 +5605,13 @@ class Logger {
5546
5605
  addCleanupListeners() {
5547
5606
  const cleanup = () => {
5548
5607
  this.cleanupListeners.forEach((fn) => fn());
5549
- if (this.outputStream.isTTY) {
5550
- this.outputStream.write(SHOW_CURSOR);
5551
- }
5608
+ if (this.outputStream.isTTY) this.outputStream.write(SHOW_CURSOR);
5552
5609
  };
5553
5610
  const onExit = (signal, exitCode) => {
5554
5611
  cleanup();
5555
- if (process.exitCode === undefined) {
5556
- process.exitCode = exitCode !== undefined ? 128 + exitCode : Number(signal);
5557
- }
5612
+ // Interrupted signals don't set exit code automatically.
5613
+ // Use same exit code as node: https://nodejs.org/api/process.html#signal-events
5614
+ if (process.exitCode === void 0) process.exitCode = exitCode !== void 0 ? 128 + exitCode : Number(signal);
5558
5615
  process.exit();
5559
5616
  };
5560
5617
  process.once("SIGINT", onExit);
@@ -5590,9 +5647,7 @@ class VitestPackageInstaller {
5590
5647
  return isPackageExists(name, options);
5591
5648
  }
5592
5649
  async ensureInstalled(dependency, root, version) {
5593
- if (process.env.VITEST_SKIP_INSTALL_CHECKS) {
5594
- return true;
5595
- }
5650
+ if (process.env.VITEST_SKIP_INSTALL_CHECKS) return true;
5596
5651
  if (process.versions.pnp) {
5597
5652
  const targetRequire = createRequire(__dirname);
5598
5653
  try {
@@ -5600,13 +5655,9 @@ class VitestPackageInstaller {
5600
5655
  return true;
5601
5656
  } catch {}
5602
5657
  }
5603
- if (/* @__PURE__ */ isPackageExists(dependency, { paths: [root, __dirname] })) {
5604
- return true;
5605
- }
5658
+ if (/* @__PURE__ */ isPackageExists(dependency, { paths: [root, __dirname] })) return true;
5606
5659
  process.stderr.write(c.red(`${c.inverse(c.red(" MISSING DEPENDENCY "))} Cannot find dependency '${dependency}'\n\n`));
5607
- if (!isTTY) {
5608
- return false;
5609
- }
5660
+ if (!isTTY) return false;
5610
5661
  const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; });
5611
5662
  const { install } = await prompts.default({
5612
5663
  type: "confirm",
@@ -5615,7 +5666,8 @@ class VitestPackageInstaller {
5615
5666
  });
5616
5667
  if (install) {
5617
5668
  const packageName = version ? `${dependency}@${version}` : dependency;
5618
- await (await import('./index.CK1YOQaa.js')).installPackage(packageName, { dev: true });
5669
+ await (await import('./index.D3XRDfWc.js')).installPackage(packageName, { dev: true });
5670
+ // TODO: somehow it fails to load the package after installation, remove this when it's fixed
5619
5671
  process.stderr.write(c.yellow(`\nPackage ${packageName} installed, re-run the command to start.\n`));
5620
5672
  process.exit();
5621
5673
  return true;
@@ -5627,6 +5679,7 @@ class VitestPackageInstaller {
5627
5679
  function serializeConfig(config, coreConfig, viteConfig) {
5628
5680
  const optimizer = config.deps?.optimizer;
5629
5681
  const poolOptions = config.poolOptions;
5682
+ // Resolve from server.config to avoid comparing against default value
5630
5683
  const isolate = viteConfig?.test?.isolate;
5631
5684
  return {
5632
5685
  environmentOptions: config.environmentOptions,
@@ -5667,8 +5720,8 @@ function serializeConfig(config, coreConfig, viteConfig) {
5667
5720
  reportsDirectory: coverage.reportsDirectory,
5668
5721
  provider: coverage.provider,
5669
5722
  enabled: coverage.enabled,
5670
- htmlReporter: htmlReporter ? { subdir } : undefined,
5671
- customProviderModule: "customProviderModule" in coverage ? coverage.customProviderModule : undefined
5723
+ htmlReporter: htmlReporter ? { subdir } : void 0,
5724
+ customProviderModule: "customProviderModule" in coverage ? coverage.customProviderModule : void 0
5672
5725
  };
5673
5726
  })(config.coverage),
5674
5727
  fakeTimers: config.fakeTimers,
@@ -5694,11 +5747,11 @@ function serializeConfig(config, coreConfig, viteConfig) {
5694
5747
  moduleDirectories: config.deps.moduleDirectories
5695
5748
  },
5696
5749
  snapshotOptions: {
5697
- snapshotEnvironment: undefined,
5750
+ snapshotEnvironment: void 0,
5698
5751
  updateSnapshot: coreConfig.snapshotOptions.updateSnapshot,
5699
5752
  snapshotFormat: {
5700
5753
  ...coreConfig.snapshotOptions.snapshotFormat,
5701
- compareKeys: undefined
5754
+ compareKeys: void 0
5702
5755
  },
5703
5756
  expand: config.snapshotOptions.expand ?? coreConfig.snapshotOptions.expand
5704
5757
  },
@@ -5747,25 +5800,17 @@ async function loadGlobalSetupFile(file, runner) {
5747
5800
  "default",
5748
5801
  "setup",
5749
5802
  "teardown"
5750
- ]) {
5751
- if (m[exp] != null && typeof m[exp] !== "function") {
5752
- throw new Error(`invalid export in globalSetup file ${file}: ${exp} must be a function`);
5753
- }
5754
- }
5755
- if (m.default) {
5756
- return {
5757
- file,
5758
- setup: m.default
5759
- };
5760
- } else if (m.setup || m.teardown) {
5761
- return {
5762
- file,
5763
- setup: m.setup,
5764
- teardown: m.teardown
5765
- };
5766
- } else {
5767
- throw new Error(`invalid globalSetup file ${file}. Must export setup, teardown or have a default export`);
5768
- }
5803
+ ]) if (m[exp] != null && typeof m[exp] !== "function") throw new Error(`invalid export in globalSetup file ${file}: ${exp} must be a function`);
5804
+ if (m.default) return {
5805
+ file,
5806
+ setup: m.default
5807
+ };
5808
+ else if (m.setup || m.teardown) return {
5809
+ file,
5810
+ setup: m.setup,
5811
+ teardown: m.teardown
5812
+ };
5813
+ else throw new Error(`invalid globalSetup file ${file}. Must export setup, teardown or have a default export`);
5769
5814
  }
5770
5815
 
5771
5816
  function CoverageTransform(ctx) {
@@ -5781,12 +5826,8 @@ function MocksPlugins(options = {}) {
5781
5826
  const normalizedDistDir = normalize(distDir);
5782
5827
  return [hoistMocksPlugin({
5783
5828
  filter(id) {
5784
- if (id.includes(normalizedDistDir)) {
5785
- return false;
5786
- }
5787
- if (options.filter) {
5788
- return options.filter(id);
5789
- }
5829
+ if (id.includes(normalizedDistDir)) return false;
5830
+ if (options.filter) return options.filter(id);
5790
5831
  return true;
5791
5832
  },
5792
5833
  codeFrameGenerator(node, id, code) {
@@ -5799,12 +5840,9 @@ function generateCssFilenameHash(filepath) {
5799
5840
  return hash("md5", filepath, "hex").slice(0, 6);
5800
5841
  }
5801
5842
  function generateScopedClassName(strategy, name, filename) {
5802
- if (strategy === "scoped") {
5803
- return null;
5804
- }
5805
- if (strategy === "non-scoped") {
5806
- return name;
5807
- }
5843
+ // should be configured by Vite defaults
5844
+ if (strategy === "scoped") return null;
5845
+ if (strategy === "non-scoped") return name;
5808
5846
  const hash = generateCssFilenameHash(filename);
5809
5847
  return `_${name}_${hash}`;
5810
5848
  }
@@ -5823,17 +5861,23 @@ function clearScreen(logger) {
5823
5861
  let lastType;
5824
5862
  let lastMsg;
5825
5863
  let sameCount = 0;
5864
+ // Only initialize the timeFormatter when the timestamp option is used, and
5865
+ // reuse it across all loggers
5826
5866
  let timeFormatter;
5827
5867
  function getTimeFormatter() {
5828
- timeFormatter ??= new Intl.DateTimeFormat(undefined, {
5868
+ timeFormatter ??= new Intl.DateTimeFormat(void 0, {
5829
5869
  hour: "numeric",
5830
5870
  minute: "numeric",
5831
5871
  second: "numeric"
5832
5872
  });
5833
5873
  return timeFormatter;
5834
5874
  }
5875
+ // This is copy-pasted and needs to be synced from time to time. Ideally, Vite's `createLogger` should accept a custom `console`
5876
+ // https://github.com/vitejs/vite/blob/main/packages/vite/src/node/logger.ts?rgh-link-date=2024-10-16T23%3A29%3A19Z
5877
+ // When Vitest supports only Vite 6 and above, we can use Vite's `createLogger({ console })`
5878
+ // https://github.com/vitejs/vite/pull/18379
5835
5879
  function createViteLogger(console, level = "info", options = {}) {
5836
- const loggedErrors = new WeakSet();
5880
+ const loggedErrors = /* @__PURE__ */ new WeakSet();
5837
5881
  const { prefix = "[vite]", allowClearScreen = true } = options;
5838
5882
  const thresh = LogLevels[level];
5839
5883
  const canClearScreen = allowClearScreen && process.stdout.isTTY && !process.env.CI;
@@ -5841,45 +5885,32 @@ function createViteLogger(console, level = "info", options = {}) {
5841
5885
  function format(type, msg, options = {}) {
5842
5886
  if (options.timestamp) {
5843
5887
  let tag = "";
5844
- if (type === "info") {
5845
- tag = c.cyan(c.bold(prefix));
5846
- } else if (type === "warn") {
5847
- tag = c.yellow(c.bold(prefix));
5848
- } else {
5849
- tag = c.red(c.bold(prefix));
5850
- }
5888
+ if (type === "info") tag = c.cyan(c.bold(prefix));
5889
+ else if (type === "warn") tag = c.yellow(c.bold(prefix));
5890
+ else tag = c.red(c.bold(prefix));
5851
5891
  const environment = options.environment ? `${options.environment} ` : "";
5852
- return `${c.dim(getTimeFormatter().format(new Date()))} ${tag} ${environment}${msg}`;
5853
- } else {
5854
- return msg;
5855
- }
5892
+ return `${c.dim(getTimeFormatter().format(/* @__PURE__ */ new Date()))} ${tag} ${environment}${msg}`;
5893
+ } else return msg;
5856
5894
  }
5857
5895
  function output(type, msg, options = {}) {
5858
5896
  if (thresh >= LogLevels[type]) {
5859
5897
  const method = type === "info" ? "log" : type;
5860
- if (options.error) {
5861
- loggedErrors.add(options.error);
5862
- }
5863
- if (canClearScreen) {
5864
- if (type === lastType && msg === lastMsg) {
5865
- sameCount++;
5866
- clear(console);
5867
- console[method](format(type, msg, options), c.yellow(`(x${sameCount + 1})`));
5868
- } else {
5869
- sameCount = 0;
5870
- lastMsg = msg;
5871
- lastType = type;
5872
- if (options.clear) {
5873
- clear(console);
5874
- }
5875
- console[method](format(type, msg, options));
5876
- }
5898
+ if (options.error) loggedErrors.add(options.error);
5899
+ if (canClearScreen) if (type === lastType && msg === lastMsg) {
5900
+ sameCount++;
5901
+ clear(console);
5902
+ console[method](format(type, msg, options), c.yellow(`(x${sameCount + 1})`));
5877
5903
  } else {
5904
+ sameCount = 0;
5905
+ lastMsg = msg;
5906
+ lastType = type;
5907
+ if (options.clear) clear(console);
5878
5908
  console[method](format(type, msg, options));
5879
5909
  }
5910
+ else console[method](format(type, msg, options));
5880
5911
  }
5881
5912
  }
5882
- const warnedMessages = new Set();
5913
+ const warnedMessages = /* @__PURE__ */ new Set();
5883
5914
  const logger = {
5884
5915
  hasWarned: false,
5885
5916
  info(msg, opts) {
@@ -5890,9 +5921,7 @@ function createViteLogger(console, level = "info", options = {}) {
5890
5921
  output("warn", msg, opts);
5891
5922
  },
5892
5923
  warnOnce(msg, opts) {
5893
- if (warnedMessages.has(msg)) {
5894
- return;
5895
- }
5924
+ if (warnedMessages.has(msg)) return;
5896
5925
  logger.hasWarned = true;
5897
5926
  output("warn", msg, opts);
5898
5927
  warnedMessages.add(msg);
@@ -5902,9 +5931,7 @@ function createViteLogger(console, level = "info", options = {}) {
5902
5931
  output("error", msg, opts);
5903
5932
  },
5904
5933
  clearScreen(type) {
5905
- if (thresh >= LogLevels[type]) {
5906
- clear(console);
5907
- }
5934
+ if (thresh >= LogLevels[type]) clear(console);
5908
5935
  },
5909
5936
  hasErrorLogged(error) {
5910
5937
  return loggedErrors.has(error);
@@ -5912,13 +5939,12 @@ function createViteLogger(console, level = "info", options = {}) {
5912
5939
  };
5913
5940
  return logger;
5914
5941
  }
5942
+ // silence warning by Vite for statically not analyzable dynamic import
5915
5943
  function silenceImportViteIgnoreWarning(logger) {
5916
5944
  return {
5917
5945
  ...logger,
5918
5946
  warn(msg, options) {
5919
- if (msg.includes("The above dynamic import cannot be analyzed by Vite")) {
5920
- return;
5921
- }
5947
+ if (msg.includes("The above dynamic import cannot be analyzed by Vite")) return;
5922
5948
  logger.warn(msg, options);
5923
5949
  }
5924
5950
  };
@@ -5934,49 +5960,42 @@ function isCSS(id) {
5934
5960
  function isCSSModule(id) {
5935
5961
  return cssModuleRE.test(id);
5936
5962
  }
5963
+ // inline css requests are expected to just return the
5964
+ // string content directly and not the proxy module
5937
5965
  function isInline(id) {
5938
5966
  return cssInlineRE.test(id);
5939
5967
  }
5940
5968
  function getCSSModuleProxyReturn(strategy, filename) {
5941
- if (strategy === "non-scoped") {
5942
- return "style";
5943
- }
5969
+ if (strategy === "non-scoped") return "style";
5944
5970
  const hash = generateCssFilenameHash(filename);
5945
5971
  return `\`_\${style}_${hash}\``;
5946
5972
  }
5947
5973
  function CSSEnablerPlugin(ctx) {
5948
5974
  const shouldProcessCSS = (id) => {
5949
5975
  const { css } = ctx.config;
5950
- if (typeof css === "boolean") {
5951
- return css;
5952
- }
5953
- if (toArray(css.exclude).some((re) => re.test(id))) {
5954
- return false;
5955
- }
5956
- if (toArray(css.include).some((re) => re.test(id))) {
5957
- return true;
5958
- }
5976
+ if (typeof css === "boolean") return css;
5977
+ if (toArray(css.exclude).some((re) => re.test(id))) return false;
5978
+ if (toArray(css.include).some((re) => re.test(id))) return true;
5959
5979
  return false;
5960
5980
  };
5961
5981
  return [{
5962
5982
  name: "vitest:css-disable",
5963
5983
  enforce: "pre",
5964
5984
  transform(code, id) {
5965
- if (!isCSS(id)) {
5966
- return;
5967
- }
5968
- if (!shouldProcessCSS(id)) {
5969
- return { code: "" };
5970
- }
5985
+ if (!isCSS(id)) return;
5986
+ // css plugin inside Vite won't do anything if the code is empty
5987
+ // but it will put __vite__updateStyle anyway
5988
+ if (!shouldProcessCSS(id)) return { code: "" };
5971
5989
  }
5972
5990
  }, {
5973
5991
  name: "vitest:css-empty-post",
5974
5992
  enforce: "post",
5975
5993
  transform(_, id) {
5976
- if (!isCSS(id) || shouldProcessCSS(id)) {
5977
- return;
5978
- }
5994
+ if (!isCSS(id) || shouldProcessCSS(id)) return;
5979
5995
  if (isCSSModule(id) && !isInline(id)) {
5996
+ // return proxy for css modules, so that imported module has names:
5997
+ // styles.foo returns a "foo" instead of "undefined"
5998
+ // we don't use code content to generate hash for "scoped", because it's empty
5980
5999
  const scopeStrategy = typeof ctx.config.css !== "boolean" && ctx.config.css.modules?.classNameStrategy || "stable";
5981
6000
  const proxyReturn = getCSSModuleProxyReturn(scopeStrategy, relative(ctx.config.root, id));
5982
6001
  const code = `export default new Proxy(Object.create(null), {
@@ -6475,21 +6494,24 @@ function stripLiteralDetailed(code, options) {
6475
6494
  return stripLiteralJsTokens(code);
6476
6495
  }
6477
6496
 
6478
- const metaUrlLength = "import.meta.url".length;
6497
+ const metaUrlLength = 15;
6479
6498
  const locationString = "self.location".padEnd(metaUrlLength, " ");
6499
+ // Vite transforms new URL('./path', import.meta.url) to new URL('/path.js', import.meta.url)
6500
+ // This makes "href" equal to "http://localhost:3000/path.js" in the browser, but if we keep it like this,
6501
+ // then in tests the URL will become "file:///path.js".
6502
+ // To battle this, we replace "import.meta.url" with "self.location" in the code to keep the browser behavior.
6480
6503
  function NormalizeURLPlugin() {
6481
6504
  return {
6482
6505
  name: "vitest:normalize-url",
6483
6506
  enforce: "post",
6484
6507
  transform(code, id, options) {
6485
6508
  const ssr = options?.ssr === true;
6486
- if (ssr || !code.includes("new URL") || !code.includes("import.meta.url")) {
6487
- return;
6488
- }
6509
+ if (ssr || !code.includes("new URL") || !code.includes("import.meta.url")) return;
6489
6510
  const cleanString = stripLiteral(code);
6490
6511
  const assetImportMetaUrlRE = /\bnew\s+URL\s*\(\s*(?:'[^']+'|"[^"]+"|`[^`]+`)\s*,\s*(?:'' \+ )?import\.meta\.url\s*(?:,\s*)?\)/g;
6491
6512
  let updatedCode = code;
6492
6513
  let match;
6514
+ // eslint-disable-next-line no-cond-assign
6493
6515
  while (match = assetImportMetaUrlRE.exec(cleanString)) {
6494
6516
  const { 0: exp, index } = match;
6495
6517
  const metaUrlIndex = index + exp.indexOf("import.meta.url");
@@ -6503,24 +6525,20 @@ function NormalizeURLPlugin() {
6503
6525
  };
6504
6526
  }
6505
6527
 
6506
- function resolveOptimizerConfig(_testOptions, viteOptions, testConfig, viteCacheDir) {
6528
+ function resolveOptimizerConfig(_testOptions, viteOptions) {
6507
6529
  const testOptions = _testOptions || {};
6508
6530
  const newConfig = {};
6509
6531
  const [major, minor, fix] = version.split(".").map(Number);
6510
6532
  const allowed = major >= 5 || major === 4 && minor >= 4 || major === 4 && minor === 3 && fix >= 2;
6511
- if (!allowed && testOptions?.enabled === true) {
6512
- console.warn(`Vitest: "deps.optimizer" is only available in Vite >= 4.3.2, current Vite version: ${version}`);
6513
- } else {
6514
- testOptions.enabled ??= false;
6515
- }
6533
+ if (!allowed && testOptions?.enabled === true) console.warn(`Vitest: "deps.optimizer" is only available in Vite >= 4.3.2, current Vite version: ${version}`);
6534
+ else testOptions.enabled ??= false;
6516
6535
  if (!allowed || testOptions?.enabled !== true) {
6517
- newConfig.cacheDir = undefined;
6536
+ newConfig.cacheDir = void 0;
6518
6537
  newConfig.optimizeDeps = {
6519
6538
  disabled: true,
6520
6539
  entries: []
6521
6540
  };
6522
6541
  } else {
6523
- const root = testConfig.root ?? process.cwd();
6524
6542
  const currentInclude = testOptions.include || viteOptions?.include || [];
6525
6543
  const exclude = [
6526
6544
  "vitest",
@@ -6531,8 +6549,6 @@ function resolveOptimizerConfig(_testOptions, viteOptions, testConfig, viteCache
6531
6549
  const runtime = currentInclude.filter((n) => n.endsWith("jsx-dev-runtime") || n.endsWith("jsx-runtime"));
6532
6550
  exclude.push(...runtime);
6533
6551
  const include = (testOptions.include || viteOptions?.include || []).filter((n) => !exclude.includes(n));
6534
- const projectName = typeof testConfig.name === "string" ? testConfig.name : testConfig.name?.label;
6535
- newConfig.cacheDir = testConfig.cache !== false && testConfig.cache?.dir || VitestCache.resolveCacheDir(root, viteCacheDir, projectName);
6536
6552
  newConfig.optimizeDeps = {
6537
6553
  ...viteOptions,
6538
6554
  ...testOptions,
@@ -6543,6 +6559,8 @@ function resolveOptimizerConfig(_testOptions, viteOptions, testConfig, viteCache
6543
6559
  include
6544
6560
  };
6545
6561
  }
6562
+ // `optimizeDeps.disabled` is deprecated since v5.1.0-beta.1
6563
+ // https://github.com/vitejs/vite/pull/15184
6546
6564
  if (major >= 5 && minor >= 1 || major >= 6) {
6547
6565
  if (newConfig.optimizeDeps.disabled) {
6548
6566
  newConfig.optimizeDeps.noDiscovery = true;
@@ -6566,14 +6584,16 @@ function deleteDefineConfig(viteConfig) {
6566
6584
  try {
6567
6585
  replacement = typeof val === "string" ? JSON.parse(val) : val;
6568
6586
  } catch {
6587
+ // probably means it contains reference to some variable,
6588
+ // like this: "__VAR__": "process.env.VAR"
6569
6589
  continue;
6570
6590
  }
6571
6591
  if (key.startsWith("import.meta.env.")) {
6572
- const envKey = key.slice("import.meta.env.".length);
6592
+ const envKey = key.slice(16);
6573
6593
  process.env[envKey] = replacement;
6574
6594
  delete viteConfig.define[key];
6575
6595
  } else if (key.startsWith("process.env.")) {
6576
- const envKey = key.slice("process.env.".length);
6596
+ const envKey = key.slice(12);
6577
6597
  process.env[envKey] = replacement;
6578
6598
  delete viteConfig.define[key];
6579
6599
  } else if (!key.includes(".")) {
@@ -6584,9 +6604,7 @@ function deleteDefineConfig(viteConfig) {
6584
6604
  return defines;
6585
6605
  }
6586
6606
  function resolveFsAllow(projectRoot, rootConfigFile) {
6587
- if (!rootConfigFile) {
6588
- return [searchForWorkspaceRoot(projectRoot), rootDir];
6589
- }
6607
+ if (!rootConfigFile) return [searchForWorkspaceRoot(projectRoot), rootDir];
6590
6608
  return [
6591
6609
  dirname(rootConfigFile),
6592
6610
  searchForWorkspaceRoot(projectRoot),
@@ -6615,9 +6633,12 @@ function VitestOptimizer() {
6615
6633
  order: "post",
6616
6634
  handler(viteConfig) {
6617
6635
  const testConfig = viteConfig.test || {};
6618
- const webOptimizer = resolveOptimizerConfig(testConfig.deps?.optimizer?.web, viteConfig.optimizeDeps, testConfig, viteConfig.cacheDir);
6619
- const ssrOptimizer = resolveOptimizerConfig(testConfig.deps?.optimizer?.ssr, viteConfig.ssr?.optimizeDeps, testConfig, viteConfig.cacheDir);
6620
- viteConfig.cacheDir = webOptimizer.cacheDir || ssrOptimizer.cacheDir || viteConfig.cacheDir;
6636
+ const webOptimizer = resolveOptimizerConfig(testConfig.deps?.optimizer?.web, viteConfig.optimizeDeps);
6637
+ const ssrOptimizer = resolveOptimizerConfig(testConfig.deps?.optimizer?.ssr, viteConfig.ssr?.optimizeDeps);
6638
+ const root = resolve(viteConfig.root || process.cwd());
6639
+ const name = viteConfig.test?.name;
6640
+ const label = typeof name === "string" ? name : name?.label || "";
6641
+ viteConfig.cacheDir = VitestCache.resolveCacheDir(resolve(root || process.cwd()), testConfig.cache != null && testConfig.cache !== false ? testConfig.cache.dir : viteConfig.cacheDir, label);
6621
6642
  viteConfig.optimizeDeps = webOptimizer.optimizeDeps;
6622
6643
  viteConfig.ssr ??= {};
6623
6644
  viteConfig.ssr.optimizeDeps = ssrOptimizer.optimizeDeps;
@@ -6626,14 +6647,14 @@ function VitestOptimizer() {
6626
6647
  };
6627
6648
  }
6628
6649
 
6650
+ // so people can reassign envs at runtime
6651
+ // import.meta.env.VITE_NAME = 'app' -> process.env.VITE_NAME = 'app'
6629
6652
  function SsrReplacerPlugin() {
6630
6653
  return {
6631
6654
  name: "vitest:ssr-replacer",
6632
6655
  enforce: "pre",
6633
6656
  transform(code, id) {
6634
- if (!/\bimport\.meta\.env\b/.test(code)) {
6635
- return null;
6636
- }
6657
+ if (!/\bimport\.meta\.env\b/.test(code)) return null;
6637
6658
  let s = null;
6638
6659
  const cleanCode = stripLiteral(code);
6639
6660
  const envs = cleanCode.matchAll(/\bimport\.meta\.env\b/g);
@@ -6643,15 +6664,13 @@ function SsrReplacerPlugin() {
6643
6664
  const endIndex = startIndex + env[0].length;
6644
6665
  s.overwrite(startIndex, endIndex, "__vite_ssr_import_meta__.env");
6645
6666
  }
6646
- if (s) {
6647
- return {
6648
- code: s.toString(),
6649
- map: s.generateMap({
6650
- hires: "boundary",
6651
- source: cleanUrl(id)
6652
- })
6653
- };
6654
- }
6667
+ if (s) return {
6668
+ code: s.toString(),
6669
+ map: s.generateMap({
6670
+ hires: "boundary",
6671
+ source: cleanUrl(id)
6672
+ })
6673
+ };
6655
6674
  }
6656
6675
  };
6657
6676
  }
@@ -6662,7 +6681,9 @@ function VitestProjectResolver(ctx) {
6662
6681
  enforce: "pre",
6663
6682
  async resolveId(id, _, { ssr }) {
6664
6683
  if (id === "vitest" || id.startsWith("@vitest/") || id.startsWith("vitest/")) {
6665
- const resolved = await ctx.server.pluginContainer.resolveId(id, undefined, {
6684
+ // always redirect the request to the root vitest plugin since
6685
+ // it will be the one used to run Vitest
6686
+ const resolved = await ctx.server.pluginContainer.resolveId(id, void 0, {
6666
6687
  skip: new Set([plugin]),
6667
6688
  ssr
6668
6689
  });
@@ -6677,12 +6698,10 @@ function VitestCoreResolver(ctx) {
6677
6698
  name: "vitest:resolve-core",
6678
6699
  enforce: "pre",
6679
6700
  async resolveId(id) {
6680
- if (id === "vitest") {
6681
- return resolve(distDir, "index.js");
6682
- }
6683
- if (id.startsWith("@vitest/") || id.startsWith("vitest/")) {
6684
- return this.resolve(id, join(ctx.config.root, "index.html"), { skipSelf: true });
6685
- }
6701
+ if (id === "vitest") return resolve(distDir, "index.js");
6702
+ if (id.startsWith("@vitest/") || id.startsWith("vitest/"))
6703
+ // ignore actual importer, we want it to be resolved relative to the root
6704
+ return this.resolve(id, join(ctx.config.root, "index.html"), { skipSelf: true });
6686
6705
  }
6687
6706
  };
6688
6707
  }
@@ -6691,7 +6710,7 @@ function WorkspaceVitestPlugin(project, options) {
6691
6710
  return [
6692
6711
  {
6693
6712
  name: "vitest:project",
6694
- enforce: "pre",
6713
+ enforce: "post",
6695
6714
  options() {
6696
6715
  this.meta.watchMode = false;
6697
6716
  },
@@ -6703,20 +6722,13 @@ function WorkspaceVitestPlugin(project, options) {
6703
6722
  label: "",
6704
6723
  ...testConfig.name
6705
6724
  };
6706
- if (!name) {
6707
- if (typeof options.workspacePath === "string") {
6708
- const dir = options.workspacePath.endsWith("/") ? options.workspacePath.slice(0, -1) : dirname(options.workspacePath);
6709
- const pkgJsonPath = resolve(dir, "package.json");
6710
- if (existsSync(pkgJsonPath)) {
6711
- name = JSON.parse(readFileSync(pkgJsonPath, "utf-8")).name;
6712
- }
6713
- if (typeof name !== "string" || !name) {
6714
- name = basename(dir);
6715
- }
6716
- } else {
6717
- name = options.workspacePath.toString();
6718
- }
6719
- }
6725
+ if (!name) if (typeof options.workspacePath === "string") {
6726
+ // if there is a package.json, read the name from it
6727
+ const dir = options.workspacePath.endsWith("/") ? options.workspacePath.slice(0, -1) : dirname(options.workspacePath);
6728
+ const pkgJsonPath = resolve(dir, "package.json");
6729
+ if (existsSync(pkgJsonPath)) name = JSON.parse(readFileSync(pkgJsonPath, "utf-8")).name;
6730
+ if (typeof name !== "string" || !name) name = basename(dir);
6731
+ } else name = options.workspacePath.toString();
6720
6732
  const resolveOptions = getDefaultResolveOptions();
6721
6733
  const config = {
6722
6734
  root,
@@ -6745,40 +6757,38 @@ function WorkspaceVitestPlugin(project, options) {
6745
6757
  color
6746
6758
  } }
6747
6759
  };
6748
- if (project.vitest._options.browser && viteConfig.test?.browser) {
6749
- viteConfig.test.browser = mergeConfig(viteConfig.test.browser, project.vitest._options.browser);
6750
- }
6751
6760
  config.test.defines = defines;
6761
+ const isUserBrowserEnabled = viteConfig.test?.browser?.enabled;
6762
+ const isBrowserEnabled = isUserBrowserEnabled ?? (viteConfig.test?.browser && project.vitest._cliOptions.browser?.enabled);
6763
+ // keep project names to potentially filter it out
6752
6764
  const workspaceNames = [name];
6753
- if (viteConfig.test?.browser?.enabled) {
6754
- if (viteConfig.test.browser.name && !viteConfig.test.browser.instances?.length) {
6755
- const browser = viteConfig.test.browser.name;
6756
- workspaceNames.push(name ? `${name} (${browser})` : browser);
6757
- }
6758
- viteConfig.test.browser.instances?.forEach((instance) => {
6759
- instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
6760
- workspaceNames.push(instance.name);
6761
- });
6762
- }
6765
+ const browser = viteConfig.test.browser || {};
6766
+ if (isBrowserEnabled && browser.name && !browser.instances?.length)
6767
+ // vitest injects `instances` in this case later on
6768
+ workspaceNames.push(name ? `${name} (${browser.name})` : browser.name);
6769
+ viteConfig.test?.browser?.instances?.forEach((instance) => {
6770
+ // every instance is a potential project
6771
+ instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
6772
+ if (isBrowserEnabled) workspaceNames.push(instance.name);
6773
+ });
6763
6774
  const filters = project.vitest.config.project;
6775
+ // if there is `--project=...` filter, check if any of the potential projects match
6776
+ // if projects don't match, we ignore the test project altogether
6777
+ // if some of them match, they will later be filtered again by `resolveWorkspace`
6764
6778
  if (filters.length) {
6765
6779
  const hasProject = workspaceNames.some((name) => {
6766
6780
  return project.vitest.matchesProjectFilter(name);
6767
6781
  });
6768
- if (!hasProject) {
6769
- throw new VitestFilteredOutProjectError();
6770
- }
6782
+ if (!hasProject) throw new VitestFilteredOutProjectError();
6771
6783
  }
6772
6784
  const classNameStrategy = typeof testConfig.css !== "boolean" && testConfig.css?.modules?.classNameStrategy || "stable";
6773
6785
  if (classNameStrategy !== "scoped") {
6774
6786
  config.css ??= {};
6775
6787
  config.css.modules ??= {};
6776
- if (config.css.modules) {
6777
- config.css.modules.generateScopedName = (name, filename) => {
6778
- const root = project.config.root;
6779
- return generateScopedClassName(classNameStrategy, name, relative(root, filename));
6780
- };
6781
- }
6788
+ if (config.css.modules) config.css.modules.generateScopedName = (name, filename) => {
6789
+ const root = project.config.root;
6790
+ return generateScopedClassName(classNameStrategy, name, relative(root, filename));
6791
+ };
6782
6792
  }
6783
6793
  config.customLogger = createViteLogger(project.vitest.logger, viteConfig.logLevel || "warn", { allowClearScreen: false });
6784
6794
  config.customLogger = silenceImportViteIgnoreWarning(config.customLogger);
@@ -6792,9 +6802,9 @@ function WorkspaceVitestPlugin(project, options) {
6792
6802
  },
6793
6803
  SsrReplacerPlugin(),
6794
6804
  ...CSSEnablerPlugin(project),
6795
- CoverageTransform(project.ctx),
6805
+ CoverageTransform(project.vitest),
6796
6806
  ...MocksPlugins(),
6797
- VitestProjectResolver(project.ctx),
6807
+ VitestProjectResolver(project.vitest),
6798
6808
  VitestOptimizer(),
6799
6809
  NormalizeURLPlugin()
6800
6810
  ];
@@ -6851,9 +6861,7 @@ class TestSpecification {
6851
6861
  */
6852
6862
  get testModule() {
6853
6863
  const task = this.project.vitest.state.idMap.get(this.taskId);
6854
- if (!task) {
6855
- return undefined;
6856
- }
6864
+ if (!task) return void 0;
6857
6865
  return this.project.vitest.state.getReportedEntity(task);
6858
6866
  }
6859
6867
  toJSON() {
@@ -6881,11 +6889,11 @@ class TestSpecification {
6881
6889
  }
6882
6890
 
6883
6891
  async function createViteServer(inlineConfig) {
6892
+ // Vite prints an error (https://github.com/vitejs/vite/issues/14328)
6893
+ // But Vitest works correctly either way
6884
6894
  const error = console.error;
6885
6895
  console.error = (...args) => {
6886
- if (typeof args[0] === "string" && args[0].includes("WebSocket server error:")) {
6887
- return;
6888
- }
6896
+ if (typeof args[0] === "string" && args[0].includes("WebSocket server error:")) return;
6889
6897
  error(...args);
6890
6898
  };
6891
6899
  const server = await createServer(inlineConfig);
@@ -6937,11 +6945,11 @@ class TestProject {
6937
6945
  * It is based on the root of the project (not consistent between OS) and its name.
6938
6946
  */
6939
6947
  get hash() {
6940
- if (!this._hash) {
6941
- throw new Error("The server was not set. It means that `project.hash` was called before the Vite server was established.");
6942
- }
6948
+ if (!this._hash) throw new Error("The server was not set. It means that `project.hash` was called before the Vite server was established.");
6943
6949
  return this._hash;
6944
6950
  }
6951
+ // "provide" is a property, not a method to keep the context when destructed in the global setup,
6952
+ // making it a method would be a breaking change, and can be done in Vitest 3 at minimum
6945
6953
  /**
6946
6954
  * Provide a value to the test context. This value will be available to all tests with `inject`.
6947
6955
  */
@@ -6951,15 +6959,16 @@ class TestProject {
6951
6959
  } catch (err) {
6952
6960
  throw new Error(`Cannot provide "${key}" because it's not serializable.`, { cause: err });
6953
6961
  }
6962
+ // casting `any` because the default type is `never` since `ProvidedContext` is empty
6954
6963
  this._provided[key] = value;
6955
6964
  };
6956
6965
  /**
6957
6966
  * Get the provided context. The project context is merged with the global context.
6958
6967
  */
6959
6968
  getProvidedContext() {
6960
- if (this.isRootProject()) {
6961
- return this._provided;
6962
- }
6969
+ if (this.isRootProject()) return this._provided;
6970
+ // globalSetup can run even if core workspace is not part of the test run
6971
+ // so we need to inherit its provided context
6963
6972
  return {
6964
6973
  ...this.vitest.getRootProject().getProvidedContext(),
6965
6974
  ...this._provided
@@ -6983,9 +6992,8 @@ class TestProject {
6983
6992
  * Vite's dev server instance. Every workspace project has its own server.
6984
6993
  */
6985
6994
  get vite() {
6986
- if (!this._vite) {
6987
- throw new Error("The server was not set. It means that `project.vite` was called before the Vite server was established.");
6988
- }
6995
+ if (!this._vite) throw new Error("The server was not set. It means that `project.vite` was called before the Vite server was established.");
6996
+ // checking it once should be enough
6989
6997
  Object.defineProperty(this, "vite", {
6990
6998
  configurable: true,
6991
6999
  writable: true,
@@ -6997,9 +7005,13 @@ class TestProject {
6997
7005
  * Resolved project configuration.
6998
7006
  */
6999
7007
  get config() {
7000
- if (!this._config) {
7001
- throw new Error("The config was not set. It means that `project.config` was called before the Vite server was established.");
7002
- }
7008
+ if (!this._config) throw new Error("The config was not set. It means that `project.config` was called before the Vite server was established.");
7009
+ // checking it once should be enough
7010
+ // Object.defineProperty(this, 'config', {
7011
+ // configurable: true,
7012
+ // writable: true,
7013
+ // value: this._config,
7014
+ // })
7003
7015
  return this._config;
7004
7016
  }
7005
7017
  /**
@@ -7044,18 +7056,12 @@ class TestProject {
7044
7056
  }
7045
7057
  /** @internal */
7046
7058
  async _initializeGlobalSetup() {
7047
- if (this._globalSetups) {
7048
- return;
7049
- }
7059
+ if (this._globalSetups) return;
7050
7060
  this._globalSetups = await loadGlobalSetupFiles(this.runner, this.config.globalSetup);
7051
7061
  for (const globalSetupFile of this._globalSetups) {
7052
7062
  const teardown = await globalSetupFile.setup?.(this);
7053
- if (teardown == null || !!globalSetupFile.teardown) {
7054
- continue;
7055
- }
7056
- if (typeof teardown !== "function") {
7057
- throw new TypeError(`invalid return value in globalSetup file ${globalSetupFile.file}. Must return a function`);
7058
- }
7063
+ if (teardown == null || !!globalSetupFile.teardown) continue;
7064
+ if (typeof teardown !== "function") throw new TypeError(`invalid return value in globalSetup file ${globalSetupFile.file}. Must return a function`);
7059
7065
  globalSetupFile.teardown = teardown;
7060
7066
  }
7061
7067
  }
@@ -7068,21 +7074,18 @@ class TestProject {
7068
7074
  }
7069
7075
  /** @internal */
7070
7076
  async _teardownGlobalSetup() {
7071
- if (!this._globalSetups) {
7072
- return;
7073
- }
7074
- for (const globalSetupFile of [...this._globalSetups].reverse()) {
7075
- await globalSetupFile.teardown?.();
7076
- }
7077
+ if (!this._globalSetups) return;
7078
+ for (const globalSetupFile of [...this._globalSetups].reverse()) await globalSetupFile.teardown?.();
7077
7079
  }
7078
7080
  /** @deprecated use `vitest.logger` instead */
7079
7081
  get logger() {
7080
7082
  return this.vitest.logger;
7081
7083
  }
7084
+ // it's possible that file path was imported with different queries (?raw, ?url, etc)
7082
7085
  /** @deprecated use `.vite` or `.browser.vite` directly */
7083
7086
  getModulesByFilepath(file) {
7084
7087
  const set = this.server.moduleGraph.getModulesByFile(file) || this.browser?.vite.moduleGraph.getModulesByFile(file);
7085
- return set || new Set();
7088
+ return set || /* @__PURE__ */ new Set();
7086
7089
  }
7087
7090
  /** @deprecated use `.vite` or `.browser.vite` directly */
7088
7091
  getModuleById(id) {
@@ -7113,18 +7116,14 @@ class TestProject {
7113
7116
  };
7114
7117
  }
7115
7118
  async globAllTestFiles(include, exclude, includeSource, cwd) {
7116
- if (this.testFilesList) {
7117
- return this.testFilesList;
7118
- }
7119
+ if (this.testFilesList) return this.testFilesList;
7119
7120
  const testFiles = await this.globFiles(include, exclude, cwd);
7120
7121
  if (includeSource?.length) {
7121
7122
  const files = await this.globFiles(includeSource, exclude, cwd);
7122
7123
  await Promise.all(files.map(async (file) => {
7123
7124
  try {
7124
7125
  const code = await promises.readFile(file, "utf-8");
7125
- if (this.isInSourceTestCode(code)) {
7126
- testFiles.push(file);
7127
- }
7126
+ if (this.isInSourceTestCode(code)) testFiles.push(file);
7128
7127
  } catch {
7129
7128
  return null;
7130
7129
  }
@@ -7141,9 +7140,7 @@ class TestProject {
7141
7140
  }
7142
7141
  /** @internal */
7143
7142
  _removeCachedTestFile(testPath) {
7144
- if (this.testFilesList) {
7145
- this.testFilesList = this.testFilesList.filter((file) => file !== testPath);
7146
- }
7143
+ if (this.testFilesList) this.testFilesList = this.testFilesList.filter((file) => file !== testPath);
7147
7144
  }
7148
7145
  /**
7149
7146
  * Returns if the file is a test file. Requires `.globTestFiles()` to be called first.
@@ -7172,19 +7169,18 @@ class TestProject {
7172
7169
  expandDirectories: false
7173
7170
  };
7174
7171
  const files = await glob(include, globOptions);
7172
+ // keep the slashes consistent with Vite
7173
+ // we are not using the pathe here because it normalizes the drive letter on Windows
7174
+ // and we want to keep it the same as working dir
7175
7175
  return files.map((file) => slash(path.resolve(cwd, file)));
7176
7176
  }
7177
7177
  /**
7178
7178
  * Test if a file matches the test globs. This does the actual glob matching if the test is not cached, unlike `isCachedTestFile`.
7179
7179
  */
7180
7180
  matchesTestGlob(moduleId, source) {
7181
- if (this._isCachedTestFile(moduleId)) {
7182
- return true;
7183
- }
7181
+ if (this._isCachedTestFile(moduleId)) return true;
7184
7182
  const relativeId = relative(this.config.dir || this.config.root, moduleId);
7185
- if (pm.isMatch(relativeId, this.config.exclude)) {
7186
- return false;
7187
- }
7183
+ if (pm.isMatch(relativeId, this.config.exclude)) return false;
7188
7184
  if (pm.isMatch(relativeId, this.config.include)) {
7189
7185
  this.markTestFile(moduleId);
7190
7186
  return true;
@@ -7200,27 +7196,22 @@ class TestProject {
7200
7196
  }
7201
7197
  /** @deprecated use `matchesTestGlob` instead */
7202
7198
  async isTargetFile(id, source) {
7203
- return this.matchesTestGlob(id, source ? () => source : undefined);
7199
+ return this.matchesTestGlob(id, source ? () => source : void 0);
7204
7200
  }
7205
7201
  isInSourceTestCode(code) {
7206
7202
  return code.includes("import.meta.vitest");
7207
7203
  }
7208
7204
  filterFiles(testFiles, filters, dir) {
7209
- if (filters.length && process.platform === "win32") {
7210
- filters = filters.map((f) => slash(f));
7211
- }
7212
- if (filters.length) {
7213
- return testFiles.filter((t) => {
7214
- const testFile = relative(dir, t).toLocaleLowerCase();
7215
- return filters.some((f) => {
7216
- if (isAbsolute(f) && t.startsWith(f)) {
7217
- return true;
7218
- }
7219
- const relativePath = f.endsWith("/") ? join(relative(dir, f), "/") : relative(dir, f);
7220
- return testFile.includes(f.toLocaleLowerCase()) || testFile.includes(relativePath.toLocaleLowerCase());
7221
- });
7205
+ if (filters.length && process.platform === "win32") filters = filters.map((f) => slash(f));
7206
+ if (filters.length) return testFiles.filter((t) => {
7207
+ const testFile = relative(dir, t).toLocaleLowerCase();
7208
+ return filters.some((f) => {
7209
+ // if filter is a full file path, we should include it if it's in the same folder
7210
+ if (isAbsolute(f) && t.startsWith(f)) return true;
7211
+ const relativePath = f.endsWith("/") ? join(relative(dir, f), "/") : relative(dir, f);
7212
+ return testFile.includes(f.toLocaleLowerCase()) || testFile.includes(relativePath.toLocaleLowerCase());
7222
7213
  });
7223
- }
7214
+ });
7224
7215
  return testFiles;
7225
7216
  }
7226
7217
  _parentBrowser;
@@ -7228,9 +7219,7 @@ class TestProject {
7228
7219
  _parent;
7229
7220
  /** @internal */
7230
7221
  _initParentBrowser = deduped(async () => {
7231
- if (!this.isBrowserEnabled() || this._parentBrowser) {
7232
- return;
7233
- }
7222
+ if (!this.isBrowserEnabled() || this._parentBrowser) return;
7234
7223
  await this.vitest.packageInstaller.ensureInstalled("@vitest/browser", this.config.root, this.vitest.version);
7235
7224
  const { createBrowserServer, distRoot } = await import('@vitest/browser');
7236
7225
  let cacheDir;
@@ -7240,15 +7229,11 @@ class TestProject {
7240
7229
  cacheDir = config.cacheDir;
7241
7230
  }
7242
7231
  }, ...MocksPlugins({ filter(id) {
7243
- if (id.includes(distRoot) || id.includes(cacheDir)) {
7244
- return false;
7245
- }
7232
+ if (id.includes(distRoot) || id.includes(cacheDir)) return false;
7246
7233
  return true;
7247
7234
  } })], [CoverageTransform(this.vitest)]);
7248
7235
  this._parentBrowser = browser;
7249
- if (this.config.browser.ui) {
7250
- setup(this.vitest, browser.vite);
7251
- }
7236
+ if (this.config.browser.ui) setup(this.vitest, browser.vite);
7252
7237
  });
7253
7238
  /** @internal */
7254
7239
  _initBrowserServer = deduped(async () => {
@@ -7263,17 +7248,15 @@ class TestProject {
7263
7248
  * If the resources are needed again, create a new project.
7264
7249
  */
7265
7250
  close() {
7266
- if (!this.closingPromise) {
7267
- this.closingPromise = Promise.all([
7268
- this.vite?.close(),
7269
- this.typechecker?.stop(),
7270
- this.browser?.close(),
7271
- this.clearTmpDir()
7272
- ].filter(Boolean)).then(() => {
7273
- this._provided = {};
7274
- this._vite = undefined;
7275
- });
7276
- }
7251
+ if (!this.closingPromise) this.closingPromise = Promise.all([
7252
+ this.vite?.close(),
7253
+ this.typechecker?.stop(),
7254
+ this.browser?.close(),
7255
+ this.clearTmpDir()
7256
+ ].filter(Boolean)).then(() => {
7257
+ this._provided = {};
7258
+ this._vite = void 0;
7259
+ });
7277
7260
  return this.closingPromise;
7278
7261
  }
7279
7262
  /**
@@ -7303,9 +7286,10 @@ class TestProject {
7303
7286
  this._setHash();
7304
7287
  for (const _providedKey in this.config.provide) {
7305
7288
  const providedKey = _providedKey;
7289
+ // type is very strict here, so we cast it to any
7306
7290
  this.provide(providedKey, this.config.provide[providedKey]);
7307
7291
  }
7308
- this.closingPromise = undefined;
7292
+ this.closingPromise = void 0;
7309
7293
  this._vite = server;
7310
7294
  this.vitenode = new ViteNodeServer(server, this.config.server);
7311
7295
  const node = this.vitenode;
@@ -7321,10 +7305,9 @@ class TestProject {
7321
7305
  });
7322
7306
  }
7323
7307
  _serializeOverriddenConfig() {
7308
+ // TODO: serialize the config _once_ or when needed
7324
7309
  const config = serializeConfig(this.config, this.vitest.config, this.vite.config);
7325
- if (!this.vitest.configOverride) {
7326
- return config;
7327
- }
7310
+ if (!this.vitest.configOverride) return config;
7328
7311
  return deepMerge(config, this.vitest.configOverride);
7329
7312
  }
7330
7313
  async clearTmpDir() {
@@ -7338,18 +7321,15 @@ class TestProject {
7338
7321
  }
7339
7322
  /** @internal */
7340
7323
  _initBrowserProvider = deduped(async () => {
7341
- if (!this.isBrowserEnabled() || this.browser?.provider) {
7342
- return;
7343
- }
7344
- if (!this.browser) {
7345
- await this._initBrowserServer();
7346
- }
7324
+ if (!this.isBrowserEnabled() || this.browser?.provider) return;
7325
+ if (!this.browser) await this._initBrowserServer();
7347
7326
  await this.browser?.initBrowserProvider(this);
7348
7327
  });
7349
7328
  /** @internal */
7350
7329
  _provideObject(context) {
7351
7330
  for (const _providedKey in context) {
7352
7331
  const providedKey = _providedKey;
7332
+ // type is very strict here, so we cast it to any
7353
7333
  this.provide(providedKey, context[providedKey]);
7354
7334
  }
7355
7335
  }
@@ -7380,11 +7360,9 @@ class TestProject {
7380
7360
  function deduped(cb) {
7381
7361
  let _promise;
7382
7362
  return (...args) => {
7383
- if (!_promise) {
7384
- _promise = cb(...args).finally(() => {
7385
- _promise = undefined;
7386
- });
7387
- }
7363
+ if (!_promise) _promise = cb(...args).finally(() => {
7364
+ _promise = void 0;
7365
+ });
7388
7366
  return _promise;
7389
7367
  };
7390
7368
  }
@@ -7406,9 +7384,7 @@ async function initializeProject(workspacePath, ctx, options) {
7406
7384
  }
7407
7385
  function generateHash(str) {
7408
7386
  let hash = 0;
7409
- if (str.length === 0) {
7410
- return `${hash}`;
7411
- }
7387
+ if (str.length === 0) return `${hash}`;
7412
7388
  for (let i = 0; i < str.length; i++) {
7413
7389
  const char = str.charCodeAt(i);
7414
7390
  hash = (hash << 5) - hash + char;
@@ -7419,6 +7395,8 @@ function generateHash(str) {
7419
7395
 
7420
7396
  async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projectsDefinition, names) {
7421
7397
  const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition);
7398
+ // cli options that affect the project config,
7399
+ // not all options are allowed to be overridden
7422
7400
  const overridesOptions = [
7423
7401
  "logHeapUsage",
7424
7402
  "allowOnly",
@@ -7440,9 +7418,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7440
7418
  "fileParallelism"
7441
7419
  ];
7442
7420
  const cliOverrides = overridesOptions.reduce((acc, name) => {
7443
- if (name in cliOptions) {
7444
- acc[name] = cliOptions[name];
7445
- }
7421
+ if (name in cliOptions) acc[name] = cliOptions[name];
7446
7422
  return acc;
7447
7423
  }, {});
7448
7424
  const projectPromises = [];
@@ -7450,7 +7426,10 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7450
7426
  const concurrent = limitConcurrency(nodeos__default.availableParallelism?.() || nodeos__default.cpus().length || 5);
7451
7427
  projectConfigs.forEach((options, index) => {
7452
7428
  const configRoot = workspaceConfigPath ? dirname(workspaceConfigPath) : vitest.config.root;
7429
+ // if extends a config file, resolve the file path
7453
7430
  const configFile = typeof options.extends === "string" ? resolve(configRoot, options.extends) : options.extends === true ? vitest.vite.config.configFile || false : false;
7431
+ // if `root` is configured, resolve it relative to the workspace file or vite root (like other options)
7432
+ // if `root` is not specified, inline configs use the same root as the root project
7454
7433
  const root = options.root ? resolve(configRoot, options.root) : vitest.config.root;
7455
7434
  projectPromises.push(concurrent(() => initializeProject(index, vitest, {
7456
7435
  ...options,
@@ -7463,11 +7442,10 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7463
7442
  })));
7464
7443
  });
7465
7444
  for (const path of fileProjects) {
7445
+ // if file leads to the root config, then we can just reuse it because we already initialized it
7466
7446
  if (vitest.vite.config.configFile === path) {
7467
7447
  const project = getDefaultTestProject(vitest);
7468
- if (project) {
7469
- projectPromises.push(Promise.resolve(project));
7470
- }
7448
+ if (project) projectPromises.push(Promise.resolve(project));
7471
7449
  continue;
7472
7450
  }
7473
7451
  const configFile = path.endsWith("/") ? false : path;
@@ -7478,29 +7456,23 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7478
7456
  test: cliOverrides
7479
7457
  })));
7480
7458
  }
7481
- if (!projectPromises.length) {
7482
- throw new Error([
7483
- "No projects were found. Make sure your configuration is correct. ",
7484
- vitest.config.project.length ? `The filter matched no projects: ${vitest.config.project.join(", ")}. ` : "",
7485
- `The projects definition: ${JSON.stringify(projectsDefinition, null, 4)}.`
7486
- ].join(""));
7487
- }
7459
+ // pretty rare case - the glob didn't match anything and there are no inline configs
7460
+ if (!projectPromises.length) throw new Error([
7461
+ "No projects were found. Make sure your configuration is correct. ",
7462
+ vitest.config.project.length ? `The filter matched no projects: ${vitest.config.project.join(", ")}. ` : "",
7463
+ `The projects definition: ${JSON.stringify(projectsDefinition, null, 4)}.`
7464
+ ].join(""));
7488
7465
  const resolvedProjectsPromises = await Promise.allSettled(projectPromises);
7489
7466
  const errors = [];
7490
7467
  const resolvedProjects = [];
7491
- for (const result of resolvedProjectsPromises) {
7492
- if (result.status === "rejected") {
7493
- if (result.reason instanceof VitestFilteredOutProjectError) {
7494
- continue;
7495
- }
7496
- errors.push(result.reason);
7497
- } else {
7498
- resolvedProjects.push(result.value);
7499
- }
7500
- }
7501
- if (errors.length) {
7502
- throw new AggregateError(errors, "Failed to initialize projects. There were errors during projects setup. See below for more details.");
7503
- }
7468
+ for (const result of resolvedProjectsPromises) if (result.status === "rejected") {
7469
+ if (result.reason instanceof VitestFilteredOutProjectError)
7470
+ // filter out filtered out projects
7471
+ continue;
7472
+ errors.push(result.reason);
7473
+ } else resolvedProjects.push(result.value);
7474
+ if (errors.length) throw new AggregateError(errors, "Failed to initialize projects. There were errors during projects setup. See below for more details.");
7475
+ // project names are guaranteed to be unique
7504
7476
  for (const project of resolvedProjects) {
7505
7477
  const name = project.name;
7506
7478
  if (names.has(name)) {
@@ -7524,11 +7496,9 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7524
7496
  return resolveBrowserProjects(vitest, names, resolvedProjects);
7525
7497
  }
7526
7498
  async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7527
- const removeProjects = new Set();
7499
+ const removeProjects = /* @__PURE__ */ new Set();
7528
7500
  resolvedProjects.forEach((project) => {
7529
- if (!project.config.browser.enabled) {
7530
- return;
7531
- }
7501
+ if (!project.config.browser.enabled) return;
7532
7502
  const instances = project.config.browser.instances || [];
7533
7503
  const browser = project.config.browser.name;
7534
7504
  if (instances.length === 0 && browser) {
@@ -7545,17 +7515,17 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7545
7515
  ].filter(Boolean).join("")));
7546
7516
  }
7547
7517
  const originalName = project.config.name;
7518
+ // if original name is in the --project=name filter, keep all instances
7548
7519
  const filteredInstances = vitest.matchesProjectFilter(originalName) ? instances : instances.filter((instance) => {
7549
7520
  const newName = instance.name;
7550
7521
  return vitest.matchesProjectFilter(newName);
7551
7522
  });
7523
+ // every project was filtered out
7552
7524
  if (!filteredInstances.length) {
7553
7525
  removeProjects.add(project);
7554
7526
  return;
7555
7527
  }
7556
- if (project.config.browser.providerOptions) {
7557
- vitest.logger.warn(withLabel("yellow", "Vitest", `"providerOptions"${originalName ? ` in "${originalName}" project` : ""} is ignored because it's overridden by the configs. To hide this warning, remove the "providerOptions" property from the browser configuration.`));
7558
- }
7528
+ if (project.config.browser.providerOptions) vitest.logger.warn(withLabel("yellow", "Vitest", `"providerOptions"${originalName ? ` in "${originalName}" project` : ""} is ignored because it's overridden by the configs. To hide this warning, remove the "providerOptions" property from the browser configuration.`));
7559
7529
  filteredInstances.forEach((config, index) => {
7560
7530
  const browser = config.browser;
7561
7531
  if (!browser) {
@@ -7564,16 +7534,12 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7564
7534
  throw new Error(`The browser configuration must have a "browser" property. The ${nth}${ending} item in "browser.instances" doesn't have it. Make sure your${originalName ? ` "${originalName}"` : ""} configuration is correct.`);
7565
7535
  }
7566
7536
  const name = config.name;
7567
- if (name == null) {
7568
- throw new Error(`The browser configuration must have a "name" property. This is a bug in Vitest. Please, open a new issue with reproduction`);
7569
- }
7570
- if (names.has(name)) {
7571
- throw new Error([
7572
- `Cannot define a nested project for a ${browser} browser. The project name "${name}" was already defined. `,
7573
- "If you have multiple instances for the same browser, make sure to define a custom \"name\". ",
7574
- "All projects should have unique names. Make sure your configuration is correct."
7575
- ].join(""));
7576
- }
7537
+ if (name == null) throw new Error(`The browser configuration must have a "name" property. This is a bug in Vitest. Please, open a new issue with reproduction`);
7538
+ if (names.has(name)) throw new Error([
7539
+ `Cannot define a nested project for a ${browser} browser. The project name "${name}" was already defined. `,
7540
+ "If you have multiple instances for the same browser, make sure to define a custom \"name\". ",
7541
+ "All projects should have unique names. Make sure your configuration is correct."
7542
+ ].join(""));
7577
7543
  names.add(name);
7578
7544
  const clonedConfig = cloneConfig(project, config);
7579
7545
  clonedConfig.name = name;
@@ -7588,9 +7554,7 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7588
7554
  });
7589
7555
  if (headedBrowserProjects.length > 1) {
7590
7556
  const message = [`Found multiple projects that run browser tests in headed mode: "${headedBrowserProjects.map((p) => p.name).join("\", \"")}".`, ` Vitest cannot run multiple headed browsers at the same time.`].join("");
7591
- if (!isTTY) {
7592
- throw new Error(`${message} Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option.`);
7593
- }
7557
+ if (!isTTY) throw new Error(`${message} Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option.`);
7594
7558
  const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; });
7595
7559
  const { projectName } = await prompts.default({
7596
7560
  type: "select",
@@ -7601,9 +7565,7 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7601
7565
  })),
7602
7566
  message: `${message} Select a single project to run or cancel and run tests with "headless: true" option. Note that you can also start tests with --browser=name or --project=name flag.`
7603
7567
  });
7604
- if (!projectName) {
7605
- throw new Error("The test run was aborted.");
7606
- }
7568
+ if (!projectName) throw new Error("The test run was aborted.");
7607
7569
  return resolvedProjects.filter((project) => project.name === projectName);
7608
7570
  }
7609
7571
  return resolvedProjects;
@@ -7623,53 +7585,51 @@ function cloneConfig(project, { browser,...config }) {
7623
7585
  headless: headless ?? currentConfig.headless,
7624
7586
  name: browser,
7625
7587
  providerOptions: config,
7626
- instances: undefined
7588
+ instances: void 0
7627
7589
  }
7628
7590
  }, overrideConfig);
7629
7591
  }
7630
7592
  async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition) {
7593
+ // project configurations that were specified directly
7631
7594
  const projectsOptions = [];
7595
+ // custom config files that were specified directly or resolved from a directory
7632
7596
  const projectsConfigFiles = [];
7597
+ // custom glob matches that should be resolved as directories or config files
7633
7598
  const projectsGlobMatches = [];
7599
+ // directories that don't have a config file inside, but should be treated as projects
7634
7600
  const nonConfigProjectDirectories = [];
7635
- for (const definition of projectsDefinition) {
7636
- if (typeof definition === "string") {
7637
- const stringOption = definition.replace("<rootDir>", vitest.config.root);
7638
- if (!isDynamicPattern(stringOption)) {
7639
- const file = resolve(vitest.config.root, stringOption);
7640
- if (!existsSync(file)) {
7641
- const relativeWorkspaceConfigPath = workspaceConfigPath ? relative(vitest.config.root, workspaceConfigPath) : undefined;
7642
- const note = workspaceConfigPath ? `Workspace config file "${relativeWorkspaceConfigPath}"` : "Projects definition";
7643
- throw new Error(`${note} references a non-existing file or a directory: ${file}`);
7644
- }
7645
- const stats = await promises.stat(file);
7646
- if (stats.isFile()) {
7647
- projectsConfigFiles.push(file);
7648
- } else if (stats.isDirectory()) {
7649
- const configFile = await resolveDirectoryConfig(file);
7650
- if (configFile) {
7651
- projectsConfigFiles.push(configFile);
7652
- } else {
7653
- const directory = file[file.length - 1] === "/" ? file : `${file}/`;
7654
- nonConfigProjectDirectories.push(directory);
7655
- }
7656
- } else {
7657
- throw new TypeError(`Unexpected file type: ${file}`);
7658
- }
7659
- } else {
7660
- projectsGlobMatches.push(stringOption);
7601
+ for (const definition of projectsDefinition) if (typeof definition === "string") {
7602
+ const stringOption = definition.replace("<rootDir>", vitest.config.root);
7603
+ // if the string doesn't contain a glob, we can resolve it directly
7604
+ // ['./vitest.config.js']
7605
+ if (!isDynamicPattern(stringOption)) {
7606
+ const file = resolve(vitest.config.root, stringOption);
7607
+ if (!existsSync(file)) {
7608
+ const relativeWorkspaceConfigPath = workspaceConfigPath ? relative(vitest.config.root, workspaceConfigPath) : void 0;
7609
+ const note = workspaceConfigPath ? `Workspace config file "${relativeWorkspaceConfigPath}"` : "Projects definition";
7610
+ throw new Error(`${note} references a non-existing file or a directory: ${file}`);
7661
7611
  }
7662
- } else if (typeof definition === "function") {
7663
- projectsOptions.push(await definition({
7664
- command: vitest.vite.config.command,
7665
- mode: vitest.vite.config.mode,
7666
- isPreview: false,
7667
- isSsrBuild: false
7668
- }));
7669
- } else {
7670
- projectsOptions.push(await definition);
7671
- }
7672
- }
7612
+ const stats = await promises.stat(file);
7613
+ // user can specify a config file directly
7614
+ if (stats.isFile()) projectsConfigFiles.push(file);
7615
+ else if (stats.isDirectory()) {
7616
+ const configFile = await resolveDirectoryConfig(file);
7617
+ if (configFile) projectsConfigFiles.push(configFile);
7618
+ else {
7619
+ const directory = file[file.length - 1] === "/" ? file : `${file}/`;
7620
+ nonConfigProjectDirectories.push(directory);
7621
+ }
7622
+ } else
7623
+ // should never happen
7624
+ throw new TypeError(`Unexpected file type: ${file}`);
7625
+ } else projectsGlobMatches.push(stringOption);
7626
+ } else if (typeof definition === "function") projectsOptions.push(await definition({
7627
+ command: vitest.vite.config.command,
7628
+ mode: vitest.vite.config.mode,
7629
+ isPreview: false,
7630
+ isSsrBuild: false
7631
+ }));
7632
+ else projectsOptions.push(await definition);
7673
7633
  if (projectsGlobMatches.length) {
7674
7634
  const globOptions = {
7675
7635
  absolute: true,
@@ -7685,16 +7645,13 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
7685
7645
  };
7686
7646
  const projectsFs = await glob(projectsGlobMatches, globOptions);
7687
7647
  await Promise.all(projectsFs.map(async (path) => {
7648
+ // directories are allowed with a glob like `packages/*`
7649
+ // in this case every directory is treated as a project
7688
7650
  if (path.endsWith("/")) {
7689
7651
  const configFile = await resolveDirectoryConfig(path);
7690
- if (configFile) {
7691
- projectsConfigFiles.push(configFile);
7692
- } else {
7693
- nonConfigProjectDirectories.push(path);
7694
- }
7695
- } else {
7696
- projectsConfigFiles.push(path);
7697
- }
7652
+ if (configFile) projectsConfigFiles.push(configFile);
7653
+ else nonConfigProjectDirectories.push(path);
7654
+ } else projectsConfigFiles.push(path);
7698
7655
  }));
7699
7656
  }
7700
7657
  const projectConfigFiles = Array.from(new Set(projectsConfigFiles));
@@ -7706,31 +7663,25 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
7706
7663
  }
7707
7664
  async function resolveDirectoryConfig(directory) {
7708
7665
  const files = new Set(await promises.readdir(directory));
7666
+ // default resolution looks for vitest.config.* or vite.config.* files
7667
+ // this simulates how `findUp` works in packages/vitest/src/node/create.ts:29
7709
7668
  const configFile = configFiles.find((file) => files.has(file));
7710
- if (configFile) {
7711
- return resolve(directory, configFile);
7712
- }
7669
+ if (configFile) return resolve(directory, configFile);
7713
7670
  return null;
7714
7671
  }
7715
7672
  function getDefaultTestProject(vitest) {
7716
7673
  const filter = vitest.config.project;
7717
7674
  const project = vitest._ensureRootProject();
7718
- if (!filter.length) {
7719
- return project;
7720
- }
7675
+ if (!filter.length) return project;
7676
+ // check for the project name and browser names
7721
7677
  const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
7722
- if (hasProjects) {
7723
- return project;
7724
- }
7678
+ if (hasProjects) return project;
7725
7679
  return null;
7726
7680
  }
7727
7681
  function getPotentialProjectNames(project) {
7728
7682
  const names = [project.name];
7729
- if (project.config.browser.instances) {
7730
- names.push(...project.config.browser.instances.map((i) => i.name));
7731
- } else if (project.config.browser.name) {
7732
- names.push(project.config.browser.name);
7733
- }
7683
+ if (project.config.browser.instances) names.push(...project.config.browser.instances.map((i) => i.name));
7684
+ else if (project.config.browser.name) names.push(project.config.browser.name);
7734
7685
  return names;
7735
7686
  }
7736
7687
 
@@ -7741,9 +7692,7 @@ async function loadCustomReporterModule(path, runner) {
7741
7692
  } catch (customReporterModuleError) {
7742
7693
  throw new Error(`Failed to load custom Reporter from ${path}`, { cause: customReporterModuleError });
7743
7694
  }
7744
- if (customReporterModule.default === null || customReporterModule.default === undefined) {
7745
- throw new Error(`Custom reporter loaded from ${path} was not the default export`);
7746
- }
7695
+ if (customReporterModule.default === null || customReporterModule.default === void 0) throw new Error(`Custom reporter loaded from ${path} was not the default export`);
7747
7696
  return customReporterModule.default;
7748
7697
  }
7749
7698
  function createReporters(reporterReferences, ctx) {
@@ -7769,14 +7718,12 @@ function createReporters(reporterReferences, ctx) {
7769
7718
  }
7770
7719
  function createBenchmarkReporters(reporterReferences, runner) {
7771
7720
  const promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
7772
- if (typeof referenceOrInstance === "string") {
7773
- if (referenceOrInstance in BenchmarkReportsMap) {
7774
- const BuiltinReporter = BenchmarkReportsMap[referenceOrInstance];
7775
- return new BuiltinReporter();
7776
- } else {
7777
- const CustomReporter = await loadCustomReporterModule(referenceOrInstance, runner);
7778
- return new CustomReporter();
7779
- }
7721
+ if (typeof referenceOrInstance === "string") if (referenceOrInstance in BenchmarkReportsMap) {
7722
+ const BuiltinReporter = BenchmarkReportsMap[referenceOrInstance];
7723
+ return new BuiltinReporter();
7724
+ } else {
7725
+ const CustomReporter = await loadCustomReporterModule(referenceOrInstance, runner);
7726
+ return new CustomReporter();
7780
7727
  }
7781
7728
  return referenceOrInstance;
7782
7729
  });
@@ -7785,49 +7732,37 @@ function createBenchmarkReporters(reporterReferences, runner) {
7785
7732
 
7786
7733
  function parseFilter(filter) {
7787
7734
  const colonIndex = filter.lastIndexOf(":");
7788
- if (colonIndex === -1) {
7789
- return { filename: filter };
7790
- }
7735
+ if (colonIndex === -1) return { filename: filter };
7791
7736
  const [parsedFilename, lineNumber] = [filter.substring(0, colonIndex), filter.substring(colonIndex + 1)];
7792
- if (lineNumber.match(/^\d+$/)) {
7793
- return {
7794
- filename: parsedFilename,
7795
- lineNumber: Number.parseInt(lineNumber)
7796
- };
7797
- } else if (lineNumber.match(/^\d+-\d+$/)) {
7798
- throw new RangeLocationFilterProvidedError(filter);
7799
- } else {
7800
- return { filename: filter };
7801
- }
7737
+ if (lineNumber.match(/^\d+$/)) return {
7738
+ filename: parsedFilename,
7739
+ lineNumber: Number.parseInt(lineNumber)
7740
+ };
7741
+ else if (lineNumber.match(/^\d+-\d+$/)) throw new RangeLocationFilterProvidedError(filter);
7742
+ else return { filename: filter };
7802
7743
  }
7803
7744
  function groupFilters(filters) {
7804
7745
  const groupedFilters_ = groupBy(filters, (f) => f.filename);
7805
7746
  const groupedFilters = Object.fromEntries(Object.entries(groupedFilters_).map((entry) => {
7806
7747
  const [filename, filters] = entry;
7807
7748
  const testLocations = filters.map((f) => f.lineNumber);
7808
- return [filename, testLocations.filter((l) => l !== undefined)];
7749
+ return [filename, testLocations.filter((l) => l !== void 0)];
7809
7750
  }));
7810
7751
  return groupedFilters;
7811
7752
  }
7812
7753
 
7813
7754
  class VitestSpecifications {
7814
- _cachedSpecs = new Map();
7755
+ _cachedSpecs = /* @__PURE__ */ new Map();
7815
7756
  constructor(vitest) {
7816
7757
  this.vitest = vitest;
7817
7758
  }
7818
7759
  getModuleSpecifications(moduleId) {
7819
7760
  const _cached = this.getCachedSpecifications(moduleId);
7820
- if (_cached) {
7821
- return _cached;
7822
- }
7761
+ if (_cached) return _cached;
7823
7762
  const specs = [];
7824
7763
  for (const project of this.vitest.projects) {
7825
- if (project._isCachedTestFile(moduleId)) {
7826
- specs.push(project.createSpecification(moduleId));
7827
- }
7828
- if (project._isCachedTypecheckFile(moduleId)) {
7829
- specs.push(project.createSpecification(moduleId, [], "typescript"));
7830
- }
7764
+ if (project._isCachedTestFile(moduleId)) specs.push(project.createSpecification(moduleId));
7765
+ if (project._isCachedTypecheckFile(moduleId)) specs.push(project.createSpecification(moduleId, [], "typescript"));
7831
7766
  }
7832
7767
  specs.forEach((spec) => this.ensureSpecificationCached(spec));
7833
7768
  return specs;
@@ -7839,13 +7774,13 @@ class VitestSpecifications {
7839
7774
  const files = [];
7840
7775
  const dir = process.cwd();
7841
7776
  const parsedFilters = filters.map((f) => parseFilter(f));
7842
- if (!this.vitest.config.includeTaskLocation && parsedFilters.some((f) => f.lineNumber !== undefined)) {
7843
- throw new IncludeTaskLocationDisabledError();
7844
- }
7777
+ // Require includeTaskLocation when a location filter is passed
7778
+ if (!this.vitest.config.includeTaskLocation && parsedFilters.some((f) => f.lineNumber !== void 0)) throw new IncludeTaskLocationDisabledError();
7845
7779
  const testLines = groupFilters(parsedFilters.map((f) => ({
7846
7780
  ...f,
7847
7781
  filename: resolve(dir, f.filename)
7848
7782
  })));
7783
+ // Key is file and val specifies whether we have matched this file with testLocation
7849
7784
  const testLocHasMatch = {};
7850
7785
  await Promise.all(this.vitest.projects.map(async (project) => {
7851
7786
  const { testFiles, typecheckTestFiles } = await project.globTestFiles(parsedFilters.map((f) => f.filename));
@@ -7865,18 +7800,13 @@ class VitestSpecifications {
7865
7800
  });
7866
7801
  }));
7867
7802
  Object.entries(testLines).forEach(([filepath, loc]) => {
7868
- if (loc.length !== 0 && !testLocHasMatch[filepath]) {
7869
- throw new LocationFilterFileNotFoundError(relative(dir, filepath));
7870
- }
7803
+ if (loc.length !== 0 && !testLocHasMatch[filepath]) throw new LocationFilterFileNotFoundError(relative(dir, filepath));
7871
7804
  });
7872
7805
  return files;
7873
7806
  }
7874
7807
  clearCache(moduleId) {
7875
- if (moduleId) {
7876
- this._cachedSpecs.delete(moduleId);
7877
- } else {
7878
- this._cachedSpecs.clear();
7879
- }
7808
+ if (moduleId) this._cachedSpecs.delete(moduleId);
7809
+ else this._cachedSpecs.clear();
7880
7810
  }
7881
7811
  getCachedSpecifications(moduleId) {
7882
7812
  return this._cachedSpecs.get(moduleId);
@@ -7888,14 +7818,12 @@ class VitestSpecifications {
7888
7818
  if (index === -1) {
7889
7819
  specs.push(spec);
7890
7820
  this._cachedSpecs.set(file, specs);
7891
- } else {
7892
- specs.splice(index, 1, spec);
7893
- }
7821
+ } else specs.splice(index, 1, spec);
7894
7822
  return specs;
7895
7823
  }
7896
7824
  async filterTestsBySource(specs) {
7897
7825
  if (this.vitest.config.changed && !this.vitest.config.related) {
7898
- const { VitestGit } = await import('./git.DXfdBEfR.js');
7826
+ const { VitestGit } = await import('./git.BVQ8w_Sw.js');
7899
7827
  const vitestGit = new VitestGit(this.vitest.config.root);
7900
7828
  const related = await vitestGit.findChangedFiles({ changedSince: this.vitest.config.changed });
7901
7829
  if (!related) {
@@ -7905,46 +7833,34 @@ class VitestSpecifications {
7905
7833
  this.vitest.config.related = Array.from(new Set(related));
7906
7834
  }
7907
7835
  const related = this.vitest.config.related;
7908
- if (!related) {
7909
- return specs;
7910
- }
7836
+ if (!related) return specs;
7911
7837
  const forceRerunTriggers = this.vitest.config.forceRerunTriggers;
7912
- const matcher = forceRerunTriggers.length ? pm(forceRerunTriggers) : undefined;
7913
- if (matcher && related.some((file) => matcher(file))) {
7914
- return specs;
7915
- }
7916
- if (!this.vitest.config.watch && !related.length) {
7917
- return [];
7918
- }
7838
+ const matcher = forceRerunTriggers.length ? pm(forceRerunTriggers) : void 0;
7839
+ if (matcher && related.some((file) => matcher(file))) return specs;
7840
+ // don't run anything if no related sources are found
7841
+ // if we are in watch mode, we want to process all tests
7842
+ if (!this.vitest.config.watch && !related.length) return [];
7919
7843
  const testGraphs = await Promise.all(specs.map(async (spec) => {
7920
7844
  const deps = await this.getTestDependencies(spec);
7921
7845
  return [spec, deps];
7922
7846
  }));
7923
7847
  const runningTests = [];
7924
- for (const [specification, deps] of testGraphs) {
7925
- if (related.some((path) => path === specification.moduleId || deps.has(path))) {
7926
- runningTests.push(specification);
7927
- }
7928
- }
7848
+ for (const [specification, deps] of testGraphs)
7849
+ // if deps or the test itself were changed
7850
+ if (related.some((path) => path === specification.moduleId || deps.has(path))) runningTests.push(specification);
7929
7851
  return runningTests;
7930
7852
  }
7931
- async getTestDependencies(spec, deps = new Set()) {
7853
+ async getTestDependencies(spec, deps = /* @__PURE__ */ new Set()) {
7932
7854
  const addImports = async (project, filepath) => {
7933
- if (deps.has(filepath)) {
7934
- return;
7935
- }
7855
+ if (deps.has(filepath)) return;
7936
7856
  deps.add(filepath);
7937
7857
  const mod = project.vite.moduleGraph.getModuleById(filepath);
7938
7858
  const transformed = mod?.ssrTransformResult || await project.vitenode.transformRequest(filepath);
7939
- if (!transformed) {
7940
- return;
7941
- }
7859
+ if (!transformed) return;
7942
7860
  const dependencies = [...transformed.deps || [], ...transformed.dynamicDeps || []];
7943
7861
  await Promise.all(dependencies.map(async (dep) => {
7944
7862
  const fsPath = dep.startsWith("/@fs/") ? dep.slice(isWindows ? 5 : 4) : join(project.config.root, dep);
7945
- if (!fsPath.includes("node_modules") && !deps.has(fsPath) && existsSync(fsPath)) {
7946
- await addImports(project, fsPath);
7947
- }
7863
+ if (!fsPath.includes("node_modules") && !deps.has(fsPath) && existsSync(fsPath)) await addImports(project, fsPath);
7948
7864
  }));
7949
7865
  };
7950
7866
  await addImports(spec.project, spec.moduleId);
@@ -8029,24 +7945,16 @@ class TestCase extends ReportedTaskImplementation {
8029
7945
  this.name = task.name;
8030
7946
  this.module = getReportedTask(project, task.file);
8031
7947
  const suite = this.task.suite;
8032
- if (suite) {
8033
- this.parent = getReportedTask(project, suite);
8034
- } else {
8035
- this.parent = this.module;
8036
- }
7948
+ if (suite) this.parent = getReportedTask(project, suite);
7949
+ else this.parent = this.module;
8037
7950
  this.options = buildOptions(task);
8038
7951
  }
8039
7952
  /**
8040
7953
  * Full name of the test including all parent suites separated with `>`.
8041
7954
  */
8042
7955
  get fullName() {
8043
- if (this.#fullName === undefined) {
8044
- if (this.parent.type !== "module") {
8045
- this.#fullName = `${this.parent.fullName} > ${this.name}`;
8046
- } else {
8047
- this.#fullName = this.name;
8048
- }
8049
- }
7956
+ if (this.#fullName === void 0) if (this.parent.type !== "module") this.#fullName = `${this.parent.fullName} > ${this.name}`;
7957
+ else this.#fullName = this.name;
8050
7958
  return this.#fullName;
8051
7959
  }
8052
7960
  /**
@@ -8059,47 +7967,44 @@ class TestCase extends ReportedTaskImplementation {
8059
7967
  result() {
8060
7968
  const result = this.task.result;
8061
7969
  const mode = result?.state || this.task.mode;
8062
- if (!result && (mode === "skip" || mode === "todo")) {
8063
- return {
8064
- state: "skipped",
8065
- note: undefined,
8066
- errors: undefined
8067
- };
8068
- }
8069
- if (!result || result.state === "run" || result.state === "queued") {
8070
- return {
8071
- state: "pending",
8072
- errors: undefined
8073
- };
8074
- }
7970
+ if (!result && (mode === "skip" || mode === "todo")) return {
7971
+ state: "skipped",
7972
+ note: void 0,
7973
+ errors: void 0
7974
+ };
7975
+ if (!result || result.state === "run" || result.state === "queued") return {
7976
+ state: "pending",
7977
+ errors: void 0
7978
+ };
8075
7979
  const state = result.state === "fail" ? "failed" : result.state === "pass" ? "passed" : "skipped";
8076
- if (state === "skipped") {
8077
- return {
8078
- state,
8079
- note: result.note,
8080
- errors: undefined
8081
- };
8082
- }
8083
- if (state === "passed") {
8084
- return {
8085
- state,
8086
- errors: result.errors
8087
- };
8088
- }
7980
+ if (state === "skipped") return {
7981
+ state,
7982
+ note: result.note,
7983
+ errors: void 0
7984
+ };
7985
+ if (state === "passed") return {
7986
+ state,
7987
+ errors: result.errors
7988
+ };
8089
7989
  return {
8090
7990
  state,
8091
7991
  errors: result.errors || []
8092
7992
  };
8093
7993
  }
8094
7994
  /**
7995
+ * Test annotations added via the `task.annotate` API during the test execution.
7996
+ */
7997
+ annotations() {
7998
+ return [...this.task.annotations];
7999
+ }
8000
+ /**
8095
8001
  * Useful information about the test like duration, memory usage, etc.
8096
8002
  * Diagnostic is only available after the test has finished.
8097
8003
  */
8098
8004
  diagnostic() {
8099
8005
  const result = this.task.result;
8100
- if (!result || !result.startTime) {
8101
- return undefined;
8102
- }
8006
+ // startTime should always be available if the test has properly finished
8007
+ if (!result || !result.startTime) return void 0;
8103
8008
  const duration = result.duration || 0;
8104
8009
  const slow = duration > this.project.globalConfig.slowTestThreshold;
8105
8010
  return {
@@ -8124,9 +8029,7 @@ class TestCollection {
8124
8029
  * Returns the test or suite at a specific index.
8125
8030
  */
8126
8031
  at(index) {
8127
- if (index < 0) {
8128
- index = this.size + index;
8129
- }
8032
+ if (index < 0) index = this.size + index;
8130
8033
  return getReportedTask(this.#project, this.#task.tasks[index]);
8131
8034
  }
8132
8035
  /**
@@ -8145,62 +8048,41 @@ class TestCollection {
8145
8048
  * Filters all tests that are part of this collection and its children.
8146
8049
  */
8147
8050
  *allTests(state) {
8148
- for (const child of this) {
8149
- if (child.type === "suite") {
8150
- yield* child.children.allTests(state);
8151
- } else if (state) {
8152
- const testState = child.result().state;
8153
- if (state === testState) {
8154
- yield child;
8155
- }
8156
- } else {
8157
- yield child;
8158
- }
8159
- }
8051
+ for (const child of this) if (child.type === "suite") yield* child.children.allTests(state);
8052
+ else if (state) {
8053
+ const testState = child.result().state;
8054
+ if (state === testState) yield child;
8055
+ } else yield child;
8160
8056
  }
8161
8057
  /**
8162
8058
  * Filters only the tests that are part of this collection.
8163
8059
  */
8164
8060
  *tests(state) {
8165
8061
  for (const child of this) {
8166
- if (child.type !== "test") {
8167
- continue;
8168
- }
8062
+ if (child.type !== "test") continue;
8169
8063
  if (state) {
8170
8064
  const testState = child.result().state;
8171
- if (state === testState) {
8172
- yield child;
8173
- }
8174
- } else {
8175
- yield child;
8176
- }
8065
+ if (state === testState) yield child;
8066
+ } else yield child;
8177
8067
  }
8178
8068
  }
8179
8069
  /**
8180
8070
  * Filters only the suites that are part of this collection.
8181
8071
  */
8182
8072
  *suites() {
8183
- for (const child of this) {
8184
- if (child.type === "suite") {
8185
- yield child;
8186
- }
8187
- }
8073
+ for (const child of this) if (child.type === "suite") yield child;
8188
8074
  }
8189
8075
  /**
8190
8076
  * Filters all suites that are part of this collection and its children.
8191
8077
  */
8192
8078
  *allSuites() {
8193
- for (const child of this) {
8194
- if (child.type === "suite") {
8195
- yield child;
8196
- yield* child.children.allSuites();
8197
- }
8079
+ for (const child of this) if (child.type === "suite") {
8080
+ yield child;
8081
+ yield* child.children.allSuites();
8198
8082
  }
8199
8083
  }
8200
8084
  *[Symbol.iterator]() {
8201
- for (const task of this.#task.tasks) {
8202
- yield getReportedTask(this.#project, task);
8203
- }
8085
+ for (const task of this.#task.tasks) yield getReportedTask(this.#project, task);
8204
8086
  }
8205
8087
  }
8206
8088
  class SuiteImplementation extends ReportedTaskImplementation {
@@ -8245,11 +8127,8 @@ class TestSuite extends SuiteImplementation {
8245
8127
  this.name = task.name;
8246
8128
  this.module = getReportedTask(project, task.file);
8247
8129
  const suite = this.task.suite;
8248
- if (suite) {
8249
- this.parent = getReportedTask(project, suite);
8250
- } else {
8251
- this.parent = this.module;
8252
- }
8130
+ if (suite) this.parent = getReportedTask(project, suite);
8131
+ else this.parent = this.module;
8253
8132
  this.options = buildOptions(task);
8254
8133
  }
8255
8134
  /**
@@ -8262,13 +8141,8 @@ class TestSuite extends SuiteImplementation {
8262
8141
  * Full name of the suite including all parent suites separated with `>`.
8263
8142
  */
8264
8143
  get fullName() {
8265
- if (this.#fullName === undefined) {
8266
- if (this.parent.type !== "module") {
8267
- this.#fullName = `${this.parent.fullName} > ${this.name}`;
8268
- } else {
8269
- this.#fullName = this.name;
8270
- }
8271
- }
8144
+ if (this.#fullName === void 0) if (this.parent.type !== "module") this.#fullName = `${this.parent.fullName} > ${this.name}`;
8145
+ else this.#fullName = this.name;
8272
8146
  return this.#fullName;
8273
8147
  }
8274
8148
  }
@@ -8290,9 +8164,7 @@ class TestModule extends SuiteImplementation {
8290
8164
  */
8291
8165
  state() {
8292
8166
  const state = this.task.result?.state;
8293
- if (state === "queued") {
8294
- return "queued";
8295
- }
8167
+ if (state === "queued") return "queued";
8296
8168
  return getSuiteState(this.task);
8297
8169
  }
8298
8170
  /**
@@ -8306,13 +8178,15 @@ class TestModule extends SuiteImplementation {
8306
8178
  const environmentSetupDuration = this.task.environmentLoad || 0;
8307
8179
  const duration = this.task.result?.duration || 0;
8308
8180
  const heap = this.task.result?.heap;
8181
+ const importDurations = this.task.importDurations ?? {};
8309
8182
  return {
8310
8183
  environmentSetupDuration,
8311
8184
  prepareDuration,
8312
8185
  collectDuration,
8313
8186
  setupDuration,
8314
8187
  duration,
8315
- heap
8188
+ heap,
8189
+ importDurations
8316
8190
  };
8317
8191
  }
8318
8192
  }
@@ -8332,56 +8206,39 @@ function storeTask(project, runnerTask, reportedTask) {
8332
8206
  }
8333
8207
  function getReportedTask(project, runnerTask) {
8334
8208
  const reportedTask = project.vitest.state.getReportedEntity(runnerTask);
8335
- if (!reportedTask) {
8336
- throw new Error(`Task instance was not found for ${runnerTask.type} "${runnerTask.name}"`);
8337
- }
8209
+ if (!reportedTask) throw new Error(`Task instance was not found for ${runnerTask.type} "${runnerTask.name}"`);
8338
8210
  return reportedTask;
8339
8211
  }
8340
8212
  function getSuiteState(task) {
8341
8213
  const mode = task.mode;
8342
8214
  const state = task.result?.state;
8343
- if (mode === "skip" || mode === "todo" || state === "skip" || state === "todo") {
8344
- return "skipped";
8345
- }
8346
- if (state == null || state === "run" || state === "only") {
8347
- return "pending";
8348
- }
8349
- if (state === "fail") {
8350
- return "failed";
8351
- }
8352
- if (state === "pass") {
8353
- return "passed";
8354
- }
8215
+ if (mode === "skip" || mode === "todo" || state === "skip" || state === "todo") return "skipped";
8216
+ if (state == null || state === "run" || state === "only") return "pending";
8217
+ if (state === "fail") return "failed";
8218
+ if (state === "pass") return "passed";
8355
8219
  throw new Error(`Unknown suite state: ${state}`);
8356
8220
  }
8357
8221
 
8358
8222
  function isAggregateError(err) {
8359
- if (typeof AggregateError !== "undefined" && err instanceof AggregateError) {
8360
- return true;
8361
- }
8223
+ if (typeof AggregateError !== "undefined" && err instanceof AggregateError) return true;
8362
8224
  return err instanceof Error && "errors" in err;
8363
8225
  }
8364
8226
  class StateManager {
8365
- filesMap = new Map();
8366
- pathsSet = new Set();
8367
- idMap = new Map();
8368
- taskFileMap = new WeakMap();
8369
- errorsSet = new Set();
8370
- processTimeoutCauses = new Set();
8371
- reportedTasksMap = new WeakMap();
8227
+ filesMap = /* @__PURE__ */ new Map();
8228
+ pathsSet = /* @__PURE__ */ new Set();
8229
+ idMap = /* @__PURE__ */ new Map();
8230
+ taskFileMap = /* @__PURE__ */ new WeakMap();
8231
+ errorsSet = /* @__PURE__ */ new Set();
8232
+ processTimeoutCauses = /* @__PURE__ */ new Set();
8233
+ reportedTasksMap = /* @__PURE__ */ new WeakMap();
8372
8234
  blobs;
8373
8235
  catchError(err, type) {
8374
- if (isAggregateError(err)) {
8375
- return err.errors.forEach((error) => this.catchError(error, type));
8376
- }
8377
- if (err === Object(err)) {
8378
- err.type = type;
8379
- } else {
8380
- err = {
8381
- type,
8382
- message: err
8383
- };
8384
- }
8236
+ if (isAggregateError(err)) return err.errors.forEach((error) => this.catchError(error, type));
8237
+ if (err === Object(err)) err.type = type;
8238
+ else err = {
8239
+ type,
8240
+ message: err
8241
+ };
8385
8242
  const _err = err;
8386
8243
  if (_err && typeof _err === "object" && _err.code === "VITEST_PENDING") {
8387
8244
  const task = this.idMap.get(_err.taskId);
@@ -8414,16 +8271,11 @@ class StateManager {
8414
8271
  * Return files that were running or collected.
8415
8272
  */
8416
8273
  getFiles(keys) {
8417
- if (keys) {
8418
- return keys.map((key) => this.filesMap.get(key)).flat().filter((file) => file && !file.local);
8419
- }
8274
+ if (keys) return keys.map((key) => this.filesMap.get(key)).flat().filter((file) => file && !file.local);
8420
8275
  return Array.from(this.filesMap.values()).flat().filter((file) => !file.local).sort((f1, f2) => {
8421
- if (f1.meta?.typecheck && f2.meta?.typecheck) {
8422
- return 0;
8423
- }
8424
- if (f1.meta?.typecheck) {
8425
- return -1;
8426
- }
8276
+ // print typecheck files first
8277
+ if (f1.meta?.typecheck && f2.meta?.typecheck) return 0;
8278
+ if (f1.meta?.typecheck) return -1;
8427
8279
  return 1;
8428
8280
  });
8429
8281
  }
@@ -8446,9 +8298,9 @@ class StateManager {
8446
8298
  const existing = this.filesMap.get(file.filepath) || [];
8447
8299
  const otherFiles = existing.filter((i) => i.projectName !== file.projectName || i.meta.typecheck !== file.meta.typecheck);
8448
8300
  const currentFile = existing.find((i) => i.projectName === file.projectName);
8449
- if (currentFile) {
8450
- file.logs = currentFile.logs;
8451
- }
8301
+ // keep logs for the previous file because it should always be initiated before the collections phase
8302
+ // which means that all logs are collected during the collection and not inside tests
8303
+ if (currentFile) file.logs = currentFile.logs;
8452
8304
  otherFiles.push(file);
8453
8305
  this.filesMap.set(file.filepath, otherFiles);
8454
8306
  this.updateId(file, project);
@@ -8466,30 +8318,20 @@ class StateManager {
8466
8318
  return;
8467
8319
  }
8468
8320
  const filtered = files.filter((file) => file.projectName !== project.config.name);
8469
- if (!filtered.length) {
8470
- this.filesMap.set(path, [fileTask]);
8471
- } else {
8472
- this.filesMap.set(path, [...filtered, fileTask]);
8473
- }
8321
+ // always keep a File task, so we can associate logs with it
8322
+ if (!filtered.length) this.filesMap.set(path, [fileTask]);
8323
+ else this.filesMap.set(path, [...filtered, fileTask]);
8474
8324
  });
8475
8325
  }
8476
8326
  updateId(task, project) {
8477
- if (this.idMap.get(task.id) === task) {
8478
- return;
8479
- }
8480
- if (task.type === "suite" && "filepath" in task) {
8481
- TestModule.register(task, project);
8482
- } else if (task.type === "suite") {
8483
- TestSuite.register(task, project);
8484
- } else {
8485
- TestCase.register(task, project);
8486
- }
8327
+ if (this.idMap.get(task.id) === task) return;
8328
+ if (task.type === "suite" && "filepath" in task) TestModule.register(task, project);
8329
+ else if (task.type === "suite") TestSuite.register(task, project);
8330
+ else TestCase.register(task, project);
8487
8331
  this.idMap.set(task.id, task);
8488
- if (task.type === "suite") {
8489
- task.tasks.forEach((task) => {
8490
- this.updateId(task, project);
8491
- });
8492
- }
8332
+ if (task.type === "suite") task.tasks.forEach((task) => {
8333
+ this.updateId(task, project);
8334
+ });
8493
8335
  }
8494
8336
  getReportedEntity(task) {
8495
8337
  return this.reportedTasksMap.get(task);
@@ -8500,18 +8342,15 @@ class StateManager {
8500
8342
  if (task) {
8501
8343
  task.result = result;
8502
8344
  task.meta = meta;
8503
- if (result?.state === "skip") {
8504
- task.mode = "skip";
8505
- }
8345
+ // skipped with new PendingError
8346
+ if (result?.state === "skip") task.mode = "skip";
8506
8347
  }
8507
8348
  }
8508
8349
  }
8509
8350
  updateUserLog(log) {
8510
8351
  const task = log.taskId && this.idMap.get(log.taskId);
8511
8352
  if (task) {
8512
- if (!task.logs) {
8513
- task.logs = [];
8514
- }
8353
+ if (!task.logs) task.logs = [];
8515
8354
  task.logs.push(log);
8516
8355
  }
8517
8356
  }
@@ -8523,6 +8362,458 @@ class StateManager {
8523
8362
  }
8524
8363
  }
8525
8364
 
8365
+ const types = {
8366
+ 'application/andrew-inset': ['ez'],
8367
+ 'application/appinstaller': ['appinstaller'],
8368
+ 'application/applixware': ['aw'],
8369
+ 'application/appx': ['appx'],
8370
+ 'application/appxbundle': ['appxbundle'],
8371
+ 'application/atom+xml': ['atom'],
8372
+ 'application/atomcat+xml': ['atomcat'],
8373
+ 'application/atomdeleted+xml': ['atomdeleted'],
8374
+ 'application/atomsvc+xml': ['atomsvc'],
8375
+ 'application/atsc-dwd+xml': ['dwd'],
8376
+ 'application/atsc-held+xml': ['held'],
8377
+ 'application/atsc-rsat+xml': ['rsat'],
8378
+ 'application/automationml-aml+xml': ['aml'],
8379
+ 'application/automationml-amlx+zip': ['amlx'],
8380
+ 'application/bdoc': ['bdoc'],
8381
+ 'application/calendar+xml': ['xcs'],
8382
+ 'application/ccxml+xml': ['ccxml'],
8383
+ 'application/cdfx+xml': ['cdfx'],
8384
+ 'application/cdmi-capability': ['cdmia'],
8385
+ 'application/cdmi-container': ['cdmic'],
8386
+ 'application/cdmi-domain': ['cdmid'],
8387
+ 'application/cdmi-object': ['cdmio'],
8388
+ 'application/cdmi-queue': ['cdmiq'],
8389
+ 'application/cpl+xml': ['cpl'],
8390
+ 'application/cu-seeme': ['cu'],
8391
+ 'application/cwl': ['cwl'],
8392
+ 'application/dash+xml': ['mpd'],
8393
+ 'application/dash-patch+xml': ['mpp'],
8394
+ 'application/davmount+xml': ['davmount'],
8395
+ 'application/dicom': ['dcm'],
8396
+ 'application/docbook+xml': ['dbk'],
8397
+ 'application/dssc+der': ['dssc'],
8398
+ 'application/dssc+xml': ['xdssc'],
8399
+ 'application/ecmascript': ['ecma'],
8400
+ 'application/emma+xml': ['emma'],
8401
+ 'application/emotionml+xml': ['emotionml'],
8402
+ 'application/epub+zip': ['epub'],
8403
+ 'application/exi': ['exi'],
8404
+ 'application/express': ['exp'],
8405
+ 'application/fdf': ['fdf'],
8406
+ 'application/fdt+xml': ['fdt'],
8407
+ 'application/font-tdpfr': ['pfr'],
8408
+ 'application/geo+json': ['geojson'],
8409
+ 'application/gml+xml': ['gml'],
8410
+ 'application/gpx+xml': ['gpx'],
8411
+ 'application/gxf': ['gxf'],
8412
+ 'application/gzip': ['gz'],
8413
+ 'application/hjson': ['hjson'],
8414
+ 'application/hyperstudio': ['stk'],
8415
+ 'application/inkml+xml': ['ink', 'inkml'],
8416
+ 'application/ipfix': ['ipfix'],
8417
+ 'application/its+xml': ['its'],
8418
+ 'application/java-archive': ['jar', 'war', 'ear'],
8419
+ 'application/java-serialized-object': ['ser'],
8420
+ 'application/java-vm': ['class'],
8421
+ 'application/javascript': ['*js'],
8422
+ 'application/json': ['json', 'map'],
8423
+ 'application/json5': ['json5'],
8424
+ 'application/jsonml+json': ['jsonml'],
8425
+ 'application/ld+json': ['jsonld'],
8426
+ 'application/lgr+xml': ['lgr'],
8427
+ 'application/lost+xml': ['lostxml'],
8428
+ 'application/mac-binhex40': ['hqx'],
8429
+ 'application/mac-compactpro': ['cpt'],
8430
+ 'application/mads+xml': ['mads'],
8431
+ 'application/manifest+json': ['webmanifest'],
8432
+ 'application/marc': ['mrc'],
8433
+ 'application/marcxml+xml': ['mrcx'],
8434
+ 'application/mathematica': ['ma', 'nb', 'mb'],
8435
+ 'application/mathml+xml': ['mathml'],
8436
+ 'application/mbox': ['mbox'],
8437
+ 'application/media-policy-dataset+xml': ['mpf'],
8438
+ 'application/mediaservercontrol+xml': ['mscml'],
8439
+ 'application/metalink+xml': ['metalink'],
8440
+ 'application/metalink4+xml': ['meta4'],
8441
+ 'application/mets+xml': ['mets'],
8442
+ 'application/mmt-aei+xml': ['maei'],
8443
+ 'application/mmt-usd+xml': ['musd'],
8444
+ 'application/mods+xml': ['mods'],
8445
+ 'application/mp21': ['m21', 'mp21'],
8446
+ 'application/mp4': ['*mp4', '*mpg4', 'mp4s', 'm4p'],
8447
+ 'application/msix': ['msix'],
8448
+ 'application/msixbundle': ['msixbundle'],
8449
+ 'application/msword': ['doc', 'dot'],
8450
+ 'application/mxf': ['mxf'],
8451
+ 'application/n-quads': ['nq'],
8452
+ 'application/n-triples': ['nt'],
8453
+ 'application/node': ['cjs'],
8454
+ 'application/octet-stream': [
8455
+ 'bin',
8456
+ 'dms',
8457
+ 'lrf',
8458
+ 'mar',
8459
+ 'so',
8460
+ 'dist',
8461
+ 'distz',
8462
+ 'pkg',
8463
+ 'bpk',
8464
+ 'dump',
8465
+ 'elc',
8466
+ 'deploy',
8467
+ 'exe',
8468
+ 'dll',
8469
+ 'deb',
8470
+ 'dmg',
8471
+ 'iso',
8472
+ 'img',
8473
+ 'msi',
8474
+ 'msp',
8475
+ 'msm',
8476
+ 'buffer',
8477
+ ],
8478
+ 'application/oda': ['oda'],
8479
+ 'application/oebps-package+xml': ['opf'],
8480
+ 'application/ogg': ['ogx'],
8481
+ 'application/omdoc+xml': ['omdoc'],
8482
+ 'application/onenote': [
8483
+ 'onetoc',
8484
+ 'onetoc2',
8485
+ 'onetmp',
8486
+ 'onepkg',
8487
+ 'one',
8488
+ 'onea',
8489
+ ],
8490
+ 'application/oxps': ['oxps'],
8491
+ 'application/p2p-overlay+xml': ['relo'],
8492
+ 'application/patch-ops-error+xml': ['xer'],
8493
+ 'application/pdf': ['pdf'],
8494
+ 'application/pgp-encrypted': ['pgp'],
8495
+ 'application/pgp-keys': ['asc'],
8496
+ 'application/pgp-signature': ['sig', '*asc'],
8497
+ 'application/pics-rules': ['prf'],
8498
+ 'application/pkcs10': ['p10'],
8499
+ 'application/pkcs7-mime': ['p7m', 'p7c'],
8500
+ 'application/pkcs7-signature': ['p7s'],
8501
+ 'application/pkcs8': ['p8'],
8502
+ 'application/pkix-attr-cert': ['ac'],
8503
+ 'application/pkix-cert': ['cer'],
8504
+ 'application/pkix-crl': ['crl'],
8505
+ 'application/pkix-pkipath': ['pkipath'],
8506
+ 'application/pkixcmp': ['pki'],
8507
+ 'application/pls+xml': ['pls'],
8508
+ 'application/postscript': ['ai', 'eps', 'ps'],
8509
+ 'application/provenance+xml': ['provx'],
8510
+ 'application/pskc+xml': ['pskcxml'],
8511
+ 'application/raml+yaml': ['raml'],
8512
+ 'application/rdf+xml': ['rdf', 'owl'],
8513
+ 'application/reginfo+xml': ['rif'],
8514
+ 'application/relax-ng-compact-syntax': ['rnc'],
8515
+ 'application/resource-lists+xml': ['rl'],
8516
+ 'application/resource-lists-diff+xml': ['rld'],
8517
+ 'application/rls-services+xml': ['rs'],
8518
+ 'application/route-apd+xml': ['rapd'],
8519
+ 'application/route-s-tsid+xml': ['sls'],
8520
+ 'application/route-usd+xml': ['rusd'],
8521
+ 'application/rpki-ghostbusters': ['gbr'],
8522
+ 'application/rpki-manifest': ['mft'],
8523
+ 'application/rpki-roa': ['roa'],
8524
+ 'application/rsd+xml': ['rsd'],
8525
+ 'application/rss+xml': ['rss'],
8526
+ 'application/rtf': ['rtf'],
8527
+ 'application/sbml+xml': ['sbml'],
8528
+ 'application/scvp-cv-request': ['scq'],
8529
+ 'application/scvp-cv-response': ['scs'],
8530
+ 'application/scvp-vp-request': ['spq'],
8531
+ 'application/scvp-vp-response': ['spp'],
8532
+ 'application/sdp': ['sdp'],
8533
+ 'application/senml+xml': ['senmlx'],
8534
+ 'application/sensml+xml': ['sensmlx'],
8535
+ 'application/set-payment-initiation': ['setpay'],
8536
+ 'application/set-registration-initiation': ['setreg'],
8537
+ 'application/shf+xml': ['shf'],
8538
+ 'application/sieve': ['siv', 'sieve'],
8539
+ 'application/smil+xml': ['smi', 'smil'],
8540
+ 'application/sparql-query': ['rq'],
8541
+ 'application/sparql-results+xml': ['srx'],
8542
+ 'application/sql': ['sql'],
8543
+ 'application/srgs': ['gram'],
8544
+ 'application/srgs+xml': ['grxml'],
8545
+ 'application/sru+xml': ['sru'],
8546
+ 'application/ssdl+xml': ['ssdl'],
8547
+ 'application/ssml+xml': ['ssml'],
8548
+ 'application/swid+xml': ['swidtag'],
8549
+ 'application/tei+xml': ['tei', 'teicorpus'],
8550
+ 'application/thraud+xml': ['tfi'],
8551
+ 'application/timestamped-data': ['tsd'],
8552
+ 'application/toml': ['toml'],
8553
+ 'application/trig': ['trig'],
8554
+ 'application/ttml+xml': ['ttml'],
8555
+ 'application/ubjson': ['ubj'],
8556
+ 'application/urc-ressheet+xml': ['rsheet'],
8557
+ 'application/urc-targetdesc+xml': ['td'],
8558
+ 'application/voicexml+xml': ['vxml'],
8559
+ 'application/wasm': ['wasm'],
8560
+ 'application/watcherinfo+xml': ['wif'],
8561
+ 'application/widget': ['wgt'],
8562
+ 'application/winhlp': ['hlp'],
8563
+ 'application/wsdl+xml': ['wsdl'],
8564
+ 'application/wspolicy+xml': ['wspolicy'],
8565
+ 'application/xaml+xml': ['xaml'],
8566
+ 'application/xcap-att+xml': ['xav'],
8567
+ 'application/xcap-caps+xml': ['xca'],
8568
+ 'application/xcap-diff+xml': ['xdf'],
8569
+ 'application/xcap-el+xml': ['xel'],
8570
+ 'application/xcap-ns+xml': ['xns'],
8571
+ 'application/xenc+xml': ['xenc'],
8572
+ 'application/xfdf': ['xfdf'],
8573
+ 'application/xhtml+xml': ['xhtml', 'xht'],
8574
+ 'application/xliff+xml': ['xlf'],
8575
+ 'application/xml': ['xml', 'xsl', 'xsd', 'rng'],
8576
+ 'application/xml-dtd': ['dtd'],
8577
+ 'application/xop+xml': ['xop'],
8578
+ 'application/xproc+xml': ['xpl'],
8579
+ 'application/xslt+xml': ['*xsl', 'xslt'],
8580
+ 'application/xspf+xml': ['xspf'],
8581
+ 'application/xv+xml': ['mxml', 'xhvml', 'xvml', 'xvm'],
8582
+ 'application/yang': ['yang'],
8583
+ 'application/yin+xml': ['yin'],
8584
+ 'application/zip': ['zip'],
8585
+ 'application/zip+dotlottie': ['lottie'],
8586
+ 'audio/3gpp': ['*3gpp'],
8587
+ 'audio/aac': ['adts', 'aac'],
8588
+ 'audio/adpcm': ['adp'],
8589
+ 'audio/amr': ['amr'],
8590
+ 'audio/basic': ['au', 'snd'],
8591
+ 'audio/midi': ['mid', 'midi', 'kar', 'rmi'],
8592
+ 'audio/mobile-xmf': ['mxmf'],
8593
+ 'audio/mp3': ['*mp3'],
8594
+ 'audio/mp4': ['m4a', 'mp4a', 'm4b'],
8595
+ 'audio/mpeg': ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'],
8596
+ 'audio/ogg': ['oga', 'ogg', 'spx', 'opus'],
8597
+ 'audio/s3m': ['s3m'],
8598
+ 'audio/silk': ['sil'],
8599
+ 'audio/wav': ['wav'],
8600
+ 'audio/wave': ['*wav'],
8601
+ 'audio/webm': ['weba'],
8602
+ 'audio/xm': ['xm'],
8603
+ 'font/collection': ['ttc'],
8604
+ 'font/otf': ['otf'],
8605
+ 'font/ttf': ['ttf'],
8606
+ 'font/woff': ['woff'],
8607
+ 'font/woff2': ['woff2'],
8608
+ 'image/aces': ['exr'],
8609
+ 'image/apng': ['apng'],
8610
+ 'image/avci': ['avci'],
8611
+ 'image/avcs': ['avcs'],
8612
+ 'image/avif': ['avif'],
8613
+ 'image/bmp': ['bmp', 'dib'],
8614
+ 'image/cgm': ['cgm'],
8615
+ 'image/dicom-rle': ['drle'],
8616
+ 'image/dpx': ['dpx'],
8617
+ 'image/emf': ['emf'],
8618
+ 'image/fits': ['fits'],
8619
+ 'image/g3fax': ['g3'],
8620
+ 'image/gif': ['gif'],
8621
+ 'image/heic': ['heic'],
8622
+ 'image/heic-sequence': ['heics'],
8623
+ 'image/heif': ['heif'],
8624
+ 'image/heif-sequence': ['heifs'],
8625
+ 'image/hej2k': ['hej2'],
8626
+ 'image/ief': ['ief'],
8627
+ 'image/jaii': ['jaii'],
8628
+ 'image/jais': ['jais'],
8629
+ 'image/jls': ['jls'],
8630
+ 'image/jp2': ['jp2', 'jpg2'],
8631
+ 'image/jpeg': ['jpg', 'jpeg', 'jpe'],
8632
+ 'image/jph': ['jph'],
8633
+ 'image/jphc': ['jhc'],
8634
+ 'image/jpm': ['jpm', 'jpgm'],
8635
+ 'image/jpx': ['jpx', 'jpf'],
8636
+ 'image/jxl': ['jxl'],
8637
+ 'image/jxr': ['jxr'],
8638
+ 'image/jxra': ['jxra'],
8639
+ 'image/jxrs': ['jxrs'],
8640
+ 'image/jxs': ['jxs'],
8641
+ 'image/jxsc': ['jxsc'],
8642
+ 'image/jxsi': ['jxsi'],
8643
+ 'image/jxss': ['jxss'],
8644
+ 'image/ktx': ['ktx'],
8645
+ 'image/ktx2': ['ktx2'],
8646
+ 'image/pjpeg': ['jfif'],
8647
+ 'image/png': ['png'],
8648
+ 'image/sgi': ['sgi'],
8649
+ 'image/svg+xml': ['svg', 'svgz'],
8650
+ 'image/t38': ['t38'],
8651
+ 'image/tiff': ['tif', 'tiff'],
8652
+ 'image/tiff-fx': ['tfx'],
8653
+ 'image/webp': ['webp'],
8654
+ 'image/wmf': ['wmf'],
8655
+ 'message/disposition-notification': ['disposition-notification'],
8656
+ 'message/global': ['u8msg'],
8657
+ 'message/global-delivery-status': ['u8dsn'],
8658
+ 'message/global-disposition-notification': ['u8mdn'],
8659
+ 'message/global-headers': ['u8hdr'],
8660
+ 'message/rfc822': ['eml', 'mime', 'mht', 'mhtml'],
8661
+ 'model/3mf': ['3mf'],
8662
+ 'model/gltf+json': ['gltf'],
8663
+ 'model/gltf-binary': ['glb'],
8664
+ 'model/iges': ['igs', 'iges'],
8665
+ 'model/jt': ['jt'],
8666
+ 'model/mesh': ['msh', 'mesh', 'silo'],
8667
+ 'model/mtl': ['mtl'],
8668
+ 'model/obj': ['obj'],
8669
+ 'model/prc': ['prc'],
8670
+ 'model/step': ['step', 'stp', 'stpnc', 'p21', '210'],
8671
+ 'model/step+xml': ['stpx'],
8672
+ 'model/step+zip': ['stpz'],
8673
+ 'model/step-xml+zip': ['stpxz'],
8674
+ 'model/stl': ['stl'],
8675
+ 'model/u3d': ['u3d'],
8676
+ 'model/vrml': ['wrl', 'vrml'],
8677
+ 'model/x3d+binary': ['*x3db', 'x3dbz'],
8678
+ 'model/x3d+fastinfoset': ['x3db'],
8679
+ 'model/x3d+vrml': ['*x3dv', 'x3dvz'],
8680
+ 'model/x3d+xml': ['x3d', 'x3dz'],
8681
+ 'model/x3d-vrml': ['x3dv'],
8682
+ 'text/cache-manifest': ['appcache', 'manifest'],
8683
+ 'text/calendar': ['ics', 'ifb'],
8684
+ 'text/coffeescript': ['coffee', 'litcoffee'],
8685
+ 'text/css': ['css'],
8686
+ 'text/csv': ['csv'],
8687
+ 'text/html': ['html', 'htm', 'shtml'],
8688
+ 'text/jade': ['jade'],
8689
+ 'text/javascript': ['js', 'mjs'],
8690
+ 'text/jsx': ['jsx'],
8691
+ 'text/less': ['less'],
8692
+ 'text/markdown': ['md', 'markdown'],
8693
+ 'text/mathml': ['mml'],
8694
+ 'text/mdx': ['mdx'],
8695
+ 'text/n3': ['n3'],
8696
+ 'text/plain': ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'ini'],
8697
+ 'text/richtext': ['rtx'],
8698
+ 'text/rtf': ['*rtf'],
8699
+ 'text/sgml': ['sgml', 'sgm'],
8700
+ 'text/shex': ['shex'],
8701
+ 'text/slim': ['slim', 'slm'],
8702
+ 'text/spdx': ['spdx'],
8703
+ 'text/stylus': ['stylus', 'styl'],
8704
+ 'text/tab-separated-values': ['tsv'],
8705
+ 'text/troff': ['t', 'tr', 'roff', 'man', 'me', 'ms'],
8706
+ 'text/turtle': ['ttl'],
8707
+ 'text/uri-list': ['uri', 'uris', 'urls'],
8708
+ 'text/vcard': ['vcard'],
8709
+ 'text/vtt': ['vtt'],
8710
+ 'text/wgsl': ['wgsl'],
8711
+ 'text/xml': ['*xml'],
8712
+ 'text/yaml': ['yaml', 'yml'],
8713
+ 'video/3gpp': ['3gp', '3gpp'],
8714
+ 'video/3gpp2': ['3g2'],
8715
+ 'video/h261': ['h261'],
8716
+ 'video/h263': ['h263'],
8717
+ 'video/h264': ['h264'],
8718
+ 'video/iso.segment': ['m4s'],
8719
+ 'video/jpeg': ['jpgv'],
8720
+ 'video/jpm': ['*jpm', '*jpgm'],
8721
+ 'video/mj2': ['mj2', 'mjp2'],
8722
+ 'video/mp2t': ['ts', 'm2t', 'm2ts', 'mts'],
8723
+ 'video/mp4': ['mp4', 'mp4v', 'mpg4'],
8724
+ 'video/mpeg': ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v'],
8725
+ 'video/ogg': ['ogv'],
8726
+ 'video/quicktime': ['qt', 'mov'],
8727
+ 'video/webm': ['webm'],
8728
+ };
8729
+ Object.freeze(types);
8730
+
8731
+ var __classPrivateFieldGet = ({} && {}.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8732
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
8733
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
8734
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
8735
+ };
8736
+ var _Mime_extensionToType, _Mime_typeToExtension, _Mime_typeToExtensions;
8737
+ class Mime {
8738
+ constructor(...args) {
8739
+ _Mime_extensionToType.set(this, new Map());
8740
+ _Mime_typeToExtension.set(this, new Map());
8741
+ _Mime_typeToExtensions.set(this, new Map());
8742
+ for (const arg of args) {
8743
+ this.define(arg);
8744
+ }
8745
+ }
8746
+ define(typeMap, force = false) {
8747
+ for (let [type, extensions] of Object.entries(typeMap)) {
8748
+ type = type.toLowerCase();
8749
+ extensions = extensions.map((ext) => ext.toLowerCase());
8750
+ if (!__classPrivateFieldGet(this, _Mime_typeToExtensions, "f").has(type)) {
8751
+ __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").set(type, new Set());
8752
+ }
8753
+ const allExtensions = __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").get(type);
8754
+ let first = true;
8755
+ for (let extension of extensions) {
8756
+ const starred = extension.startsWith('*');
8757
+ extension = starred ? extension.slice(1) : extension;
8758
+ allExtensions?.add(extension);
8759
+ if (first) {
8760
+ __classPrivateFieldGet(this, _Mime_typeToExtension, "f").set(type, extension);
8761
+ }
8762
+ first = false;
8763
+ if (starred)
8764
+ continue;
8765
+ const currentType = __classPrivateFieldGet(this, _Mime_extensionToType, "f").get(extension);
8766
+ if (currentType && currentType != type && !force) {
8767
+ throw new Error(`"${type} -> ${extension}" conflicts with "${currentType} -> ${extension}". Pass \`force=true\` to override this definition.`);
8768
+ }
8769
+ __classPrivateFieldGet(this, _Mime_extensionToType, "f").set(extension, type);
8770
+ }
8771
+ }
8772
+ return this;
8773
+ }
8774
+ getType(path) {
8775
+ if (typeof path !== 'string')
8776
+ return null;
8777
+ const last = path.replace(/^.*[/\\]/s, '').toLowerCase();
8778
+ const ext = last.replace(/^.*\./s, '').toLowerCase();
8779
+ const hasPath = last.length < path.length;
8780
+ const hasDot = ext.length < last.length - 1;
8781
+ if (!hasDot && hasPath)
8782
+ return null;
8783
+ return __classPrivateFieldGet(this, _Mime_extensionToType, "f").get(ext) ?? null;
8784
+ }
8785
+ getExtension(type) {
8786
+ if (typeof type !== 'string')
8787
+ return null;
8788
+ type = type?.split?.(';')[0];
8789
+ return ((type && __classPrivateFieldGet(this, _Mime_typeToExtension, "f").get(type.trim().toLowerCase())) ?? null);
8790
+ }
8791
+ getAllExtensions(type) {
8792
+ if (typeof type !== 'string')
8793
+ return null;
8794
+ return __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").get(type.toLowerCase()) ?? null;
8795
+ }
8796
+ _freeze() {
8797
+ this.define = () => {
8798
+ throw new Error('define() not allowed for built-in Mime objects. See https://github.com/broofa/mime/blob/main/README.md#custom-mime-instances');
8799
+ };
8800
+ Object.freeze(this);
8801
+ for (const extensions of __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").values()) {
8802
+ Object.freeze(extensions);
8803
+ }
8804
+ return this;
8805
+ }
8806
+ _getTestState() {
8807
+ return {
8808
+ types: __classPrivateFieldGet(this, _Mime_extensionToType, "f"),
8809
+ extensions: __classPrivateFieldGet(this, _Mime_typeToExtension, "f"),
8810
+ };
8811
+ }
8812
+ }
8813
+ _Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _Mime_typeToExtensions = new WeakMap();
8814
+
8815
+ var mime = new Mime(types)._freeze();
8816
+
8526
8817
  class TestRun {
8527
8818
  constructor(vitest) {
8528
8819
  this.vitest = vitest;
@@ -8550,55 +8841,61 @@ class TestRun {
8550
8841
  this.vitest.state.updateUserLog(log);
8551
8842
  await this.vitest.report("onUserConsoleLog", log);
8552
8843
  }
8844
+ async annotate(testId, annotation) {
8845
+ const task = this.vitest.state.idMap.get(testId);
8846
+ const entity = task && this.vitest.state.getReportedEntity(task);
8847
+ assert$1(task && entity, `Entity must be found for task ${task?.name || testId}`);
8848
+ assert$1(entity.type === "test", `Annotation can only be added to a test, instead got ${entity.type}`);
8849
+ await this.resolveTestAttachment(entity, annotation);
8850
+ entity.task.annotations.push(annotation);
8851
+ await this.vitest.report("onTestCaseAnnotate", entity, annotation);
8852
+ return annotation;
8853
+ }
8553
8854
  async updated(update, events) {
8554
8855
  this.vitest.state.updateTasks(update);
8555
- await this.vitest.report("onTaskUpdate", update);
8556
- for (const [id, event] of events) {
8557
- await this.reportEvent(id, event).catch((error) => {
8558
- this.vitest.state.catchError(serializeError(error), "Unhandled Reporter Error");
8559
- });
8560
- }
8856
+ for (const [id, event, data] of events) await this.reportEvent(id, event, data).catch((error) => {
8857
+ this.vitest.state.catchError(serializeError(error), "Unhandled Reporter Error");
8858
+ });
8859
+ // TODO: what is the order or reports here?
8860
+ // "onTaskUpdate" in parallel with others or before all or after all?
8861
+ // TODO: error handling - what happens if custom reporter throws an error?
8862
+ await this.vitest.report("onTaskUpdate", update, events);
8561
8863
  }
8562
8864
  async end(specifications, errors, coverage) {
8865
+ // specification won't have the File task if they were filtered by the --shard command
8563
8866
  const modules = specifications.map((spec) => spec.testModule).filter((s) => s != null);
8564
8867
  const files = modules.map((m) => m.task);
8565
- const state = this.vitest.isCancelling ? "interrupted" : process.exitCode ? "failed" : "passed";
8868
+ const state = this.vitest.isCancelling ? "interrupted" : this.hasFailed(modules) ? "failed" : "passed";
8869
+ if (state !== "passed") process.exitCode = 1;
8566
8870
  try {
8567
8871
  await Promise.all([this.vitest.report("onTestRunEnd", modules, [...errors], state), this.vitest.report("onFinished", files, errors, coverage)]);
8568
8872
  } finally {
8569
- if (coverage) {
8570
- await this.vitest.report("onCoverage", coverage);
8571
- }
8873
+ if (coverage) await this.vitest.report("onCoverage", coverage);
8572
8874
  }
8573
8875
  }
8574
- async reportEvent(id, event) {
8876
+ hasFailed(modules) {
8877
+ if (!modules.length) return !this.vitest.config.passWithNoTests;
8878
+ return modules.some((m) => !m.ok());
8879
+ }
8880
+ async reportEvent(id, event, data) {
8575
8881
  const task = this.vitest.state.idMap.get(id);
8576
8882
  const entity = task && this.vitest.state.getReportedEntity(task);
8577
8883
  assert$1(task && entity, `Entity must be found for task ${task?.name || id}`);
8578
- if (event === "suite-prepare" && entity.type === "suite") {
8579
- return await this.vitest.report("onTestSuiteReady", entity);
8580
- }
8581
- if (event === "suite-prepare" && entity.type === "module") {
8582
- return await this.vitest.report("onTestModuleStart", entity);
8583
- }
8884
+ if (event === "suite-prepare" && entity.type === "suite") return await this.vitest.report("onTestSuiteReady", entity);
8885
+ if (event === "suite-prepare" && entity.type === "module") return await this.vitest.report("onTestModuleStart", entity);
8584
8886
  if (event === "suite-finished") {
8585
8887
  assert$1(entity.type === "suite" || entity.type === "module", "Entity type must be suite or module");
8586
- if (entity.state() === "skipped") {
8587
- await this.reportChildren(entity.children);
8588
- }
8589
- if (entity.type === "module") {
8590
- await this.vitest.report("onTestModuleEnd", entity);
8591
- } else {
8592
- await this.vitest.report("onTestSuiteResult", entity);
8593
- }
8888
+ if (entity.state() === "skipped")
8889
+ // everything inside suite or a module is skipped,
8890
+ // so we won't get any children events
8891
+ // we need to report everything manually
8892
+ await this.reportChildren(entity.children);
8893
+ if (entity.type === "module") await this.vitest.report("onTestModuleEnd", entity);
8894
+ else await this.vitest.report("onTestSuiteResult", entity);
8594
8895
  return;
8595
8896
  }
8596
- if (event === "test-prepare" && entity.type === "test") {
8597
- return await this.vitest.report("onTestCaseReady", entity);
8598
- }
8599
- if (event === "test-finished" && entity.type === "test") {
8600
- return await this.vitest.report("onTestCaseResult", entity);
8601
- }
8897
+ if (event === "test-prepare" && entity.type === "test") return await this.vitest.report("onTestCaseReady", entity);
8898
+ if (event === "test-finished" && entity.type === "test") return await this.vitest.report("onTestCaseResult", entity);
8602
8899
  if (event.startsWith("before-hook") || event.startsWith("after-hook")) {
8603
8900
  const isBefore = event.startsWith("before-hook");
8604
8901
  const hook = entity.type === "test" ? {
@@ -8608,36 +8905,58 @@ class TestRun {
8608
8905
  name: isBefore ? "beforeAll" : "afterAll",
8609
8906
  entity
8610
8907
  };
8611
- if (event.endsWith("-start")) {
8612
- await this.vitest.report("onHookStart", hook);
8613
- } else {
8614
- await this.vitest.report("onHookEnd", hook);
8908
+ if (event.endsWith("-start")) await this.vitest.report("onHookStart", hook);
8909
+ else await this.vitest.report("onHookEnd", hook);
8910
+ // this can only happen in --merge-reports, and annotation is already resolved
8911
+ if (event === "test-annotation") {
8912
+ const annotation = data?.annotation;
8913
+ assert$1(annotation && entity.type === "test");
8914
+ await this.vitest.report("onTestCaseAnnotate", entity, annotation);
8615
8915
  }
8616
8916
  }
8617
8917
  }
8918
+ async resolveTestAttachment(test, annotation) {
8919
+ const project = test.project;
8920
+ const attachment = annotation.attachment;
8921
+ if (!attachment) return attachment;
8922
+ const path = attachment.path;
8923
+ if (path && !path.startsWith("http://") && !path.startsWith("https://")) {
8924
+ const currentPath = resolve(project.config.root, path);
8925
+ const hash = createHash("sha1").update(currentPath).digest("hex");
8926
+ const newPath = resolve(project.config.attachmentsDir, `${sanitizeFilePath(annotation.message)}-${hash}${extname(currentPath)}`);
8927
+ await mkdir(dirname(newPath), { recursive: true });
8928
+ await copyFile(currentPath, newPath);
8929
+ attachment.path = newPath;
8930
+ const contentType = attachment.contentType ?? mime.getType(basename(currentPath));
8931
+ attachment.contentType = contentType || void 0;
8932
+ }
8933
+ return attachment;
8934
+ }
8618
8935
  async reportChildren(children) {
8619
- for (const child of children) {
8620
- if (child.type === "test") {
8621
- await this.vitest.report("onTestCaseReady", child);
8622
- await this.vitest.report("onTestCaseResult", child);
8623
- } else {
8624
- await this.vitest.report("onTestSuiteReady", child);
8625
- await this.reportChildren(child.children);
8626
- await this.vitest.report("onTestSuiteResult", child);
8627
- }
8936
+ for (const child of children) if (child.type === "test") {
8937
+ await this.vitest.report("onTestCaseReady", child);
8938
+ await this.vitest.report("onTestCaseResult", child);
8939
+ } else {
8940
+ await this.vitest.report("onTestSuiteReady", child);
8941
+ await this.reportChildren(child.children);
8942
+ await this.vitest.report("onTestSuiteResult", child);
8628
8943
  }
8629
8944
  }
8630
8945
  }
8946
+ function sanitizeFilePath(s) {
8947
+ // eslint-disable-next-line no-control-regex
8948
+ return s.replace(/[\x00-\x2C\x2E\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, "-");
8949
+ }
8631
8950
 
8632
8951
  class VitestWatcher {
8633
8952
  /**
8634
8953
  * Modules that will be invalidated on the next run.
8635
8954
  */
8636
- invalidates = new Set();
8955
+ invalidates = /* @__PURE__ */ new Set();
8637
8956
  /**
8638
8957
  * Test files that have changed and need to be rerun.
8639
8958
  */
8640
- changedTests = new Set();
8959
+ changedTests = /* @__PURE__ */ new Set();
8641
8960
  _onRerun = [];
8642
8961
  constructor(vitest) {
8643
8962
  this.vitest = vitest;
@@ -8655,9 +8974,7 @@ class VitestWatcher {
8655
8974
  unregisterWatcher = noop;
8656
8975
  registerWatcher() {
8657
8976
  const watcher = this.vitest.vite.watcher;
8658
- if (this.vitest.config.forceRerunTriggers.length) {
8659
- watcher.add(this.vitest.config.forceRerunTriggers);
8660
- }
8977
+ if (this.vitest.config.forceRerunTriggers.length) watcher.add(this.vitest.config.forceRerunTriggers);
8661
8978
  watcher.on("change", this.onChange);
8662
8979
  watcher.on("unlink", this.onUnlink);
8663
8980
  watcher.on("add", this.onAdd);
@@ -8673,9 +8990,7 @@ class VitestWatcher {
8673
8990
  this._onRerun.forEach((cb) => cb(file));
8674
8991
  }
8675
8992
  getTestFilesFromWatcherTrigger(id) {
8676
- if (!this.vitest.config.watchTriggerPatterns) {
8677
- return false;
8678
- }
8993
+ if (!this.vitest.config.watchTriggerPatterns) return false;
8679
8994
  let triggered = false;
8680
8995
  this.vitest.config.watchTriggerPatterns.forEach((definition) => {
8681
8996
  const exec = definition.pattern.exec(id);
@@ -8697,13 +9012,10 @@ class VitestWatcher {
8697
9012
  this.vitest.logger.clearHighlightCache(id);
8698
9013
  this.vitest.invalidateFile(id);
8699
9014
  const testFiles = this.getTestFilesFromWatcherTrigger(id);
8700
- if (testFiles) {
8701
- this.scheduleRerun(id);
8702
- } else {
9015
+ if (testFiles) this.scheduleRerun(id);
9016
+ else {
8703
9017
  const needsRerun = this.handleFileChanged(id);
8704
- if (needsRerun) {
8705
- this.scheduleRerun(id);
8706
- }
9018
+ if (needsRerun) this.scheduleRerun(id);
8707
9019
  }
8708
9020
  };
8709
9021
  onUnlink = (id) => {
@@ -8730,27 +9042,22 @@ class VitestWatcher {
8730
9042
  let fileContent;
8731
9043
  const matchingProjects = [];
8732
9044
  this.vitest.projects.forEach((project) => {
8733
- if (project.matchesTestGlob(id, () => fileContent ??= readFileSync(id, "utf-8"))) {
8734
- matchingProjects.push(project);
8735
- }
9045
+ if (project.matchesTestGlob(id, () => fileContent ??= readFileSync(id, "utf-8"))) matchingProjects.push(project);
8736
9046
  });
8737
9047
  if (matchingProjects.length > 0) {
8738
9048
  this.changedTests.add(id);
8739
9049
  this.scheduleRerun(id);
8740
9050
  } else {
9051
+ // it's possible that file was already there but watcher triggered "add" event instead
8741
9052
  const needsRerun = this.handleFileChanged(id);
8742
- if (needsRerun) {
8743
- this.scheduleRerun(id);
8744
- }
9053
+ if (needsRerun) this.scheduleRerun(id);
8745
9054
  }
8746
9055
  };
8747
9056
  /**
8748
9057
  * @returns A value indicating whether rerun is needed (changedTests was mutated)
8749
9058
  */
8750
9059
  handleFileChanged(filepath) {
8751
- if (this.changedTests.has(filepath) || this.invalidates.has(filepath)) {
8752
- return false;
8753
- }
9060
+ if (this.changedTests.has(filepath) || this.invalidates.has(filepath)) return false;
8754
9061
  if (pm.isMatch(filepath, this.vitest.config.forceRerunTriggers)) {
8755
9062
  this.vitest.state.getFilepaths().forEach((file) => this.changedTests.add(file));
8756
9063
  return true;
@@ -8760,6 +9067,8 @@ class VitestWatcher {
8760
9067
  return moduleGraph.getModulesByFile(filepath)?.size;
8761
9068
  });
8762
9069
  if (!projects.length) {
9070
+ // if there are no modules it's possible that server was restarted
9071
+ // we don't have information about importers anymore, so let's check if the file is a test file at least
8763
9072
  if (this.vitest.state.filesMap.has(filepath) || this.vitest.projects.some((project) => project._isCachedTestFile(filepath))) {
8764
9073
  this.changedTests.add(filepath);
8765
9074
  return true;
@@ -8769,30 +9078,21 @@ class VitestWatcher {
8769
9078
  const files = [];
8770
9079
  for (const project of projects) {
8771
9080
  const mods = project.browser?.vite.moduleGraph.getModulesByFile(filepath) || project.vite.moduleGraph.getModulesByFile(filepath);
8772
- if (!mods || !mods.size) {
8773
- continue;
8774
- }
9081
+ if (!mods || !mods.size) continue;
8775
9082
  this.invalidates.add(filepath);
9083
+ // one of test files that we already run, or one of test files that we can run
8776
9084
  if (this.vitest.state.filesMap.has(filepath) || project._isCachedTestFile(filepath)) {
8777
9085
  this.changedTests.add(filepath);
8778
9086
  files.push(filepath);
8779
9087
  continue;
8780
9088
  }
8781
9089
  let rerun = false;
8782
- for (const mod of mods) {
8783
- mod.importers.forEach((i) => {
8784
- if (!i.file) {
8785
- return;
8786
- }
8787
- const needsRerun = this.handleFileChanged(i.file);
8788
- if (needsRerun) {
8789
- rerun = true;
8790
- }
8791
- });
8792
- }
8793
- if (rerun) {
8794
- files.push(filepath);
8795
- }
9090
+ for (const mod of mods) mod.importers.forEach((i) => {
9091
+ if (!i.file) return;
9092
+ const needsRerun = this.handleFileChanged(i.file);
9093
+ if (needsRerun) rerun = true;
9094
+ });
9095
+ if (rerun) files.push(filepath);
8796
9096
  }
8797
9097
  return !!files.length;
8798
9098
  }
@@ -8844,11 +9144,11 @@ class Vitest {
8844
9144
  resolvedProjects = [];
8845
9145
  /** @internal */ _browserLastPort = defaultBrowserPort;
8846
9146
  /** @internal */ _browserSessions = new BrowserSessions();
8847
- /** @internal */ _options = {};
9147
+ /** @internal */ _cliOptions = {};
8848
9148
  /** @internal */ reporters = [];
8849
- /** @internal */ vitenode = undefined;
8850
- /** @internal */ runner = undefined;
8851
- /** @internal */ _testRun = undefined;
9149
+ /** @internal */ vitenode = void 0;
9150
+ /** @internal */ runner = void 0;
9151
+ /** @internal */ _testRun = void 0;
8852
9152
  isFirstRun = true;
8853
9153
  restartsCount = 0;
8854
9154
  specifications;
@@ -8860,8 +9160,9 @@ class Vitest {
8860
9160
  _cache;
8861
9161
  _snapshot;
8862
9162
  _workspaceConfigPath;
8863
- constructor(mode, options = {}) {
9163
+ constructor(mode, cliOptions, options = {}) {
8864
9164
  this.mode = mode;
9165
+ this._cliOptions = cliOptions;
8865
9166
  this.logger = new Logger(this, options.stdout, options.stderr);
8866
9167
  this.packageInstaller = options.packageInstaller || new VitestPackageInstaller();
8867
9168
  this.specifications = new VitestSpecifications(this);
@@ -8922,25 +9223,24 @@ class Vitest {
8922
9223
  return this._cache;
8923
9224
  }
8924
9225
  /** @deprecated internal */
8925
- setServer(options, server, cliOptions) {
8926
- return this._setServer(options, server, cliOptions);
9226
+ setServer(options, server) {
9227
+ return this._setServer(options, server);
8927
9228
  }
8928
9229
  /** @internal */
8929
- async _setServer(options, server, cliOptions) {
8930
- this._options = options;
9230
+ async _setServer(options, server) {
8931
9231
  this.watcher.unregisterWatcher();
8932
9232
  clearTimeout(this._rerunTimer);
8933
9233
  this.restartsCount += 1;
8934
9234
  this._browserLastPort = defaultBrowserPort;
8935
9235
  this.pool?.close?.();
8936
- this.pool = undefined;
8937
- this.closingPromise = undefined;
9236
+ this.pool = void 0;
9237
+ this.closingPromise = void 0;
8938
9238
  this.projects = [];
8939
9239
  this.resolvedProjects = [];
8940
- this._workspaceConfigPath = undefined;
8941
- this.coverageProvider = undefined;
8942
- this.runningPromise = undefined;
8943
- this.coreWorkspaceProject = undefined;
9240
+ this._workspaceConfigPath = void 0;
9241
+ this.coverageProvider = void 0;
9242
+ this.runningPromise = void 0;
9243
+ this.coreWorkspaceProject = void 0;
8944
9244
  this.specifications.clearCache();
8945
9245
  this._onUserTestsRerun = [];
8946
9246
  this._vite = server;
@@ -8950,9 +9250,7 @@ class Vitest {
8950
9250
  this._cache = new VitestCache(this.version);
8951
9251
  this._snapshot = new SnapshotManager({ ...resolved.snapshotOptions });
8952
9252
  this._testRun = new TestRun(this);
8953
- if (this.config.watch) {
8954
- this.watcher.registerWatcher();
8955
- }
9253
+ if (this.config.watch) this.watcher.registerWatcher();
8956
9254
  this.vitenode = new ViteNodeServer(server, this.config.server);
8957
9255
  const node = this.vitenode;
8958
9256
  this.runner = new ViteNodeRunner({
@@ -8966,6 +9264,7 @@ class Vitest {
8966
9264
  }
8967
9265
  });
8968
9266
  if (this.config.watch) {
9267
+ // hijack server restart
8969
9268
  const serverRestart = server.restart;
8970
9269
  server.restart = async (...args) => {
8971
9270
  await Promise.all(this._onRestartListeners.map((fn) => fn()));
@@ -8973,6 +9272,7 @@ class Vitest {
8973
9272
  await this.close();
8974
9273
  await serverRestart(...args);
8975
9274
  };
9275
+ // since we set `server.hmr: false`, Vite does not auto restart itself
8976
9276
  server.watcher.on("change", async (file) => {
8977
9277
  file = normalize(file);
8978
9278
  const isConfig = file === server.config.configFile || this.projects.some((p) => p.vite.config.configFile === file) || file === this._workspaceConfigPath;
@@ -8988,7 +9288,7 @@ class Vitest {
8988
9288
  try {
8989
9289
  await this.cache.results.readFromCache();
8990
9290
  } catch {}
8991
- const projects = await this.resolveProjects(cliOptions);
9291
+ const projects = await this.resolveProjects(this._cliOptions);
8992
9292
  this.resolvedProjects = projects;
8993
9293
  this.projects = projects;
8994
9294
  await Promise.all(projects.flatMap((project) => {
@@ -8999,26 +9299,17 @@ class Vitest {
8999
9299
  injectTestProjects: this.injectTestProject
9000
9300
  }));
9001
9301
  }));
9002
- if (options.browser?.enabled) {
9302
+ if (this._cliOptions.browser?.enabled) {
9003
9303
  const browserProjects = this.projects.filter((p) => p.config.browser.enabled);
9004
- if (!browserProjects.length) {
9005
- throw new Error(`Vitest received --browser flag, but no project had a browser configuration.`);
9006
- }
9304
+ if (!browserProjects.length) throw new Error(`Vitest received --browser flag, but no project had a browser configuration.`);
9007
9305
  }
9008
9306
  if (!this.projects.length) {
9009
9307
  const filter = toArray(resolved.project).join("\", \"");
9010
- if (filter) {
9011
- throw new Error(`No projects matched the filter "${filter}".`);
9012
- } else {
9013
- throw new Error(`Vitest wasn't able to resolve any project.`);
9014
- }
9015
- }
9016
- if (!this.coreWorkspaceProject) {
9017
- this.coreWorkspaceProject = TestProject._createBasicProject(this);
9018
- }
9019
- if (this.config.testNamePattern) {
9020
- this.configOverride.testNamePattern = this.config.testNamePattern;
9308
+ if (filter) throw new Error(`No projects matched the filter "${filter}".`);
9309
+ else throw new Error(`Vitest wasn't able to resolve any project.`);
9021
9310
  }
9311
+ if (!this.coreWorkspaceProject) this.coreWorkspaceProject = TestProject._createBasicProject(this);
9312
+ if (this.config.testNamePattern) this.configOverride.testNamePattern = this.config.testNamePattern;
9022
9313
  this.reporters = resolved.mode === "benchmark" ? await createBenchmarkReporters(toArray(resolved.benchmark?.reporters), this.runner) : await createReporters(resolved.reporters, this);
9023
9314
  await Promise.all(this._onSetServer.map((fn) => fn()));
9024
9315
  }
@@ -9029,7 +9320,7 @@ class Vitest {
9029
9320
  */
9030
9321
  injectTestProject = async (config) => {
9031
9322
  const currentNames = new Set(this.projects.map((p) => p.name));
9032
- const projects = await resolveProjects(this, this._options, undefined, Array.isArray(config) ? config : [config], currentNames);
9323
+ const projects = await resolveProjects(this, this._cliOptions, void 0, Array.isArray(config) ? config : [config], currentNames);
9033
9324
  this.projects.push(...projects);
9034
9325
  return projects;
9035
9326
  };
@@ -9047,9 +9338,7 @@ class Vitest {
9047
9338
  }
9048
9339
  /** @internal */
9049
9340
  _ensureRootProject() {
9050
- if (this.coreWorkspaceProject) {
9051
- return this.coreWorkspaceProject;
9052
- }
9341
+ if (this.coreWorkspaceProject) return this.coreWorkspaceProject;
9053
9342
  this.coreWorkspaceProject = TestProject._createBasicProject(this);
9054
9343
  return this.coreWorkspaceProject;
9055
9344
  }
@@ -9061,9 +9350,7 @@ class Vitest {
9061
9350
  * Return project that has the root (or "global") config.
9062
9351
  */
9063
9352
  getRootProject() {
9064
- if (!this.coreWorkspaceProject) {
9065
- throw new Error(`Root project is not initialized. This means that the Vite server was not established yet and the the workspace config is not resolved.`);
9066
- }
9353
+ if (!this.coreWorkspaceProject) throw new Error(`Root project is not initialized. This means that the Vite server was not established yet and the the workspace config is not resolved.`);
9067
9354
  return this.coreWorkspaceProject;
9068
9355
  }
9069
9356
  /**
@@ -9076,9 +9363,7 @@ class Vitest {
9076
9363
  }
9077
9364
  getProjectByName(name) {
9078
9365
  const project = this.projects.find((p) => p.name === name) || this.coreWorkspaceProject || this.projects[0];
9079
- if (!project) {
9080
- throw new Error(`Project "${name}" was not found.`);
9081
- }
9366
+ if (!project) throw new Error(`Project "${name}" was not found.`);
9082
9367
  return project;
9083
9368
  }
9084
9369
  /**
@@ -9089,46 +9374,39 @@ class Vitest {
9089
9374
  return this.runner.executeId(moduleId);
9090
9375
  }
9091
9376
  async resolveWorkspaceConfigPath() {
9092
- if (typeof this.config.workspace === "string") {
9093
- return this.config.workspace;
9094
- }
9377
+ if (typeof this.config.workspace === "string") return this.config.workspace;
9095
9378
  const configDir = this.vite.config.configFile ? dirname(this.vite.config.configFile) : this.config.root;
9096
9379
  const rootFiles = await promises.readdir(configDir);
9097
9380
  const workspaceConfigName = workspacesFiles.find((configFile) => {
9098
9381
  return rootFiles.includes(configFile);
9099
9382
  });
9100
- if (!workspaceConfigName) {
9101
- return undefined;
9102
- }
9383
+ if (!workspaceConfigName) return void 0;
9103
9384
  return join(configDir, workspaceConfigName);
9104
9385
  }
9105
9386
  async resolveProjects(cliOptions) {
9106
- const names = new Set();
9387
+ const names = /* @__PURE__ */ new Set();
9107
9388
  if (this.config.projects) {
9108
- if (typeof this.config.workspace !== "undefined") {
9109
- this.logger.warn("Both `config.projects` and `config.workspace` are defined. Ignoring the `workspace` option.");
9110
- }
9111
- return resolveProjects(this, cliOptions, undefined, this.config.projects, names);
9389
+ if (typeof this.config.workspace !== "undefined") this.logger.warn("Both `config.projects` and `config.workspace` are defined. Ignoring the `workspace` option.");
9390
+ return resolveProjects(this, cliOptions, void 0, this.config.projects, names);
9112
9391
  }
9113
9392
  if (Array.isArray(this.config.workspace)) {
9114
9393
  this.logger.deprecate("The `workspace` option is deprecated and will be removed in the next major. To hide this warning, rename `workspace` option to `projects`.");
9115
- return resolveProjects(this, cliOptions, undefined, this.config.workspace, names);
9394
+ return resolveProjects(this, cliOptions, void 0, this.config.workspace, names);
9116
9395
  }
9117
9396
  const workspaceConfigPath = await this.resolveWorkspaceConfigPath();
9118
9397
  this._workspaceConfigPath = workspaceConfigPath;
9398
+ // user doesn't have a workspace config, return default project
9119
9399
  if (!workspaceConfigPath) {
9400
+ // user can filter projects with --project flag, `getDefaultTestProject`
9401
+ // returns the project only if it matches the filter
9120
9402
  const project = getDefaultTestProject(this);
9121
- if (!project) {
9122
- return [];
9123
- }
9403
+ if (!project) return [];
9124
9404
  return resolveBrowserProjects(this, new Set([project.name]), [project]);
9125
9405
  }
9126
9406
  const configFile = this.vite.config.configFile ? resolve(this.vite.config.root, this.vite.config.configFile) : "the root config file";
9127
9407
  this.logger.deprecate(`The workspace file is deprecated and will be removed in the next major. Please, use the \`projects\` field in ${configFile} instead.`);
9128
9408
  const workspaceModule = await this.import(workspaceConfigPath);
9129
- if (!workspaceModule.default || !Array.isArray(workspaceModule.default)) {
9130
- throw new TypeError(`Workspace config file "${workspaceConfigPath}" must export a default array of project paths.`);
9131
- }
9409
+ if (!workspaceModule.default || !Array.isArray(workspaceModule.default)) throw new TypeError(`Workspace config file "${workspaceConfigPath}" must export a default array of project paths.`);
9132
9410
  return resolveProjects(this, cliOptions, workspaceConfigPath, workspaceModule.default, names);
9133
9411
  }
9134
9412
  /**
@@ -9139,9 +9417,7 @@ class Vitest {
9139
9417
  return this.specifications.globTestSpecifications(filters);
9140
9418
  }
9141
9419
  async initCoverageProvider() {
9142
- if (this.coverageProvider !== undefined) {
9143
- return;
9144
- }
9420
+ if (this.coverageProvider !== void 0) return;
9145
9421
  this.coverageProvider = await getCoverageProvider(this.config.coverage, this.runner);
9146
9422
  if (this.coverageProvider) {
9147
9423
  await this.coverageProvider.initialize(this);
@@ -9153,9 +9429,7 @@ class Vitest {
9153
9429
  * Merge reports from multiple runs located in the specified directory (value from `--merge-reports` if not specified).
9154
9430
  */
9155
9431
  async mergeReports(directory) {
9156
- if (this.reporters.some((r) => r instanceof BlobReporter)) {
9157
- throw new Error("Cannot merge reports when `--reporter=blob` is used. Remove blob reporter from the config first.");
9158
- }
9432
+ if (this.reporters.some((r) => r instanceof BlobReporter)) throw new Error("Cannot merge reports when `--reporter=blob` is used. Remove blob reporter from the config first.");
9159
9433
  const { files, errors, coverages, executionTimes } = await readBlobs(this.version, directory || this.config.mergeReports, this.projects);
9160
9434
  this.state.blobs = {
9161
9435
  files,
@@ -9168,17 +9442,12 @@ class Vitest {
9168
9442
  const specifications = [];
9169
9443
  for (const file of files) {
9170
9444
  const project = this.getProjectByName(file.projectName || "");
9171
- const specification = project.createSpecification(file.filepath, undefined, file.pool);
9445
+ const specification = project.createSpecification(file.filepath, void 0, file.pool);
9172
9446
  specifications.push(specification);
9173
9447
  }
9174
9448
  await this.report("onSpecsCollected", specifications.map((spec) => spec.toJSON()));
9175
9449
  await this._testRun.start(specifications).catch(noop);
9176
- for (const file of files) {
9177
- await this._reportFileTask(file);
9178
- }
9179
- if (hasFailed(files)) {
9180
- process.exitCode = 1;
9181
- }
9450
+ for (const file of files) await this._reportFileTask(file);
9182
9451
  this._checkUnhandledErrors(errors);
9183
9452
  await this._testRun.end(specifications, errors).catch(noop);
9184
9453
  await this.initCoverageProvider();
@@ -9195,24 +9464,19 @@ class Vitest {
9195
9464
  await this._testRun.collected(project, [file]).catch(noop);
9196
9465
  const logs = [];
9197
9466
  const { packs, events } = convertTasksToEvents(file, (task) => {
9198
- if (task.logs) {
9199
- logs.push(...task.logs);
9200
- }
9467
+ if (task.logs) logs.push(...task.logs);
9201
9468
  });
9202
9469
  logs.sort((log1, log2) => log1.time - log2.time);
9203
- for (const log of logs) {
9204
- await this._testRun.log(log).catch(noop);
9205
- }
9470
+ for (const log of logs) await this._testRun.log(log).catch(noop);
9206
9471
  await this._testRun.updated(packs, events).catch(noop);
9207
9472
  }
9208
9473
  async collect(filters) {
9209
9474
  const files = await this.specifications.getRelevantTestSpecifications(filters);
9210
- if (!files.length) {
9211
- return {
9212
- testModules: [],
9213
- unhandledErrors: []
9214
- };
9215
- }
9475
+ // if run with --changed, don't exit if no tests are found
9476
+ if (!files.length) return {
9477
+ testModules: [],
9478
+ unhandledErrors: []
9479
+ };
9216
9480
  return this.collectTests(files);
9217
9481
  }
9218
9482
  /** @deprecated use `getRelevantTestSpecifications` instead */
@@ -9241,33 +9505,27 @@ class Vitest {
9241
9505
  } finally {
9242
9506
  await this.report("onInit", this);
9243
9507
  }
9244
- this.filenamePattern = filters && filters?.length > 0 ? filters : undefined;
9508
+ this.filenamePattern = filters && filters?.length > 0 ? filters : void 0;
9245
9509
  const files = await this.specifications.getRelevantTestSpecifications(filters);
9510
+ // if run with --changed, don't exit if no tests are found
9246
9511
  if (!files.length) {
9247
- const throwAnError = !this.config.watch || !(this.config.changed || this.config.related?.length);
9248
9512
  await this._testRun.start([]);
9249
9513
  const coverage = await this.coverageProvider?.generateCoverage?.({ allTestsRun: true });
9250
- if (throwAnError) {
9251
- const exitCode = this.config.passWithNoTests ? 0 : 1;
9252
- process.exitCode = exitCode;
9253
- }
9254
9514
  await this._testRun.end([], [], coverage);
9515
+ // Report coverage for uncovered files
9255
9516
  await this.reportCoverage(coverage, true);
9256
- if (throwAnError) {
9257
- throw new FilesNotFoundError(this.mode);
9258
- }
9517
+ if (!this.config.watch || !(this.config.changed || this.config.related?.length)) throw new FilesNotFoundError(this.mode);
9259
9518
  }
9260
9519
  let testModules = {
9261
9520
  testModules: [],
9262
9521
  unhandledErrors: []
9263
9522
  };
9264
9523
  if (files.length) {
9524
+ // populate once, update cache on watch
9265
9525
  await this.cache.stats.populateStats(this.config.root, files);
9266
9526
  testModules = await this.runFiles(files, true);
9267
9527
  }
9268
- if (this.config.watch) {
9269
- await this.report("onWatcherStart");
9270
- }
9528
+ if (this.config.watch) await this.report("onWatcherStart");
9271
9529
  return testModules;
9272
9530
  }
9273
9531
  /**
@@ -9281,10 +9539,9 @@ class Vitest {
9281
9539
  } finally {
9282
9540
  await this.report("onInit", this);
9283
9541
  }
9542
+ // populate test files cache so watch mode can trigger a file rerun
9284
9543
  await this.globTestSpecifications();
9285
- if (this.config.watch) {
9286
- await this.report("onWatcherStart");
9287
- }
9544
+ if (this.config.watch) await this.report("onWatcherStart");
9288
9545
  }
9289
9546
  /**
9290
9547
  * @deprecated remove when vscode extension supports "getModuleSpecifications"
@@ -9326,7 +9583,7 @@ class Vitest {
9326
9583
  * @param allTestsRun Indicates whether all tests were run. This only matters for coverage.
9327
9584
  */
9328
9585
  async rerunTestSpecifications(specifications, allTestsRun = false) {
9329
- this.configOverride.testNamePattern = undefined;
9586
+ this.configOverride.testNamePattern = void 0;
9330
9587
  const files = specifications.map((spec) => spec.moduleId);
9331
9588
  await Promise.all([this.report("onWatcherRerun", files, "rerun test"), ...this._onUserTestsRerun.map((fn) => fn(specifications))]);
9332
9589
  const result = await this.runTestSpecifications(specifications, allTestsRun);
@@ -9335,21 +9592,19 @@ class Vitest {
9335
9592
  }
9336
9593
  async runFiles(specs, allTestsRun) {
9337
9594
  await this._testRun.start(specs);
9595
+ // previous run
9338
9596
  await this.runningPromise;
9339
9597
  this._onCancelListeners = [];
9340
9598
  this.isCancelling = false;
9599
+ // schedule the new run
9341
9600
  this.runningPromise = (async () => {
9342
9601
  try {
9343
- if (!this.pool) {
9344
- this.pool = createPool(this);
9345
- }
9602
+ if (!this.pool) this.pool = createPool(this);
9346
9603
  const invalidates = Array.from(this.watcher.invalidates);
9347
9604
  this.watcher.invalidates.clear();
9348
9605
  this.snapshot.clear();
9349
9606
  this.state.clearErrors();
9350
- if (!this.isFirstRun && this.config.coverage.cleanOnRerun) {
9351
- await this.coverageProvider?.clean();
9352
- }
9607
+ if (!this.isFirstRun && this.config.coverage.cleanOnRerun) await this.coverageProvider?.clean();
9353
9608
  await this.initializeGlobalSetup(specs);
9354
9609
  try {
9355
9610
  await this.pool.runTests(specs, invalidates);
@@ -9357,9 +9612,6 @@ class Vitest {
9357
9612
  this.state.catchError(err, "Unhandled Error");
9358
9613
  }
9359
9614
  const files = this.state.getFiles();
9360
- if (hasFailed(files)) {
9361
- process.exitCode = 1;
9362
- }
9363
9615
  this.cache.results.updateResults(files);
9364
9616
  try {
9365
9617
  await this.cache.results.writeToCache();
@@ -9369,6 +9621,7 @@ class Vitest {
9369
9621
  unhandledErrors: this.state.getUnhandledErrors()
9370
9622
  };
9371
9623
  } finally {
9624
+ // TODO: wait for coverage only if `onFinished` is defined
9372
9625
  const coverage = await this.coverageProvider?.generateCoverage({ allTestsRun });
9373
9626
  const errors = this.state.getUnhandledErrors();
9374
9627
  this._checkUnhandledErrors(errors);
@@ -9376,10 +9629,11 @@ class Vitest {
9376
9629
  await this.reportCoverage(coverage, allTestsRun);
9377
9630
  }
9378
9631
  })().finally(() => {
9379
- this.runningPromise = undefined;
9632
+ this.runningPromise = void 0;
9380
9633
  this.isFirstRun = false;
9634
+ // all subsequent runs will treat this as a fresh run
9381
9635
  this.config.changed = false;
9382
- this.config.related = undefined;
9636
+ this.config.related = void 0;
9383
9637
  });
9384
9638
  return await this.runningPromise;
9385
9639
  }
@@ -9390,13 +9644,13 @@ class Vitest {
9390
9644
  async collectTests(specifications) {
9391
9645
  const filepaths = specifications.map((spec) => spec.moduleId);
9392
9646
  this.state.collectPaths(filepaths);
9647
+ // previous run
9393
9648
  await this.runningPromise;
9394
9649
  this._onCancelListeners = [];
9395
9650
  this.isCancelling = false;
9651
+ // schedule the new run
9396
9652
  this.runningPromise = (async () => {
9397
- if (!this.pool) {
9398
- this.pool = createPool(this);
9399
- }
9653
+ if (!this.pool) this.pool = createPool(this);
9400
9654
  const invalidates = Array.from(this.watcher.invalidates);
9401
9655
  this.watcher.invalidates.clear();
9402
9656
  this.snapshot.clear();
@@ -9408,17 +9662,18 @@ class Vitest {
9408
9662
  this.state.catchError(err, "Unhandled Error");
9409
9663
  }
9410
9664
  const files = this.state.getFiles();
9411
- if (hasFailed(files)) {
9412
- process.exitCode = 1;
9413
- }
9665
+ // can only happen if there was a syntax error in describe block
9666
+ // or there was an error importing a file
9667
+ if (hasFailed(files)) process.exitCode = 1;
9414
9668
  return {
9415
9669
  testModules: this.state.getTestModules(),
9416
9670
  unhandledErrors: this.state.getUnhandledErrors()
9417
9671
  };
9418
9672
  })().finally(() => {
9419
- this.runningPromise = undefined;
9673
+ this.runningPromise = void 0;
9674
+ // all subsequent runs will treat this as a fresh run
9420
9675
  this.config.changed = false;
9421
- this.config.related = undefined;
9676
+ this.config.related = void 0;
9422
9677
  });
9423
9678
  return await this.runningPromise;
9424
9679
  }
@@ -9437,18 +9692,12 @@ class Vitest {
9437
9692
  async initializeGlobalSetup(paths) {
9438
9693
  const projects = new Set(paths.map((spec) => spec.project));
9439
9694
  const coreProject = this.getRootProject();
9440
- if (!projects.has(coreProject)) {
9441
- projects.add(coreProject);
9442
- }
9443
- for (const project of projects) {
9444
- await project._initializeGlobalSetup();
9445
- }
9695
+ if (!projects.has(coreProject)) projects.add(coreProject);
9696
+ for (const project of projects) await project._initializeGlobalSetup();
9446
9697
  }
9447
9698
  /** @internal */
9448
9699
  async rerunFiles(files = this.state.getFilepaths(), trigger, allTestsRun = true, resetTestNamePattern = false) {
9449
- if (resetTestNamePattern) {
9450
- this.configOverride.testNamePattern = undefined;
9451
- }
9700
+ if (resetTestNamePattern) this.configOverride.testNamePattern = void 0;
9452
9701
  if (this.filenamePattern) {
9453
9702
  const filteredFiles = await this.globTestSpecifications(this.filenamePattern);
9454
9703
  files = files.filter((file) => filteredFiles.some((f) => f.moduleId === file));
@@ -9462,37 +9711,30 @@ class Vitest {
9462
9711
  /** @internal */
9463
9712
  async rerunTask(id) {
9464
9713
  const task = this.state.idMap.get(id);
9465
- if (!task) {
9466
- throw new Error(`Task ${id} was not found`);
9467
- }
9714
+ if (!task) throw new Error(`Task ${id} was not found`);
9468
9715
  const taskNamePattern = task.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9469
9716
  await this.changeNamePattern(taskNamePattern, [task.file.filepath], "tasks" in task ? "rerun suite" : "rerun test");
9470
9717
  }
9471
9718
  /** @internal */
9472
9719
  async changeProjectName(pattern) {
9473
- if (pattern === "") {
9474
- this.configOverride.project = undefined;
9475
- } else {
9476
- this.configOverride.project = [pattern];
9477
- }
9720
+ if (pattern === "") this.configOverride.project = void 0;
9721
+ else this.configOverride.project = [pattern];
9478
9722
  await this.vite.restart();
9479
9723
  }
9480
9724
  /** @internal */
9481
9725
  async changeNamePattern(pattern, files = this.state.getFilepaths(), trigger) {
9482
- if (pattern === "") {
9483
- this.filenamePattern = undefined;
9484
- }
9485
- const testNamePattern = pattern ? new RegExp(pattern) : undefined;
9726
+ // Empty test name pattern should reset filename pattern as well
9727
+ if (pattern === "") this.filenamePattern = void 0;
9728
+ const testNamePattern = pattern ? new RegExp(pattern) : void 0;
9486
9729
  this.configOverride.testNamePattern = testNamePattern;
9487
- if (testNamePattern) {
9488
- files = files.filter((filepath) => {
9489
- const files = this.state.getFiles([filepath]);
9490
- return !files.length || files.some((file) => {
9491
- const tasks = getTasks(file);
9492
- return !tasks.length || tasks.some((task) => testNamePattern.test(task.name));
9493
- });
9730
+ // filter only test files that have tests matching the pattern
9731
+ if (testNamePattern) files = files.filter((filepath) => {
9732
+ const files = this.state.getFiles([filepath]);
9733
+ return !files.length || files.some((file) => {
9734
+ const tasks = getTasks(file);
9735
+ return !tasks.length || tasks.some((task) => testNamePattern.test(task.name));
9494
9736
  });
9495
- }
9737
+ });
9496
9738
  await this.rerunFiles(files, trigger, pattern === "");
9497
9739
  }
9498
9740
  /** @internal */
@@ -9510,6 +9752,7 @@ class Vitest {
9510
9752
  * @param files The list of files on the file system
9511
9753
  */
9512
9754
  async updateSnapshot(files) {
9755
+ // default to failed files
9513
9756
  files = files || [...this.state.getFailedFilepaths(), ...this.snapshot.summary.uncheckedKeysByFile.map((s) => s.filePath)];
9514
9757
  this.enableSnapshotUpdate();
9515
9758
  try {
@@ -9544,52 +9787,46 @@ class Vitest {
9544
9787
  * This method doesn't run any tests.
9545
9788
  */
9546
9789
  setGlobalTestNamePattern(pattern) {
9547
- if (pattern instanceof RegExp) {
9548
- this.configOverride.testNamePattern = pattern;
9549
- } else {
9550
- this.configOverride.testNamePattern = pattern ? new RegExp(pattern) : undefined;
9551
- }
9790
+ if (pattern instanceof RegExp) this.configOverride.testNamePattern = pattern;
9791
+ else this.configOverride.testNamePattern = pattern ? new RegExp(pattern) : void 0;
9552
9792
  }
9553
9793
  /**
9554
9794
  * Resets the global test name pattern. This method doesn't run any tests.
9555
9795
  */
9556
9796
  resetGlobalTestNamePattern() {
9557
- this.configOverride.testNamePattern = undefined;
9797
+ this.configOverride.testNamePattern = void 0;
9558
9798
  }
9559
9799
  _rerunTimer;
9800
+ // we can't use a single `triggerId` yet because vscode extension relies on this
9560
9801
  async scheduleRerun(triggerId) {
9561
9802
  const currentCount = this.restartsCount;
9562
9803
  clearTimeout(this._rerunTimer);
9563
9804
  await this.runningPromise;
9564
9805
  clearTimeout(this._rerunTimer);
9565
- if (this.restartsCount !== currentCount) {
9566
- return;
9567
- }
9806
+ // server restarted
9807
+ if (this.restartsCount !== currentCount) return;
9568
9808
  this._rerunTimer = setTimeout(async () => {
9569
9809
  if (this.watcher.changedTests.size === 0) {
9570
9810
  this.watcher.invalidates.clear();
9571
9811
  return;
9572
9812
  }
9573
- if (this.restartsCount !== currentCount) {
9574
- return;
9575
- }
9813
+ // server restarted
9814
+ if (this.restartsCount !== currentCount) return;
9576
9815
  this.isFirstRun = false;
9577
9816
  this.snapshot.clear();
9578
9817
  let files = Array.from(this.watcher.changedTests);
9579
9818
  if (this.filenamePattern) {
9580
9819
  const filteredFiles = await this.globTestSpecifications(this.filenamePattern);
9581
9820
  files = files.filter((file) => filteredFiles.some((f) => f.moduleId === file));
9582
- if (files.length === 0) {
9583
- return;
9584
- }
9821
+ // A file that does not match the current filename pattern was changed
9822
+ if (files.length === 0) return;
9585
9823
  }
9586
9824
  this.watcher.changedTests.clear();
9587
9825
  const triggerIds = new Set(triggerId.map((id) => relative(this.config.root, id)));
9588
9826
  const triggerLabel = Array.from(triggerIds).join(", ");
9827
+ // get file specifications and filter them if needed
9589
9828
  const specifications = files.flatMap((file) => this.getModuleSpecifications(file)).filter((specification) => {
9590
- if (this._onFilterWatchedSpecification.length === 0) {
9591
- return true;
9592
- }
9829
+ if (this._onFilterWatchedSpecification.length === 0) return true;
9593
9830
  return this._onFilterWatchedSpecification.every((fn) => fn(specification));
9594
9831
  });
9595
9832
  await Promise.all([this.report("onWatcherRerun", files, triggerLabel), ...this._onUserTestsRerun.map((fn) => fn(specifications))]);
@@ -9616,24 +9853,17 @@ class Vitest {
9616
9853
  }
9617
9854
  /** @internal */
9618
9855
  _checkUnhandledErrors(errors) {
9619
- if (errors.length && !this.config.dangerouslyIgnoreUnhandledErrors) {
9620
- process.exitCode = 1;
9621
- }
9856
+ if (errors.length && !this.config.dangerouslyIgnoreUnhandledErrors) process.exitCode = 1;
9622
9857
  }
9623
9858
  async reportCoverage(coverage, allTestsRun) {
9624
9859
  if (this.state.getCountOfFailedTests() > 0) {
9625
9860
  await this.coverageProvider?.onTestFailure?.();
9626
- if (!this.config.coverage.reportOnFailure) {
9627
- return;
9628
- }
9861
+ if (!this.config.coverage.reportOnFailure) return;
9629
9862
  }
9630
9863
  if (this.coverageProvider) {
9631
9864
  await this.coverageProvider.reportCoverage(coverage, { allTestsRun });
9632
- for (const reporter of this.reporters) {
9633
- if (reporter instanceof WebSocketReporter) {
9634
- reporter.onFinishedReportCoverage();
9635
- }
9636
- }
9865
+ // notify coverage iframe reload
9866
+ for (const reporter of this.reporters) if (reporter instanceof WebSocketReporter) reporter.onFinishedReportCoverage();
9637
9867
  }
9638
9868
  }
9639
9869
  /**
@@ -9641,35 +9871,26 @@ class Vitest {
9641
9871
  * This can only be called once; the closing promise is cached until the server restarts.
9642
9872
  */
9643
9873
  async close() {
9644
- if (!this.closingPromise) {
9645
- this.closingPromise = (async () => {
9646
- const teardownProjects = [...this.projects];
9647
- if (this.coreWorkspaceProject && !teardownProjects.includes(this.coreWorkspaceProject)) {
9648
- teardownProjects.push(this.coreWorkspaceProject);
9649
- }
9650
- for (const project of teardownProjects.reverse()) {
9651
- await project._teardownGlobalSetup();
9652
- }
9653
- const closePromises = this.projects.map((w) => w.close());
9654
- if (this.coreWorkspaceProject && !this.projects.includes(this.coreWorkspaceProject)) {
9655
- closePromises.push(this.coreWorkspaceProject.close().then(() => this._vite = undefined));
9656
- }
9657
- if (this.pool) {
9658
- closePromises.push((async () => {
9659
- await this.pool?.close?.();
9660
- this.pool = undefined;
9661
- })());
9662
- }
9663
- closePromises.push(...this._onClose.map((fn) => fn()));
9664
- return Promise.allSettled(closePromises).then((results) => {
9665
- results.forEach((r) => {
9666
- if (r.status === "rejected") {
9667
- this.logger.error("error during close", r.reason);
9668
- }
9669
- });
9874
+ if (!this.closingPromise) this.closingPromise = (async () => {
9875
+ const teardownProjects = [...this.projects];
9876
+ if (this.coreWorkspaceProject && !teardownProjects.includes(this.coreWorkspaceProject)) teardownProjects.push(this.coreWorkspaceProject);
9877
+ // do teardown before closing the server
9878
+ for (const project of teardownProjects.reverse()) await project._teardownGlobalSetup();
9879
+ const closePromises = this.projects.map((w) => w.close());
9880
+ // close the core workspace server only once
9881
+ // it's possible that it's not initialized at all because it's not running any tests
9882
+ if (this.coreWorkspaceProject && !this.projects.includes(this.coreWorkspaceProject)) closePromises.push(this.coreWorkspaceProject.close().then(() => this._vite = void 0));
9883
+ if (this.pool) closePromises.push((async () => {
9884
+ await this.pool?.close?.();
9885
+ this.pool = void 0;
9886
+ })());
9887
+ closePromises.push(...this._onClose.map((fn) => fn()));
9888
+ return Promise.allSettled(closePromises).then((results) => {
9889
+ results.forEach((r) => {
9890
+ if (r.status === "rejected") this.logger.error("error during close", r.reason);
9670
9891
  });
9671
- })();
9672
- }
9892
+ });
9893
+ })();
9673
9894
  return this.closingPromise;
9674
9895
  }
9675
9896
  /**
@@ -9683,31 +9904,23 @@ class Vitest {
9683
9904
  this.state.getProcessTimeoutCauses().forEach((cause) => console.warn(cause));
9684
9905
  if (!this.pool) {
9685
9906
  const runningServers = [this._vite, ...this.projects.map((p) => p._vite)].filter(Boolean).length;
9686
- if (runningServers === 1) {
9687
- console.warn("Tests closed successfully but something prevents Vite server from exiting");
9688
- } else if (runningServers > 1) {
9689
- console.warn(`Tests closed successfully but something prevents ${runningServers} Vite servers from exiting`);
9690
- } else {
9691
- console.warn("Tests closed successfully but something prevents the main process from exiting");
9692
- }
9693
- if (!this.reporters.some((r) => r instanceof HangingProcessReporter)) {
9694
- console.warn("You can try to identify the cause by enabling \"hanging-process\" reporter. See https://vitest.dev/config/#reporters");
9695
- }
9907
+ if (runningServers === 1) console.warn("Tests closed successfully but something prevents Vite server from exiting");
9908
+ else if (runningServers > 1) console.warn(`Tests closed successfully but something prevents ${runningServers} Vite servers from exiting`);
9909
+ else console.warn("Tests closed successfully but something prevents the main process from exiting");
9910
+ if (!this.reporters.some((r) => r instanceof HangingProcessReporter)) console.warn("You can try to identify the cause by enabling \"hanging-process\" reporter. See https://vitest.dev/config/#reporters");
9696
9911
  }
9697
9912
  process.exit();
9698
9913
  });
9699
9914
  }, this.config.teardownTimeout).unref();
9700
9915
  await this.close();
9701
- if (force) {
9702
- process.exit();
9703
- }
9916
+ if (force) process.exit();
9704
9917
  }
9705
9918
  /** @internal */
9706
9919
  async report(name, ...args) {
9707
9920
  await Promise.all(this.reporters.map((r) => r[name]?.(
9708
9921
  // @ts-expect-error let me go
9709
9922
  ...args
9710
- )));
9923
+ )));
9711
9924
  }
9712
9925
  /** @internal */
9713
9926
  async _globTestFilepaths() {
@@ -9730,6 +9943,7 @@ class Vitest {
9730
9943
  getModuleProjects(filepath) {
9731
9944
  return this.projects.filter((project) => {
9732
9945
  return project.getModulesByFilepath(filepath).size;
9946
+ // TODO: reevaluate || project.browser?.moduleGraph.getModulesByFile(id)?.size
9733
9947
  });
9734
9948
  }
9735
9949
  /**
@@ -9780,10 +9994,9 @@ class Vitest {
9780
9994
  * Check if the project with a given name should be included.
9781
9995
  */
9782
9996
  matchesProjectFilter(name) {
9783
- const projects = this._config?.project || this._options?.project;
9784
- if (!projects || !projects.length) {
9785
- return true;
9786
- }
9997
+ const projects = this._config?.project || this._cliOptions?.project;
9998
+ // no filters applied, any project can be included
9999
+ if (!projects || !projects.length) return true;
9787
10000
  return toArray(projects).some((project) => {
9788
10001
  const regexp = wildcardPatternToRegExp(project);
9789
10002
  return regexp.test(name);
@@ -9791,16 +10004,14 @@ class Vitest {
9791
10004
  }
9792
10005
  }
9793
10006
  function assert(condition, property, name = property) {
9794
- if (!condition) {
9795
- throw new Error(`The ${name} was not set. It means that \`vitest.${property}\` was called before the Vite server was established. Await the Vitest promise before accessing \`vitest.${property}\`.`);
9796
- }
10007
+ if (!condition) throw new Error(`The ${name} was not set. It means that \`vitest.${property}\` was called before the Vite server was established. Await the Vitest promise before accessing \`vitest.${property}\`.`);
9797
10008
  }
9798
10009
 
9799
- async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
10010
+ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(options))) {
9800
10011
  const userConfig = deepMerge({}, options);
9801
10012
  async function UIPlugin() {
9802
- await ctx.packageInstaller.ensureInstalled("@vitest/ui", options.root || process.cwd(), ctx.version);
9803
- return (await import('@vitest/ui')).default(ctx);
10013
+ await vitest.packageInstaller.ensureInstalled("@vitest/ui", options.root || process.cwd(), vitest.version);
10014
+ return (await import('@vitest/ui')).default(vitest);
9804
10015
  }
9805
10016
  return [
9806
10017
  {
@@ -9810,17 +10021,21 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9810
10021
  this.meta.watchMode = false;
9811
10022
  },
9812
10023
  async config(viteConfig) {
9813
- if (options.watch) {
9814
- options = deepMerge({}, userConfig);
9815
- }
10024
+ if (options.watch)
10025
+ // Earlier runs have overwritten values of the `options`.
10026
+ // Reset it back to initial user config before setting up the server again.
10027
+ options = deepMerge({}, userConfig);
10028
+ // preliminary merge of options to be able to create server options for vite
10029
+ // however to allow vitest plugins to modify vitest config values
10030
+ // this is repeated in configResolved where the config is final
9816
10031
  const testConfig = deepMerge({}, configDefaults, removeUndefinedValues(viteConfig.test ?? {}), options);
9817
10032
  testConfig.api = resolveApiServerConfig(testConfig, defaultPort);
10033
+ // store defines for globalThis to make them
10034
+ // reassignable when running in worker in src/runtime/setup.ts
9818
10035
  const defines = deleteDefineConfig(viteConfig);
9819
10036
  options.defines = defines;
9820
10037
  let open = false;
9821
- if (testConfig.ui && testConfig.open) {
9822
- open = testConfig.uiBase ?? "/__vitest__/";
9823
- }
10038
+ if (testConfig.ui && testConfig.open) open = testConfig.uiBase ?? "/__vitest__/";
9824
10039
  const resolveOptions = getDefaultResolveOptions();
9825
10040
  const config = {
9826
10041
  root: viteConfig.test?.root || options.root,
@@ -9838,7 +10053,7 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9838
10053
  ...testConfig.api,
9839
10054
  open,
9840
10055
  hmr: false,
9841
- ws: testConfig.api?.middlewareMode ? false : undefined,
10056
+ ws: testConfig.api?.middlewareMode ? false : void 0,
9842
10057
  preTransformRequests: false,
9843
10058
  fs: { allow: resolveFsAllow(options.root || process.cwd(), testConfig.config) }
9844
10059
  },
@@ -9856,95 +10071,81 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9856
10071
  deps: testConfig.deps ?? viteConfig.test?.deps
9857
10072
  }
9858
10073
  };
9859
- if (ctx.configOverride.project) {
9860
- options.project = ctx.configOverride.project;
9861
- }
9862
- config.customLogger = createViteLogger(ctx.logger, viteConfig.logLevel || "warn", { allowClearScreen: false });
10074
+ // inherit so it's available in VitestOptimizer
10075
+ // I cannot wait to rewrite all of this in Vitest 4
10076
+ if (options.cache != null) config.test.cache = options.cache;
10077
+ if (vitest.configOverride.project)
10078
+ // project filter was set by the user, so we need to filter the project
10079
+ options.project = vitest.configOverride.project;
10080
+ config.customLogger = createViteLogger(vitest.logger, viteConfig.logLevel || "warn", { allowClearScreen: false });
9863
10081
  config.customLogger = silenceImportViteIgnoreWarning(config.customLogger);
10082
+ // we want inline dependencies to be resolved by analyser plugin so module graph is populated correctly
9864
10083
  if (viteConfig.ssr?.noExternal !== true) {
9865
10084
  const inline = testConfig.server?.deps?.inline;
9866
- if (inline === true) {
9867
- config.ssr = { noExternal: true };
9868
- } else {
10085
+ if (inline === true) config.ssr = { noExternal: true };
10086
+ else {
9869
10087
  const noExternal = viteConfig.ssr?.noExternal;
9870
- const noExternalArray = typeof noExternal !== "undefined" ? toArray(noExternal) : undefined;
10088
+ const noExternalArray = typeof noExternal !== "undefined" ? toArray(noExternal) : void 0;
10089
+ // filter the same packages
9871
10090
  const uniqueInline = inline && noExternalArray ? inline.filter((dep) => !noExternalArray.includes(dep)) : inline;
9872
10091
  config.ssr = { noExternal: uniqueInline };
9873
10092
  }
9874
10093
  }
9875
- if (process.platform === "darwin" && process.env.VITE_TEST_WATCHER_DEBUG) {
9876
- const watch = config.server.watch;
9877
- if (watch) {
9878
- watch.useFsEvents = false;
9879
- watch.usePolling = false;
9880
- }
9881
- }
10094
+ // chokidar fsevents is unstable on macos when emitting "ready" event
10095
+ if (process.platform === "darwin" && false);
9882
10096
  const classNameStrategy = typeof testConfig.css !== "boolean" && testConfig.css?.modules?.classNameStrategy || "stable";
9883
10097
  if (classNameStrategy !== "scoped") {
9884
10098
  config.css ??= {};
9885
10099
  config.css.modules ??= {};
9886
- if (config.css.modules) {
9887
- config.css.modules.generateScopedName = (name, filename) => {
9888
- const root = ctx.config.root || options.root || process.cwd();
9889
- return generateScopedClassName(classNameStrategy, name, relative(root, filename));
9890
- };
9891
- }
10100
+ if (config.css.modules) config.css.modules.generateScopedName = (name, filename) => {
10101
+ const root = vitest.config.root || options.root || process.cwd();
10102
+ return generateScopedClassName(classNameStrategy, name, relative(root, filename));
10103
+ };
9892
10104
  }
9893
10105
  return config;
9894
10106
  },
9895
10107
  async configResolved(viteConfig) {
9896
10108
  const viteConfigTest = viteConfig.test || {};
9897
- if (viteConfigTest.watch === false) {
9898
- viteConfigTest.run = true;
9899
- }
9900
- if ("alias" in viteConfigTest) {
9901
- delete viteConfigTest.alias;
9902
- }
10109
+ if (viteConfigTest.watch === false) viteConfigTest.run = true;
10110
+ if ("alias" in viteConfigTest) delete viteConfigTest.alias;
10111
+ // viteConfig.test is final now, merge it for real
9903
10112
  options = deepMerge({}, configDefaults, viteConfigTest, options);
9904
10113
  options.api = resolveApiServerConfig(options, defaultPort);
10114
+ // we replace every "import.meta.env" with "process.env"
10115
+ // to allow reassigning, so we need to put all envs on process.env
9905
10116
  const { PROD, DEV,...envs } = viteConfig.env;
10117
+ // process.env can have only string values and will cast string on it if we pass other type,
10118
+ // so we are making them truthy
9906
10119
  process.env.PROD ??= PROD ? "1" : "";
9907
10120
  process.env.DEV ??= DEV ? "1" : "";
9908
- for (const name in envs) {
9909
- process.env[name] ??= envs[name];
9910
- }
9911
- if (!options.watch) {
9912
- viteConfig.server.watch = null;
9913
- }
10121
+ for (const name in envs) process.env[name] ??= envs[name];
10122
+ // don't watch files in run mode
10123
+ if (!options.watch) viteConfig.server.watch = null;
9914
10124
  Object.defineProperty(viteConfig, "_vitest", {
9915
10125
  value: options,
9916
10126
  enumerable: false,
9917
10127
  configurable: true
9918
10128
  });
9919
10129
  const originalName = options.name;
9920
- if (options.browser?.enabled && options.browser?.instances) {
9921
- options.browser.instances.forEach((instance) => {
9922
- instance.name ??= originalName ? `${originalName} (${instance.browser})` : instance.browser;
9923
- });
9924
- }
10130
+ if (options.browser?.instances) options.browser.instances.forEach((instance) => {
10131
+ instance.name ??= originalName ? `${originalName} (${instance.browser})` : instance.browser;
10132
+ });
9925
10133
  },
9926
10134
  configureServer: {
9927
10135
  order: "post",
9928
10136
  async handler(server) {
9929
- if (options.watch && process.env.VITE_TEST_WATCHER_DEBUG) {
9930
- server.watcher.on("ready", () => {
9931
- console.log("[debug] watcher is ready");
9932
- });
9933
- }
9934
- await ctx._setServer(options, server, userConfig);
9935
- if (options.api && options.watch) {
9936
- (await Promise.resolve().then(function () { return setup$1; })).setup(ctx);
9937
- }
9938
- if (!options.watch) {
9939
- await server.watcher.close();
9940
- }
10137
+ if (options.watch && false);
10138
+ await vitest._setServer(options, server);
10139
+ if (options.api && options.watch) (await Promise.resolve().then(function () { return setup$1; })).setup(vitest);
10140
+ // #415, in run mode we don't need the watcher, close it would improve the performance
10141
+ if (!options.watch) await server.watcher.close();
9941
10142
  }
9942
10143
  }
9943
10144
  },
9944
10145
  SsrReplacerPlugin(),
9945
- ...CSSEnablerPlugin(ctx),
9946
- CoverageTransform(ctx),
9947
- VitestCoreResolver(ctx),
10146
+ ...CSSEnablerPlugin(vitest),
10147
+ CoverageTransform(vitest),
10148
+ VitestCoreResolver(vitest),
9948
10149
  options.ui ? await UIPlugin() : null,
9949
10150
  ...MocksPlugins(),
9950
10151
  VitestOptimizer(),
@@ -9952,29 +10153,24 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
9952
10153
  ].filter(notNullish);
9953
10154
  }
9954
10155
  function removeUndefinedValues(obj) {
9955
- for (const key in Object.keys(obj)) {
9956
- if (obj[key] === undefined) {
9957
- delete obj[key];
9958
- }
9959
- }
10156
+ for (const key in Object.keys(obj)) if (obj[key] === void 0) delete obj[key];
9960
10157
  return obj;
9961
10158
  }
9962
10159
 
9963
10160
  async function createVitest(mode, options, viteOverrides = {}, vitestOptions = {}) {
9964
- const ctx = new Vitest(mode, vitestOptions);
10161
+ const ctx = new Vitest(mode, deepClone(options), vitestOptions);
9965
10162
  const root = slash(resolve$1(options.root || process.cwd()));
9966
10163
  const configPath = options.config === false ? false : options.config ? resolve$1(root, options.config) : await findUp(configFiles, { cwd: root });
9967
10164
  options.config = configPath;
10165
+ const { browser: _removeBrowser,...restOptions } = options;
9968
10166
  const config = {
9969
10167
  configFile: configPath,
9970
10168
  configLoader: options.configLoader,
9971
10169
  mode: options.mode || mode,
9972
- plugins: await VitestPlugin(options, ctx)
10170
+ plugins: await VitestPlugin(restOptions, ctx)
9973
10171
  };
9974
10172
  const server = await createViteServer(mergeConfig(config, mergeConfig(viteOverrides, { root: options.root })));
9975
- if (ctx.config.api?.port) {
9976
- await server.listen();
9977
- }
10173
+ if (ctx.config.api?.port) await server.listen();
9978
10174
  return ctx;
9979
10175
  }
9980
10176
 
@@ -9983,7 +10179,7 @@ const SELECTION_MAX_INDEX = 7;
9983
10179
  const ESC = "\x1B[";
9984
10180
  class WatchFilter {
9985
10181
  filterRL;
9986
- currentKeyword = undefined;
10182
+ currentKeyword = void 0;
9987
10183
  message;
9988
10184
  results = [];
9989
10185
  selectionIndex = -1;
@@ -9999,9 +10195,7 @@ class WatchFilter {
9999
10195
  escapeCodeTimeout: 50
10000
10196
  });
10001
10197
  readline.emitKeypressEvents(this.stdin, this.filterRL);
10002
- if (this.stdin.isTTY) {
10003
- this.stdin.setRawMode(true);
10004
- }
10198
+ if (this.stdin.isTTY) this.stdin.setRawMode(true);
10005
10199
  }
10006
10200
  async filter(filterFunc) {
10007
10201
  this.write(this.promptLine());
@@ -10020,57 +10214,41 @@ class WatchFilter {
10020
10214
  return async (str, key) => {
10021
10215
  switch (true) {
10022
10216
  case key.sequence === "":
10023
- if (this.currentKeyword && this.currentKeyword?.length > 1) {
10024
- this.currentKeyword = this.currentKeyword?.slice(0, -1);
10025
- } else {
10026
- this.currentKeyword = undefined;
10027
- }
10217
+ if (this.currentKeyword && this.currentKeyword?.length > 1) this.currentKeyword = this.currentKeyword?.slice(0, -1);
10218
+ else this.currentKeyword = void 0;
10028
10219
  break;
10029
10220
  case key?.ctrl && key?.name === "c":
10030
10221
  case key?.name === "escape":
10031
10222
  this.write(`${ESC}1G${ESC}0J`);
10032
- onSubmit(undefined);
10223
+ onSubmit(void 0);
10033
10224
  return;
10034
10225
  case key?.name === "enter":
10035
10226
  case key?.name === "return":
10036
10227
  onSubmit(this.results[this.selectionIndex] || this.currentKeyword || "");
10037
- this.currentKeyword = undefined;
10228
+ this.currentKeyword = void 0;
10038
10229
  break;
10039
10230
  case key?.name === "up":
10040
- if (this.selectionIndex && this.selectionIndex > 0) {
10041
- this.selectionIndex--;
10042
- } else {
10043
- this.selectionIndex = -1;
10044
- }
10231
+ if (this.selectionIndex && this.selectionIndex > 0) this.selectionIndex--;
10232
+ else this.selectionIndex = -1;
10045
10233
  break;
10046
10234
  case key?.name === "down":
10047
- if (this.selectionIndex < this.results.length - 1) {
10048
- this.selectionIndex++;
10049
- } else if (this.selectionIndex >= this.results.length - 1) {
10050
- this.selectionIndex = this.results.length - 1;
10051
- }
10235
+ if (this.selectionIndex < this.results.length - 1) this.selectionIndex++;
10236
+ else if (this.selectionIndex >= this.results.length - 1) this.selectionIndex = this.results.length - 1;
10052
10237
  break;
10053
10238
  case !key?.ctrl && !key?.meta:
10054
- if (this.currentKeyword === undefined) {
10055
- this.currentKeyword = str;
10056
- } else {
10057
- this.currentKeyword += str || "";
10058
- }
10239
+ if (this.currentKeyword === void 0) this.currentKeyword = str;
10240
+ else this.currentKeyword += str || "";
10059
10241
  break;
10060
10242
  }
10061
- if (this.currentKeyword) {
10062
- this.results = await filterFunc(this.currentKeyword);
10063
- }
10243
+ if (this.currentKeyword) this.results = await filterFunc(this.currentKeyword);
10064
10244
  this.render();
10065
10245
  };
10066
10246
  }
10067
10247
  render() {
10068
10248
  let printStr = this.promptLine();
10069
- if (!this.currentKeyword) {
10070
- printStr += "\nPlease input filter pattern";
10071
- } else if (this.currentKeyword && this.results.length === 0) {
10072
- printStr += "\nPattern matches no results";
10073
- } else {
10249
+ if (!this.currentKeyword) printStr += "\nPlease input filter pattern";
10250
+ else if (this.currentKeyword && this.results.length === 0) printStr += "\nPattern matches no results";
10251
+ else {
10074
10252
  const resultCountLine = this.results.length === 1 ? `Pattern matches ${this.results.length} result` : `Pattern matches ${this.results.length} results`;
10075
10253
  let resultBody = "";
10076
10254
  if (this.results.length > MAX_RESULT_COUNT) {
@@ -10078,12 +10256,9 @@ class WatchFilter {
10078
10256
  const displayResults = this.results.slice(offset, MAX_RESULT_COUNT + offset);
10079
10257
  const remainingResultCount = this.results.length - offset - displayResults.length;
10080
10258
  resultBody = `${displayResults.map((result, index) => index + offset === this.selectionIndex ? c.green(` › ${result}`) : c.dim(` › ${result}`)).join("\n")}`;
10081
- if (remainingResultCount > 0) {
10082
- resultBody += "\n" + `${c.dim(` ...and ${remainingResultCount} more ${remainingResultCount === 1 ? "result" : "results"}`)}`;
10083
- }
10084
- } else {
10085
- resultBody = this.results.map((result, index) => index === this.selectionIndex ? c.green(` › ${result}`) : c.dim(` › ${result}`)).join("\n");
10086
- }
10259
+ if (remainingResultCount > 0) resultBody += `
10260
+ ${c.dim(` ...and ${remainingResultCount} more ${remainingResultCount === 1 ? "result" : "results"}`)}`;
10261
+ } else resultBody = this.results.map((result, index) => index === this.selectionIndex ? c.green(` › ${result}`) : c.dim(` › ${result}`)).join("\n");
10087
10262
  printStr += `\n${resultCountLine}\n${resultBody}`;
10088
10263
  }
10089
10264
  this.eraseAndPrint(printStr);
@@ -10100,6 +10275,7 @@ class WatchFilter {
10100
10275
  const lines = str.split(/\r?\n/);
10101
10276
  for (const line of lines) {
10102
10277
  const columns = "columns" in this.stdout ? this.stdout.columns : 80;
10278
+ // We have to take care of screen width in case of long lines
10103
10279
  rows += 1 + Math.floor(Math.max(stripVTControlCharacters(line).length - 1, 0) / columns);
10104
10280
  }
10105
10281
  this.write(`${ESC}1G`);
@@ -10109,12 +10285,8 @@ class WatchFilter {
10109
10285
  }
10110
10286
  close() {
10111
10287
  this.filterRL.close();
10112
- if (this.onKeyPress) {
10113
- this.stdin.removeListener("keypress", this.onKeyPress);
10114
- }
10115
- if (this.stdin.isTTY) {
10116
- this.stdin.setRawMode(false);
10117
- }
10288
+ if (this.onKeyPress) this.stdin.removeListener("keypress", this.onKeyPress);
10289
+ if (this.stdin.isTTY) this.stdin.setRawMode(false);
10118
10290
  }
10119
10291
  restoreCursor() {
10120
10292
  const cursortPos = this.keywordOffset() + (this.currentKeyword?.length || 0);
@@ -10154,6 +10326,8 @@ ${keys.map((i) => c.dim(" press ") + c.reset([i[0]].flat().map(c.bold).join(",
10154
10326
  function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10155
10327
  let latestFilename = "";
10156
10328
  async function _keypressHandler(str, key) {
10329
+ // Cancel run and exit when ctrl-c or esc is pressed.
10330
+ // If cancelling takes long and key is pressed multiple times, exit forcefully.
10157
10331
  if (str === "" || str === "\x1B" || key && key.ctrl && key.name === "c") {
10158
10332
  if (!ctx.isCancelling) {
10159
10333
  ctx.logger.log(c.red("Cancelling test run. Press CTRL+c again to exit forcefully.\n"));
@@ -10162,6 +10336,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10162
10336
  }
10163
10337
  return ctx.exit(true);
10164
10338
  }
10339
+ // window not support suspend
10165
10340
  if (!isWindows && key && key.ctrl && key.name === "z") {
10166
10341
  process.kill(process.ppid, "SIGTSTP");
10167
10342
  process.kill(process.pid, "SIGTSTP");
@@ -10169,39 +10344,30 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10169
10344
  }
10170
10345
  const name = key?.name;
10171
10346
  if (ctx.runningPromise) {
10172
- if (cancelKeys.includes(name)) {
10173
- await ctx.cancelCurrentRun("keyboard-input");
10174
- }
10347
+ if (cancelKeys.includes(name)) await ctx.cancelCurrentRun("keyboard-input");
10175
10348
  return;
10176
10349
  }
10177
- if (name === "q") {
10178
- return ctx.exit(true);
10179
- }
10180
- if (name === "h") {
10181
- return printShortcutsHelp();
10182
- }
10183
- if (name === "u") {
10184
- return ctx.updateSnapshot();
10185
- }
10350
+ // quit
10351
+ if (name === "q") return ctx.exit(true);
10352
+ // help
10353
+ if (name === "h") return printShortcutsHelp();
10354
+ // update snapshot
10355
+ if (name === "u") return ctx.updateSnapshot();
10356
+ // rerun all tests
10186
10357
  if (name === "a" || name === "return") {
10187
10358
  const files = await ctx._globTestFilepaths();
10188
10359
  return ctx.changeNamePattern("", files, "rerun all tests");
10189
10360
  }
10190
- if (name === "r") {
10191
- return ctx.rerunFiles();
10192
- }
10193
- if (name === "f") {
10194
- return ctx.rerunFailed();
10195
- }
10196
- if (name === "w") {
10197
- return inputProjectName();
10198
- }
10199
- if (name === "t") {
10200
- return inputNamePattern();
10201
- }
10202
- if (name === "p") {
10203
- return inputFilePattern();
10204
- }
10361
+ // rerun current pattern tests
10362
+ if (name === "r") return ctx.rerunFiles();
10363
+ // rerun only failed tests
10364
+ if (name === "f") return ctx.rerunFailed();
10365
+ // change project filter
10366
+ if (name === "w") return inputProjectName();
10367
+ // change testNamePattern
10368
+ if (name === "t") return inputNamePattern();
10369
+ // change fileNamePattern
10370
+ if (name === "p") return inputFilePattern();
10205
10371
  if (name === "b") {
10206
10372
  await ctx._initBrowserServers();
10207
10373
  ctx.projects.forEach((project) => {
@@ -10224,15 +10390,15 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10224
10390
  const reg = new RegExp(str);
10225
10391
  return tests.map((test) => test.name).filter((testName) => testName.match(reg));
10226
10392
  } catch {
10393
+ // `new RegExp` may throw error when input is invalid regexp
10227
10394
  return [];
10228
10395
  }
10229
10396
  });
10230
10397
  on();
10231
- if (typeof filter === "undefined") {
10232
- return;
10233
- }
10398
+ if (typeof filter === "undefined") return;
10234
10399
  const files = ctx.state.getFilepaths();
10235
- const cliFiles = ctx.config.standalone && !files.length ? await ctx._globTestFilepaths() : undefined;
10400
+ // if running in standalone mode, Vitest instance doesn't know about any test file
10401
+ const cliFiles = ctx.config.standalone && !files.length ? await ctx._globTestFilepaths() : void 0;
10236
10402
  await ctx.changeNamePattern(filter?.trim() || "", cliFiles, "change pattern");
10237
10403
  }
10238
10404
  async function inputProjectName() {
@@ -10254,12 +10420,10 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10254
10420
  return files.map((file) => relative(ctx.config.root, file[1]));
10255
10421
  });
10256
10422
  on();
10257
- if (typeof filter === "undefined") {
10258
- return;
10259
- }
10423
+ if (typeof filter === "undefined") return;
10260
10424
  latestFilename = filter?.trim() || "";
10261
10425
  const lastResults = watchFilter.getLastResults();
10262
- await ctx.changeFilenamePattern(latestFilename, filter && lastResults.length ? lastResults.map((i) => resolve(ctx.config.root, i)) : undefined);
10426
+ await ctx.changeFilenamePattern(latestFilename, filter && lastResults.length ? lastResults.map((i) => resolve(ctx.config.root, i)) : void 0);
10263
10427
  }
10264
10428
  let rl;
10265
10429
  function on() {
@@ -10269,18 +10433,14 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10269
10433
  escapeCodeTimeout: 50
10270
10434
  });
10271
10435
  readline.emitKeypressEvents(stdin, rl);
10272
- if (stdin.isTTY) {
10273
- stdin.setRawMode(true);
10274
- }
10436
+ if (stdin.isTTY) stdin.setRawMode(true);
10275
10437
  stdin.on("keypress", keypressHandler);
10276
10438
  }
10277
10439
  function off() {
10278
10440
  rl?.close();
10279
- rl = undefined;
10441
+ rl = void 0;
10280
10442
  stdin.removeListener("keypress", keypressHandler);
10281
- if (stdin.isTTY) {
10282
- stdin.setRawMode(false);
10283
- }
10443
+ if (stdin.isTTY) stdin.setRawMode(false);
10284
10444
  }
10285
10445
  on();
10286
10446
  return function cleanup() {
@@ -10309,28 +10469,17 @@ async function startVitest(mode, cliFilters = [], options = {}, viteOverrides, v
10309
10469
  const stdin = vitestOptions?.stdin || process.stdin;
10310
10470
  const stdout = vitestOptions?.stdout || process.stdout;
10311
10471
  let stdinCleanup;
10312
- if (stdin.isTTY && ctx.config.watch) {
10313
- stdinCleanup = registerConsoleShortcuts(ctx, stdin, stdout);
10314
- }
10472
+ if (stdin.isTTY && ctx.config.watch) stdinCleanup = registerConsoleShortcuts(ctx, stdin, stdout);
10315
10473
  ctx.onAfterSetServer(() => {
10316
- if (ctx.config.standalone) {
10317
- ctx.init();
10318
- } else {
10319
- ctx.start(cliFilters);
10320
- }
10474
+ if (ctx.config.standalone) ctx.init();
10475
+ else ctx.start(cliFilters);
10321
10476
  });
10322
10477
  try {
10323
- if (ctx.config.mergeReports) {
10324
- await ctx.mergeReports();
10325
- } else if (ctx.config.standalone) {
10326
- await ctx.init();
10327
- } else {
10328
- await ctx.start(cliFilters);
10329
- }
10478
+ if (ctx.config.mergeReports) await ctx.mergeReports();
10479
+ else if (ctx.config.standalone) await ctx.init();
10480
+ else await ctx.start(cliFilters);
10330
10481
  } catch (e) {
10331
- if (e instanceof FilesNotFoundError) {
10332
- return ctx;
10333
- }
10482
+ if (e instanceof FilesNotFoundError) return ctx;
10334
10483
  if (e instanceof GitNotFoundError) {
10335
10484
  ctx.logger.error(e.message);
10336
10485
  return ctx;
@@ -10347,9 +10496,7 @@ async function startVitest(mode, cliFilters = [], options = {}, viteOverrides, v
10347
10496
  ctx.logger.error("\n\n");
10348
10497
  return ctx;
10349
10498
  }
10350
- if (ctx.shouldKeepServer()) {
10351
- return ctx;
10352
- }
10499
+ if (ctx.shouldKeepServer()) return ctx;
10353
10500
  stdinCleanup?.();
10354
10501
  await ctx.close();
10355
10502
  return ctx;
@@ -10358,9 +10505,8 @@ async function prepareVitest(mode, options = {}, viteOverrides, vitestOptions) {
10358
10505
  process.env.TEST = "true";
10359
10506
  process.env.VITEST = "true";
10360
10507
  process.env.NODE_ENV ??= "test";
10361
- if (options.run) {
10362
- options.watch = false;
10363
- }
10508
+ if (options.run) options.watch = false;
10509
+ // this shouldn't affect _application root_ that can be changed inside config
10364
10510
  const root = resolve(options.root || process.cwd());
10365
10511
  const ctx = await createVitest(mode, options, viteOverrides, vitestOptions);
10366
10512
  const environmentPackage = getEnvPackageName(ctx.config.environment);
@@ -10378,24 +10524,16 @@ function processCollected(ctx, files, options) {
10378
10524
  ctx.logger.printError(error, { project: suite.project });
10379
10525
  });
10380
10526
  });
10381
- if (errorsPrinted) {
10382
- return;
10383
- }
10384
- if (typeof options.json !== "undefined") {
10385
- return processJsonOutput(files, options);
10386
- }
10527
+ if (errorsPrinted) return;
10528
+ if (typeof options.json !== "undefined") return processJsonOutput(files, options);
10387
10529
  return formatCollectedAsString(files).forEach((test) => console.log(test));
10388
10530
  }
10389
10531
  function outputFileList(files, options) {
10390
- if (typeof options.json !== "undefined") {
10391
- return outputJsonFileList(files, options);
10392
- }
10532
+ if (typeof options.json !== "undefined") return outputJsonFileList(files, options);
10393
10533
  formatFilesAsString(files, options).map((file) => console.log(file));
10394
10534
  }
10395
10535
  function outputJsonFileList(files, options) {
10396
- if (typeof options.json === "boolean") {
10397
- return console.log(JSON.stringify(formatFilesAsJSON(files), null, 2));
10398
- }
10536
+ if (typeof options.json === "boolean") return console.log(JSON.stringify(formatFilesAsJSON(files), null, 2));
10399
10537
  if (typeof options.json === "string") {
10400
10538
  const jsonPath = resolve(options.root || process.cwd(), options.json);
10401
10539
  mkdirSync(dirname(jsonPath), { recursive: true });
@@ -10405,25 +10543,19 @@ function outputJsonFileList(files, options) {
10405
10543
  function formatFilesAsJSON(files) {
10406
10544
  return files.map((file) => {
10407
10545
  const result = { file: file.moduleId };
10408
- if (file.project.name) {
10409
- result.projectName = file.project.name;
10410
- }
10546
+ if (file.project.name) result.projectName = file.project.name;
10411
10547
  return result;
10412
10548
  });
10413
10549
  }
10414
10550
  function formatFilesAsString(files, options) {
10415
10551
  return files.map((file) => {
10416
10552
  let name = relative(options.root || process.cwd(), file.moduleId);
10417
- if (file.project.name) {
10418
- name = `[${file.project.name}] ${name}`;
10419
- }
10553
+ if (file.project.name) name = `[${file.project.name}] ${name}`;
10420
10554
  return name;
10421
10555
  });
10422
10556
  }
10423
10557
  function processJsonOutput(files, options) {
10424
- if (typeof options.json === "boolean") {
10425
- return console.log(JSON.stringify(formatCollectedAsJSON(files), null, 2));
10426
- }
10558
+ if (typeof options.json === "boolean") return console.log(JSON.stringify(formatCollectedAsJSON(files), null, 2));
10427
10559
  if (typeof options.json === "string") {
10428
10560
  const jsonPath = resolve(options.root || process.cwd(), options.json);
10429
10561
  mkdirSync(dirname(jsonPath), { recursive: true });
@@ -10433,28 +10565,20 @@ function processJsonOutput(files, options) {
10433
10565
  function forEachSuite(modules, callback) {
10434
10566
  modules.forEach((testModule) => {
10435
10567
  callback(testModule);
10436
- for (const suite of testModule.children.allSuites()) {
10437
- callback(suite);
10438
- }
10568
+ for (const suite of testModule.children.allSuites()) callback(suite);
10439
10569
  });
10440
10570
  }
10441
10571
  function formatCollectedAsJSON(files) {
10442
10572
  const results = [];
10443
10573
  files.forEach((file) => {
10444
10574
  for (const test of file.children.allTests()) {
10445
- if (test.result().state === "skipped") {
10446
- continue;
10447
- }
10575
+ if (test.result().state === "skipped") continue;
10448
10576
  const result = {
10449
10577
  name: test.fullName,
10450
10578
  file: test.module.moduleId
10451
10579
  };
10452
- if (test.project.name) {
10453
- result.projectName = test.project.name;
10454
- }
10455
- if (test.location) {
10456
- result.location = test.location;
10457
- }
10580
+ if (test.project.name) result.projectName = test.project.name;
10581
+ if (test.location) result.location = test.location;
10458
10582
  results.push(result);
10459
10583
  }
10460
10584
  });
@@ -10464,9 +10588,7 @@ function formatCollectedAsString(testModules) {
10464
10588
  const results = [];
10465
10589
  testModules.forEach((testModule) => {
10466
10590
  for (const test of testModule.children.allTests()) {
10467
- if (test.result().state === "skipped") {
10468
- continue;
10469
- }
10591
+ if (test.result().state === "skipped") continue;
10470
10592
  const fullName = `${test.module.task.name} > ${test.fullName}`;
10471
10593
  results.push((test.project.name ? `[${test.project.name}] ` : "") + fullName);
10472
10594
  }
@@ -10479,15 +10601,9 @@ const envPackageNames = {
10479
10601
  "edge-runtime": "@edge-runtime/vm"
10480
10602
  };
10481
10603
  function getEnvPackageName(env) {
10482
- if (env === "node") {
10483
- return null;
10484
- }
10485
- if (env in envPackageNames) {
10486
- return envPackageNames[env];
10487
- }
10488
- if (env[0] === "." || env[0] === "/") {
10489
- return null;
10490
- }
10604
+ if (env === "node") return null;
10605
+ if (env in envPackageNames) return envPackageNames[env];
10606
+ if (env[0] === "." || env[0] === "/") return null;
10491
10607
  return `vitest-environment-${env}`;
10492
10608
  }
10493
10609