vitest 4.1.5 → 5.0.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 (80) hide show
  1. package/LICENSE.md +7 -0
  2. package/dist/browser.d.ts +9 -9
  3. package/dist/browser.js +4 -4
  4. package/dist/chunks/{base.RR7zL1h0.js → base.Opc_YHkk.js} +10 -11
  5. package/dist/chunks/browser.d.BUhkKcDl.d.ts +899 -0
  6. package/dist/chunks/{cac.DJJmV0dT.js → cac.8N4bOkkB.js} +23 -11
  7. package/dist/chunks/{cli-api.Cjt90eJu.js → cli-api.B0RFke2g.js} +5799 -353
  8. package/dist/chunks/{config.d.A1h_Y6Jt.d.ts → config.d.D91DHYaD.d.ts} +11 -3
  9. package/dist/chunks/{console.3WNpx0tS.js → console.B3IRP8fX.js} +3 -1
  10. package/dist/chunks/{constants.CPYnjOGj.js → constants.-juJ8b_4.js} +1 -1
  11. package/dist/chunks/{coverage.d.BZtK59WP.d.ts → coverage.d.g2xbl2sP.d.ts} +4 -0
  12. package/dist/chunks/{creator.DgVhQm5q.js → creator.BqL2U_x4.js} +1 -1
  13. package/dist/chunks/{defaults.9aQKnqFk.js → defaults.szbHWQun.js} +4 -2
  14. package/dist/chunks/environment.d-DOJxxZV9.d.DOJxxZV9.d.ts +17 -0
  15. package/dist/chunks/general.d.DFAHgpC2.d.ts +247 -0
  16. package/dist/chunks/{global.d.DVsSRdQ5.d.ts → global.d.DhbKSQoV.d.ts} +4 -5
  17. package/dist/chunks/{globals.Dj1TGiMC.js → globals.EHmmu0nC.js} +15 -14
  18. package/dist/chunks/{index.DXx9Dtk7.js → index.CViWo__T.js} +5 -5
  19. package/dist/chunks/{startVitestModuleRunner.bRl2_oI_.js → index.CbgUM9E5.js} +731 -5
  20. package/dist/chunks/{test.DNmyFkvJ.js → index.D_7-4CaB.js} +2659 -14
  21. package/dist/chunks/{init-forks.UV3ZQGQH.js → init-forks.DMge3WTt.js} +1 -1
  22. package/dist/chunks/{init-threads.D3eCsY76.js → init-threads.eIoyCTon.js} +1 -1
  23. package/dist/chunks/{init.D98-gwRW.js → init.BVd7SaCA.js} +3 -5
  24. package/dist/chunks/{nativeModuleMocker.BRN2oBJd.js → nativeModuleMocker.DKpFw0pk.js} +3 -2
  25. package/dist/chunks/{index.BCY_7LL2.js → nativeModuleRunner.BOeMnHl4.js} +43 -12
  26. package/dist/chunks/node.CwFbQqI1.js +47 -0
  27. package/dist/chunks/{reporters.d.CEnv6XRv.d.ts → plugin.d.cIKZEZ16.d.ts} +306 -19
  28. package/dist/chunks/plugins.DrsmdUE2.js +37 -0
  29. package/dist/chunks/{rpc.MzXet3jl.js → rpc.DFRWVnRh.js} +16 -1
  30. package/dist/chunks/{rpc.d.B_8sPU0w.d.ts → rpc.d.7JZuxZ8u.d.ts} +19 -3
  31. package/dist/chunks/{setup-common.DYx3LtFI.js → setup-common.Hpq30zVk.js} +7 -3
  32. package/dist/chunks/{utils.BS4fH3nR.js → utils.DKODp04v.js} +3 -4
  33. package/dist/chunks/{vm.DVLYObm9.js → vm.2okbRRME.js} +6 -6
  34. package/dist/chunks/{worker.d.ZpHpO4yb.d.ts → worker.d.Bu1kXGw4.d.ts} +3 -3
  35. package/dist/cli.js +2 -2
  36. package/dist/config.cjs +4 -2
  37. package/dist/config.d.ts +21 -18
  38. package/dist/config.js +2 -2
  39. package/dist/index.d.ts +84 -22
  40. package/dist/index.js +15 -13
  41. package/dist/module-evaluator.d.ts +5 -3
  42. package/dist/module-evaluator.js +1 -1
  43. package/dist/node.d.ts +114 -19
  44. package/dist/node.js +21 -26
  45. package/dist/runtime.d.ts +40 -4
  46. package/dist/runtime.js +5 -6
  47. package/dist/{chunks/traces.DT5aQ62U.js → traces.js} +1 -1
  48. package/dist/worker.d.ts +5 -5
  49. package/dist/worker.js +21 -23
  50. package/dist/workers/forks.js +21 -23
  51. package/dist/workers/runVmTests.js +17 -16
  52. package/dist/workers/threads.js +21 -23
  53. package/dist/workers/vmForks.js +7 -9
  54. package/dist/workers/vmThreads.js +7 -9
  55. package/package.json +21 -38
  56. package/dist/chunks/benchmark.CX_oY03V.js +0 -40
  57. package/dist/chunks/benchmark.d.DAaHLpsq.d.ts +0 -24
  58. package/dist/chunks/browser.d.BcoexmFG.d.ts +0 -62
  59. package/dist/chunks/coverage.DM_a_rWm.js +0 -1087
  60. package/dist/chunks/evaluatedModules.Dg1zASAC.js +0 -17
  61. package/dist/chunks/index.DC7d2Pf8.js +0 -729
  62. package/dist/chunks/index.DdgEv5B1.js +0 -42
  63. package/dist/chunks/index.UpGiHP7g.js +0 -4255
  64. package/dist/chunks/nativeModuleRunner.BIakptoF.js +0 -36
  65. package/dist/chunks/node.COQbm6gK.js +0 -14
  66. package/dist/chunks/plugin.d.BM2TCi12.d.ts +0 -38
  67. package/dist/chunks/suite.d.udJtyAgw.d.ts +0 -10
  68. package/dist/chunks/traces.d.D2T_R8rx.d.ts +0 -60
  69. package/dist/coverage.d.ts +0 -123
  70. package/dist/coverage.js +0 -27
  71. package/dist/environments.d.ts +0 -22
  72. package/dist/environments.js +0 -5
  73. package/dist/reporters.d.ts +0 -27
  74. package/dist/reporters.js +0 -26
  75. package/dist/runners.d.ts +0 -70
  76. package/dist/runners.js +0 -19
  77. package/dist/snapshot.d.ts +0 -9
  78. package/dist/snapshot.js +0 -6
  79. package/dist/suite.d.ts +0 -5
  80. package/dist/suite.js +0 -8
