vitest 4.1.0-beta.1 → 4.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE.md +36 -0
  2. package/dist/browser.d.ts +1 -1
  3. package/dist/browser.js +2 -2
  4. package/dist/chunks/acorn.B2iPLyUM.js +5958 -0
  5. package/dist/chunks/{base.CBRNZa3k.js → base.DiopZV8F.js} +48 -14
  6. package/dist/chunks/{benchmark.B3N2zMcH.js → benchmark.BoqSLF53.js} +1 -1
  7. package/dist/chunks/{browser.d.8hOapKZr.d.ts → browser.d.BE4kbYok.d.ts} +2 -1
  8. package/dist/chunks/{cac.B1v3xxoC.js → cac.C4jjt2RX.js} +797 -13
  9. package/dist/chunks/{cli-api.B4CqEpI6.js → cli-api.ChbI1JU9.js} +322 -124
  10. package/dist/chunks/{config.d.idH22YSr.d.ts → config.d.Cr1Ep39N.d.ts} +6 -1
  11. package/dist/chunks/{console.uGgdMhyZ.js → console.CNlG1KsP.js} +2 -2
  12. package/dist/chunks/{constants.D_Q9UYh-.js → constants.B63TT-Bl.js} +1 -1
  13. package/dist/chunks/coverage.tyqbzn4W.js +1001 -0
  14. package/dist/chunks/{creator.C7WwjkuR.js → creator.yyCHuw5R.js} +1 -1
  15. package/dist/chunks/{global.d.B15mdLcR.d.ts → global.d.JeWMqlOm.d.ts} +1 -1
  16. package/dist/chunks/{globals.DjuGMoMc.js → globals.C6Ecf1TO.js} +6 -6
  17. package/dist/chunks/{index.Dm4xqZ0s.js → index.B-iBE_Gx.js} +20 -4
  18. package/dist/chunks/{coverage.BMlOMIWl.js → index.BCY_7LL2.js} +5 -969
  19. package/dist/chunks/{index.BiOAd_ki.js → index.CAN630q3.js} +7 -7
  20. package/dist/chunks/{index.DyBZXrH3.js → index.CFulQRmC.js} +1 -1
  21. package/dist/chunks/{index.BEFi2-_3.js → index.CouFDptX.js} +2 -2
  22. package/dist/chunks/{init-forks.CHeQ9Moq.js → init-forks.BnCXPazU.js} +1 -1
  23. package/dist/chunks/{init-threads.uZiNAuPk.js → init-threads.Cyh2PqXi.js} +1 -1
  24. package/dist/chunks/{init.DVtKdFty.js → init.B95Mm0Iz.js} +47 -9
  25. package/dist/chunks/native.mV0-490A.js +148 -0
  26. package/dist/chunks/nativeModuleMocker.D_q5sFv6.js +206 -0
  27. package/dist/chunks/nativeModuleRunner.BIakptoF.js +36 -0
  28. package/dist/chunks/{node.Ce0vMQM7.js → node.CrSEwhm4.js} +1 -1
  29. package/dist/chunks/{plugin.d.D8KU2PY_.d.ts → plugin.d.C9o5bttz.d.ts} +1 -1
  30. package/dist/chunks/{reporters.d.Db3MiIWX.d.ts → reporters.d.7faYdkxy.d.ts} +120 -51
  31. package/dist/chunks/{rpc.HLmECnw_.js → rpc.DcRWTy5G.js} +1 -1
  32. package/dist/chunks/{rpc.d.RH3apGEf.d.ts → rpc.d.CM7x9-sm.d.ts} +1 -0
  33. package/dist/chunks/{setup-common.BcqLPsn5.js → setup-common.cvFp-ao9.js} +2 -2
  34. package/dist/chunks/{startModuleRunner.C5CcWyXW.js → startVitestModuleRunner.BK-u7y4N.js} +163 -372
  35. package/dist/chunks/{test.prxIahgM.js → test.G82XYNFk.js} +9 -4
  36. package/dist/chunks/{utils.DvEY5TfP.js → utils.DT4VyRyl.js} +5 -1
  37. package/dist/chunks/{vm.CrifS09m.js → vm.BdLtzhnj.js} +13 -6
  38. package/dist/chunks/{worker.d.Bji1eq5g.d.ts → worker.d.CPzI2ZzJ.d.ts} +2 -2
  39. package/dist/cli.js +4 -3
  40. package/dist/config.d.ts +8 -8
  41. package/dist/config.js +1 -1
  42. package/dist/coverage.d.ts +7 -5
  43. package/dist/coverage.js +5 -4
  44. package/dist/index.d.ts +18 -23
  45. package/dist/index.js +5 -5
  46. package/dist/module-evaluator.d.ts +10 -1
  47. package/dist/node.d.ts +9 -9
  48. package/dist/node.js +18 -16
  49. package/dist/nodejs-worker-loader.js +41 -0
  50. package/dist/reporters.d.ts +5 -5
  51. package/dist/reporters.js +2 -2
  52. package/dist/runners.d.ts +2 -1
  53. package/dist/runners.js +4 -4
  54. package/dist/runtime.js +4 -5
  55. package/dist/snapshot.js +2 -2
  56. package/dist/suite.js +2 -2
  57. package/dist/worker.d.ts +6 -6
  58. package/dist/worker.js +25 -18
  59. package/dist/workers/forks.js +21 -14
  60. package/dist/workers/runVmTests.js +7 -7
  61. package/dist/workers/threads.js +21 -14
  62. package/dist/workers/vmForks.js +14 -10
  63. package/dist/workers/vmThreads.js +14 -10
  64. package/package.json +17 -14
  65. package/suppress-warnings.cjs +1 -0
