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

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