vitest 3.1.3 → 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 (42) hide show
  1. package/dist/browser.d.ts +2 -2
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{base.DslwPSCy.js → base.SfTiRNZf.js} +1 -1
  4. package/dist/chunks/{cac.BN2e7cE1.js → cac.TfX2-DVH.js} +9 -7
  5. package/dist/chunks/{cli-api.Bti1vevt.js → cli-api.2970Nj9J.js} +908 -847
  6. package/dist/chunks/{coverage.87S59-Sl.js → coverage.z0LVMxgb.js} +3 -1
  7. package/dist/chunks/{environment.d.Dmw5ulng.d.ts → environment.d.D8YDy2v5.d.ts} +2 -1
  8. package/dist/chunks/{global.d.CXRAxnWc.d.ts → global.d.BCOHQEpR.d.ts} +7 -2
  9. package/dist/chunks/{globals.CZAEe_Gf.js → globals.Cg4NtV4P.js} +2 -2
  10. package/dist/chunks/{index.B0uVAVvx.js → index.BPc7M5ni.js} +1 -1
  11. package/dist/chunks/{index.De2FqGmR.js → index.CUacZlWG.js} +14 -8
  12. package/dist/chunks/{index.Cu2UlluP.js → index.DbWBPwtH.js} +2 -2
  13. package/dist/chunks/{reporters.d.DG9VKi4m.d.ts → reporters.d.DGm4k1Wx.d.ts} +51 -11
  14. package/dist/chunks/{runBaseTests.BV8m0B-u.js → runBaseTests.CguliJB5.js} +5 -5
  15. package/dist/chunks/{setup-common.AQcDs321.js → setup-common.BP6KrF_Z.js} +1 -1
  16. package/dist/chunks/{utils.Cc45eY3L.js → utils.8gfOgtry.js} +19 -12
  17. package/dist/chunks/{vi.ClIskdbk.js → vi.BFR5YIgu.js} +3 -0
  18. package/dist/chunks/{vite.d.D3ndlJcw.d.ts → vite.d.DjP_ALCZ.d.ts} +1 -1
  19. package/dist/chunks/{worker.d.CHGSOG0s.d.ts → worker.d.CoCI7hzP.d.ts} +1 -1
  20. package/dist/chunks/{worker.d.C-KN07Ls.d.ts → worker.d.D5Xdi-Zr.d.ts} +1 -1
  21. package/dist/cli.js +1 -1
  22. package/dist/config.cjs +3 -0
  23. package/dist/config.d.ts +8 -5
  24. package/dist/config.js +3 -0
  25. package/dist/coverage.d.ts +3 -3
  26. package/dist/coverage.js +1 -1
  27. package/dist/environments.d.ts +2 -2
  28. package/dist/execute.d.ts +2 -2
  29. package/dist/index.d.ts +45 -10
  30. package/dist/index.js +2 -2
  31. package/dist/node.d.ts +8 -8
  32. package/dist/node.js +7 -7
  33. package/dist/reporters.d.ts +3 -3
  34. package/dist/reporters.js +2 -2
  35. package/dist/runners.d.ts +1 -1
  36. package/dist/runners.js +2 -2
  37. package/dist/workers/forks.js +1 -1
  38. package/dist/workers/runVmTests.js +5 -5
  39. package/dist/workers/threads.js +1 -1
  40. package/dist/workers.d.ts +3 -3
  41. package/dist/workers.js +1 -1
  42. package/package.json +11 -11
