vitest 3.1.3 → 3.2.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 (57) hide show
  1. package/LICENSE.md +0 -232
  2. package/dist/browser.d.ts +4 -2
  3. package/dist/browser.js +3 -4
  4. package/dist/chunks/{base.DslwPSCy.js → base.DwtwORaC.js} +2 -2
  5. package/dist/chunks/{cac.BN2e7cE1.js → cac.I9MLYfT-.js} +14 -10
  6. package/dist/chunks/{cli-api.Bti1vevt.js → cli-api.d6IK1pnk.js} +945 -852
  7. package/dist/chunks/{coverage.87S59-Sl.js → coverage.OGU09Jbh.js} +129 -4216
  8. package/dist/chunks/{creator.CuL7xDWI.js → creator.DGAdZ4Hj.js} +18 -39
  9. package/dist/chunks/{environment.d.Dmw5ulng.d.ts → environment.d.D8YDy2v5.d.ts} +2 -1
  10. package/dist/chunks/{execute.BpmIjFTD.js → execute.JlGHLJZT.js} +3 -5
  11. package/dist/chunks/{global.d.CXRAxnWc.d.ts → global.d.BPa1eL3O.d.ts} +17 -12
  12. package/dist/chunks/{globals.CZAEe_Gf.js → globals.CpxW8ccg.js} +2 -3
  13. package/dist/chunks/{index.Bw6JxgX8.js → index.CK1YOQaa.js} +7 -7
  14. package/dist/chunks/{index.De2FqGmR.js → index.CV36oG_L.js} +896 -957
  15. package/dist/chunks/{index.B0uVAVvx.js → index.CfXMNXHg.js} +2 -14
  16. package/dist/chunks/index.CmC5OK9L.js +275 -0
  17. package/dist/chunks/{index.Cu2UlluP.js → index.DswW_LEs.js} +2 -2
  18. package/dist/chunks/{index.DBIGubLC.js → index.X0nbfr6-.js} +7 -7
  19. package/dist/chunks/{reporters.d.DG9VKi4m.d.ts → reporters.d.CLC9rhKy.d.ts} +68 -11
  20. package/dist/chunks/{runBaseTests.BV8m0B-u.js → runBaseTests.Dn2vyej_.js} +5 -6
  21. package/dist/chunks/{setup-common.AQcDs321.js → setup-common.CYo3Y0dD.js} +1 -3
  22. package/dist/chunks/typechecker.DnTrplSJ.js +897 -0
  23. package/dist/chunks/{vi.ClIskdbk.js → vi.BFR5YIgu.js} +3 -0
  24. package/dist/chunks/{vite.d.D3ndlJcw.d.ts → vite.d.CBZ3M_ru.d.ts} +1 -1
  25. package/dist/chunks/{vm.CuLHT1BG.js → vm.C1HHjtNS.js} +1 -1
  26. package/dist/chunks/{worker.d.CHGSOG0s.d.ts → worker.d.CoCI7hzP.d.ts} +1 -1
  27. package/dist/chunks/{worker.d.C-KN07Ls.d.ts → worker.d.D5Xdi-Zr.d.ts} +1 -1
  28. package/dist/cli.js +20 -1
  29. package/dist/config.cjs +3 -0
  30. package/dist/config.d.ts +8 -5
  31. package/dist/config.js +3 -0
  32. package/dist/coverage.d.ts +3 -3
  33. package/dist/coverage.js +4 -7
  34. package/dist/environments.d.ts +2 -2
  35. package/dist/execute.d.ts +2 -2
  36. package/dist/execute.js +1 -1
  37. package/dist/index.d.ts +45 -32
  38. package/dist/index.js +2 -3
  39. package/dist/node.d.ts +8 -8
  40. package/dist/node.js +16 -18
  41. package/dist/reporters.d.ts +3 -3
  42. package/dist/reporters.js +14 -14
  43. package/dist/runners.d.ts +1 -1
  44. package/dist/runners.js +2 -2
  45. package/dist/workers/forks.js +2 -2
  46. package/dist/workers/runVmTests.js +5 -6
  47. package/dist/workers/threads.js +2 -2
  48. package/dist/workers/vmForks.js +2 -2
  49. package/dist/workers/vmThreads.js +2 -2
  50. package/dist/workers.d.ts +3 -3
  51. package/dist/workers.js +3 -3
  52. package/package.json +15 -19
  53. package/dist/chunks/run-once.Dimr7O9f.js +0 -47
  54. package/dist/chunks/typechecker.DYQbn8uK.js +0 -956
  55. package/dist/chunks/utils.Cc45eY3L.js +0 -200
  56. package/dist/utils.d.ts +0 -3
  57. package/dist/utils.js +0 -2
