vitest 3.1.2 → 3.2.0-beta.1

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 (47) hide show
  1. package/dist/browser.d.ts +2 -2
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{base.k5EeHg0m.js → base.SfTiRNZf.js} +2 -2
  4. package/dist/chunks/{cac.C5_4mAsf.js → cac.TfX2-DVH.js} +9 -7
  5. package/dist/chunks/{cli-api.z029PxYZ.js → cli-api.2970Nj9J.js} +937 -852
  6. package/dist/chunks/{coverage.BUdIvXbr.js → coverage.z0LVMxgb.js} +12 -11
  7. package/dist/chunks/{environment.d.Dmw5ulng.d.ts → environment.d.D8YDy2v5.d.ts} +2 -1
  8. package/dist/chunks/{execute.CwmnH2oH.js → execute.BpmIjFTD.js} +22 -12
  9. package/dist/chunks/{global.d.CXRAxnWc.d.ts → global.d.BCOHQEpR.d.ts} +7 -2
  10. package/dist/chunks/{globals.CZAEe_Gf.js → globals.Cg4NtV4P.js} +2 -2
  11. package/dist/chunks/{index.B0uVAVvx.js → index.BPc7M5ni.js} +1 -1
  12. package/dist/chunks/{index._vwY_KdO.js → index.CUacZlWG.js} +42 -21
  13. package/dist/chunks/{index.Cu2UlluP.js → index.DbWBPwtH.js} +2 -2
  14. package/dist/chunks/{reporters.d.79o4mouw.d.ts → reporters.d.DGm4k1Wx.d.ts} +59 -11
  15. package/dist/chunks/{runBaseTests.BV8m0B-u.js → runBaseTests.CguliJB5.js} +5 -5
  16. package/dist/chunks/{setup-common.AQcDs321.js → setup-common.BP6KrF_Z.js} +1 -1
  17. package/dist/chunks/{utils.Cc45eY3L.js → utils.8gfOgtry.js} +19 -12
  18. package/dist/chunks/{vi.ClIskdbk.js → vi.BFR5YIgu.js} +3 -0
  19. package/dist/chunks/{vite.d.BVr6Nvdj.d.ts → vite.d.DjP_ALCZ.d.ts} +1 -1
  20. package/dist/chunks/{vm.BmHENIuV.js → vm.CuLHT1BG.js} +1 -1
  21. package/dist/chunks/{worker.d.CHGSOG0s.d.ts → worker.d.CoCI7hzP.d.ts} +1 -1
  22. package/dist/chunks/{worker.d.C-KN07Ls.d.ts → worker.d.D5Xdi-Zr.d.ts} +1 -1
  23. package/dist/cli.js +1 -1
  24. package/dist/config.cjs +3 -0
  25. package/dist/config.d.ts +8 -5
  26. package/dist/config.js +3 -0
  27. package/dist/coverage.d.ts +3 -3
  28. package/dist/coverage.js +1 -1
  29. package/dist/environments.d.ts +2 -2
  30. package/dist/execute.d.ts +2 -2
  31. package/dist/execute.js +1 -1
  32. package/dist/index.d.ts +45 -10
  33. package/dist/index.js +2 -2
  34. package/dist/node.d.ts +8 -8
  35. package/dist/node.js +7 -7
  36. package/dist/reporters.d.ts +3 -3
  37. package/dist/reporters.js +2 -2
  38. package/dist/runners.d.ts +1 -1
  39. package/dist/runners.js +3 -3
  40. package/dist/workers/forks.js +2 -2
  41. package/dist/workers/runVmTests.js +5 -5
  42. package/dist/workers/threads.js +2 -2
  43. package/dist/workers/vmForks.js +2 -2
  44. package/dist/workers/vmThreads.js +2 -2
  45. package/dist/workers.d.ts +3 -3
  46. package/dist/workers.js +3 -3
  47. package/package.json +11 -11
@@ -5,15 +5,15 @@ import p, { resolve as resolve$1 } from 'node:path';
5
5
  import { noop, isPrimitive, createDefer, highlight, toArray, deepMerge, nanoid, slash, deepClone, notNullish } from '@vitest/utils';
6
6
  import { f as findUp, p as prompt } from './index.DBIGubLC.js';
7
7
  import * as vite from 'vite';
8
- import { searchForWorkspaceRoot, version, createServer, mergeConfig } from 'vite';
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.C5_4mAsf.js';
14
+ import { v as version$1 } from './cac.TfX2-DVH.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._vwY_KdO.js';
16
+ import { p as parse, s as stringify, g as printError, h as generateCodeFrame, b as BenchmarkReportsMap, R as ReportersMap, i as BlobReporter, r as readBlobs, H as HangingProcessReporter } from './index.CUacZlWG.js';
17
17
  import require$$0$3 from 'events';
18
18
  import require$$1$1 from 'https';
19
19
  import require$$2 from 'http';
@@ -28,11 +28,11 @@ 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.BUdIvXbr.js';
31
+ import { R as RandomSequencer, i as isPackageExists, h as hash, V as VitestCache, g as getFilePoolName, d as isBrowserEnabled, m as mm, r as resolveConfig, e as groupBy, f as getCoverageProvider, j as createPool, w as wildcardPatternToRegExp, a as resolveApiServerConfig, s as stdout } from './coverage.z0LVMxgb.js';
32
32
  import { c as convertTasksToEvents } from './typechecker.DYQbn8uK.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';
35
+ import { c as formatProjectName, w as withLabel, e as errorBanner, d as divider } from './utils.8gfOgtry.js';
36
36
  import { createRequire } from 'node:module';
37
37
  import url from 'node:url';
38
38
  import { i as isTTY, a as isWindows } from './env.Dq0hM4Xv.js';
