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.
- package/LICENSE.md +36 -0
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +2 -2
- package/dist/chunks/acorn.B2iPLyUM.js +5958 -0
- package/dist/chunks/{base.CBRNZa3k.js → base.DiopZV8F.js} +48 -14
- package/dist/chunks/{benchmark.B3N2zMcH.js → benchmark.BoqSLF53.js} +1 -1
- package/dist/chunks/{browser.d.8hOapKZr.d.ts → browser.d.BE4kbYok.d.ts} +2 -1
- package/dist/chunks/{cac.B1v3xxoC.js → cac.C4jjt2RX.js} +797 -13
- package/dist/chunks/{cli-api.B4CqEpI6.js → cli-api.ChbI1JU9.js} +322 -124
- package/dist/chunks/{config.d.idH22YSr.d.ts → config.d.Cr1Ep39N.d.ts} +6 -1
- package/dist/chunks/{console.uGgdMhyZ.js → console.CNlG1KsP.js} +2 -2
- package/dist/chunks/{constants.D_Q9UYh-.js → constants.B63TT-Bl.js} +1 -1
- package/dist/chunks/coverage.tyqbzn4W.js +1001 -0
- package/dist/chunks/{creator.C7WwjkuR.js → creator.yyCHuw5R.js} +1 -1
- package/dist/chunks/{global.d.B15mdLcR.d.ts → global.d.JeWMqlOm.d.ts} +1 -1
- package/dist/chunks/{globals.DjuGMoMc.js → globals.C6Ecf1TO.js} +6 -6
- package/dist/chunks/{index.Dm4xqZ0s.js → index.B-iBE_Gx.js} +20 -4
- package/dist/chunks/{coverage.BMlOMIWl.js → index.BCY_7LL2.js} +5 -969
- package/dist/chunks/{index.BiOAd_ki.js → index.CAN630q3.js} +7 -7
- package/dist/chunks/{index.DyBZXrH3.js → index.CFulQRmC.js} +1 -1
- package/dist/chunks/{index.BEFi2-_3.js → index.CouFDptX.js} +2 -2
- package/dist/chunks/{init-forks.CHeQ9Moq.js → init-forks.BnCXPazU.js} +1 -1
- package/dist/chunks/{init-threads.uZiNAuPk.js → init-threads.Cyh2PqXi.js} +1 -1
- package/dist/chunks/{init.DVtKdFty.js → init.B95Mm0Iz.js} +47 -9
- package/dist/chunks/native.mV0-490A.js +148 -0
- package/dist/chunks/nativeModuleMocker.D_q5sFv6.js +206 -0
- package/dist/chunks/nativeModuleRunner.BIakptoF.js +36 -0
- package/dist/chunks/{node.Ce0vMQM7.js → node.CrSEwhm4.js} +1 -1
- package/dist/chunks/{plugin.d.D8KU2PY_.d.ts → plugin.d.C9o5bttz.d.ts} +1 -1
- package/dist/chunks/{reporters.d.Db3MiIWX.d.ts → reporters.d.7faYdkxy.d.ts} +120 -51
- package/dist/chunks/{rpc.HLmECnw_.js → rpc.DcRWTy5G.js} +1 -1
- package/dist/chunks/{rpc.d.RH3apGEf.d.ts → rpc.d.CM7x9-sm.d.ts} +1 -0
- package/dist/chunks/{setup-common.BcqLPsn5.js → setup-common.cvFp-ao9.js} +2 -2
- package/dist/chunks/{startModuleRunner.C5CcWyXW.js → startVitestModuleRunner.BK-u7y4N.js} +163 -372
- package/dist/chunks/{test.prxIahgM.js → test.G82XYNFk.js} +9 -4
- package/dist/chunks/{utils.DvEY5TfP.js → utils.DT4VyRyl.js} +5 -1
- package/dist/chunks/{vm.CrifS09m.js → vm.BdLtzhnj.js} +13 -6
- package/dist/chunks/{worker.d.Bji1eq5g.d.ts → worker.d.CPzI2ZzJ.d.ts} +2 -2
- package/dist/cli.js +4 -3
- package/dist/config.d.ts +8 -8
- package/dist/config.js +1 -1
- package/dist/coverage.d.ts +7 -5
- package/dist/coverage.js +5 -4
- package/dist/index.d.ts +18 -23
- package/dist/index.js +5 -5
- package/dist/module-evaluator.d.ts +10 -1
- package/dist/node.d.ts +9 -9
- package/dist/node.js +18 -16
- package/dist/nodejs-worker-loader.js +41 -0
- package/dist/reporters.d.ts +5 -5
- package/dist/reporters.js +2 -2
- package/dist/runners.d.ts +2 -1
- package/dist/runners.js +4 -4
- package/dist/runtime.js +4 -5
- package/dist/snapshot.js +2 -2
- package/dist/suite.js +2 -2
- package/dist/worker.d.ts +6 -6
- package/dist/worker.js +25 -18
- package/dist/workers/forks.js +21 -14
- package/dist/workers/runVmTests.js +7 -7
- package/dist/workers/threads.js +21 -14
- package/dist/workers/vmForks.js +14 -10
- package/dist/workers/vmThreads.js +14 -10
- package/package.json +17 -14
- package/suppress-warnings.cjs +1 -0
|
@@ -2,20 +2,20 @@ import fs, { promises, existsSync, mkdirSync, readFileSync, statSync, readdirSyn
|
|
|
2
2
|
import { relative, resolve, dirname, join, extname, normalize, basename, isAbsolute } from 'pathe';
|
|
3
3
|
import { C as CoverageProviderMap } from './coverage.D_JHT54q.js';
|
|
4
4
|
import path, { resolve as resolve$1 } from 'node:path';
|
|
5
|
-
import { noop, createDefer, slash, withTrailingSlash,
|
|
5
|
+
import { cleanUrl, noop, unique, createDefer, slash, withTrailingSlash, wrapId, isExternalUrl, unwrapId, toArray, deepMerge, nanoid, deepClone, isPrimitive, notNullish } from '@vitest/utils/helpers';
|
|
6
6
|
import { a as any, p as prompt } from './index.D4KonVSU.js';
|
|
7
|
-
import {
|
|
7
|
+
import { i as isPackageExists, r as resolveModule } from './index.BCY_7LL2.js';
|
|
8
8
|
import * as vite from 'vite';
|
|
9
|
-
import {
|
|
10
|
-
import { A as API_PATH, c as configFiles, d as defaultBrowserPort,
|
|
9
|
+
import { createServer, isFileLoadingAllowed, normalizePath, parseAst, searchForWorkspaceRoot, fetchModule, version, mergeConfig } from 'vite';
|
|
10
|
+
import { A as API_PATH, c as configFiles, d as defaultBrowserPort, b as defaultPort } from './constants.B63TT-Bl.js';
|
|
11
11
|
import * as nodeos from 'node:os';
|
|
12
12
|
import nodeos__default, { tmpdir } from 'node:os';
|
|
13
|
-
import { generateHash as generateHash$1, createTaskName, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, hasFailed, generateFileHash, limitConcurrency, createFileTask as createFileTask$1, getTasks, isTestCase } from '@vitest/runner/utils';
|
|
13
|
+
import { generateHash as generateHash$1, createTaskName, validateTags, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, hasFailed, generateFileHash, limitConcurrency, createFileTask as createFileTask$1, getTasks, isTestCase } from '@vitest/runner/utils';
|
|
14
14
|
import { SnapshotManager } from '@vitest/snapshot/manager';
|
|
15
|
-
import { v as version$1 } from './cac.
|
|
15
|
+
import { v as version$1 } from './cac.C4jjt2RX.js';
|
|
16
16
|
import { performance as performance$1 } from 'node:perf_hooks';
|
|
17
17
|
import { c as createBirpc } from './index.Chj8NDwU.js';
|
|
18
|
-
import { p as parse, d as stringify, e as createIndexLocationsMap, h as TraceMap, o as originalPositionFor, i as ancestor, j as printError, f as formatProjectName, w as withLabel, k as errorBanner, l as divider, m as Typechecker, n as generateCodeFrame, q as escapeRegExp, r as createDefinesScript, R as ReportersMap, u as groupBy, B as BlobReporter, v as readBlobs, x as convertTasksToEvents, H as HangingProcessReporter, y as wildcardPatternToRegExp, z as stdout } from './index.
|
|
18
|
+
import { p as parse, d as stringify, e as createIndexLocationsMap, h as TraceMap, o as originalPositionFor, i as ancestor, j as printError, f as formatProjectName, w as withLabel, k as errorBanner, l as divider, m as Typechecker, n as generateCodeFrame, q as escapeRegExp, r as createDefinesScript, R as ReportersMap, u as groupBy, B as BlobReporter, v as readBlobs, x as convertTasksToEvents, H as HangingProcessReporter, y as wildcardPatternToRegExp, z as stdout } from './index.B-iBE_Gx.js';
|
|
19
19
|
import require$$0$3 from 'events';
|
|
20
20
|
import require$$1$1 from 'https';
|
|
21
21
|
import require$$2 from 'http';
|
|
@@ -29,8 +29,10 @@ import require$$0$1 from 'buffer';
|
|
|
29
29
|
import { g as getDefaultExportFromCjs } from './_commonjsHelpers.D26ty3Ew.js';
|
|
30
30
|
import crypto, { createHash } from 'node:crypto';
|
|
31
31
|
import { rootDir, distDir } from '../path.js';
|
|
32
|
+
import { N as NativeModuleRunner } from './nativeModuleRunner.BIakptoF.js';
|
|
32
33
|
import { T as Traces } from './traces.CCmnQaNT.js';
|
|
33
34
|
import { createDebug } from 'obug';
|
|
35
|
+
import { h as hash, R as RandomSequencer, i as isBrowserEnabled, r as resolveConfig, g as getCoverageProvider, a as resolveApiServerConfig } from './coverage.tyqbzn4W.js';
|
|
34
36
|
import { rm, readFile, writeFile, rename, stat, unlink, mkdir, copyFile } from 'node:fs/promises';
|
|
35
37
|
import c from 'tinyrainbow';
|
|
36
38
|
import { VitestModuleEvaluator } from '#module-evaluator';
|
|
@@ -53,7 +55,7 @@ import { c as configDefaults } from './defaults.BOqNVLsY.js';
|
|
|
53
55
|
import { KNOWN_ASSET_RE } from '@vitest/utils/constants';
|
|
54
56
|
import { findNearestPackageData } from '@vitest/utils/resolver';
|
|
55
57
|
import * as esModuleLexer from 'es-module-lexer';
|
|
56
|
-
import { a as BenchmarkReportsMap } from './index.
|
|
58
|
+
import { a as BenchmarkReportsMap } from './index.CFulQRmC.js';
|
|
57
59
|
import assert$1 from 'node:assert';
|
|
58
60
|
import { serializeValue } from '@vitest/utils/serialize';
|
|
59
61
|
import { parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
@@ -5036,17 +5038,40 @@ function requireWebsocketServer () {
|
|
|
5036
5038
|
var websocketServerExports = requireWebsocketServer();
|
|
5037
5039
|
var WebSocketServer = /*@__PURE__*/getDefaultExportFromCjs(websocketServerExports);
|
|
5038
5040
|
|
|
5041
|
+
async function createViteServer(inlineConfig) {
|
|
5042
|
+
// Vite prints an error (https://github.com/vitejs/vite/issues/14328)
|
|
5043
|
+
// But Vitest works correctly either way
|
|
5044
|
+
const error = console.error;
|
|
5045
|
+
console.error = (...args) => {
|
|
5046
|
+
if (typeof args[0] === "string" && args[0].includes("WebSocket server error:")) return;
|
|
5047
|
+
error(...args);
|
|
5048
|
+
};
|
|
5049
|
+
const server = await createServer(inlineConfig);
|
|
5050
|
+
console.error = error;
|
|
5051
|
+
return server;
|
|
5052
|
+
}
|
|
5053
|
+
function isFileServingAllowed(configOrUrl, urlOrServer) {
|
|
5054
|
+
const config = typeof urlOrServer === "string" ? configOrUrl : urlOrServer.config;
|
|
5055
|
+
const url = typeof urlOrServer === "string" ? urlOrServer : configOrUrl;
|
|
5056
|
+
if (!config.server.fs.strict) return true;
|
|
5057
|
+
return isFileLoadingAllowed(config, fsPathFromUrl(url));
|
|
5058
|
+
}
|
|
5059
|
+
const FS_PREFIX = "/@fs/";
|
|
5060
|
+
const VOLUME_RE = /^[A-Z]:/i;
|
|
5061
|
+
function fsPathFromId(id) {
|
|
5062
|
+
const fsPath = normalizePath(id.startsWith(FS_PREFIX) ? id.slice(5) : id);
|
|
5063
|
+
return fsPath[0] === "/" || VOLUME_RE.test(fsPath) ? fsPath : `/${fsPath}`;
|
|
5064
|
+
}
|
|
5065
|
+
function fsPathFromUrl(url) {
|
|
5066
|
+
return fsPathFromId(cleanUrl(url));
|
|
5067
|
+
}
|
|
5068
|
+
|
|
5039
5069
|
function getTestFileEnvironment(project, testFile, browser = false) {
|
|
5040
|
-
|
|
5041
|
-
if (browser) environment = project.browser?.vite.environments.client;
|
|
5070
|
+
if (browser) return project.browser?.vite.environments.client;
|
|
5042
5071
|
else for (const name in project.vite.environments) {
|
|
5043
5072
|
const env = project.vite.environments[name];
|
|
5044
|
-
if (env.moduleGraph.getModuleById(testFile))
|
|
5045
|
-
environment = env;
|
|
5046
|
-
break;
|
|
5047
|
-
}
|
|
5073
|
+
if (env.moduleGraph.getModuleById(testFile)) return env;
|
|
5048
5074
|
}
|
|
5049
|
-
return environment;
|
|
5050
5075
|
}
|
|
5051
5076
|
|
|
5052
5077
|
async function getModuleGraph(ctx, projectName, testFilePath, browser = false) {
|
|
@@ -5054,7 +5079,7 @@ async function getModuleGraph(ctx, projectName, testFilePath, browser = false) {
|
|
|
5054
5079
|
const externalized = /* @__PURE__ */ new Set();
|
|
5055
5080
|
const inlined = /* @__PURE__ */ new Set();
|
|
5056
5081
|
const project = ctx.getProjectByName(projectName);
|
|
5057
|
-
const environment = getTestFileEnvironment(project, testFilePath, browser);
|
|
5082
|
+
const environment = project.config.experimental.viteModuleRunner === false ? project.vite.environments.__vitest__ : getTestFileEnvironment(project, testFilePath, browser);
|
|
5058
5083
|
if (!environment) throw new Error(`Cannot find environment for ${testFilePath}`);
|
|
5059
5084
|
const seen = /* @__PURE__ */ new Map();
|
|
5060
5085
|
function get(mod) {
|
|
@@ -5185,7 +5210,7 @@ function setup(ctx, _server) {
|
|
|
5185
5210
|
async getExternalResult(moduleId, testFileTaskId) {
|
|
5186
5211
|
const testModule = ctx.state.getReportedEntityById(testFileTaskId);
|
|
5187
5212
|
if (!testModule) return;
|
|
5188
|
-
if (!isFileServingAllowed
|
|
5213
|
+
if (!isFileServingAllowed(testModule.project.vite.config, moduleId)) return;
|
|
5189
5214
|
const result = {};
|
|
5190
5215
|
try {
|
|
5191
5216
|
result.source = await promises.readFile(moduleId, "utf-8");
|
|
@@ -5195,7 +5220,7 @@ function setup(ctx, _server) {
|
|
|
5195
5220
|
async getTransformResult(projectName, moduleId, testFileTaskId, browser = false) {
|
|
5196
5221
|
const project = ctx.getProjectByName(projectName);
|
|
5197
5222
|
const testModule = ctx.state.getReportedEntityById(testFileTaskId);
|
|
5198
|
-
if (!testModule || !isFileServingAllowed
|
|
5223
|
+
if (!testModule || !isFileServingAllowed(project.vite.config, moduleId)) return;
|
|
5199
5224
|
const environment = getTestFileEnvironment(project, testModule.moduleId, browser);
|
|
5200
5225
|
const moduleNode = environment?.moduleGraph.getModuleById(moduleId);
|
|
5201
5226
|
if (!environment || !moduleNode?.transformResult) return;
|
|
@@ -5335,6 +5360,54 @@ function createDebugger(namespace) {
|
|
|
5335
5360
|
if (debug.enabled) return debug;
|
|
5336
5361
|
}
|
|
5337
5362
|
|
|
5363
|
+
async function getSpecificationsOptions(specifications) {
|
|
5364
|
+
const environments = /* @__PURE__ */ new WeakMap();
|
|
5365
|
+
const cache = /* @__PURE__ */ new Map();
|
|
5366
|
+
const tags = /* @__PURE__ */ new WeakMap();
|
|
5367
|
+
await Promise.all(specifications.map(async (spec) => {
|
|
5368
|
+
const { moduleId: filepath, project, pool } = spec;
|
|
5369
|
+
// browser pool handles its own environment
|
|
5370
|
+
if (pool === "browser") return;
|
|
5371
|
+
// reuse if projects have the same test files
|
|
5372
|
+
let code = cache.get(filepath);
|
|
5373
|
+
if (!code) {
|
|
5374
|
+
code = await promises.readFile(filepath, "utf-8").catch(() => "");
|
|
5375
|
+
cache.set(filepath, code);
|
|
5376
|
+
}
|
|
5377
|
+
const { env = project.config.environment || "node", envOptions, tags: specTags = [] } = detectCodeBlock(code);
|
|
5378
|
+
tags.set(spec, specTags);
|
|
5379
|
+
const environment = {
|
|
5380
|
+
name: env,
|
|
5381
|
+
options: envOptions ? { [env === "happy-dom" ? "happyDOM" : env]: envOptions } : null
|
|
5382
|
+
};
|
|
5383
|
+
environments.set(spec, environment);
|
|
5384
|
+
}));
|
|
5385
|
+
return {
|
|
5386
|
+
environments,
|
|
5387
|
+
tags
|
|
5388
|
+
};
|
|
5389
|
+
}
|
|
5390
|
+
function detectCodeBlock(content) {
|
|
5391
|
+
const env = content.match(/@(?:vitest|jest)-environment\s+([\w-]+)\b/)?.[1];
|
|
5392
|
+
let envOptionsJson = content.match(/@(?:vitest|jest)-environment-options\s+(.+)/)?.[1];
|
|
5393
|
+
if (envOptionsJson?.endsWith("*/"))
|
|
5394
|
+
// Trim closing Docblock characters the above regex might have captured
|
|
5395
|
+
envOptionsJson = envOptionsJson.slice(0, -2);
|
|
5396
|
+
const envOptions = JSON.parse(envOptionsJson || "null");
|
|
5397
|
+
const tags = [];
|
|
5398
|
+
let tagMatch;
|
|
5399
|
+
// eslint-disable-next-line no-cond-assign
|
|
5400
|
+
while (tagMatch = content.match(/(\/\/|\*)\s*@module-tag\s+([\w\-/]+)\b/)) {
|
|
5401
|
+
tags.push(tagMatch[2]);
|
|
5402
|
+
content = content.slice(tagMatch.index + tagMatch[0].length);
|
|
5403
|
+
}
|
|
5404
|
+
return {
|
|
5405
|
+
env,
|
|
5406
|
+
envOptions,
|
|
5407
|
+
tags
|
|
5408
|
+
};
|
|
5409
|
+
}
|
|
5410
|
+
|
|
5338
5411
|
const debug$1 = createDebugger("vitest:ast-collect-info");
|
|
5339
5412
|
const verbose = createDebugger("vitest:ast-collect-verbose");
|
|
5340
5413
|
function isTestFunctionName(name) {
|
|
@@ -5408,7 +5481,23 @@ function astParseFile(filepath, code) {
|
|
|
5408
5481
|
const property = callee.tag?.property?.name;
|
|
5409
5482
|
isDynamicEach = property === "each" || property === "for";
|
|
5410
5483
|
}
|
|
5411
|
-
|
|
5484
|
+
// Extract tags from the second argument if it's an options object
|
|
5485
|
+
const tags = [];
|
|
5486
|
+
const secondArg = node.arguments?.[1];
|
|
5487
|
+
if (secondArg?.type === "ObjectExpression") {
|
|
5488
|
+
const tagsProperty = secondArg.properties?.find((p) => p.type === "Property" && p.key?.type === "Identifier" && p.key.name === "tags");
|
|
5489
|
+
if (tagsProperty) {
|
|
5490
|
+
const tagsValue = tagsProperty.value;
|
|
5491
|
+
if (tagsValue?.type === "Literal" && typeof tagsValue.value === "string")
|
|
5492
|
+
// tags: 'single-tag'
|
|
5493
|
+
tags.push(tagsValue.value);
|
|
5494
|
+
else if (tagsValue?.type === "ArrayExpression") {
|
|
5495
|
+
// tags: ['tag1', 'tag2']
|
|
5496
|
+
for (const element of tagsValue.elements || []) if (element?.type === "Literal" && typeof element.value === "string") tags.push(element.value);
|
|
5497
|
+
}
|
|
5498
|
+
}
|
|
5499
|
+
}
|
|
5500
|
+
debug$1?.("Found", name, message, `(${mode})`, tags.length ? `[${tags.join(", ")}]` : "");
|
|
5412
5501
|
definitions.push({
|
|
5413
5502
|
start,
|
|
5414
5503
|
end,
|
|
@@ -5416,7 +5505,8 @@ function astParseFile(filepath, code) {
|
|
|
5416
5505
|
type: isTestFunctionName(name) ? "test" : "suite",
|
|
5417
5506
|
mode,
|
|
5418
5507
|
task: null,
|
|
5419
|
-
dynamic: isDynamicEach
|
|
5508
|
+
dynamic: isDynamicEach,
|
|
5509
|
+
tags
|
|
5420
5510
|
});
|
|
5421
5511
|
} });
|
|
5422
5512
|
return {
|
|
@@ -5462,22 +5552,23 @@ function serializeError(ctx, error) {
|
|
|
5462
5552
|
message: error.message
|
|
5463
5553
|
}];
|
|
5464
5554
|
}
|
|
5465
|
-
function createFileTask(testFilepath, code, requestMap,
|
|
5555
|
+
function createFileTask(testFilepath, code, requestMap, config, filepath, fileTags) {
|
|
5466
5556
|
const { definitions, ast } = astParseFile(testFilepath, code);
|
|
5467
5557
|
const file = {
|
|
5468
|
-
filepath
|
|
5558
|
+
filepath,
|
|
5469
5559
|
type: "suite",
|
|
5470
|
-
id: /* @__PURE__ */ generateHash$1(`${testFilepath}${
|
|
5560
|
+
id: /* @__PURE__ */ generateHash$1(`${testFilepath}${config.name || ""}`),
|
|
5471
5561
|
name: testFilepath,
|
|
5472
5562
|
fullName: testFilepath,
|
|
5473
5563
|
mode: "run",
|
|
5474
5564
|
tasks: [],
|
|
5475
5565
|
start: ast.start,
|
|
5476
5566
|
end: ast.end,
|
|
5477
|
-
projectName:
|
|
5567
|
+
projectName: config.name,
|
|
5478
5568
|
meta: {},
|
|
5479
5569
|
pool: "browser",
|
|
5480
|
-
file: null
|
|
5570
|
+
file: null,
|
|
5571
|
+
tags: fileTags || []
|
|
5481
5572
|
};
|
|
5482
5573
|
file.file = file;
|
|
5483
5574
|
const indexMap = createIndexLocationsMap(code);
|
|
@@ -5508,6 +5599,7 @@ function createFileTask(testFilepath, code, requestMap, options) {
|
|
|
5508
5599
|
};
|
|
5509
5600
|
} else debug$1?.("Cannot find original location for", definition.type, definition.name, `${processedLocation.column}:${processedLocation.line}`);
|
|
5510
5601
|
} else debug$1?.("Cannot find original location for", definition.type, definition.name, `${definition.start}`);
|
|
5602
|
+
const taskTags = unique([...latestSuite.tags || [], ...definition.tags]);
|
|
5511
5603
|
if (definition.type === "suite") {
|
|
5512
5604
|
const task = {
|
|
5513
5605
|
type: definition.type,
|
|
@@ -5516,6 +5608,7 @@ function createFileTask(testFilepath, code, requestMap, options) {
|
|
|
5516
5608
|
file,
|
|
5517
5609
|
tasks: [],
|
|
5518
5610
|
mode,
|
|
5611
|
+
each: definition.dynamic,
|
|
5519
5612
|
name: definition.name,
|
|
5520
5613
|
fullName: createTaskName([latestSuite.fullName, definition.name]),
|
|
5521
5614
|
fullTestName: createTaskName([latestSuite.fullTestName, definition.name]),
|
|
@@ -5523,18 +5616,21 @@ function createFileTask(testFilepath, code, requestMap, options) {
|
|
|
5523
5616
|
start: definition.start,
|
|
5524
5617
|
location,
|
|
5525
5618
|
dynamic: definition.dynamic,
|
|
5526
|
-
meta: {}
|
|
5619
|
+
meta: {},
|
|
5620
|
+
tags: taskTags
|
|
5527
5621
|
};
|
|
5528
5622
|
definition.task = task;
|
|
5529
5623
|
latestSuite.tasks.push(task);
|
|
5530
5624
|
lastSuite = task;
|
|
5531
5625
|
return;
|
|
5532
5626
|
}
|
|
5627
|
+
validateTags(config, taskTags);
|
|
5533
5628
|
const task = {
|
|
5534
5629
|
type: definition.type,
|
|
5535
5630
|
id: "",
|
|
5536
5631
|
suite: latestSuite,
|
|
5537
5632
|
file,
|
|
5633
|
+
each: definition.dynamic,
|
|
5538
5634
|
mode,
|
|
5539
5635
|
context: {},
|
|
5540
5636
|
name: definition.name,
|
|
@@ -5547,20 +5643,21 @@ function createFileTask(testFilepath, code, requestMap, options) {
|
|
|
5547
5643
|
meta: {},
|
|
5548
5644
|
timeout: 0,
|
|
5549
5645
|
annotations: [],
|
|
5550
|
-
artifacts: []
|
|
5646
|
+
artifacts: [],
|
|
5647
|
+
tags: taskTags
|
|
5551
5648
|
};
|
|
5552
5649
|
definition.task = task;
|
|
5553
5650
|
latestSuite.tasks.push(task);
|
|
5554
5651
|
});
|
|
5555
5652
|
calculateSuiteHash(file);
|
|
5556
5653
|
const hasOnly = someTasksAreOnly(file);
|
|
5557
|
-
interpretTaskModes(file,
|
|
5654
|
+
interpretTaskModes(file, config.testNamePattern, void 0, void 0, void 0, hasOnly, false, config.allowOnly);
|
|
5558
5655
|
markDynamicTests(file.tasks);
|
|
5559
5656
|
if (!file.tasks.length) file.result = {
|
|
5560
5657
|
state: "fail",
|
|
5561
5658
|
errors: [{
|
|
5562
5659
|
name: "Error",
|
|
5563
|
-
message: `No test suite found in file ${
|
|
5660
|
+
message: `No test suite found in file ${filepath}`
|
|
5564
5661
|
}]
|
|
5565
5662
|
};
|
|
5566
5663
|
return file;
|
|
@@ -5572,18 +5669,17 @@ async function astCollectTests(project, filepath) {
|
|
|
5572
5669
|
debug$1?.("Cannot parse", testFilepath, "(vite didn't return anything)");
|
|
5573
5670
|
return createFailedFileTask(project, filepath, /* @__PURE__ */ new Error(`Failed to parse ${testFilepath}. Vite didn't return anything.`));
|
|
5574
5671
|
}
|
|
5575
|
-
return createFileTask(testFilepath, request.code, request.map,
|
|
5576
|
-
name: project.config.name,
|
|
5577
|
-
filepath,
|
|
5578
|
-
allowOnly: project.config.allowOnly,
|
|
5579
|
-
testNamePattern: project.config.testNamePattern,
|
|
5580
|
-
pool: project.browser ? "browser" : project.config.pool
|
|
5581
|
-
});
|
|
5672
|
+
return createFileTask(testFilepath, request.code, request.map, project.serializedConfig, filepath, request.fileTags);
|
|
5582
5673
|
}
|
|
5583
5674
|
async function transformSSR(project, filepath) {
|
|
5584
|
-
const
|
|
5585
|
-
|
|
5586
|
-
|
|
5675
|
+
const { env: pragmaEnv, tags: fileTags } = detectCodeBlock(await promises.readFile(filepath, "utf-8").catch(() => ""));
|
|
5676
|
+
// Use environment from pragma if defined, otherwise fall back to config
|
|
5677
|
+
const environment = pragmaEnv || project.config.environment;
|
|
5678
|
+
const transformResult = await (environment === "jsdom" || environment === "happy-dom" ? project.vite.environments.client : project.vite.environments.ssr).transformRequest(filepath);
|
|
5679
|
+
return transformResult ? {
|
|
5680
|
+
...transformResult,
|
|
5681
|
+
fileTags
|
|
5682
|
+
} : null;
|
|
5587
5683
|
}
|
|
5588
5684
|
function markDynamicTests(tasks) {
|
|
5589
5685
|
for (const task of tasks) {
|
|
@@ -6571,6 +6667,25 @@ class Logger {
|
|
|
6571
6667
|
this._highlights.set(filename, code);
|
|
6572
6668
|
return code;
|
|
6573
6669
|
}
|
|
6670
|
+
printNoTestTagsFound() {
|
|
6671
|
+
this.error(c.bgRed(" ERROR "), c.red("No test tags found in any project. Exiting with code 1."));
|
|
6672
|
+
}
|
|
6673
|
+
printTags() {
|
|
6674
|
+
const vitest = this.ctx;
|
|
6675
|
+
const rootProject = vitest.getRootProject();
|
|
6676
|
+
const projects = [rootProject, ...vitest.projects.filter((p) => p !== rootProject)];
|
|
6677
|
+
if (!projects.some((p) => p.config.tags && p.config.tags.length > 0)) {
|
|
6678
|
+
process.exitCode = 1;
|
|
6679
|
+
return this.printNoTestTagsFound();
|
|
6680
|
+
}
|
|
6681
|
+
for (const project of projects) {
|
|
6682
|
+
if (project.name) this.log(formatProjectName(project, ""));
|
|
6683
|
+
project.config.tags.forEach((tag) => {
|
|
6684
|
+
const tagLog = `${tag.name}${tag.description ? `: ${tag.description}` : ""}`;
|
|
6685
|
+
this.log(` ${tagLog}`);
|
|
6686
|
+
});
|
|
6687
|
+
}
|
|
6688
|
+
}
|
|
6574
6689
|
printNoTestFound(filters) {
|
|
6575
6690
|
const config = this.ctx.config;
|
|
6576
6691
|
if (config.watch && (config.changed || config.related?.length)) this.log(`No affected ${config.mode} files found\n`);
|
|
@@ -7003,35 +7118,6 @@ function stringToBytes(input, percentageReference) {
|
|
|
7003
7118
|
return null;
|
|
7004
7119
|
}
|
|
7005
7120
|
|
|
7006
|
-
async function getSpecificationsEnvironments(specifications) {
|
|
7007
|
-
const environments = /* @__PURE__ */ new WeakMap();
|
|
7008
|
-
const cache = /* @__PURE__ */ new Map();
|
|
7009
|
-
await Promise.all(specifications.map(async (spec) => {
|
|
7010
|
-
const { moduleId: filepath, project } = spec;
|
|
7011
|
-
// reuse if projects have the same test files
|
|
7012
|
-
let code = cache.get(filepath);
|
|
7013
|
-
if (!code) {
|
|
7014
|
-
code = await promises.readFile(filepath, "utf-8");
|
|
7015
|
-
cache.set(filepath, code);
|
|
7016
|
-
}
|
|
7017
|
-
// 1. Check for control comments in the file
|
|
7018
|
-
let env = code.match(/@(?:vitest|jest)-environment\s+([\w-]+)\b/)?.[1];
|
|
7019
|
-
// 2. Fallback to global env
|
|
7020
|
-
env ||= project.config.environment || "node";
|
|
7021
|
-
let envOptionsJson = code.match(/@(?:vitest|jest)-environment-options\s+(.+)/)?.[1];
|
|
7022
|
-
if (envOptionsJson?.endsWith("*/"))
|
|
7023
|
-
// Trim closing Docblock characters the above regex might have captured
|
|
7024
|
-
envOptionsJson = envOptionsJson.slice(0, -2);
|
|
7025
|
-
const envOptions = JSON.parse(envOptionsJson || "null");
|
|
7026
|
-
const environment = {
|
|
7027
|
-
name: env,
|
|
7028
|
-
options: envOptions ? { [env === "happy-dom" ? "happyDOM" : env]: envOptions } : null
|
|
7029
|
-
};
|
|
7030
|
-
environments.set(spec, environment);
|
|
7031
|
-
}));
|
|
7032
|
-
return environments;
|
|
7033
|
-
}
|
|
7034
|
-
|
|
7035
7121
|
const debug = createDebugger("vitest:browser:pool");
|
|
7036
7122
|
function createBrowserPool(vitest) {
|
|
7037
7123
|
const providers = /* @__PURE__ */ new Set();
|
|
@@ -7059,13 +7145,29 @@ function createBrowserPool(vitest) {
|
|
|
7059
7145
|
};
|
|
7060
7146
|
const runWorkspaceTests = async (method, specs) => {
|
|
7061
7147
|
const groupedFiles = /* @__PURE__ */ new Map();
|
|
7062
|
-
|
|
7148
|
+
const testFilesCode = /* @__PURE__ */ new Map();
|
|
7149
|
+
const testFileTags = /* @__PURE__ */ new WeakMap();
|
|
7150
|
+
await Promise.all(specs.map(async (spec) => {
|
|
7151
|
+
let code = testFilesCode.get(spec.moduleId);
|
|
7152
|
+
// TODO: this really should be done only once when collecting specifications
|
|
7153
|
+
if (code == null) {
|
|
7154
|
+
code = await readFile(spec.moduleId, "utf-8").catch(() => "");
|
|
7155
|
+
testFilesCode.set(spec.moduleId, code);
|
|
7156
|
+
}
|
|
7157
|
+
const { tags } = detectCodeBlock(code);
|
|
7158
|
+
testFileTags.set(spec, tags);
|
|
7159
|
+
}));
|
|
7160
|
+
// to keep the sorting, we need to iterate over specs separately
|
|
7161
|
+
for (const spec of specs) {
|
|
7162
|
+
const { project, moduleId, testLines, testIds, testNamePattern, testTagsFilter } = spec;
|
|
7063
7163
|
const files = groupedFiles.get(project) || [];
|
|
7064
7164
|
files.push({
|
|
7065
7165
|
filepath: moduleId,
|
|
7066
7166
|
testLocations: testLines,
|
|
7067
7167
|
testIds,
|
|
7068
|
-
testNamePattern
|
|
7168
|
+
testNamePattern,
|
|
7169
|
+
testTagsFilter,
|
|
7170
|
+
fileTags: testFileTags.get(spec)
|
|
7069
7171
|
});
|
|
7070
7172
|
groupedFiles.set(project, files);
|
|
7071
7173
|
}
|
|
@@ -7189,7 +7291,7 @@ class BrowserPool {
|
|
|
7189
7291
|
let page = this._traces.$(`vitest.browser.open`, {
|
|
7190
7292
|
context: this._otel.context,
|
|
7191
7293
|
attributes: { "vitest.browser.session_id": sessionId }
|
|
7192
|
-
}, () => this.openPage(sessionId));
|
|
7294
|
+
}, () => this.openPage(sessionId, { parallel: workerCount > 1 }));
|
|
7193
7295
|
page = page.then(() => {
|
|
7194
7296
|
// start running tests on the page when it's ready
|
|
7195
7297
|
this.runNextTest(method, sessionId);
|
|
@@ -7200,14 +7302,14 @@ class BrowserPool {
|
|
|
7200
7302
|
debug?.("all sessions are created");
|
|
7201
7303
|
return this._promise;
|
|
7202
7304
|
}
|
|
7203
|
-
async openPage(sessionId) {
|
|
7305
|
+
async openPage(sessionId, options) {
|
|
7204
7306
|
const sessionPromise = this.project.vitest._browserSessions.createSession(sessionId, this.project, this);
|
|
7205
7307
|
const browser = this.project.browser;
|
|
7206
7308
|
const url = new URL("/__vitest_test__/", this.options.origin);
|
|
7207
7309
|
url.searchParams.set("sessionId", sessionId);
|
|
7208
7310
|
const otelCarrier = this._traces.getContextCarrier();
|
|
7209
7311
|
if (otelCarrier) url.searchParams.set("otelCarrier", JSON.stringify(otelCarrier));
|
|
7210
|
-
const pagePromise = browser.provider.openPage(sessionId, url.toString());
|
|
7312
|
+
const pagePromise = browser.provider.openPage(sessionId, url.toString(), options);
|
|
7211
7313
|
await Promise.all([sessionPromise, pagePromise]);
|
|
7212
7314
|
}
|
|
7213
7315
|
getOrchestrator(sessionId) {
|
|
@@ -7377,6 +7479,34 @@ function createMethodsRPC(project, methodsOptions = {}) {
|
|
|
7377
7479
|
},
|
|
7378
7480
|
getCountOfFailedTests() {
|
|
7379
7481
|
return vitest.state.getCountOfFailedTests();
|
|
7482
|
+
},
|
|
7483
|
+
ensureModuleGraphEntry(id, importer) {
|
|
7484
|
+
const filepath = id.startsWith("file:") ? fileURLToPath(id) : id;
|
|
7485
|
+
const importerPath = importer.startsWith("file:") ? fileURLToPath(importer) : importer;
|
|
7486
|
+
// environment itself doesn't matter
|
|
7487
|
+
const moduleGraph = project.vite.environments.__vitest__?.moduleGraph;
|
|
7488
|
+
if (!moduleGraph) {
|
|
7489
|
+
// TODO: is it possible?
|
|
7490
|
+
console.error("no module graph for", id);
|
|
7491
|
+
return;
|
|
7492
|
+
}
|
|
7493
|
+
const importerNode = moduleGraph.getModuleById(importerPath) || moduleGraph.createFileOnlyEntry(importerPath);
|
|
7494
|
+
const moduleNode = moduleGraph.getModuleById(filepath) || moduleGraph.createFileOnlyEntry(filepath);
|
|
7495
|
+
if (!moduleGraph.idToModuleMap.has(importerPath)) {
|
|
7496
|
+
importerNode.id = importerPath;
|
|
7497
|
+
moduleGraph.idToModuleMap.set(importerPath, importerNode);
|
|
7498
|
+
}
|
|
7499
|
+
if (!moduleGraph.idToModuleMap.has(filepath)) {
|
|
7500
|
+
moduleNode.id = filepath;
|
|
7501
|
+
moduleGraph.idToModuleMap.set(filepath, moduleNode);
|
|
7502
|
+
}
|
|
7503
|
+
// this is checked by the "printError" function - TODO: is there a better way?
|
|
7504
|
+
moduleNode.transformResult = {
|
|
7505
|
+
code: " ",
|
|
7506
|
+
map: null
|
|
7507
|
+
};
|
|
7508
|
+
importerNode.importedModules.add(moduleNode);
|
|
7509
|
+
moduleNode.importers.add(importerNode);
|
|
7380
7510
|
}
|
|
7381
7511
|
};
|
|
7382
7512
|
}
|
|
@@ -8218,7 +8348,7 @@ function createPool(ctx) {
|
|
|
8218
8348
|
const taskGroups = [];
|
|
8219
8349
|
let workerId = 0;
|
|
8220
8350
|
const sorted = await sequencer.sort(specs);
|
|
8221
|
-
const environments = await
|
|
8351
|
+
const { environments, tags } = await getSpecificationsOptions(specs);
|
|
8222
8352
|
const groups = groupSpecs(sorted, environments);
|
|
8223
8353
|
const projectEnvs = /* @__PURE__ */ new WeakMap();
|
|
8224
8354
|
const projectExecArgvs = /* @__PURE__ */ new WeakMap();
|
|
@@ -8260,9 +8390,11 @@ function createPool(ctx) {
|
|
|
8260
8390
|
context: {
|
|
8261
8391
|
files: specs.map((spec) => ({
|
|
8262
8392
|
filepath: spec.moduleId,
|
|
8393
|
+
fileTags: tags.get(spec),
|
|
8263
8394
|
testLocations: spec.testLines,
|
|
8264
8395
|
testNamePattern: spec.testNamePattern,
|
|
8265
|
-
testIds: spec.testIds
|
|
8396
|
+
testIds: spec.testIds,
|
|
8397
|
+
testTagsFilter: spec.testTagsFilter
|
|
8266
8398
|
})),
|
|
8267
8399
|
invalidates,
|
|
8268
8400
|
providedContext: project.getProvidedContext(),
|
|
@@ -8536,8 +8668,13 @@ function serializeConfig(project) {
|
|
|
8536
8668
|
experimental: {
|
|
8537
8669
|
fsModuleCache: config.experimental.fsModuleCache ?? false,
|
|
8538
8670
|
printImportBreakdown: config.experimental.printImportBreakdown,
|
|
8671
|
+
viteModuleRunner: config.experimental.viteModuleRunner ?? true,
|
|
8672
|
+
nodeLoader: config.experimental.nodeLoader ?? true,
|
|
8539
8673
|
openTelemetry: config.experimental.openTelemetry
|
|
8540
|
-
}
|
|
8674
|
+
},
|
|
8675
|
+
tags: config.tags || [],
|
|
8676
|
+
tagsFilter: config.tagsFilter,
|
|
8677
|
+
strictTags: config.strictTags ?? true
|
|
8541
8678
|
};
|
|
8542
8679
|
}
|
|
8543
8680
|
|
|
@@ -9595,15 +9732,12 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
9595
9732
|
label: name,
|
|
9596
9733
|
color
|
|
9597
9734
|
} };
|
|
9735
|
+
vitestConfig.experimental ??= {};
|
|
9598
9736
|
// always inherit the global `fsModuleCache` value even without `extends: true`
|
|
9599
|
-
if (testConfig.experimental?.fsModuleCache == null && project.vitest.config.experimental?.fsModuleCache
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
if (testConfig.experimental?.fsModuleCachePath == null && project.vitest.config.experimental?.fsModuleCachePath !== null) {
|
|
9604
|
-
vitestConfig.experimental ??= {};
|
|
9605
|
-
vitestConfig.experimental.fsModuleCachePath = project.vitest.config.experimental.fsModuleCachePath;
|
|
9606
|
-
}
|
|
9737
|
+
if (testConfig.experimental?.fsModuleCache == null && project.vitest.config.experimental?.fsModuleCache != null) vitestConfig.experimental.fsModuleCache = project.vitest.config.experimental.fsModuleCache;
|
|
9738
|
+
if (testConfig.experimental?.fsModuleCachePath == null && project.vitest.config.experimental?.fsModuleCachePath != null) vitestConfig.experimental.fsModuleCachePath = project.vitest.config.experimental.fsModuleCachePath;
|
|
9739
|
+
if (testConfig.experimental?.viteModuleRunner == null && project.vitest.config.experimental?.viteModuleRunner != null) vitestConfig.experimental.viteModuleRunner = project.vitest.config.experimental.viteModuleRunner;
|
|
9740
|
+
if (testConfig.experimental?.nodeLoader == null && project.vitest.config.experimental?.nodeLoader != null) vitestConfig.experimental.nodeLoader = project.vitest.config.experimental.nodeLoader;
|
|
9607
9741
|
return {
|
|
9608
9742
|
base: "/",
|
|
9609
9743
|
environments: { __vitest__: { dev: {} } },
|
|
@@ -9851,6 +9985,10 @@ class TestSpecification {
|
|
|
9851
9985
|
*/
|
|
9852
9986
|
testIds;
|
|
9853
9987
|
/**
|
|
9988
|
+
* The tags of tests to run.
|
|
9989
|
+
*/
|
|
9990
|
+
testTagsFilter;
|
|
9991
|
+
/**
|
|
9854
9992
|
* This class represents a test suite for a test module within a single project.
|
|
9855
9993
|
* @internal
|
|
9856
9994
|
*/
|
|
@@ -9866,6 +10004,7 @@ class TestSpecification {
|
|
|
9866
10004
|
this.testLines = testLinesOrOptions.testLines;
|
|
9867
10005
|
this.testNamePattern = testLinesOrOptions.testNamePattern;
|
|
9868
10006
|
this.testIds = testLinesOrOptions.testIds;
|
|
10007
|
+
this.testTagsFilter = testLinesOrOptions.testTagsFilter;
|
|
9869
10008
|
}
|
|
9870
10009
|
}
|
|
9871
10010
|
/**
|
|
@@ -9887,40 +10026,13 @@ class TestSpecification {
|
|
|
9887
10026
|
pool: this.pool,
|
|
9888
10027
|
testLines: this.testLines,
|
|
9889
10028
|
testIds: this.testIds,
|
|
9890
|
-
testNamePattern: this.testNamePattern
|
|
10029
|
+
testNamePattern: this.testNamePattern,
|
|
10030
|
+
testTagsFilter: this.testTagsFilter
|
|
9891
10031
|
}
|
|
9892
10032
|
];
|
|
9893
10033
|
}
|
|
9894
10034
|
}
|
|
9895
10035
|
|
|
9896
|
-
async function createViteServer(inlineConfig) {
|
|
9897
|
-
// Vite prints an error (https://github.com/vitejs/vite/issues/14328)
|
|
9898
|
-
// But Vitest works correctly either way
|
|
9899
|
-
const error = console.error;
|
|
9900
|
-
console.error = (...args) => {
|
|
9901
|
-
if (typeof args[0] === "string" && args[0].includes("WebSocket server error:")) return;
|
|
9902
|
-
error(...args);
|
|
9903
|
-
};
|
|
9904
|
-
const server = await createServer(inlineConfig);
|
|
9905
|
-
console.error = error;
|
|
9906
|
-
return server;
|
|
9907
|
-
}
|
|
9908
|
-
function isFileServingAllowed(configOrUrl, urlOrServer) {
|
|
9909
|
-
const config = typeof urlOrServer === "string" ? configOrUrl : urlOrServer.config;
|
|
9910
|
-
const url = typeof urlOrServer === "string" ? urlOrServer : configOrUrl;
|
|
9911
|
-
if (!config.server.fs.strict) return true;
|
|
9912
|
-
return isFileLoadingAllowed(config, fsPathFromUrl(url));
|
|
9913
|
-
}
|
|
9914
|
-
const FS_PREFIX = "/@fs/";
|
|
9915
|
-
const VOLUME_RE = /^[A-Z]:/i;
|
|
9916
|
-
function fsPathFromId(id) {
|
|
9917
|
-
const fsPath = normalizePath(id.startsWith(FS_PREFIX) ? id.slice(5) : id);
|
|
9918
|
-
return fsPath[0] === "/" || VOLUME_RE.test(fsPath) ? fsPath : `/${fsPath}`;
|
|
9919
|
-
}
|
|
9920
|
-
function fsPathFromUrl(url) {
|
|
9921
|
-
return fsPathFromId(cleanUrl(url));
|
|
9922
|
-
}
|
|
9923
|
-
|
|
9924
10036
|
class TestProject {
|
|
9925
10037
|
/**
|
|
9926
10038
|
* The global Vitest instance.
|
|
@@ -10261,7 +10373,11 @@ class TestProject {
|
|
|
10261
10373
|
this._serializedDefines = createDefinesScript(server.config.define);
|
|
10262
10374
|
this._fetcher = createFetchModuleFunction(this._resolver, this._config, this.vitest._fsCache, this.vitest._traces, this.tmpDir);
|
|
10263
10375
|
const environment = server.environments.__vitest__;
|
|
10264
|
-
this.runner = new ServerModuleRunner(environment, this._fetcher, this._config);
|
|
10376
|
+
this.runner = this._config.experimental.viteModuleRunner === false ? new NativeModuleRunner(this._config.root) : new ServerModuleRunner(environment, this._fetcher, this._config);
|
|
10377
|
+
}
|
|
10378
|
+
/** @internal */
|
|
10379
|
+
_getViteEnvironments() {
|
|
10380
|
+
return [...Object.values(this.browser?.vite.environments || {}), ...Object.values(this.vite.environments || {})];
|
|
10265
10381
|
}
|
|
10266
10382
|
_serializeOverriddenConfig() {
|
|
10267
10383
|
// TODO: serialize the config _once_ or when needed
|
|
@@ -10376,7 +10492,8 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
10376
10492
|
"printConsoleTrace",
|
|
10377
10493
|
"inspect",
|
|
10378
10494
|
"inspectBrk",
|
|
10379
|
-
"fileParallelism"
|
|
10495
|
+
"fileParallelism",
|
|
10496
|
+
"tagsFilter"
|
|
10380
10497
|
].reduce((acc, name) => {
|
|
10381
10498
|
if (name in cliOptions) acc[name] = cliOptions[name];
|
|
10382
10499
|
return acc;
|
|
@@ -10395,6 +10512,14 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
10395
10512
|
...options,
|
|
10396
10513
|
root,
|
|
10397
10514
|
configFile,
|
|
10515
|
+
plugins: [{
|
|
10516
|
+
name: "vitest:tags",
|
|
10517
|
+
configResolved(config) {
|
|
10518
|
+
config.test ??= {};
|
|
10519
|
+
config.test.tags = options.test?.tags;
|
|
10520
|
+
},
|
|
10521
|
+
api: { vitest: { experimental: { ignoreFsModuleCache: true } } }
|
|
10522
|
+
}, ...options.plugins || []],
|
|
10398
10523
|
test: {
|
|
10399
10524
|
...options.test,
|
|
10400
10525
|
...cliOverrides
|
|
@@ -10861,6 +10986,10 @@ class TestCase extends ReportedTaskImplementation {
|
|
|
10861
10986
|
* Parent suite. If the test was called directly inside the module, the parent will be the module itself.
|
|
10862
10987
|
*/
|
|
10863
10988
|
parent;
|
|
10989
|
+
/**
|
|
10990
|
+
* Tags associated with the test.
|
|
10991
|
+
*/
|
|
10992
|
+
tags;
|
|
10864
10993
|
/** @internal */
|
|
10865
10994
|
constructor(task, project) {
|
|
10866
10995
|
super(task, project);
|
|
@@ -10870,6 +10999,7 @@ class TestCase extends ReportedTaskImplementation {
|
|
|
10870
10999
|
if (suite) this.parent = getReportedTask(project, suite);
|
|
10871
11000
|
else this.parent = this.module;
|
|
10872
11001
|
this.options = buildOptions(task);
|
|
11002
|
+
this.tags = this.options.tags || [];
|
|
10873
11003
|
}
|
|
10874
11004
|
/**
|
|
10875
11005
|
* Full name of the test including all parent suites separated with `>`.
|
|
@@ -10946,6 +11076,13 @@ class TestCase extends ReportedTaskImplementation {
|
|
|
10946
11076
|
flaky: !!result.retryCount && result.state === "pass" && result.retryCount > 0
|
|
10947
11077
|
};
|
|
10948
11078
|
}
|
|
11079
|
+
/**
|
|
11080
|
+
* Returns a new test specification that can be used to filter or run this specific test case.
|
|
11081
|
+
*/
|
|
11082
|
+
toTestSpecification() {
|
|
11083
|
+
const isTypecheck = this.task.meta.typecheck === true;
|
|
11084
|
+
return this.project.createSpecification(this.module.moduleId, { testIds: [this.id] }, isTypecheck ? "typecheck" : void 0);
|
|
11085
|
+
}
|
|
10949
11086
|
}
|
|
10950
11087
|
class TestCollection {
|
|
10951
11088
|
#task;
|
|
@@ -11065,6 +11202,14 @@ class TestSuite extends SuiteImplementation {
|
|
|
11065
11202
|
return getSuiteState(this.task);
|
|
11066
11203
|
}
|
|
11067
11204
|
/**
|
|
11205
|
+
* Returns a new test specification that can be used to filter or run this specific test suite.
|
|
11206
|
+
*/
|
|
11207
|
+
toTestSpecification() {
|
|
11208
|
+
const isTypecheck = this.task.meta.typecheck === true;
|
|
11209
|
+
const testIds = [...this.children.allTests()].map((test) => test.id);
|
|
11210
|
+
return this.project.createSpecification(this.module.moduleId, { testIds }, isTypecheck ? "typecheck" : void 0);
|
|
11211
|
+
}
|
|
11212
|
+
/**
|
|
11068
11213
|
* Full name of the suite including all parent suites separated with `>`.
|
|
11069
11214
|
*/
|
|
11070
11215
|
get fullName() {
|
|
@@ -11100,6 +11245,13 @@ class TestModule extends SuiteImplementation {
|
|
|
11100
11245
|
else if (typeof task.viteEnvironment === "string") this.viteEnvironment = project.vite.environments[task.viteEnvironment];
|
|
11101
11246
|
}
|
|
11102
11247
|
/**
|
|
11248
|
+
* Returns a new test specification that can be used to filter or run this specific test module.
|
|
11249
|
+
*/
|
|
11250
|
+
toTestSpecification() {
|
|
11251
|
+
const isTypecheck = this.task.meta.typecheck === true;
|
|
11252
|
+
return this.project.createSpecification(this.moduleId, void 0, isTypecheck ? "typecheck" : void 0);
|
|
11253
|
+
}
|
|
11254
|
+
/**
|
|
11103
11255
|
* Checks the running state of the test file.
|
|
11104
11256
|
*/
|
|
11105
11257
|
state() {
|
|
@@ -11133,6 +11285,8 @@ function buildOptions(task) {
|
|
|
11133
11285
|
shuffle: task.shuffle,
|
|
11134
11286
|
retry: task.retry,
|
|
11135
11287
|
repeats: task.repeats,
|
|
11288
|
+
tags: task.tags,
|
|
11289
|
+
timeout: task.type === "test" ? task.timeout : void 0,
|
|
11136
11290
|
mode: task.mode
|
|
11137
11291
|
};
|
|
11138
11292
|
}
|
|
@@ -11313,6 +11467,19 @@ class StateManager {
|
|
|
11313
11467
|
}
|
|
11314
11468
|
}
|
|
11315
11469
|
|
|
11470
|
+
function populateProjectsTags(rootProject, projects) {
|
|
11471
|
+
// Include root project if not already in the list
|
|
11472
|
+
const allProjects = projects.includes(rootProject) ? projects : [rootProject, ...projects];
|
|
11473
|
+
// Collect all tags from all projects (first definition wins)
|
|
11474
|
+
const globalTags = /* @__PURE__ */ new Map();
|
|
11475
|
+
for (const project of allProjects) for (const tag of project.config.tags || []) if (!globalTags.has(tag.name)) globalTags.set(tag.name, tag);
|
|
11476
|
+
// Add missing tags to each project (without overriding local definitions)
|
|
11477
|
+
for (const project of allProjects) {
|
|
11478
|
+
const projectTagNames = new Set(project.config.tags.map((t) => t.name));
|
|
11479
|
+
for (const [tagName, tagDef] of globalTags) if (!projectTagNames.has(tagName)) project.config.tags.push(tagDef);
|
|
11480
|
+
}
|
|
11481
|
+
}
|
|
11482
|
+
|
|
11316
11483
|
const types = {
|
|
11317
11484
|
'application/andrew-inset': ['ez'],
|
|
11318
11485
|
'application/appinstaller': ['appinstaller'],
|
|
@@ -11952,6 +12119,9 @@ class VitestWatcher {
|
|
|
11952
12119
|
this._onRerun.push(cb);
|
|
11953
12120
|
return this;
|
|
11954
12121
|
}
|
|
12122
|
+
close() {
|
|
12123
|
+
this.vitest.vite.watcher.close();
|
|
12124
|
+
}
|
|
11955
12125
|
unregisterWatcher = noop;
|
|
11956
12126
|
registerWatcher() {
|
|
11957
12127
|
const watcher = this.vitest.vite.watcher;
|
|
@@ -12051,7 +12221,9 @@ class VitestWatcher {
|
|
|
12051
12221
|
}
|
|
12052
12222
|
if (this.handleSetupFile(filepath)) return true;
|
|
12053
12223
|
const projects = this.vitest.projects.filter((project) => {
|
|
12054
|
-
return
|
|
12224
|
+
return project._getViteEnvironments().some(({ moduleGraph }) => {
|
|
12225
|
+
return moduleGraph.getModulesByFile(filepath)?.size;
|
|
12226
|
+
});
|
|
12055
12227
|
});
|
|
12056
12228
|
if (!projects.length) {
|
|
12057
12229
|
// if there are no modules it's possible that server was restarted
|
|
@@ -12064,8 +12236,8 @@ class VitestWatcher {
|
|
|
12064
12236
|
}
|
|
12065
12237
|
const files = [];
|
|
12066
12238
|
for (const project of projects) {
|
|
12067
|
-
const
|
|
12068
|
-
if (!
|
|
12239
|
+
const environmentMods = project._getViteEnvironments().map(({ moduleGraph }) => moduleGraph.getModulesByFile(filepath));
|
|
12240
|
+
if (!environmentMods.length) continue;
|
|
12069
12241
|
this.invalidates.add(filepath);
|
|
12070
12242
|
// one of test files that we already run, or one of test files that we can run
|
|
12071
12243
|
if (this.vitest.state.filesMap.has(filepath) || project._isCachedTestFile(filepath)) {
|
|
@@ -12074,7 +12246,7 @@ class VitestWatcher {
|
|
|
12074
12246
|
continue;
|
|
12075
12247
|
}
|
|
12076
12248
|
let rerun = false;
|
|
12077
|
-
for (const mod of mods) mod.importers.forEach((i) => {
|
|
12249
|
+
for (const mods of environmentMods) for (const mod of mods || []) mod.importers.forEach((i) => {
|
|
12078
12250
|
if (!i.file) return;
|
|
12079
12251
|
if (this.handleFileChanged(i.file)) rerun = true;
|
|
12080
12252
|
});
|
|
@@ -12136,6 +12308,7 @@ class Vitest {
|
|
|
12136
12308
|
/** @internal */ reporters = [];
|
|
12137
12309
|
/** @internal */ runner;
|
|
12138
12310
|
/** @internal */ _testRun = void 0;
|
|
12311
|
+
/** @internal */ _config;
|
|
12139
12312
|
/** @internal */ _resolver;
|
|
12140
12313
|
/** @internal */ _fetcher;
|
|
12141
12314
|
/** @internal */ _fsCache;
|
|
@@ -12145,7 +12318,6 @@ class Vitest {
|
|
|
12145
12318
|
restartsCount = 0;
|
|
12146
12319
|
specifications;
|
|
12147
12320
|
pool;
|
|
12148
|
-
_config;
|
|
12149
12321
|
_vite;
|
|
12150
12322
|
_state;
|
|
12151
12323
|
_cache;
|
|
@@ -12233,7 +12405,7 @@ class Vitest {
|
|
|
12233
12405
|
this._fsCache = new FileSystemModuleCache(this);
|
|
12234
12406
|
this._fetcher = createFetchModuleFunction(this._resolver, this._config, this._fsCache, this._traces, this._tmpDir);
|
|
12235
12407
|
const environment = server.environments.__vitest__;
|
|
12236
|
-
this.runner = new ServerModuleRunner(environment, this._fetcher, resolved);
|
|
12408
|
+
this.runner = resolved.experimental.viteModuleRunner === false ? new NativeModuleRunner(resolved.root) : new ServerModuleRunner(environment, this._fetcher, resolved);
|
|
12237
12409
|
if (this.config.watch) {
|
|
12238
12410
|
// hijack server restart
|
|
12239
12411
|
const serverRestart = server.restart;
|
|
@@ -12282,6 +12454,9 @@ class Vitest {
|
|
|
12282
12454
|
}
|
|
12283
12455
|
if (!this.coreWorkspaceProject) this.coreWorkspaceProject = TestProject._createBasicProject(this);
|
|
12284
12456
|
if (this.config.testNamePattern) this.configOverride.testNamePattern = this.config.testNamePattern;
|
|
12457
|
+
// populate will merge all configs into every project,
|
|
12458
|
+
// we don't want that when just listing tags
|
|
12459
|
+
if (!this.config.listTags) populateProjectsTags(this.coreWorkspaceProject, this.projects);
|
|
12285
12460
|
this.reporters = resolved.mode === "benchmark" ? await createBenchmarkReporters(toArray(resolved.benchmark?.reporters), this.runner) : await createReporters(resolved.reporters, this);
|
|
12286
12461
|
await this._fsCache.ensureCacheIntegrity();
|
|
12287
12462
|
await Promise.all([...this._onSetServer.map((fn) => fn()), this._traces.waitInit()]);
|
|
@@ -12291,6 +12466,24 @@ class Vitest {
|
|
|
12291
12466
|
if (this.configOverride.coverage?.enabled === false) return null;
|
|
12292
12467
|
return this._coverageProvider;
|
|
12293
12468
|
}
|
|
12469
|
+
async listTags() {
|
|
12470
|
+
const listTags = this.config.listTags;
|
|
12471
|
+
if (typeof listTags === "boolean") this.logger.printTags();
|
|
12472
|
+
else if (listTags === "json") if (![this.getRootProject(), ...this.projects].some((p) => p.config.tags && p.config.tags.length > 0)) {
|
|
12473
|
+
process.exitCode = 1;
|
|
12474
|
+
this.logger.printNoTestTagsFound();
|
|
12475
|
+
} else {
|
|
12476
|
+
const manifest = {
|
|
12477
|
+
tags: this.config.tags,
|
|
12478
|
+
projects: this.projects.filter((p) => p !== this.coreWorkspaceProject).map((p) => ({
|
|
12479
|
+
name: p.name,
|
|
12480
|
+
tags: p.config.tags
|
|
12481
|
+
}))
|
|
12482
|
+
};
|
|
12483
|
+
this.logger.log(JSON.stringify(manifest, null, 2));
|
|
12484
|
+
}
|
|
12485
|
+
else throw new Error(`Unknown value for "test.listTags": ${listTags}`);
|
|
12486
|
+
}
|
|
12294
12487
|
async enableCoverage() {
|
|
12295
12488
|
this.configOverride.coverage = {};
|
|
12296
12489
|
this.configOverride.coverage.enabled = true;
|
|
@@ -12793,8 +12986,12 @@ class Vitest {
|
|
|
12793
12986
|
async rerunTask(id) {
|
|
12794
12987
|
const task = this.state.idMap.get(id);
|
|
12795
12988
|
if (!task) throw new Error(`Task ${id} was not found`);
|
|
12796
|
-
const
|
|
12797
|
-
|
|
12989
|
+
const reportedTask = this.state.getReportedEntityById(id);
|
|
12990
|
+
if (!reportedTask) throw new Error(`Test specification for task ${id} was not found`);
|
|
12991
|
+
const specifications = [reportedTask.toTestSpecification()];
|
|
12992
|
+
await Promise.all([this.report("onWatcherRerun", [task.file.filepath], "tasks" in task ? "rerun suite" : "rerun test"), ...this._onUserTestsRerun.map((fn) => fn(specifications))]);
|
|
12993
|
+
await this.runFiles(specifications, false);
|
|
12994
|
+
await this.report("onWatcherStart", ["module" in reportedTask ? reportedTask.module.task : reportedTask.task]);
|
|
12798
12995
|
}
|
|
12799
12996
|
/** @internal */
|
|
12800
12997
|
async changeProjectName(pattern) {
|
|
@@ -13558,7 +13755,8 @@ async function startVitest(mode, cliFilters = [], options = {}, viteOverrides, v
|
|
|
13558
13755
|
else ctx.start(cliFilters);
|
|
13559
13756
|
});
|
|
13560
13757
|
try {
|
|
13561
|
-
if (ctx.config.
|
|
13758
|
+
if (ctx.config.listTags) await ctx.listTags();
|
|
13759
|
+
else if (ctx.config.clearCache) await ctx.experimental_clearCache();
|
|
13562
13760
|
else if (ctx.config.mergeReports) await ctx.mergeReports();
|
|
13563
13761
|
else if (ctx.config.standalone) await ctx.init();
|
|
13564
13762
|
else await ctx.start(cliFilters);
|