@@ -7,13 +7,13 @@ import { f as findUp, p as prompt } from './index.DBIGubLC.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.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.De2FqGmR.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.87S59-Sl.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);
@@ -5409,6 +5415,9 @@ class Logger {
5409
5415
  printError(err, options = {}) {
5410
5416
  printError(err, this.ctx, this, options);
5411
5417
  }
5418
+ deprecate(message) {
5419
+ this.log(c.bold(c.bgYellow(" DEPRECATED ")), c.yellow(message));
5420
+ }
5412
5421
  clearHighlightCache(filename) {
5413
5422
  if (filename) {
5414
5423
  this._highlights.delete(filename);
@@ -5449,7 +5458,7 @@ class Logger {
5449
5458
  const config = project.config;
5450
5459
  const printConfig = !project.isRootProject() && project.name;
5451
5460
  if (printConfig) {
5452
- this.console.error(`\n${formatProjectName(project.name)}\n`);
5461
+ this.console.error(`\n${formatProjectName(project)}\n`);
5453
5462
  }
5454
5463
  if (config.include) {
5455
5464
  this.console.error(c.dim("include: ") + c.yellow(config.include.join(comma)));
@@ -5501,7 +5510,7 @@ class Logger {
5501
5510
  if (!origin) {
5502
5511
  return;
5503
5512
  }
5504
- const output = project.isRootProject() ? "" : formatProjectName(project.name);
5513
+ const output = project.isRootProject() ? "" : formatProjectName(project);
5505
5514
  const provider = project.browser.provider.name;
5506
5515
  const providerString = provider === "preview" ? "" : ` by ${c.reset(c.bold(provider))}`;
5507
5516
  this.log(c.dim(`${output}Browser runner started${providerString} ${c.dim("at")} ${c.blue(new URL("/", origin))}\n`));
@@ -6521,7 +6530,8 @@ function resolveOptimizerConfig(_testOptions, viteOptions, testConfig, viteCache
6521
6530
  const runtime = currentInclude.filter((n) => n.endsWith("jsx-dev-runtime") || n.endsWith("jsx-runtime"));
6522
6531
  exclude.push(...runtime);
6523
6532
  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);
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);
6525
6535
  newConfig.optimizeDeps = {
6526
6536
  ...viteOptions,
6527
6537
  ...testOptions,
@@ -6688,7 +6698,10 @@ function WorkspaceVitestPlugin(project, options) {
6688
6698
  const defines = deleteDefineConfig(viteConfig);
6689
6699
  const testConfig = viteConfig.test || {};
6690
6700
  const root = testConfig.root || viteConfig.root || options.root;
6691
- let name = testConfig.name;
6701
+ let { label: name, color } = typeof testConfig.name === "string" ? { label: testConfig.name } : {
6702
+ label: "",
6703
+ ...testConfig.name
6704
+ };
6692
6705
  if (!name) {
6693
6706
  if (typeof options.workspacePath === "string") {
6694
6707
  const dir = options.workspacePath.endsWith("/") ? options.workspacePath.slice(0, -1) : dirname(options.workspacePath);
@@ -6726,7 +6739,10 @@ function WorkspaceVitestPlugin(project, options) {
6726
6739
  fs: { allow: resolveFsAllow(project.vitest.config.root, project.vitest.vite.config.configFile) }
6727
6740
  },
6728
6741
  environments: { ssr: { resolve: resolveOptions } },
6729
- test: { name }
6742
+ test: { name: {
6743
+ label: name,
6744
+ color
6745
+ } }
6730
6746
  };
6731
6747
  if (project.vitest._options.browser && viteConfig.test?.browser) {
6732
6748
  viteConfig.test.browser = mergeConfig(viteConfig.test.browser, project.vitest._options.browser);
@@ -6980,6 +6996,12 @@ class TestProject {
6980
6996
  return this.config.name || "";
6981
6997
  }
6982
6998
  /**
6999
+ * The color used when reporting tasks of this project.
7000
+ */
7001
+ get color() {
7002
+ return this.config.color;
7003
+ }
7004
+ /**
6983
7005
  * Serialized project configuration. This is the config that tests receive.
6984
7006
  */
6985
7007
  get serializedConfig() {
@@ -7364,182 +7386,499 @@ async function initializeProject(workspacePath, ctx, options) {
7364
7386
  return project;
7365
7387
  }
7366
7388
 
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
- }
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];
7395
7414
  }
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();
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
7409
7431
  }
7410
- }
7411
- return referenceOrInstance;
7432
+ })));
7412
7433
  });
7413
- return Promise.all(promisedReporters);
7414
- }
7415
-
7416
- function parseFilter(filter) {
7417
- const colonIndex = filter.lastIndexOf(":");
7418
- if (colonIndex === -1) {
7419
- return { filename: filter };
7420
- }
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 };
7431
- }
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;
7441
- }
7442
-
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;
7452
- }
7453
- const specs = [];
7454
- for (const project of this.vitest.projects) {
7455
- if (project._isCachedTestFile(moduleId)) {
7456
- specs.push(project.createSpecification(moduleId));
7457
- }
7458
- if (project._isCachedTypecheckFile(moduleId)) {
7459
- 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));
7460
7439
  }
7440
+ continue;
7461
7441
  }
7462
- specs.forEach((spec) => this.ensureSpecificationCached(spec));
7463
- 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
+ })));
7464
7449
  }
7465
- async getRelevantTestSpecifications(filters = []) {
7466
- 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(""));
7467
7456
  }
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));
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;
7500
7464
  }
7501
- });
7502
- return files;
7503
- }
7504
- clearCache(moduleId) {
7505
- if (moduleId) {
7506
- this._cachedSpecs.delete(moduleId);
7465
+ errors.push(result.reason);
7507
7466
  } else {
7508
- this._cachedSpecs.clear();
7467
+ resolvedProjects.push(result.value);
7509
7468
  }
7510
7469
  }