@@ -5125,6 +5125,12 @@ function setup(ctx, _server) {
5125
5125
  getResolvedProjectNames() {
5126
5126
  return ctx.projects.map((p) => p.name);
5127
5127
  },
5128
+ getResolvedProjectLabels() {
5129
+ return ctx.projects.map((p) => ({
5130
+ name: p.name,
5131
+ color: p.color
5132
+ }));
5133
+ },
5128
5134
  async getTransformResult(projectName, id, browser = false) {
5129
5135
  const project = ctx.getProjectByName(projectName);
5130
5136
  const result = browser ? await project.browser.vite.transformRequest(id) : await project.vitenode.transformRequest(id);
@@ -5252,6 +5258,7 @@ var setup$1 = /*#__PURE__*/Object.freeze({
5252
5258
 
5253
5259
  class BrowserSessions {
5254
5260
  sessions = new Map();
5261
+ sessionIds = new Set();
5255
5262
  getSession(sessionId) {
5256
5263
  return this.sessions.get(sessionId);
5257
5264
  }
@@ -5408,6 +5415,9 @@ class Logger {
5408
5415
  printError(err, options = {}) {
5409
5416
  printError(err, this.ctx, this, options);
5410
5417
  }
5418
+ deprecate(message) {
5419
+ this.log(c.bold(c.bgYellow(" DEPRECATED ")), c.yellow(message));
5420
+ }
5411
5421
  clearHighlightCache(filename) {
5412
5422
  if (filename) {
5413
5423
  this._highlights.delete(filename);
@@ -5448,7 +5458,7 @@ class Logger {
5448
5458
  const config = project.config;
5449
5459
  const printConfig = !project.isRootProject() && project.name;
5450
5460
  if (printConfig) {
5451
- this.console.error(`\n${formatProjectName(project.name)}\n`);
5461
+ this.console.error(`\n${formatProjectName(project)}\n`);
5452
5462
  }
5453
5463
  if (config.include) {
5454
5464
  this.console.error(c.dim("include: ") + c.yellow(config.include.join(comma)));
@@ -5500,7 +5510,7 @@ class Logger {
5500
5510
  if (!origin) {
5501
5511
  return;
5502
5512
  }
5503
- const output = project.isRootProject() ? "" : formatProjectName(project.name);
5513
+ const output = project.isRootProject() ? "" : formatProjectName(project);
5504
5514
  const provider = project.browser.provider.name;
5505
5515
  const providerString = provider === "preview" ? "" : ` by ${c.reset(c.bold(provider))}`;
5506
5516
  this.log(c.dim(`${output}Browser runner started${providerString} ${c.dim("at")} ${c.blue(new URL("/", origin))}\n`));
@@ -6520,7 +6530,8 @@ function resolveOptimizerConfig(_testOptions, viteOptions, testConfig, viteCache
6520
6530
  const runtime = currentInclude.filter((n) => n.endsWith("jsx-dev-runtime") || n.endsWith("jsx-runtime"));
6521
6531
  exclude.push(...runtime);
6522
6532
  const include = (testOptions.include || viteOptions?.include || []).filter((n) => !exclude.includes(n));
6523
- newConfig.cacheDir = testConfig.cache !== false && testConfig.cache?.dir || VitestCache.resolveCacheDir(root, viteCacheDir, testConfig.name);
6533
+ const projectName = typeof testConfig.name === "string" ? testConfig.name : testConfig.name?.label;
6534
+ newConfig.cacheDir = testConfig.cache !== false && testConfig.cache?.dir || VitestCache.resolveCacheDir(root, viteCacheDir, projectName);
6524
6535
  newConfig.optimizeDeps = {
6525
6536
  ...viteOptions,
6526
6537
  ...testOptions,
@@ -6687,7 +6698,10 @@ function WorkspaceVitestPlugin(project, options) {
6687
6698
  const defines = deleteDefineConfig(viteConfig);
6688
6699
  const testConfig = viteConfig.test || {};
6689
6700
  const root = testConfig.root || viteConfig.root || options.root;
6690
- let name = testConfig.name;
6701
+ let { label: name, color } = typeof testConfig.name === "string" ? { label: testConfig.name } : {
6702
+ label: "",
6703
+ ...testConfig.name
6704
+ };
6691
6705
  if (!name) {
6692
6706
  if (typeof options.workspacePath === "string") {
6693
6707
  const dir = options.workspacePath.endsWith("/") ? options.workspacePath.slice(0, -1) : dirname(options.workspacePath);
@@ -6702,26 +6716,6 @@ function WorkspaceVitestPlugin(project, options) {
6702
6716
  name = options.workspacePath.toString();
6703
6717
  }
6704
6718
  }
6705
- const workspaceNames = [name];
6706
- if (viteConfig.test?.browser?.enabled) {
6707
- if (viteConfig.test.browser.name) {
6708
- const browser = viteConfig.test.browser.name;
6709
- workspaceNames.push(name ? `${name} (${browser})` : browser);
6710
- }
6711
- viteConfig.test.browser.instances?.forEach((instance) => {
6712
- instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
6713
- workspaceNames.push(instance.name);
6714
- });
6715
- }
6716
- const filters = project.vitest.config.project;
6717
- if (filters.length) {
6718
- const hasProject = workspaceNames.some((name) => {
6719
- return project.vitest.matchesProjectFilter(name);
6720
- });
6721
- if (!hasProject) {
6722
- throw new VitestFilteredOutProjectError();
6723
- }
6724
- }
6725
6719
  const resolveOptions = getDefaultResolveOptions();
6726
6720
  const config = {
6727
6721
  root,
@@ -6745,9 +6739,35 @@ function WorkspaceVitestPlugin(project, options) {
6745
6739
  fs: { allow: resolveFsAllow(project.vitest.config.root, project.vitest.vite.config.configFile) }
6746
6740
  },
6747
6741
  environments: { ssr: { resolve: resolveOptions } },
6748
- test: { name }
6742
+ test: { name: {
6743
+ label: name,
6744
+ color
6745
+ } }
6749
6746
  };
6747
+ if (project.vitest._options.browser && viteConfig.test?.browser) {
6748
+ viteConfig.test.browser = mergeConfig(viteConfig.test.browser, project.vitest._options.browser);
6749
+ }
6750
6750
  config.test.defines = defines;
6751
+ const workspaceNames = [name];
6752
+ if (viteConfig.test?.browser?.enabled) {
6753
+ if (viteConfig.test.browser.name && !viteConfig.test.browser.instances?.length) {
6754
+ const browser = viteConfig.test.browser.name;
6755
+ workspaceNames.push(name ? `${name} (${browser})` : browser);
6756
+ }
6757
+ viteConfig.test.browser.instances?.forEach((instance) => {
6758
+ instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
6759
+ workspaceNames.push(instance.name);
6760
+ });
6761
+ }
6762
+ const filters = project.vitest.config.project;
6763
+ if (filters.length) {
6764
+ const hasProject = workspaceNames.some((name) => {
6765
+ return project.vitest.matchesProjectFilter(name);
6766
+ });
6767
+ if (!hasProject) {
6768
+ throw new VitestFilteredOutProjectError();
6769
+ }
6770
+ }
6751
6771
  const classNameStrategy = typeof testConfig.css !== "boolean" && testConfig.css?.modules?.classNameStrategy || "stable";
6752
6772
  if (classNameStrategy !== "scoped") {
6753
6773
  config.css ??= {};
@@ -6976,6 +6996,12 @@ class TestProject {
6976
6996
  return this.config.name || "";
6977
6997
  }
6978
6998
  /**
6999
+ * The color used when reporting tasks of this project.
7000
+ */
7001
+ get color() {
7002
+ return this.config.color;
7003
+ }
7004
+ /**
6979
7005
  * Serialized project configuration. This is the config that tests receive.
6980
7006
  */
6981
7007
  get serializedConfig() {
@@ -7360,161 +7386,478 @@ async function initializeProject(workspacePath, ctx, options) {
7360
7386
  return project;
7361
7387
  }
7362
7388
 
7363
- async function loadCustomReporterModule(path, runner) {
7364
- let customReporterModule;
7365
- try {
7366
- customReporterModule = await runner.executeId(path);
7367
- } catch (customReporterModuleError) {
7368
- throw new Error(`Failed to load custom Reporter from ${path}`, { cause: customReporterModuleError });
7369
- }
7370
- if (customReporterModule.default === null || customReporterModule.default === undefined) {
7371
- throw new Error(`Custom reporter loaded from ${path} was not the default export`);
7372
- }
7373
- return customReporterModule.default;
7374
- }
7375
- function createReporters(reporterReferences, ctx) {
7376
- const runner = ctx.runner;
7377
- const promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
7378
- if (Array.isArray(referenceOrInstance)) {
7379
- const [reporterName, reporterOptions] = referenceOrInstance;
7380
- if (reporterName === "html") {
7381
- await ctx.packageInstaller.ensureInstalled("@vitest/ui", runner.root, ctx.version);
7382
- const CustomReporter = await loadCustomReporterModule("@vitest/ui/reporter", runner);
7383
- return new CustomReporter(reporterOptions);
7384
- } else if (reporterName in ReportersMap) {
7385
- const BuiltinReporter = ReportersMap[reporterName];
7386
- return new BuiltinReporter(reporterOptions);
7387
- } else {
7388
- const CustomReporter = await loadCustomReporterModule(reporterName, runner);
7389
- return new CustomReporter(reporterOptions);
7390
- }
7389
+ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projectsDefinition, names) {
7390
+ const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition);
7391
+ const overridesOptions = [
7392
+ "logHeapUsage",
7393
+ "allowOnly",
7394
+ "sequence",
7395
+ "testTimeout",
7396
+ "pool",
7397
+ "update",
7398
+ "globals",
7399
+ "expandSnapshotDiff",
7400
+ "disableConsoleIntercept",
7401
+ "retry",
7402
+ "testNamePattern",
7403
+ "passWithNoTests",
7404
+ "bail",
7405
+ "isolate",
7406
+ "printConsoleTrace",
7407
+ "inspect",
7408
+ "inspectBrk",
7409
+ "fileParallelism"
7410
+ ];
7411
+ const cliOverrides = overridesOptions.reduce((acc, name) => {
7412
+ if (name in cliOptions) {
7413
+ acc[name] = cliOptions[name];
7391
7414
  }
7392
- return referenceOrInstance;
7393
- });
7394
- return Promise.all(promisedReporters);
7395
- }
7396
- function createBenchmarkReporters(reporterReferences, runner) {
7397
- const promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
7398
- if (typeof referenceOrInstance === "string") {
7399
- if (referenceOrInstance in BenchmarkReportsMap) {
7400
- const BuiltinReporter = BenchmarkReportsMap[referenceOrInstance];
7401
- return new BuiltinReporter();
7402
- } else {
7403
- const CustomReporter = await loadCustomReporterModule(referenceOrInstance, runner);
7404
- return new CustomReporter();
7415
+ return acc;
7416
+ }, {});
7417
+ const projectPromises = [];
7418
+ const fileProjects = [...configFiles, ...nonConfigDirectories];
7419
+ const concurrent = limitConcurrency(nodeos__default.availableParallelism?.() || nodeos__default.cpus().length || 5);
7420
+ projectConfigs.forEach((options, index) => {
7421
+ const configRoot = workspaceConfigPath ? dirname(workspaceConfigPath) : vitest.config.root;
7422
+ const configFile = typeof options.extends === "string" ? resolve(configRoot, options.extends) : options.extends === true ? vitest.vite.config.configFile || false : false;
7423
+ const root = options.root ? resolve(configRoot, options.root) : vitest.config.root;
7424
+ projectPromises.push(concurrent(() => initializeProject(index, vitest, {
7425
+ ...options,
7426
+ root,
7427
+ configFile,
7428
+ test: {
7429
+ ...options.test,
7430
+ ...cliOverrides
7405
7431
  }
7406
- }
7407
- return referenceOrInstance;
7432
+ })));
7408
7433
  });
7409
- return Promise.all(promisedReporters);
7410
- }
7411
-
7412
- function parseFilter(filter) {
7413
- const colonIndex = filter.lastIndexOf(":");
7414
- if (colonIndex === -1) {
7415
- return { filename: filter };
7416
- }
7417
- const [parsedFilename, lineNumber] = [filter.substring(0, colonIndex), filter.substring(colonIndex + 1)];
7418
- if (lineNumber.match(/^\d+$/)) {
7419
- return {
7420
- filename: parsedFilename,
7421
- lineNumber: Number.parseInt(lineNumber)
7422
- };
7423
- } else if (lineNumber.match(/^\d+-\d+$/)) {
7424
- throw new RangeLocationFilterProvidedError(filter);
7425
- } else {
7426
- return { filename: filter };
7427
- }
7428
- }
7429
- function groupFilters(filters) {
7430
- const groupedFilters_ = groupBy(filters, (f) => f.filename);
7431
- const groupedFilters = Object.fromEntries(Object.entries(groupedFilters_).map((entry) => {
7432
- const [filename, filters] = entry;
7433
- const testLocations = filters.map((f) => f.lineNumber);
7434
- return [filename, testLocations.filter((l) => l !== undefined)];
7435
- }));
7436
- return groupedFilters;
7437
- }
7438
-
7439
- class VitestSpecifications {
7440
- _cachedSpecs = new Map();
7441
- constructor(vitest) {
7442
- this.vitest = vitest;
7443
- }
7444
- getModuleSpecifications(moduleId) {
7445
- const _cached = this.getCachedSpecifications(moduleId);
7446
- if (_cached) {
7447
- return _cached;
7448
- }
7449
- const specs = [];
7450
- for (const project of this.vitest.projects) {
7451
- if (project._isCachedTestFile(moduleId)) {
7452
- specs.push(project.createSpecification(moduleId));
7453
- }
7454
- if (project._isCachedTypecheckFile(moduleId)) {
7455
- specs.push(project.createSpecification(moduleId, [], "typescript"));
7434
+ for (const path of fileProjects) {
7435
+ if (vitest.vite.config.configFile === path) {
7436
+ const project = getDefaultTestProject(vitest);
7437
+ if (project) {
7438
+ projectPromises.push(Promise.resolve(project));
7456
7439
  }
7440
+ continue;
7457
7441
  }
7458
- specs.forEach((spec) => this.ensureSpecificationCached(spec));
7459
- return specs;
7442
+ const configFile = path.endsWith("/") ? false : path;
7443
+ const root = path.endsWith("/") ? path : dirname(path);
7444
+ projectPromises.push(concurrent(() => initializeProject(path, vitest, {
7445
+ root,
7446
+ configFile,
7447
+ test: cliOverrides
7448
+ })));
7460
7449
  }
7461
- async getRelevantTestSpecifications(filters = []) {
7462
- return this.filterTestsBySource(await this.globTestSpecifications(filters));
7450
+ if (!projectPromises.length) {
7451
+ throw new Error([
7452
+ "No projects were found. Make sure your configuration is correct. ",
7453
+ vitest.config.project.length ? `The filter matched no projects: ${vitest.config.project.join(", ")}. ` : "",
7454
+ `The projects definition: ${JSON.stringify(projectsDefinition, null, 4)}.`
7455
+ ].join(""));
7463
7456
  }
7464
- async globTestSpecifications(filters = []) {
7465
- const files = [];
7466
- const dir = process.cwd();
7467
- const parsedFilters = filters.map((f) => parseFilter(f));
7468
- if (!this.vitest.config.includeTaskLocation && parsedFilters.some((f) => f.lineNumber !== undefined)) {
7469
- throw new IncludeTaskLocationDisabledError();
7470
- }
7471
- const testLines = groupFilters(parsedFilters.map((f) => ({
7472
- ...f,
7473
- filename: resolve(dir, f.filename)
7474
- })));
7475
- const testLocHasMatch = {};
7476
- await Promise.all(this.vitest.projects.map(async (project) => {
7477
- const { testFiles, typecheckTestFiles } = await project.globTestFiles(parsedFilters.map((f) => f.filename));
7478
- testFiles.forEach((file) => {
7479
- const lines = testLines[file];
7480
- testLocHasMatch[file] = true;
7481
- const spec = project.createSpecification(file, lines);
7482
- this.ensureSpecificationCached(spec);
7483
- files.push(spec);
7484
- });
7485
- typecheckTestFiles.forEach((file) => {
7486
- const lines = testLines[file];
7487
- testLocHasMatch[file] = true;
7488
- const spec = project.createSpecification(file, lines, "typescript");
7489
- this.ensureSpecificationCached(spec);
7490
- files.push(spec);
7491
- });
7492
- }));
7493
- Object.entries(testLines).forEach(([filepath, loc]) => {
7494
- if (loc.length !== 0 && !testLocHasMatch[filepath]) {
7495
- throw new LocationFilterFileNotFoundError(relative(dir, filepath));
7457
+ const resolvedProjectsPromises = await Promise.allSettled(projectPromises);
7458
+ const errors = [];
7459
+ const resolvedProjects = [];
7460
+ for (const result of resolvedProjectsPromises) {
7461
+ if (result.status === "rejected") {
7462
+ if (result.reason instanceof VitestFilteredOutProjectError) {
7463
+ continue;
7496
7464
  }
7497
- });
7498
- return files;
7499
- }
7500
- clearCache(moduleId) {
7501
- if (moduleId) {
7502
- this._cachedSpecs.delete(moduleId);
7465
+ errors.push(result.reason);
7503
7466
  } else {
7504
- this._cachedSpecs.clear();
7467
+ resolvedProjects.push(result.value);
7505
7468
  }
7506
7469
  }
7507
- getCachedSpecifications(moduleId) {
7508
- return this._cachedSpecs.get(moduleId);
7470
+ if (errors.length) {
7471
+ throw new AggregateError(errors, "Failed to initialize projects. There were errors during projects setup. See below for more details.");
7509
7472
  }
7510
- ensureSpecificationCached(spec) {
7511
- const file = spec.moduleId;
7512
- const specs = this._cachedSpecs.get(file) || [];
7513
- const index = specs.findIndex((_s) => _s.project === spec.project && _s.pool === spec.pool);
7514
- if (index === -1) {
7515
- specs.push(spec);
7516
- this._cachedSpecs.set(file, specs);
7517
- } else {
7473
+ for (const project of resolvedProjects) {
7474
+ const name = project.name;
7475
+ if (names.has(name)) {
7476
+ const duplicate = resolvedProjects.find((p) => p.name === name && p !== project);
7477
+ const filesError = fileProjects.length ? [
7478
+ "\n\nYour config matched these files:\n",
7479
+ fileProjects.map((p) => ` - ${relative(vitest.config.root, p)}`).join("\n"),
7480
+ "\n\n"
7481
+ ].join("") : " ";
7482
+ throw new Error([
7483
+ `Project name "${name}"`,
7484
+ project.vite.config.configFile ? ` from "${relative(vitest.config.root, project.vite.config.configFile)}"` : "",
7485
+ " is not unique.",
7486
+ duplicate?.vite.config.configFile ? ` The project is already defined by "${relative(vitest.config.root, duplicate.vite.config.configFile)}".` : "",
7487
+ filesError,
7488
+ "All projects should have unique names. Make sure your configuration is correct."
7489
+ ].join(""));
7490
+ }
7491
+ names.add(name);
7492
+ }
7493
+ return resolveBrowserProjects(vitest, names, resolvedProjects);
7494
+ }
7495
+ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7496
+ const removeProjects = new Set();
7497
+ resolvedProjects.forEach((project) => {
7498
+ if (!project.config.browser.enabled) {
7499
+ return;
7500
+ }
7501
+ const instances = project.config.browser.instances || [];
7502
+ const browser = project.config.browser.name;
7503
+ if (instances.length === 0 && browser) {
7504
+ instances.push({
7505
+ browser,
7506
+ name: project.name ? `${project.name} (${browser})` : browser
7507
+ });
7508
+ vitest.logger.warn(withLabel("yellow", "Vitest", [
7509
+ `No browser "instances" were defined`,
7510
+ project.name ? ` for the "${project.name}" project. ` : ". ",
7511
+ `Running tests in "${project.config.browser.name}" browser. `,
7512
+ "The \"browser.name\" field is deprecated since Vitest 3. ",
7513
+ "Read more: https://vitest.dev/guide/browser/config#browser-instances"
7514
+ ].filter(Boolean).join("")));
7515
+ }
7516
+ const originalName = project.config.name;
7517
+ const filteredInstances = vitest.matchesProjectFilter(originalName) ? instances : instances.filter((instance) => {
7518
+ const newName = instance.name;
7519
+ return vitest.matchesProjectFilter(newName);
7520
+ });
7521
+ if (!filteredInstances.length) {
7522
+ removeProjects.add(project);
7523
+ return;
7524
+ }
7525
+ if (project.config.browser.providerOptions) {
7526
+ 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.`));
7527
+ }
7528
+ filteredInstances.forEach((config, index) => {
7529
+ const browser = config.browser;
7530
+ if (!browser) {
7531
+ const nth = index + 1;
7532
+ const ending = nth === 2 ? "nd" : nth === 3 ? "rd" : "th";
7533
+ 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.`);
7534
+ }
7535
+ const name = config.name;
7536
+ if (name == null) {
7537
+ throw new Error(`The browser configuration must have a "name" property. This is a bug in Vitest. Please, open a new issue with reproduction`);
7538
+ }
7539
+ if (names.has(name)) {
7540
+ throw new Error([
7541
+ `Cannot define a nested project for a ${browser} browser. The project name "${name}" was already defined. `,
7542
+ "If you have multiple instances for the same browser, make sure to define a custom \"name\". ",
7543
+ "All projects should have unique names. Make sure your configuration is correct."
7544
+ ].join(""));
7545
+ }
7546
+ names.add(name);
7547
+ const clonedConfig = cloneConfig(project, config);
7548
+ clonedConfig.name = name;
7549
+ const clone = TestProject._cloneBrowserProject(project, clonedConfig);
7550
+ resolvedProjects.push(clone);
7551
+ });
7552
+ removeProjects.add(project);
7553
+ });
7554
+ resolvedProjects = resolvedProjects.filter((project) => !removeProjects.has(project));
7555
+ const headedBrowserProjects = resolvedProjects.filter((project) => {
7556
+ return project.config.browser.enabled && !project.config.browser.headless;
7557
+ });
7558
+ if (headedBrowserProjects.length > 1) {
7559
+ 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("");
7560
+ if (!isTTY) {
7561
+ throw new Error(`${message} Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option.`);
7562
+ }
7563
+ const prompts = await import('./index.DBIGubLC.js').then(function (n) { return n.i; });
7564
+ const { projectName } = await prompts.default({
7565
+ type: "select",
7566
+ name: "projectName",
7567
+ choices: headedBrowserProjects.map((project) => ({
7568
+ title: project.name,
7569
+ value: project.name
7570
+ })),
7571
+ 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.`
7572
+ });
7573
+ if (!projectName) {
7574
+ throw new Error("The test run was aborted.");
7575
+ }
7576
+ return resolvedProjects.filter((project) => project.name === projectName);
7577
+ }
7578
+ return resolvedProjects;
7579
+ }
7580
+ function cloneConfig(project, { browser,...config }) {
7581
+ const { locators, viewport, testerHtmlPath, headless, screenshotDirectory, screenshotFailures, browser: _browser, name,...overrideConfig } = config;
7582
+ const currentConfig = project.config.browser;
7583
+ return mergeConfig({
7584
+ ...deepClone(project.config),
7585
+ browser: {
7586
+ ...project.config.browser,
7587
+ locators: locators ? { testIdAttribute: locators.testIdAttribute ?? currentConfig.locators.testIdAttribute } : project.config.browser.locators,
7588
+ viewport: viewport ?? currentConfig.viewport,
7589
+ testerHtmlPath: testerHtmlPath ?? currentConfig.testerHtmlPath,
7590
+ screenshotDirectory: screenshotDirectory ?? currentConfig.screenshotDirectory,
7591
+ screenshotFailures: screenshotFailures ?? currentConfig.screenshotFailures,
7592
+ headless: headless ?? currentConfig.headless,
7593
+ name: browser,
7594
+ providerOptions: config,
7595
+ instances: undefined
7596
+ }
7597
+ }, overrideConfig);
7598
+ }
7599
+ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition) {
7600
+ const projectsOptions = [];
7601
+ const projectsConfigFiles = [];
7602
+ const projectsGlobMatches = [];
7603
+ const nonConfigProjectDirectories = [];
7604
+ for (const definition of projectsDefinition) {
7605
+ if (typeof definition === "string") {
7606
+ const stringOption = definition.replace("<rootDir>", vitest.config.root);
7607
+ if (!isDynamicPattern(stringOption)) {
7608
+ const file = resolve(vitest.config.root, stringOption);
7609
+ if (!existsSync(file)) {
7610
+ const relativeWorkspaceConfigPath = workspaceConfigPath ? relative(vitest.config.root, workspaceConfigPath) : undefined;
7611
+ const note = workspaceConfigPath ? `Workspace config file "${relativeWorkspaceConfigPath}"` : "Projects definition";
7612
+ throw new Error(`${note} references a non-existing file or a directory: ${file}`);
7613
+ }
7614
+ const stats = await promises.stat(file);
7615
+ if (stats.isFile()) {
7616
+ projectsConfigFiles.push(file);
7617
+ } else if (stats.isDirectory()) {
7618
+ const configFile = await resolveDirectoryConfig(file);
7619
+ if (configFile) {
7620
+ projectsConfigFiles.push(configFile);
7621
+ } else {
7622
+ const directory = file[file.length - 1] === "/" ? file : `${file}/`;
7623
+ nonConfigProjectDirectories.push(directory);
7624
+ }
7625
+ } else {
7626
+ throw new TypeError(`Unexpected file type: ${file}`);
7627
+ }
7628
+ } else {
7629
+ projectsGlobMatches.push(stringOption);
7630
+ }
7631
+ } else if (typeof definition === "function") {
7632
+ projectsOptions.push(await definition({
7633
+ command: vitest.vite.config.command,
7634
+ mode: vitest.vite.config.mode,
7635
+ isPreview: false,
7636
+ isSsrBuild: false
7637
+ }));
7638
+ } else {
7639
+ projectsOptions.push(await definition);
7640
+ }
7641
+ }
7642
+ if (projectsGlobMatches.length) {
7643
+ const globOptions = {
7644
+ absolute: true,
7645
+ dot: true,
7646
+ onlyFiles: false,
7647
+ cwd: vitest.config.root,
7648
+ expandDirectories: false,
7649
+ ignore: [
7650
+ "**/node_modules/**",
7651
+ "**/*.timestamp-*",
7652
+ "**/.DS_Store"
7653
+ ]
7654
+ };
7655
+ const projectsFs = await glob(projectsGlobMatches, globOptions);
7656
+ await Promise.all(projectsFs.map(async (path) => {
7657
+ if (path.endsWith("/")) {
7658
+ const configFile = await resolveDirectoryConfig(path);
7659
+ if (configFile) {
7660
+ projectsConfigFiles.push(configFile);
7661
+ } else {
7662
+ nonConfigProjectDirectories.push(path);
7663
+ }
7664
+ } else {
7665
+ projectsConfigFiles.push(path);
7666
+ }
7667
+ }));
7668
+ }
7669
+ const projectConfigFiles = Array.from(new Set(projectsConfigFiles));
7670
+ return {
7671
+ projectConfigs: projectsOptions,
7672
+ nonConfigDirectories: nonConfigProjectDirectories,
7673
+ configFiles: projectConfigFiles
7674
+ };
7675
+ }
7676
+ async function resolveDirectoryConfig(directory) {
7677
+ const files = new Set(await promises.readdir(directory));
7678
+ const configFile = configFiles.find((file) => files.has(file));
7679
+ if (configFile) {
7680
+ return resolve(directory, configFile);
7681
+ }
7682
+ return null;
7683
+ }
7684
+ function getDefaultTestProject(vitest) {
7685
+ const filter = vitest.config.project;
7686
+ const project = vitest._ensureRootProject();
7687
+ if (!filter.length) {
7688
+ return project;
7689
+ }
7690
+ const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
7691
+ if (hasProjects) {
7692
+ return project;
7693
+ }
7694
+ return null;
7695
+ }
7696
+ function getPotentialProjectNames(project) {
7697
+ const names = [project.name];
7698
+ if (project.config.browser.instances) {
7699
+ names.push(...project.config.browser.instances.map((i) => i.name));
7700
+ } else if (project.config.browser.name) {
7701
+ names.push(project.config.browser.name);
7702
+ }
7703
+ return names;
7704
+ }
7705
+
7706
+ async function loadCustomReporterModule(path, runner) {
7707
+ let customReporterModule;
7708
+ try {
7709
+ customReporterModule = await runner.executeId(path);
7710
+ } catch (customReporterModuleError) {
7711
+ throw new Error(`Failed to load custom Reporter from ${path}`, { cause: customReporterModuleError });
7712
+ }
7713
+ if (customReporterModule.default === null || customReporterModule.default === undefined) {
7714
+ throw new Error(`Custom reporter loaded from ${path} was not the default export`);
7715
+ }
7716
+ return customReporterModule.default;
7717
+ }
7718
+ function createReporters(reporterReferences, ctx) {
7719
+ const runner = ctx.runner;
7720
+ const promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
7721
+ if (Array.isArray(referenceOrInstance)) {
7722
+ const [reporterName, reporterOptions] = referenceOrInstance;
7723
+ if (reporterName === "html") {
7724
+ await ctx.packageInstaller.ensureInstalled("@vitest/ui", runner.root, ctx.version);
7725
+ const CustomReporter = await loadCustomReporterModule("@vitest/ui/reporter", runner);
7726
+ return new CustomReporter(reporterOptions);
7727
+ } else if (reporterName in ReportersMap) {
7728
+ const BuiltinReporter = ReportersMap[reporterName];
7729
+ return new BuiltinReporter(reporterOptions);
7730
+ } else {
7731
+ const CustomReporter = await loadCustomReporterModule(reporterName, runner);
7732
+ return new CustomReporter(reporterOptions);
7733
+ }
7734
+ }
7735
+ return referenceOrInstance;
7736
+ });
7737
+ return Promise.all(promisedReporters);
7738
+ }
7739
+ function createBenchmarkReporters(reporterReferences, runner) {
7740
+ const promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
7741
+ if (typeof referenceOrInstance === "string") {
7742
+ if (referenceOrInstance in BenchmarkReportsMap) {
7743
+ const BuiltinReporter = BenchmarkReportsMap[referenceOrInstance];
7744
+ return new BuiltinReporter();
7745
+ } else {
7746
+ const CustomReporter = await loadCustomReporterModule(referenceOrInstance, runner);
7747
+ return new CustomReporter();
7748
+ }
7749
+ }
7750
+ return referenceOrInstance;
7751
+ });
7752
+ return Promise.all(promisedReporters);
7753
+ }
7754
+
7755
+ function parseFilter(filter) {
7756
+ const colonIndex = filter.lastIndexOf(":");
7757
+ if (colonIndex === -1) {
7758
+ return { filename: filter };
7759
+ }
7760
+ const [parsedFilename, lineNumber] = [filter.substring(0, colonIndex), filter.substring(colonIndex + 1)];
7761
+ if (lineNumber.match(/^\d+$/)) {
7762
+ return {
7763
+ filename: parsedFilename,
7764
+ lineNumber: Number.parseInt(lineNumber)
7765
+ };
7766
+ } else if (lineNumber.match(/^\d+-\d+$/)) {
7767
+ throw new RangeLocationFilterProvidedError(filter);
7768
+ } else {
7769
+ return { filename: filter };
7770
+ }
7771
+ }
7772
+ function groupFilters(filters) {
7773
+ const groupedFilters_ = groupBy(filters, (f) => f.filename);
7774
+ const groupedFilters = Object.fromEntries(Object.entries(groupedFilters_).map((entry) => {
7775
+ const [filename, filters] = entry;
7776
+ const testLocations = filters.map((f) => f.lineNumber);
7777
+ return [filename, testLocations.filter((l) => l !== undefined)];
7778
+ }));
7779
+ return groupedFilters;
7780
+ }
7781
+
7782
+ class VitestSpecifications {
7783
+ _cachedSpecs = new Map();
7784
+ constructor(vitest) {
7785
+ this.vitest = vitest;
7786
+ }
7787
+ getModuleSpecifications(moduleId) {
7788
+ const _cached = this.getCachedSpecifications(moduleId);
7789
+ if (_cached) {
7790
+ return _cached;
7791
+ }
7792
+ const specs = [];
7793
+ for (const project of this.vitest.projects) {
7794
+ if (project._isCachedTestFile(moduleId)) {
7795
+ specs.push(project.createSpecification(moduleId));
7796
+ }
7797
+ if (project._isCachedTypecheckFile(moduleId)) {
7798
+ specs.push(project.createSpecification(moduleId, [], "typescript"));
7799
+ }
7800
+ }
7801
+ specs.forEach((spec) => this.ensureSpecificationCached(spec));
7802
+ return specs;
7803
+ }
7804
+ async getRelevantTestSpecifications(filters = []) {
7805
+ return this.filterTestsBySource(await this.globTestSpecifications(filters));
7806
+ }
7807
+ async globTestSpecifications(filters = []) {
7808
+ const files = [];
7809
+ const dir = process.cwd();
7810
+ const parsedFilters = filters.map((f) => parseFilter(f));
7811
+ if (!this.vitest.config.includeTaskLocation && parsedFilters.some((f) => f.lineNumber !== undefined)) {
7812
+ throw new IncludeTaskLocationDisabledError();
7813
+ }
7814
+ const testLines = groupFilters(parsedFilters.map((f) => ({
7815
+ ...f,
7816
+ filename: resolve(dir, f.filename)
7817
+ })));
7818
+ const testLocHasMatch = {};
7819
+ await Promise.all(this.vitest.projects.map(async (project) => {
7820
+ const { testFiles, typecheckTestFiles } = await project.globTestFiles(parsedFilters.map((f) => f.filename));
7821
+ testFiles.forEach((file) => {
7822
+ const lines = testLines[file];
7823
+ testLocHasMatch[file] = true;
7824
+ const spec = project.createSpecification(file, lines);
7825
+ this.ensureSpecificationCached(spec);
7826
+ files.push(spec);
7827
+ });
7828
+ typecheckTestFiles.forEach((file) => {
7829
+ const lines = testLines[file];
7830
+ testLocHasMatch[file] = true;
7831
+ const spec = project.createSpecification(file, lines, "typescript");
7832
+ this.ensureSpecificationCached(spec);
7833
+ files.push(spec);
7834
+ });
7835
+ }));
7836
+ Object.entries(testLines).forEach(([filepath, loc]) => {
7837
+ if (loc.length !== 0 && !testLocHasMatch[filepath]) {
7838
+ throw new LocationFilterFileNotFoundError(relative(dir, filepath));
7839
+ }
7840
+ });
7841
+ return files;
7842
+ }
7843
+ clearCache(moduleId) {
7844
+ if (moduleId) {
7845
+ this._cachedSpecs.delete(moduleId);
7846
+ } else {
7847
+ this._cachedSpecs.clear();
7848
+ }
7849
+ }
7850
+ getCachedSpecifications(moduleId) {
7851
+ return this._cachedSpecs.get(moduleId);
7852
+ }
7853
+ ensureSpecificationCached(spec) {
7854
+ const file = spec.moduleId;
7855
+ const specs = this._cachedSpecs.get(file) || [];
7856
+ const index = specs.findIndex((_s) => _s.project === spec.project && _s.pool === spec.pool);
7857
+ if (index === -1) {
7858
+ specs.push(spec);
7859
+ this._cachedSpecs.set(file, specs);
7860
+ } else {
7518
7861
  specs.splice(index, 1, spec);
7519
7862
  }
7520
7863
  return specs;
@@ -7992,721 +8335,435 @@ class StateManager {
7992
8335
  idMap = new Map();
7993
8336
  taskFileMap = new WeakMap();
7994
8337
  errorsSet = new Set();
7995
- processTimeoutCauses = new Set();
7996
- reportedTasksMap = new WeakMap();
7997
- catchError(err, type) {
7998
- if (isAggregateError(err)) {
7999
- return err.errors.forEach((error) => this.catchError(error, type));
8000
- }
8001
- if (err === Object(err)) {
8002
- err.type = type;
8003
- } else {
8004
- err = {
8005
- type,
8006
- message: err
8007
- };
8008
- }
8009
- const _err = err;
8010
- if (_err && typeof _err === "object" && _err.code === "VITEST_PENDING") {
8011
- const task = this.idMap.get(_err.taskId);
8012
- if (task) {
8013
- task.mode = "skip";
8014
- task.result ??= { state: "skip" };
8015
- task.result.state = "skip";
8016
- task.result.note = _err.note;
8017
- }
8018
- return;
8019
- }
8020
- this.errorsSet.add(err);
8021
- }
8022
- clearErrors() {
8023
- this.errorsSet.clear();
8024
- }
8025
- getUnhandledErrors() {
8026
- return Array.from(this.errorsSet.values());
8027
- }
8028
- addProcessTimeoutCause(cause) {
8029
- this.processTimeoutCauses.add(cause);
8030
- }
8031
- getProcessTimeoutCauses() {
8032
- return Array.from(this.processTimeoutCauses.values());
8033
- }
8034
- getPaths() {
8035
- return Array.from(this.pathsSet);
8036
- }
8037
- /**
8038
- * Return files that were running or collected.
8039
- */
8040
- getFiles(keys) {
8041
- if (keys) {
8042
- return keys.map((key) => this.filesMap.get(key)).flat().filter((file) => file && !file.local);
8043
- }
8044
- return Array.from(this.filesMap.values()).flat().filter((file) => !file.local).sort((f1, f2) => {
8045
- if (f1.meta?.typecheck && f2.meta?.typecheck) {
8046
- return 0;
8047
- }
8048
- if (f1.meta?.typecheck) {
8049
- return -1;
8050
- }
8051
- return 1;
8052
- });
8053
- }
8054
- getTestModules(keys) {
8055
- return this.getFiles(keys).map((file) => this.getReportedEntity(file));
8056
- }
8057
- getFilepaths() {
8058
- return Array.from(this.filesMap.keys());
8059
- }
8060
- getFailedFilepaths() {
8061
- return this.getFiles().filter((i) => i.result?.state === "fail").map((i) => i.filepath);
8062
- }
8063
- collectPaths(paths = []) {
8064
- paths.forEach((path) => {
8065
- this.pathsSet.add(path);
8066
- });
8067
- }
8068
- collectFiles(project, files = []) {
8069
- files.forEach((file) => {
8070
- const existing = this.filesMap.get(file.filepath) || [];
8071
- const otherFiles = existing.filter((i) => i.projectName !== file.projectName || i.meta.typecheck !== file.meta.typecheck);
8072
- const currentFile = existing.find((i) => i.projectName === file.projectName);
8073
- if (currentFile) {
8074
- file.logs = currentFile.logs;
8075
- }
8076
- otherFiles.push(file);
8077
- this.filesMap.set(file.filepath, otherFiles);
8078
- this.updateId(file, project);
8079
- });
8080
- }
8081
- clearFiles(project, paths = []) {
8082
- paths.forEach((path) => {
8083
- const files = this.filesMap.get(path);
8084
- const fileTask = createFileTask(path, project.config.root, project.config.name);
8085
- fileTask.local = true;
8086
- TestModule.register(fileTask, project);
8087
- this.idMap.set(fileTask.id, fileTask);
8088
- if (!files) {
8089
- this.filesMap.set(path, [fileTask]);
8090
- return;
8091
- }
8092
- const filtered = files.filter((file) => file.projectName !== project.config.name);
8093
- if (!filtered.length) {
8094
- this.filesMap.set(path, [fileTask]);
8095
- } else {
8096
- this.filesMap.set(path, [...filtered, fileTask]);
8097
- }
8098
- });
8099
- }
8100
- updateId(task, project) {
8101
- if (this.idMap.get(task.id) === task) {
8102
- return;
8103
- }
8104
- if (task.type === "suite" && "filepath" in task) {
8105
- TestModule.register(task, project);
8106
- } else if (task.type === "suite") {
8107
- TestSuite.register(task, project);
8108
- } else {
8109
- TestCase.register(task, project);
8110
- }
8111
- this.idMap.set(task.id, task);
8112
- if (task.type === "suite") {
8113
- task.tasks.forEach((task) => {
8114
- this.updateId(task, project);
8115
- });
8116
- }
8117
- }
8118
- getReportedEntity(task) {
8119
- return this.reportedTasksMap.get(task);
8120
- }
8121
- updateTasks(packs) {
8122
- for (const [id, result, meta] of packs) {
8123
- const task = this.idMap.get(id);
8124
- if (task) {
8125
- task.result = result;
8126
- task.meta = meta;
8127
- if (result?.state === "skip") {
8128
- task.mode = "skip";
8129
- }
8130
- }
8131
- }
8132
- }
8133
- updateUserLog(log) {
8134
- const task = log.taskId && this.idMap.get(log.taskId);
8135
- if (task) {
8136
- if (!task.logs) {
8137
- task.logs = [];
8138
- }
8139
- task.logs.push(log);
8140
- }
8141
- }
8142
- getCountOfFailedTests() {
8143
- return Array.from(this.idMap.values()).filter((t) => t.result?.state === "fail").length;
8144
- }
8145
- cancelFiles(files, project) {
8146
- this.collectFiles(project, files.map((filepath) => createFileTask(filepath, project.config.root, project.config.name)));
8147
- }
8148
- }
8149
-
8150
- class TestRun {
8151
- constructor(vitest) {
8152
- this.vitest = vitest;
8153
- }
8154
- async start(specifications) {
8155
- const filepaths = specifications.map((spec) => spec.moduleId);
8156
- this.vitest.state.collectPaths(filepaths);
8157
- await this.vitest.report("onPathsCollected", Array.from(new Set(filepaths)));
8158
- await this.vitest.report("onSpecsCollected", specifications.map((spec) => spec.toJSON()));
8159
- await this.vitest.report("onTestRunStart", [...specifications]);
8160
- }
8161
- async enqueued(project, file) {
8162
- this.vitest.state.collectFiles(project, [file]);
8163
- const testModule = this.vitest.state.getReportedEntity(file);
8164
- await this.vitest.report("onTestModuleQueued", testModule);
8165
- }
8166
- async collected(project, files) {
8167
- this.vitest.state.collectFiles(project, files);
8168
- await Promise.all([this.vitest.report("onCollected", files), ...files.map((file) => {
8169
- const testModule = this.vitest.state.getReportedEntity(file);
8170
- return this.vitest.report("onTestModuleCollected", testModule);
8171
- })]);
8172
- }
8173
- async log(log) {
8174
- this.vitest.state.updateUserLog(log);
8175
- await this.vitest.report("onUserConsoleLog", log);
8176
- }
8177
- async updated(update, events) {
8178
- this.vitest.state.updateTasks(update);
8179
- await this.vitest.report("onTaskUpdate", update);
8180
- for (const [id, event] of events) {
8181
- await this.reportEvent(id, event).catch((error) => {
8182
- this.vitest.state.catchError(serializeError(error), "Unhandled Reporter Error");
8183
- });
8184
- }
8185
- }
8186
- async end(specifications, errors, coverage) {
8187
- const modules = specifications.map((spec) => spec.testModule).filter((s) => s != null);
8188
- const files = modules.map((m) => m.task);
8189
- const state = this.vitest.isCancelling ? "interrupted" : process.exitCode ? "failed" : "passed";
8190
- try {
8191
- await Promise.all([this.vitest.report("onTestRunEnd", modules, [...errors], state), this.vitest.report("onFinished", files, errors, coverage)]);
8192
- } finally {
8193
- if (coverage) {
8194
- await this.vitest.report("onCoverage", coverage);
8195
- }
8196
- }
8197
- }
8198
- async reportEvent(id, event) {
8199
- const task = this.vitest.state.idMap.get(id);
8200
- const entity = task && this.vitest.state.getReportedEntity(task);
8201
- assert$1(task && entity, `Entity must be found for task ${task?.name || id}`);
8202
- if (event === "suite-prepare" && entity.type === "suite") {
8203
- return await this.vitest.report("onTestSuiteReady", entity);
8204
- }
8205
- if (event === "suite-prepare" && entity.type === "module") {
8206
- return await this.vitest.report("onTestModuleStart", entity);
8207
- }
8208
- if (event === "suite-finished") {
8209
- assert$1(entity.type === "suite" || entity.type === "module", "Entity type must be suite or module");
8210
- if (entity.state() === "skipped") {
8211
- await this.reportChildren(entity.children);
8212
- }
8213
- if (entity.type === "module") {
8214
- await this.vitest.report("onTestModuleEnd", entity);
8215
- } else {
8216
- await this.vitest.report("onTestSuiteResult", entity);
8217
- }
8218
- return;
8219
- }
8220
- if (event === "test-prepare" && entity.type === "test") {
8221
- return await this.vitest.report("onTestCaseReady", entity);
8222
- }
8223
- if (event === "test-finished" && entity.type === "test") {
8224
- return await this.vitest.report("onTestCaseResult", entity);
8225
- }
8226
- if (event.startsWith("before-hook") || event.startsWith("after-hook")) {
8227
- const isBefore = event.startsWith("before-hook");
8228
- const hook = entity.type === "test" ? {
8229
- name: isBefore ? "beforeEach" : "afterEach",
8230
- entity
8231
- } : {
8232
- name: isBefore ? "beforeAll" : "afterAll",
8233
- entity
8338
+ processTimeoutCauses = new Set();
8339
+ reportedTasksMap = new WeakMap();
8340
+ blobs;
8341
+ catchError(err, type) {
8342
+ if (isAggregateError(err)) {
8343
+ return err.errors.forEach((error) => this.catchError(error, type));
8344
+ }
8345
+ if (err === Object(err)) {
8346
+ err.type = type;
8347
+ } else {
8348
+ err = {
8349
+ type,
8350
+ message: err
8234
8351
  };
8235
- if (event.endsWith("-start")) {
8236
- await this.vitest.report("onHookStart", hook);
8237
- } else {
8238
- await this.vitest.report("onHookEnd", hook);
8239
- }
8240
8352
  }
8241
- }
8242
- async reportChildren(children) {
8243
- for (const child of children) {
8244
- if (child.type === "test") {
8245
- await this.vitest.report("onTestCaseReady", child);
8246
- await this.vitest.report("onTestCaseResult", child);
8247
- } else {
8248
- await this.vitest.report("onTestSuiteReady", child);
8249
- await this.reportChildren(child.children);
8250
- await this.vitest.report("onTestSuiteResult", child);
8353
+ const _err = err;
8354
+ if (_err && typeof _err === "object" && _err.code === "VITEST_PENDING") {
8355
+ const task = this.idMap.get(_err.taskId);
8356
+ if (task) {
8357
+ task.mode = "skip";
8358
+ task.result ??= { state: "skip" };
8359
+ task.result.state = "skip";
8360
+ task.result.note = _err.note;
8251
8361
  }
8362
+ return;
8252
8363
  }
8364
+ this.errorsSet.add(err);
8253
8365
  }
8254
- }
8255
-
8256
- class VitestWatcher {
8257
- /**
8258
- * Modules that will be invalidated on the next run.
8259
- */
8260
- invalidates = new Set();
8261
- /**
8262
- * Test files that have changed and need to be rerun.
8263
- */
8264
- changedTests = new Set();
8265
- _onRerun = [];
8266
- constructor(vitest) {
8267
- this.vitest = vitest;
8366
+ clearErrors() {
8367
+ this.errorsSet.clear();
8268
8368
  }
8269
- /**
8270
- * Register a handler that will be called when test files need to be rerun.
8271
- * The callback can receive several files in case the changed file is imported by several test files.
8272
- * Several invocations of this method will add multiple handlers.
8273
- * @internal
8274
- */
8275
- onWatcherRerun(cb) {
8276
- this._onRerun.push(cb);
8277
- return this;
8369
+ getUnhandledErrors() {
8370
+ return Array.from(this.errorsSet.values());
8278
8371
  }
8279
- unregisterWatcher = noop;
8280
- registerWatcher() {
8281
- const watcher = this.vitest.vite.watcher;
8282
- if (this.vitest.config.forceRerunTriggers.length) {
8283
- watcher.add(this.vitest.config.forceRerunTriggers);
8284
- }
8285
- watcher.on("change", this.onChange);
8286
- watcher.on("unlink", this.onUnlink);
8287
- watcher.on("add", this.onAdd);
8288
- this.unregisterWatcher = () => {
8289
- watcher.off("change", this.onChange);
8290
- watcher.off("unlink", this.onUnlink);
8291
- watcher.off("add", this.onAdd);
8292
- this.unregisterWatcher = noop;
8293
- };
8294
- return this;
8372
+ addProcessTimeoutCause(cause) {
8373
+ this.processTimeoutCauses.add(cause);
8295
8374
  }
8296
- scheduleRerun(file) {
8297
- this._onRerun.forEach((cb) => cb(file));
8375
+ getProcessTimeoutCauses() {
8376
+ return Array.from(this.processTimeoutCauses.values());
8377
+ }
8378
+ getPaths() {
8379
+ return Array.from(this.pathsSet);
8298
8380
  }
8299
- onChange = (id) => {
8300
- id = slash(id);
8301
- this.vitest.logger.clearHighlightCache(id);
8302
- this.vitest.invalidateFile(id);
8303
- const needsRerun = this.handleFileChanged(id);
8304
- if (needsRerun) {
8305
- this.scheduleRerun(id);
8306
- }
8307
- };
8308
- onUnlink = (id) => {
8309
- id = slash(id);
8310
- this.vitest.logger.clearHighlightCache(id);
8311
- this.invalidates.add(id);
8312
- if (this.vitest.state.filesMap.has(id)) {
8313
- this.vitest.projects.forEach((project) => project._removeCachedTestFile(id));
8314
- this.vitest.state.filesMap.delete(id);
8315
- this.vitest.cache.results.removeFromCache(id);
8316
- this.vitest.cache.stats.removeStats(id);
8317
- this.changedTests.delete(id);
8318
- this.vitest.report("onTestRemoved", id);
8319
- }
8320
- };
8321
- onAdd = (id) => {
8322
- id = slash(id);
8323
- this.vitest.invalidateFile(id);
8324
- let fileContent;
8325
- const matchingProjects = [];
8326
- this.vitest.projects.forEach((project) => {
8327
- if (project.matchesTestGlob(id, () => fileContent ??= readFileSync(id, "utf-8"))) {
8328
- matchingProjects.push(project);
8329
- }
8330
- });
8331
- if (matchingProjects.length > 0) {
8332
- this.changedTests.add(id);
8333
- this.scheduleRerun(id);
8334
- } else {
8335
- const needsRerun = this.handleFileChanged(id);
8336
- if (needsRerun) {
8337
- this.scheduleRerun(id);
8338
- }
8339
- }
8340
- };
8341
8381
  /**
8342
- * @returns A value indicating whether rerun is needed (changedTests was mutated)
8382
+ * Return files that were running or collected.
8343
8383
  */
8344
- handleFileChanged(filepath) {
8345
- if (this.changedTests.has(filepath) || this.invalidates.has(filepath)) {
8346
- return false;
8347
- }
8348
- if (mm.isMatch(filepath, this.vitest.config.forceRerunTriggers)) {
8349
- this.vitest.state.getFilepaths().forEach((file) => this.changedTests.add(file));
8350
- return true;
8351
- }
8352
- const projects = this.vitest.projects.filter((project) => {
8353
- const moduleGraph = project.browser?.vite.moduleGraph || project.vite.moduleGraph;
8354
- return moduleGraph.getModulesByFile(filepath)?.size;
8355
- });
8356
- if (!projects.length) {
8357
- if (this.vitest.state.filesMap.has(filepath) || this.vitest.projects.some((project) => project._isCachedTestFile(filepath))) {
8358
- this.changedTests.add(filepath);
8359
- return true;
8360
- }
8361
- return false;
8384
+ getFiles(keys) {
8385
+ if (keys) {
8386
+ return keys.map((key) => this.filesMap.get(key)).flat().filter((file) => file && !file.local);
8362
8387
  }
8363
- const files = [];
8364
- for (const project of projects) {
8365
- const mods = project.browser?.vite.moduleGraph.getModulesByFile(filepath) || project.vite.moduleGraph.getModulesByFile(filepath);
8366
- if (!mods || !mods.size) {
8367
- continue;
8368
- }
8369
- this.invalidates.add(filepath);
8370
- if (this.vitest.state.filesMap.has(filepath) || project._isCachedTestFile(filepath)) {
8371
- this.changedTests.add(filepath);
8372
- files.push(filepath);
8373
- continue;
8388
+ return Array.from(this.filesMap.values()).flat().filter((file) => !file.local).sort((f1, f2) => {
8389
+ if (f1.meta?.typecheck && f2.meta?.typecheck) {
8390
+ return 0;
8374
8391
  }
8375
- let rerun = false;
8376
- for (const mod of mods) {
8377
- mod.importers.forEach((i) => {
8378
- if (!i.file) {
8379
- return;
8380
- }
8381
- const needsRerun = this.handleFileChanged(i.file);
8382
- if (needsRerun) {
8383
- rerun = true;
8384
- }
8385
- });
8392
+ if (f1.meta?.typecheck) {
8393
+ return -1;
8386
8394
  }
8387
- if (rerun) {
8388
- files.push(filepath);
8395
+ return 1;
8396
+ });
8397
+ }
8398
+ getTestModules(keys) {
8399
+ return this.getFiles(keys).map((file) => this.getReportedEntity(file));
8400
+ }
8401
+ getFilepaths() {
8402
+ return Array.from(this.filesMap.keys());
8403
+ }
8404
+ getFailedFilepaths() {
8405
+ return this.getFiles().filter((i) => i.result?.state === "fail").map((i) => i.filepath);
8406
+ }
8407
+ collectPaths(paths = []) {
8408
+ paths.forEach((path) => {
8409
+ this.pathsSet.add(path);
8410
+ });
8411
+ }
8412
+ collectFiles(project, files = []) {
8413
+ files.forEach((file) => {
8414
+ const existing = this.filesMap.get(file.filepath) || [];
8415
+ const otherFiles = existing.filter((i) => i.projectName !== file.projectName || i.meta.typecheck !== file.meta.typecheck);
8416
+ const currentFile = existing.find((i) => i.projectName === file.projectName);
8417
+ if (currentFile) {
8418
+ file.logs = currentFile.logs;
8389
8419
  }
8390
- }
8391
- return !!files.length;
8420
+ otherFiles.push(file);
8421
+ this.filesMap.set(file.filepath, otherFiles);
8422
+ this.updateId(file, project);
8423
+ });
8392
8424
  }
8393
- }
8394
-
8395
- async function resolveWorkspace(vitest, cliOptions, workspaceConfigPath, workspaceDefinition, names) {
8396
- const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, workspaceDefinition);
8397
- const overridesOptions = [
8398
- "logHeapUsage",
8399
- "allowOnly",
8400
- "sequence",
8401
- "testTimeout",
8402
- "pool",
8403
- "update",
8404
- "globals",
8405
- "expandSnapshotDiff",
8406
- "disableConsoleIntercept",
8407
- "retry",
8408
- "testNamePattern",
8409
- "passWithNoTests",
8410
- "bail",
8411
- "isolate",
8412
- "printConsoleTrace",
8413
- "inspect",
8414
- "inspectBrk",
8415
- "fileParallelism"
8416
- ];
8417
- const cliOverrides = overridesOptions.reduce((acc, name) => {
8418
- if (name in cliOptions) {
8419
- acc[name] = cliOptions[name];
8420
- }
8421
- return acc;
8422
- }, {});
8423
- const projectPromises = [];
8424
- const fileProjects = [...configFiles, ...nonConfigDirectories];
8425
- const concurrent = limitConcurrency(nodeos__default.availableParallelism?.() || nodeos__default.cpus().length || 5);
8426
- projectConfigs.forEach((options, index) => {
8427
- const configRoot = workspaceConfigPath ? dirname(workspaceConfigPath) : vitest.config.root;
8428
- const configFile = typeof options.extends === "string" ? resolve(configRoot, options.extends) : options.extends === true ? vitest.vite.config.configFile || false : false;
8429
- const root = options.root ? resolve(configRoot, options.root) : vitest.config.root;
8430
- projectPromises.push(concurrent(() => initializeProject(index, vitest, {
8431
- ...options,
8432
- root,
8433
- configFile,
8434
- test: {
8435
- ...options.test,
8436
- ...cliOverrides
8425
+ clearFiles(project, paths = []) {
8426
+ paths.forEach((path) => {
8427
+ const files = this.filesMap.get(path);
8428
+ const fileTask = createFileTask(path, project.config.root, project.config.name);
8429
+ fileTask.local = true;
8430
+ TestModule.register(fileTask, project);
8431
+ this.idMap.set(fileTask.id, fileTask);
8432
+ if (!files) {
8433
+ this.filesMap.set(path, [fileTask]);
8434
+ return;
8437
8435
  }
8438
- })));
8439
- });
8440
- for (const path of fileProjects) {
8441
- if (vitest.vite.config.configFile === path) {
8442
- const project = getDefaultTestProject(vitest);
8443
- if (project) {
8444
- projectPromises.push(Promise.resolve(project));
8436
+ const filtered = files.filter((file) => file.projectName !== project.config.name);
8437
+ if (!filtered.length) {
8438
+ this.filesMap.set(path, [fileTask]);
8439
+ } else {
8440
+ this.filesMap.set(path, [...filtered, fileTask]);
8445
8441
  }
8446
- continue;
8442
+ });
8443
+ }
8444
+ updateId(task, project) {
8445
+ if (this.idMap.get(task.id) === task) {
8446
+ return;
8447
+ }
8448
+ if (task.type === "suite" && "filepath" in task) {
8449
+ TestModule.register(task, project);
8450
+ } else if (task.type === "suite") {
8451
+ TestSuite.register(task, project);
8452
+ } else {
8453
+ TestCase.register(task, project);
8454
+ }
8455
+ this.idMap.set(task.id, task);
8456
+ if (task.type === "suite") {
8457
+ task.tasks.forEach((task) => {
8458
+ this.updateId(task, project);
8459
+ });
8447
8460
  }
8448
- const configFile = path.endsWith("/") ? false : path;
8449
- const root = path.endsWith("/") ? path : dirname(path);
8450
- projectPromises.push(concurrent(() => initializeProject(path, vitest, {
8451
- root,
8452
- configFile,
8453
- test: cliOverrides
8454
- })));
8455
8461
  }
8456
- if (!projectPromises.length) {
8457
- throw new Error([
8458
- "No projects were found. Make sure your configuration is correct. ",
8459
- vitest.config.project.length ? `The filter matched no projects: ${vitest.config.project.join(", ")}. ` : "",
8460
- `The workspace: ${JSON.stringify(workspaceDefinition, null, 4)}.`
8461
- ].join(""));
8462
+ getReportedEntity(task) {
8463
+ return this.reportedTasksMap.get(task);
8462
8464
  }
8463
- const resolvedProjectsPromises = await Promise.allSettled(projectPromises);
8464
- const errors = [];
8465
- const resolvedProjects = [];
8466
- for (const result of resolvedProjectsPromises) {
8467
- if (result.status === "rejected") {
8468
- if (result.reason instanceof VitestFilteredOutProjectError) {
8469
- continue;
8465
+ updateTasks(packs) {
8466
+ for (const [id, result, meta] of packs) {
8467
+ const task = this.idMap.get(id);
8468
+ if (task) {
8469
+ task.result = result;
8470
+ task.meta = meta;
8471
+ if (result?.state === "skip") {
8472
+ task.mode = "skip";
8473
+ }
8470
8474
  }
8471
- errors.push(result.reason);
8472
- } else {
8473
- resolvedProjects.push(result.value);
8474
8475
  }
8475
8476
  }
8476
- if (errors.length) {
8477
- throw new AggregateError(errors, "Failed to initialize projects. There were errors during workspace setup. See below for more details.");
8478
- }
8479
- for (const project of resolvedProjects) {
8480
- const name = project.name;
8481
- if (names.has(name)) {
8482
- const duplicate = resolvedProjects.find((p) => p.name === name && p !== project);
8483
- const filesError = fileProjects.length ? [
8484
- "\n\nYour config matched these files:\n",
8485
- fileProjects.map((p) => ` - ${relative(vitest.config.root, p)}`).join("\n"),
8486
- "\n\n"
8487
- ].join("") : [" "];
8488
- throw new Error([
8489
- `Project name "${name}"`,
8490
- project.vite.config.configFile ? ` from "${relative(vitest.config.root, project.vite.config.configFile)}"` : "",
8491
- " is not unique.",
8492
- duplicate?.vite.config.configFile ? ` The project is already defined by "${relative(vitest.config.root, duplicate.vite.config.configFile)}".` : "",
8493
- filesError,
8494
- "All projects in a workspace should have unique names. Make sure your configuration is correct."
8495
- ].join(""));
8477
+ updateUserLog(log) {
8478
+ const task = log.taskId && this.idMap.get(log.taskId);
8479
+ if (task) {
8480
+ if (!task.logs) {
8481
+ task.logs = [];
8482
+ }
8483
+ task.logs.push(log);
8496
8484
  }
8497
- names.add(name);
8498
8485
  }
8499
- return resolveBrowserWorkspace(vitest, names, resolvedProjects);
8486
+ getCountOfFailedTests() {
8487
+ return Array.from(this.idMap.values()).filter((t) => t.result?.state === "fail").length;
8488
+ }
8489
+ cancelFiles(files, project) {
8490
+ this.collectFiles(project, files.map((filepath) => createFileTask(filepath, project.config.root, project.config.name)));
8491
+ }
8500
8492
  }
8501
- async function resolveBrowserWorkspace(vitest, names, resolvedProjects) {
8502
- const removeProjects = new Set();
8503
- resolvedProjects.forEach((project) => {
8504
- if (!project.config.browser.enabled) {
8505
- return;
8506
- }
8507
- const instances = project.config.browser.instances || [];
8508
- if (instances.length === 0) {
8509
- const browser = project.config.browser.name;
8510
- instances.push({
8511
- browser,
8512
- name: project.name ? `${project.name} (${browser})` : browser
8493
+
8494
+ class TestRun {
8495
+ constructor(vitest) {
8496
+ this.vitest = vitest;
8497
+ }
8498
+ async start(specifications) {
8499
+ const filepaths = specifications.map((spec) => spec.moduleId);
8500
+ this.vitest.state.collectPaths(filepaths);
8501
+ await this.vitest.report("onPathsCollected", Array.from(new Set(filepaths)));
8502
+ await this.vitest.report("onSpecsCollected", specifications.map((spec) => spec.toJSON()));
8503
+ await this.vitest.report("onTestRunStart", [...specifications]);
8504
+ }
8505
+ async enqueued(project, file) {
8506
+ this.vitest.state.collectFiles(project, [file]);
8507
+ const testModule = this.vitest.state.getReportedEntity(file);
8508
+ await this.vitest.report("onTestModuleQueued", testModule);
8509
+ }
8510
+ async collected(project, files) {
8511
+ this.vitest.state.collectFiles(project, files);
8512
+ await Promise.all([this.vitest.report("onCollected", files), ...files.map((file) => {
8513
+ const testModule = this.vitest.state.getReportedEntity(file);
8514
+ return this.vitest.report("onTestModuleCollected", testModule);
8515
+ })]);
8516
+ }
8517
+ async log(log) {
8518
+ this.vitest.state.updateUserLog(log);
8519
+ await this.vitest.report("onUserConsoleLog", log);
8520
+ }
8521
+ async updated(update, events) {
8522
+ this.vitest.state.updateTasks(update);
8523
+ await this.vitest.report("onTaskUpdate", update);
8524
+ for (const [id, event] of events) {
8525
+ await this.reportEvent(id, event).catch((error) => {
8526
+ this.vitest.state.catchError(serializeError(error), "Unhandled Reporter Error");
8513
8527
  });
8514
- console.warn(withLabel("yellow", "Vitest", [
8515
- `No browser "instances" were defined`,
8516
- project.name ? ` for the "${project.name}" project. ` : ". ",
8517
- `Running tests in "${project.config.browser.name}" browser. `,
8518
- "The \"browser.name\" field is deprecated since Vitest 3. ",
8519
- "Read more: https://vitest.dev/guide/browser/config#browser-instances"
8520
- ].filter(Boolean).join("")));
8521
8528
  }
8522
- const originalName = project.config.name;
8523
- const filteredInstances = vitest.matchesProjectFilter(originalName) ? instances : instances.filter((instance) => {
8524
- const newName = instance.name;
8525
- return vitest.matchesProjectFilter(newName);
8526
- });
8527
- if (!filteredInstances.length) {
8528
- removeProjects.add(project);
8529
- return;
8529
+ }
8530
+ async end(specifications, errors, coverage) {
8531
+ const modules = specifications.map((spec) => spec.testModule).filter((s) => s != null);
8532
+ const files = modules.map((m) => m.task);
8533
+ const state = this.vitest.isCancelling ? "interrupted" : process.exitCode ? "failed" : "passed";
8534
+ try {
8535
+ await Promise.all([this.vitest.report("onTestRunEnd", modules, [...errors], state), this.vitest.report("onFinished", files, errors, coverage)]);
8536
+ } finally {
8537
+ if (coverage) {
8538
+ await this.vitest.report("onCoverage", coverage);
8539
+ }
8530
8540
  }
8531
- if (project.config.browser.providerOptions) {
8532
- 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.`));
8541
+ }
8542
+ async reportEvent(id, event) {
8543
+ const task = this.vitest.state.idMap.get(id);
8544
+ const entity = task && this.vitest.state.getReportedEntity(task);
8545
+ assert$1(task && entity, `Entity must be found for task ${task?.name || id}`);
8546
+ if (event === "suite-prepare" && entity.type === "suite") {
8547
+ return await this.vitest.report("onTestSuiteReady", entity);
8533
8548
  }
8534
- filteredInstances.forEach((config, index) => {
8535
- const browser = config.browser;
8536
- if (!browser) {
8537
- const nth = index + 1;
8538
- const ending = nth === 2 ? "nd" : nth === 3 ? "rd" : "th";
8539
- 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.`);
8540
- }
8541
- const name = config.name;
8542
- if (name == null) {
8543
- 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
+ if (event === "suite-prepare" && entity.type === "module") {
8550
+ return await this.vitest.report("onTestModuleStart", entity);
8551
+ }
8552
+ if (event === "suite-finished") {
8553
+ assert$1(entity.type === "suite" || entity.type === "module", "Entity type must be suite or module");
8554
+ if (entity.state() === "skipped") {
8555
+ await this.reportChildren(entity.children);
8544
8556
  }
8545
- if (names.has(name)) {
8546
- throw new Error([
8547
- `Cannot define a nested project for a ${browser} browser. The project name "${name}" was already defined. `,
8548
- "If you have multiple instances for the same browser, make sure to define a custom \"name\". ",
8549
- "All projects in a workspace should have unique names. Make sure your configuration is correct."
8550
- ].join(""));
8557
+ if (entity.type === "module") {
8558
+ await this.vitest.report("onTestModuleEnd", entity);
8559
+ } else {
8560
+ await this.vitest.report("onTestSuiteResult", entity);
8551
8561
  }
8552
- names.add(name);
8553
- const clonedConfig = cloneConfig(project, config);
8554
- clonedConfig.name = name;
8555
- const clone = TestProject._cloneBrowserProject(project, clonedConfig);
8556
- resolvedProjects.push(clone);
8557
- });
8558
- removeProjects.add(project);
8559
- });
8560
- resolvedProjects = resolvedProjects.filter((project) => !removeProjects.has(project));
8561
- const headedBrowserProjects = resolvedProjects.filter((project) => {
8562
- return project.config.browser.enabled && !project.config.browser.headless;
8563
- });
8564
- if (headedBrowserProjects.length > 1) {
8565
- 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("");
8566
- if (!isTTY) {
8567
- throw new Error(`${message} Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option.`);
8562
+ return;
8568
8563
  }
8569
- const prompts = await import('./index.DBIGubLC.js').then(function (n) { return n.i; });
8570
- const { projectName } = await prompts.default({
8571
- type: "select",
8572
- name: "projectName",
8573
- choices: headedBrowserProjects.map((project) => ({
8574
- title: project.name,
8575
- value: project.name
8576
- })),
8577
- 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.`
8578
- });
8579
- if (!projectName) {
8580
- throw new Error("The test run was aborted.");
8564
+ if (event === "test-prepare" && entity.type === "test") {
8565
+ return await this.vitest.report("onTestCaseReady", entity);
8581
8566
  }
8582
- return resolvedProjects.filter((project) => project.name === projectName);
8583
- }
8584
- return resolvedProjects;
8585
- }
8586
- function cloneConfig(project, { browser,...config }) {
8587
- const { locators, viewport, testerHtmlPath, headless, screenshotDirectory, screenshotFailures, browser: _browser, name,...overrideConfig } = config;
8588
- const currentConfig = project.config.browser;
8589
- return mergeConfig({
8590
- ...deepClone(project.config),
8591
- browser: {
8592
- ...project.config.browser,
8593
- locators: locators ? { testIdAttribute: locators.testIdAttribute ?? currentConfig.locators.testIdAttribute } : project.config.browser.locators,
8594
- viewport: viewport ?? currentConfig.viewport,
8595
- testerHtmlPath: testerHtmlPath ?? currentConfig.testerHtmlPath,
8596
- screenshotDirectory: screenshotDirectory ?? currentConfig.screenshotDirectory,
8597
- screenshotFailures: screenshotFailures ?? currentConfig.screenshotFailures,
8598
- headless: project.vitest._options?.browser?.headless ?? headless ?? currentConfig.headless,
8599
- name: browser,
8600
- providerOptions: config,
8601
- instances: undefined
8567
+ if (event === "test-finished" && entity.type === "test") {
8568
+ return await this.vitest.report("onTestCaseResult", entity);
8602
8569
  }
8603
- }, overrideConfig);
8604
- }
8605
- async function resolveTestProjectConfigs(vitest, workspaceConfigPath, workspaceDefinition) {
8606
- const projectsOptions = [];
8607
- const workspaceConfigFiles = [];
8608
- const workspaceGlobMatches = [];
8609
- const nonConfigProjectDirectories = [];
8610
- for (const definition of workspaceDefinition) {
8611
- if (typeof definition === "string") {
8612
- const stringOption = definition.replace("<rootDir>", vitest.config.root);
8613
- if (!isDynamicPattern(stringOption)) {
8614
- const file = resolve(vitest.config.root, stringOption);
8615
- if (!existsSync(file)) {
8616
- const relativeWorkSpaceConfigPath = workspaceConfigPath ? relative(vitest.config.root, workspaceConfigPath) : undefined;
8617
- const note = workspaceConfigPath ? `Workspace config file "${relativeWorkSpaceConfigPath}"` : "Inline workspace";
8618
- throw new Error(`${note} references a non-existing file or a directory: ${file}`);
8619
- }
8620
- const stats = await promises.stat(file);
8621
- if (stats.isFile()) {
8622
- workspaceConfigFiles.push(file);
8623
- } else if (stats.isDirectory()) {
8624
- const configFile = await resolveDirectoryConfig(file);
8625
- if (configFile) {
8626
- workspaceConfigFiles.push(configFile);
8627
- } else {
8628
- const directory = file[file.length - 1] === "/" ? file : `${file}/`;
8629
- nonConfigProjectDirectories.push(directory);
8630
- }
8631
- } else {
8632
- throw new TypeError(`Unexpected file type: ${file}`);
8633
- }
8570
+ if (event.startsWith("before-hook") || event.startsWith("after-hook")) {
8571
+ const isBefore = event.startsWith("before-hook");
8572
+ const hook = entity.type === "test" ? {
8573
+ name: isBefore ? "beforeEach" : "afterEach",
8574
+ entity
8575
+ } : {
8576
+ name: isBefore ? "beforeAll" : "afterAll",
8577
+ entity
8578
+ };
8579
+ if (event.endsWith("-start")) {
8580
+ await this.vitest.report("onHookStart", hook);
8634
8581
  } else {
8635
- workspaceGlobMatches.push(stringOption);
8582
+ await this.vitest.report("onHookEnd", hook);
8636
8583
  }
8637
- } else if (typeof definition === "function") {
8638
- projectsOptions.push(await definition({
8639
- command: vitest.vite.config.command,
8640
- mode: vitest.vite.config.mode,
8641
- isPreview: false,
8642
- isSsrBuild: false
8643
- }));
8644
- } else {
8645
- projectsOptions.push(await definition);
8646
8584
  }
8647
8585
  }
8648
- if (workspaceGlobMatches.length) {
8649
- const globOptions = {
8650
- absolute: true,
8651
- dot: true,
8652
- onlyFiles: false,
8653
- cwd: vitest.config.root,
8654
- expandDirectories: false,
8655
- ignore: [
8656
- "**/node_modules/**",
8657
- "**/*.timestamp-*",
8658
- "**/.DS_Store"
8659
- ]
8660
- };
8661
- const workspacesFs = await glob(workspaceGlobMatches, globOptions);
8662
- await Promise.all(workspacesFs.map(async (path) => {
8663
- if (path.endsWith("/")) {
8664
- const configFile = await resolveDirectoryConfig(path);
8665
- if (configFile) {
8666
- workspaceConfigFiles.push(configFile);
8667
- } else {
8668
- nonConfigProjectDirectories.push(path);
8669
- }
8586
+ async reportChildren(children) {
8587
+ for (const child of children) {
8588
+ if (child.type === "test") {
8589
+ await this.vitest.report("onTestCaseReady", child);
8590
+ await this.vitest.report("onTestCaseResult", child);
8670
8591
  } else {
8671
- workspaceConfigFiles.push(path);
8592
+ await this.vitest.report("onTestSuiteReady", child);
8593
+ await this.reportChildren(child.children);
8594
+ await this.vitest.report("onTestSuiteResult", child);
8672
8595
  }
8673
- }));
8596
+ }
8674
8597
  }
8675
- const projectConfigFiles = Array.from(new Set(workspaceConfigFiles));
8676
- return {
8677
- projectConfigs: projectsOptions,
8678
- nonConfigDirectories: nonConfigProjectDirectories,
8679
- configFiles: projectConfigFiles
8680
- };
8681
8598
  }
8682
- async function resolveDirectoryConfig(directory) {
8683
- const files = new Set(await promises.readdir(directory));
8684
- const configFile = configFiles.find((file) => files.has(file));
8685
- if (configFile) {
8686
- return resolve(directory, configFile);
8599
+
8600
+ class VitestWatcher {
8601
+ /**
8602
+ * Modules that will be invalidated on the next run.
8603
+ */
8604
+ invalidates = new Set();
8605
+ /**
8606
+ * Test files that have changed and need to be rerun.
8607
+ */
8608
+ changedTests = new Set();
8609
+ _onRerun = [];
8610
+ constructor(vitest) {
8611
+ this.vitest = vitest;
8687
8612
  }
8688
- return null;
8689
- }
8690
- function getDefaultTestProject(vitest) {
8691
- const filter = vitest.config.project;
8692
- const project = vitest._ensureRootProject();
8693
- if (!filter.length) {
8694
- return project;
8613
+ /**
8614
+ * Register a handler that will be called when test files need to be rerun.
8615
+ * The callback can receive several files in case the changed file is imported by several test files.
8616
+ * Several invocations of this method will add multiple handlers.
8617
+ * @internal
8618
+ */
8619
+ onWatcherRerun(cb) {
8620
+ this._onRerun.push(cb);
8621
+ return this;
8695
8622
  }
8696
- const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
8697
- if (hasProjects) {
8698
- return project;
8623
+ unregisterWatcher = noop;
8624
+ registerWatcher() {
8625
+ const watcher = this.vitest.vite.watcher;
8626
+ if (this.vitest.config.forceRerunTriggers.length) {
8627
+ watcher.add(this.vitest.config.forceRerunTriggers);
8628
+ }
8629
+ watcher.on("change", this.onChange);
8630
+ watcher.on("unlink", this.onUnlink);
8631
+ watcher.on("add", this.onAdd);
8632
+ this.unregisterWatcher = () => {
8633
+ watcher.off("change", this.onChange);
8634
+ watcher.off("unlink", this.onUnlink);
8635
+ watcher.off("add", this.onAdd);
8636
+ this.unregisterWatcher = noop;
8637
+ };
8638
+ return this;
8699
8639
  }
8700
- return null;
8701
- }
8702
- function getPotentialProjectNames(project) {
8703
- const names = [project.name];
8704
- if (project.config.browser.instances) {
8705
- names.push(...project.config.browser.instances.map((i) => i.name));
8706
- } else if (project.config.browser.name) {
8707
- names.push(project.config.browser.name);
8640
+ scheduleRerun(file) {
8641
+ this._onRerun.forEach((cb) => cb(file));
8642
+ }
8643
+ getTestFilesFromWatcherTrigger(id) {
8644
+ if (!this.vitest.config.watchTriggerPatterns) {
8645
+ return false;
8646
+ }
8647
+ let triggered = false;
8648
+ this.vitest.config.watchTriggerPatterns.forEach((definition) => {
8649
+ const exec = definition.pattern.exec(id);
8650
+ if (exec) {
8651
+ const files = definition.testsToRun(id, exec);
8652
+ if (Array.isArray(files)) {
8653
+ triggered = true;
8654
+ files.forEach((file) => this.changedTests.add(resolve(this.vitest.config.root, file)));
8655
+ } else if (typeof files === "string") {
8656
+ triggered = true;
8657
+ this.changedTests.add(resolve(this.vitest.config.root, files));
8658
+ }
8659
+ }
8660
+ });
8661
+ return triggered;
8662
+ }
8663
+ onChange = (id) => {
8664
+ id = slash(id);
8665
+ this.vitest.logger.clearHighlightCache(id);
8666
+ this.vitest.invalidateFile(id);
8667
+ const testFiles = this.getTestFilesFromWatcherTrigger(id);
8668
+ if (testFiles) {
8669
+ this.scheduleRerun(id);
8670
+ } else {
8671
+ const needsRerun = this.handleFileChanged(id);
8672
+ if (needsRerun) {
8673
+ this.scheduleRerun(id);
8674
+ }
8675
+ }
8676
+ };
8677
+ onUnlink = (id) => {
8678
+ id = slash(id);
8679
+ this.vitest.logger.clearHighlightCache(id);
8680
+ this.invalidates.add(id);
8681
+ if (this.vitest.state.filesMap.has(id)) {
8682
+ this.vitest.projects.forEach((project) => project._removeCachedTestFile(id));
8683
+ this.vitest.state.filesMap.delete(id);
8684
+ this.vitest.cache.results.removeFromCache(id);
8685
+ this.vitest.cache.stats.removeStats(id);
8686
+ this.changedTests.delete(id);
8687
+ this.vitest.report("onTestRemoved", id);
8688
+ }
8689
+ };
8690
+ onAdd = (id) => {
8691
+ id = slash(id);
8692
+ this.vitest.invalidateFile(id);
8693
+ const testFiles = this.getTestFilesFromWatcherTrigger(id);
8694
+ if (testFiles) {
8695
+ this.scheduleRerun(id);
8696
+ return;
8697
+ }
8698
+ let fileContent;
8699
+ const matchingProjects = [];
8700
+ this.vitest.projects.forEach((project) => {
8701
+ if (project.matchesTestGlob(id, () => fileContent ??= readFileSync(id, "utf-8"))) {
8702
+ matchingProjects.push(project);
8703
+ }
8704
+ });
8705
+ if (matchingProjects.length > 0) {
8706
+ this.changedTests.add(id);
8707
+ this.scheduleRerun(id);
8708
+ } else {
8709
+ const needsRerun = this.handleFileChanged(id);
8710
+ if (needsRerun) {
8711
+ this.scheduleRerun(id);
8712
+ }
8713
+ }
8714
+ };
8715
+ /**
8716
+ * @returns A value indicating whether rerun is needed (changedTests was mutated)
8717
+ */
8718
+ handleFileChanged(filepath) {
8719
+ if (this.changedTests.has(filepath) || this.invalidates.has(filepath)) {
8720
+ return false;
8721
+ }
8722
+ if (mm.isMatch(filepath, this.vitest.config.forceRerunTriggers)) {
8723
+ this.vitest.state.getFilepaths().forEach((file) => this.changedTests.add(file));
8724
+ return true;
8725
+ }
8726
+ const projects = this.vitest.projects.filter((project) => {
8727
+ const moduleGraph = project.browser?.vite.moduleGraph || project.vite.moduleGraph;
8728
+ return moduleGraph.getModulesByFile(filepath)?.size;
8729
+ });
8730
+ if (!projects.length) {
8731
+ if (this.vitest.state.filesMap.has(filepath) || this.vitest.projects.some((project) => project._isCachedTestFile(filepath))) {
8732
+ this.changedTests.add(filepath);
8733
+ return true;
8734
+ }
8735
+ return false;
8736
+ }
8737
+ const files = [];
8738
+ for (const project of projects) {
8739
+ const mods = project.browser?.vite.moduleGraph.getModulesByFile(filepath) || project.vite.moduleGraph.getModulesByFile(filepath);
8740
+ if (!mods || !mods.size) {
8741
+ continue;
8742
+ }
8743
+ this.invalidates.add(filepath);
8744
+ if (this.vitest.state.filesMap.has(filepath) || project._isCachedTestFile(filepath)) {
8745
+ this.changedTests.add(filepath);
8746
+ files.push(filepath);
8747
+ continue;
8748
+ }
8749
+ let rerun = false;
8750
+ for (const mod of mods) {
8751
+ mod.importers.forEach((i) => {
8752
+ if (!i.file) {
8753
+ return;
8754
+ }
8755
+ const needsRerun = this.handleFileChanged(i.file);
8756
+ if (needsRerun) {
8757
+ rerun = true;
8758
+ }
8759
+ });
8760
+ }
8761
+ if (rerun) {
8762
+ files.push(filepath);
8763
+ }
8764
+ }
8765
+ return !!files.length;
8708
8766
  }
8709
- return names;
8710
8767
  }
8711
8768
 
8712
8769
  const WATCHER_DEBOUNCE = 100;
@@ -8899,7 +8956,7 @@ class Vitest {
8899
8956
  try {
8900
8957
  await this.cache.results.readFromCache();
8901
8958
  } catch {}
8902
- const projects = await this.resolveWorkspace(cliOptions);
8959
+ const projects = await this.resolveProjects(cliOptions);
8903
8960
  this.resolvedProjects = projects;
8904
8961
  this.projects = projects;
8905
8962
  await Promise.all(projects.flatMap((project) => {
@@ -8910,8 +8967,19 @@ class Vitest {
8910
8967
  injectTestProjects: this.injectTestProject
8911
8968
  }));
8912
8969
  }));
8970
+ if (options.browser?.enabled) {
8971
+ const browserProjects = this.projects.filter((p) => p.config.browser.enabled);
8972
+ if (!browserProjects.length) {
8973
+ throw new Error(`Vitest received --browser flag, but no project had a browser configuration.`);
8974
+ }
8975
+ }
8913
8976
  if (!this.projects.length) {
8914
- throw new Error(`No projects matched the filter "${toArray(resolved.project).join("\", \"")}".`);
8977
+ const filter = toArray(resolved.project).join("\", \"");
8978
+ if (filter) {
8979
+ throw new Error(`No projects matched the filter "${filter}".`);
8980
+ } else {
8981
+ throw new Error(`Vitest wasn't able to resolve any project.`);
8982
+ }
8915
8983
  }
8916
8984
  if (!this.coreWorkspaceProject) {
8917
8985
  this.coreWorkspaceProject = TestProject._createBasicProject(this);
@@ -8929,9 +8997,9 @@ class Vitest {
8929
8997
  */
8930
8998
  injectTestProject = async (config) => {
8931
8999
  const currentNames = new Set(this.projects.map((p) => p.name));
8932
- const workspace = await resolveWorkspace(this, this._options, undefined, Array.isArray(config) ? config : [config], currentNames);
8933
- this.projects.push(...workspace);
8934
- return workspace;
9000
+ const projects = await resolveProjects(this, this._options, undefined, Array.isArray(config) ? config : [config], currentNames);
9001
+ this.projects.push(...projects);
9002
+ return projects;
8935
9003
  };
8936
9004
  /**
8937
9005
  * Provide a value to the test context. This value will be available to all tests with `inject`.
@@ -9002,10 +9070,17 @@ class Vitest {
9002
9070
  }
9003
9071
  return join(configDir, workspaceConfigName);
9004
9072
  }
9005
- async resolveWorkspace(cliOptions) {
9073
+ async resolveProjects(cliOptions) {
9006
9074
  const names = new Set();
9075
+ if (this.config.projects) {
9076
+ if (typeof this.config.workspace !== "undefined") {
9077
+ this.logger.warn("Both `config.projects` and `config.workspace` are defined. Ignoring the `workspace` option.");
9078
+ }
9079
+ return resolveProjects(this, cliOptions, undefined, this.config.projects, names);
9080
+ }
9007
9081
  if (Array.isArray(this.config.workspace)) {
9008
- return resolveWorkspace(this, cliOptions, undefined, this.config.workspace, names);
9082
+ 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`.");
9083
+ return resolveProjects(this, cliOptions, undefined, this.config.workspace, names);
9009
9084
  }
9010
9085
  const workspaceConfigPath = await this.resolveWorkspaceConfigPath();
9011
9086
  this._workspaceConfigPath = workspaceConfigPath;
@@ -9014,13 +9089,15 @@ class Vitest {
9014
9089
  if (!project) {
9015
9090
  return [];
9016
9091
  }
9017
- return resolveBrowserWorkspace(this, new Set([project.name]), [project]);
9092
+ return resolveBrowserProjects(this, new Set([project.name]), [project]);
9018
9093
  }
9094
+ const configFile = this.vite.config.configFile ? resolve(this.vite.config.root, this.vite.config.configFile) : "the root config file";
9095
+ this.logger.deprecate(`The workspace file is deprecated and will be removed in the next major. Please, use the \`projects\` field in ${configFile} instead.`);
9019
9096
  const workspaceModule = await this.import(workspaceConfigPath);
9020
9097
  if (!workspaceModule.default || !Array.isArray(workspaceModule.default)) {
9021
9098
  throw new TypeError(`Workspace config file "${workspaceConfigPath}" must export a default array of project paths.`);
9022
9099
  }
9023
- return resolveWorkspace(this, cliOptions, workspaceConfigPath, workspaceModule.default, names);
9100
+ return resolveProjects(this, cliOptions, workspaceConfigPath, workspaceModule.default, names);
9024
9101
  }
9025
9102
  /**
9026
9103
  * Glob test files in every project and create a TestSpecification for each file and pool.
@@ -9047,7 +9124,13 @@ class Vitest {
9047
9124
  if (this.reporters.some((r) => r instanceof BlobReporter)) {
9048
9125
  throw new Error("Cannot merge reports when `--reporter=blob` is used. Remove blob reporter from the config first.");
9049
9126
  }
9050
- const { files, errors, coverages } = await readBlobs(this.version, directory || this.config.mergeReports, this.projects);
9127
+ const { files, errors, coverages, executionTimes } = await readBlobs(this.version, directory || this.config.mergeReports, this.projects);
9128
+ this.state.blobs = {
9129
+ files,
9130
+ errors,
9131
+ coverages,
9132
+ executionTimes
9133
+ };
9051
9134
  await this.report("onInit", this);
9052
9135
  await this.report("onPathsCollected", files.flatMap((f) => f.filepath));
9053
9136
  const specifications = [];
@@ -9246,7 +9329,9 @@ class Vitest {
9246
9329
  process.exitCode = 1;
9247
9330
  }
9248
9331
  this.cache.results.updateResults(files);
9249
- await this.cache.results.writeToCache();
9332
+ try {
9333
+ await this.cache.results.writeToCache();
9334
+ } catch {}
9250
9335
  return {
9251
9336
  testModules: this.state.getTestModules(),
9252
9337
  unhandledErrors: this.state.getUnhandledErrors()