vitest 4.0.17 → 4.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE.md +36 -0
  2. package/dist/browser.d.ts +1 -1
  3. package/dist/browser.js +2 -2
  4. package/dist/chunks/acorn.B2iPLyUM.js +5958 -0
  5. package/dist/chunks/{base.XJJQZiKB.js → base.DiopZV8F.js} +49 -14
  6. package/dist/chunks/{benchmark.B3N2zMcH.js → benchmark.BoqSLF53.js} +1 -1
  7. package/dist/chunks/{browser.d.ChKACdzH.d.ts → browser.d.BE4kbYok.d.ts} +4 -1
  8. package/dist/chunks/{cac.jRCLJDDc.js → cac.C4jjt2RX.js} +816 -14
  9. package/dist/chunks/{cli-api.Cx2DW4Bc.js → cli-api.ChbI1JU9.js} +412 -166
  10. package/dist/chunks/{config.d.Cy95HiCx.d.ts → config.d.Cr1Ep39N.d.ts} +13 -11
  11. package/dist/chunks/{console.Cf-YriPC.js → console.CNlG1KsP.js} +3 -2
  12. package/dist/chunks/{constants.D_Q9UYh-.js → constants.B63TT-Bl.js} +1 -1
  13. package/dist/chunks/coverage.tyqbzn4W.js +1001 -0
  14. package/dist/chunks/{creator.DAmOKTvJ.js → creator.yyCHuw5R.js} +33 -2
  15. package/dist/chunks/{global.d.B15mdLcR.d.ts → global.d.JeWMqlOm.d.ts} +1 -1
  16. package/dist/chunks/{globals.DOayXfHP.js → globals.C6Ecf1TO.js} +11 -10
  17. package/dist/chunks/{index.M8mOzt4Y.js → index.B-iBE_Gx.js} +21 -5
  18. package/dist/chunks/{coverage.AVPTjMgw.js → index.BCY_7LL2.js} +5 -959
  19. package/dist/chunks/{index.6Qv1eEA6.js → index.CAN630q3.js} +20 -8
  20. package/dist/chunks/{index.C5r1PdPD.js → index.CFulQRmC.js} +1 -1
  21. package/dist/chunks/{index.Z5E_ObnR.js → index.CouFDptX.js} +4 -2
  22. package/dist/chunks/{init-forks.BC6ZwHQN.js → init-forks.BnCXPazU.js} +1 -1
  23. package/dist/chunks/{init-threads.CxSxLC0N.js → init-threads.Cyh2PqXi.js} +1 -1
  24. package/dist/chunks/{init.C9kljSTm.js → init.B95Mm0Iz.js} +65 -12
  25. package/dist/chunks/native.mV0-490A.js +148 -0
  26. package/dist/chunks/nativeModuleMocker.D_q5sFv6.js +206 -0
  27. package/dist/chunks/nativeModuleRunner.BIakptoF.js +36 -0
  28. package/dist/chunks/{node.Ce0vMQM7.js → node.CrSEwhm4.js} +1 -1
  29. package/dist/chunks/{plugin.d.CtqpEehP.d.ts → plugin.d.C9o5bttz.d.ts} +1 -1
  30. package/dist/chunks/{reporters.d.CWXNI2jG.d.ts → reporters.d.7faYdkxy.d.ts} +146 -49
  31. package/dist/chunks/rpc.DcRWTy5G.js +148 -0
  32. package/dist/chunks/{rpc.d.RH3apGEf.d.ts → rpc.d.CM7x9-sm.d.ts} +1 -0
  33. package/dist/chunks/{setup-common.Cm-kSBVi.js → setup-common.cvFp-ao9.js} +2 -2
  34. package/dist/chunks/{startModuleRunner.DEj0jb3e.js → startVitestModuleRunner.BK-u7y4N.js} +182 -391
  35. package/dist/chunks/{vi.2VT5v0um.js → test.G82XYNFk.js} +505 -119
  36. package/dist/chunks/{utils.DvEY5TfP.js → utils.DT4VyRyl.js} +5 -1
  37. package/dist/chunks/{vm.CMjifoPa.js → vm.BdLtzhnj.js} +15 -11
  38. package/dist/chunks/{worker.d.Dyxm8DEL.d.ts → worker.d.CPzI2ZzJ.d.ts} +2 -2
  39. package/dist/cli.js +4 -3
  40. package/dist/config.d.ts +11 -11
  41. package/dist/config.js +1 -1
  42. package/dist/coverage.d.ts +10 -8
  43. package/dist/coverage.js +7 -4
  44. package/dist/environments.js +2 -0
  45. package/dist/index.d.ts +30 -23
  46. package/dist/index.js +9 -8
  47. package/dist/module-evaluator.d.ts +10 -1
  48. package/dist/module-evaluator.js +1 -5
  49. package/dist/node.d.ts +13 -12
  50. package/dist/node.js +27 -25
  51. package/dist/nodejs-worker-loader.js +41 -0
  52. package/dist/reporters.d.ts +8 -8
  53. package/dist/reporters.js +4 -2
  54. package/dist/runners.d.ts +24 -4
  55. package/dist/runners.js +6 -6
  56. package/dist/runtime.d.ts +6 -0
  57. package/dist/runtime.js +35 -0
  58. package/dist/snapshot.js +4 -2
  59. package/dist/suite.js +4 -2
  60. package/dist/worker.d.ts +8 -7
  61. package/dist/worker.js +25 -20
  62. package/dist/workers/forks.js +21 -16
  63. package/dist/workers/runVmTests.js +11 -13
  64. package/dist/workers/threads.js +21 -16
  65. package/dist/workers/vmForks.js +14 -11
  66. package/dist/workers/vmThreads.js +14 -11
  67. package/package.json +28 -29
  68. package/suppress-warnings.cjs +1 -0
  69. package/dist/chunks/date.Bq6ZW5rf.js +0 -73
  70. package/dist/chunks/rpc.BoxB0q7B.js +0 -76
  71. package/dist/chunks/test.B8ej_ZHS.js +0 -254
  72. package/dist/mocker.d.ts +0 -1
  73. package/dist/mocker.js +0 -1
  74. package/dist/module-runner.js +0 -17
@@ -1,27 +1,13 @@
1
- import fs, { statSync, realpathSync, existsSync, promises, readdirSync, writeFileSync } from 'node:fs';
2
- import path, { win32, dirname, join } from 'node:path';
3
- import { slash, shuffle, toArray } from '@vitest/utils/helpers';
4
- import { isAbsolute, resolve, relative, normalize } from 'pathe';
5
- import pm from 'picomatch';
6
- import { glob } from 'tinyglobby';
7
- import c from 'tinyrainbow';
8
- import { c as configDefaults, e as benchmarkConfigDefaults, a as coverageConfigDefaults } from './defaults.BOqNVLsY.js';
9
- import crypto from 'node:crypto';
10
- import { fileURLToPath as fileURLToPath$1, pathToFileURL as pathToFileURL$1, URL as URL$1 } from 'node:url';
1
+ import fs, { statSync, realpathSync } from 'node:fs';
11
2
  import { builtinModules, createRequire } from 'node:module';
3
+ import path, { win32, dirname, join } from 'node:path';
12
4
  import process$1 from 'node:process';
13
5
  import fs$1 from 'node:fs/promises';
6
+ import { fileURLToPath as fileURLToPath$1, pathToFileURL as pathToFileURL$1, URL as URL$1 } from 'node:url';
7
+ import { isAbsolute } from 'pathe';
14
8
  import assert from 'node:assert';
15
9
  import v8 from 'node:v8';
16
10
  import { format, inspect } from 'node:util';