7511
- getCachedSpecifications(moduleId) {
7512
- 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.");
7513
7472
  }
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);
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(""));
7523
7490
  }
7524
- return specs;
7491
+ names.add(name);
7525
7492
  }
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));
7536
- }
7537
- const related = this.vitest.config.related;
7538
- if (!related) {
7539
- return specs;
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;
7540
7500
  }
7541
- const forceRerunTriggers = this.vitest.config.forceRerunTriggers;
7542
- if (forceRerunTriggers.length && mm(related, forceRerunTriggers).length) {
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 {
7861
+ specs.splice(index, 1, spec);
7862
+ }
7863
+ return specs;
7864
+ }
7865
+ async filterTestsBySource(specs) {
7866
+ if (this.vitest.config.changed && !this.vitest.config.related) {
7867
+ const { VitestGit } = await import('./git.DXfdBEfR.js');
7868
+ const vitestGit = new VitestGit(this.vitest.config.root);
7869
+ const related = await vitestGit.findChangedFiles({ changedSince: this.vitest.config.changed });
7870
+ if (!related) {
7871
+ process.exitCode = 1;
7872
+ throw new GitNotFoundError();
7873
+ }
7874
+ this.vitest.config.related = Array.from(new Set(related));
7875
+ }
7876
+ const related = this.vitest.config.related;
7877
+ if (!related) {
7878
+ return specs;
7879
+ }
7880
+ const forceRerunTriggers = this.vitest.config.forceRerunTriggers;
7881
+ if (forceRerunTriggers.length && mm(related, forceRerunTriggers).length) {
7543
7882
  return specs;
7544
7883
  }
7545
7884
  if (!this.vitest.config.watch && !related.length) {
@@ -7998,720 +8337,433 @@ class StateManager {
7998
8337
  errorsSet = new Set();
7999
8338
  processTimeoutCauses = new Set();
8000
8339
  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
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
8239
8351
  };
8240
- if (event.endsWith("-start")) {
8241
- await this.vitest.report("onHookStart", hook);
8242
- } else {
8243
- await this.vitest.report("onHookEnd", hook);
8244
- }
8245
8352
  }
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);
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;
8256
8361
  }
8362
+ return;
8257
8363
  }
8364
+ this.errorsSet.add(err);
8258
8365
  }
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;
8366
+ clearErrors() {
8367
+ this.errorsSet.clear();
8273
8368
  }
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;
8369
+ getUnhandledErrors() {
8370
+ return Array.from(this.errorsSet.values());
8283
8371
  }
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;
8372
+ addProcessTimeoutCause(cause) {
8373
+ this.processTimeoutCauses.add(cause);
8300
8374
  }
8301
- scheduleRerun(file) {
8302
- this._onRerun.forEach((cb) => cb(file));
8375
+ getProcessTimeoutCauses() {
8376
+ return Array.from(this.processTimeoutCauses.values());
8377
+ }
8378
+ getPaths() {
8379
+ return Array.from(this.pathsSet);
8303
8380
  }
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
8381
  /**
8347
- * @returns A value indicating whether rerun is needed (changedTests was mutated)
8382
+ * Return files that were running or collected.
8348
8383
  */
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;
8384
+ getFiles(keys) {
8385
+ if (keys) {
8386
+ return keys.map((key) => this.filesMap.get(key)).flat().filter((file) => file && !file.local);
8367
8387
  }
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;
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;
8379
8391
  }
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
- });
8392
+ if (f1.meta?.typecheck) {
8393
+ return -1;
8391
8394
  }
8392
- if (rerun) {
8393
- 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;
8394
8419
  }
8395
- }
8396
- return !!files.length;
8420
+ otherFiles.push(file);
8421
+ this.filesMap.set(file.filepath, otherFiles);
8422
+ this.updateId(file, project);
8423
+ });
8397
8424
  }
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
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;
8442
8435
  }
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));
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]);
8450
8441
  }
8451
- 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
+ });
8452
8460
  }
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
8461
  }
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(""));
8462
+ getReportedEntity(task) {
8463
+ return this.reportedTasksMap.get(task);
8467
8464
  }
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;
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
+ }
8475
8474
  }
8476
- errors.push(result.reason);
8477
- } else {
8478
- resolvedProjects.push(result.value);
8479
8475
  }
8480
8476
  }
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(""));
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);
8501
8484
  }
8502
- names.add(name);
8503
8485
  }