@@ -1,19 +1,19 @@
1
1
  import { promises, existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { extname, normalize, relative, dirname, resolve, join, basename, isAbsolute } from 'pathe';
3
3
  import { C as CoverageProviderMap } from './coverage.0iPg4Wrz.js';
4
- import p, { resolve as resolve$1 } from 'node:path';
4
+ import path, { resolve as resolve$1 } from 'node:path';
5
5
  import { noop, isPrimitive, createDefer, highlight, toArray, deepMerge, nanoid, slash, deepClone, notNullish } from '@vitest/utils';
6
- import { f as findUp, p as prompt } from './index.DBIGubLC.js';
6
+ import { f as findUp, p as prompt } from './index.X0nbfr6-.js';
7
7
  import * as vite from 'vite';
8
8
  import { searchForWorkspaceRoot, version, mergeConfig, createServer } from 'vite';
9
9
  import { A as API_PATH, c as configFiles, d as defaultBrowserPort, w as workspacesFiles, a as defaultPort } from './constants.BZZyIeIE.js';
10
- import { generateFileHash, createFileTask, limitConcurrency, hasFailed, getTasks, getTests } from '@vitest/runner/utils';
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.BN2e7cE1.js';
14
+ import { v as version$1 } from './cac.I9MLYfT-.js';
15
15
  import { c as createBirpc } from './index.CJ0plNrh.js';
16
- import { p as parse, s as stringify, g as printError, h as generateCodeFrame, b as BenchmarkReportsMap, R as ReportersMap, i as BlobReporter, r as readBlobs, H as HangingProcessReporter } from './index.De2FqGmR.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';
17
17
  import require$$0$3 from 'events';
18
18
  import require$$1$1 from 'https';
19
19
  import require$$2 from 'http';
@@ -28,21 +28,22 @@ import { g as getDefaultExportFromCjs } from './_commonjsHelpers.BFTU3MAI.js';
28
28
  import { parseErrorStacktrace } from '@vitest/utils/source-map';
29
29
  import crypto from 'node:crypto';
30
30
  import { distDir, rootDir } from '../path.js';
31
- import { R as RandomSequencer, i as isPackageExists, h as hash, V as VitestCache, g as getFilePoolName, d as isBrowserEnabled, m as mm, r as resolveConfig, e as groupBy, f as getCoverageProvider, j as createPool, w as wildcardPatternToRegExp, a as resolveApiServerConfig, s as stdout } from './coverage.87S59-Sl.js';
32
- import { c as convertTasksToEvents } from './typechecker.DYQbn8uK.js';
31
+ import { R as RandomSequencer, i as isPackageExists, h as hash, V as VitestCache, g as getFilePoolName, d as isBrowserEnabled, r as resolveConfig, e as groupBy, f as getCoverageProvider, j as createPool, w as wildcardPatternToRegExp, a as resolveApiServerConfig, s as stdout } from './coverage.OGU09Jbh.js';
32
+ import { c as convertTasksToEvents } from './typechecker.DnTrplSJ.js';
33
33
  import { Console } from 'node:console';
34
34
  import c from 'tinyrainbow';
35
- import { c as formatProjectName, w as withLabel, e as errorBanner, d as divider } from './utils.Cc45eY3L.js';
36
35
  import { createRequire } from 'node:module';
37
36
  import url from 'node:url';
38
37
  import { i as isTTY, a as isWindows } from './env.Dq0hM4Xv.js';
39
38
  import { rm } from 'node:fs/promises';
40
39
  import nodeos__default, { tmpdir } from 'node:os';
40
+ import pm from 'picomatch';
41
41
  import { glob, isDynamicPattern } from 'tinyglobby';
42
42
  import { normalizeRequestId, cleanUrl } from 'vite-node/utils';
43
43
  import { hoistMocksPlugin, automockPlugin } from '@vitest/mocker/node';
44
44
  import { c as configDefaults } from './defaults.DSxsTG0h.js';
45
45
  import MagicString from 'magic-string';
46
+ import { a as BenchmarkReportsMap } from './index.CmC5OK9L.js';
46
47
  import assert$1 from 'node:assert';
47
48
  import { serializeError } from '@vitest/utils/error';
48
49
  import readline from 'node:readline';
@@ -5125,6 +5126,12 @@ function setup(ctx, _server) {
5125
5126
  getResolvedProjectNames() {
5126
5127
  return ctx.projects.map((p) => p.name);
5127
5128
  },
5129
+ getResolvedProjectLabels() {
5130
+ return ctx.projects.map((p) => ({
5131
+ name: p.name,
5132
+ color: p.color
5133
+ }));
5134
+ },
5128
5135
  async getTransformResult(projectName, id, browser = false) {
5129
5136
  const project = ctx.getProjectByName(projectName);
5130
5137
  const result = browser ? await project.browser.vite.transformRequest(id) : await project.vitenode.transformRequest(id);
@@ -5409,6 +5416,9 @@ class Logger {
5409
5416
  printError(err, options = {}) {
5410
5417
  printError(err, this.ctx, this, options);
5411
5418
  }
5419
+ deprecate(message) {
5420
+ this.log(c.bold(c.bgYellow(" DEPRECATED ")), c.yellow(message));
5421
+ }
5412
5422
  clearHighlightCache(filename) {
5413
5423
  if (filename) {
5414
5424
  this._highlights.delete(filename);
@@ -5449,7 +5459,7 @@ class Logger {
5449
5459
  const config = project.config;
5450
5460
  const printConfig = !project.isRootProject() && project.name;
5451
5461
  if (printConfig) {
5452
- this.console.error(`\n${formatProjectName(project.name)}\n`);
5462
+ this.console.error(`\n${formatProjectName(project)}\n`);
5453
5463
  }
5454
5464
  if (config.include) {
5455
5465
  this.console.error(c.dim("include: ") + c.yellow(config.include.join(comma)));
@@ -5501,7 +5511,7 @@ class Logger {
5501
5511
  if (!origin) {
5502
5512
  return;
5503
5513
  }
5504
- const output = project.isRootProject() ? "" : formatProjectName(project.name);
5514
+ const output = project.isRootProject() ? "" : formatProjectName(project);
5505
5515
  const provider = project.browser.provider.name;
5506
5516
  const providerString = provider === "preview" ? "" : ` by ${c.reset(c.bold(provider))}`;
5507
5517
  this.log(c.dim(`${output}Browser runner started${providerString} ${c.dim("at")} ${c.blue(new URL("/", origin))}\n`));
@@ -5597,7 +5607,7 @@ class VitestPackageInstaller {
5597
5607
  if (!isTTY) {
5598
5608
  return false;
5599
5609
  }
5600
- const prompts = await import('./index.DBIGubLC.js').then(function (n) { return n.i; });
5610
+ const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; });
5601
5611
  const { install } = await prompts.default({
5602
5612
  type: "confirm",
5603
5613
  name: "install",
@@ -5605,7 +5615,7 @@ class VitestPackageInstaller {
5605
5615
  });
5606
5616
  if (install) {
5607
5617
  const packageName = version ? `${dependency}@${version}` : dependency;
5608
- await (await import('./index.Bw6JxgX8.js')).installPackage(packageName, { dev: true });
5618
+ await (await import('./index.CK1YOQaa.js')).installPackage(packageName, { dev: true });
5609
5619
  process.stderr.write(c.yellow(`\nPackage ${packageName} installed, re-run the command to start.\n`));
5610
5620
  process.exit();
5611
5621
  return true;
@@ -6521,7 +6531,8 @@ function resolveOptimizerConfig(_testOptions, viteOptions, testConfig, viteCache
6521
6531
  const runtime = currentInclude.filter((n) => n.endsWith("jsx-dev-runtime") || n.endsWith("jsx-runtime"));
6522
6532
  exclude.push(...runtime);
6523
6533
  const include = (testOptions.include || viteOptions?.include || []).filter((n) => !exclude.includes(n));
6524
- newConfig.cacheDir = testConfig.cache !== false && testConfig.cache?.dir || VitestCache.resolveCacheDir(root, viteCacheDir, testConfig.name);
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);
6525
6536
  newConfig.optimizeDeps = {
6526
6537
  ...viteOptions,
6527
6538
  ...testOptions,
@@ -6688,7 +6699,10 @@ function WorkspaceVitestPlugin(project, options) {
6688
6699
  const defines = deleteDefineConfig(viteConfig);
6689
6700
  const testConfig = viteConfig.test || {};
6690
6701
  const root = testConfig.root || viteConfig.root || options.root;
6691
- let name = testConfig.name;
6702
+ let { label: name, color } = typeof testConfig.name === "string" ? { label: testConfig.name } : {
6703
+ label: "",
6704
+ ...testConfig.name
6705
+ };
6692
6706
  if (!name) {
6693
6707
  if (typeof options.workspacePath === "string") {
6694
6708
  const dir = options.workspacePath.endsWith("/") ? options.workspacePath.slice(0, -1) : dirname(options.workspacePath);
@@ -6726,7 +6740,10 @@ function WorkspaceVitestPlugin(project, options) {
6726
6740
  fs: { allow: resolveFsAllow(project.vitest.config.root, project.vitest.vite.config.configFile) }
6727
6741
  },
6728
6742
  environments: { ssr: { resolve: resolveOptions } },
6729
- test: { name }
6743
+ test: { name: {
6744
+ label: name,
6745
+ color
6746
+ } }
6730
6747
  };
6731
6748
  if (project.vitest._options.browser && viteConfig.test?.browser) {
6732
6749
  viteConfig.test.browser = mergeConfig(viteConfig.test.browser, project.vitest._options.browser);
@@ -6900,6 +6917,7 @@ class TestProject {
6900
6917
  /** @internal */ typechecker;
6901
6918
  /** @internal */ _config;
6902
6919
  /** @internal */ _vite;
6920
+ /** @internal */ _hash;
6903
6921
  runner;
6904
6922
  closingPromise;
6905
6923
  testFilesList = null;
@@ -6914,6 +6932,17 @@ class TestProject {
6914
6932
  this.globalConfig = vitest.config;
6915
6933
  }
6916
6934
  /**
6935
+ * The unique hash of this project. This value is consistent between the reruns.
6936
+ *
6937
+ * It is based on the root of the project (not consistent between OS) and its name.
6938
+ */
6939
+ 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
+ }
6943
+ return this._hash;
6944
+ }
6945
+ /**
6917
6946
  * Provide a value to the test context. This value will be available to all tests with `inject`.
6918
6947
  */
6919
6948
  provide = (key, value) => {
@@ -6980,6 +7009,12 @@ class TestProject {
6980
7009
  return this.config.name || "";
6981
7010
  }
6982
7011
  /**
7012
+ * The color used when reporting tasks of this project.
7013
+ */
7014
+ get color() {
7015
+ return this.config.color;
7016
+ }
7017
+ /**
6983
7018
  * Serialized project configuration. This is the config that tests receive.
6984
7019
  */
6985
7020
  get serializedConfig() {
@@ -7137,7 +7172,7 @@ class TestProject {
7137
7172
  expandDirectories: false
7138
7173
  };
7139
7174
  const files = await glob(include, globOptions);
7140
- return files.map((file) => slash(p.resolve(cwd, file)));
7175
+ return files.map((file) => slash(path.resolve(cwd, file)));
7141
7176
  }
7142
7177
  /**
7143
7178
  * Test if a file matches the test globs. This does the actual glob matching if the test is not cached, unlike `isCachedTestFile`.
@@ -7147,14 +7182,14 @@ class TestProject {
7147
7182
  return true;
7148
7183
  }
7149
7184
  const relativeId = relative(this.config.dir || this.config.root, moduleId);
7150
- if (mm.isMatch(relativeId, this.config.exclude)) {
7185
+ if (pm.isMatch(relativeId, this.config.exclude)) {
7151
7186
  return false;
7152
7187
  }
7153
- if (mm.isMatch(relativeId, this.config.include)) {
7188
+ if (pm.isMatch(relativeId, this.config.include)) {
7154
7189
  this.markTestFile(moduleId);
7155
7190
  return true;
7156
7191
  }
7157
- if (this.config.includeSource?.length && mm.isMatch(relativeId, this.config.includeSource)) {
7192
+ if (this.config.includeSource?.length && pm.isMatch(relativeId, this.config.includeSource)) {
7158
7193
  const code = source?.() || readFileSync(moduleId, "utf-8");
7159
7194
  if (this.isInSourceTestCode(code)) {
7160
7195
  this.markTestFile(moduleId);
@@ -7256,12 +7291,16 @@ class TestProject {
7256
7291
  setServer(options, server) {
7257
7292
  return this._configureServer(options, server);
7258
7293
  }
7294
+ _setHash() {
7295
+ this._hash = generateHash(this._config.root + this._config.name);
7296
+ }
7259
7297
  /** @internal */
7260
7298
  async _configureServer(options, server) {
7261
7299
  this._config = resolveConfig(this.vitest, {
7262
7300
  ...options,
7263
7301
  coverage: this.vitest.config.coverage
7264
7302
  }, server.config);
7303
+ this._setHash();
7265
7304
  for (const _providedKey in this.config.provide) {
7266
7305
  const providedKey = _providedKey;
7267
7306
  this.provide(providedKey, this.config.provide[providedKey]);
@@ -7321,6 +7360,7 @@ class TestProject {
7321
7360
  project.runner = vitest.runner;
7322
7361
  project._vite = vitest.server;
7323
7362
  project._config = vitest.config;
7363
+ project._setHash();
7324
7364
  project._provideObject(vitest.config.provide);
7325
7365
  return project;
7326
7366
  }
@@ -7331,6 +7371,7 @@ class TestProject {
7331
7371
  clone.runner = parent.runner;
7332
7372
  clone._vite = parent._vite;
7333
7373
  clone._config = config;
7374
+ clone._setHash();
7334
7375
  clone._parent = parent;
7335
7376
  clone._provideObject(config.provide);
7336
7377
  return clone;
@@ -7363,183 +7404,513 @@ async function initializeProject(workspacePath, ctx, options) {
7363
7404
  await createViteServer(config);
7364
7405
  return project;
7365
7406
  }
7366
-
7367
- async function loadCustomReporterModule(path, runner) {
7368
- let customReporterModule;
7369
- try {
7370
- customReporterModule = await runner.executeId(path);
7371
- } catch (customReporterModuleError) {
7372
- throw new Error(`Failed to load custom Reporter from ${path}`, { cause: customReporterModuleError });
7373
- }
7374
- if (customReporterModule.default === null || customReporterModule.default === undefined) {
7375
- throw new Error(`Custom reporter loaded from ${path} was not the default export`);
7376
- }
7377
- return customReporterModule.default;
7378
- }
7379
- function createReporters(reporterReferences, ctx) {
7380
- const runner = ctx.runner;
7381
- const promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
7382
- if (Array.isArray(referenceOrInstance)) {
7383
- const [reporterName, reporterOptions] = referenceOrInstance;
7384
- if (reporterName === "html") {
7385
- await ctx.packageInstaller.ensureInstalled("@vitest/ui", runner.root, ctx.version);
7386
- const CustomReporter = await loadCustomReporterModule("@vitest/ui/reporter", runner);
7387
- return new CustomReporter(reporterOptions);
7388
- } else if (reporterName in ReportersMap) {
7389
- const BuiltinReporter = ReportersMap[reporterName];
7390
- return new BuiltinReporter(reporterOptions);
7391
- } else {
7392
- const CustomReporter = await loadCustomReporterModule(reporterName, runner);
7393
- return new CustomReporter(reporterOptions);
7394
- }
7395
- }
7396
- return referenceOrInstance;
7397
- });
7398
- return Promise.all(promisedReporters);
7399
- }
7400
- function createBenchmarkReporters(reporterReferences, runner) {
7401
- const promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
7402
- if (typeof referenceOrInstance === "string") {
7403
- if (referenceOrInstance in BenchmarkReportsMap) {
7404
- const BuiltinReporter = BenchmarkReportsMap[referenceOrInstance];
7405
- return new BuiltinReporter();
7406
- } else {
7407
- const CustomReporter = await loadCustomReporterModule(referenceOrInstance, runner);
7408
- return new CustomReporter();
7409
- }
7410
- }
7411
- return referenceOrInstance;
7412
- });
7413
- return Promise.all(promisedReporters);
7414
- }
7415
-
7416
- function parseFilter(filter) {
7417
- const colonIndex = filter.lastIndexOf(":");
7418
- if (colonIndex === -1) {
7419
- return { filename: filter };
7407
+ function generateHash(str) {
7408
+ let hash = 0;
7409
+ if (str.length === 0) {
7410
+ return `${hash}`;
7420
7411
  }
7421
- const [parsedFilename, lineNumber] = [filter.substring(0, colonIndex), filter.substring(colonIndex + 1)];
7422
- if (lineNumber.match(/^\d+$/)) {
7423
- return {
7424
- filename: parsedFilename,
7425
- lineNumber: Number.parseInt(lineNumber)
7426
- };
7427
- } else if (lineNumber.match(/^\d+-\d+$/)) {
7428
- throw new RangeLocationFilterProvidedError(filter);
7429
- } else {
7430
- return { filename: filter };
7412
+ for (let i = 0; i < str.length; i++) {
7413
+ const char = str.charCodeAt(i);
7414
+ hash = (hash << 5) - hash + char;
7415
+ hash = hash & hash;
7431
7416
  }
7432
- }
7433
- function groupFilters(filters) {
7434
- const groupedFilters_ = groupBy(filters, (f) => f.filename);
7435
- const groupedFilters = Object.fromEntries(Object.entries(groupedFilters_).map((entry) => {
7436
- const [filename, filters] = entry;
7437
- const testLocations = filters.map((f) => f.lineNumber);
7438
- return [filename, testLocations.filter((l) => l !== undefined)];
7439
- }));
7440
- return groupedFilters;
7417
+ return `${hash}`;
7441
7418
  }
7442
7419
 
7443
- class VitestSpecifications {
7444
- _cachedSpecs = new Map();
7445
- constructor(vitest) {
7446
- this.vitest = vitest;
7447
- }
7448
- getModuleSpecifications(moduleId) {
7449
- const _cached = this.getCachedSpecifications(moduleId);
7450
- if (_cached) {
7451
- return _cached;
7420
+ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projectsDefinition, names) {
7421
+ const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition);
7422
+ const overridesOptions = [
7423
+ "logHeapUsage",
7424
+ "allowOnly",
7425
+ "sequence",
7426
+ "testTimeout",
7427
+ "pool",
7428
+ "update",
7429
+ "globals",
7430
+ "expandSnapshotDiff",
7431
+ "disableConsoleIntercept",
7432
+ "retry",
7433
+ "testNamePattern",
7434
+ "passWithNoTests",
7435
+ "bail",
7436
+ "isolate",
7437
+ "printConsoleTrace",
7438
+ "inspect",
7439
+ "inspectBrk",
7440
+ "fileParallelism"
7441
+ ];
7442
+ const cliOverrides = overridesOptions.reduce((acc, name) => {
7443
+ if (name in cliOptions) {
7444
+ acc[name] = cliOptions[name];
7452
7445
  }
7453
- const specs = [];
7454
- for (const project of this.vitest.projects) {
7455
- if (project._isCachedTestFile(moduleId)) {
7456
- specs.push(project.createSpecification(moduleId));
7446
+ return acc;
7447
+ }, {});
7448
+ const projectPromises = [];
7449
+ const fileProjects = [...configFiles, ...nonConfigDirectories];
7450
+ const concurrent = limitConcurrency(nodeos__default.availableParallelism?.() || nodeos__default.cpus().length || 5);
7451
+ projectConfigs.forEach((options, index) => {
7452
+ const configRoot = workspaceConfigPath ? dirname(workspaceConfigPath) : vitest.config.root;
7453
+ const configFile = typeof options.extends === "string" ? resolve(configRoot, options.extends) : options.extends === true ? vitest.vite.config.configFile || false : false;
7454
+ const root = options.root ? resolve(configRoot, options.root) : vitest.config.root;
7455
+ projectPromises.push(concurrent(() => initializeProject(index, vitest, {
7456
+ ...options,
7457
+ root,
7458
+ configFile,
7459
+ test: {
7460
+ ...options.test,
7461
+ ...cliOverrides
7457
7462
  }
7458
- if (project._isCachedTypecheckFile(moduleId)) {
7459
- specs.push(project.createSpecification(moduleId, [], "typescript"));
7463
+ })));
7464
+ });
7465
+ for (const path of fileProjects) {
7466
+ if (vitest.vite.config.configFile === path) {
7467
+ const project = getDefaultTestProject(vitest);
7468
+ if (project) {
7469
+ projectPromises.push(Promise.resolve(project));
7460
7470
  }
7471
+ continue;
7461
7472
  }
7462
- specs.forEach((spec) => this.ensureSpecificationCached(spec));
7463
- return specs;
7473
+ const configFile = path.endsWith("/") ? false : path;
7474
+ const root = path.endsWith("/") ? path : dirname(path);
7475
+ projectPromises.push(concurrent(() => initializeProject(path, vitest, {
7476
+ root,
7477
+ configFile,
7478
+ test: cliOverrides
7479
+ })));
7464
7480
  }
7465
- async getRelevantTestSpecifications(filters = []) {
7466
- return this.filterTestsBySource(await this.globTestSpecifications(filters));
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(""));
7467
7487
  }
7468
- async globTestSpecifications(filters = []) {
7469
- const files = [];
7470
- const dir = process.cwd();
7471
- const parsedFilters = filters.map((f) => parseFilter(f));
7472
- if (!this.vitest.config.includeTaskLocation && parsedFilters.some((f) => f.lineNumber !== undefined)) {
7473
- throw new IncludeTaskLocationDisabledError();
7474
- }
7475
- const testLines = groupFilters(parsedFilters.map((f) => ({
7476
- ...f,
7477
- filename: resolve(dir, f.filename)
7478
- })));
7479
- const testLocHasMatch = {};
7480
- await Promise.all(this.vitest.projects.map(async (project) => {
7481
- const { testFiles, typecheckTestFiles } = await project.globTestFiles(parsedFilters.map((f) => f.filename));
7482
- testFiles.forEach((file) => {
7483
- const lines = testLines[file];
7484
- testLocHasMatch[file] = true;
7485
- const spec = project.createSpecification(file, lines);
7486
- this.ensureSpecificationCached(spec);
7487
- files.push(spec);
7488
- });
7489
- typecheckTestFiles.forEach((file) => {
7490
- const lines = testLines[file];
7491
- testLocHasMatch[file] = true;
7492
- const spec = project.createSpecification(file, lines, "typescript");
7493
- this.ensureSpecificationCached(spec);
7494
- files.push(spec);
7495
- });
7496
- }));
7497
- Object.entries(testLines).forEach(([filepath, loc]) => {
7498
- if (loc.length !== 0 && !testLocHasMatch[filepath]) {
7499
- throw new LocationFilterFileNotFoundError(relative(dir, filepath));
7488
+ const resolvedProjectsPromises = await Promise.allSettled(projectPromises);
7489
+ const errors = [];
7490
+ const resolvedProjects = [];
7491
+ for (const result of resolvedProjectsPromises) {
7492
+ if (result.status === "rejected") {
7493
+ if (result.reason instanceof VitestFilteredOutProjectError) {
7494
+ continue;
7500
7495
  }
7501
- });
7502
- return files;
7503
- }
7504
- clearCache(moduleId) {
7505
- if (moduleId) {
7506
- this._cachedSpecs.delete(moduleId);
7496
+ errors.push(result.reason);
7507
7497
  } else {
7508
- this._cachedSpecs.clear();
7498
+ resolvedProjects.push(result.value);
7509
7499
  }
7510
7500
  }
7511
- getCachedSpecifications(moduleId) {
7512
- return this._cachedSpecs.get(moduleId);
7501
+ if (errors.length) {
7502
+ throw new AggregateError(errors, "Failed to initialize projects. There were errors during projects setup. See below for more details.");
7513
7503
  }
7514
- ensureSpecificationCached(spec) {
7515
- const file = spec.moduleId;
7516
- const specs = this._cachedSpecs.get(file) || [];
7517
- const index = specs.findIndex((_s) => _s.project === spec.project && _s.pool === spec.pool);
7518
- if (index === -1) {
7519
- specs.push(spec);
7520
- this._cachedSpecs.set(file, specs);
7521
- } else {
7522
- specs.splice(index, 1, spec);
7504
+ for (const project of resolvedProjects) {
7505
+ const name = project.name;
7506
+ if (names.has(name)) {
7507
+ const duplicate = resolvedProjects.find((p) => p.name === name && p !== project);
7508
+ const filesError = fileProjects.length ? [
7509
+ "\n\nYour config matched these files:\n",
7510
+ fileProjects.map((p) => ` - ${relative(vitest.config.root, p)}`).join("\n"),
7511
+ "\n\n"
7512
+ ].join("") : " ";
7513
+ throw new Error([
7514
+ `Project name "${name}"`,
7515
+ project.vite.config.configFile ? ` from "${relative(vitest.config.root, project.vite.config.configFile)}"` : "",
7516
+ " is not unique.",
7517
+ duplicate?.vite.config.configFile ? ` The project is already defined by "${relative(vitest.config.root, duplicate.vite.config.configFile)}".` : "",
7518
+ filesError,
7519
+ "All projects should have unique names. Make sure your configuration is correct."
7520
+ ].join(""));
7523
7521
  }
7524
- return specs;
7522
+ names.add(name);
7525
7523
  }
7526
- async filterTestsBySource(specs) {
7527
- if (this.vitest.config.changed && !this.vitest.config.related) {
7528
- const { VitestGit } = await import('./git.DXfdBEfR.js');
7529
- const vitestGit = new VitestGit(this.vitest.config.root);
7530
- const related = await vitestGit.findChangedFiles({ changedSince: this.vitest.config.changed });
7531
- if (!related) {
7532
- process.exitCode = 1;
7533
- throw new GitNotFoundError();
7534
- }
7535
- this.vitest.config.related = Array.from(new Set(related));
7524
+ return resolveBrowserProjects(vitest, names, resolvedProjects);
7525
+ }
7526
+ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7527
+ const removeProjects = new Set();
7528
+ resolvedProjects.forEach((project) => {
7529
+ if (!project.config.browser.enabled) {
7530
+ return;
7536
7531
  }
7537
- const related = this.vitest.config.related;
7532
+ const instances = project.config.browser.instances || [];
7533
+ const browser = project.config.browser.name;
7534
+ if (instances.length === 0 && browser) {
7535
+ instances.push({
7536
+ browser,
7537
+ name: project.name ? `${project.name} (${browser})` : browser
7538
+ });
7539
+ vitest.logger.warn(withLabel("yellow", "Vitest", [
7540
+ `No browser "instances" were defined`,
7541
+ project.name ? ` for the "${project.name}" project. ` : ". ",
7542
+ `Running tests in "${project.config.browser.name}" browser. `,
7543
+ "The \"browser.name\" field is deprecated since Vitest 3. ",
7544
+ "Read more: https://vitest.dev/guide/browser/config#browser-instances"
7545
+ ].filter(Boolean).join("")));
7546
+ }
7547
+ const originalName = project.config.name;
7548
+ const filteredInstances = vitest.matchesProjectFilter(originalName) ? instances : instances.filter((instance) => {
7549
+ const newName = instance.name;
7550
+ return vitest.matchesProjectFilter(newName);
7551
+ });
7552
+ if (!filteredInstances.length) {
7553
+ removeProjects.add(project);
7554
+ return;
7555
+ }
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
+ }
7559
+ filteredInstances.forEach((config, index) => {
7560
+ const browser = config.browser;
7561
+ if (!browser) {
7562
+ const nth = index + 1;
7563
+ const ending = nth === 2 ? "nd" : nth === 3 ? "rd" : "th";
7564
+ 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
+ }
7566
+ 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
+ }
7577
+ names.add(name);
7578
+ const clonedConfig = cloneConfig(project, config);
7579
+ clonedConfig.name = name;
7580
+ const clone = TestProject._cloneBrowserProject(project, clonedConfig);
7581
+ resolvedProjects.push(clone);
7582
+ });
7583
+ removeProjects.add(project);
7584
+ });
7585
+ resolvedProjects = resolvedProjects.filter((project) => !removeProjects.has(project));
7586
+ const headedBrowserProjects = resolvedProjects.filter((project) => {
7587
+ return project.config.browser.enabled && !project.config.browser.headless;
7588
+ });
7589
+ if (headedBrowserProjects.length > 1) {
7590
+ 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
+ }
7594
+ const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; });
7595
+ const { projectName } = await prompts.default({
7596
+ type: "select",
7597
+ name: "projectName",
7598
+ choices: headedBrowserProjects.map((project) => ({
7599
+ title: project.name,
7600
+ value: project.name
7601
+ })),
7602
+ 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
+ });
7604
+ if (!projectName) {
7605
+ throw new Error("The test run was aborted.");
7606
+ }
7607
+ return resolvedProjects.filter((project) => project.name === projectName);
7608
+ }
7609
+ return resolvedProjects;
7610
+ }
7611
+ function cloneConfig(project, { browser,...config }) {
7612
+ const { locators, viewport, testerHtmlPath, headless, screenshotDirectory, screenshotFailures, browser: _browser, name,...overrideConfig } = config;
7613
+ const currentConfig = project.config.browser;
7614
+ return mergeConfig({
7615
+ ...deepClone(project.config),
7616
+ browser: {
7617
+ ...project.config.browser,
7618
+ locators: locators ? { testIdAttribute: locators.testIdAttribute ?? currentConfig.locators.testIdAttribute } : project.config.browser.locators,
7619
+ viewport: viewport ?? currentConfig.viewport,
7620
+ testerHtmlPath: testerHtmlPath ?? currentConfig.testerHtmlPath,
7621
+ screenshotDirectory: screenshotDirectory ?? currentConfig.screenshotDirectory,
7622
+ screenshotFailures: screenshotFailures ?? currentConfig.screenshotFailures,
7623
+ headless: headless ?? currentConfig.headless,
7624
+ name: browser,
7625
+ providerOptions: config,
7626
+ instances: undefined
7627
+ }
7628
+ }, overrideConfig);
7629
+ }
7630
+ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition) {
7631
+ const projectsOptions = [];
7632
+ const projectsConfigFiles = [];
7633
+ const projectsGlobMatches = [];
7634
+ 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);
7661
+ }
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
+ }
7673
+ if (projectsGlobMatches.length) {
7674
+ const globOptions = {
7675
+ absolute: true,
7676
+ dot: true,
7677
+ onlyFiles: false,
7678
+ cwd: vitest.config.root,
7679
+ expandDirectories: false,
7680
+ ignore: [
7681
+ "**/node_modules/**",
7682
+ "**/*.timestamp-*",
7683
+ "**/.DS_Store"
7684
+ ]
7685
+ };
7686
+ const projectsFs = await glob(projectsGlobMatches, globOptions);
7687
+ await Promise.all(projectsFs.map(async (path) => {
7688
+ if (path.endsWith("/")) {
7689
+ 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
+ }
7698
+ }));
7699
+ }
7700
+ const projectConfigFiles = Array.from(new Set(projectsConfigFiles));
7701
+ return {
7702
+ projectConfigs: projectsOptions,
7703
+ nonConfigDirectories: nonConfigProjectDirectories,
7704
+ configFiles: projectConfigFiles
7705
+ };
7706
+ }
7707
+ async function resolveDirectoryConfig(directory) {
7708
+ const files = new Set(await promises.readdir(directory));
7709
+ const configFile = configFiles.find((file) => files.has(file));
7710
+ if (configFile) {
7711
+ return resolve(directory, configFile);
7712
+ }
7713
+ return null;
7714
+ }
7715
+ function getDefaultTestProject(vitest) {
7716
+ const filter = vitest.config.project;
7717
+ const project = vitest._ensureRootProject();
7718
+ if (!filter.length) {
7719
+ return project;
7720
+ }
7721
+ const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
7722
+ if (hasProjects) {
7723
+ return project;
7724
+ }
7725
+ return null;
7726
+ }
7727
+ function getPotentialProjectNames(project) {
7728
+ 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
+ }
7734
+ return names;
7735
+ }
7736
+
7737
+ async function loadCustomReporterModule(path, runner) {
7738
+ let customReporterModule;
7739
+ try {
7740
+ customReporterModule = await runner.executeId(path);
7741
+ } catch (customReporterModuleError) {
7742
+ throw new Error(`Failed to load custom Reporter from ${path}`, { cause: customReporterModuleError });
7743
+ }
7744
+ if (customReporterModule.default === null || customReporterModule.default === undefined) {
7745
+ throw new Error(`Custom reporter loaded from ${path} was not the default export`);
7746
+ }
7747
+ return customReporterModule.default;
7748
+ }
7749
+ function createReporters(reporterReferences, ctx) {
7750
+ const runner = ctx.runner;
7751
+ const promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
7752
+ if (Array.isArray(referenceOrInstance)) {
7753
+ const [reporterName, reporterOptions] = referenceOrInstance;
7754
+ if (reporterName === "html") {
7755
+ await ctx.packageInstaller.ensureInstalled("@vitest/ui", runner.root, ctx.version);
7756
+ const CustomReporter = await loadCustomReporterModule("@vitest/ui/reporter", runner);
7757
+ return new CustomReporter(reporterOptions);
7758
+ } else if (reporterName in ReportersMap) {
7759
+ const BuiltinReporter = ReportersMap[reporterName];
7760
+ return new BuiltinReporter(reporterOptions);
7761
+ } else {
7762
+ const CustomReporter = await loadCustomReporterModule(reporterName, runner);
7763
+ return new CustomReporter(reporterOptions);
7764
+ }
7765
+ }
7766
+ return referenceOrInstance;
7767
+ });
7768
+ return Promise.all(promisedReporters);
7769
+ }
7770
+ function createBenchmarkReporters(reporterReferences, runner) {
7771
+ 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
+ }
7780
+ }
7781
+ return referenceOrInstance;
7782
+ });
7783
+ return Promise.all(promisedReporters);
7784
+ }
7785
+
7786
+ function parseFilter(filter) {
7787
+ const colonIndex = filter.lastIndexOf(":");
7788
+ if (colonIndex === -1) {
7789
+ return { filename: filter };
7790
+ }
7791
+ 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
+ }
7802
+ }
7803
+ function groupFilters(filters) {
7804
+ const groupedFilters_ = groupBy(filters, (f) => f.filename);
7805
+ const groupedFilters = Object.fromEntries(Object.entries(groupedFilters_).map((entry) => {
7806
+ const [filename, filters] = entry;
7807
+ const testLocations = filters.map((f) => f.lineNumber);
7808
+ return [filename, testLocations.filter((l) => l !== undefined)];
7809
+ }));
7810
+ return groupedFilters;
7811
+ }
7812
+
7813
+ class VitestSpecifications {
7814
+ _cachedSpecs = new Map();
7815
+ constructor(vitest) {
7816
+ this.vitest = vitest;
7817
+ }
7818
+ getModuleSpecifications(moduleId) {
7819
+ const _cached = this.getCachedSpecifications(moduleId);
7820
+ if (_cached) {
7821
+ return _cached;
7822
+ }
7823
+ const specs = [];
7824
+ 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
+ }
7831
+ }
7832
+ specs.forEach((spec) => this.ensureSpecificationCached(spec));
7833
+ return specs;
7834
+ }
7835
+ async getRelevantTestSpecifications(filters = []) {
7836
+ return this.filterTestsBySource(await this.globTestSpecifications(filters));
7837
+ }
7838
+ async globTestSpecifications(filters = []) {
7839
+ const files = [];
7840
+ const dir = process.cwd();
7841
+ const parsedFilters = filters.map((f) => parseFilter(f));
7842
+ if (!this.vitest.config.includeTaskLocation && parsedFilters.some((f) => f.lineNumber !== undefined)) {
7843
+ throw new IncludeTaskLocationDisabledError();
7844
+ }
7845
+ const testLines = groupFilters(parsedFilters.map((f) => ({
7846
+ ...f,
7847
+ filename: resolve(dir, f.filename)
7848
+ })));
7849
+ const testLocHasMatch = {};
7850
+ await Promise.all(this.vitest.projects.map(async (project) => {
7851
+ const { testFiles, typecheckTestFiles } = await project.globTestFiles(parsedFilters.map((f) => f.filename));
7852
+ testFiles.forEach((file) => {
7853
+ const lines = testLines[file];
7854
+ testLocHasMatch[file] = true;
7855
+ const spec = project.createSpecification(file, lines);
7856
+ this.ensureSpecificationCached(spec);
7857
+ files.push(spec);
7858
+ });
7859
+ typecheckTestFiles.forEach((file) => {
7860
+ const lines = testLines[file];
7861
+ testLocHasMatch[file] = true;
7862
+ const spec = project.createSpecification(file, lines, "typescript");
7863
+ this.ensureSpecificationCached(spec);
7864
+ files.push(spec);
7865
+ });
7866
+ }));
7867
+ Object.entries(testLines).forEach(([filepath, loc]) => {
7868
+ if (loc.length !== 0 && !testLocHasMatch[filepath]) {
7869
+ throw new LocationFilterFileNotFoundError(relative(dir, filepath));
7870
+ }
7871
+ });
7872
+ return files;
7873
+ }
7874
+ clearCache(moduleId) {
7875
+ if (moduleId) {
7876
+ this._cachedSpecs.delete(moduleId);
7877
+ } else {
7878
+ this._cachedSpecs.clear();
7879
+ }
7880
+ }
7881
+ getCachedSpecifications(moduleId) {
7882
+ return this._cachedSpecs.get(moduleId);
7883
+ }
7884
+ ensureSpecificationCached(spec) {
7885
+ const file = spec.moduleId;
7886
+ const specs = this._cachedSpecs.get(file) || [];
7887
+ const index = specs.findIndex((_s) => _s.project === spec.project && _s.pool === spec.pool);
7888
+ if (index === -1) {
7889
+ specs.push(spec);
7890
+ this._cachedSpecs.set(file, specs);
7891
+ } else {
7892
+ specs.splice(index, 1, spec);
7893
+ }
7894
+ return specs;
7895
+ }
7896
+ async filterTestsBySource(specs) {
7897
+ if (this.vitest.config.changed && !this.vitest.config.related) {
7898
+ const { VitestGit } = await import('./git.DXfdBEfR.js');
7899
+ const vitestGit = new VitestGit(this.vitest.config.root);
7900
+ const related = await vitestGit.findChangedFiles({ changedSince: this.vitest.config.changed });
7901
+ if (!related) {
7902
+ process.exitCode = 1;
7903
+ throw new GitNotFoundError();
7904
+ }
7905
+ this.vitest.config.related = Array.from(new Set(related));
7906
+ }
7907
+ const related = this.vitest.config.related;
7538
7908
  if (!related) {
7539
7909
  return specs;
7540
7910
  }
7541
7911
  const forceRerunTriggers = this.vitest.config.forceRerunTriggers;
7542
- if (forceRerunTriggers.length && mm(related, forceRerunTriggers).length) {
7912
+ const matcher = forceRerunTriggers.length ? pm(forceRerunTriggers) : undefined;
7913
+ if (matcher && related.some((file) => matcher(file))) {
7543
7914
  return specs;
7544
7915
  }
7545
7916
  if (!this.vitest.config.watch && !related.length) {
@@ -7997,721 +8368,434 @@ class StateManager {
7997
8368
  taskFileMap = new WeakMap();
7998
8369
  errorsSet = new Set();
7999
8370
  processTimeoutCauses = new Set();
8000
- reportedTasksMap = new WeakMap();
8001
- blobs;
8002
- catchError(err, type) {
8003
- if (isAggregateError(err)) {
8004
- return err.errors.forEach((error) => this.catchError(error, type));
8005
- }
8006
- if (err === Object(err)) {
8007
- err.type = type;
8008
- } else {
8009
- err = {
8010
- type,
8011
- message: err
8012
- };
8013
- }
8014
- const _err = err;
8015
- if (_err && typeof _err === "object" && _err.code === "VITEST_PENDING") {
8016
- const task = this.idMap.get(_err.taskId);
8017
- if (task) {
8018
- task.mode = "skip";
8019
- task.result ??= { state: "skip" };
8020
- task.result.state = "skip";
8021
- task.result.note = _err.note;
8022
- }
8023
- return;
8024
- }
8025
- this.errorsSet.add(err);
8026
- }
8027
- clearErrors() {
8028
- this.errorsSet.clear();
8029
- }
8030
- getUnhandledErrors() {
8031
- return Array.from(this.errorsSet.values());
8032
- }
8033
- addProcessTimeoutCause(cause) {
8034
- this.processTimeoutCauses.add(cause);
8035
- }
8036
- getProcessTimeoutCauses() {
8037
- return Array.from(this.processTimeoutCauses.values());
8038
- }
8039
- getPaths() {
8040
- return Array.from(this.pathsSet);
8041
- }
8042
- /**
8043
- * Return files that were running or collected.
8044
- */
8045
- getFiles(keys) {
8046
- if (keys) {
8047
- return keys.map((key) => this.filesMap.get(key)).flat().filter((file) => file && !file.local);
8048
- }
8049
- return Array.from(this.filesMap.values()).flat().filter((file) => !file.local).sort((f1, f2) => {
8050
- if (f1.meta?.typecheck && f2.meta?.typecheck) {
8051
- return 0;
8052
- }
8053
- if (f1.meta?.typecheck) {
8054
- return -1;
8055
- }
8056
- return 1;
8057
- });
8058
- }
8059
- getTestModules(keys) {
8060
- return this.getFiles(keys).map((file) => this.getReportedEntity(file));
8061
- }
8062
- getFilepaths() {
8063
- return Array.from(this.filesMap.keys());
8064
- }
8065
- getFailedFilepaths() {
8066
- return this.getFiles().filter((i) => i.result?.state === "fail").map((i) => i.filepath);
8067
- }
8068
- collectPaths(paths = []) {
8069
- paths.forEach((path) => {
8070
- this.pathsSet.add(path);
8071
- });
8072
- }
8073
- collectFiles(project, files = []) {
8074
- files.forEach((file) => {
8075
- const existing = this.filesMap.get(file.filepath) || [];
8076
- const otherFiles = existing.filter((i) => i.projectName !== file.projectName || i.meta.typecheck !== file.meta.typecheck);
8077
- const currentFile = existing.find((i) => i.projectName === file.projectName);
8078
- if (currentFile) {
8079
- file.logs = currentFile.logs;
8080
- }
8081
- otherFiles.push(file);
8082
- this.filesMap.set(file.filepath, otherFiles);
8083
- this.updateId(file, project);
8084
- });
8085
- }
8086
- clearFiles(project, paths = []) {
8087
- paths.forEach((path) => {
8088
- const files = this.filesMap.get(path);
8089
- const fileTask = createFileTask(path, project.config.root, project.config.name);
8090
- fileTask.local = true;
8091
- TestModule.register(fileTask, project);
8092
- this.idMap.set(fileTask.id, fileTask);
8093
- if (!files) {
8094
- this.filesMap.set(path, [fileTask]);
8095
- return;
8096
- }
8097
- const filtered = files.filter((file) => file.projectName !== project.config.name);
8098
- if (!filtered.length) {
8099
- this.filesMap.set(path, [fileTask]);
8100
- } else {
8101
- this.filesMap.set(path, [...filtered, fileTask]);
8102
- }
8103
- });
8104
- }
8105
- updateId(task, project) {
8106
- if (this.idMap.get(task.id) === task) {
8107
- return;
8108
- }
8109
- if (task.type === "suite" && "filepath" in task) {
8110
- TestModule.register(task, project);
8111
- } else if (task.type === "suite") {
8112
- TestSuite.register(task, project);
8113
- } else {
8114
- TestCase.register(task, project);
8115
- }
8116
- this.idMap.set(task.id, task);
8117
- if (task.type === "suite") {
8118
- task.tasks.forEach((task) => {
8119
- this.updateId(task, project);
8120
- });
8121
- }
8122
- }
8123
- getReportedEntity(task) {
8124
- return this.reportedTasksMap.get(task);
8125
- }
8126
- updateTasks(packs) {
8127
- for (const [id, result, meta] of packs) {
8128
- const task = this.idMap.get(id);
8129
- if (task) {
8130
- task.result = result;
8131
- task.meta = meta;
8132
- if (result?.state === "skip") {
8133
- task.mode = "skip";
8134
- }
8135
- }
8136
- }
8137
- }
8138
- updateUserLog(log) {
8139
- const task = log.taskId && this.idMap.get(log.taskId);
8140
- if (task) {
8141
- if (!task.logs) {
8142
- task.logs = [];
8143
- }
8144
- task.logs.push(log);
8145
- }
8146
- }
8147
- getCountOfFailedTests() {
8148
- return Array.from(this.idMap.values()).filter((t) => t.result?.state === "fail").length;
8149
- }
8150
- cancelFiles(files, project) {
8151
- this.collectFiles(project, files.map((filepath) => createFileTask(filepath, project.config.root, project.config.name)));
8152
- }
8153
- }
8154
-
8155
- class TestRun {
8156
- constructor(vitest) {
8157
- this.vitest = vitest;
8158
- }
8159
- async start(specifications) {
8160
- const filepaths = specifications.map((spec) => spec.moduleId);
8161
- this.vitest.state.collectPaths(filepaths);
8162
- await this.vitest.report("onPathsCollected", Array.from(new Set(filepaths)));
8163
- await this.vitest.report("onSpecsCollected", specifications.map((spec) => spec.toJSON()));
8164
- await this.vitest.report("onTestRunStart", [...specifications]);
8165
- }
8166
- async enqueued(project, file) {
8167
- this.vitest.state.collectFiles(project, [file]);
8168
- const testModule = this.vitest.state.getReportedEntity(file);
8169
- await this.vitest.report("onTestModuleQueued", testModule);
8170
- }
8171
- async collected(project, files) {
8172
- this.vitest.state.collectFiles(project, files);
8173
- await Promise.all([this.vitest.report("onCollected", files), ...files.map((file) => {
8174
- const testModule = this.vitest.state.getReportedEntity(file);
8175
- return this.vitest.report("onTestModuleCollected", testModule);
8176
- })]);
8177
- }
8178
- async log(log) {
8179
- this.vitest.state.updateUserLog(log);
8180
- await this.vitest.report("onUserConsoleLog", log);
8181
- }
8182
- async updated(update, events) {
8183
- this.vitest.state.updateTasks(update);
8184
- await this.vitest.report("onTaskUpdate", update);
8185
- for (const [id, event] of events) {
8186
- await this.reportEvent(id, event).catch((error) => {
8187
- this.vitest.state.catchError(serializeError(error), "Unhandled Reporter Error");
8188
- });
8189
- }
8190
- }
8191
- async end(specifications, errors, coverage) {
8192
- const modules = specifications.map((spec) => spec.testModule).filter((s) => s != null);
8193
- const files = modules.map((m) => m.task);
8194
- const state = this.vitest.isCancelling ? "interrupted" : process.exitCode ? "failed" : "passed";
8195
- try {
8196
- await Promise.all([this.vitest.report("onTestRunEnd", modules, [...errors], state), this.vitest.report("onFinished", files, errors, coverage)]);
8197
- } finally {
8198
- if (coverage) {
8199
- await this.vitest.report("onCoverage", coverage);
8200
- }
8201
- }
8202
- }
8203
- async reportEvent(id, event) {
8204
- const task = this.vitest.state.idMap.get(id);
8205
- const entity = task && this.vitest.state.getReportedEntity(task);
8206
- assert$1(task && entity, `Entity must be found for task ${task?.name || id}`);
8207
- if (event === "suite-prepare" && entity.type === "suite") {
8208
- return await this.vitest.report("onTestSuiteReady", entity);
8209
- }
8210
- if (event === "suite-prepare" && entity.type === "module") {
8211
- return await this.vitest.report("onTestModuleStart", entity);
8212
- }
8213
- if (event === "suite-finished") {
8214
- assert$1(entity.type === "suite" || entity.type === "module", "Entity type must be suite or module");
8215
- if (entity.state() === "skipped") {
8216
- await this.reportChildren(entity.children);
8217
- }
8218
- if (entity.type === "module") {
8219
- await this.vitest.report("onTestModuleEnd", entity);
8220
- } else {
8221
- await this.vitest.report("onTestSuiteResult", entity);
8222
- }
8223
- return;
8224
- }
8225
- if (event === "test-prepare" && entity.type === "test") {
8226
- return await this.vitest.report("onTestCaseReady", entity);
8227
- }
8228
- if (event === "test-finished" && entity.type === "test") {
8229
- return await this.vitest.report("onTestCaseResult", entity);
8230
- }
8231
- if (event.startsWith("before-hook") || event.startsWith("after-hook")) {
8232
- const isBefore = event.startsWith("before-hook");
8233
- const hook = entity.type === "test" ? {
8234
- name: isBefore ? "beforeEach" : "afterEach",
8235
- entity
8236
- } : {
8237
- name: isBefore ? "beforeAll" : "afterAll",
8238
- entity
8371
+ reportedTasksMap = new WeakMap();
8372
+ blobs;
8373
+ 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
8239
8383
  };
8240
- if (event.endsWith("-start")) {
8241
- await this.vitest.report("onHookStart", hook);
8242
- } else {
8243
- await this.vitest.report("onHookEnd", hook);
8244
- }
8245
8384
  }
8246
- }
8247
- async reportChildren(children) {
8248
- for (const child of children) {
8249
- if (child.type === "test") {
8250
- await this.vitest.report("onTestCaseReady", child);
8251
- await this.vitest.report("onTestCaseResult", child);
8252
- } else {
8253
- await this.vitest.report("onTestSuiteReady", child);
8254
- await this.reportChildren(child.children);
8255
- await this.vitest.report("onTestSuiteResult", child);
8385
+ const _err = err;
8386
+ if (_err && typeof _err === "object" && _err.code === "VITEST_PENDING") {
8387
+ const task = this.idMap.get(_err.taskId);
8388
+ if (task) {
8389
+ task.mode = "skip";
8390
+ task.result ??= { state: "skip" };
8391
+ task.result.state = "skip";
8392
+ task.result.note = _err.note;
8256
8393
  }
8394
+ return;
8257
8395
  }
8396
+ this.errorsSet.add(err);
8258
8397
  }
8259
- }
8260
-
8261
- class VitestWatcher {
8262
- /**
8263
- * Modules that will be invalidated on the next run.
8264
- */
8265
- invalidates = new Set();
8266
- /**
8267
- * Test files that have changed and need to be rerun.
8268
- */
8269
- changedTests = new Set();
8270
- _onRerun = [];
8271
- constructor(vitest) {
8272
- this.vitest = vitest;
8398
+ clearErrors() {
8399
+ this.errorsSet.clear();
8273
8400
  }
8274
- /**
8275
- * Register a handler that will be called when test files need to be rerun.
8276
- * The callback can receive several files in case the changed file is imported by several test files.
8277
- * Several invocations of this method will add multiple handlers.
8278
- * @internal
8279
- */
8280
- onWatcherRerun(cb) {
8281
- this._onRerun.push(cb);
8282
- return this;
8401
+ getUnhandledErrors() {
8402
+ return Array.from(this.errorsSet.values());
8283
8403
  }
8284
- unregisterWatcher = noop;
8285
- registerWatcher() {
8286
- const watcher = this.vitest.vite.watcher;
8287
- if (this.vitest.config.forceRerunTriggers.length) {
8288
- watcher.add(this.vitest.config.forceRerunTriggers);
8289
- }
8290
- watcher.on("change", this.onChange);
8291
- watcher.on("unlink", this.onUnlink);
8292
- watcher.on("add", this.onAdd);
8293
- this.unregisterWatcher = () => {
8294
- watcher.off("change", this.onChange);
8295
- watcher.off("unlink", this.onUnlink);
8296
- watcher.off("add", this.onAdd);
8297
- this.unregisterWatcher = noop;
8298
- };
8299
- return this;
8404
+ addProcessTimeoutCause(cause) {
8405
+ this.processTimeoutCauses.add(cause);
8300
8406
  }
8301
- scheduleRerun(file) {
8302
- this._onRerun.forEach((cb) => cb(file));
8407
+ getProcessTimeoutCauses() {
8408
+ return Array.from(this.processTimeoutCauses.values());
8409
+ }
8410
+ getPaths() {
8411
+ return Array.from(this.pathsSet);
8303
8412
  }
8304
- onChange = (id) => {
8305
- id = slash(id);
8306
- this.vitest.logger.clearHighlightCache(id);
8307
- this.vitest.invalidateFile(id);
8308
- const needsRerun = this.handleFileChanged(id);
8309
- if (needsRerun) {
8310
- this.scheduleRerun(id);
8311
- }
8312
- };
8313
- onUnlink = (id) => {
8314
- id = slash(id);
8315
- this.vitest.logger.clearHighlightCache(id);
8316
- this.invalidates.add(id);
8317
- if (this.vitest.state.filesMap.has(id)) {
8318
- this.vitest.projects.forEach((project) => project._removeCachedTestFile(id));
8319
- this.vitest.state.filesMap.delete(id);
8320
- this.vitest.cache.results.removeFromCache(id);
8321
- this.vitest.cache.stats.removeStats(id);
8322
- this.changedTests.delete(id);
8323
- this.vitest.report("onTestRemoved", id);
8324
- }
8325
- };
8326
- onAdd = (id) => {
8327
- id = slash(id);
8328
- this.vitest.invalidateFile(id);
8329
- let fileContent;
8330
- const matchingProjects = [];
8331
- this.vitest.projects.forEach((project) => {
8332
- if (project.matchesTestGlob(id, () => fileContent ??= readFileSync(id, "utf-8"))) {
8333
- matchingProjects.push(project);
8334
- }
8335
- });
8336
- if (matchingProjects.length > 0) {
8337
- this.changedTests.add(id);
8338
- this.scheduleRerun(id);
8339
- } else {
8340
- const needsRerun = this.handleFileChanged(id);
8341
- if (needsRerun) {
8342
- this.scheduleRerun(id);
8343
- }
8344
- }
8345
- };
8346
8413
  /**
8347
- * @returns A value indicating whether rerun is needed (changedTests was mutated)
8414
+ * Return files that were running or collected.
8348
8415
  */
8349
- handleFileChanged(filepath) {
8350
- if (this.changedTests.has(filepath) || this.invalidates.has(filepath)) {
8351
- return false;
8352
- }
8353
- if (mm.isMatch(filepath, this.vitest.config.forceRerunTriggers)) {
8354
- this.vitest.state.getFilepaths().forEach((file) => this.changedTests.add(file));
8355
- return true;
8356
- }
8357
- const projects = this.vitest.projects.filter((project) => {
8358
- const moduleGraph = project.browser?.vite.moduleGraph || project.vite.moduleGraph;
8359
- return moduleGraph.getModulesByFile(filepath)?.size;
8360
- });
8361
- if (!projects.length) {
8362
- if (this.vitest.state.filesMap.has(filepath) || this.vitest.projects.some((project) => project._isCachedTestFile(filepath))) {
8363
- this.changedTests.add(filepath);
8364
- return true;
8365
- }
8366
- return false;
8416
+ getFiles(keys) {
8417
+ if (keys) {
8418
+ return keys.map((key) => this.filesMap.get(key)).flat().filter((file) => file && !file.local);
8367
8419
  }
8368
- const files = [];
8369
- for (const project of projects) {
8370
- const mods = project.browser?.vite.moduleGraph.getModulesByFile(filepath) || project.vite.moduleGraph.getModulesByFile(filepath);
8371
- if (!mods || !mods.size) {
8372
- continue;
8373
- }
8374
- this.invalidates.add(filepath);
8375
- if (this.vitest.state.filesMap.has(filepath) || project._isCachedTestFile(filepath)) {
8376
- this.changedTests.add(filepath);
8377
- files.push(filepath);
8378
- continue;
8420
+ 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;
8379
8423
  }
8380
- let rerun = false;
8381
- for (const mod of mods) {
8382
- mod.importers.forEach((i) => {
8383
- if (!i.file) {
8384
- return;
8385
- }
8386
- const needsRerun = this.handleFileChanged(i.file);
8387
- if (needsRerun) {
8388
- rerun = true;
8389
- }
8390
- });
8424
+ if (f1.meta?.typecheck) {
8425
+ return -1;
8391
8426
  }
8392
- if (rerun) {
8393
- files.push(filepath);
8427
+ return 1;
8428
+ });
8429
+ }
8430
+ getTestModules(keys) {
8431
+ return this.getFiles(keys).map((file) => this.getReportedEntity(file));
8432
+ }
8433
+ getFilepaths() {
8434
+ return Array.from(this.filesMap.keys());
8435
+ }
8436
+ getFailedFilepaths() {
8437
+ return this.getFiles().filter((i) => i.result?.state === "fail").map((i) => i.filepath);
8438
+ }
8439
+ collectPaths(paths = []) {
8440
+ paths.forEach((path) => {
8441
+ this.pathsSet.add(path);
8442
+ });
8443
+ }
8444
+ collectFiles(project, files = []) {
8445
+ files.forEach((file) => {
8446
+ const existing = this.filesMap.get(file.filepath) || [];
8447
+ const otherFiles = existing.filter((i) => i.projectName !== file.projectName || i.meta.typecheck !== file.meta.typecheck);
8448
+ const currentFile = existing.find((i) => i.projectName === file.projectName);
8449
+ if (currentFile) {
8450
+ file.logs = currentFile.logs;
8394
8451
  }
8395
- }
8396
- return !!files.length;
8452
+ otherFiles.push(file);
8453
+ this.filesMap.set(file.filepath, otherFiles);
8454
+ this.updateId(file, project);
8455
+ });
8397
8456
  }
8398
- }
8399
-
8400
- async function resolveWorkspace(vitest, cliOptions, workspaceConfigPath, workspaceDefinition, names) {
8401
- const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, workspaceDefinition);
8402
- const overridesOptions = [
8403
- "logHeapUsage",
8404
- "allowOnly",
8405
- "sequence",
8406
- "testTimeout",
8407
- "pool",
8408
- "update",
8409
- "globals",
8410
- "expandSnapshotDiff",
8411
- "disableConsoleIntercept",
8412
- "retry",
8413
- "testNamePattern",
8414
- "passWithNoTests",
8415
- "bail",
8416
- "isolate",
8417
- "printConsoleTrace",
8418
- "inspect",
8419
- "inspectBrk",
8420
- "fileParallelism"
8421
- ];
8422
- const cliOverrides = overridesOptions.reduce((acc, name) => {
8423
- if (name in cliOptions) {
8424
- acc[name] = cliOptions[name];
8425
- }
8426
- return acc;
8427
- }, {});
8428
- const projectPromises = [];
8429
- const fileProjects = [...configFiles, ...nonConfigDirectories];
8430
- const concurrent = limitConcurrency(nodeos__default.availableParallelism?.() || nodeos__default.cpus().length || 5);
8431
- projectConfigs.forEach((options, index) => {
8432
- const configRoot = workspaceConfigPath ? dirname(workspaceConfigPath) : vitest.config.root;
8433
- const configFile = typeof options.extends === "string" ? resolve(configRoot, options.extends) : options.extends === true ? vitest.vite.config.configFile || false : false;
8434
- const root = options.root ? resolve(configRoot, options.root) : vitest.config.root;
8435
- projectPromises.push(concurrent(() => initializeProject(index, vitest, {
8436
- ...options,
8437
- root,
8438
- configFile,
8439
- test: {
8440
- ...options.test,
8441
- ...cliOverrides
8457
+ clearFiles(project, paths = []) {
8458
+ paths.forEach((path) => {
8459
+ const files = this.filesMap.get(path);
8460
+ const fileTask = createFileTask(path, project.config.root, project.config.name);
8461
+ fileTask.local = true;
8462
+ TestModule.register(fileTask, project);
8463
+ this.idMap.set(fileTask.id, fileTask);
8464
+ if (!files) {
8465
+ this.filesMap.set(path, [fileTask]);
8466
+ return;
8442
8467
  }
8443
- })));
8444
- });
8445
- for (const path of fileProjects) {
8446
- if (vitest.vite.config.configFile === path) {
8447
- const project = getDefaultTestProject(vitest);
8448
- if (project) {
8449
- projectPromises.push(Promise.resolve(project));
8468
+ 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]);
8450
8473
  }
8451
- continue;
8474
+ });
8475
+ }
8476
+ 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
+ }
8487
+ this.idMap.set(task.id, task);
8488
+ if (task.type === "suite") {
8489
+ task.tasks.forEach((task) => {
8490
+ this.updateId(task, project);
8491
+ });
8452
8492
  }
8453
- const configFile = path.endsWith("/") ? false : path;
8454
- const root = path.endsWith("/") ? path : dirname(path);
8455
- projectPromises.push(concurrent(() => initializeProject(path, vitest, {
8456
- root,
8457
- configFile,
8458
- test: cliOverrides
8459
- })));
8460
8493
  }
8461
- if (!projectPromises.length) {
8462
- throw new Error([
8463
- "No projects were found. Make sure your configuration is correct. ",
8464
- vitest.config.project.length ? `The filter matched no projects: ${vitest.config.project.join(", ")}. ` : "",
8465
- `The workspace: ${JSON.stringify(workspaceDefinition, null, 4)}.`
8466
- ].join(""));
8494
+ getReportedEntity(task) {
8495
+ return this.reportedTasksMap.get(task);
8467
8496
  }
8468
- const resolvedProjectsPromises = await Promise.allSettled(projectPromises);
8469
- const errors = [];
8470
- const resolvedProjects = [];
8471
- for (const result of resolvedProjectsPromises) {
8472
- if (result.status === "rejected") {
8473
- if (result.reason instanceof VitestFilteredOutProjectError) {
8474
- continue;
8497
+ updateTasks(packs) {
8498
+ for (const [id, result, meta] of packs) {
8499
+ const task = this.idMap.get(id);
8500
+ if (task) {
8501
+ task.result = result;
8502
+ task.meta = meta;
8503
+ if (result?.state === "skip") {
8504
+ task.mode = "skip";
8505
+ }
8475
8506
  }
8476
- errors.push(result.reason);
8477
- } else {
8478
- resolvedProjects.push(result.value);
8479
8507
  }
8480
8508
  }
8481
- if (errors.length) {
8482
- throw new AggregateError(errors, "Failed to initialize projects. There were errors during workspace setup. See below for more details.");
8483
- }
8484
- for (const project of resolvedProjects) {
8485
- const name = project.name;
8486
- if (names.has(name)) {
8487
- const duplicate = resolvedProjects.find((p) => p.name === name && p !== project);
8488
- const filesError = fileProjects.length ? [
8489
- "\n\nYour config matched these files:\n",
8490
- fileProjects.map((p) => ` - ${relative(vitest.config.root, p)}`).join("\n"),
8491
- "\n\n"
8492
- ].join("") : [" "];
8493
- throw new Error([
8494
- `Project name "${name}"`,
8495
- project.vite.config.configFile ? ` from "${relative(vitest.config.root, project.vite.config.configFile)}"` : "",
8496
- " is not unique.",
8497
- duplicate?.vite.config.configFile ? ` The project is already defined by "${relative(vitest.config.root, duplicate.vite.config.configFile)}".` : "",
8498
- filesError,
8499
- "All projects in a workspace should have unique names. Make sure your configuration is correct."
8500
- ].join(""));
8509
+ updateUserLog(log) {
8510
+ const task = log.taskId && this.idMap.get(log.taskId);
8511
+ if (task) {
8512
+ if (!task.logs) {
8513
+ task.logs = [];
8514
+ }
8515
+ task.logs.push(log);
8501
8516
  }
8502
- names.add(name);
8503
8517
  }
8504
- return resolveBrowserWorkspace(vitest, names, resolvedProjects);
8518
+ getCountOfFailedTests() {
8519
+ return Array.from(this.idMap.values()).filter((t) => t.result?.state === "fail").length;
8520
+ }
8521
+ cancelFiles(files, project) {
8522
+ this.collectFiles(project, files.map((filepath) => createFileTask(filepath, project.config.root, project.config.name)));
8523
+ }
8505
8524
  }
8506
- async function resolveBrowserWorkspace(vitest, names, resolvedProjects) {
8507
- const removeProjects = new Set();
8508
- resolvedProjects.forEach((project) => {
8509
- if (!project.config.browser.enabled) {
8510
- return;
8511
- }
8512
- const instances = project.config.browser.instances || [];
8513
- const browser = project.config.browser.name;
8514
- if (instances.length === 0 && browser) {
8515
- instances.push({
8516
- browser,
8517
- name: project.name ? `${project.name} (${browser})` : browser
8525
+
8526
+ class TestRun {
8527
+ constructor(vitest) {
8528
+ this.vitest = vitest;
8529
+ }
8530
+ async start(specifications) {
8531
+ const filepaths = specifications.map((spec) => spec.moduleId);
8532
+ this.vitest.state.collectPaths(filepaths);
8533
+ await this.vitest.report("onPathsCollected", Array.from(new Set(filepaths)));
8534
+ await this.vitest.report("onSpecsCollected", specifications.map((spec) => spec.toJSON()));
8535
+ await this.vitest.report("onTestRunStart", [...specifications]);
8536
+ }
8537
+ async enqueued(project, file) {
8538
+ this.vitest.state.collectFiles(project, [file]);
8539
+ const testModule = this.vitest.state.getReportedEntity(file);
8540
+ await this.vitest.report("onTestModuleQueued", testModule);
8541
+ }
8542
+ async collected(project, files) {
8543
+ this.vitest.state.collectFiles(project, files);
8544
+ await Promise.all([this.vitest.report("onCollected", files), ...files.map((file) => {
8545
+ const testModule = this.vitest.state.getReportedEntity(file);
8546
+ return this.vitest.report("onTestModuleCollected", testModule);
8547
+ })]);
8548
+ }
8549
+ async log(log) {
8550
+ this.vitest.state.updateUserLog(log);
8551
+ await this.vitest.report("onUserConsoleLog", log);
8552
+ }
8553
+ async updated(update, events) {
8554
+ 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");
8518
8559
  });
8519
- console.warn(withLabel("yellow", "Vitest", [
8520
- `No browser "instances" were defined`,
8521
- project.name ? ` for the "${project.name}" project. ` : ". ",
8522
- `Running tests in "${project.config.browser.name}" browser. `,
8523
- "The \"browser.name\" field is deprecated since Vitest 3. ",
8524
- "Read more: https://vitest.dev/guide/browser/config#browser-instances"
8525
- ].filter(Boolean).join("")));
8526
- }
8527
- const originalName = project.config.name;
8528
- const filteredInstances = vitest.matchesProjectFilter(originalName) ? instances : instances.filter((instance) => {
8529
- const newName = instance.name;
8530
- return vitest.matchesProjectFilter(newName);
8531
- });
8532
- if (!filteredInstances.length) {
8533
- removeProjects.add(project);
8534
- return;
8535
- }
8536
- if (project.config.browser.providerOptions) {
8537
- 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.`));
8538
8560
  }
8539
- filteredInstances.forEach((config, index) => {
8540
- const browser = config.browser;
8541
- if (!browser) {
8542
- const nth = index + 1;
8543
- const ending = nth === 2 ? "nd" : nth === 3 ? "rd" : "th";
8544
- 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.`);
8545
- }
8546
- const name = config.name;
8547
- if (name == null) {
8548
- throw new Error(`The browser configuration must have a "name" property. This is a bug in Vitest. Please, open a new issue with reproduction`);
8549
- }
8550
- if (names.has(name)) {
8551
- throw new Error([
8552
- `Cannot define a nested project for a ${browser} browser. The project name "${name}" was already defined. `,
8553
- "If you have multiple instances for the same browser, make sure to define a custom \"name\". ",
8554
- "All projects in a workspace should have unique names. Make sure your configuration is correct."
8555
- ].join(""));
8561
+ }
8562
+ async end(specifications, errors, coverage) {
8563
+ const modules = specifications.map((spec) => spec.testModule).filter((s) => s != null);
8564
+ const files = modules.map((m) => m.task);
8565
+ const state = this.vitest.isCancelling ? "interrupted" : process.exitCode ? "failed" : "passed";
8566
+ try {
8567
+ await Promise.all([this.vitest.report("onTestRunEnd", modules, [...errors], state), this.vitest.report("onFinished", files, errors, coverage)]);
8568
+ } finally {
8569
+ if (coverage) {
8570
+ await this.vitest.report("onCoverage", coverage);
8556
8571
  }
8557
- names.add(name);
8558
- const clonedConfig = cloneConfig(project, config);
8559
- clonedConfig.name = name;
8560
- const clone = TestProject._cloneBrowserProject(project, clonedConfig);
8561
- resolvedProjects.push(clone);
8562
- });
8563
- removeProjects.add(project);
8564
- });
8565
- resolvedProjects = resolvedProjects.filter((project) => !removeProjects.has(project));
8566
- const headedBrowserProjects = resolvedProjects.filter((project) => {
8567
- return project.config.browser.enabled && !project.config.browser.headless;
8568
- });
8569
- if (headedBrowserProjects.length > 1) {
8570
- 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("");
8571
- if (!isTTY) {
8572
- throw new Error(`${message} Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option.`);
8573
- }
8574
- const prompts = await import('./index.DBIGubLC.js').then(function (n) { return n.i; });
8575
- const { projectName } = await prompts.default({
8576
- type: "select",
8577
- name: "projectName",
8578
- choices: headedBrowserProjects.map((project) => ({
8579
- title: project.name,
8580
- value: project.name
8581
- })),
8582
- 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.`
8583
- });
8584
- if (!projectName) {
8585
- throw new Error("The test run was aborted.");
8586
8572
  }
8587
- return resolvedProjects.filter((project) => project.name === projectName);
8588
8573
  }
8589
- return resolvedProjects;
8590
- }
8591
- function cloneConfig(project, { browser,...config }) {
8592
- const { locators, viewport, testerHtmlPath, headless, screenshotDirectory, screenshotFailures, browser: _browser, name,...overrideConfig } = config;
8593
- const currentConfig = project.config.browser;
8594
- return mergeConfig({
8595
- ...deepClone(project.config),
8596
- browser: {
8597
- ...project.config.browser,
8598
- locators: locators ? { testIdAttribute: locators.testIdAttribute ?? currentConfig.locators.testIdAttribute } : project.config.browser.locators,
8599
- viewport: viewport ?? currentConfig.viewport,
8600
- testerHtmlPath: testerHtmlPath ?? currentConfig.testerHtmlPath,
8601
- screenshotDirectory: screenshotDirectory ?? currentConfig.screenshotDirectory,
8602
- screenshotFailures: screenshotFailures ?? currentConfig.screenshotFailures,
8603
- headless: headless ?? currentConfig.headless,
8604
- name: browser,
8605
- providerOptions: config,
8606
- instances: undefined
8574
+ async reportEvent(id, event) {
8575
+ const task = this.vitest.state.idMap.get(id);
8576
+ const entity = task && this.vitest.state.getReportedEntity(task);
8577
+ 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
+ }
8584
+ if (event === "suite-finished") {
8585
+ 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
+ }
8594
+ return;
8607
8595
  }
8608
- }, overrideConfig);
8609
- }
8610
- async function resolveTestProjectConfigs(vitest, workspaceConfigPath, workspaceDefinition) {
8611
- const projectsOptions = [];
8612
- const workspaceConfigFiles = [];
8613
- const workspaceGlobMatches = [];
8614
- const nonConfigProjectDirectories = [];
8615
- for (const definition of workspaceDefinition) {
8616
- if (typeof definition === "string") {
8617
- const stringOption = definition.replace("<rootDir>", vitest.config.root);
8618
- if (!isDynamicPattern(stringOption)) {
8619
- const file = resolve(vitest.config.root, stringOption);
8620
- if (!existsSync(file)) {
8621
- const relativeWorkSpaceConfigPath = workspaceConfigPath ? relative(vitest.config.root, workspaceConfigPath) : undefined;
8622
- const note = workspaceConfigPath ? `Workspace config file "${relativeWorkSpaceConfigPath}"` : "Inline workspace";
8623
- throw new Error(`${note} references a non-existing file or a directory: ${file}`);
8624
- }
8625
- const stats = await promises.stat(file);
8626
- if (stats.isFile()) {
8627
- workspaceConfigFiles.push(file);
8628
- } else if (stats.isDirectory()) {
8629
- const configFile = await resolveDirectoryConfig(file);
8630
- if (configFile) {
8631
- workspaceConfigFiles.push(configFile);
8632
- } else {
8633
- const directory = file[file.length - 1] === "/" ? file : `${file}/`;
8634
- nonConfigProjectDirectories.push(directory);
8635
- }
8636
- } else {
8637
- throw new TypeError(`Unexpected file type: ${file}`);
8638
- }
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
+ }
8602
+ if (event.startsWith("before-hook") || event.startsWith("after-hook")) {
8603
+ const isBefore = event.startsWith("before-hook");
8604
+ const hook = entity.type === "test" ? {
8605
+ name: isBefore ? "beforeEach" : "afterEach",
8606
+ entity
8607
+ } : {
8608
+ name: isBefore ? "beforeAll" : "afterAll",
8609
+ entity
8610
+ };
8611
+ if (event.endsWith("-start")) {
8612
+ await this.vitest.report("onHookStart", hook);
8639
8613
  } else {
8640
- workspaceGlobMatches.push(stringOption);
8614
+ await this.vitest.report("onHookEnd", hook);
8641
8615
  }
8642
- } else if (typeof definition === "function") {
8643
- projectsOptions.push(await definition({
8644
- command: vitest.vite.config.command,
8645
- mode: vitest.vite.config.mode,
8646
- isPreview: false,
8647
- isSsrBuild: false
8648
- }));
8649
- } else {
8650
- projectsOptions.push(await definition);
8651
8616
  }
8652
8617
  }
8653
- if (workspaceGlobMatches.length) {
8654
- const globOptions = {
8655
- absolute: true,
8656
- dot: true,
8657
- onlyFiles: false,
8658
- cwd: vitest.config.root,
8659
- expandDirectories: false,
8660
- ignore: [
8661
- "**/node_modules/**",
8662
- "**/*.timestamp-*",
8663
- "**/.DS_Store"
8664
- ]
8665
- };
8666
- const workspacesFs = await glob(workspaceGlobMatches, globOptions);
8667
- await Promise.all(workspacesFs.map(async (path) => {
8668
- if (path.endsWith("/")) {
8669
- const configFile = await resolveDirectoryConfig(path);
8670
- if (configFile) {
8671
- workspaceConfigFiles.push(configFile);
8672
- } else {
8673
- nonConfigProjectDirectories.push(path);
8674
- }
8618
+ 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);
8675
8623
  } else {
8676
- workspaceConfigFiles.push(path);
8624
+ await this.vitest.report("onTestSuiteReady", child);
8625
+ await this.reportChildren(child.children);
8626
+ await this.vitest.report("onTestSuiteResult", child);
8677
8627
  }
8678
- }));
8628
+ }
8679
8629
  }
8680
- const projectConfigFiles = Array.from(new Set(workspaceConfigFiles));
8681
- return {
8682
- projectConfigs: projectsOptions,
8683
- nonConfigDirectories: nonConfigProjectDirectories,
8684
- configFiles: projectConfigFiles
8685
- };
8686
8630
  }
8687
- async function resolveDirectoryConfig(directory) {
8688
- const files = new Set(await promises.readdir(directory));
8689
- const configFile = configFiles.find((file) => files.has(file));
8690
- if (configFile) {
8691
- return resolve(directory, configFile);
8631
+
8632
+ class VitestWatcher {
8633
+ /**
8634
+ * Modules that will be invalidated on the next run.
8635
+ */
8636
+ invalidates = new Set();
8637
+ /**
8638
+ * Test files that have changed and need to be rerun.
8639
+ */
8640
+ changedTests = new Set();
8641
+ _onRerun = [];
8642
+ constructor(vitest) {
8643
+ this.vitest = vitest;
8692
8644
  }
8693
- return null;
8694
- }
8695
- function getDefaultTestProject(vitest) {
8696
- const filter = vitest.config.project;
8697
- const project = vitest._ensureRootProject();
8698
- if (!filter.length) {
8699
- return project;
8645
+ /**
8646
+ * Register a handler that will be called when test files need to be rerun.
8647
+ * The callback can receive several files in case the changed file is imported by several test files.
8648
+ * Several invocations of this method will add multiple handlers.
8649
+ * @internal
8650
+ */
8651
+ onWatcherRerun(cb) {
8652
+ this._onRerun.push(cb);
8653
+ return this;
8700
8654
  }
8701
- const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
8702
- if (hasProjects) {
8703
- return project;
8655
+ unregisterWatcher = noop;
8656
+ registerWatcher() {
8657
+ const watcher = this.vitest.vite.watcher;
8658
+ if (this.vitest.config.forceRerunTriggers.length) {
8659
+ watcher.add(this.vitest.config.forceRerunTriggers);
8660
+ }
8661
+ watcher.on("change", this.onChange);
8662
+ watcher.on("unlink", this.onUnlink);
8663
+ watcher.on("add", this.onAdd);
8664
+ this.unregisterWatcher = () => {
8665
+ watcher.off("change", this.onChange);
8666
+ watcher.off("unlink", this.onUnlink);
8667
+ watcher.off("add", this.onAdd);
8668
+ this.unregisterWatcher = noop;
8669
+ };
8670
+ return this;
8704
8671
  }
8705
- return null;
8706
- }
8707
- function getPotentialProjectNames(project) {
8708
- const names = [project.name];
8709
- if (project.config.browser.instances) {
8710
- names.push(...project.config.browser.instances.map((i) => i.name));
8711
- } else if (project.config.browser.name) {
8712
- names.push(project.config.browser.name);
8672
+ scheduleRerun(file) {
8673
+ this._onRerun.forEach((cb) => cb(file));
8674
+ }
8675
+ getTestFilesFromWatcherTrigger(id) {
8676
+ if (!this.vitest.config.watchTriggerPatterns) {
8677
+ return false;
8678
+ }
8679
+ let triggered = false;
8680
+ this.vitest.config.watchTriggerPatterns.forEach((definition) => {
8681
+ const exec = definition.pattern.exec(id);
8682
+ if (exec) {
8683
+ const files = definition.testsToRun(id, exec);
8684
+ if (Array.isArray(files)) {
8685
+ triggered = true;
8686
+ files.forEach((file) => this.changedTests.add(resolve(this.vitest.config.root, file)));
8687
+ } else if (typeof files === "string") {
8688
+ triggered = true;
8689
+ this.changedTests.add(resolve(this.vitest.config.root, files));
8690
+ }
8691
+ }
8692
+ });
8693
+ return triggered;
8694
+ }
8695
+ onChange = (id) => {
8696
+ id = slash(id);
8697
+ this.vitest.logger.clearHighlightCache(id);
8698
+ this.vitest.invalidateFile(id);
8699
+ const testFiles = this.getTestFilesFromWatcherTrigger(id);
8700
+ if (testFiles) {
8701
+ this.scheduleRerun(id);
8702
+ } else {
8703
+ const needsRerun = this.handleFileChanged(id);
8704
+ if (needsRerun) {
8705
+ this.scheduleRerun(id);
8706
+ }
8707
+ }
8708
+ };
8709
+ onUnlink = (id) => {
8710
+ id = slash(id);
8711
+ this.vitest.logger.clearHighlightCache(id);
8712
+ this.invalidates.add(id);
8713
+ if (this.vitest.state.filesMap.has(id)) {
8714
+ this.vitest.projects.forEach((project) => project._removeCachedTestFile(id));
8715
+ this.vitest.state.filesMap.delete(id);
8716
+ this.vitest.cache.results.removeFromCache(id);
8717
+ this.vitest.cache.stats.removeStats(id);
8718
+ this.changedTests.delete(id);
8719
+ this.vitest.report("onTestRemoved", id);
8720
+ }
8721
+ };
8722
+ onAdd = (id) => {
8723
+ id = slash(id);
8724
+ this.vitest.invalidateFile(id);
8725
+ const testFiles = this.getTestFilesFromWatcherTrigger(id);
8726
+ if (testFiles) {
8727
+ this.scheduleRerun(id);
8728
+ return;
8729
+ }
8730
+ let fileContent;
8731
+ const matchingProjects = [];
8732
+ this.vitest.projects.forEach((project) => {
8733
+ if (project.matchesTestGlob(id, () => fileContent ??= readFileSync(id, "utf-8"))) {
8734
+ matchingProjects.push(project);
8735
+ }
8736
+ });
8737
+ if (matchingProjects.length > 0) {
8738
+ this.changedTests.add(id);
8739
+ this.scheduleRerun(id);
8740
+ } else {
8741
+ const needsRerun = this.handleFileChanged(id);
8742
+ if (needsRerun) {
8743
+ this.scheduleRerun(id);
8744
+ }
8745
+ }
8746
+ };
8747
+ /**
8748
+ * @returns A value indicating whether rerun is needed (changedTests was mutated)
8749
+ */
8750
+ handleFileChanged(filepath) {
8751
+ if (this.changedTests.has(filepath) || this.invalidates.has(filepath)) {
8752
+ return false;
8753
+ }
8754
+ if (pm.isMatch(filepath, this.vitest.config.forceRerunTriggers)) {
8755
+ this.vitest.state.getFilepaths().forEach((file) => this.changedTests.add(file));
8756
+ return true;
8757
+ }
8758
+ const projects = this.vitest.projects.filter((project) => {
8759
+ const moduleGraph = project.browser?.vite.moduleGraph || project.vite.moduleGraph;
8760
+ return moduleGraph.getModulesByFile(filepath)?.size;
8761
+ });
8762
+ if (!projects.length) {
8763
+ if (this.vitest.state.filesMap.has(filepath) || this.vitest.projects.some((project) => project._isCachedTestFile(filepath))) {
8764
+ this.changedTests.add(filepath);
8765
+ return true;
8766
+ }
8767
+ return false;
8768
+ }
8769
+ const files = [];
8770
+ for (const project of projects) {
8771
+ const mods = project.browser?.vite.moduleGraph.getModulesByFile(filepath) || project.vite.moduleGraph.getModulesByFile(filepath);
8772
+ if (!mods || !mods.size) {
8773
+ continue;
8774
+ }
8775
+ this.invalidates.add(filepath);
8776
+ if (this.vitest.state.filesMap.has(filepath) || project._isCachedTestFile(filepath)) {
8777
+ this.changedTests.add(filepath);
8778
+ files.push(filepath);
8779
+ continue;
8780
+ }
8781
+ 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
+ }
8796
+ }
8797
+ return !!files.length;
8713
8798
  }
8714
- return names;
8715
8799
  }
8716
8800
 
8717
8801
  const WATCHER_DEBOUNCE = 100;
@@ -8904,7 +8988,7 @@ class Vitest {
8904
8988
  try {
8905
8989
  await this.cache.results.readFromCache();
8906
8990
  } catch {}
8907
- const projects = await this.resolveWorkspace(cliOptions);
8991
+ const projects = await this.resolveProjects(cliOptions);
8908
8992
  this.resolvedProjects = projects;
8909
8993
  this.projects = projects;
8910
8994
  await Promise.all(projects.flatMap((project) => {
@@ -8945,9 +9029,9 @@ class Vitest {
8945
9029
  */
8946
9030
  injectTestProject = async (config) => {
8947
9031
  const currentNames = new Set(this.projects.map((p) => p.name));
8948
- const workspace = await resolveWorkspace(this, this._options, undefined, Array.isArray(config) ? config : [config], currentNames);
8949
- this.projects.push(...workspace);
8950
- return workspace;
9032
+ const projects = await resolveProjects(this, this._options, undefined, Array.isArray(config) ? config : [config], currentNames);
9033
+ this.projects.push(...projects);
9034
+ return projects;
8951
9035
  };
8952
9036
  /**
8953
9037
  * Provide a value to the test context. This value will be available to all tests with `inject`.
@@ -9018,10 +9102,17 @@ class Vitest {
9018
9102
  }
9019
9103
  return join(configDir, workspaceConfigName);
9020
9104
  }
9021
- async resolveWorkspace(cliOptions) {
9105
+ async resolveProjects(cliOptions) {
9022
9106
  const names = new Set();
9107
+ 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);
9112
+ }
9023
9113
  if (Array.isArray(this.config.workspace)) {
9024
- return resolveWorkspace(this, cliOptions, undefined, this.config.workspace, names);
9114
+ 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);
9025
9116
  }
9026
9117
  const workspaceConfigPath = await this.resolveWorkspaceConfigPath();
9027
9118
  this._workspaceConfigPath = workspaceConfigPath;
@@ -9030,13 +9121,15 @@ class Vitest {
9030
9121
  if (!project) {
9031
9122
  return [];
9032
9123
  }
9033
- return resolveBrowserWorkspace(this, new Set([project.name]), [project]);
9124
+ return resolveBrowserProjects(this, new Set([project.name]), [project]);
9034
9125
  }
9126
+ const configFile = this.vite.config.configFile ? resolve(this.vite.config.root, this.vite.config.configFile) : "the root config file";
9127
+ this.logger.deprecate(`The workspace file is deprecated and will be removed in the next major. Please, use the \`projects\` field in ${configFile} instead.`);
9035
9128
  const workspaceModule = await this.import(workspaceConfigPath);
9036
9129
  if (!workspaceModule.default || !Array.isArray(workspaceModule.default)) {
9037
9130
  throw new TypeError(`Workspace config file "${workspaceConfigPath}" must export a default array of project paths.`);
9038
9131
  }
9039
- return resolveWorkspace(this, cliOptions, workspaceConfigPath, workspaceModule.default, names);
9132
+ return resolveProjects(this, cliOptions, workspaceConfigPath, workspaceModule.default, names);
9040
9133
  }
9041
9134
  /**
9042
9135
  * Glob test files in every project and create a TestSpecification for each file and pool.