@@ -2,20 +2,20 @@ import fs, { promises, existsSync, mkdirSync, readFileSync, statSync, readdirSyn
2
2
  import { relative, resolve, dirname, join, extname, normalize, basename, isAbsolute } from 'pathe';
3
3
  import { C as CoverageProviderMap } from './coverage.D_JHT54q.js';
4
4
  import path, { resolve as resolve$1 } from 'node:path';
5
- import { noop, createDefer, slash, withTrailingSlash, cleanUrl, wrapId, isExternalUrl, unwrapId, toArray, deepMerge, nanoid, deepClone, isPrimitive, notNullish } from '@vitest/utils/helpers';
5
+ import { cleanUrl, noop, unique, createDefer, slash, withTrailingSlash, wrapId, isExternalUrl, unwrapId, toArray, deepMerge, nanoid, deepClone, isPrimitive, notNullish } from '@vitest/utils/helpers';
6
6
  import { a as any, p as prompt } from './index.D4KonVSU.js';
7
- import { h as hash, R as RandomSequencer, i as isPackageExists, c as isBrowserEnabled, r as resolveConfig, g as getCoverageProvider, a as resolveApiServerConfig, d as resolveModule } from './coverage.BMlOMIWl.js';
7
+ import { i as isPackageExists, r as resolveModule } from './index.BCY_7LL2.js';
8
8
  import * as vite from 'vite';
9
- import { isFileServingAllowed as isFileServingAllowed$1, parseAst, searchForWorkspaceRoot, fetchModule, version, mergeConfig, createServer, isFileLoadingAllowed, normalizePath } from 'vite';
10
- import { A as API_PATH, c as configFiles, d as defaultBrowserPort, a as defaultPort } from './constants.D_Q9UYh-.js';
9
+ import { createServer, isFileLoadingAllowed, normalizePath, parseAst, searchForWorkspaceRoot, fetchModule, version, mergeConfig } from 'vite';
10
+ import { A as API_PATH, c as configFiles, d as defaultBrowserPort, b as defaultPort } from './constants.B63TT-Bl.js';
11
11
  import * as nodeos from 'node:os';
12
12
  import nodeos__default, { tmpdir } from 'node:os';
13
- import { generateHash as generateHash$1, createTaskName, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, hasFailed, generateFileHash, limitConcurrency, createFileTask as createFileTask$1, getTasks, isTestCase } from '@vitest/runner/utils';
13
+ import { generateHash as generateHash$1, createTaskName, validateTags, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, hasFailed, generateFileHash, limitConcurrency, createFileTask as createFileTask$1, getTasks, isTestCase } from '@vitest/runner/utils';
14
14
  import { SnapshotManager } from '@vitest/snapshot/manager';
15
- import { v as version$1 } from './cac.B1v3xxoC.js';
15
+ import { v as version$1 } from './cac.C4jjt2RX.js';
16
16
  import { performance as performance$1 } from 'node:perf_hooks';
17
17
  import { c as createBirpc } from './index.Chj8NDwU.js';
18
- import { p as parse, d as stringify, e as createIndexLocationsMap, h as TraceMap, o as originalPositionFor, i as ancestor, j as printError, f as formatProjectName, w as withLabel, k as errorBanner, l as divider, m as Typechecker, n as generateCodeFrame, q as escapeRegExp, r as createDefinesScript, R as ReportersMap, u as groupBy, B as BlobReporter, v as readBlobs, x as convertTasksToEvents, H as HangingProcessReporter, y as wildcardPatternToRegExp, z as stdout } from './index.Dm4xqZ0s.js';
18
+ import { p as parse, d as stringify, e as createIndexLocationsMap, h as TraceMap, o as originalPositionFor, i as ancestor, j as printError, f as formatProjectName, w as withLabel, k as errorBanner, l as divider, m as Typechecker, n as generateCodeFrame, q as escapeRegExp, r as createDefinesScript, R as ReportersMap, u as groupBy, B as BlobReporter, v as readBlobs, x as convertTasksToEvents, H as HangingProcessReporter, y as wildcardPatternToRegExp, z as stdout } from './index.B-iBE_Gx.js';
19
19
  import require$$0$3 from 'events';
20
20
  import require$$1$1 from 'https';
21
21
  import require$$2 from 'http';
@@ -29,8 +29,10 @@ import require$$0$1 from 'buffer';
29
29
  import { g as getDefaultExportFromCjs } from './_commonjsHelpers.D26ty3Ew.js';
30
30
  import crypto, { createHash } from 'node:crypto';
31
31
  import { rootDir, distDir } from '../path.js';
32
+ import { N as NativeModuleRunner } from './nativeModuleRunner.BIakptoF.js';
32
33
  import { T as Traces } from './traces.CCmnQaNT.js';
33
34
  import { createDebug } from 'obug';
35
+ import { h as hash, R as RandomSequencer, i as isBrowserEnabled, r as resolveConfig, g as getCoverageProvider, a as resolveApiServerConfig } from './coverage.tyqbzn4W.js';
34
36
  import { rm, readFile, writeFile, rename, stat, unlink, mkdir, copyFile } from 'node:fs/promises';
35
37
  import c from 'tinyrainbow';
36
38
  import { VitestModuleEvaluator } from '#module-evaluator';
@@ -53,7 +55,7 @@ import { c as configDefaults } from './defaults.BOqNVLsY.js';
53
55
  import { KNOWN_ASSET_RE } from '@vitest/utils/constants';
54
56
  import { findNearestPackageData } from '@vitest/utils/resolver';
55
57
  import * as esModuleLexer from 'es-module-lexer';
56
- import { a as BenchmarkReportsMap } from './index.DyBZXrH3.js';
58
+ import { a as BenchmarkReportsMap } from './index.CFulQRmC.js';
57
59
  import assert$1 from 'node:assert';
58
60
  import { serializeValue } from '@vitest/utils/serialize';
59
61
  import { parseErrorStacktrace } from '@vitest/utils/source-map';
@@ -5036,17 +5038,40 @@ function requireWebsocketServer () {
5036
5038
  var websocketServerExports = requireWebsocketServer();
5037
5039
  var WebSocketServer = /*@__PURE__*/getDefaultExportFromCjs(websocketServerExports);
5038
5040
 
5041
+ async function createViteServer(inlineConfig) {
5042
+ // Vite prints an error (https://github.com/vitejs/vite/issues/14328)
5043
+ // But Vitest works correctly either way
5044
+ const error = console.error;
5045
+ console.error = (...args) => {
5046
+ if (typeof args[0] === "string" && args[0].includes("WebSocket server error:")) return;
5047
+ error(...args);
5048
+ };
5049
+ const server = await createServer(inlineConfig);
5050
+ console.error = error;
5051
+ return server;
5052
+ }
5053
+ function isFileServingAllowed(configOrUrl, urlOrServer) {
5054
+ const config = typeof urlOrServer === "string" ? configOrUrl : urlOrServer.config;
5055
+ const url = typeof urlOrServer === "string" ? urlOrServer : configOrUrl;
5056
+ if (!config.server.fs.strict) return true;
5057
+ return isFileLoadingAllowed(config, fsPathFromUrl(url));
5058
+ }
5059
+ const FS_PREFIX = "/@fs/";
5060
+ const VOLUME_RE = /^[A-Z]:/i;
5061
+ function fsPathFromId(id) {
5062
+ const fsPath = normalizePath(id.startsWith(FS_PREFIX) ? id.slice(5) : id);
5063
+ return fsPath[0] === "/" || VOLUME_RE.test(fsPath) ? fsPath : `/${fsPath}`;
5064
+ }
5065
+ function fsPathFromUrl(url) {
5066
+ return fsPathFromId(cleanUrl(url));
5067
+ }
5068
+
5039
5069
  function getTestFileEnvironment(project, testFile, browser = false) {
5040
- let environment;
5041
- if (browser) environment = project.browser?.vite.environments.client;
5070
+ if (browser) return project.browser?.vite.environments.client;
5042
5071
  else for (const name in project.vite.environments) {
5043
5072
  const env = project.vite.environments[name];
5044
- if (env.moduleGraph.getModuleById(testFile)) {
5045
- environment = env;
5046
- break;
5047
- }
5073
+ if (env.moduleGraph.getModuleById(testFile)) return env;
5048
5074
  }
5049
- return environment;
5050
5075
  }
5051
5076
 
5052
5077
  async function getModuleGraph(ctx, projectName, testFilePath, browser = false) {
@@ -5054,7 +5079,7 @@ async function getModuleGraph(ctx, projectName, testFilePath, browser = false) {
5054
5079
  const externalized = /* @__PURE__ */ new Set();
5055
5080
  const inlined = /* @__PURE__ */ new Set();
5056
5081
  const project = ctx.getProjectByName(projectName);
5057
- const environment = getTestFileEnvironment(project, testFilePath, browser);
5082
+ const environment = project.config.experimental.viteModuleRunner === false ? project.vite.environments.__vitest__ : getTestFileEnvironment(project, testFilePath, browser);
5058
5083
  if (!environment) throw new Error(`Cannot find environment for ${testFilePath}`);
5059
5084
  const seen = /* @__PURE__ */ new Map();
5060
5085
  function get(mod) {
@@ -5185,7 +5210,7 @@ function setup(ctx, _server) {
5185
5210
  async getExternalResult(moduleId, testFileTaskId) {
5186
5211
  const testModule = ctx.state.getReportedEntityById(testFileTaskId);
5187
5212
  if (!testModule) return;
5188
- if (!isFileServingAllowed$1(testModule.project.vite.config, moduleId)) return;
5213
+ if (!isFileServingAllowed(testModule.project.vite.config, moduleId)) return;
5189
5214
  const result = {};
5190
5215
  try {
5191
5216
  result.source = await promises.readFile(moduleId, "utf-8");
@@ -5195,7 +5220,7 @@ function setup(ctx, _server) {
5195
5220
  async getTransformResult(projectName, moduleId, testFileTaskId, browser = false) {
5196
5221
  const project = ctx.getProjectByName(projectName);
5197
5222
  const testModule = ctx.state.getReportedEntityById(testFileTaskId);
5198
- if (!testModule || !isFileServingAllowed$1(project.vite.config, moduleId)) return;
5223
+ if (!testModule || !isFileServingAllowed(project.vite.config, moduleId)) return;
5199
5224
  const environment = getTestFileEnvironment(project, testModule.moduleId, browser);
5200
5225
  const moduleNode = environment?.moduleGraph.getModuleById(moduleId);
5201
5226
  if (!environment || !moduleNode?.transformResult) return;
@@ -5335,6 +5360,54 @@ function createDebugger(namespace) {
5335
5360
  if (debug.enabled) return debug;
5336
5361
  }
5337
5362
 
5363
+ async function getSpecificationsOptions(specifications) {
5364
+ const environments = /* @__PURE__ */ new WeakMap();
5365
+ const cache = /* @__PURE__ */ new Map();
5366
+ const tags = /* @__PURE__ */ new WeakMap();
5367
+ await Promise.all(specifications.map(async (spec) => {
5368
+ const { moduleId: filepath, project, pool } = spec;
5369
+ // browser pool handles its own environment
5370
+ if (pool === "browser") return;
5371
+ // reuse if projects have the same test files
5372
+ let code = cache.get(filepath);
5373
+ if (!code) {
5374
+ code = await promises.readFile(filepath, "utf-8").catch(() => "");
5375
+ cache.set(filepath, code);
5376
+ }
5377
+ const { env = project.config.environment || "node", envOptions, tags: specTags = [] } = detectCodeBlock(code);
5378
+ tags.set(spec, specTags);
5379
+ const environment = {
5380
+ name: env,
5381
+ options: envOptions ? { [env === "happy-dom" ? "happyDOM" : env]: envOptions } : null
5382
+ };
5383
+ environments.set(spec, environment);
5384
+ }));
5385
+ return {
5386
+ environments,
5387
+ tags
5388
+ };
5389
+ }
5390
+ function detectCodeBlock(content) {
5391
+ const env = content.match(/@(?:vitest|jest)-environment\s+([\w-]+)\b/)?.[1];
5392
+ let envOptionsJson = content.match(/@(?:vitest|jest)-environment-options\s+(.+)/)?.[1];
5393
+ if (envOptionsJson?.endsWith("*/"))
5394
+ // Trim closing Docblock characters the above regex might have captured
5395
+ envOptionsJson = envOptionsJson.slice(0, -2);
5396
+ const envOptions = JSON.parse(envOptionsJson || "null");
5397
+ const tags = [];
5398
+ let tagMatch;
5399
+ // eslint-disable-next-line no-cond-assign
5400
+ while (tagMatch = content.match(/(\/\/|\*)\s*@module-tag\s+([\w\-/]+)\b/)) {
5401
+ tags.push(tagMatch[2]);
5402
+ content = content.slice(tagMatch.index + tagMatch[0].length);
5403
+ }
5404
+ return {
5405
+ env,
5406
+ envOptions,
5407
+ tags
5408
+ };
5409
+ }
5410
+
5338
5411
  const debug$1 = createDebugger("vitest:ast-collect-info");
5339
5412
  const verbose = createDebugger("vitest:ast-collect-verbose");
5340
5413
  function isTestFunctionName(name) {
@@ -5408,7 +5481,23 @@ function astParseFile(filepath, code) {
5408
5481
  const property = callee.tag?.property?.name;
5409
5482
  isDynamicEach = property === "each" || property === "for";
5410
5483
  }
5411
- debug$1?.("Found", name, message, `(${mode})`);
5484
+ // Extract tags from the second argument if it's an options object
5485
+ const tags = [];
5486
+ const secondArg = node.arguments?.[1];
5487
+ if (secondArg?.type === "ObjectExpression") {
5488
+ const tagsProperty = secondArg.properties?.find((p) => p.type === "Property" && p.key?.type === "Identifier" && p.key.name === "tags");
5489
+ if (tagsProperty) {
5490
+ const tagsValue = tagsProperty.value;
5491
+ if (tagsValue?.type === "Literal" && typeof tagsValue.value === "string")
5492
+ // tags: 'single-tag'
5493
+ tags.push(tagsValue.value);
5494
+ else if (tagsValue?.type === "ArrayExpression") {
5495
+ // tags: ['tag1', 'tag2']
5496
+ for (const element of tagsValue.elements || []) if (element?.type === "Literal" && typeof element.value === "string") tags.push(element.value);
5497
+ }
5498
+ }
5499
+ }
5500
+ debug$1?.("Found", name, message, `(${mode})`, tags.length ? `[${tags.join(", ")}]` : "");
5412
5501
  definitions.push({
5413
5502
  start,
5414
5503
  end,
@@ -5416,7 +5505,8 @@ function astParseFile(filepath, code) {
5416
5505
  type: isTestFunctionName(name) ? "test" : "suite",
5417
5506
  mode,
5418
5507
  task: null,
5419
- dynamic: isDynamicEach
5508
+ dynamic: isDynamicEach,
5509
+ tags
5420
5510
  });
5421
5511
  } });
5422
5512
  return {
@@ -5462,22 +5552,23 @@ function serializeError(ctx, error) {
5462
5552
  message: error.message
5463
5553
  }];
5464
5554
  }
5465
- function createFileTask(testFilepath, code, requestMap, options) {
5555
+ function createFileTask(testFilepath, code, requestMap, config, filepath, fileTags) {
5466
5556
  const { definitions, ast } = astParseFile(testFilepath, code);
5467
5557
  const file = {
5468
- filepath: options.filepath,
5558
+ filepath,
5469
5559
  type: "suite",
5470
- id: /* @__PURE__ */ generateHash$1(`${testFilepath}${options.name || ""}`),
5560
+ id: /* @__PURE__ */ generateHash$1(`${testFilepath}${config.name || ""}`),
5471
5561
  name: testFilepath,
5472
5562
  fullName: testFilepath,
5473
5563
  mode: "run",
5474
5564
  tasks: [],
5475
5565
  start: ast.start,
5476
5566
  end: ast.end,
5477
- projectName: options.name,
5567
+ projectName: config.name,
5478
5568
  meta: {},
5479
5569
  pool: "browser",
5480
- file: null
5570
+ file: null,
5571
+ tags: fileTags || []
5481
5572
  };
5482
5573
  file.file = file;
5483
5574
  const indexMap = createIndexLocationsMap(code);
@@ -5508,6 +5599,7 @@ function createFileTask(testFilepath, code, requestMap, options) {
5508
5599
  };
5509
5600
  } else debug$1?.("Cannot find original location for", definition.type, definition.name, `${processedLocation.column}:${processedLocation.line}`);
5510
5601
  } else debug$1?.("Cannot find original location for", definition.type, definition.name, `${definition.start}`);
5602
+ const taskTags = unique([...latestSuite.tags || [], ...definition.tags]);
5511
5603
  if (definition.type === "suite") {
5512
5604
  const task = {
5513
5605
  type: definition.type,
@@ -5516,6 +5608,7 @@ function createFileTask(testFilepath, code, requestMap, options) {
5516
5608
  file,
5517
5609
  tasks: [],
5518
5610
  mode,
5611
+ each: definition.dynamic,
5519
5612
  name: definition.name,
5520
5613
  fullName: createTaskName([latestSuite.fullName, definition.name]),
5521
5614
  fullTestName: createTaskName([latestSuite.fullTestName, definition.name]),
@@ -5523,18 +5616,21 @@ function createFileTask(testFilepath, code, requestMap, options) {
5523
5616
  start: definition.start,
5524
5617
  location,
5525
5618
  dynamic: definition.dynamic,
5526
- meta: {}
5619
+ meta: {},
5620
+ tags: taskTags
5527
5621
  };
5528
5622
  definition.task = task;
5529
5623
  latestSuite.tasks.push(task);
5530
5624
  lastSuite = task;
5531
5625
  return;
5532
5626
  }
5627
+ validateTags(config, taskTags);
5533
5628
  const task = {
5534
5629
  type: definition.type,
5535
5630
  id: "",
5536
5631
  suite: latestSuite,
5537
5632
  file,
5633
+ each: definition.dynamic,
5538
5634
  mode,
5539
5635
  context: {},
5540
5636
  name: definition.name,
@@ -5547,20 +5643,21 @@ function createFileTask(testFilepath, code, requestMap, options) {
5547
5643
  meta: {},
5548
5644
  timeout: 0,
5549
5645
  annotations: [],
5550
- artifacts: []
5646
+ artifacts: [],
5647
+ tags: taskTags
5551
5648
  };
5552
5649
  definition.task = task;
5553
5650
  latestSuite.tasks.push(task);
5554
5651
  });
5555
5652
  calculateSuiteHash(file);
5556
5653
  const hasOnly = someTasksAreOnly(file);
5557
- interpretTaskModes(file, options.testNamePattern, void 0, void 0, hasOnly, false, options.allowOnly);
5654
+ interpretTaskModes(file, config.testNamePattern, void 0, void 0, void 0, hasOnly, false, config.allowOnly);
5558
5655
  markDynamicTests(file.tasks);
5559
5656
  if (!file.tasks.length) file.result = {
5560
5657
  state: "fail",
5561
5658
  errors: [{
5562
5659
  name: "Error",
5563
- message: `No test suite found in file ${options.filepath}`
5660
+ message: `No test suite found in file ${filepath}`
5564
5661
  }]
5565
5662
  };
5566
5663
  return file;
@@ -5572,18 +5669,17 @@ async function astCollectTests(project, filepath) {
5572
5669
  debug$1?.("Cannot parse", testFilepath, "(vite didn't return anything)");
5573
5670
  return createFailedFileTask(project, filepath, /* @__PURE__ */ new Error(`Failed to parse ${testFilepath}. Vite didn't return anything.`));
5574
5671
  }
5575
- return createFileTask(testFilepath, request.code, request.map, {
5576
- name: project.config.name,
5577
- filepath,
5578
- allowOnly: project.config.allowOnly,
5579
- testNamePattern: project.config.testNamePattern,
5580
- pool: project.browser ? "browser" : project.config.pool
5581
- });
5672
+ return createFileTask(testFilepath, request.code, request.map, project.serializedConfig, filepath, request.fileTags);
5582
5673
  }
5583
5674
  async function transformSSR(project, filepath) {
5584
- const environment = project.config.environment;
5585
- if (environment === "jsdom" || environment === "happy-dom") return project.vite.environments.client.transformRequest(filepath);
5586
- return project.vite.environments.ssr.transformRequest(filepath);
5675
+ const { env: pragmaEnv, tags: fileTags } = detectCodeBlock(await promises.readFile(filepath, "utf-8").catch(() => ""));
5676
+ // Use environment from pragma if defined, otherwise fall back to config
5677
+ const environment = pragmaEnv || project.config.environment;
5678
+ const transformResult = await (environment === "jsdom" || environment === "happy-dom" ? project.vite.environments.client : project.vite.environments.ssr).transformRequest(filepath);
5679
+ return transformResult ? {
5680
+ ...transformResult,
5681
+ fileTags
5682
+ } : null;
5587
5683
  }
5588
5684
  function markDynamicTests(tasks) {
5589
5685
  for (const task of tasks) {
@@ -6571,6 +6667,25 @@ class Logger {
6571
6667
  this._highlights.set(filename, code);
6572
6668
  return code;
6573
6669
  }
6670
+ printNoTestTagsFound() {
6671
+ this.error(c.bgRed(" ERROR "), c.red("No test tags found in any project. Exiting with code 1."));
6672
+ }
6673
+ printTags() {
6674
+ const vitest = this.ctx;
6675
+ const rootProject = vitest.getRootProject();
6676
+ const projects = [rootProject, ...vitest.projects.filter((p) => p !== rootProject)];
6677
+ if (!projects.some((p) => p.config.tags && p.config.tags.length > 0)) {
6678
+ process.exitCode = 1;
6679
+ return this.printNoTestTagsFound();
6680
+ }
6681
+ for (const project of projects) {
6682
+ if (project.name) this.log(formatProjectName(project, ""));
6683
+ project.config.tags.forEach((tag) => {
6684
+ const tagLog = `${tag.name}${tag.description ? `: ${tag.description}` : ""}`;
6685
+ this.log(` ${tagLog}`);
6686
+ });
6687
+ }
6688
+ }
6574
6689
  printNoTestFound(filters) {
6575
6690
  const config = this.ctx.config;
6576
6691
  if (config.watch && (config.changed || config.related?.length)) this.log(`No affected ${config.mode} files found\n`);
@@ -7003,35 +7118,6 @@ function stringToBytes(input, percentageReference) {
7003
7118
  return null;
7004
7119
  }
7005
7120
 
7006
- async function getSpecificationsEnvironments(specifications) {
7007
- const environments = /* @__PURE__ */ new WeakMap();
7008
- const cache = /* @__PURE__ */ new Map();
7009
- await Promise.all(specifications.map(async (spec) => {
7010
- const { moduleId: filepath, project } = spec;
7011
- // reuse if projects have the same test files
7012
- let code = cache.get(filepath);
7013
- if (!code) {
7014
- code = await promises.readFile(filepath, "utf-8");
7015
- cache.set(filepath, code);
7016
- }
7017
- // 1. Check for control comments in the file
7018
- let env = code.match(/@(?:vitest|jest)-environment\s+([\w-]+)\b/)?.[1];
7019
- // 2. Fallback to global env
7020
- env ||= project.config.environment || "node";
7021
- let envOptionsJson = code.match(/@(?:vitest|jest)-environment-options\s+(.+)/)?.[1];
7022
- if (envOptionsJson?.endsWith("*/"))
7023
- // Trim closing Docblock characters the above regex might have captured
7024
- envOptionsJson = envOptionsJson.slice(0, -2);
7025
- const envOptions = JSON.parse(envOptionsJson || "null");
7026
- const environment = {
7027
- name: env,
7028
- options: envOptions ? { [env === "happy-dom" ? "happyDOM" : env]: envOptions } : null
7029
- };
7030
- environments.set(spec, environment);
7031
- }));
7032
- return environments;
7033
- }
7034
-
7035
7121
  const debug = createDebugger("vitest:browser:pool");
7036
7122
  function createBrowserPool(vitest) {
7037
7123
  const providers = /* @__PURE__ */ new Set();
@@ -7059,13 +7145,29 @@ function createBrowserPool(vitest) {
7059
7145
  };
7060
7146
  const runWorkspaceTests = async (method, specs) => {
7061
7147
  const groupedFiles = /* @__PURE__ */ new Map();
7062
- for (const { project, moduleId, testLines, testIds, testNamePattern } of specs) {
7148
+ const testFilesCode = /* @__PURE__ */ new Map();
7149
+ const testFileTags = /* @__PURE__ */ new WeakMap();
7150
+ await Promise.all(specs.map(async (spec) => {
7151
+ let code = testFilesCode.get(spec.moduleId);
7152
+ // TODO: this really should be done only once when collecting specifications
7153
+ if (code == null) {
7154
+ code = await readFile(spec.moduleId, "utf-8").catch(() => "");
7155
+ testFilesCode.set(spec.moduleId, code);
7156
+ }
7157
+ const { tags } = detectCodeBlock(code);
7158
+ testFileTags.set(spec, tags);
7159
+ }));
7160
+ // to keep the sorting, we need to iterate over specs separately
7161
+ for (const spec of specs) {
7162
+ const { project, moduleId, testLines, testIds, testNamePattern, testTagsFilter } = spec;
7063
7163
  const files = groupedFiles.get(project) || [];
7064
7164
  files.push({
7065
7165
  filepath: moduleId,
7066
7166
  testLocations: testLines,
7067
7167
  testIds,
7068
- testNamePattern
7168
+ testNamePattern,
7169
+ testTagsFilter,
7170
+ fileTags: testFileTags.get(spec)
7069
7171
  });
7070
7172
  groupedFiles.set(project, files);
7071
7173
  }
@@ -7189,7 +7291,7 @@ class BrowserPool {
7189
7291
  let page = this._traces.$(`vitest.browser.open`, {
7190
7292
  context: this._otel.context,
7191
7293
  attributes: { "vitest.browser.session_id": sessionId }
7192
- }, () => this.openPage(sessionId));
7294
+ }, () => this.openPage(sessionId, { parallel: workerCount > 1 }));
7193
7295
  page = page.then(() => {
7194
7296
  // start running tests on the page when it's ready
7195
7297
  this.runNextTest(method, sessionId);
@@ -7200,14 +7302,14 @@ class BrowserPool {
7200
7302
  debug?.("all sessions are created");
7201
7303
  return this._promise;
7202
7304
  }
7203
- async openPage(sessionId) {
7305
+ async openPage(sessionId, options) {
7204
7306
  const sessionPromise = this.project.vitest._browserSessions.createSession(sessionId, this.project, this);
7205
7307
  const browser = this.project.browser;
7206
7308
  const url = new URL("/__vitest_test__/", this.options.origin);
7207
7309
  url.searchParams.set("sessionId", sessionId);
7208
7310
  const otelCarrier = this._traces.getContextCarrier();
7209
7311
  if (otelCarrier) url.searchParams.set("otelCarrier", JSON.stringify(otelCarrier));
7210
- const pagePromise = browser.provider.openPage(sessionId, url.toString());
7312
+ const pagePromise = browser.provider.openPage(sessionId, url.toString(), options);
7211
7313
  await Promise.all([sessionPromise, pagePromise]);
7212
7314
  }
7213
7315
  getOrchestrator(sessionId) {
@@ -7377,6 +7479,34 @@ function createMethodsRPC(project, methodsOptions = {}) {
7377
7479
  },
7378
7480
  getCountOfFailedTests() {
7379
7481
  return vitest.state.getCountOfFailedTests();
7482
+ },
7483
+ ensureModuleGraphEntry(id, importer) {
7484
+ const filepath = id.startsWith("file:") ? fileURLToPath(id) : id;
7485
+ const importerPath = importer.startsWith("file:") ? fileURLToPath(importer) : importer;
7486
+ // environment itself doesn't matter
7487
+ const moduleGraph = project.vite.environments.__vitest__?.moduleGraph;
7488
+ if (!moduleGraph) {
7489
+ // TODO: is it possible?
7490
+ console.error("no module graph for", id);
7491
+ return;
7492
+ }
7493
+ const importerNode = moduleGraph.getModuleById(importerPath) || moduleGraph.createFileOnlyEntry(importerPath);
7494
+ const moduleNode = moduleGraph.getModuleById(filepath) || moduleGraph.createFileOnlyEntry(filepath);
7495
+ if (!moduleGraph.idToModuleMap.has(importerPath)) {
7496
+ importerNode.id = importerPath;
7497
+ moduleGraph.idToModuleMap.set(importerPath, importerNode);
7498
+ }
7499
+ if (!moduleGraph.idToModuleMap.has(filepath)) {
7500
+ moduleNode.id = filepath;
7501
+ moduleGraph.idToModuleMap.set(filepath, moduleNode);
7502
+ }
7503
+ // this is checked by the "printError" function - TODO: is there a better way?
7504
+ moduleNode.transformResult = {
7505
+ code: " ",
7506
+ map: null
7507
+ };
7508
+ importerNode.importedModules.add(moduleNode);
7509
+ moduleNode.importers.add(importerNode);
7380
7510
  }
7381
7511
  };
7382
7512
  }
@@ -8218,7 +8348,7 @@ function createPool(ctx) {
8218
8348
  const taskGroups = [];
8219
8349
  let workerId = 0;
8220
8350
  const sorted = await sequencer.sort(specs);
8221
- const environments = await getSpecificationsEnvironments(specs);
8351
+ const { environments, tags } = await getSpecificationsOptions(specs);
8222
8352
  const groups = groupSpecs(sorted, environments);
8223
8353
  const projectEnvs = /* @__PURE__ */ new WeakMap();
8224
8354
  const projectExecArgvs = /* @__PURE__ */ new WeakMap();
@@ -8260,9 +8390,11 @@ function createPool(ctx) {
8260
8390
  context: {
8261
8391
  files: specs.map((spec) => ({
8262
8392
  filepath: spec.moduleId,
8393
+ fileTags: tags.get(spec),
8263
8394
  testLocations: spec.testLines,
8264
8395
  testNamePattern: spec.testNamePattern,
8265
- testIds: spec.testIds
8396
+ testIds: spec.testIds,
8397
+ testTagsFilter: spec.testTagsFilter
8266
8398
  })),
8267
8399
  invalidates,
8268
8400
  providedContext: project.getProvidedContext(),
@@ -8536,8 +8668,13 @@ function serializeConfig(project) {
8536
8668
  experimental: {
8537
8669
  fsModuleCache: config.experimental.fsModuleCache ?? false,
8538
8670
  printImportBreakdown: config.experimental.printImportBreakdown,
8671
+ viteModuleRunner: config.experimental.viteModuleRunner ?? true,
8672
+ nodeLoader: config.experimental.nodeLoader ?? true,
8539
8673
  openTelemetry: config.experimental.openTelemetry
8540
- }
8674
+ },
8675
+ tags: config.tags || [],
8676
+ tagsFilter: config.tagsFilter,
8677
+ strictTags: config.strictTags ?? true
8541
8678
  };
8542
8679
  }
8543
8680
 
@@ -9595,15 +9732,12 @@ function WorkspaceVitestPlugin(project, options) {
9595
9732
  label: name,
9596
9733
  color
9597
9734
  } };
9735
+ vitestConfig.experimental ??= {};
9598
9736
  // always inherit the global `fsModuleCache` value even without `extends: true`
9599
- if (testConfig.experimental?.fsModuleCache == null && project.vitest.config.experimental?.fsModuleCache !== null) {
9600
- vitestConfig.experimental ??= {};
9601
- vitestConfig.experimental.fsModuleCache = project.vitest.config.experimental.fsModuleCache;
9602
- }
9603
- if (testConfig.experimental?.fsModuleCachePath == null && project.vitest.config.experimental?.fsModuleCachePath !== null) {
9604
- vitestConfig.experimental ??= {};
9605
- vitestConfig.experimental.fsModuleCachePath = project.vitest.config.experimental.fsModuleCachePath;
9606
- }
9737
+ if (testConfig.experimental?.fsModuleCache == null && project.vitest.config.experimental?.fsModuleCache != null) vitestConfig.experimental.fsModuleCache = project.vitest.config.experimental.fsModuleCache;
9738
+ if (testConfig.experimental?.fsModuleCachePath == null && project.vitest.config.experimental?.fsModuleCachePath != null) vitestConfig.experimental.fsModuleCachePath = project.vitest.config.experimental.fsModuleCachePath;
9739
+ if (testConfig.experimental?.viteModuleRunner == null && project.vitest.config.experimental?.viteModuleRunner != null) vitestConfig.experimental.viteModuleRunner = project.vitest.config.experimental.viteModuleRunner;
9740
+ if (testConfig.experimental?.nodeLoader == null && project.vitest.config.experimental?.nodeLoader != null) vitestConfig.experimental.nodeLoader = project.vitest.config.experimental.nodeLoader;
9607
9741
  return {
9608
9742
  base: "/",
9609
9743
  environments: { __vitest__: { dev: {} } },
@@ -9851,6 +9985,10 @@ class TestSpecification {
9851
9985
  */
9852
9986
  testIds;
9853
9987
  /**
9988
+ * The tags of tests to run.
9989
+ */
9990
+ testTagsFilter;
9991
+ /**
9854
9992
  * This class represents a test suite for a test module within a single project.
9855
9993
  * @internal
9856
9994
  */
@@ -9866,6 +10004,7 @@ class TestSpecification {
9866
10004
  this.testLines = testLinesOrOptions.testLines;
9867
10005
  this.testNamePattern = testLinesOrOptions.testNamePattern;
9868
10006
  this.testIds = testLinesOrOptions.testIds;
10007
+ this.testTagsFilter = testLinesOrOptions.testTagsFilter;
9869
10008
  }
9870
10009
  }
9871
10010
  /**
@@ -9887,40 +10026,13 @@ class TestSpecification {
9887
10026
  pool: this.pool,
9888
10027
  testLines: this.testLines,
9889
10028
  testIds: this.testIds,
9890
- testNamePattern: this.testNamePattern
10029
+ testNamePattern: this.testNamePattern,
10030
+ testTagsFilter: this.testTagsFilter
9891
10031
  }
9892
10032
  ];
9893
10033
  }
9894
10034
  }
9895
10035
 
9896
- async function createViteServer(inlineConfig) {
9897
- // Vite prints an error (https://github.com/vitejs/vite/issues/14328)
9898
- // But Vitest works correctly either way
9899
- const error = console.error;
9900
- console.error = (...args) => {
9901
- if (typeof args[0] === "string" && args[0].includes("WebSocket server error:")) return;
9902
- error(...args);
9903
- };
9904
- const server = await createServer(inlineConfig);
9905
- console.error = error;
9906
- return server;
9907
- }
9908
- function isFileServingAllowed(configOrUrl, urlOrServer) {
9909
- const config = typeof urlOrServer === "string" ? configOrUrl : urlOrServer.config;
9910
- const url = typeof urlOrServer === "string" ? urlOrServer : configOrUrl;
9911
- if (!config.server.fs.strict) return true;
9912
- return isFileLoadingAllowed(config, fsPathFromUrl(url));
9913
- }
9914
- const FS_PREFIX = "/@fs/";
9915
- const VOLUME_RE = /^[A-Z]:/i;
9916
- function fsPathFromId(id) {
9917
- const fsPath = normalizePath(id.startsWith(FS_PREFIX) ? id.slice(5) : id);
9918
- return fsPath[0] === "/" || VOLUME_RE.test(fsPath) ? fsPath : `/${fsPath}`;
9919
- }
9920
- function fsPathFromUrl(url) {
9921
- return fsPathFromId(cleanUrl(url));
9922
- }
9923
-
9924
10036
  class TestProject {
9925
10037
  /**
9926
10038
  * The global Vitest instance.
@@ -10261,7 +10373,11 @@ class TestProject {
10261
10373
  this._serializedDefines = createDefinesScript(server.config.define);
10262
10374
  this._fetcher = createFetchModuleFunction(this._resolver, this._config, this.vitest._fsCache, this.vitest._traces, this.tmpDir);
10263
10375
  const environment = server.environments.__vitest__;
10264
- this.runner = new ServerModuleRunner(environment, this._fetcher, this._config);
10376
+ this.runner = this._config.experimental.viteModuleRunner === false ? new NativeModuleRunner(this._config.root) : new ServerModuleRunner(environment, this._fetcher, this._config);
10377
+ }
10378
+ /** @internal */
10379
+ _getViteEnvironments() {
10380
+ return [...Object.values(this.browser?.vite.environments || {}), ...Object.values(this.vite.environments || {})];
10265
10381
  }
10266
10382
  _serializeOverriddenConfig() {
10267
10383
  // TODO: serialize the config _once_ or when needed
@@ -10376,7 +10492,8 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
10376
10492
  "printConsoleTrace",
10377
10493
  "inspect",
10378
10494
  "inspectBrk",
10379
- "fileParallelism"
10495
+ "fileParallelism",
10496
+ "tagsFilter"
10380
10497
  ].reduce((acc, name) => {
10381
10498
  if (name in cliOptions) acc[name] = cliOptions[name];
10382
10499
  return acc;
@@ -10395,6 +10512,14 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
10395
10512
  ...options,
10396
10513
  root,
10397
10514
  configFile,
10515
+ plugins: [{
10516
+ name: "vitest:tags",
10517
+ configResolved(config) {
10518
+ config.test ??= {};
10519
+ config.test.tags = options.test?.tags;
10520
+ },
10521
+ api: { vitest: { experimental: { ignoreFsModuleCache: true } } }
10522
+ }, ...options.plugins || []],
10398
10523
  test: {
10399
10524
  ...options.test,
10400
10525
  ...cliOverrides
@@ -10861,6 +10986,10 @@ class TestCase extends ReportedTaskImplementation {
10861
10986
  * Parent suite. If the test was called directly inside the module, the parent will be the module itself.
10862
10987
  */
10863
10988
  parent;
10989
+ /**
10990
+ * Tags associated with the test.
10991
+ */
10992
+ tags;
10864
10993
  /** @internal */
10865
10994
  constructor(task, project) {
10866
10995
  super(task, project);
@@ -10870,6 +10999,7 @@ class TestCase extends ReportedTaskImplementation {
10870
10999
  if (suite) this.parent = getReportedTask(project, suite);
10871
11000
  else this.parent = this.module;
10872
11001
  this.options = buildOptions(task);
11002
+ this.tags = this.options.tags || [];
10873
11003
  }
10874
11004
  /**
10875
11005
  * Full name of the test including all parent suites separated with `>`.
@@ -10946,6 +11076,13 @@ class TestCase extends ReportedTaskImplementation {
10946
11076
  flaky: !!result.retryCount && result.state === "pass" && result.retryCount > 0
10947
11077
  };
10948
11078
  }
11079
+ /**
11080
+ * Returns a new test specification that can be used to filter or run this specific test case.
11081
+ */
11082
+ toTestSpecification() {
11083
+ const isTypecheck = this.task.meta.typecheck === true;
11084
+ return this.project.createSpecification(this.module.moduleId, { testIds: [this.id] }, isTypecheck ? "typecheck" : void 0);
11085
+ }
10949
11086
  }
10950
11087
  class TestCollection {
10951
11088
  #task;
@@ -11065,6 +11202,14 @@ class TestSuite extends SuiteImplementation {
11065
11202
  return getSuiteState(this.task);
11066
11203
  }
11067
11204
  /**
11205
+ * Returns a new test specification that can be used to filter or run this specific test suite.
11206
+ */
11207
+ toTestSpecification() {
11208
+ const isTypecheck = this.task.meta.typecheck === true;
11209
+ const testIds = [...this.children.allTests()].map((test) => test.id);
11210
+ return this.project.createSpecification(this.module.moduleId, { testIds }, isTypecheck ? "typecheck" : void 0);
11211
+ }
11212
+ /**
11068
11213
  * Full name of the suite including all parent suites separated with `>`.
11069
11214
  */
11070
11215
  get fullName() {
@@ -11100,6 +11245,13 @@ class TestModule extends SuiteImplementation {
11100
11245
  else if (typeof task.viteEnvironment === "string") this.viteEnvironment = project.vite.environments[task.viteEnvironment];
11101
11246
  }
11102
11247
  /**
11248
+ * Returns a new test specification that can be used to filter or run this specific test module.
11249
+ */
11250
+ toTestSpecification() {
11251
+ const isTypecheck = this.task.meta.typecheck === true;
11252
+ return this.project.createSpecification(this.moduleId, void 0, isTypecheck ? "typecheck" : void 0);
11253
+ }
11254
+ /**
11103
11255
  * Checks the running state of the test file.
11104
11256
  */
11105
11257
  state() {
@@ -11133,6 +11285,8 @@ function buildOptions(task) {
11133
11285
  shuffle: task.shuffle,
11134
11286
  retry: task.retry,
11135
11287
  repeats: task.repeats,
11288
+ tags: task.tags,
11289
+ timeout: task.type === "test" ? task.timeout : void 0,
11136
11290
  mode: task.mode
11137
11291
  };
11138
11292
  }
@@ -11313,6 +11467,19 @@ class StateManager {
11313
11467
  }
11314
11468
  }
11315
11469
 
11470
+ function populateProjectsTags(rootProject, projects) {
11471
+ // Include root project if not already in the list
11472
+ const allProjects = projects.includes(rootProject) ? projects : [rootProject, ...projects];
11473
+ // Collect all tags from all projects (first definition wins)
11474
+ const globalTags = /* @__PURE__ */ new Map();
11475
+ for (const project of allProjects) for (const tag of project.config.tags || []) if (!globalTags.has(tag.name)) globalTags.set(tag.name, tag);
11476
+ // Add missing tags to each project (without overriding local definitions)
11477
+ for (const project of allProjects) {
11478
+ const projectTagNames = new Set(project.config.tags.map((t) => t.name));
11479
+ for (const [tagName, tagDef] of globalTags) if (!projectTagNames.has(tagName)) project.config.tags.push(tagDef);
11480
+ }
11481
+ }
11482
+
11316
11483
  const types = {
11317
11484
  'application/andrew-inset': ['ez'],
11318
11485
  'application/appinstaller': ['appinstaller'],
@@ -11952,6 +12119,9 @@ class VitestWatcher {
11952
12119
  this._onRerun.push(cb);
11953
12120
  return this;
11954
12121
  }
12122
+ close() {
12123
+ this.vitest.vite.watcher.close();
12124
+ }
11955
12125
  unregisterWatcher = noop;
11956
12126
  registerWatcher() {
11957
12127
  const watcher = this.vitest.vite.watcher;
@@ -12051,7 +12221,9 @@ class VitestWatcher {
12051
12221
  }
12052
12222
  if (this.handleSetupFile(filepath)) return true;
12053
12223
  const projects = this.vitest.projects.filter((project) => {
12054
- return (project.browser?.vite.moduleGraph || project.vite.moduleGraph).getModulesByFile(filepath)?.size;
12224
+ return project._getViteEnvironments().some(({ moduleGraph }) => {
12225
+ return moduleGraph.getModulesByFile(filepath)?.size;
12226
+ });
12055
12227
  });
12056
12228
  if (!projects.length) {
12057
12229
  // if there are no modules it's possible that server was restarted
@@ -12064,8 +12236,8 @@ class VitestWatcher {
12064
12236
  }
12065
12237
  const files = [];
12066
12238
  for (const project of projects) {
12067
- const mods = project.browser?.vite.moduleGraph.getModulesByFile(filepath) || project.vite.moduleGraph.getModulesByFile(filepath);
12068
- if (!mods || !mods.size) continue;
12239
+ const environmentMods = project._getViteEnvironments().map(({ moduleGraph }) => moduleGraph.getModulesByFile(filepath));
12240
+ if (!environmentMods.length) continue;
12069
12241
  this.invalidates.add(filepath);
12070
12242
  // one of test files that we already run, or one of test files that we can run
12071
12243
  if (this.vitest.state.filesMap.has(filepath) || project._isCachedTestFile(filepath)) {
@@ -12074,7 +12246,7 @@ class VitestWatcher {
12074
12246
  continue;
12075
12247
  }
12076
12248
  let rerun = false;
12077
- for (const mod of mods) mod.importers.forEach((i) => {
12249
+ for (const mods of environmentMods) for (const mod of mods || []) mod.importers.forEach((i) => {
12078
12250
  if (!i.file) return;
12079
12251
  if (this.handleFileChanged(i.file)) rerun = true;
12080
12252
  });
@@ -12136,6 +12308,7 @@ class Vitest {
12136
12308
  /** @internal */ reporters = [];
12137
12309
  /** @internal */ runner;
12138
12310
  /** @internal */ _testRun = void 0;
12311
+ /** @internal */ _config;
12139
12312
  /** @internal */ _resolver;
12140
12313
  /** @internal */ _fetcher;
12141
12314
  /** @internal */ _fsCache;
@@ -12145,7 +12318,6 @@ class Vitest {
12145
12318
  restartsCount = 0;
12146
12319
  specifications;
12147
12320
  pool;
12148
- _config;
12149
12321
  _vite;
12150
12322
  _state;
12151
12323
  _cache;
@@ -12233,7 +12405,7 @@ class Vitest {
12233
12405
  this._fsCache = new FileSystemModuleCache(this);
12234
12406
  this._fetcher = createFetchModuleFunction(this._resolver, this._config, this._fsCache, this._traces, this._tmpDir);
12235
12407
  const environment = server.environments.__vitest__;
12236
- this.runner = new ServerModuleRunner(environment, this._fetcher, resolved);
12408
+ this.runner = resolved.experimental.viteModuleRunner === false ? new NativeModuleRunner(resolved.root) : new ServerModuleRunner(environment, this._fetcher, resolved);
12237
12409
  if (this.config.watch) {
12238
12410
  // hijack server restart
12239
12411
  const serverRestart = server.restart;
@@ -12282,6 +12454,9 @@ class Vitest {
12282
12454
  }
12283
12455
  if (!this.coreWorkspaceProject) this.coreWorkspaceProject = TestProject._createBasicProject(this);
12284
12456
  if (this.config.testNamePattern) this.configOverride.testNamePattern = this.config.testNamePattern;
12457
+ // populate will merge all configs into every project,
12458
+ // we don't want that when just listing tags
12459
+ if (!this.config.listTags) populateProjectsTags(this.coreWorkspaceProject, this.projects);
12285
12460
  this.reporters = resolved.mode === "benchmark" ? await createBenchmarkReporters(toArray(resolved.benchmark?.reporters), this.runner) : await createReporters(resolved.reporters, this);
12286
12461
  await this._fsCache.ensureCacheIntegrity();
12287
12462
  await Promise.all([...this._onSetServer.map((fn) => fn()), this._traces.waitInit()]);
@@ -12291,6 +12466,24 @@ class Vitest {
12291
12466
  if (this.configOverride.coverage?.enabled === false) return null;
12292
12467
  return this._coverageProvider;
12293
12468
  }
12469
+ async listTags() {
12470
+ const listTags = this.config.listTags;
12471
+ if (typeof listTags === "boolean") this.logger.printTags();
12472
+ else if (listTags === "json") if (![this.getRootProject(), ...this.projects].some((p) => p.config.tags && p.config.tags.length > 0)) {
12473
+ process.exitCode = 1;
12474
+ this.logger.printNoTestTagsFound();
12475
+ } else {
12476
+ const manifest = {
12477
+ tags: this.config.tags,
12478
+ projects: this.projects.filter((p) => p !== this.coreWorkspaceProject).map((p) => ({
12479
+ name: p.name,
12480
+ tags: p.config.tags
12481
+ }))
12482
+ };
12483
+ this.logger.log(JSON.stringify(manifest, null, 2));
12484
+ }
12485
+ else throw new Error(`Unknown value for "test.listTags": ${listTags}`);
12486
+ }
12294
12487
  async enableCoverage() {
12295
12488
  this.configOverride.coverage = {};
12296
12489
  this.configOverride.coverage.enabled = true;
@@ -12793,8 +12986,12 @@ class Vitest {
12793
12986
  async rerunTask(id) {
12794
12987
  const task = this.state.idMap.get(id);
12795
12988
  if (!task) throw new Error(`Task ${id} was not found`);
12796
- const taskNamePattern = task.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12797
- await this.changeNamePattern(taskNamePattern, [task.file.filepath], "tasks" in task ? "rerun suite" : "rerun test");
12989
+ const reportedTask = this.state.getReportedEntityById(id);
12990
+ if (!reportedTask) throw new Error(`Test specification for task ${id} was not found`);
12991
+ const specifications = [reportedTask.toTestSpecification()];
12992
+ await Promise.all([this.report("onWatcherRerun", [task.file.filepath], "tasks" in task ? "rerun suite" : "rerun test"), ...this._onUserTestsRerun.map((fn) => fn(specifications))]);
12993
+ await this.runFiles(specifications, false);
12994
+ await this.report("onWatcherStart", ["module" in reportedTask ? reportedTask.module.task : reportedTask.task]);
12798
12995
  }
12799
12996
  /** @internal */
12800
12997
  async changeProjectName(pattern) {
@@ -13558,7 +13755,8 @@ async function startVitest(mode, cliFilters = [], options = {}, viteOverrides, v
13558
13755
  else ctx.start(cliFilters);
13559
13756
  });
13560
13757
  try {
13561
- if (ctx.config.clearCache) await ctx.experimental_clearCache();
13758
+ if (ctx.config.listTags) await ctx.listTags();
13759
+ else if (ctx.config.clearCache) await ctx.experimental_clearCache();
13562
13760
  else if (ctx.config.mergeReports) await ctx.mergeReports();
13563
13761
  else if (ctx.config.standalone) await ctx.init();
13564
13762
  else await ctx.start(cliFilters);