8504
- 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
+ }
8505
8492
  }
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
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");
8518
8527
  });
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
8528
  }
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;
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
+ }
8535
8540
  }
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.`));
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);
8538
8548
  }
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
+ 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);
8549
8556
  }
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(""));
8557
+ if (entity.type === "module") {
8558
+ await this.vitest.report("onTestModuleEnd", entity);
8559
+ } else {
8560
+ await this.vitest.report("onTestSuiteResult", entity);
8556
8561
  }
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
- }
8587
- return resolvedProjects.filter((project) => project.name === projectName);
8588
- }
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
8562
+ return;
8607
8563
  }
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
- }
8564
+ if (event === "test-prepare" && entity.type === "test") {
8565
+ return await this.vitest.report("onTestCaseReady", entity);
8566
+ }
8567
+ if (event === "test-finished" && entity.type === "test") {
8568
+ return await this.vitest.report("onTestCaseResult", entity);
8569
+ }
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);
8639
8581
  } else {
8640
- workspaceGlobMatches.push(stringOption);
8582
+ await this.vitest.report("onHookEnd", hook);
8641
8583
  }
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
8584
  }
8652
8585
  }
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
- }
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);
8675
8591
  } else {
8676
- workspaceConfigFiles.push(path);
8592
+ await this.vitest.report("onTestSuiteReady", child);
8593
+ await this.reportChildren(child.children);
8594
+ await this.vitest.report("onTestSuiteResult", child);
8677
8595
  }
8678
- }));
8596
+ }
8679
8597
  }
8680
- const projectConfigFiles = Array.from(new Set(workspaceConfigFiles));
8681
- return {
8682
- projectConfigs: projectsOptions,
8683
- nonConfigDirectories: nonConfigProjectDirectories,
8684
- configFiles: projectConfigFiles
8685
- };
8686
8598
  }
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);
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;
8692
8612
  }
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;
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;
8700
8622
  }
8701
- const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
8702
- if (hasProjects) {
8703
- 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;
8704
8639
  }
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);
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;
8713
8766
  }
8714
- return names;
8715
8767
  }
8716
8768
 
8717
8769
  const WATCHER_DEBOUNCE = 100;
@@ -8904,7 +8956,7 @@ class Vitest {
8904
8956
  try {
8905
8957
  await this.cache.results.readFromCache();
8906
8958
  } catch {}
8907
- const projects = await this.resolveWorkspace(cliOptions);
8959
+ const projects = await this.resolveProjects(cliOptions);
8908
8960
  this.resolvedProjects = projects;
8909
8961
  this.projects = projects;
8910
8962
  await Promise.all(projects.flatMap((project) => {
@@ -8945,9 +8997,9 @@ class Vitest {
8945
8997
  */
8946
8998
  injectTestProject = async (config) => {
8947
8999
  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;
9000
+ const projects = await resolveProjects(this, this._options, undefined, Array.isArray(config) ? config : [config], currentNames);
9001
+ this.projects.push(...projects);
9002
+ return projects;
8951
9003
  };
8952
9004
  /**
8953
9005
  * Provide a value to the test context. This value will be available to all tests with `inject`.
@@ -9018,10 +9070,17 @@ class Vitest {
9018
9070
  }
9019
9071
  return join(configDir, workspaceConfigName);
9020
9072
  }
9021
- async resolveWorkspace(cliOptions) {
9073
+ async resolveProjects(cliOptions) {
9022
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
+ }
9023
9081
  if (Array.isArray(this.config.workspace)) {
9024
- 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);
9025
9084
  }
9026
9085
  const workspaceConfigPath = await this.resolveWorkspaceConfigPath();
9027
9086
  this._workspaceConfigPath = workspaceConfigPath;
@@ -9030,13 +9089,15 @@ class Vitest {
9030
9089
  if (!project) {
9031
9090
  return [];
9032
9091
  }
9033
- return resolveBrowserWorkspace(this, new Set([project.name]), [project]);
9092
+ return resolveBrowserProjects(this, new Set([project.name]), [project]);
9034
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.`);
9035
9096
  const workspaceModule = await this.import(workspaceConfigPath);
9036
9097
  if (!workspaceModule.default || !Array.isArray(workspaceModule.default)) {
9037
9098
  throw new TypeError(`Workspace config file "${workspaceConfigPath}" must export a default array of project paths.`);
9038
9099
  }
9039
- return resolveWorkspace(this, cliOptions, workspaceConfigPath, workspaceModule.default, names);
9100
+ return resolveProjects(this, cliOptions, workspaceConfigPath, workspaceModule.default, names);
9040
9101
  }
9041
9102
  /**
9042
9103
  * Glob test files in every project and create a TestSpecification for each file and pool.