@@ -1,1087 +0,0 @@
1
- import { existsSync, promises, readdirSync, writeFileSync } from 'node:fs';
2
- import module$1 from 'node:module';
3
- import path from 'node:path';
4
- import { pathToFileURL, fileURLToPath } from 'node:url';
5
- import { slash, shuffle, toArray, cleanUrl } from '@vitest/utils/helpers';
6
- import { resolve, relative, normalize, join } from 'pathe';
7
- import pm from 'picomatch';
8
- import { glob } from 'tinyglobby';
9
- import c from 'tinyrainbow';
10
- import { c as configDefaults, e as benchmarkConfigDefaults, a as coverageConfigDefaults } from './defaults.9aQKnqFk.js';
11
- import crypto from 'node:crypto';
12
- import { r as resolveModule } from './index.BCY_7LL2.js';
13
- import { mergeConfig } from 'vite';
14
- import { c as configFiles, d as defaultBrowserPort, a as defaultInspectPort, b as defaultPort } from './constants.CPYnjOGj.js';
15
- import './env.D4Lgay0q.js';
16
- import nodeos__default from 'node:os';
17
- import { w as withLabel } from './utils.BS4fH3nR.js';
18
- import { isAgent, isCI, provider } from 'std-env';
19
- import { r as resolveCoverageProviderModule } from './coverage.CTzCuANN.js';
20
-
21
- const hash = crypto.hash ?? ((algorithm, data, outputEncoding) => crypto.createHash(algorithm).update(data).digest(outputEncoding));
22
-
23
- function getWorkersCountByPercentage(percent) {
24
- const maxWorkersCount = nodeos__default.availableParallelism?.() ?? nodeos__default.cpus().length;
25
- const workersCountByPercentage = Math.round(Number.parseInt(percent) / 100 * maxWorkersCount);
26
- return Math.max(1, Math.min(maxWorkersCount, workersCountByPercentage));
27
- }
28
-
29
- class BaseSequencer {
30
- ctx;
31
- constructor(ctx) {
32
- this.ctx = ctx;
33
- }
34
- // async so it can be extended by other sequencers
35
- async shard(files) {
36
- const { config } = this.ctx;
37
- const { index, count } = config.shard;
38
- const [shardStart, shardEnd] = this.calculateShardRange(files.length, index, count);
39
- return [...files].map((spec) => {
40
- const specPath = resolve(slash(config.root), slash(spec.moduleId))?.slice(config.root.length);
41
- return {
42
- spec,
43
- hash: hash("sha1", specPath, "hex")
44
- };
45
- }).sort((a, b) => a.hash < b.hash ? -1 : a.hash > b.hash ? 1 : 0).slice(shardStart, shardEnd).map(({ spec }) => spec);
46
- }
47
- // async so it can be extended by other sequencers
48
- async sort(files) {
49
- const cache = this.ctx.cache;
50
- return [...files].sort((a, b) => {
51
- // "sequence.groupOrder" is higher priority
52
- const groupOrderDiff = a.project.config.sequence.groupOrder - b.project.config.sequence.groupOrder;
53
- if (groupOrderDiff !== 0) return groupOrderDiff;
54
- // Projects run sequential
55
- if (a.project.name !== b.project.name) return a.project.name < b.project.name ? -1 : 1;
56
- // Isolated run first
57
- if (a.project.config.isolate && !b.project.config.isolate) return -1;
58
- if (!a.project.config.isolate && b.project.config.isolate) return 1;
59
- const keyA = `${a.project.name}:${relative(this.ctx.config.root, a.moduleId)}`;
60
- const keyB = `${b.project.name}:${relative(this.ctx.config.root, b.moduleId)}`;
61
- const aState = cache.getFileTestResults(keyA);
62
- const bState = cache.getFileTestResults(keyB);
63
- if (!aState || !bState) {
64
- const statsA = cache.getFileStats(keyA);
65
- const statsB = cache.getFileStats(keyB);
66
- // run unknown first
67
- if (!statsA || !statsB) return !statsA && statsB ? -1 : !statsB && statsA ? 1 : 0;
68
- // run larger files first
69
- return statsB.size - statsA.size;
70
- }
71
- // run failed first
72
- if (aState.failed && !bState.failed) return -1;
73
- if (!aState.failed && bState.failed) return 1;
74
- // run longer first
75
- return bState.duration - aState.duration;
76
- });
77
- }
78
- // Calculate distributed shard range [start, end] distributed equally
79
- calculateShardRange(filesCount, index, count) {
80
- const baseShardSize = Math.floor(filesCount / count);
81
- const remainderTestFilesCount = filesCount % count;
82
- if (remainderTestFilesCount >= index) {
83
- const shardSize = baseShardSize + 1;
84
- return [shardSize * (index - 1), shardSize * index];
85
- }
86
- const shardStart = remainderTestFilesCount * (baseShardSize + 1) + (index - remainderTestFilesCount - 1) * baseShardSize;
87
- return [shardStart, shardStart + baseShardSize];
88
- }
89
- }
90
-
91
- class RandomSequencer extends BaseSequencer {
92
- async sort(files) {
93
- const { sequence } = this.ctx.config;
94
- return shuffle(files, sequence.seed);
95
- }
96
- }
97
-
98
- function resolvePath(path, root) {
99
- // local-pkg (mlly)'s resolveModule("./file", { paths: ["/some/root"] }) tries
100
- // /some/file
101
- // /some/file.js
102
- // /some/root/file
103
- // /some/root/file.js
104
- // etc.
105
- // but we don't want to resolve files from parent directories,
106
- // so we ensure passing "/" suffix such as "/some/root/"
107
- // https://github.com/unjs/mlly/blob/401d42983f6f3a9112658d67b0a92ba4fb1d7efa/src/resolve.ts#L104-L110
108
- return normalize(/* @__PURE__ */ resolveModule(path, { paths: [join(root, "/")] }) ?? resolve(root, path));
109
- }
110
- function parseInspector(inspect) {
111
- if (typeof inspect === "boolean" || inspect === void 0) return {};
112
- if (typeof inspect === "number") return { port: inspect };
113
- if (inspect.match(/https?:\//)) throw new Error(`Inspector host cannot be a URL. Use "host:port" instead of "${inspect}"`);
114
- const [host, port] = inspect.split(":");
115
- if (!port) return { host };
116
- return {
117
- host,
118
- port: Number(port) || defaultInspectPort
119
- };
120
- }
121
- /**
122
- * @deprecated Internal function
123
- */
124
- function resolveApiServerConfig(options, defaultPort, parentApi, logger) {
125
- let api;
126
- if (options.ui && !options.api) api = { port: defaultPort };
127
- else if (options.api === true) api = { port: defaultPort };
128
- else if (typeof options.api === "number") api = { port: options.api };
129
- if (typeof options.api === "object") if (api) {
130
- if (options.api.port) api.port = options.api.port;
131
- if (options.api.strictPort) api.strictPort = options.api.strictPort;
132
- if (options.api.host) api.host = options.api.host;
133
- } else api = { ...options.api };
134
- if (api) {
135
- if (!api.port && !api.middlewareMode) api.port = defaultPort;
136
- } else api = { middlewareMode: true };
137
- // if the API server is exposed to network, disable write operations by default
138
- if (!api.middlewareMode && api.host && api.host !== "localhost" && api.host !== "127.0.0.1") {
139
- // assigned to browser
140
- if (parentApi) {
141
- if (api.allowWrite == null && api.allowExec == null) logger?.error(c.yellow(`${c.yellowBright(" WARNING ")} API server is exposed to network, disabling write and exec operations by default for security reasons. This can cause some APIs to not work as expected. Set \`browser.api.allowExec\` manually to hide this warning. See https://vitest.dev/config/browser/api for more details.`));
142
- }
143
- api.allowWrite ??= parentApi?.allowWrite ?? false;
144
- api.allowExec ??= parentApi?.allowExec ?? false;
145
- } else {
146
- api.allowWrite ??= parentApi?.allowWrite ?? true;
147
- api.allowExec ??= parentApi?.allowExec ?? true;
148
- }
149
- return api;
150
- }
151
- function resolveInlineWorkerOption(value) {
152
- if (typeof value === "string" && value.trim().endsWith("%")) return getWorkersCountByPercentage(value);
153
- else return Number(value);
154
- }
155
- function resolveConfig$1(vitest, options, viteConfig) {
156
- const mode = vitest.mode;
157
- const logger = vitest.logger;
158
- if (options.dom) {
159
- if (viteConfig.test?.environment != null && viteConfig.test.environment !== "happy-dom") logger.console.warn(withLabel("yellow", "Vitest", `Your config.test.environment ("${viteConfig.test.environment}") conflicts with --dom flag ("happy-dom"), ignoring "${viteConfig.test.environment}"`));
160
- options.environment = "happy-dom";
161
- }
162
- const resolved = {
163
- ...configDefaults,
164
- ...options,
165
- root: viteConfig.root,
166
- mode
167
- };
168
- if (resolved.retry && typeof resolved.retry === "object" && typeof resolved.retry.condition === "function") {
169
- 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."));
170
- resolved.retry = {
171
- ...resolved.retry,
172
- condition: void 0
173
- };
174
- }
175
- if (options.pool && typeof options.pool !== "string") {
176
- resolved.pool = options.pool.name;
177
- resolved.poolRunner = options.pool;
178
- }
179
- 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");
180
- resolved.pool ??= "forks";
181
- resolved.project = toArray(resolved.project);
182
- resolved.provide ??= {};
183
- // shallow copy tags array to avoid mutating user config
184
- resolved.tags = [...resolved.tags || []];
185
- const definedTags = /* @__PURE__ */ new Set();
186
- resolved.tags.forEach((tag) => {
187
- if (!tag.name || typeof tag.name !== "string") throw new Error(`Each tag defined in "test.tags" must have a "name" property, received: ${JSON.stringify(tag)}`);
188
- if (definedTags.has(tag.name)) throw new Error(`Tag name "${tag.name}" is already defined in "test.tags". Tag names must be unique.`);
189
- if (tag.name.match(/\s/)) throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot contain spaces.`);
190
- if (tag.name.match(/([!()*|&])/)) throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot contain "!", "*", "&", "|", "(", or ")".`);
191
- if (tag.name.match(/^\s*(and|or|not)\s*$/i)) throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot be a logical operator like "and", "or", "not".`);
192
- if (typeof tag.retry === "object" && typeof tag.retry.condition === "function") throw new TypeError(`Tag "${tag.name}": retry.condition function cannot be used inside a config file. Use a RegExp pattern instead, or define the function in your test file.`);
193
- if (tag.priority != null && (typeof tag.priority !== "number" || tag.priority < 0)) throw new TypeError(`Tag "${tag.name}": priority must be a non-negative number.`);
194
- definedTags.add(tag.name);
195
- });
196
- resolved.name = typeof options.name === "string" ? options.name : options.name?.label || "";
197
- resolved.color = typeof options.name !== "string" ? options.name?.color : void 0;
198
- if (resolved.environment === "browser") throw new Error(`Looks like you set "test.environment" to "browser". To enable Browser Mode, use "test.browser.enabled" instead.`);
199
- const inspector = resolved.inspect || resolved.inspectBrk;
200
- resolved.inspector = {
201
- ...resolved.inspector,
202
- ...parseInspector(inspector),
203
- enabled: !!inspector,
204
- waitForDebugger: options.inspector?.waitForDebugger ?? !!resolved.inspectBrk
205
- };
206
- if (viteConfig.base !== "/") resolved.base = viteConfig.base;
207
- resolved.clearScreen = resolved.clearScreen ?? viteConfig.clearScreen ?? true;
208
- if (options.shard) {
209
- if (resolved.watch) throw new Error("You cannot use --shard option with enabled watch");
210
- const [indexString, countString] = options.shard.split("/");
211
- const index = Math.abs(Number.parseInt(indexString, 10));
212
- const count = Math.abs(Number.parseInt(countString, 10));
213
- if (Number.isNaN(count) || count <= 0) throw new Error("--shard <count> must be a positive number");
214
- if (Number.isNaN(index) || index <= 0 || index > count) throw new Error("--shard <index> must be a positive number less then <count>");
215
- resolved.shard = {
216
- index,
217
- count
218
- };
219
- }
220
- if (resolved.standalone && !resolved.watch) throw new Error(`Vitest standalone mode requires --watch`);
221
- if (resolved.mergeReports && resolved.watch) throw new Error(`Cannot merge reports with --watch enabled`);
222
- if (resolved.maxWorkers) resolved.maxWorkers = resolveInlineWorkerOption(resolved.maxWorkers);
223
- if (!(options.fileParallelism ?? mode !== "benchmark"))
224
- // ignore user config, parallelism cannot be implemented without limiting workers
225
- resolved.maxWorkers = 1;
226
- if (resolved.maxConcurrency === 0) {
227
- logger.console.warn(c.yellow(`The option "maxConcurrency" cannot be set to 0. Using default value ${configDefaults.maxConcurrency} instead.`));
228
- resolved.maxConcurrency = configDefaults.maxConcurrency;
229
- }
230
- if (resolved.inspect || resolved.inspectBrk) {
231
- if (resolved.maxWorkers !== 1) {
232
- const inspectOption = `--inspect${resolved.inspectBrk ? "-brk" : ""}`;
233
- throw new Error(`You cannot use ${inspectOption} without "--no-file-parallelism"`);
234
- }
235
- }
236
- // apply browser CLI options only if the config already has the browser config and not disabled manually
237
- if (vitest._cliOptions.browser && resolved.browser && (resolved.browser.enabled !== false || vitest._cliOptions.browser.enabled)) resolved.browser = mergeConfig(resolved.browser, vitest._cliOptions.browser);
238
- resolved.browser ??= {};
239
- const browser = resolved.browser;
240
- if (browser.enabled) {
241
- const instances = browser.instances;
242
- if (!browser.instances) browser.instances = [];
243
- // use `chromium` by default when the preview provider is specified
244
- // for a smoother experience. if chromium is not available, it will
245
- // open the default browser anyway
246
- if (!browser.instances.length && browser.provider?.name === "preview") browser.instances = [{ browser: "chromium" }];
247
- if (browser.name && instances?.length) {
248
- // --browser=chromium filters configs to a single one
249
- browser.instances = browser.instances.filter((instance) => instance.browser === browser.name);
250
- // if `instances` were defined, but now they are empty,
251
- // let's throw an error because the filter is invalid
252
- 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(""));
253
- }
254
- }
255
- if (resolved.coverage.enabled && resolved.coverage.provider === "istanbul" && resolved.experimental?.viteModuleRunner === false) throw new Error(`"Istanbul" coverage provider is not compatible with "experimental.viteModuleRunner: false". Please, enable "viteModuleRunner" or switch to "v8" coverage provider.`);
256
- if (browser.enabled && resolved.detectAsyncLeaks) logger.console.warn(c.yellow("The option \"detectAsyncLeaks\" is not supported in browser mode and will be ignored."));
257
- const containsChromium = hasBrowserChromium(vitest, resolved);
258
- const hasOnlyChromium = hasOnlyBrowserChromium(vitest, resolved);
259
- // Browser-mode "Chromium" only features:
260
- if (browser.enabled && (!containsChromium || !hasOnlyChromium)) {
261
- const browserConfig = `
262
- {
263
- browser: {
264
- provider: ${browser.provider?.name || "preview"}(),
265
- instances: [
266
- ${(browser.instances || []).map((i) => `{ browser: '${i.browser}' }`).join(",\n ")}
267
- ],
268
- },
269
- }
270
- `.trim();
271
- const preferredProvider = !browser.provider?.name || browser.provider.name === "preview" ? "playwright" : browser.provider.name;
272
- const correctExample = `
273
- {
274
- browser: {
275
- provider: ${preferredProvider}(),
276
- instances: [
277
- { browser: '${preferredProvider === "playwright" ? "chromium" : "chrome"}' }
278
- ],
279
- },
280
- }
281
- `.trim();
282
- // requires all projects to be chromium
283
- if (!hasOnlyChromium && resolved.coverage.enabled && resolved.coverage.provider === "v8") {
284
- const coverageExample = `
285
- {
286
- coverage: {
287
- provider: 'istanbul',
288
- },
289
- }
290
- `.trim();
291
- 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`);
292
- }
293
- // ignores non-chromium browsers when there is at least one chromium project
294
- if (!containsChromium && (resolved.inspect || resolved.inspectBrk)) {
295
- const inspectOption = `--inspect${resolved.inspectBrk ? "-brk" : ""}`;
296
- throw new Error(`${inspectOption} does not work with\n${browserConfig}\n\nUse either:\n${correctExample}\n\n...or disable ${inspectOption}\n`);
297
- }
298
- }
299
- resolved.coverage.reporter = resolveCoverageReporters(resolved.coverage.reporter);
300
- if (isAgent) {
301
- // default to `skipFull` and add `text-summary` reporter when `text` reporter is used on agents
302
- const text = resolved.coverage.reporter.find(([name]) => name === "text");
303
- const textSummary = resolved.coverage.reporter.find(([name]) => name === "text-summary");
304
- if (text) {
305
- text[1] = {
306
- skipFull: true,
307
- ...text[1]
308
- };
309
- if (!textSummary) resolved.coverage.reporter.push(["text-summary", {}]);
310
- }
311
- }
312
- if (resolved.coverage.changed === void 0 && resolved.changed !== void 0) resolved.coverage.changed = resolved.changed;
313
- if (resolved.coverage.enabled && resolved.coverage.reportsDirectory) {
314
- const reportsDirectory = resolve(resolved.root, resolved.coverage.reportsDirectory);
315
- 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`);
316
- if (resolved.coverage.htmlDir) resolved.coverage.htmlDir = resolve(resolved.root, resolved.coverage.htmlDir);
317
- // infer default htmlDir based on builtin reporter's html output location
318
- if (!resolved.coverage.htmlDir) {
319
- const htmlReporter = resolved.coverage.reporter.find(([name]) => name === "html" || name === "html-spa");
320
- if (htmlReporter) {
321
- const [, options] = htmlReporter;
322
- const subdir = options && typeof options === "object" && "subdir" in options && typeof options.subdir === "string" ? options.subdir : void 0;
323
- resolved.coverage.htmlDir = resolve(reportsDirectory, subdir || ".");
324
- } else if (resolved.coverage.reporter.find(([name]) => name === "lcov")) resolved.coverage.htmlDir = resolve(reportsDirectory, "lcov-report");
325
- }
326
- }
327
- if (resolved.coverage.enabled && resolved.coverage.provider === "custom" && resolved.coverage.customProviderModule) resolved.coverage.customProviderModule = resolvePath(resolved.coverage.customProviderModule, resolved.root);
328
- resolved.expect ??= {};
329
- resolved.deps ??= {};
330
- resolved.deps.moduleDirectories ??= [];
331
- resolved.deps.optimizer ??= {};
332
- resolved.deps.optimizer.ssr ??= {};
333
- resolved.deps.optimizer.ssr.enabled ??= false;
334
- resolved.deps.optimizer.client ??= {};
335
- resolved.deps.optimizer.client.enabled ??= false;
336
- resolved.deps.web ??= {};
337
- resolved.deps.web.transformAssets ??= true;
338
- resolved.deps.web.transformCss ??= true;
339
- resolved.deps.web.transformGlobPattern ??= [];
340
- resolved.setupFiles = toArray(resolved.setupFiles || []).map((file) => resolvePath(file, resolved.root));
341
- resolved.globalSetup = toArray(resolved.globalSetup || []).map((file) => resolvePath(file, resolved.root));
342
- // Add hard-coded default coverage exclusions. These cannot be overridden by user config.
343
- // Override original exclude array for cases where user re-uses same object in test.exclude.
344
- resolved.coverage.exclude = [
345
- ...resolved.coverage.exclude,
346
- ...resolved.setupFiles.map((file) => `${resolved.coverage.allowExternal ? "**/" : ""}${relative(resolved.root, file)}`),
347
- ...resolved.include,
348
- resolved.config && slash(resolved.config),
349
- ...configFiles,
350
- "**/virtual:*",
351
- "**/__x00__*",
352
- "**/node_modules/**"
353
- ].filter((pattern) => typeof pattern === "string");
354
- resolved.forceRerunTriggers = [...resolved.forceRerunTriggers, ...resolved.setupFiles];
355
- if (resolved.cliExclude) resolved.exclude.push(...resolved.cliExclude);
356
- if (resolved.runner) resolved.runner = resolvePath(resolved.runner, resolved.root);
357
- resolved.attachmentsDir = resolve(resolved.root, resolved.attachmentsDir ?? ".vitest-attachments");
358
- if (resolved.snapshotEnvironment) resolved.snapshotEnvironment = resolvePath(resolved.snapshotEnvironment, resolved.root);
359
- resolved.testNamePattern = resolved.testNamePattern ? resolved.testNamePattern instanceof RegExp ? resolved.testNamePattern : new RegExp(resolved.testNamePattern) : void 0;
360
- if (resolved.snapshotFormat && "plugins" in resolved.snapshotFormat) {
361
- resolved.snapshotFormat.plugins = [];
362
- // TODO: support it via separate config (like DiffOptions) or via `Function.toString()`
363
- if (typeof resolved.snapshotFormat.compareKeys === "function") throw new TypeError(`"snapshotFormat.compareKeys" function is not supported.`);
364
- }
365
- const UPDATE_SNAPSHOT = resolved.update || process.env.UPDATE_SNAPSHOT;
366
- resolved.snapshotOptions = {
367
- expand: resolved.expandSnapshotDiff ?? false,
368
- snapshotFormat: resolved.snapshotFormat || {},
369
- updateSnapshot: UPDATE_SNAPSHOT === "all" || UPDATE_SNAPSHOT === "new" || UPDATE_SNAPSHOT === "none" ? UPDATE_SNAPSHOT : isCI && !UPDATE_SNAPSHOT ? "none" : UPDATE_SNAPSHOT ? "all" : "new",
370
- resolveSnapshotPath: options.resolveSnapshotPath,
371
- snapshotEnvironment: null
372
- };
373
- resolved.snapshotSerializers ??= [];
374
- resolved.snapshotSerializers = resolved.snapshotSerializers.map((file) => resolvePath(file, resolved.root));
375
- resolved.forceRerunTriggers.push(...resolved.snapshotSerializers);
376
- if (options.resolveSnapshotPath) delete resolved.resolveSnapshotPath;
377
- resolved.execArgv ??= [];
378
- resolved.pool ??= "threads";
379
- if (resolved.pool === "vmForks" || resolved.pool === "vmThreads" || resolved.pool === "typescript") resolved.isolate = false;
380
- if (process.env.VITEST_MAX_WORKERS) resolved.maxWorkers = Number.parseInt(process.env.VITEST_MAX_WORKERS);
381
- if (mode === "benchmark") {
382
- resolved.benchmark = {
383
- ...benchmarkConfigDefaults,
384
- ...resolved.benchmark
385
- };
386
- // override test config
387
- resolved.coverage.enabled = false;
388
- resolved.typecheck.enabled = false;
389
- resolved.include = resolved.benchmark.include;
390
- resolved.exclude = resolved.benchmark.exclude;
391
- resolved.includeSource = resolved.benchmark.includeSource;
392
- const reporters = Array.from(new Set([...toArray(resolved.benchmark.reporters), ...toArray(options.reporter)])).filter(Boolean);
393
- if (reporters.length) resolved.benchmark.reporters = reporters;
394
- else resolved.benchmark.reporters = ["default"];
395
- if (options.outputFile) resolved.benchmark.outputFile = options.outputFile;
396
- // --compare from cli
397
- if (options.compare) resolved.benchmark.compare = options.compare;
398
- if (options.outputJson) resolved.benchmark.outputJson = options.outputJson;
399
- }
400
- if (typeof resolved.diff === "string") {
401
- resolved.diff = resolvePath(resolved.diff, resolved.root);
402
- resolved.forceRerunTriggers.push(resolved.diff);
403
- }
404
- resolved.api = {
405
- ...resolveApiServerConfig(options, defaultPort),
406
- token: crypto.randomUUID()
407
- };
408
- if (options.related) resolved.related = toArray(options.related).map((file) => resolve(resolved.root, file));
409
- /*
410
- * Reporters can be defined in many different ways:
411
- * { reporter: 'json' }
412
- * { reporter: { onFinish() { method() } } }
413
- * { reporter: ['json', { onFinish() { method() } }] }
414
- * { reporter: [[ 'json' ]] }
415
- * { reporter: [[ 'json' ], 'html'] }
416
- * { reporter: [[ 'json', { outputFile: 'test.json' } ], 'html'] }
417
- */
418
- if (options.reporters) if (!Array.isArray(options.reporters))
419
- // Reporter name, e.g. { reporters: 'json' }
420
- if (typeof options.reporters === "string") resolved.reporters = [[options.reporters, {}]];
421
- else resolved.reporters = [options.reporters];
422
- else {
423
- resolved.reporters = [];
424
- for (const reporter of options.reporters) if (Array.isArray(reporter))
425
- // Reporter with options, e.g. { reporters: [ [ 'json', { outputFile: 'test.json' } ] ] }
426
- resolved.reporters.push([reporter[0], reporter[1] || {}]);
427
- else if (typeof reporter === "string")
428
- // Reporter name in array, e.g. { reporters: ["html", "json"]}
429
- resolved.reporters.push([reporter, {}]);
430
- else
431
- // Inline reporter, e.g. { reporter: [{ onFinish() { method() } }] }
432
- resolved.reporters.push(reporter);
433
- }
434
- if (mode !== "benchmark") {
435
- // @ts-expect-error "reporter" is from CLI, should be absolute to the running directory
436
- // it is passed down as "vitest --reporter ../reporter.js"
437
- const reportersFromCLI = resolved.reporter;
438
- const cliReporters = toArray(reportersFromCLI || []).map((reporter) => {
439
- // ./reporter.js || ../reporter.js, but not .reporters/reporter.js
440
- if (/^\.\.?\//.test(reporter)) return resolve(process.cwd(), reporter);
441
- return reporter;
442
- });
443
- if (cliReporters.length) {
444
- // When CLI reporters are specified, preserve options from config file
445
- const configReportersMap = /* @__PURE__ */ new Map();
446
- // Build a map of reporter names to their options from the config
447
- for (const reporter of resolved.reporters) if (Array.isArray(reporter)) {
448
- const [reporterName, reporterOptions] = reporter;
449
- if (typeof reporterName === "string") configReportersMap.set(reporterName, reporterOptions);
450
- }
451
- resolved.reporters = Array.from(new Set(toArray(cliReporters))).filter(Boolean).map((reporter) => [reporter, configReportersMap.get(reporter) || {}]);
452
- }
453
- }
454
- if (!resolved.reporters.length) {
455
- resolved.reporters.push([isAgent ? "agent" : "default", {}]);
456
- // also enable github-actions reporter as a default
457
- if (process.env.GITHUB_ACTIONS === "true") resolved.reporters.push(["github-actions", {}]);
458
- }
459
- if (resolved.changed) resolved.passWithNoTests ??= true;
460
- resolved.css ??= {};
461
- if (typeof resolved.css === "object") {
462
- resolved.css.modules ??= {};
463
- resolved.css.modules.classNameStrategy ??= "stable";
464
- }
465
- if (resolved.cache !== false) {
466
- 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"`);
467
- resolved.cache = { dir: viteConfig.cacheDir };
468
- }
469
- resolved.sequence ??= {};
470
- if (resolved.sequence.shuffle && typeof resolved.sequence.shuffle === "object") {
471
- const { files, tests } = resolved.sequence.shuffle;
472
- resolved.sequence.sequencer ??= files ? RandomSequencer : BaseSequencer;
473
- resolved.sequence.shuffle = tests;
474
- }
475
- if (!resolved.sequence?.sequencer)
476
- // CLI flag has higher priority
477
- resolved.sequence.sequencer = resolved.sequence.shuffle ? RandomSequencer : BaseSequencer;
478
- resolved.sequence.groupOrder ??= 0;
479
- resolved.sequence.hooks ??= "stack";
480
- // Set seed if either files or tests are shuffled
481
- if (resolved.sequence.sequencer === RandomSequencer || resolved.sequence.shuffle) resolved.sequence.seed ??= Date.now();
482
- resolved.typecheck = {
483
- ...configDefaults.typecheck,
484
- ...resolved.typecheck
485
- };
486
- resolved.typecheck ??= {};
487
- resolved.typecheck.enabled ??= false;
488
- 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."));
489
- resolved.browser.enabled ??= false;
490
- resolved.browser.headless ??= isCI;
491
- if (resolved.browser.isolate) logger.console.warn(c.yellow("`browser.isolate` is deprecated. Use top-level `isolate` instead."));
492
- resolved.browser.isolate ??= resolved.isolate ?? true;
493
- resolved.browser.fileParallelism ??= options.fileParallelism ?? mode !== "benchmark";
494
- // disable in headless mode by default, and if CI is detected
495
- resolved.browser.ui ??= resolved.browser.headless === true ? false : !isCI;
496
- resolved.browser.commands ??= {};
497
- resolved.browser.detailsPanelPosition ??= "right";
498
- if (resolved.browser.screenshotDirectory) resolved.browser.screenshotDirectory = resolve(resolved.root, resolved.browser.screenshotDirectory);
499
- if (resolved.inspector.enabled) resolved.browser.trackUnhandledErrors ??= false;
500
- resolved.browser.viewport ??= {};
501
- resolved.browser.viewport.width ??= 414;
502
- resolved.browser.viewport.height ??= 896;
503
- resolved.browser.locators ??= {};
504
- resolved.browser.locators.testIdAttribute ??= "data-testid";
505
- resolved.browser.locators.exact ??= false;
506
- if (typeof resolved.browser.provider === "string") {
507
- const source = `@vitest/browser-${resolved.browser.provider}`;
508
- 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`);
509
- }
510
- const isPreview = resolved.browser.provider?.name === "preview";
511
- 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.`);
512
- if (isPreview && resolved.browser.screenshotFailures === true) {
513
- console.warn(c.yellow([
514
- `Browser provider "preview" doesn't support screenshots, `,
515
- `so "browser.screenshotFailures" option is forcefully disabled. `,
516
- `Set "browser.screenshotFailures" to false or remove it from the config to suppress this warning.`
517
- ].join("")));
518
- resolved.browser.screenshotFailures = false;
519
- } else resolved.browser.screenshotFailures ??= !isPreview && !resolved.browser.ui;
520
- if (resolved.browser.provider && resolved.browser.provider.options == null) resolved.browser.provider.options = {};
521
- resolved.browser.api = resolveApiServerConfig(resolved.browser, defaultBrowserPort, resolved.api, logger) || { port: defaultBrowserPort };
522
- // enable includeTaskLocation by default in UI mode
523
- if (resolved.browser.enabled) {
524
- if (resolved.browser.ui) resolved.includeTaskLocation ??= true;
525
- } else if (resolved.ui) resolved.includeTaskLocation ??= true;
526
- if (typeof resolved.browser.trace === "string" || !resolved.browser.trace) resolved.browser.trace = { mode: resolved.browser.trace || "off" };
527
- if (resolved.browser.trace.tracesDir != null) resolved.browser.trace.tracesDir = resolvePath(resolved.browser.trace.tracesDir, resolved.root);
528
- if (toArray(resolved.reporters).some((reporter) => {
529
- if (Array.isArray(reporter)) return reporter[0] === "html";
530
- return false;
531
- })) resolved.includeTaskLocation ??= true;
532
- resolved.server ??= {};
533
- resolved.server.deps ??= {};
534
- if (resolved.server.debug?.dump || process.env.VITEST_DEBUG_DUMP) {
535
- const userFolder = resolved.server.debug?.dump || process.env.VITEST_DEBUG_DUMP;
536
- resolved.dumpDir = resolve(resolved.root, typeof userFolder === "string" && userFolder !== "true" ? userFolder : ".vitest-dump", resolved.name || "root");
537
- }
538
- resolved.testTimeout ??= resolved.browser.enabled ? 15e3 : 5e3;
539
- resolved.hookTimeout ??= resolved.browser.enabled ? 3e4 : 1e4;
540
- resolved.experimental ??= {};
541
- if (resolved.experimental.openTelemetry?.sdkPath) {
542
- const sdkPath = resolve(resolved.root, resolved.experimental.openTelemetry.sdkPath);
543
- resolved.experimental.openTelemetry.sdkPath = pathToFileURL(sdkPath).toString();
544
- }
545
- if (resolved.experimental.openTelemetry?.browserSdkPath) {
546
- const browserSdkPath = resolve(resolved.root, resolved.experimental.openTelemetry.browserSdkPath);
547
- resolved.experimental.openTelemetry.browserSdkPath = browserSdkPath;
548
- }
549
- if (resolved.experimental.fsModuleCachePath) resolved.experimental.fsModuleCachePath = resolve(resolved.root, resolved.experimental.fsModuleCachePath);
550
- resolved.experimental.importDurations ??= {};
551
- resolved.experimental.importDurations.print ??= false;
552
- resolved.experimental.importDurations.failOnDanger ??= false;
553
- if (resolved.experimental.importDurations.limit == null) {
554
- const shouldCollect = resolved.experimental.importDurations.print || resolved.experimental.importDurations.failOnDanger || resolved.ui;
555
- resolved.experimental.importDurations.limit = shouldCollect ? 10 : 0;
556
- }
557
- resolved.experimental.importDurations.thresholds ??= {};
558
- resolved.experimental.importDurations.thresholds.warn ??= 100;
559
- resolved.experimental.importDurations.thresholds.danger ??= 500;
560
- if (typeof resolved.experimental.vcsProvider === "string" && resolved.experimental.vcsProvider !== "git") resolved.experimental.vcsProvider = resolvePath(resolved.experimental.vcsProvider, resolved.root);
561
- return resolved;
562
- }
563
- function isBrowserEnabled(config) {
564
- return Boolean(config.browser?.enabled);
565
- }
566
- function resolveCoverageReporters(configReporters) {
567
- // E.g. { reporter: "html" }
568
- if (!Array.isArray(configReporters)) return [[configReporters, {}]];
569
- const resolvedReporters = [];
570
- for (const reporter of configReporters) if (Array.isArray(reporter))
571
- // E.g. { reporter: [ ["html", { skipEmpty: true }], ["lcov"], ["json", { file: "map.json" }] ]}
572
- resolvedReporters.push([reporter[0], reporter[1] || {}]);
573
- else
574
- // E.g. { reporter: ["html", "json"]}
575
- resolvedReporters.push([reporter, {}]);
576
- return resolvedReporters;
577
- }
578
- function isChromiumName(provider, name) {
579
- if (provider === "playwright") return name === "chromium";
580
- return name === "chrome" || name === "edge";
581
- }
582
- function hasBrowserChromium(vitest, config) {
583
- const browser = config.browser;
584
- if (!browser || !browser.provider || browser.provider.name === "preview" || !browser.enabled) return false;
585
- if (browser.name) return isChromiumName(browser.provider.name, browser.name);
586
- if (!browser.instances) return false;
587
- return browser.instances.some((instance) => {
588
- const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser);
589
- // browser config is filtered out
590
- if (!vitest.matchesProjectFilter(name)) return false;
591
- return isChromiumName(browser.provider.name, instance.browser);
592
- });
593
- }
594
- function hasOnlyBrowserChromium(vitest, config) {
595
- const browser = config.browser;
596
- if (!browser || !browser.provider || browser.provider.name === "preview" || !browser.enabled) return false;
597
- if (browser.name) return isChromiumName(browser.provider.name, browser.name);
598
- if (!browser.instances) return false;
599
- return browser.instances.every((instance) => {
600
- const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser);
601
- // browser config is filtered out
602
- if (!vitest.matchesProjectFilter(name)) return true;
603
- return isChromiumName(browser.provider.name, instance.browser);
604
- });
605
- }
606
-
607
- const THRESHOLD_KEYS = [
608
- "lines",
609
- "functions",
610
- "statements",
611
- "branches"
612
- ];
613
- const GLOBAL_THRESHOLDS_KEY = "global";
614
- const DEFAULT_PROJECT = Symbol.for("default-project");
615
- let uniqueId = 0;
616
- async function getCoverageProvider(options, loader) {
617
- const coverageModule = await resolveCoverageProviderModule(options, loader);
618
- if (coverageModule) return coverageModule.getProvider();
619
- return null;
620
- }
621
- class BaseCoverageProvider {
622
- ctx;
623
- name;
624
- version;
625
- options;
626
- globCache = /* @__PURE__ */ new Map();
627
- autoUpdateMarker = "\n// __VITEST_COVERAGE_MARKER__";
628
- coverageFiles = /* @__PURE__ */ new Map();
629
- pendingPromises = [];
630
- coverageFilesDirectory;
631
- roots = [];
632
- changedFiles;
633
- _initialize(ctx) {
634
- this.ctx = ctx;
635
- 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} `))}.
636
- Running mixed versions is not supported and may lead into bugs
637
- Update your dependencies and make sure the versions match.`));
638
- const config = ctx._coverageOptions;
639
- this.options = {
640
- ...coverageConfigDefaults,
641
- ...config,
642
- provider: this.name,
643
- reportsDirectory: resolve(ctx.config.root, config.reportsDirectory || coverageConfigDefaults.reportsDirectory),
644
- reporter: resolveCoverageReporters(config.reporter || coverageConfigDefaults.reporter),
645
- thresholds: config.thresholds && {
646
- ...config.thresholds,
647
- lines: config.thresholds["100"] ? 100 : config.thresholds.lines,
648
- branches: config.thresholds["100"] ? 100 : config.thresholds.branches,
649
- functions: config.thresholds["100"] ? 100 : config.thresholds.functions,
650
- statements: config.thresholds["100"] ? 100 : config.thresholds.statements
651
- }
652
- };
653
- const shard = this.ctx.config.shard;
654
- const tempDirectory = `.tmp${shard ? `-${shard.index}-${shard.count}` : ""}`;
655
- this.coverageFilesDirectory = resolve(this.options.reportsDirectory, tempDirectory);
656
- // If --project filter is set pick only roots of resolved projects
657
- this.roots = ctx.config.project?.length ? [...new Set(ctx.projects.map((project) => project.config.root))] : [ctx.config.root];
658
- }
659
- /**
660
- * Check if file matches `coverage.include` but not `coverage.exclude`
661
- */
662
- isIncluded(_filename, root) {
663
- const roots = root ? [root] : this.roots;
664
- const filename = slash(cleanUrl(_filename));
665
- const cacheHit = this.globCache.get(filename);
666
- if (cacheHit !== void 0) return cacheHit;
667
- // File outside project root with default allowExternal
668
- if (this.options.allowExternal === false && roots.every((root) => !filename.startsWith(root))) {
669
- this.globCache.set(filename, false);
670
- return false;
671
- }
672
- // By default `coverage.include` matches all files, except "coverage.exclude"
673
- const glob = this.options.include || "**";
674
- let included = pm.isMatch(filename, glob, {
675
- contains: true,
676
- dot: true,
677
- ignore: this.options.exclude
678
- });
679
- if (included && this.changedFiles) included = this.changedFiles.includes(filename);
680
- this.globCache.set(filename, included);
681
- return included;
682
- }
683
- async getUntestedFilesByRoot(testedFiles, include, root) {
684
- let includedFiles = await glob(include, {
685
- cwd: root,
686
- ignore: [...this.options.exclude, ...testedFiles.map((file) => slash(file))],
687
- absolute: true,
688
- dot: true,
689
- onlyFiles: true
690
- });
691
- // Run again through picomatch as tinyglobby's exclude pattern is different ({ "exclude": ["math"] } should ignore "src/math.ts")
692
- includedFiles = includedFiles.filter((file) => this.isIncluded(file, root));
693
- if (this.changedFiles) includedFiles = this.changedFiles.filter((file) => includedFiles.includes(file));
694
- return includedFiles.map((file) => slash(path.resolve(root, file)));
695
- }
696
- async getUntestedFiles(testedFiles) {
697
- if (this.options.include == null) return [];
698
- const rootMapper = this.getUntestedFilesByRoot.bind(this, testedFiles, this.options.include);
699
- return (await Promise.all(this.roots.map(rootMapper))).flatMap((files) => files);
700
- }
701
- createCoverageMap() {
702
- throw new Error("BaseReporter's createCoverageMap was not overwritten");
703
- }
704
- async generateReports(_, __) {
705
- throw new Error("BaseReporter's generateReports was not overwritten");
706
- }
707
- async parseConfigModule(_) {
708
- throw new Error("BaseReporter's parseConfigModule was not overwritten");
709
- }
710
- resolveOptions() {
711
- return this.options;
712
- }
713
- async clean(clean = true) {
714
- if (clean && existsSync(this.options.reportsDirectory)) await promises.rm(this.options.reportsDirectory, {
715
- recursive: true,
716
- force: true,
717
- maxRetries: 10
718
- });
719
- if (existsSync(this.coverageFilesDirectory)) await promises.rm(this.coverageFilesDirectory, {
720
- recursive: true,
721
- force: true,
722
- maxRetries: 10
723
- });
724
- await promises.mkdir(this.coverageFilesDirectory, { recursive: true });
725
- this.coverageFiles = /* @__PURE__ */ new Map();
726
- this.pendingPromises = [];
727
- }
728
- normalizeCoverageFileError(error) {
729
- if (error instanceof Error && "code" in error && error.code === "ENOENT" && !existsSync(this.coverageFilesDirectory)) return new Error(`Something removed the coverage directory "${this.coverageFilesDirectory}" Vitest created earlier. Make sure you are not running multiple Vitests with the same "coverage.reportsDirectory" at the same time.`, { cause: error });
730
- return error;
731
- }
732
- onAfterSuiteRun({ coverage, environment, projectName, testFiles }) {
733
- if (!coverage) return;
734
- let entry = this.coverageFiles.get(projectName || DEFAULT_PROJECT);
735
- if (!entry) {
736
- entry = {};
737
- this.coverageFiles.set(projectName || DEFAULT_PROJECT, entry);
738
- }
739
- const testFilenames = testFiles.join();
740
- const filename = resolve(this.coverageFilesDirectory, `coverage-${uniqueId++}.json`);
741
- entry[environment] ??= {};
742
- // If there's a result from previous run, overwrite it
743
- entry[environment][testFilenames] = filename;
744
- const promise = promises.writeFile(filename, JSON.stringify(coverage), "utf-8").catch((error) => {
745
- throw this.normalizeCoverageFileError(error);
746
- });
747
- this.pendingPromises.push(promise);
748
- }
749
- async readCoverageFiles({ onFileRead, onFinished, onDebug }) {
750
- let index = 0;
751
- const total = this.pendingPromises.length;
752
- await Promise.all(this.pendingPromises);
753
- this.pendingPromises = [];
754
- for (const [projectName, coveragePerProject] of this.coverageFiles.entries()) for (const [environment, coverageByTestfiles] of Object.entries(coveragePerProject)) {
755
- const filenames = Object.values(coverageByTestfiles);
756
- const project = this.ctx.getProjectByName(projectName);
757
- for (const chunk of this.toSlices(filenames, this.options.processingConcurrency)) {
758
- if (onDebug.enabled) {
759
- index += chunk.length;
760
- onDebug(`Reading coverage results ${index}/${total}`);
761
- }
762
- await Promise.all(chunk.map(async (filename) => {
763
- const contents = await promises.readFile(filename, "utf-8").catch((error) => {
764
- throw this.normalizeCoverageFileError(error);
765
- });
766
- onFileRead(JSON.parse(contents));
767
- }));
768
- }
769
- await onFinished(project, environment);
770
- }
771
- }
772
- async cleanAfterRun() {
773
- this.coverageFiles = /* @__PURE__ */ new Map();
774
- await promises.rm(this.coverageFilesDirectory, { recursive: true });
775
- // Remove empty reports directory, e.g. when only text-reporter is used
776
- if (readdirSync(this.options.reportsDirectory).length === 0) await promises.rm(this.options.reportsDirectory, { recursive: true });
777
- }
778
- async onTestRunStart() {
779
- if (this.options.changed) try {
780
- this.changedFiles = await this.ctx.vcs.findChangedFiles({
781
- root: this.ctx.config.root,
782
- changedSince: this.options.changed
783
- });
784
- } catch {
785
- this.changedFiles = void 0;
786
- }
787
- else if (this.ctx.config.changed) this.changedFiles = this.ctx.config.related;
788
- if (this.changedFiles) this.globCache.clear();
789
- }
790
- async onTestFailure() {
791
- if (!this.options.reportOnFailure) await this.cleanAfterRun();
792
- }
793
- async reportCoverage(coverageMap, { allTestsRun }) {
794
- await this.generateReports(coverageMap || this.createCoverageMap(), allTestsRun);
795
- if (!(!this.options.cleanOnRerun && this.ctx.config.watch)) await this.cleanAfterRun();
796
- }
797
- async reportThresholds(coverageMap, allTestsRun) {
798
- const resolvedThresholds = this.resolveThresholds(coverageMap);
799
- this.checkThresholds(resolvedThresholds);
800
- if (this.options.thresholds?.autoUpdate && allTestsRun) {
801
- if (!this.ctx.vite.config.configFile) throw new Error("Missing configurationFile. The \"coverage.thresholds.autoUpdate\" can only be enabled when configuration file is used.");
802
- const configFilePath = this.ctx.vite.config.configFile;
803
- const configModule = await this.parseConfigModule(configFilePath);
804
- await this.updateThresholds({
805
- thresholds: resolvedThresholds,
806
- configurationFile: configModule,
807
- onUpdate: () => writeFileSync(configFilePath, configModule.generate().code.replace(this.autoUpdateMarker, ""), "utf-8")
808
- });
809
- }
810
- }
811
- /**
812
- * Constructs collected coverage and users' threshold options into separate sets
813
- * where each threshold set holds their own coverage maps. Threshold set is either
814
- * for specific files defined by glob pattern or global for all other files.
815
- */
816
- resolveThresholds(coverageMap) {
817
- const resolvedThresholds = [];
818
- const files = coverageMap.files();
819
- const globalCoverageMap = this.createCoverageMap();
820
- for (const key of Object.keys(this.options.thresholds)) {
821
- if (key === "perFile" || key === "autoUpdate" || key === "100" || THRESHOLD_KEYS.includes(key)) continue;
822
- const glob = key;
823
- const globThresholds = resolveGlobThresholds(this.options.thresholds[glob]);
824
- const globCoverageMap = this.createCoverageMap();
825
- const matcher = pm(glob);
826
- const matchingFiles = files.filter((file) => matcher(relative(this.ctx.config.root, file)));
827
- for (const file of matchingFiles) {
828
- const fileCoverage = coverageMap.fileCoverageFor(file);
829
- globCoverageMap.addFileCoverage(fileCoverage);
830
- }
831
- resolvedThresholds.push({
832
- name: glob,
833
- coverageMap: globCoverageMap,
834
- thresholds: globThresholds
835
- });
836
- }
837
- // Global threshold is for all files, even if they are included by glob patterns
838
- for (const file of files) {
839
- const fileCoverage = coverageMap.fileCoverageFor(file);
840
- globalCoverageMap.addFileCoverage(fileCoverage);
841
- }
842
- resolvedThresholds.unshift({
843
- name: GLOBAL_THRESHOLDS_KEY,
844
- coverageMap: globalCoverageMap,
845
- thresholds: {
846
- branches: this.options.thresholds?.branches,
847
- functions: this.options.thresholds?.functions,
848
- lines: this.options.thresholds?.lines,
849
- statements: this.options.thresholds?.statements
850
- }
851
- });
852
- return resolvedThresholds;
853
- }
854
- /**
855
- * Check collected coverage against configured thresholds. Sets exit code to 1 when thresholds not reached.
856
- */
857
- checkThresholds(allThresholds) {
858
- for (const { coverageMap, thresholds, name } of allThresholds) {
859
- if (thresholds.branches === void 0 && thresholds.functions === void 0 && thresholds.lines === void 0 && thresholds.statements === void 0) continue;
860
- // Construct list of coverage summaries where thresholds are compared against
861
- const summaries = this.options.thresholds?.perFile ? coverageMap.files().map((file) => ({
862
- file,
863
- summary: coverageMap.fileCoverageFor(file).toSummary()
864
- })) : [{
865
- file: null,
866
- summary: coverageMap.getCoverageSummary()
867
- }];
868
- // Check thresholds of each summary
869
- for (const { summary, file } of summaries) for (const thresholdKey of THRESHOLD_KEYS) {
870
- const threshold = thresholds[thresholdKey];
871
- if (threshold === void 0) continue;
872
- /**
873
- * Positive thresholds are treated as minimum coverage percentages (X means: X% of lines must be covered),
874
- * while negative thresholds are treated as maximum uncovered counts (-X means: X lines may be uncovered).
875
- */
876
- if (threshold >= 0) {
877
- const coverage = summary.data[thresholdKey].pct;
878
- if (coverage < threshold) {
879
- process.exitCode = 1;
880
- /**
881
- * Generate error message based on perFile flag:
882
- * - ERROR: Coverage for statements (33.33%) does not meet threshold (85%) for src/math.ts
883
- * - ERROR: Coverage for statements (50%) does not meet global threshold (85%)
884
- */
885
- let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet ${name === GLOBAL_THRESHOLDS_KEY ? name : `"${name}"`} threshold (${threshold}%)`;
886
- if (this.options.thresholds?.perFile && file) errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
887
- this.ctx.logger.error(errorMessage);
888
- }
889
- } else {
890
- const uncovered = summary.data[thresholdKey].total - summary.data[thresholdKey].covered;
891
- const absoluteThreshold = threshold * -1;
892
- if (uncovered > absoluteThreshold) {
893
- process.exitCode = 1;
894
- /**
895
- * Generate error message based on perFile flag:
896
- * - ERROR: Uncovered statements (33) exceed threshold (30) for src/math.ts
897
- * - ERROR: Uncovered statements (33) exceed global threshold (30)
898
- */
899
- let errorMessage = `ERROR: Uncovered ${thresholdKey} (${uncovered}) exceed ${name === GLOBAL_THRESHOLDS_KEY ? name : `"${name}"`} threshold (${absoluteThreshold})`;
900
- if (this.options.thresholds?.perFile && file) errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
901
- this.ctx.logger.error(errorMessage);
902
- }
903
- }
904
- }
905
- }
906
- }
907
- /**
908
- * Check if current coverage is above configured thresholds and bump the thresholds if needed
909
- */
910
- async updateThresholds({ thresholds: allThresholds, onUpdate, configurationFile }) {
911
- let updatedThresholds = false;
912
- const config = resolveConfig(configurationFile);
913
- assertConfigurationModule(config);
914
- for (const { coverageMap, thresholds, name } of allThresholds) {
915
- const summaries = this.options.thresholds?.perFile ? coverageMap.files().map((file) => coverageMap.fileCoverageFor(file).toSummary()) : [coverageMap.getCoverageSummary()];
916
- const thresholdsToUpdate = [];
917
- for (const key of THRESHOLD_KEYS) {
918
- const threshold = thresholds[key] ?? 100;
919
- /**
920
- * Positive thresholds are treated as minimum coverage percentages (X means: X% of lines must be covered),
921
- * while negative thresholds are treated as maximum uncovered counts (-X means: X lines may be uncovered).
922
- */
923
- if (threshold >= 0) {
924
- const actual = Math.min(...summaries.map((summary) => summary[key].pct));
925
- if (actual > threshold) thresholdsToUpdate.push([key, actual]);
926
- } else {
927
- const absoluteThreshold = threshold * -1;
928
- const actual = Math.max(...summaries.map((summary) => summary[key].total - summary[key].covered));
929
- if (actual < absoluteThreshold) {
930
- // If everything was covered, set new threshold to 100% (since a threshold of 0 would be considered as 0%)
931
- const updatedThreshold = actual === 0 ? 100 : actual * -1;
932
- thresholdsToUpdate.push([key, updatedThreshold]);
933
- }
934
- }
935
- }
936
- if (thresholdsToUpdate.length === 0) continue;
937
- updatedThresholds = true;
938
- const thresholdFormatter = typeof this.options.thresholds?.autoUpdate === "function" ? this.options.thresholds?.autoUpdate : (value) => value;
939
- for (const [threshold, newValue] of thresholdsToUpdate) {
940
- const formattedValue = thresholdFormatter(newValue);
941
- if (name === GLOBAL_THRESHOLDS_KEY) config.test.coverage.thresholds[threshold] = formattedValue;
942
- else {
943
- const glob = config.test.coverage.thresholds[name];
944
- glob[threshold] = formattedValue;
945
- }
946
- }
947
- }
948
- if (updatedThresholds) {
949
- this.ctx.logger.log("Updating thresholds to configuration file. You may want to push with updated coverage thresholds.");
950
- onUpdate();
951
- }
952
- }
953
- async mergeReports(coverageMaps) {
954
- const coverageMap = this.createCoverageMap();
955
- for (const coverage of coverageMaps) coverageMap.merge(coverage);
956
- await this.generateReports(coverageMap, true);
957
- }
958
- hasTerminalReporter(reporters) {
959
- return reporters.some(([reporter]) => reporter === "text" || reporter === "text-summary" || reporter === "text-lcov" || reporter === "teamcity");
960
- }
961
- toSlices(array, size) {
962
- return array.reduce((chunks, item) => {
963
- const index = Math.max(0, chunks.length - 1);
964
- const lastChunk = chunks[index] || [];
965
- chunks[index] = lastChunk;
966
- if (lastChunk.length >= size) chunks.push([item]);
967
- else lastChunk.push(item);
968
- return chunks;
969
- }, []);
970
- }
971
- // TODO: should this be abstracted in `project`/`vitest` instead?
972
- // if we decide to keep `viteModuleRunner: false`, we will need to abstract transformation in both main thread and tests
973
- // custom --import=module.registerHooks need to be transformed as well somehow
974
- async transformFile(url, project, viteEnvironment) {
975
- const config = project.config;
976
- // vite is disabled, should transform manually if possible
977
- if (config.experimental.viteModuleRunner === false) {
978
- const pathname = url.split("?")[0];
979
- const filename = pathname.startsWith("file://") ? fileURLToPath(pathname) : pathname;
980
- const extension = path.extname(filename);
981
- if (!(extension === ".ts" || extension === ".mts" || extension === ".cts")) return {
982
- code: await promises.readFile(filename, "utf-8"),
983
- map: null
984
- };
985
- if (!module$1.stripTypeScriptTypes) throw new Error(`Cannot parse '${url}' because "module.stripTypeScriptTypes" is not supported. TypeScript coverage requires Node.js 22.15 or higher. This is NOT a bug of Vitest.`);
986
- const isTransform = process.execArgv.includes("--experimental-transform-types") || config.execArgv.includes("--experimental-transform-types") || process.env.NODE_OPTIONS?.includes("--experimental-transform-types") || config.env?.NODE_OPTIONS?.includes("--experimental-transform-types");
987
- const code = await promises.readFile(filename, "utf-8");
988
- return {
989
- code: module$1.stripTypeScriptTypes(code, { mode: isTransform ? "transform" : "strip" }),
990
- map: null
991
- };
992
- }
993
- if (project.isBrowserEnabled() || viteEnvironment === "__browser__") {
994
- const result = await (project.browser?.vite.environments.client || project.vite.environments.client).transformRequest(url);
995
- if (result) return result;
996
- }
997
- return project.vite.environments[viteEnvironment].transformRequest(url);
998
- }
999
- createUncoveredFileTransformer(ctx) {
1000
- const projects = new Set([...ctx.projects, ctx.getRootProject()]);
1001
- return async (filename) => {
1002
- let lastError;
1003
- for (const project of projects) {
1004
- const root = project.config.root;
1005
- // On Windows root doesn't start with "/" while filenames do
1006
- if (!filename.startsWith(root) && !filename.startsWith(`/${root}`)) continue;
1007
- try {
1008
- const environment = project.config.environment;
1009
- const viteEnvironment = environment === "jsdom" || environment === "happy-dom" ? "client" : "ssr";
1010
- return await this.transformFile(filename, project, viteEnvironment);
1011
- } catch (err) {
1012
- lastError = err;
1013
- }
1014
- }
1015
- // All vite servers failed to transform the file
1016
- throw lastError;
1017
- };
1018
- }
1019
- }
1020
- /**
1021
- * Narrow down `unknown` glob thresholds to resolved ones
1022
- */
1023
- function resolveGlobThresholds(thresholds) {
1024
- if (!thresholds || typeof thresholds !== "object") return {};
1025
- if (100 in thresholds && thresholds[100] === true) return {
1026
- lines: 100,
1027
- branches: 100,
1028
- functions: 100,
1029
- statements: 100
1030
- };
1031
- return {
1032
- lines: "lines" in thresholds && typeof thresholds.lines === "number" ? thresholds.lines : void 0,
1033
- branches: "branches" in thresholds && typeof thresholds.branches === "number" ? thresholds.branches : void 0,
1034
- functions: "functions" in thresholds && typeof thresholds.functions === "number" ? thresholds.functions : void 0,
1035
- statements: "statements" in thresholds && typeof thresholds.statements === "number" ? thresholds.statements : void 0
1036
- };
1037
- }
1038
- function assertConfigurationModule(config) {
1039
- try {
1040
- // @ts-expect-error -- Intentional unsafe null pointer check as wrapped in try-catch
1041
- if (typeof config.test.coverage.thresholds !== "object") throw new TypeError("Expected config.test.coverage.thresholds to be an object");
1042
- } catch (error) {
1043
- const message = error instanceof Error ? error.message : String(error);
1044
- throw new Error(`Unable to parse thresholds from configuration file: ${message}`);
1045
- }
1046
- }
1047
- function resolveConfig(configModule) {
1048
- const mod = configModule.exports.default;
1049
- try {
1050
- // Check for "export default { test: {...} }"
1051
- if (mod.$type === "object") return mod;
1052
- // "export default defineConfig(...)"
1053
- let config = resolveDefineConfig(mod);
1054
- if (config) return config;
1055
- // "export default mergeConfig(..., defineConfig(...))"
1056
- if (mod.$type === "function-call" && mod.$callee === "mergeConfig") {
1057
- config = resolveMergeConfig(mod);
1058
- if (config) return config;
1059
- }
1060
- } catch (error) {
1061
- // Reduce magicast's verbose errors to readable ones
1062
- throw new Error(error instanceof Error ? error.message : String(error));
1063
- }
1064
- throw new Error("Failed to update coverage thresholds. Configuration file is too complex.");
1065
- }
1066
- function resolveDefineConfig(mod) {
1067
- if (mod.$type === "function-call" && mod.$callee === "defineConfig") {
1068
- // "export default defineConfig({ test: {...} })"
1069
- if (mod.$args[0].$type === "object") return mod.$args[0];
1070
- if (mod.$args[0].$type === "arrow-function-expression") {
1071
- if (mod.$args[0].$body.$type === "object")
1072
- // "export default defineConfig(() => ({ test: {...} }))"
1073
- return mod.$args[0].$body;
1074
- // "export default defineConfig(() => mergeConfig({...}, ...))"
1075
- const config = resolveMergeConfig(mod.$args[0].$body);
1076
- if (config) return config;
1077
- }
1078
- }
1079
- }
1080
- function resolveMergeConfig(mod) {
1081
- if (mod.$type === "function-call" && mod.$callee === "mergeConfig") for (const arg of mod.$args) {
1082
- const config = resolveDefineConfig(arg);
1083
- if (config) return config;
1084
- }
1085
- }
1086
-
1087
- export { BaseCoverageProvider as B, RandomSequencer as R, BaseSequencer as a, resolveApiServerConfig as b, getCoverageProvider as g, hash as h, isBrowserEnabled as i, resolveConfig$1 as r };