17
- import { mergeConfig } from 'vite';
18
- import { c as configFiles, d as defaultBrowserPort, b as defaultInspectPort, a as defaultPort } from './constants.D_Q9UYh-.js';
19
- import './env.D4Lgay0q.js';
20
- import nodeos__default from 'node:os';
21
- import { isCI, provider } from 'std-env';
22
- import { r as resolveCoverageProviderModule } from './coverage.D_JHT54q.js';
23
-
24
- const hash = crypto.hash ?? ((algorithm, data, outputEncoding) => crypto.createHash(algorithm).update(data).digest(outputEncoding));
25
11
 
26
12
  const JOIN_LEADING_SLASH_RE = /^\.?\//;
27
13
  function withTrailingSlash(input = "", respectQueryAndFragment) {
@@ -2349,944 +2335,4 @@ const isPackageListed = quansync(function* (name, cwd) {
2349
2335
  });
2350
2336
  isPackageListed.sync;
2351
2337
 
2352
- function getWorkersCountByPercentage(percent) {
2353
- const maxWorkersCount = nodeos__default.availableParallelism?.() ?? nodeos__default.cpus().length;
2354
- const workersCountByPercentage = Math.round(Number.parseInt(percent) / 100 * maxWorkersCount);
2355
- return Math.max(1, Math.min(maxWorkersCount, workersCountByPercentage));
2356
- }
2357
-
2358
- class BaseSequencer {
2359
- ctx;
2360
- constructor(ctx) {
2361
- this.ctx = ctx;
2362
- }
2363
- // async so it can be extended by other sequelizers
2364
- async shard(files) {
2365
- const { config } = this.ctx;
2366
- const { index, count } = config.shard;
2367
- const [shardStart, shardEnd] = this.calculateShardRange(files.length, index, count);
2368
- return [...files].map((spec) => {
2369
- const specPath = resolve(slash(config.root), slash(spec.moduleId))?.slice(config.root.length);
2370
- return {
2371
- spec,
2372
- hash: hash("sha1", specPath, "hex")
2373
- };
2374
- }).sort((a, b) => a.hash < b.hash ? -1 : a.hash > b.hash ? 1 : 0).slice(shardStart, shardEnd).map(({ spec }) => spec);
2375
- }
2376
- // async so it can be extended by other sequelizers
2377
- async sort(files) {
2378
- const cache = this.ctx.cache;
2379
- return [...files].sort((a, b) => {
2380
- // "sequence.groupOrder" is higher priority
2381
- const groupOrderDiff = a.project.config.sequence.groupOrder - b.project.config.sequence.groupOrder;
2382
- if (groupOrderDiff !== 0) return groupOrderDiff;
2383
- // Projects run sequential
2384
- if (a.project.name !== b.project.name) return a.project.name < b.project.name ? -1 : 1;
2385
- // Isolated run first
2386
- if (a.project.config.isolate && !b.project.config.isolate) return -1;
2387
- if (!a.project.config.isolate && b.project.config.isolate) return 1;
2388
- const keyA = `${a.project.name}:${relative(this.ctx.config.root, a.moduleId)}`;
2389
- const keyB = `${b.project.name}:${relative(this.ctx.config.root, b.moduleId)}`;
2390
- const aState = cache.getFileTestResults(keyA);
2391
- const bState = cache.getFileTestResults(keyB);
2392
- if (!aState || !bState) {
2393
- const statsA = cache.getFileStats(keyA);
2394
- const statsB = cache.getFileStats(keyB);
2395
- // run unknown first
2396
- if (!statsA || !statsB) return !statsA && statsB ? -1 : !statsB && statsA ? 1 : 0;
2397
- // run larger files first
2398
- return statsB.size - statsA.size;
2399
- }
2400
- // run failed first
2401
- if (aState.failed && !bState.failed) return -1;
2402
- if (!aState.failed && bState.failed) return 1;
2403
- // run longer first
2404
- return bState.duration - aState.duration;
2405
- });
2406
- }
2407
- // Calculate distributed shard range [start, end] distributed equally
2408
- calculateShardRange(filesCount, index, count) {
2409
- const baseShardSize = Math.floor(filesCount / count);
2410
- const remainderTestFilesCount = filesCount % count;
2411
- if (remainderTestFilesCount >= index) {
2412
- const shardSize = baseShardSize + 1;
2413
- return [shardSize * (index - 1), shardSize * index];
2414
- }
2415
- const shardStart = remainderTestFilesCount * (baseShardSize + 1) + (index - remainderTestFilesCount - 1) * baseShardSize;
2416
- return [shardStart, shardStart + baseShardSize];
2417
- }
2418
- }
2419
-
2420
- class RandomSequencer extends BaseSequencer {
2421
- async sort(files) {
2422
- const { sequence } = this.ctx.config;
2423
- return shuffle(files, sequence.seed);
2424
- }
2425
- }
2426
-
2427
- function resolvePath(path, root) {
2428
- return normalize(/* @__PURE__ */ resolveModule(path, { paths: [root] }) ?? resolve(root, path));
2429
- }
2430
- function parseInspector(inspect) {
2431
- if (typeof inspect === "boolean" || inspect === void 0) return {};
2432
- if (typeof inspect === "number") return { port: inspect };
2433
- if (inspect.match(/https?:\//)) throw new Error(`Inspector host cannot be a URL. Use "host:port" instead of "${inspect}"`);
2434
- const [host, port] = inspect.split(":");
2435
- if (!port) return { host };
2436
- return {
2437
- host,
2438
- port: Number(port) || defaultInspectPort
2439
- };
2440
- }
2441
- function resolveApiServerConfig(options, defaultPort) {
2442
- let api;
2443
- if (options.ui && !options.api) api = { port: defaultPort };
2444
- else if (options.api === true) api = { port: defaultPort };
2445
- else if (typeof options.api === "number") api = { port: options.api };
2446
- if (typeof options.api === "object") if (api) {
2447
- if (options.api.port) api.port = options.api.port;
2448
- if (options.api.strictPort) api.strictPort = options.api.strictPort;
2449
- if (options.api.host) api.host = options.api.host;
2450
- } else api = { ...options.api };
2451
- if (api) {
2452
- if (!api.port && !api.middlewareMode) api.port = defaultPort;
2453
- } else api = { middlewareMode: true };
2454
- return api;
2455
- }
2456
- function resolveInlineWorkerOption(value) {
2457
- if (typeof value === "string" && value.trim().endsWith("%")) return getWorkersCountByPercentage(value);
2458
- else return Number(value);
2459
- }
2460
- function resolveConfig$1(vitest, options, viteConfig) {
2461
- const mode = vitest.mode;
2462
- const logger = vitest.logger;
2463
- if (options.dom) {
2464
- if (viteConfig.test?.environment != null && viteConfig.test.environment !== "happy-dom") logger.console.warn(c.yellow(`${c.inverse(c.yellow(" Vitest "))} Your config.test.environment ("${viteConfig.test.environment}") conflicts with --dom flag ("happy-dom"), ignoring "${viteConfig.test.environment}"`));
2465
- options.environment = "happy-dom";
2466
- }
2467
- const resolved = {
2468
- ...configDefaults,
2469
- ...options,
2470
- root: viteConfig.root,
2471
- mode
2472
- };
2473
- if (options.pool && typeof options.pool !== "string") {
2474
- resolved.pool = options.pool.name;
2475
- resolved.poolRunner = options.pool;
2476
- }
2477
- if ("poolOptions" in resolved) logger.deprecate("`test.poolOptions` was removed in Vitest 4. All previous `poolOptions` are now top-level options. Please, refer to the migration guide: https://vitest.dev/guide/migration#pool-rework");
2478
- resolved.pool ??= "forks";
2479
- resolved.project = toArray(resolved.project);
2480
- resolved.provide ??= {};
2481
- resolved.name = typeof options.name === "string" ? options.name : options.name?.label || "";
2482
- resolved.color = typeof options.name !== "string" ? options.name?.color : void 0;
2483
- if (resolved.environment === "browser") throw new Error(`Looks like you set "test.environment" to "browser". To enable Browser Mode, use "test.browser.enabled" instead.`);
2484
- const inspector = resolved.inspect || resolved.inspectBrk;
2485
- resolved.inspector = {
2486
- ...resolved.inspector,
2487
- ...parseInspector(inspector),
2488
- enabled: !!inspector,
2489
- waitForDebugger: options.inspector?.waitForDebugger ?? !!resolved.inspectBrk
2490
- };
2491
- if (viteConfig.base !== "/") resolved.base = viteConfig.base;
2492
- resolved.clearScreen = resolved.clearScreen ?? viteConfig.clearScreen ?? true;
2493
- if (options.shard) {
2494
- if (resolved.watch) throw new Error("You cannot use --shard option with enabled watch");
2495
- const [indexString, countString] = options.shard.split("/");
2496
- const index = Math.abs(Number.parseInt(indexString, 10));
2497
- const count = Math.abs(Number.parseInt(countString, 10));
2498
- if (Number.isNaN(count) || count <= 0) throw new Error("--shard <count> must be a positive number");
2499
- if (Number.isNaN(index) || index <= 0 || index > count) throw new Error("--shard <index> must be a positive number less then <count>");
2500
- resolved.shard = {
2501
- index,
2502
- count
2503
- };
2504
- }
2505
- if (resolved.standalone && !resolved.watch) throw new Error(`Vitest standalone mode requires --watch`);
2506
- if (resolved.mergeReports && resolved.watch) throw new Error(`Cannot merge reports with --watch enabled`);
2507
- if (resolved.maxWorkers) resolved.maxWorkers = resolveInlineWorkerOption(resolved.maxWorkers);
2508
- if (!(options.fileParallelism ?? mode !== "benchmark"))
2509
- // ignore user config, parallelism cannot be implemented without limiting workers
2510
- resolved.maxWorkers = 1;
2511
- if (resolved.maxConcurrency === 0) {
2512
- logger.console.warn(c.yellow(`The option "maxConcurrency" cannot be set to 0. Using default value ${configDefaults.maxConcurrency} instead.`));
2513
- resolved.maxConcurrency = configDefaults.maxConcurrency;
2514
- }
2515
- if (resolved.inspect || resolved.inspectBrk) {
2516
- if (resolved.maxWorkers !== 1) {
2517
- const inspectOption = `--inspect${resolved.inspectBrk ? "-brk" : ""}`;
2518
- throw new Error(`You cannot use ${inspectOption} without "--no-file-parallelism"`);
2519
- }
2520
- }
2521
- // apply browser CLI options only if the config already has the browser config and not disabled manually
2522
- if (vitest._cliOptions.browser && resolved.browser && (resolved.browser.enabled !== false || vitest._cliOptions.browser.enabled)) resolved.browser = mergeConfig(resolved.browser, vitest._cliOptions.browser);
2523
- resolved.browser ??= {};
2524
- const browser = resolved.browser;
2525
- if (browser.enabled) {
2526
- const instances = browser.instances;
2527
- if (!browser.instances) browser.instances = [];
2528
- // use `chromium` by default when the preview provider is specified
2529
- // for a smoother experience. if chromium is not available, it will
2530
- // open the default browser anyway
2531
- if (!browser.instances.length && browser.provider?.name === "preview") browser.instances = [{ browser: "chromium" }];
2532
- if (browser.name && instances?.length) {
2533
- // --browser=chromium filters configs to a single one
2534
- browser.instances = browser.instances.filter((instance) => instance.browser === browser.name);
2535
- // if `instances` were defined, but now they are empty,
2536
- // let's throw an error because the filter is invalid
2537
- if (!browser.instances.length) throw new Error([`"browser.instances" was set in the config, but the array is empty. Define at least one browser config.`, ` The "browser.name" was set to "${browser.name}" which filtered all configs (${instances.map((c) => c.browser).join(", ")}). Did you mean to use another name?`].join(""));
2538
- }
2539
- }
2540
- const containsChromium = hasBrowserChromium(vitest, resolved);
2541
- const hasOnlyChromium = hasOnlyBrowserChromium(vitest, resolved);
2542
- // Browser-mode "Chromium" only features:
2543
- if (browser.enabled && (!containsChromium || !hasOnlyChromium)) {
2544
- const browserConfig = `
2545
- {
2546
- browser: {
2547
- provider: ${browser.provider?.name || "preview"}(),
2548
- instances: [
2549
- ${(browser.instances || []).map((i) => `{ browser: '${i.browser}' }`).join(",\n ")}
2550
- ],
2551
- },
2552
- }
2553
- `.trim();
2554
- const preferredProvider = !browser.provider?.name || browser.provider.name === "preview" ? "playwright" : browser.provider.name;
2555
- const correctExample = `
2556
- {
2557
- browser: {
2558
- provider: ${preferredProvider}(),
2559
- instances: [
2560
- { browser: '${preferredProvider === "playwright" ? "chromium" : "chrome"}' }
2561
- ],
2562
- },
2563
- }
2564
- `.trim();
2565
- // requires all projects to be chromium
2566
- if (!hasOnlyChromium && resolved.coverage.enabled && resolved.coverage.provider === "v8") {
2567
- const coverageExample = `
2568
- {
2569
- coverage: {
2570
- provider: 'istanbul',
2571
- },
2572
- }
2573
- `.trim();
2574
- throw new Error(`@vitest/coverage-v8 does not work with\n${browserConfig}\n\nUse either:\n${correctExample}\n\n...or change your coverage provider to:\n${coverageExample}\n`);
2575
- }
2576
- // ignores non-chromium browsers when there is at least one chromium project
2577
- if (!containsChromium && (resolved.inspect || resolved.inspectBrk)) {
2578
- const inspectOption = `--inspect${resolved.inspectBrk ? "-brk" : ""}`;
2579
- throw new Error(`${inspectOption} does not work with\n${browserConfig}\n\nUse either:\n${correctExample}\n\n...or disable ${inspectOption}\n`);
2580
- }
2581
- }
2582
- resolved.coverage.reporter = resolveCoverageReporters(resolved.coverage.reporter);
2583
- if (resolved.coverage.enabled && resolved.coverage.reportsDirectory) {
2584
- const reportsDirectory = resolve(resolved.root, resolved.coverage.reportsDirectory);
2585
- if (reportsDirectory === resolved.root || reportsDirectory === process.cwd()) throw new Error(`You cannot set "coverage.reportsDirectory" as ${reportsDirectory}. Vitest needs to be able to remove this directory before test run`);
2586
- }
2587
- if (resolved.coverage.enabled && resolved.coverage.provider === "custom" && resolved.coverage.customProviderModule) resolved.coverage.customProviderModule = resolvePath(resolved.coverage.customProviderModule, resolved.root);
2588
- resolved.expect ??= {};
2589
- resolved.deps ??= {};
2590
- resolved.deps.moduleDirectories ??= [];
2591
- resolved.deps.optimizer ??= {};
2592
- resolved.deps.optimizer.ssr ??= {};
2593
- resolved.deps.optimizer.ssr.enabled ??= false;
2594
- resolved.deps.optimizer.client ??= {};
2595
- resolved.deps.optimizer.client.enabled ??= false;
2596
- resolved.deps.web ??= {};
2597
- resolved.deps.web.transformAssets ??= true;
2598
- resolved.deps.web.transformCss ??= true;
2599
- resolved.deps.web.transformGlobPattern ??= [];
2600
- resolved.setupFiles = toArray(resolved.setupFiles || []).map((file) => resolvePath(file, resolved.root));
2601
- resolved.globalSetup = toArray(resolved.globalSetup || []).map((file) => resolvePath(file, resolved.root));
2602
- // Add hard-coded default coverage exclusions. These cannot be overidden by user config.
2603
- // Override original exclude array for cases where user re-uses same object in test.exclude.
2604
- resolved.coverage.exclude = [
2605
- ...resolved.coverage.exclude,
2606
- ...resolved.setupFiles.map((file) => `${resolved.coverage.allowExternal ? "**/" : ""}${relative(resolved.root, file)}`),
2607
- ...resolved.include,
2608
- resolved.config && slash(resolved.config),
2609
- ...configFiles,
2610
- "**/virtual:*",
2611
- "**/__x00__*",
2612
- "**/node_modules/**"
2613
- ].filter((pattern) => typeof pattern === "string");
2614
- resolved.forceRerunTriggers = [...resolved.forceRerunTriggers, ...resolved.setupFiles];
2615
- if (resolved.cliExclude) resolved.exclude.push(...resolved.cliExclude);
2616
- if (resolved.runner) resolved.runner = resolvePath(resolved.runner, resolved.root);
2617
- resolved.attachmentsDir = resolve(resolved.root, resolved.attachmentsDir ?? ".vitest-attachments");
2618
- if (resolved.snapshotEnvironment) resolved.snapshotEnvironment = resolvePath(resolved.snapshotEnvironment, resolved.root);
2619
- resolved.testNamePattern = resolved.testNamePattern ? resolved.testNamePattern instanceof RegExp ? resolved.testNamePattern : new RegExp(resolved.testNamePattern) : void 0;
2620
- if (resolved.snapshotFormat && "plugins" in resolved.snapshotFormat) {
2621
- resolved.snapshotFormat.plugins = [];
2622
- // TODO: support it via separate config (like DiffOptions) or via `Function.toString()`
2623
- if (typeof resolved.snapshotFormat.compareKeys === "function") throw new TypeError(`"snapshotFormat.compareKeys" function is not supported.`);
2624
- }
2625
- const UPDATE_SNAPSHOT = resolved.update || process.env.UPDATE_SNAPSHOT;
2626
- resolved.snapshotOptions = {
2627
- expand: resolved.expandSnapshotDiff ?? false,
2628
- snapshotFormat: resolved.snapshotFormat || {},
2629
- updateSnapshot: isCI && !UPDATE_SNAPSHOT ? "none" : UPDATE_SNAPSHOT ? "all" : "new",
2630
- resolveSnapshotPath: options.resolveSnapshotPath,
2631
- snapshotEnvironment: null
2632
- };
2633
- resolved.snapshotSerializers ??= [];
2634
- resolved.snapshotSerializers = resolved.snapshotSerializers.map((file) => resolvePath(file, resolved.root));
2635
- resolved.forceRerunTriggers.push(...resolved.snapshotSerializers);
2636
- if (options.resolveSnapshotPath) delete resolved.resolveSnapshotPath;
2637
- resolved.execArgv ??= [];
2638
- resolved.pool ??= "threads";
2639
- if (resolved.pool === "vmForks" || resolved.pool === "vmThreads" || resolved.pool === "typescript") resolved.isolate = false;
2640
- if (process.env.VITEST_MAX_WORKERS) resolved.maxWorkers = Number.parseInt(process.env.VITEST_MAX_WORKERS);
2641
- if (mode === "benchmark") {
2642
- resolved.benchmark = {
2643
- ...benchmarkConfigDefaults,
2644
- ...resolved.benchmark
2645
- };
2646
- // override test config
2647
- resolved.coverage.enabled = false;
2648
- resolved.typecheck.enabled = false;
2649
- resolved.include = resolved.benchmark.include;
2650
- resolved.exclude = resolved.benchmark.exclude;
2651
- resolved.includeSource = resolved.benchmark.includeSource;
2652
- const reporters = Array.from(new Set([...toArray(resolved.benchmark.reporters), ...toArray(options.reporter)])).filter(Boolean);
2653
- if (reporters.length) resolved.benchmark.reporters = reporters;
2654
- else resolved.benchmark.reporters = ["default"];
2655
- if (options.outputFile) resolved.benchmark.outputFile = options.outputFile;
2656
- // --compare from cli
2657
- if (options.compare) resolved.benchmark.compare = options.compare;
2658
- if (options.outputJson) resolved.benchmark.outputJson = options.outputJson;
2659
- }
2660
- if (typeof resolved.diff === "string") {
2661
- resolved.diff = resolvePath(resolved.diff, resolved.root);
2662
- resolved.forceRerunTriggers.push(resolved.diff);
2663
- }
2664
- resolved.api = {
2665
- ...resolveApiServerConfig(options, defaultPort),
2666
- token: crypto.randomUUID()
2667
- };
2668
- if (options.related) resolved.related = toArray(options.related).map((file) => resolve(resolved.root, file));
2669
- /*
2670
- * Reporters can be defined in many different ways:
2671
- * { reporter: 'json' }
2672
- * { reporter: { onFinish() { method() } } }
2673
- * { reporter: ['json', { onFinish() { method() } }] }
2674
- * { reporter: [[ 'json' ]] }
2675
- * { reporter: [[ 'json' ], 'html'] }
2676
- * { reporter: [[ 'json', { outputFile: 'test.json' } ], 'html'] }
2677
- */
2678
- if (options.reporters) if (!Array.isArray(options.reporters))
2679
- // Reporter name, e.g. { reporters: 'json' }
2680
- if (typeof options.reporters === "string") resolved.reporters = [[options.reporters, {}]];
2681
- else resolved.reporters = [options.reporters];
2682
- else {
2683
- resolved.reporters = [];
2684
- for (const reporter of options.reporters) if (Array.isArray(reporter))
2685
- // Reporter with options, e.g. { reporters: [ [ 'json', { outputFile: 'test.json' } ] ] }
2686
- resolved.reporters.push([reporter[0], reporter[1] || {}]);
2687
- else if (typeof reporter === "string")
2688
- // Reporter name in array, e.g. { reporters: ["html", "json"]}
2689
- resolved.reporters.push([reporter, {}]);
2690
- else
2691
- // Inline reporter, e.g. { reporter: [{ onFinish() { method() } }] }
2692
- resolved.reporters.push(reporter);
2693
- }
2694
- if (mode !== "benchmark") {
2695
- // @ts-expect-error "reporter" is from CLI, should be absolute to the running directory
2696
- // it is passed down as "vitest --reporter ../reporter.js"
2697
- const reportersFromCLI = resolved.reporter;
2698
- const cliReporters = toArray(reportersFromCLI || []).map((reporter) => {
2699
- // ./reporter.js || ../reporter.js, but not .reporters/reporter.js
2700
- if (/^\.\.?\//.test(reporter)) return resolve(process.cwd(), reporter);
2701
- return reporter;
2702
- });
2703
- if (cliReporters.length) {
2704
- // When CLI reporters are specified, preserve options from config file
2705
- const configReportersMap = /* @__PURE__ */ new Map();
2706
- // Build a map of reporter names to their options from the config
2707
- for (const reporter of resolved.reporters) if (Array.isArray(reporter)) {
2708
- const [reporterName, reporterOptions] = reporter;
2709
- if (typeof reporterName === "string") configReportersMap.set(reporterName, reporterOptions);
2710
- }
2711
- resolved.reporters = Array.from(new Set(toArray(cliReporters))).filter(Boolean).map((reporter) => [reporter, configReportersMap.get(reporter) || {}]);
2712
- }
2713
- }
2714
- if (!resolved.reporters.length) {
2715
- resolved.reporters.push(["default", {}]);
2716
- // also enable github-actions reporter as a default
2717
- if (process.env.GITHUB_ACTIONS === "true") resolved.reporters.push(["github-actions", {}]);
2718
- }
2719
- if (resolved.changed) resolved.passWithNoTests ??= true;
2720
- resolved.css ??= {};
2721
- if (typeof resolved.css === "object") {
2722
- resolved.css.modules ??= {};
2723
- resolved.css.modules.classNameStrategy ??= "stable";
2724
- }
2725
- if (resolved.cache !== false) {
2726
- if (resolved.cache && typeof resolved.cache.dir === "string") vitest.logger.deprecate(`"cache.dir" is deprecated, use Vite's "cacheDir" instead if you want to change the cache director. Note caches will be written to "cacheDir\/vitest"`);
2727
- resolved.cache = { dir: viteConfig.cacheDir };
2728
- }
2729
- resolved.sequence ??= {};
2730
- if (resolved.sequence.shuffle && typeof resolved.sequence.shuffle === "object") {
2731
- const { files, tests } = resolved.sequence.shuffle;
2732
- resolved.sequence.sequencer ??= files ? RandomSequencer : BaseSequencer;
2733
- resolved.sequence.shuffle = tests;
2734
- }
2735
- if (!resolved.sequence?.sequencer)
2736
- // CLI flag has higher priority
2737
- resolved.sequence.sequencer = resolved.sequence.shuffle ? RandomSequencer : BaseSequencer;
2738
- resolved.sequence.groupOrder ??= 0;
2739
- resolved.sequence.hooks ??= "stack";
2740
- if (resolved.sequence.sequencer === RandomSequencer) resolved.sequence.seed ??= Date.now();
2741
- resolved.typecheck = {
2742
- ...configDefaults.typecheck,
2743
- ...resolved.typecheck
2744
- };
2745
- resolved.typecheck ??= {};
2746
- resolved.typecheck.enabled ??= false;
2747
- if (resolved.typecheck.enabled) logger.console.warn(c.yellow("Testing types with tsc and vue-tsc is an experimental feature.\nBreaking changes might not follow SemVer, please pin Vitest's version when using it."));
2748
- resolved.browser.enabled ??= false;
2749
- resolved.browser.headless ??= isCI;
2750
- resolved.browser.isolate ??= resolved.isolate ?? true;
2751
- resolved.browser.fileParallelism ??= options.fileParallelism ?? mode !== "benchmark";
2752
- // disable in headless mode by default, and if CI is detected
2753
- resolved.browser.ui ??= resolved.browser.headless === true ? false : !isCI;
2754
- resolved.browser.commands ??= {};
2755
- if (resolved.browser.screenshotDirectory) resolved.browser.screenshotDirectory = resolve(resolved.root, resolved.browser.screenshotDirectory);
2756
- if (resolved.inspector.enabled) resolved.browser.trackUnhandledErrors ??= false;
2757
- resolved.browser.viewport ??= {};
2758
- resolved.browser.viewport.width ??= 414;
2759
- resolved.browser.viewport.height ??= 896;
2760
- resolved.browser.locators ??= {};
2761
- resolved.browser.locators.testIdAttribute ??= "data-testid";
2762
- if (typeof resolved.browser.provider === "string") {
2763
- const source = `@vitest/browser-${resolved.browser.provider}`;
2764
- throw new TypeError(`The \`browser.provider\` configuration was changed to accept a factory instead of a string. Add an import of "${resolved.browser.provider}" from "${source}" instead. See: https://vitest.dev/config/browser/provider`);
2765
- }
2766
- const isPreview = resolved.browser.provider?.name === "preview";
2767
- if (!isPreview && resolved.browser.enabled && provider === "stackblitz") throw new Error(`stackblitz environment does not support the ${resolved.browser.provider?.name} provider. Please, use "@vitest/browser-preview" instead.`);
2768
- if (isPreview && resolved.browser.screenshotFailures === true) {
2769
- console.warn(c.yellow([
2770
- `Browser provider "preview" doesn't support screenshots, `,
2771
- `so "browser.screenshotFailures" option is forcefully disabled. `,
2772
- `Set "browser.screenshotFailures" to false or remove it from the config to suppress this warning.`
2773
- ].join("")));
2774
- resolved.browser.screenshotFailures = false;
2775
- } else resolved.browser.screenshotFailures ??= !isPreview && !resolved.browser.ui;
2776
- if (resolved.browser.provider && resolved.browser.provider.options == null) resolved.browser.provider.options = {};
2777
- resolved.browser.api = resolveApiServerConfig(resolved.browser, defaultBrowserPort) || { port: defaultBrowserPort };
2778
- // enable includeTaskLocation by default in UI mode
2779
- if (resolved.browser.enabled) {
2780
- if (resolved.browser.ui) resolved.includeTaskLocation ??= true;
2781
- } else if (resolved.ui) resolved.includeTaskLocation ??= true;
2782
- if (typeof resolved.browser.trace === "string" || !resolved.browser.trace) resolved.browser.trace = { mode: resolved.browser.trace || "off" };
2783
- if (resolved.browser.trace.tracesDir != null) resolved.browser.trace.tracesDir = resolvePath(resolved.browser.trace.tracesDir, resolved.root);
2784
- if (toArray(resolved.reporters).some((reporter) => {
2785
- if (Array.isArray(reporter)) return reporter[0] === "html";
2786
- return false;
2787
- })) resolved.includeTaskLocation ??= true;
2788
- resolved.server ??= {};
2789
- resolved.server.deps ??= {};
2790
- if (resolved.server.debug?.dump || process.env.VITEST_DEBUG_DUMP) {
2791
- const userFolder = resolved.server.debug?.dump || process.env.VITEST_DEBUG_DUMP;
2792
- resolved.dumpDir = resolve(resolved.root, typeof userFolder === "string" && userFolder !== "true" ? userFolder : ".vitest-dump", resolved.name || "root");
2793
- }
2794
- resolved.testTimeout ??= resolved.browser.enabled ? 15e3 : 5e3;
2795
- resolved.hookTimeout ??= resolved.browser.enabled ? 3e4 : 1e4;
2796
- resolved.experimental ??= {};
2797
- if (resolved.experimental.openTelemetry?.sdkPath) {
2798
- const sdkPath = resolve(resolved.root, resolved.experimental.openTelemetry.sdkPath);
2799
- resolved.experimental.openTelemetry.sdkPath = pathToFileURL$1(sdkPath).toString();
2800
- }
2801
- if (resolved.experimental.openTelemetry?.browserSdkPath) {
2802
- const browserSdkPath = resolve(resolved.root, resolved.experimental.openTelemetry.browserSdkPath);
2803
- resolved.experimental.openTelemetry.browserSdkPath = browserSdkPath;
2804
- }
2805
- if (resolved.experimental.fsModuleCachePath) resolved.experimental.fsModuleCachePath = resolve(resolved.root, resolved.experimental.fsModuleCachePath);
2806
- return resolved;
2807
- }
2808
- function isBrowserEnabled(config) {
2809
- return Boolean(config.browser?.enabled);
2810
- }
2811
- function resolveCoverageReporters(configReporters) {
2812
- // E.g. { reporter: "html" }
2813
- if (!Array.isArray(configReporters)) return [[configReporters, {}]];
2814
- const resolvedReporters = [];
2815
- for (const reporter of configReporters) if (Array.isArray(reporter))
2816
- // E.g. { reporter: [ ["html", { skipEmpty: true }], ["lcov"], ["json", { file: "map.json" }] ]}
2817
- resolvedReporters.push([reporter[0], reporter[1] || {}]);
2818
- else
2819
- // E.g. { reporter: ["html", "json"]}
2820
- resolvedReporters.push([reporter, {}]);
2821
- return resolvedReporters;
2822
- }
2823
- function isChromiumName(provider, name) {
2824
- if (provider === "playwright") return name === "chromium";
2825
- return name === "chrome" || name === "edge";
2826
- }
2827
- function hasBrowserChromium(vitest, config) {
2828
- const browser = config.browser;
2829
- if (!browser || !browser.provider || browser.provider.name === "preview" || !browser.enabled) return false;
2830
- if (browser.name) return isChromiumName(browser.provider.name, browser.name);
2831
- if (!browser.instances) return false;
2832
- return browser.instances.some((instance) => {
2833
- const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser);
2834
- // browser config is filtered out
2835
- if (!vitest.matchesProjectFilter(name)) return false;
2836
- return isChromiumName(browser.provider.name, instance.browser);
2837
- });
2838
- }
2839
- function hasOnlyBrowserChromium(vitest, config) {
2840
- const browser = config.browser;
2841
- if (!browser || !browser.provider || browser.provider.name === "preview" || !browser.enabled) return false;
2842
- if (browser.name) return isChromiumName(browser.provider.name, browser.name);
2843
- if (!browser.instances) return false;
2844
- return browser.instances.every((instance) => {
2845
- const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser);
2846
- // browser config is filtered out
2847
- if (!vitest.matchesProjectFilter(name)) return true;
2848
- return isChromiumName(browser.provider.name, instance.browser);
2849
- });
2850
- }
2851
-
2852
- const THRESHOLD_KEYS = [
2853
- "lines",
2854
- "functions",
2855
- "statements",
2856
- "branches"
2857
- ];
2858
- const GLOBAL_THRESHOLDS_KEY = "global";
2859
- const DEFAULT_PROJECT = Symbol.for("default-project");
2860
- let uniqueId = 0;
2861
- async function getCoverageProvider(options, loader) {
2862
- const coverageModule = await resolveCoverageProviderModule(options, loader);
2863
- if (coverageModule) return coverageModule.getProvider();
2864
- return null;
2865
- }
2866
- class BaseCoverageProvider {
2867
- ctx;
2868
- name;
2869
- version;
2870
- options;
2871
- globCache = /* @__PURE__ */ new Map();
2872
- coverageFiles = /* @__PURE__ */ new Map();
2873
- pendingPromises = [];
2874
- coverageFilesDirectory;
2875
- roots = [];
2876
- _initialize(ctx) {
2877
- this.ctx = ctx;
2878
- if (ctx.version !== this.version) ctx.logger.warn(c.yellow(`Loaded ${c.inverse(c.yellow(` vitest@${ctx.version} `))} and ${c.inverse(c.yellow(` @vitest/coverage-${this.name}@${this.version} `))}.
2879
- Running mixed versions is not supported and may lead into bugs
2880
- Update your dependencies and make sure the versions match.`));
2881
- const config = ctx._coverageOptions;
2882
- this.options = {
2883
- ...coverageConfigDefaults,
2884
- ...config,
2885
- provider: this.name,
2886
- reportsDirectory: resolve(ctx.config.root, config.reportsDirectory || coverageConfigDefaults.reportsDirectory),
2887
- reporter: resolveCoverageReporters(config.reporter || coverageConfigDefaults.reporter),
2888
- thresholds: config.thresholds && {
2889
- ...config.thresholds,
2890
- lines: config.thresholds["100"] ? 100 : config.thresholds.lines,
2891
- branches: config.thresholds["100"] ? 100 : config.thresholds.branches,
2892
- functions: config.thresholds["100"] ? 100 : config.thresholds.functions,
2893
- statements: config.thresholds["100"] ? 100 : config.thresholds.statements
2894
- }
2895
- };
2896
- const shard = this.ctx.config.shard;
2897
- const tempDirectory = `.tmp${shard ? `-${shard.index}-${shard.count}` : ""}`;
2898
- this.coverageFilesDirectory = resolve(this.options.reportsDirectory, tempDirectory);
2899
- // If --project filter is set pick only roots of resolved projects
2900
- this.roots = ctx.config.project?.length ? [...new Set(ctx.projects.map((project) => project.config.root))] : [ctx.config.root];
2901
- }
2902
- /**
2903
- * Check if file matches `coverage.include` but not `coverage.exclude`
2904
- */
2905
- isIncluded(_filename, root) {
2906
- const roots = root ? [root] : this.roots;
2907
- const filename = slash(_filename);
2908
- const cacheHit = this.globCache.get(filename);
2909
- if (cacheHit !== void 0) return cacheHit;
2910
- // File outside project root with default allowExternal
2911
- if (this.options.allowExternal === false && roots.every((root) => !filename.startsWith(root))) {
2912
- this.globCache.set(filename, false);
2913
- return false;
2914
- }
2915
- // By default `coverage.include` matches all files, except "coverage.exclude"
2916
- const glob = this.options.include || "**";
2917
- const included = pm.isMatch(filename, glob, {
2918
- contains: true,
2919
- dot: true,
2920
- ignore: this.options.exclude
2921
- });
2922
- this.globCache.set(filename, included);
2923
- return included;
2924
- }
2925
- async getUntestedFilesByRoot(testedFiles, include, root) {
2926
- let includedFiles = await glob(include, {
2927
- cwd: root,
2928
- ignore: [...this.options.exclude, ...testedFiles.map((file) => slash(file))],
2929
- absolute: true,
2930
- dot: true,
2931
- onlyFiles: true
2932
- });
2933
- // Run again through picomatch as tinyglobby's exclude pattern is different ({ "exclude": ["math"] } should ignore "src/math.ts")
2934
- includedFiles = includedFiles.filter((file) => this.isIncluded(file, root));
2935
- if (this.ctx.config.changed) includedFiles = (this.ctx.config.related || []).filter((file) => includedFiles.includes(file));
2936
- return includedFiles.map((file) => slash(path.resolve(root, file)));
2937
- }
2938
- async getUntestedFiles(testedFiles) {
2939
- if (this.options.include == null) return [];
2940
- const rootMapper = this.getUntestedFilesByRoot.bind(this, testedFiles, this.options.include);
2941
- return (await Promise.all(this.roots.map(rootMapper))).flatMap((files) => files);
2942
- }
2943
- createCoverageMap() {
2944
- throw new Error("BaseReporter's createCoverageMap was not overwritten");
2945
- }
2946
- async generateReports(_, __) {
2947
- throw new Error("BaseReporter's generateReports was not overwritten");
2948
- }
2949
- async parseConfigModule(_) {
2950
- throw new Error("BaseReporter's parseConfigModule was not overwritten");
2951
- }
2952
- resolveOptions() {
2953
- return this.options;
2954
- }
2955
- async clean(clean = true) {
2956
- if (clean && existsSync(this.options.reportsDirectory)) await promises.rm(this.options.reportsDirectory, {
2957
- recursive: true,
2958
- force: true,
2959
- maxRetries: 10
2960
- });
2961
- if (existsSync(this.coverageFilesDirectory)) await promises.rm(this.coverageFilesDirectory, {
2962
- recursive: true,
2963
- force: true,
2964
- maxRetries: 10
2965
- });
2966
- await promises.mkdir(this.coverageFilesDirectory, { recursive: true });
2967
- this.coverageFiles = /* @__PURE__ */ new Map();
2968
- this.pendingPromises = [];
2969
- }
2970
- onAfterSuiteRun({ coverage, environment, projectName, testFiles }) {
2971
- if (!coverage) return;
2972
- let entry = this.coverageFiles.get(projectName || DEFAULT_PROJECT);
2973
- if (!entry) {
2974
- entry = {};
2975
- this.coverageFiles.set(projectName || DEFAULT_PROJECT, entry);
2976
- }
2977
- const testFilenames = testFiles.join();
2978
- const filename = resolve(this.coverageFilesDirectory, `coverage-${uniqueId++}.json`);
2979
- entry[environment] ??= {};
2980
- // If there's a result from previous run, overwrite it
2981
- entry[environment][testFilenames] = filename;
2982
- const promise = promises.writeFile(filename, JSON.stringify(coverage), "utf-8");
2983
- this.pendingPromises.push(promise);
2984
- }
2985
- async readCoverageFiles({ onFileRead, onFinished, onDebug }) {
2986
- let index = 0;
2987
- const total = this.pendingPromises.length;
2988
- await Promise.all(this.pendingPromises);
2989
- this.pendingPromises = [];
2990
- for (const [projectName, coveragePerProject] of this.coverageFiles.entries()) for (const [environment, coverageByTestfiles] of Object.entries(coveragePerProject)) {
2991
- const filenames = Object.values(coverageByTestfiles);
2992
- const project = this.ctx.getProjectByName(projectName);
2993
- for (const chunk of this.toSlices(filenames, this.options.processingConcurrency)) {
2994
- if (onDebug.enabled) {
2995
- index += chunk.length;
2996
- onDebug(`Reading coverage results ${index}/${total}`);
2997
- }
2998
- await Promise.all(chunk.map(async (filename) => {
2999
- const contents = await promises.readFile(filename, "utf-8");
3000
- onFileRead(JSON.parse(contents));
3001
- }));
3002
- }
3003
- await onFinished(project, environment);
3004
- }
3005
- }
3006
- async cleanAfterRun() {
3007
- this.coverageFiles = /* @__PURE__ */ new Map();
3008
- await promises.rm(this.coverageFilesDirectory, { recursive: true });
3009
- // Remove empty reports directory, e.g. when only text-reporter is used
3010
- if (readdirSync(this.options.reportsDirectory).length === 0) await promises.rm(this.options.reportsDirectory, { recursive: true });
3011
- }
3012
- async onTestFailure() {
3013
- if (!this.options.reportOnFailure) await this.cleanAfterRun();
3014
- }
3015
- async reportCoverage(coverageMap, { allTestsRun }) {
3016
- await this.generateReports(coverageMap || this.createCoverageMap(), allTestsRun);
3017
- if (!(!this.options.cleanOnRerun && this.ctx.config.watch)) await this.cleanAfterRun();
3018
- }
3019
- async reportThresholds(coverageMap, allTestsRun) {
3020
- const resolvedThresholds = this.resolveThresholds(coverageMap);
3021
- this.checkThresholds(resolvedThresholds);
3022
- if (this.options.thresholds?.autoUpdate && allTestsRun) {
3023
- if (!this.ctx.vite.config.configFile) throw new Error("Missing configurationFile. The \"coverage.thresholds.autoUpdate\" can only be enabled when configuration file is used.");
3024
- const configFilePath = this.ctx.vite.config.configFile;
3025
- const configModule = await this.parseConfigModule(configFilePath);
3026
- await this.updateThresholds({
3027
- thresholds: resolvedThresholds,
3028
- configurationFile: configModule,
3029
- onUpdate: () => writeFileSync(configFilePath, configModule.generate().code, "utf-8")
3030
- });
3031
- }
3032
- }
3033
- /**
3034
- * Constructs collected coverage and users' threshold options into separate sets
3035
- * where each threshold set holds their own coverage maps. Threshold set is either
3036
- * for specific files defined by glob pattern or global for all other files.
3037
- */
3038
- resolveThresholds(coverageMap) {
3039
- const resolvedThresholds = [];
3040
- const files = coverageMap.files();
3041
- const globalCoverageMap = this.createCoverageMap();
3042
- for (const key of Object.keys(this.options.thresholds)) {
3043
- if (key === "perFile" || key === "autoUpdate" || key === "100" || THRESHOLD_KEYS.includes(key)) continue;
3044
- const glob = key;
3045
- const globThresholds = resolveGlobThresholds(this.options.thresholds[glob]);
3046
- const globCoverageMap = this.createCoverageMap();
3047
- const matcher = pm(glob);
3048
- const matchingFiles = files.filter((file) => matcher(relative(this.ctx.config.root, file)));
3049
- for (const file of matchingFiles) {
3050
- const fileCoverage = coverageMap.fileCoverageFor(file);
3051
- globCoverageMap.addFileCoverage(fileCoverage);
3052
- }
3053
- resolvedThresholds.push({
3054
- name: glob,
3055
- coverageMap: globCoverageMap,
3056
- thresholds: globThresholds
3057
- });
3058
- }
3059
- // Global threshold is for all files, even if they are included by glob patterns
3060
- for (const file of files) {
3061
- const fileCoverage = coverageMap.fileCoverageFor(file);
3062
- globalCoverageMap.addFileCoverage(fileCoverage);
3063
- }
3064
- resolvedThresholds.unshift({
3065
- name: GLOBAL_THRESHOLDS_KEY,
3066
- coverageMap: globalCoverageMap,
3067
- thresholds: {
3068
- branches: this.options.thresholds?.branches,
3069
- functions: this.options.thresholds?.functions,
3070
- lines: this.options.thresholds?.lines,
3071
- statements: this.options.thresholds?.statements
3072
- }
3073
- });
3074
- return resolvedThresholds;
3075
- }
3076
- /**
3077
- * Check collected coverage against configured thresholds. Sets exit code to 1 when thresholds not reached.
3078
- */
3079
- checkThresholds(allThresholds) {
3080
- for (const { coverageMap, thresholds, name } of allThresholds) {
3081
- if (thresholds.branches === void 0 && thresholds.functions === void 0 && thresholds.lines === void 0 && thresholds.statements === void 0) continue;
3082
- // Construct list of coverage summaries where thresholds are compared against
3083
- const summaries = this.options.thresholds?.perFile ? coverageMap.files().map((file) => ({
3084
- file,
3085
- summary: coverageMap.fileCoverageFor(file).toSummary()
3086
- })) : [{
3087
- file: null,
3088
- summary: coverageMap.getCoverageSummary()
3089
- }];
3090
- // Check thresholds of each summary
3091
- for (const { summary, file } of summaries) for (const thresholdKey of THRESHOLD_KEYS) {
3092
- const threshold = thresholds[thresholdKey];
3093
- if (threshold === void 0) continue;
3094
- /**
3095
- * Positive thresholds are treated as minimum coverage percentages (X means: X% of lines must be covered),
3096
- * while negative thresholds are treated as maximum uncovered counts (-X means: X lines may be uncovered).
3097
- */
3098
- if (threshold >= 0) {
3099
- const coverage = summary.data[thresholdKey].pct;
3100
- if (coverage < threshold) {
3101
- process.exitCode = 1;
3102
- /**
3103
- * Generate error message based on perFile flag:
3104
- * - ERROR: Coverage for statements (33.33%) does not meet threshold (85%) for src/math.ts
3105
- * - ERROR: Coverage for statements (50%) does not meet global threshold (85%)
3106
- */
3107
- let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet ${name === GLOBAL_THRESHOLDS_KEY ? name : `"${name}"`} threshold (${threshold}%)`;
3108
- if (this.options.thresholds?.perFile && file) errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
3109
- this.ctx.logger.error(errorMessage);
3110
- }
3111
- } else {
3112
- const uncovered = summary.data[thresholdKey].total - summary.data[thresholdKey].covered;
3113
- const absoluteThreshold = threshold * -1;
3114
- if (uncovered > absoluteThreshold) {
3115
- process.exitCode = 1;
3116
- /**
3117
- * Generate error message based on perFile flag:
3118
- * - ERROR: Uncovered statements (33) exceed threshold (30) for src/math.ts
3119
- * - ERROR: Uncovered statements (33) exceed global threshold (30)
3120
- */
3121
- let errorMessage = `ERROR: Uncovered ${thresholdKey} (${uncovered}) exceed ${name === GLOBAL_THRESHOLDS_KEY ? name : `"${name}"`} threshold (${absoluteThreshold})`;
3122
- if (this.options.thresholds?.perFile && file) errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
3123
- this.ctx.logger.error(errorMessage);
3124
- }
3125
- }
3126
- }
3127
- }
3128
- }
3129
- /**
3130
- * Check if current coverage is above configured thresholds and bump the thresholds if needed
3131
- */
3132
- async updateThresholds({ thresholds: allThresholds, onUpdate, configurationFile }) {
3133
- let updatedThresholds = false;
3134
- const config = resolveConfig(configurationFile);
3135
- assertConfigurationModule(config);
3136
- for (const { coverageMap, thresholds, name } of allThresholds) {
3137
- const summaries = this.options.thresholds?.perFile ? coverageMap.files().map((file) => coverageMap.fileCoverageFor(file).toSummary()) : [coverageMap.getCoverageSummary()];
3138
- const thresholdsToUpdate = [];
3139
- for (const key of THRESHOLD_KEYS) {
3140
- const threshold = thresholds[key] ?? 100;
3141
- /**
3142
- * Positive thresholds are treated as minimum coverage percentages (X means: X% of lines must be covered),
3143
- * while negative thresholds are treated as maximum uncovered counts (-X means: X lines may be uncovered).
3144
- */
3145
- if (threshold >= 0) {
3146
- const actual = Math.min(...summaries.map((summary) => summary[key].pct));
3147
- if (actual > threshold) thresholdsToUpdate.push([key, actual]);
3148
- } else {
3149
- const absoluteThreshold = threshold * -1;
3150
- const actual = Math.max(...summaries.map((summary) => summary[key].total - summary[key].covered));
3151
- if (actual < absoluteThreshold) {
3152
- // If everything was covered, set new threshold to 100% (since a threshold of 0 would be considered as 0%)
3153
- const updatedThreshold = actual === 0 ? 100 : actual * -1;
3154
- thresholdsToUpdate.push([key, updatedThreshold]);
3155
- }
3156
- }
3157
- }
3158
- if (thresholdsToUpdate.length === 0) continue;
3159
- updatedThresholds = true;
3160
- const thresholdFormatter = typeof this.options.thresholds?.autoUpdate === "function" ? this.options.thresholds?.autoUpdate : (value) => value;
3161
- for (const [threshold, newValue] of thresholdsToUpdate) {
3162
- const formattedValue = thresholdFormatter(newValue);
3163
- if (name === GLOBAL_THRESHOLDS_KEY) config.test.coverage.thresholds[threshold] = formattedValue;
3164
- else {
3165
- const glob = config.test.coverage.thresholds[name];
3166
- glob[threshold] = formattedValue;
3167
- }
3168
- }
3169
- }
3170
- if (updatedThresholds) {
3171
- this.ctx.logger.log("Updating thresholds to configuration file. You may want to push with updated coverage thresholds.");
3172
- onUpdate();
3173
- }
3174
- }
3175
- async mergeReports(coverageMaps) {
3176
- const coverageMap = this.createCoverageMap();
3177
- for (const coverage of coverageMaps) coverageMap.merge(coverage);
3178
- await this.generateReports(coverageMap, true);
3179
- }
3180
- hasTerminalReporter(reporters) {
3181
- return reporters.some(([reporter]) => reporter === "text" || reporter === "text-summary" || reporter === "text-lcov" || reporter === "teamcity");
3182
- }
3183
- toSlices(array, size) {
3184
- return array.reduce((chunks, item) => {
3185
- const index = Math.max(0, chunks.length - 1);
3186
- const lastChunk = chunks[index] || [];
3187
- chunks[index] = lastChunk;
3188
- if (lastChunk.length >= size) chunks.push([item]);
3189
- else lastChunk.push(item);
3190
- return chunks;
3191
- }, []);
3192
- }
3193
- createUncoveredFileTransformer(ctx) {
3194
- const servers = [...ctx.projects.map((project) => ({
3195
- root: project.config.root,
3196
- isBrowserEnabled: project.isBrowserEnabled(),
3197
- vite: project.vite
3198
- })), (
3199
- // Check core last as it will match all files anyway
3200
- {
3201
- root: ctx.config.root,
3202
- vite: ctx.vite,
3203
- isBrowserEnabled: ctx.getRootProject().isBrowserEnabled()
3204
- })];
3205
- return async function transformFile(filename) {
3206
- let lastError;
3207
- for (const { root, vite, isBrowserEnabled } of servers) {
3208
- // On Windows root doesn't start with "/" while filenames do
3209
- if (!filename.startsWith(root) && !filename.startsWith(`/${root}`)) continue;
3210
- if (isBrowserEnabled) {
3211
- const result = await vite.environments.client.transformRequest(filename).catch(() => null);
3212
- if (result) return result;
3213
- }
3214
- try {
3215
- return await vite.environments.ssr.transformRequest(filename);
3216
- } catch (error) {
3217
- lastError = error;
3218
- }
3219
- }
3220
- // All vite-node servers failed to transform the file
3221
- throw lastError;
3222
- };
3223
- }
3224
- }
3225
- /**
3226
- * Narrow down `unknown` glob thresholds to resolved ones
3227
- */
3228
- function resolveGlobThresholds(thresholds) {
3229
- if (!thresholds || typeof thresholds !== "object") return {};
3230
- if (100 in thresholds && thresholds[100] === true) return {
3231
- lines: 100,
3232
- branches: 100,
3233
- functions: 100,
3234
- statements: 100
3235
- };
3236
- return {
3237
- lines: "lines" in thresholds && typeof thresholds.lines === "number" ? thresholds.lines : void 0,
3238
- branches: "branches" in thresholds && typeof thresholds.branches === "number" ? thresholds.branches : void 0,
3239
- functions: "functions" in thresholds && typeof thresholds.functions === "number" ? thresholds.functions : void 0,
3240
- statements: "statements" in thresholds && typeof thresholds.statements === "number" ? thresholds.statements : void 0
3241
- };
3242
- }
3243
- function assertConfigurationModule(config) {
3244
- try {
3245
- // @ts-expect-error -- Intentional unsafe null pointer check as wrapped in try-catch
3246
- if (typeof config.test.coverage.thresholds !== "object") throw new TypeError("Expected config.test.coverage.thresholds to be an object");
3247
- } catch (error) {
3248
- const message = error instanceof Error ? error.message : String(error);
3249
- throw new Error(`Unable to parse thresholds from configuration file: ${message}`);
3250
- }
3251
- }
3252
- function resolveConfig(configModule) {
3253
- const mod = configModule.exports.default;
3254
- try {
3255
- // Check for "export default { test: {...} }"
3256
- if (mod.$type === "object") return mod;
3257
- // "export default defineConfig(...)"
3258
- let config = resolveDefineConfig(mod);
3259
- if (config) return config;
3260
- // "export default mergeConfig(..., defineConfig(...))"
3261
- if (mod.$type === "function-call" && mod.$callee === "mergeConfig") {
3262
- config = resolveMergeConfig(mod);
3263
- if (config) return config;
3264
- }
3265
- } catch (error) {
3266
- // Reduce magicast's verbose errors to readable ones
3267
- throw new Error(error instanceof Error ? error.message : String(error));
3268
- }
3269
- throw new Error("Failed to update coverage thresholds. Configuration file is too complex.");
3270
- }
3271
- function resolveDefineConfig(mod) {
3272
- if (mod.$type === "function-call" && mod.$callee === "defineConfig") {
3273
- // "export default defineConfig({ test: {...} })"
3274
- if (mod.$args[0].$type === "object") return mod.$args[0];
3275
- if (mod.$args[0].$type === "arrow-function-expression") {
3276
- if (mod.$args[0].$body.$type === "object")
3277
- // "export default defineConfig(() => ({ test: {...} }))"
3278
- return mod.$args[0].$body;
3279
- // "export default defineConfig(() => mergeConfig({...}, ...))"
3280
- const config = resolveMergeConfig(mod.$args[0].$body);
3281
- if (config) return config;
3282
- }
3283
- }
3284
- }
3285
- function resolveMergeConfig(mod) {
3286
- if (mod.$type === "function-call" && mod.$callee === "mergeConfig") for (const arg of mod.$args) {
3287
- const config = resolveDefineConfig(arg);
3288
- if (config) return config;
3289
- }
3290
- }
3291
-
3292
- export { BaseCoverageProvider as B, RandomSequencer as R, resolveApiServerConfig as a, BaseSequencer as b, isBrowserEnabled as c, resolveModule as d, getCoverageProvider as g, hash as h, isPackageExists as i, resolveConfig$1 as r };
2338
+ export { isPackageExists as i, resolveModule as r };