vitest 3.2.0-beta.1 → 3.2.0-beta.3
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 +0 -232
- package/dist/browser.d.ts +5 -3
- package/dist/browser.js +3 -4
- package/dist/chunks/{base.SfTiRNZf.js → base.D4119yLM.js} +4 -3
- package/dist/chunks/{benchmark.BoF7jW0Q.js → benchmark.Cf_PACH1.js} +1 -1
- package/dist/chunks/{cac.TfX2-DVH.js → cac.DWaWHIIE.js} +21 -16
- package/dist/chunks/{cli-api.2970Nj9J.js → cli-api.CnmEXkxs.js} +292 -59
- package/dist/chunks/{config.d.UqE-KR0o.d.ts → config.d.D2ROskhv.d.ts} +2 -0
- package/dist/chunks/{console.K1NMVOSc.js → console.Cwr-MFPV.js} +3 -2
- package/dist/chunks/{constants.BZZyIeIE.js → constants.DnKduX2e.js} +1 -0
- package/dist/chunks/{coverage.z0LVMxgb.js → coverage.C73DaDgS.js} +241 -4226
- package/dist/chunks/{creator.CuL7xDWI.js → creator.C8WKy2eW.js} +26 -44
- package/dist/chunks/{date.CDOsz-HY.js → date.ByMsSlOr.js} +25 -0
- package/dist/chunks/{defaults.DSxsTG0h.js → defaults.DpVH7vbg.js} +1 -0
- package/dist/chunks/{environment.d.D8YDy2v5.d.ts → environment.d.cL3nLXbE.d.ts} +1 -0
- package/dist/chunks/{execute.BpmIjFTD.js → execute.B3q-2LPV.js} +28 -5
- package/dist/chunks/{global.d.BCOHQEpR.d.ts → global.d.BNLIi6yo.d.ts} +13 -11
- package/dist/chunks/{globals.Cg4NtV4P.js → globals.CI21aWXF.js} +7 -7
- package/dist/chunks/{index.DFXFpH3w.js → index.2jgTs_Q5.js} +19 -1
- package/dist/chunks/{index.CUacZlWG.js → index.Bter3jj9.js} +954 -954
- package/dist/chunks/{index.DbWBPwtH.js → index.CbT4iuwc.js} +7 -4
- package/dist/chunks/index.D3XRDfWc.js +213 -0
- package/dist/chunks/{index.BPc7M5ni.js → index.DNgLEKsQ.js} +5 -15
- package/dist/chunks/index.JOzufsrU.js +276 -0
- package/dist/chunks/{index.DBIGubLC.js → index.X0nbfr6-.js} +7 -7
- package/dist/chunks/{inspector.DbDkSkFn.js → inspector.BFsh5KO0.js} +3 -0
- package/dist/chunks/{node.3xsWotC9.js → node.Be-ntJnD.js} +1 -1
- package/dist/chunks/{reporters.d.DGm4k1Wx.d.ts → reporters.d.Bt4IGtsa.d.ts} +41 -6
- package/dist/chunks/{rpc.D9_013TY.js → rpc.BKExFSRG.js} +2 -1
- package/dist/chunks/{runBaseTests.CguliJB5.js → runBaseTests.B_M1TTsK.js} +19 -11
- package/dist/chunks/{setup-common.BP6KrF_Z.js → setup-common.CF-O-dZX.js} +2 -3
- package/dist/chunks/typechecker.BgzF-6iO.js +954 -0
- package/dist/chunks/{utils.CgTj3MsC.js → utils.BlI4TC7Y.js} +1 -0
- package/dist/chunks/{utils.BfxieIyZ.js → utils.DPCq3gzW.js} +3 -0
- package/dist/chunks/{vi.BFR5YIgu.js → vi.pkoYCV6A.js} +25 -2
- package/dist/chunks/{vite.d.DjP_ALCZ.d.ts → vite.d.B-Kx3KCF.d.ts} +3 -1
- package/dist/chunks/{vm.CuLHT1BG.js → vm.DPYem2so.js} +72 -4
- package/dist/chunks/{worker.d.CoCI7hzP.d.ts → worker.d.BKbBp2ga.d.ts} +2 -2
- package/dist/chunks/{worker.d.D5Xdi-Zr.d.ts → worker.d.Bl1O4kuf.d.ts} +1 -1
- package/dist/cli.js +21 -2
- package/dist/config.cjs +2 -0
- package/dist/config.d.ts +7 -6
- package/dist/config.js +2 -2
- package/dist/coverage.d.ts +4 -4
- package/dist/coverage.js +7 -10
- package/dist/environments.d.ts +6 -2
- package/dist/environments.js +1 -1
- package/dist/execute.d.ts +9 -3
- package/dist/execute.js +1 -1
- package/dist/index.d.ts +25 -35
- package/dist/index.js +5 -6
- package/dist/node.d.ts +18 -10
- package/dist/node.js +22 -22
- package/dist/reporters.d.ts +4 -4
- package/dist/reporters.js +14 -14
- package/dist/runners.d.ts +1 -1
- package/dist/runners.js +13 -5
- package/dist/snapshot.js +2 -2
- package/dist/suite.js +2 -2
- package/dist/worker.js +9 -5
- package/dist/workers/forks.js +6 -4
- package/dist/workers/runVmTests.js +14 -10
- package/dist/workers/threads.js +4 -4
- package/dist/workers/vmForks.js +6 -6
- package/dist/workers/vmThreads.js +6 -6
- package/dist/workers.d.ts +4 -4
- package/dist/workers.js +10 -10
- package/package.json +22 -26
- package/dist/chunks/index.Bw6JxgX8.js +0 -143
- package/dist/chunks/run-once.Dimr7O9f.js +0 -47
- package/dist/chunks/typechecker.DYQbn8uK.js +0 -956
- package/dist/chunks/utils.8gfOgtry.js +0 -207
- package/dist/utils.d.ts +0 -3
- package/dist/utils.js +0 -2
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { promises, existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { extname, normalize, relative, dirname, resolve, join, basename, isAbsolute } from 'pathe';
|
|
3
3
|
import { C as CoverageProviderMap } from './coverage.0iPg4Wrz.js';
|
|
4
|
-
import
|
|
4
|
+
import path, { resolve as resolve$1 } from 'node:path';
|
|
5
5
|
import { noop, isPrimitive, createDefer, highlight, toArray, deepMerge, nanoid, slash, deepClone, notNullish } from '@vitest/utils';
|
|
6
|
-
import { f as findUp, p as prompt } from './index.
|
|
6
|
+
import { f as findUp, p as prompt } from './index.X0nbfr6-.js';
|
|
7
7
|
import * as vite from 'vite';
|
|
8
|
-
import { searchForWorkspaceRoot, version,
|
|
9
|
-
import { A as API_PATH, c as configFiles, d as defaultBrowserPort, w as workspacesFiles, a as defaultPort } from './constants.
|
|
8
|
+
import { searchForWorkspaceRoot, version, createServer, mergeConfig } from 'vite';
|
|
9
|
+
import { A as API_PATH, c as configFiles, d as defaultBrowserPort, w as workspacesFiles, a as defaultPort } from './constants.DnKduX2e.js';
|
|
10
10
|
import { generateFileHash, limitConcurrency, createFileTask, hasFailed, getTasks, getTests } from '@vitest/runner/utils';
|
|
11
11
|
import { SnapshotManager } from '@vitest/snapshot/manager';
|
|
12
12
|
import { ViteNodeRunner } from 'vite-node/client';
|
|
13
13
|
import { ViteNodeServer } from 'vite-node/server';
|
|
14
|
-
import { v as version$1 } from './cac.
|
|
14
|
+
import { v as version$1 } from './cac.DWaWHIIE.js';
|
|
15
15
|
import { c as createBirpc } from './index.CJ0plNrh.js';
|
|
16
|
-
import { p as parse, s as stringify,
|
|
16
|
+
import { p as parse, s as stringify, d as printError, f as formatProjectName, w as withLabel, e as errorBanner, h as divider, i as generateCodeFrame, R as ReportersMap, j as BlobReporter, r as readBlobs, H as HangingProcessReporter } from './index.Bter3jj9.js';
|
|
17
17
|
import require$$0$3 from 'events';
|
|
18
18
|
import require$$1$1 from 'https';
|
|
19
19
|
import require$$2 from 'http';
|
|
@@ -28,21 +28,22 @@ import { g as getDefaultExportFromCjs } from './_commonjsHelpers.BFTU3MAI.js';
|
|
|
28
28
|
import { parseErrorStacktrace } from '@vitest/utils/source-map';
|
|
29
29
|
import crypto from 'node:crypto';
|
|
30
30
|
import { distDir, rootDir } from '../path.js';
|
|
31
|
-
import { R as RandomSequencer, i as isPackageExists, h as hash, V as VitestCache, g as getFilePoolName, d as isBrowserEnabled,
|
|
32
|
-
import { c as convertTasksToEvents } from './typechecker.
|
|
31
|
+
import { R as RandomSequencer, i as isPackageExists, h as hash, V as VitestCache, g as getFilePoolName, d as isBrowserEnabled, r as resolveConfig, e as groupBy, f as getCoverageProvider, j as createPool, w as wildcardPatternToRegExp, a as resolveApiServerConfig, s as stdout } from './coverage.C73DaDgS.js';
|
|
32
|
+
import { c as convertTasksToEvents } from './typechecker.BgzF-6iO.js';
|
|
33
33
|
import { Console } from 'node:console';
|
|
34
34
|
import c from 'tinyrainbow';
|
|
35
|
-
import { c as formatProjectName, w as withLabel, e as errorBanner, d as divider } from './utils.8gfOgtry.js';
|
|
36
35
|
import { createRequire } from 'node:module';
|
|
37
36
|
import url from 'node:url';
|
|
38
37
|
import { i as isTTY, a as isWindows } from './env.Dq0hM4Xv.js';
|
|
39
38
|
import { rm } from 'node:fs/promises';
|
|
40
39
|
import nodeos__default, { tmpdir } from 'node:os';
|
|
40
|
+
import pm from 'picomatch';
|
|
41
41
|
import { glob, isDynamicPattern } from 'tinyglobby';
|
|
42
42
|
import { normalizeRequestId, cleanUrl } from 'vite-node/utils';
|
|
43
43
|
import { hoistMocksPlugin, automockPlugin } from '@vitest/mocker/node';
|
|
44
|
-
import { c as configDefaults } from './defaults.
|
|
44
|
+
import { c as configDefaults } from './defaults.DpVH7vbg.js';
|
|
45
45
|
import MagicString from 'magic-string';
|
|
46
|
+
import { a as BenchmarkReportsMap } from './index.JOzufsrU.js';
|
|
46
47
|
import assert$1 from 'node:assert';
|
|
47
48
|
import { serializeError } from '@vitest/utils/error';
|
|
48
49
|
import readline from 'node:readline';
|
|
@@ -778,6 +779,14 @@ function requirePermessageDeflate () {
|
|
|
778
779
|
this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
|
|
779
780
|
this[kError][kStatusCode] = 1009;
|
|
780
781
|
this.removeListener('data', inflateOnData);
|
|
782
|
+
|
|
783
|
+
//
|
|
784
|
+
// The choice to employ `zlib.reset()` over `zlib.close()` is dictated by the
|
|
785
|
+
// fact that in Node.js versions prior to 13.10.0, the callback for
|
|
786
|
+
// `zlib.flush()` is not called if `zlib.close()` is used. Utilizing
|
|
787
|
+
// `zlib.reset()` ensures that either the callback is invoked or an error is
|
|
788
|
+
// emitted.
|
|
789
|
+
//
|
|
781
790
|
this.reset();
|
|
782
791
|
}
|
|
783
792
|
|
|
@@ -793,6 +802,12 @@ function requirePermessageDeflate () {
|
|
|
793
802
|
// closed when an error is emitted.
|
|
794
803
|
//
|
|
795
804
|
this[kPerMessageDeflate]._inflate = null;
|
|
805
|
+
|
|
806
|
+
if (this[kError]) {
|
|
807
|
+
this[kCallback](this[kError]);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
|
|
796
811
|
err[kStatusCode] = 1007;
|
|
797
812
|
this[kCallback](err);
|
|
798
813
|
}
|
|
@@ -3510,7 +3525,7 @@ function requireWebsocket () {
|
|
|
3510
3525
|
if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) {
|
|
3511
3526
|
invalidUrlMessage =
|
|
3512
3527
|
'The URL\'s protocol must be one of "ws:", "wss:", ' +
|
|
3513
|
-
'"http:", "https", or "ws+unix:"';
|
|
3528
|
+
'"http:", "https:", or "ws+unix:"';
|
|
3514
3529
|
} else if (isIpcUrl && !parsedUrl.pathname) {
|
|
3515
3530
|
invalidUrlMessage = "The URL's pathname is empty";
|
|
3516
3531
|
} else if (parsedUrl.hash) {
|
|
@@ -5034,7 +5049,10 @@ function clearId(id) {
|
|
|
5034
5049
|
return id?.replace(/\?v=\w+$/, "") || "";
|
|
5035
5050
|
}
|
|
5036
5051
|
|
|
5052
|
+
// Serialization support utils.
|
|
5037
5053
|
function cloneByOwnProperties(value) {
|
|
5054
|
+
// Clones the value's properties into a new Object. The simpler approach of
|
|
5055
|
+
// Object.assign() won't work in the case that properties are not enumerable.
|
|
5038
5056
|
return Object.getOwnPropertyNames(value).reduce((clone, prop) => ({
|
|
5039
5057
|
...clone,
|
|
5040
5058
|
[prop]: value[prop]
|
|
@@ -5060,12 +5078,15 @@ function stringifyReplace(key, value) {
|
|
|
5060
5078
|
|
|
5061
5079
|
function isValidApiRequest(config, req) {
|
|
5062
5080
|
const url = new URL(req.url ?? "", "http://localhost");
|
|
5081
|
+
// validate token. token is injected in ui/tester/orchestrator html, which is cross origin protected.
|
|
5063
5082
|
try {
|
|
5064
5083
|
const token = url.searchParams.get("token");
|
|
5065
5084
|
if (token && crypto.timingSafeEqual(Buffer.from(token), Buffer.from(config.api.token))) {
|
|
5066
5085
|
return true;
|
|
5067
5086
|
}
|
|
5068
|
-
}
|
|
5087
|
+
}
|
|
5088
|
+
// an error is thrown when the length is incorrect
|
|
5089
|
+
catch {}
|
|
5069
5090
|
return false;
|
|
5070
5091
|
}
|
|
5071
5092
|
|
|
@@ -5266,6 +5287,7 @@ class BrowserSessions {
|
|
|
5266
5287
|
this.sessions.delete(sessionId);
|
|
5267
5288
|
}
|
|
5268
5289
|
createSession(sessionId, project, pool) {
|
|
5290
|
+
// this promise only waits for the WS connection with the orhcestrator to be established
|
|
5269
5291
|
const defer = createDefer();
|
|
5270
5292
|
const timeout = setTimeout(() => {
|
|
5271
5293
|
defer.reject(new Error(`Failed to connect to the browser session "${sessionId}" [${project.name}] within the timeout.`));
|
|
@@ -5488,6 +5510,7 @@ class Logger {
|
|
|
5488
5510
|
this.log(PAD + c.dim(c.green(`UI started at http://${host}:${c.bold(port)}${base}`)));
|
|
5489
5511
|
} else if (this.ctx.config.api?.port) {
|
|
5490
5512
|
const resolvedUrls = this.ctx.server.resolvedUrls;
|
|
5513
|
+
// workaround for https://github.com/vitejs/vite/issues/15438, it was fixed in vite 5.1
|
|
5491
5514
|
const fallbackUrl = `http://${this.ctx.config.api.host || "localhost"}:${this.ctx.config.api.port}`;
|
|
5492
5515
|
const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0] ?? fallbackUrl;
|
|
5493
5516
|
this.log(PAD + c.dim(c.green(`API started at ${new URL("/", origin)}`)));
|
|
@@ -5551,6 +5574,8 @@ class Logger {
|
|
|
5551
5574
|
};
|
|
5552
5575
|
const onExit = (signal, exitCode) => {
|
|
5553
5576
|
cleanup();
|
|
5577
|
+
// Interrupted signals don't set exit code automatically.
|
|
5578
|
+
// Use same exit code as node: https://nodejs.org/api/process.html#signal-events
|
|
5554
5579
|
if (process.exitCode === undefined) {
|
|
5555
5580
|
process.exitCode = exitCode !== undefined ? 128 + exitCode : Number(signal);
|
|
5556
5581
|
}
|
|
@@ -5606,7 +5631,7 @@ class VitestPackageInstaller {
|
|
|
5606
5631
|
if (!isTTY) {
|
|
5607
5632
|
return false;
|
|
5608
5633
|
}
|
|
5609
|
-
const prompts = await import('./index.
|
|
5634
|
+
const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; });
|
|
5610
5635
|
const { install } = await prompts.default({
|
|
5611
5636
|
type: "confirm",
|
|
5612
5637
|
name: "install",
|
|
@@ -5614,7 +5639,8 @@ class VitestPackageInstaller {
|
|
|
5614
5639
|
});
|
|
5615
5640
|
if (install) {
|
|
5616
5641
|
const packageName = version ? `${dependency}@${version}` : dependency;
|
|
5617
|
-
await (await import('./index.
|
|
5642
|
+
await (await import('./index.D3XRDfWc.js')).installPackage(packageName, { dev: true });
|
|
5643
|
+
// TODO: somehow it fails to load the package after installation, remove this when it's fixed
|
|
5618
5644
|
process.stderr.write(c.yellow(`\nPackage ${packageName} installed, re-run the command to start.\n`));
|
|
5619
5645
|
process.exit();
|
|
5620
5646
|
return true;
|
|
@@ -5626,6 +5652,7 @@ class VitestPackageInstaller {
|
|
|
5626
5652
|
function serializeConfig(config, coreConfig, viteConfig) {
|
|
5627
5653
|
const optimizer = config.deps?.optimizer;
|
|
5628
5654
|
const poolOptions = config.poolOptions;
|
|
5655
|
+
// Resolve from server.config to avoid comparing against default value
|
|
5629
5656
|
const isolate = viteConfig?.test?.isolate;
|
|
5630
5657
|
return {
|
|
5631
5658
|
environmentOptions: config.environmentOptions,
|
|
@@ -5798,6 +5825,7 @@ function generateCssFilenameHash(filepath) {
|
|
|
5798
5825
|
return hash("md5", filepath, "hex").slice(0, 6);
|
|
5799
5826
|
}
|
|
5800
5827
|
function generateScopedClassName(strategy, name, filename) {
|
|
5828
|
+
// should be configured by Vite defaults
|
|
5801
5829
|
if (strategy === "scoped") {
|
|
5802
5830
|
return null;
|
|
5803
5831
|
}
|
|
@@ -5822,6 +5850,8 @@ function clearScreen(logger) {
|
|
|
5822
5850
|
let lastType;
|
|
5823
5851
|
let lastMsg;
|
|
5824
5852
|
let sameCount = 0;
|
|
5853
|
+
// Only initialize the timeFormatter when the timestamp option is used, and
|
|
5854
|
+
// reuse it across all loggers
|
|
5825
5855
|
let timeFormatter;
|
|
5826
5856
|
function getTimeFormatter() {
|
|
5827
5857
|
timeFormatter ??= new Intl.DateTimeFormat(undefined, {
|
|
@@ -5831,6 +5861,10 @@ function getTimeFormatter() {
|
|
|
5831
5861
|
});
|
|
5832
5862
|
return timeFormatter;
|
|
5833
5863
|
}
|
|
5864
|
+
// This is copy-pasted and needs to be synced from time to time. Ideally, Vite's `createLogger` should accept a custom `console`
|
|
5865
|
+
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/logger.ts?rgh-link-date=2024-10-16T23%3A29%3A19Z
|
|
5866
|
+
// When Vitest supports only Vite 6 and above, we can use Vite's `createLogger({ console })`
|
|
5867
|
+
// https://github.com/vitejs/vite/pull/18379
|
|
5834
5868
|
function createViteLogger(console, level = "info", options = {}) {
|
|
5835
5869
|
const loggedErrors = new WeakSet();
|
|
5836
5870
|
const { prefix = "[vite]", allowClearScreen = true } = options;
|
|
@@ -5911,6 +5945,7 @@ function createViteLogger(console, level = "info", options = {}) {
|
|
|
5911
5945
|
};
|
|
5912
5946
|
return logger;
|
|
5913
5947
|
}
|
|
5948
|
+
// silence warning by Vite for statically not analyzable dynamic import
|
|
5914
5949
|
function silenceImportViteIgnoreWarning(logger) {
|
|
5915
5950
|
return {
|
|
5916
5951
|
...logger,
|
|
@@ -5933,6 +5968,8 @@ function isCSS(id) {
|
|
|
5933
5968
|
function isCSSModule(id) {
|
|
5934
5969
|
return cssModuleRE.test(id);
|
|
5935
5970
|
}
|
|
5971
|
+
// inline css requests are expected to just return the
|
|
5972
|
+
// string content directly and not the proxy module
|
|
5936
5973
|
function isInline(id) {
|
|
5937
5974
|
return cssInlineRE.test(id);
|
|
5938
5975
|
}
|
|
@@ -5964,6 +6001,8 @@ function CSSEnablerPlugin(ctx) {
|
|
|
5964
6001
|
if (!isCSS(id)) {
|
|
5965
6002
|
return;
|
|
5966
6003
|
}
|
|
6004
|
+
// css plugin inside Vite won't do anything if the code is empty
|
|
6005
|
+
// but it will put __vite__updateStyle anyway
|
|
5967
6006
|
if (!shouldProcessCSS(id)) {
|
|
5968
6007
|
return { code: "" };
|
|
5969
6008
|
}
|
|
@@ -5976,6 +6015,9 @@ function CSSEnablerPlugin(ctx) {
|
|
|
5976
6015
|
return;
|
|
5977
6016
|
}
|
|
5978
6017
|
if (isCSSModule(id) && !isInline(id)) {
|
|
6018
|
+
// return proxy for css modules, so that imported module has names:
|
|
6019
|
+
// styles.foo returns a "foo" instead of "undefined"
|
|
6020
|
+
// we don't use code content to generate hash for "scoped", because it's empty
|
|
5979
6021
|
const scopeStrategy = typeof ctx.config.css !== "boolean" && ctx.config.css.modules?.classNameStrategy || "stable";
|
|
5980
6022
|
const proxyReturn = getCSSModuleProxyReturn(scopeStrategy, relative(ctx.config.root, id));
|
|
5981
6023
|
const code = `export default new Proxy(Object.create(null), {
|
|
@@ -6476,6 +6518,10 @@ function stripLiteralDetailed(code, options) {
|
|
|
6476
6518
|
|
|
6477
6519
|
const metaUrlLength = "import.meta.url".length;
|
|
6478
6520
|
const locationString = "self.location".padEnd(metaUrlLength, " ");
|
|
6521
|
+
// Vite transforms new URL('./path', import.meta.url) to new URL('/path.js', import.meta.url)
|
|
6522
|
+
// This makes "href" equal to "http://localhost:3000/path.js" in the browser, but if we keep it like this,
|
|
6523
|
+
// then in tests the URL will become "file:///path.js".
|
|
6524
|
+
// To battle this, we replace "import.meta.url" with "self.location" in the code to keep the browser behavior.
|
|
6479
6525
|
function NormalizeURLPlugin() {
|
|
6480
6526
|
return {
|
|
6481
6527
|
name: "vitest:normalize-url",
|
|
@@ -6489,6 +6535,7 @@ function NormalizeURLPlugin() {
|
|
|
6489
6535
|
const assetImportMetaUrlRE = /\bnew\s+URL\s*\(\s*(?:'[^']+'|"[^"]+"|`[^`]+`)\s*,\s*(?:'' \+ )?import\.meta\.url\s*(?:,\s*)?\)/g;
|
|
6490
6536
|
let updatedCode = code;
|
|
6491
6537
|
let match;
|
|
6538
|
+
// eslint-disable-next-line no-cond-assign
|
|
6492
6539
|
while (match = assetImportMetaUrlRE.exec(cleanString)) {
|
|
6493
6540
|
const { 0: exp, index } = match;
|
|
6494
6541
|
const metaUrlIndex = index + exp.indexOf("import.meta.url");
|
|
@@ -6542,6 +6589,8 @@ function resolveOptimizerConfig(_testOptions, viteOptions, testConfig, viteCache
|
|
|
6542
6589
|
include
|
|
6543
6590
|
};
|
|
6544
6591
|
}
|
|
6592
|
+
// `optimizeDeps.disabled` is deprecated since v5.1.0-beta.1
|
|
6593
|
+
// https://github.com/vitejs/vite/pull/15184
|
|
6545
6594
|
if (major >= 5 && minor >= 1 || major >= 6) {
|
|
6546
6595
|
if (newConfig.optimizeDeps.disabled) {
|
|
6547
6596
|
newConfig.optimizeDeps.noDiscovery = true;
|
|
@@ -6565,6 +6614,8 @@ function deleteDefineConfig(viteConfig) {
|
|
|
6565
6614
|
try {
|
|
6566
6615
|
replacement = typeof val === "string" ? JSON.parse(val) : val;
|
|
6567
6616
|
} catch {
|
|
6617
|
+
// probably means it contains reference to some variable,
|
|
6618
|
+
// like this: "__VAR__": "process.env.VAR"
|
|
6568
6619
|
continue;
|
|
6569
6620
|
}
|
|
6570
6621
|
if (key.startsWith("import.meta.env.")) {
|
|
@@ -6625,6 +6676,8 @@ function VitestOptimizer() {
|
|
|
6625
6676
|
};
|
|
6626
6677
|
}
|
|
6627
6678
|
|
|
6679
|
+
// so people can reassign envs at runtime
|
|
6680
|
+
// import.meta.env.VITE_NAME = 'app' -> process.env.VITE_NAME = 'app'
|
|
6628
6681
|
function SsrReplacerPlugin() {
|
|
6629
6682
|
return {
|
|
6630
6683
|
name: "vitest:ssr-replacer",
|
|
@@ -6661,6 +6714,8 @@ function VitestProjectResolver(ctx) {
|
|
|
6661
6714
|
enforce: "pre",
|
|
6662
6715
|
async resolveId(id, _, { ssr }) {
|
|
6663
6716
|
if (id === "vitest" || id.startsWith("@vitest/") || id.startsWith("vitest/")) {
|
|
6717
|
+
// always redirect the request to the root vitest plugin since
|
|
6718
|
+
// it will be the one used to run Vitest
|
|
6664
6719
|
const resolved = await ctx.server.pluginContainer.resolveId(id, undefined, {
|
|
6665
6720
|
skip: new Set([plugin]),
|
|
6666
6721
|
ssr
|
|
@@ -6680,6 +6735,7 @@ function VitestCoreResolver(ctx) {
|
|
|
6680
6735
|
return resolve(distDir, "index.js");
|
|
6681
6736
|
}
|
|
6682
6737
|
if (id.startsWith("@vitest/") || id.startsWith("vitest/")) {
|
|
6738
|
+
// ignore actual importer, we want it to be resolved relative to the root
|
|
6683
6739
|
return this.resolve(id, join(ctx.config.root, "index.html"), { skipSelf: true });
|
|
6684
6740
|
}
|
|
6685
6741
|
}
|
|
@@ -6704,6 +6760,7 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
6704
6760
|
};
|
|
6705
6761
|
if (!name) {
|
|
6706
6762
|
if (typeof options.workspacePath === "string") {
|
|
6763
|
+
// if there is a package.json, read the name from it
|
|
6707
6764
|
const dir = options.workspacePath.endsWith("/") ? options.workspacePath.slice(0, -1) : dirname(options.workspacePath);
|
|
6708
6765
|
const pkgJsonPath = resolve(dir, "package.json");
|
|
6709
6766
|
if (existsSync(pkgJsonPath)) {
|
|
@@ -6744,22 +6801,27 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
6744
6801
|
color
|
|
6745
6802
|
} }
|
|
6746
6803
|
};
|
|
6747
|
-
if (project.vitest._options.browser && viteConfig.test?.browser) {
|
|
6748
|
-
viteConfig.test.browser = mergeConfig(viteConfig.test.browser, project.vitest._options.browser);
|
|
6749
|
-
}
|
|
6750
6804
|
config.test.defines = defines;
|
|
6805
|
+
const isUserBrowserEnabled = viteConfig.test?.browser?.enabled;
|
|
6806
|
+
const isBrowserEnabled = isUserBrowserEnabled ?? (viteConfig.test?.browser && project.vitest._cliOptions.browser?.enabled);
|
|
6807
|
+
// keep project names to potentially filter it out
|
|
6751
6808
|
const workspaceNames = [name];
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
}
|
|
6757
|
-
viteConfig.test.browser.instances?.forEach((instance) => {
|
|
6758
|
-
instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
|
|
6759
|
-
workspaceNames.push(instance.name);
|
|
6760
|
-
});
|
|
6809
|
+
const browser = viteConfig.test.browser || {};
|
|
6810
|
+
if (isBrowserEnabled && browser.name && !browser.instances?.length) {
|
|
6811
|
+
// vitest injects `instances` in this case later on
|
|
6812
|
+
workspaceNames.push(name ? `${name} (${browser.name})` : browser.name);
|
|
6761
6813
|
}
|
|
6814
|
+
viteConfig.test?.browser?.instances?.forEach((instance) => {
|
|
6815
|
+
// every instance is a potential project
|
|
6816
|
+
instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
|
|
6817
|
+
if (isBrowserEnabled) {
|
|
6818
|
+
workspaceNames.push(instance.name);
|
|
6819
|
+
}
|
|
6820
|
+
});
|
|
6762
6821
|
const filters = project.vitest.config.project;
|
|
6822
|
+
// if there is `--project=...` filter, check if any of the potential projects match
|
|
6823
|
+
// if projects don't match, we ignore the test project altogether
|
|
6824
|
+
// if some of them match, they will later be filtered again by `resolveWorkspace`
|
|
6763
6825
|
if (filters.length) {
|
|
6764
6826
|
const hasProject = workspaceNames.some((name) => {
|
|
6765
6827
|
return project.vitest.matchesProjectFilter(name);
|
|
@@ -6880,6 +6942,8 @@ class TestSpecification {
|
|
|
6880
6942
|
}
|
|
6881
6943
|
|
|
6882
6944
|
async function createViteServer(inlineConfig) {
|
|
6945
|
+
// Vite prints an error (https://github.com/vitejs/vite/issues/14328)
|
|
6946
|
+
// But Vitest works correctly either way
|
|
6883
6947
|
const error = console.error;
|
|
6884
6948
|
console.error = (...args) => {
|
|
6885
6949
|
if (typeof args[0] === "string" && args[0].includes("WebSocket server error:")) {
|
|
@@ -6916,6 +6980,7 @@ class TestProject {
|
|
|
6916
6980
|
/** @internal */ typechecker;
|
|
6917
6981
|
/** @internal */ _config;
|
|
6918
6982
|
/** @internal */ _vite;
|
|
6983
|
+
/** @internal */ _hash;
|
|
6919
6984
|
runner;
|
|
6920
6985
|
closingPromise;
|
|
6921
6986
|
testFilesList = null;
|
|
@@ -6930,6 +6995,19 @@ class TestProject {
|
|
|
6930
6995
|
this.globalConfig = vitest.config;
|
|
6931
6996
|
}
|
|
6932
6997
|
/**
|
|
6998
|
+
* The unique hash of this project. This value is consistent between the reruns.
|
|
6999
|
+
*
|
|
7000
|
+
* It is based on the root of the project (not consistent between OS) and its name.
|
|
7001
|
+
*/
|
|
7002
|
+
get hash() {
|
|
7003
|
+
if (!this._hash) {
|
|
7004
|
+
throw new Error("The server was not set. It means that `project.hash` was called before the Vite server was established.");
|
|
7005
|
+
}
|
|
7006
|
+
return this._hash;
|
|
7007
|
+
}
|
|
7008
|
+
// "provide" is a property, not a method to keep the context when destructed in the global setup,
|
|
7009
|
+
// making it a method would be a breaking change, and can be done in Vitest 3 at minimum
|
|
7010
|
+
/**
|
|
6933
7011
|
* Provide a value to the test context. This value will be available to all tests with `inject`.
|
|
6934
7012
|
*/
|
|
6935
7013
|
provide = (key, value) => {
|
|
@@ -6938,6 +7016,7 @@ class TestProject {
|
|
|
6938
7016
|
} catch (err) {
|
|
6939
7017
|
throw new Error(`Cannot provide "${key}" because it's not serializable.`, { cause: err });
|
|
6940
7018
|
}
|
|
7019
|
+
// casting `any` because the default type is `never` since `ProvidedContext` is empty
|
|
6941
7020
|
this._provided[key] = value;
|
|
6942
7021
|
};
|
|
6943
7022
|
/**
|
|
@@ -6947,6 +7026,8 @@ class TestProject {
|
|
|
6947
7026
|
if (this.isRootProject()) {
|
|
6948
7027
|
return this._provided;
|
|
6949
7028
|
}
|
|
7029
|
+
// globalSetup can run even if core workspace is not part of the test run
|
|
7030
|
+
// so we need to inherit its provided context
|
|
6950
7031
|
return {
|
|
6951
7032
|
...this.vitest.getRootProject().getProvidedContext(),
|
|
6952
7033
|
...this._provided
|
|
@@ -6973,6 +7054,7 @@ class TestProject {
|
|
|
6973
7054
|
if (!this._vite) {
|
|
6974
7055
|
throw new Error("The server was not set. It means that `project.vite` was called before the Vite server was established.");
|
|
6975
7056
|
}
|
|
7057
|
+
// checking it once should be enough
|
|
6976
7058
|
Object.defineProperty(this, "vite", {
|
|
6977
7059
|
configurable: true,
|
|
6978
7060
|
writable: true,
|
|
@@ -6987,6 +7069,12 @@ class TestProject {
|
|
|
6987
7069
|
if (!this._config) {
|
|
6988
7070
|
throw new Error("The config was not set. It means that `project.config` was called before the Vite server was established.");
|
|
6989
7071
|
}
|
|
7072
|
+
// checking it once should be enough
|
|
7073
|
+
// Object.defineProperty(this, 'config', {
|
|
7074
|
+
// configurable: true,
|
|
7075
|
+
// writable: true,
|
|
7076
|
+
// value: this._config,
|
|
7077
|
+
// })
|
|
6990
7078
|
return this._config;
|
|
6991
7079
|
}
|
|
6992
7080
|
/**
|
|
@@ -7066,6 +7154,7 @@ class TestProject {
|
|
|
7066
7154
|
get logger() {
|
|
7067
7155
|
return this.vitest.logger;
|
|
7068
7156
|
}
|
|
7157
|
+
// it's possible that file path was imported with different queries (?raw, ?url, etc)
|
|
7069
7158
|
/** @deprecated use `.vite` or `.browser.vite` directly */
|
|
7070
7159
|
getModulesByFilepath(file) {
|
|
7071
7160
|
const set = this.server.moduleGraph.getModulesByFile(file) || this.browser?.vite.moduleGraph.getModulesByFile(file);
|
|
@@ -7159,7 +7248,10 @@ class TestProject {
|
|
|
7159
7248
|
expandDirectories: false
|
|
7160
7249
|
};
|
|
7161
7250
|
const files = await glob(include, globOptions);
|
|
7162
|
-
|
|
7251
|
+
// keep the slashes consistent with Vite
|
|
7252
|
+
// we are not using the pathe here because it normalizes the drive letter on Windows
|
|
7253
|
+
// and we want to keep it the same as working dir
|
|
7254
|
+
return files.map((file) => slash(path.resolve(cwd, file)));
|
|
7163
7255
|
}
|
|
7164
7256
|
/**
|
|
7165
7257
|
* Test if a file matches the test globs. This does the actual glob matching if the test is not cached, unlike `isCachedTestFile`.
|
|
@@ -7169,14 +7261,14 @@ class TestProject {
|
|
|
7169
7261
|
return true;
|
|
7170
7262
|
}
|
|
7171
7263
|
const relativeId = relative(this.config.dir || this.config.root, moduleId);
|
|
7172
|
-
if (
|
|
7264
|
+
if (pm.isMatch(relativeId, this.config.exclude)) {
|
|
7173
7265
|
return false;
|
|
7174
7266
|
}
|
|
7175
|
-
if (
|
|
7267
|
+
if (pm.isMatch(relativeId, this.config.include)) {
|
|
7176
7268
|
this.markTestFile(moduleId);
|
|
7177
7269
|
return true;
|
|
7178
7270
|
}
|
|
7179
|
-
if (this.config.includeSource?.length &&
|
|
7271
|
+
if (this.config.includeSource?.length && pm.isMatch(relativeId, this.config.includeSource)) {
|
|
7180
7272
|
const code = source?.() || readFileSync(moduleId, "utf-8");
|
|
7181
7273
|
if (this.isInSourceTestCode(code)) {
|
|
7182
7274
|
this.markTestFile(moduleId);
|
|
@@ -7200,6 +7292,7 @@ class TestProject {
|
|
|
7200
7292
|
return testFiles.filter((t) => {
|
|
7201
7293
|
const testFile = relative(dir, t).toLocaleLowerCase();
|
|
7202
7294
|
return filters.some((f) => {
|
|
7295
|
+
// if filter is a full file path, we should include it if it's in the same folder
|
|
7203
7296
|
if (isAbsolute(f) && t.startsWith(f)) {
|
|
7204
7297
|
return true;
|
|
7205
7298
|
}
|
|
@@ -7278,14 +7371,19 @@ class TestProject {
|
|
|
7278
7371
|
setServer(options, server) {
|
|
7279
7372
|
return this._configureServer(options, server);
|
|
7280
7373
|
}
|
|
7374
|
+
_setHash() {
|
|
7375
|
+
this._hash = generateHash(this._config.root + this._config.name);
|
|
7376
|
+
}
|
|
7281
7377
|
/** @internal */
|
|
7282
7378
|
async _configureServer(options, server) {
|
|
7283
7379
|
this._config = resolveConfig(this.vitest, {
|
|
7284
7380
|
...options,
|
|
7285
7381
|
coverage: this.vitest.config.coverage
|
|
7286
7382
|
}, server.config);
|
|
7383
|
+
this._setHash();
|
|
7287
7384
|
for (const _providedKey in this.config.provide) {
|
|
7288
7385
|
const providedKey = _providedKey;
|
|
7386
|
+
// type is very strict here, so we cast it to any
|
|
7289
7387
|
this.provide(providedKey, this.config.provide[providedKey]);
|
|
7290
7388
|
}
|
|
7291
7389
|
this.closingPromise = undefined;
|
|
@@ -7304,6 +7402,7 @@ class TestProject {
|
|
|
7304
7402
|
});
|
|
7305
7403
|
}
|
|
7306
7404
|
_serializeOverriddenConfig() {
|
|
7405
|
+
// TODO: serialize the config _once_ or when needed
|
|
7307
7406
|
const config = serializeConfig(this.config, this.vitest.config, this.vite.config);
|
|
7308
7407
|
if (!this.vitest.configOverride) {
|
|
7309
7408
|
return config;
|
|
@@ -7333,6 +7432,7 @@ class TestProject {
|
|
|
7333
7432
|
_provideObject(context) {
|
|
7334
7433
|
for (const _providedKey in context) {
|
|
7335
7434
|
const providedKey = _providedKey;
|
|
7435
|
+
// type is very strict here, so we cast it to any
|
|
7336
7436
|
this.provide(providedKey, context[providedKey]);
|
|
7337
7437
|
}
|
|
7338
7438
|
}
|
|
@@ -7343,6 +7443,7 @@ class TestProject {
|
|
|
7343
7443
|
project.runner = vitest.runner;
|
|
7344
7444
|
project._vite = vitest.server;
|
|
7345
7445
|
project._config = vitest.config;
|
|
7446
|
+
project._setHash();
|
|
7346
7447
|
project._provideObject(vitest.config.provide);
|
|
7347
7448
|
return project;
|
|
7348
7449
|
}
|
|
@@ -7353,6 +7454,7 @@ class TestProject {
|
|
|
7353
7454
|
clone.runner = parent.runner;
|
|
7354
7455
|
clone._vite = parent._vite;
|
|
7355
7456
|
clone._config = config;
|
|
7457
|
+
clone._setHash();
|
|
7356
7458
|
clone._parent = parent;
|
|
7357
7459
|
clone._provideObject(config.provide);
|
|
7358
7460
|
return clone;
|
|
@@ -7385,9 +7487,23 @@ async function initializeProject(workspacePath, ctx, options) {
|
|
|
7385
7487
|
await createViteServer(config);
|
|
7386
7488
|
return project;
|
|
7387
7489
|
}
|
|
7490
|
+
function generateHash(str) {
|
|
7491
|
+
let hash = 0;
|
|
7492
|
+
if (str.length === 0) {
|
|
7493
|
+
return `${hash}`;
|
|
7494
|
+
}
|
|
7495
|
+
for (let i = 0; i < str.length; i++) {
|
|
7496
|
+
const char = str.charCodeAt(i);
|
|
7497
|
+
hash = (hash << 5) - hash + char;
|
|
7498
|
+
hash = hash & hash;
|
|
7499
|
+
}
|
|
7500
|
+
return `${hash}`;
|
|
7501
|
+
}
|
|
7388
7502
|
|
|
7389
7503
|
async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projectsDefinition, names) {
|
|
7390
7504
|
const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition);
|
|
7505
|
+
// cli options that affect the project config,
|
|
7506
|
+
// not all options are allowed to be overridden
|
|
7391
7507
|
const overridesOptions = [
|
|
7392
7508
|
"logHeapUsage",
|
|
7393
7509
|
"allowOnly",
|
|
@@ -7419,7 +7535,10 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7419
7535
|
const concurrent = limitConcurrency(nodeos__default.availableParallelism?.() || nodeos__default.cpus().length || 5);
|
|
7420
7536
|
projectConfigs.forEach((options, index) => {
|
|
7421
7537
|
const configRoot = workspaceConfigPath ? dirname(workspaceConfigPath) : vitest.config.root;
|
|
7538
|
+
// if extends a config file, resolve the file path
|
|
7422
7539
|
const configFile = typeof options.extends === "string" ? resolve(configRoot, options.extends) : options.extends === true ? vitest.vite.config.configFile || false : false;
|
|
7540
|
+
// if `root` is configured, resolve it relative to the workspace file or vite root (like other options)
|
|
7541
|
+
// if `root` is not specified, inline configs use the same root as the root project
|
|
7423
7542
|
const root = options.root ? resolve(configRoot, options.root) : vitest.config.root;
|
|
7424
7543
|
projectPromises.push(concurrent(() => initializeProject(index, vitest, {
|
|
7425
7544
|
...options,
|
|
@@ -7432,6 +7551,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7432
7551
|
})));
|
|
7433
7552
|
});
|
|
7434
7553
|
for (const path of fileProjects) {
|
|
7554
|
+
// if file leads to the root config, then we can just reuse it because we already initialized it
|
|
7435
7555
|
if (vitest.vite.config.configFile === path) {
|
|
7436
7556
|
const project = getDefaultTestProject(vitest);
|
|
7437
7557
|
if (project) {
|
|
@@ -7447,6 +7567,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7447
7567
|
test: cliOverrides
|
|
7448
7568
|
})));
|
|
7449
7569
|
}
|
|
7570
|
+
// pretty rare case - the glob didn't match anything and there are no inline configs
|
|
7450
7571
|
if (!projectPromises.length) {
|
|
7451
7572
|
throw new Error([
|
|
7452
7573
|
"No projects were found. Make sure your configuration is correct. ",
|
|
@@ -7460,6 +7581,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7460
7581
|
for (const result of resolvedProjectsPromises) {
|
|
7461
7582
|
if (result.status === "rejected") {
|
|
7462
7583
|
if (result.reason instanceof VitestFilteredOutProjectError) {
|
|
7584
|
+
// filter out filtered out projects
|
|
7463
7585
|
continue;
|
|
7464
7586
|
}
|
|
7465
7587
|
errors.push(result.reason);
|
|
@@ -7470,6 +7592,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7470
7592
|
if (errors.length) {
|
|
7471
7593
|
throw new AggregateError(errors, "Failed to initialize projects. There were errors during projects setup. See below for more details.");
|
|
7472
7594
|
}
|
|
7595
|
+
// project names are guaranteed to be unique
|
|
7473
7596
|
for (const project of resolvedProjects) {
|
|
7474
7597
|
const name = project.name;
|
|
7475
7598
|
if (names.has(name)) {
|
|
@@ -7514,10 +7637,12 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
|
7514
7637
|
].filter(Boolean).join("")));
|
|
7515
7638
|
}
|
|
7516
7639
|
const originalName = project.config.name;
|
|
7640
|
+
// if original name is in the --project=name filter, keep all instances
|
|
7517
7641
|
const filteredInstances = vitest.matchesProjectFilter(originalName) ? instances : instances.filter((instance) => {
|
|
7518
7642
|
const newName = instance.name;
|
|
7519
7643
|
return vitest.matchesProjectFilter(newName);
|
|
7520
7644
|
});
|
|
7645
|
+
// every project was filtered out
|
|
7521
7646
|
if (!filteredInstances.length) {
|
|
7522
7647
|
removeProjects.add(project);
|
|
7523
7648
|
return;
|
|
@@ -7560,7 +7685,7 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
|
7560
7685
|
if (!isTTY) {
|
|
7561
7686
|
throw new Error(`${message} Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option.`);
|
|
7562
7687
|
}
|
|
7563
|
-
const prompts = await import('./index.
|
|
7688
|
+
const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; });
|
|
7564
7689
|
const { projectName } = await prompts.default({
|
|
7565
7690
|
type: "select",
|
|
7566
7691
|
name: "projectName",
|
|
@@ -7597,13 +7722,19 @@ function cloneConfig(project, { browser,...config }) {
|
|
|
7597
7722
|
}, overrideConfig);
|
|
7598
7723
|
}
|
|
7599
7724
|
async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition) {
|
|
7725
|
+
// project configurations that were specified directly
|
|
7600
7726
|
const projectsOptions = [];
|
|
7727
|
+
// custom config files that were specified directly or resolved from a directory
|
|
7601
7728
|
const projectsConfigFiles = [];
|
|
7729
|
+
// custom glob matches that should be resolved as directories or config files
|
|
7602
7730
|
const projectsGlobMatches = [];
|
|
7731
|
+
// directories that don't have a config file inside, but should be treated as projects
|
|
7603
7732
|
const nonConfigProjectDirectories = [];
|
|
7604
7733
|
for (const definition of projectsDefinition) {
|
|
7605
7734
|
if (typeof definition === "string") {
|
|
7606
7735
|
const stringOption = definition.replace("<rootDir>", vitest.config.root);
|
|
7736
|
+
// if the string doesn't contain a glob, we can resolve it directly
|
|
7737
|
+
// ['./vitest.config.js']
|
|
7607
7738
|
if (!isDynamicPattern(stringOption)) {
|
|
7608
7739
|
const file = resolve(vitest.config.root, stringOption);
|
|
7609
7740
|
if (!existsSync(file)) {
|
|
@@ -7612,6 +7743,7 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7612
7743
|
throw new Error(`${note} references a non-existing file or a directory: ${file}`);
|
|
7613
7744
|
}
|
|
7614
7745
|
const stats = await promises.stat(file);
|
|
7746
|
+
// user can specify a config file directly
|
|
7615
7747
|
if (stats.isFile()) {
|
|
7616
7748
|
projectsConfigFiles.push(file);
|
|
7617
7749
|
} else if (stats.isDirectory()) {
|
|
@@ -7623,6 +7755,7 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7623
7755
|
nonConfigProjectDirectories.push(directory);
|
|
7624
7756
|
}
|
|
7625
7757
|
} else {
|
|
7758
|
+
// should never happen
|
|
7626
7759
|
throw new TypeError(`Unexpected file type: ${file}`);
|
|
7627
7760
|
}
|
|
7628
7761
|
} else {
|
|
@@ -7654,6 +7787,8 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7654
7787
|
};
|
|
7655
7788
|
const projectsFs = await glob(projectsGlobMatches, globOptions);
|
|
7656
7789
|
await Promise.all(projectsFs.map(async (path) => {
|
|
7790
|
+
// directories are allowed with a glob like `packages/*`
|
|
7791
|
+
// in this case every directory is treated as a project
|
|
7657
7792
|
if (path.endsWith("/")) {
|
|
7658
7793
|
const configFile = await resolveDirectoryConfig(path);
|
|
7659
7794
|
if (configFile) {
|
|
@@ -7675,6 +7810,8 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7675
7810
|
}
|
|
7676
7811
|
async function resolveDirectoryConfig(directory) {
|
|
7677
7812
|
const files = new Set(await promises.readdir(directory));
|
|
7813
|
+
// default resolution looks for vitest.config.* or vite.config.* files
|
|
7814
|
+
// this simulates how `findUp` works in packages/vitest/src/node/create.ts:29
|
|
7678
7815
|
const configFile = configFiles.find((file) => files.has(file));
|
|
7679
7816
|
if (configFile) {
|
|
7680
7817
|
return resolve(directory, configFile);
|
|
@@ -7687,6 +7824,7 @@ function getDefaultTestProject(vitest) {
|
|
|
7687
7824
|
if (!filter.length) {
|
|
7688
7825
|
return project;
|
|
7689
7826
|
}
|
|
7827
|
+
// check for the project name and browser names
|
|
7690
7828
|
const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
|
|
7691
7829
|
if (hasProjects) {
|
|
7692
7830
|
return project;
|
|
@@ -7808,6 +7946,7 @@ class VitestSpecifications {
|
|
|
7808
7946
|
const files = [];
|
|
7809
7947
|
const dir = process.cwd();
|
|
7810
7948
|
const parsedFilters = filters.map((f) => parseFilter(f));
|
|
7949
|
+
// Require includeTaskLocation when a location filter is passed
|
|
7811
7950
|
if (!this.vitest.config.includeTaskLocation && parsedFilters.some((f) => f.lineNumber !== undefined)) {
|
|
7812
7951
|
throw new IncludeTaskLocationDisabledError();
|
|
7813
7952
|
}
|
|
@@ -7815,6 +7954,7 @@ class VitestSpecifications {
|
|
|
7815
7954
|
...f,
|
|
7816
7955
|
filename: resolve(dir, f.filename)
|
|
7817
7956
|
})));
|
|
7957
|
+
// Key is file and val specifies whether we have matched this file with testLocation
|
|
7818
7958
|
const testLocHasMatch = {};
|
|
7819
7959
|
await Promise.all(this.vitest.projects.map(async (project) => {
|
|
7820
7960
|
const { testFiles, typecheckTestFiles } = await project.globTestFiles(parsedFilters.map((f) => f.filename));
|
|
@@ -7878,9 +8018,12 @@ class VitestSpecifications {
|
|
|
7878
8018
|
return specs;
|
|
7879
8019
|
}
|
|
7880
8020
|
const forceRerunTriggers = this.vitest.config.forceRerunTriggers;
|
|
7881
|
-
|
|
8021
|
+
const matcher = forceRerunTriggers.length ? pm(forceRerunTriggers) : undefined;
|
|
8022
|
+
if (matcher && related.some((file) => matcher(file))) {
|
|
7882
8023
|
return specs;
|
|
7883
8024
|
}
|
|
8025
|
+
// don't run anything if no related sources are found
|
|
8026
|
+
// if we are in watch mode, we want to process all tests
|
|
7884
8027
|
if (!this.vitest.config.watch && !related.length) {
|
|
7885
8028
|
return [];
|
|
7886
8029
|
}
|
|
@@ -7890,6 +8033,7 @@ class VitestSpecifications {
|
|
|
7890
8033
|
}));
|
|
7891
8034
|
const runningTests = [];
|
|
7892
8035
|
for (const [specification, deps] of testGraphs) {
|
|
8036
|
+
// if deps or the test itself were changed
|
|
7893
8037
|
if (related.some((path) => path === specification.moduleId || deps.has(path))) {
|
|
7894
8038
|
runningTests.push(specification);
|
|
7895
8039
|
}
|
|
@@ -8065,6 +8209,7 @@ class TestCase extends ReportedTaskImplementation {
|
|
|
8065
8209
|
*/
|
|
8066
8210
|
diagnostic() {
|
|
8067
8211
|
const result = this.task.result;
|
|
8212
|
+
// startTime should always be available if the test has properly finished
|
|
8068
8213
|
if (!result || !result.startTime) {
|
|
8069
8214
|
return undefined;
|
|
8070
8215
|
}
|
|
@@ -8386,6 +8531,7 @@ class StateManager {
|
|
|
8386
8531
|
return keys.map((key) => this.filesMap.get(key)).flat().filter((file) => file && !file.local);
|
|
8387
8532
|
}
|
|
8388
8533
|
return Array.from(this.filesMap.values()).flat().filter((file) => !file.local).sort((f1, f2) => {
|
|
8534
|
+
// print typecheck files first
|
|
8389
8535
|
if (f1.meta?.typecheck && f2.meta?.typecheck) {
|
|
8390
8536
|
return 0;
|
|
8391
8537
|
}
|
|
@@ -8414,6 +8560,8 @@ class StateManager {
|
|
|
8414
8560
|
const existing = this.filesMap.get(file.filepath) || [];
|
|
8415
8561
|
const otherFiles = existing.filter((i) => i.projectName !== file.projectName || i.meta.typecheck !== file.meta.typecheck);
|
|
8416
8562
|
const currentFile = existing.find((i) => i.projectName === file.projectName);
|
|
8563
|
+
// keep logs for the previous file because it should always be initiated before the collections phase
|
|
8564
|
+
// which means that all logs are collected during the collection and not inside tests
|
|
8417
8565
|
if (currentFile) {
|
|
8418
8566
|
file.logs = currentFile.logs;
|
|
8419
8567
|
}
|
|
@@ -8434,6 +8582,7 @@ class StateManager {
|
|
|
8434
8582
|
return;
|
|
8435
8583
|
}
|
|
8436
8584
|
const filtered = files.filter((file) => file.projectName !== project.config.name);
|
|
8585
|
+
// always keep a File task, so we can associate logs with it
|
|
8437
8586
|
if (!filtered.length) {
|
|
8438
8587
|
this.filesMap.set(path, [fileTask]);
|
|
8439
8588
|
} else {
|
|
@@ -8468,6 +8617,7 @@ class StateManager {
|
|
|
8468
8617
|
if (task) {
|
|
8469
8618
|
task.result = result;
|
|
8470
8619
|
task.meta = meta;
|
|
8620
|
+
// skipped with new PendingError
|
|
8471
8621
|
if (result?.state === "skip") {
|
|
8472
8622
|
task.mode = "skip";
|
|
8473
8623
|
}
|
|
@@ -8520,6 +8670,9 @@ class TestRun {
|
|
|
8520
8670
|
}
|
|
8521
8671
|
async updated(update, events) {
|
|
8522
8672
|
this.vitest.state.updateTasks(update);
|
|
8673
|
+
// TODO: what is the order or reports here?
|
|
8674
|
+
// "onTaskUpdate" in parallel with others or before all or after all?
|
|
8675
|
+
// TODO: error handling - what happens if custom reporter throws an error?
|
|
8523
8676
|
await this.vitest.report("onTaskUpdate", update);
|
|
8524
8677
|
for (const [id, event] of events) {
|
|
8525
8678
|
await this.reportEvent(id, event).catch((error) => {
|
|
@@ -8528,6 +8681,7 @@ class TestRun {
|
|
|
8528
8681
|
}
|
|
8529
8682
|
}
|
|
8530
8683
|
async end(specifications, errors, coverage) {
|
|
8684
|
+
// specification won't have the File task if they were filtered by the --shard command
|
|
8531
8685
|
const modules = specifications.map((spec) => spec.testModule).filter((s) => s != null);
|
|
8532
8686
|
const files = modules.map((m) => m.task);
|
|
8533
8687
|
const state = this.vitest.isCancelling ? "interrupted" : process.exitCode ? "failed" : "passed";
|
|
@@ -8552,6 +8706,9 @@ class TestRun {
|
|
|
8552
8706
|
if (event === "suite-finished") {
|
|
8553
8707
|
assert$1(entity.type === "suite" || entity.type === "module", "Entity type must be suite or module");
|
|
8554
8708
|
if (entity.state() === "skipped") {
|
|
8709
|
+
// everything inside suite or a module is skipped,
|
|
8710
|
+
// so we won't get any children events
|
|
8711
|
+
// we need to report everything manually
|
|
8555
8712
|
await this.reportChildren(entity.children);
|
|
8556
8713
|
}
|
|
8557
8714
|
if (entity.type === "module") {
|
|
@@ -8706,6 +8863,7 @@ class VitestWatcher {
|
|
|
8706
8863
|
this.changedTests.add(id);
|
|
8707
8864
|
this.scheduleRerun(id);
|
|
8708
8865
|
} else {
|
|
8866
|
+
// it's possible that file was already there but watcher triggered "add" event instead
|
|
8709
8867
|
const needsRerun = this.handleFileChanged(id);
|
|
8710
8868
|
if (needsRerun) {
|
|
8711
8869
|
this.scheduleRerun(id);
|
|
@@ -8719,7 +8877,7 @@ class VitestWatcher {
|
|
|
8719
8877
|
if (this.changedTests.has(filepath) || this.invalidates.has(filepath)) {
|
|
8720
8878
|
return false;
|
|
8721
8879
|
}
|
|
8722
|
-
if (
|
|
8880
|
+
if (pm.isMatch(filepath, this.vitest.config.forceRerunTriggers)) {
|
|
8723
8881
|
this.vitest.state.getFilepaths().forEach((file) => this.changedTests.add(file));
|
|
8724
8882
|
return true;
|
|
8725
8883
|
}
|
|
@@ -8728,6 +8886,8 @@ class VitestWatcher {
|
|
|
8728
8886
|
return moduleGraph.getModulesByFile(filepath)?.size;
|
|
8729
8887
|
});
|
|
8730
8888
|
if (!projects.length) {
|
|
8889
|
+
// if there are no modules it's possible that server was restarted
|
|
8890
|
+
// we don't have information about importers anymore, so let's check if the file is a test file at least
|
|
8731
8891
|
if (this.vitest.state.filesMap.has(filepath) || this.vitest.projects.some((project) => project._isCachedTestFile(filepath))) {
|
|
8732
8892
|
this.changedTests.add(filepath);
|
|
8733
8893
|
return true;
|
|
@@ -8741,6 +8901,7 @@ class VitestWatcher {
|
|
|
8741
8901
|
continue;
|
|
8742
8902
|
}
|
|
8743
8903
|
this.invalidates.add(filepath);
|
|
8904
|
+
// one of test files that we already run, or one of test files that we can run
|
|
8744
8905
|
if (this.vitest.state.filesMap.has(filepath) || project._isCachedTestFile(filepath)) {
|
|
8745
8906
|
this.changedTests.add(filepath);
|
|
8746
8907
|
files.push(filepath);
|
|
@@ -8812,7 +8973,7 @@ class Vitest {
|
|
|
8812
8973
|
resolvedProjects = [];
|
|
8813
8974
|
/** @internal */ _browserLastPort = defaultBrowserPort;
|
|
8814
8975
|
/** @internal */ _browserSessions = new BrowserSessions();
|
|
8815
|
-
/** @internal */
|
|
8976
|
+
/** @internal */ _cliOptions = {};
|
|
8816
8977
|
/** @internal */ reporters = [];
|
|
8817
8978
|
/** @internal */ vitenode = undefined;
|
|
8818
8979
|
/** @internal */ runner = undefined;
|
|
@@ -8828,8 +8989,9 @@ class Vitest {
|
|
|
8828
8989
|
_cache;
|
|
8829
8990
|
_snapshot;
|
|
8830
8991
|
_workspaceConfigPath;
|
|
8831
|
-
constructor(mode, options = {}) {
|
|
8992
|
+
constructor(mode, cliOptions, options = {}) {
|
|
8832
8993
|
this.mode = mode;
|
|
8994
|
+
this._cliOptions = cliOptions;
|
|
8833
8995
|
this.logger = new Logger(this, options.stdout, options.stderr);
|
|
8834
8996
|
this.packageInstaller = options.packageInstaller || new VitestPackageInstaller();
|
|
8835
8997
|
this.specifications = new VitestSpecifications(this);
|
|
@@ -8890,12 +9052,11 @@ class Vitest {
|
|
|
8890
9052
|
return this._cache;
|
|
8891
9053
|
}
|
|
8892
9054
|
/** @deprecated internal */
|
|
8893
|
-
setServer(options, server
|
|
8894
|
-
return this._setServer(options, server
|
|
9055
|
+
setServer(options, server) {
|
|
9056
|
+
return this._setServer(options, server);
|
|
8895
9057
|
}
|
|
8896
9058
|
/** @internal */
|
|
8897
|
-
async _setServer(options, server
|
|
8898
|
-
this._options = options;
|
|
9059
|
+
async _setServer(options, server) {
|
|
8899
9060
|
this.watcher.unregisterWatcher();
|
|
8900
9061
|
clearTimeout(this._rerunTimer);
|
|
8901
9062
|
this.restartsCount += 1;
|
|
@@ -8934,6 +9095,7 @@ class Vitest {
|
|
|
8934
9095
|
}
|
|
8935
9096
|
});
|
|
8936
9097
|
if (this.config.watch) {
|
|
9098
|
+
// hijack server restart
|
|
8937
9099
|
const serverRestart = server.restart;
|
|
8938
9100
|
server.restart = async (...args) => {
|
|
8939
9101
|
await Promise.all(this._onRestartListeners.map((fn) => fn()));
|
|
@@ -8941,6 +9103,7 @@ class Vitest {
|
|
|
8941
9103
|
await this.close();
|
|
8942
9104
|
await serverRestart(...args);
|
|
8943
9105
|
};
|
|
9106
|
+
// since we set `server.hmr: false`, Vite does not auto restart itself
|
|
8944
9107
|
server.watcher.on("change", async (file) => {
|
|
8945
9108
|
file = normalize(file);
|
|
8946
9109
|
const isConfig = file === server.config.configFile || this.projects.some((p) => p.vite.config.configFile === file) || file === this._workspaceConfigPath;
|
|
@@ -8956,7 +9119,7 @@ class Vitest {
|
|
|
8956
9119
|
try {
|
|
8957
9120
|
await this.cache.results.readFromCache();
|
|
8958
9121
|
} catch {}
|
|
8959
|
-
const projects = await this.resolveProjects(
|
|
9122
|
+
const projects = await this.resolveProjects(this._cliOptions);
|
|
8960
9123
|
this.resolvedProjects = projects;
|
|
8961
9124
|
this.projects = projects;
|
|
8962
9125
|
await Promise.all(projects.flatMap((project) => {
|
|
@@ -8967,7 +9130,7 @@ class Vitest {
|
|
|
8967
9130
|
injectTestProjects: this.injectTestProject
|
|
8968
9131
|
}));
|
|
8969
9132
|
}));
|
|
8970
|
-
if (
|
|
9133
|
+
if (this._cliOptions.browser?.enabled) {
|
|
8971
9134
|
const browserProjects = this.projects.filter((p) => p.config.browser.enabled);
|
|
8972
9135
|
if (!browserProjects.length) {
|
|
8973
9136
|
throw new Error(`Vitest received --browser flag, but no project had a browser configuration.`);
|
|
@@ -8997,7 +9160,7 @@ class Vitest {
|
|
|
8997
9160
|
*/
|
|
8998
9161
|
injectTestProject = async (config) => {
|
|
8999
9162
|
const currentNames = new Set(this.projects.map((p) => p.name));
|
|
9000
|
-
const projects = await resolveProjects(this, this.
|
|
9163
|
+
const projects = await resolveProjects(this, this._cliOptions, undefined, Array.isArray(config) ? config : [config], currentNames);
|
|
9001
9164
|
this.projects.push(...projects);
|
|
9002
9165
|
return projects;
|
|
9003
9166
|
};
|
|
@@ -9084,7 +9247,10 @@ class Vitest {
|
|
|
9084
9247
|
}
|
|
9085
9248
|
const workspaceConfigPath = await this.resolveWorkspaceConfigPath();
|
|
9086
9249
|
this._workspaceConfigPath = workspaceConfigPath;
|
|
9250
|
+
// user doesn't have a workspace config, return default project
|
|
9087
9251
|
if (!workspaceConfigPath) {
|
|
9252
|
+
// user can filter projects with --project flag, `getDefaultTestProject`
|
|
9253
|
+
// returns the project only if it matches the filter
|
|
9088
9254
|
const project = getDefaultTestProject(this);
|
|
9089
9255
|
if (!project) {
|
|
9090
9256
|
return [];
|
|
@@ -9175,6 +9341,7 @@ class Vitest {
|
|
|
9175
9341
|
}
|
|
9176
9342
|
async collect(filters) {
|
|
9177
9343
|
const files = await this.specifications.getRelevantTestSpecifications(filters);
|
|
9344
|
+
// if run with --changed, don't exit if no tests are found
|
|
9178
9345
|
if (!files.length) {
|
|
9179
9346
|
return {
|
|
9180
9347
|
testModules: [],
|
|
@@ -9211,15 +9378,18 @@ class Vitest {
|
|
|
9211
9378
|
}
|
|
9212
9379
|
this.filenamePattern = filters && filters?.length > 0 ? filters : undefined;
|
|
9213
9380
|
const files = await this.specifications.getRelevantTestSpecifications(filters);
|
|
9381
|
+
// if run with --changed, don't exit if no tests are found
|
|
9214
9382
|
if (!files.length) {
|
|
9215
9383
|
const throwAnError = !this.config.watch || !(this.config.changed || this.config.related?.length);
|
|
9216
9384
|
await this._testRun.start([]);
|
|
9217
9385
|
const coverage = await this.coverageProvider?.generateCoverage?.({ allTestsRun: true });
|
|
9386
|
+
// set exit code before calling `onTestRunEnd` so the lifecycle is consistent
|
|
9218
9387
|
if (throwAnError) {
|
|
9219
9388
|
const exitCode = this.config.passWithNoTests ? 0 : 1;
|
|
9220
9389
|
process.exitCode = exitCode;
|
|
9221
9390
|
}
|
|
9222
9391
|
await this._testRun.end([], [], coverage);
|
|
9392
|
+
// Report coverage for uncovered files
|
|
9223
9393
|
await this.reportCoverage(coverage, true);
|
|
9224
9394
|
if (throwAnError) {
|
|
9225
9395
|
throw new FilesNotFoundError(this.mode);
|
|
@@ -9230,6 +9400,7 @@ class Vitest {
|
|
|
9230
9400
|
unhandledErrors: []
|
|
9231
9401
|
};
|
|
9232
9402
|
if (files.length) {
|
|
9403
|
+
// populate once, update cache on watch
|
|
9233
9404
|
await this.cache.stats.populateStats(this.config.root, files);
|
|
9234
9405
|
testModules = await this.runFiles(files, true);
|
|
9235
9406
|
}
|
|
@@ -9249,6 +9420,7 @@ class Vitest {
|
|
|
9249
9420
|
} finally {
|
|
9250
9421
|
await this.report("onInit", this);
|
|
9251
9422
|
}
|
|
9423
|
+
// populate test files cache so watch mode can trigger a file rerun
|
|
9252
9424
|
await this.globTestSpecifications();
|
|
9253
9425
|
if (this.config.watch) {
|
|
9254
9426
|
await this.report("onWatcherStart");
|
|
@@ -9303,9 +9475,11 @@ class Vitest {
|
|
|
9303
9475
|
}
|
|
9304
9476
|
async runFiles(specs, allTestsRun) {
|
|
9305
9477
|
await this._testRun.start(specs);
|
|
9478
|
+
// previous run
|
|
9306
9479
|
await this.runningPromise;
|
|
9307
9480
|
this._onCancelListeners = [];
|
|
9308
9481
|
this.isCancelling = false;
|
|
9482
|
+
// schedule the new run
|
|
9309
9483
|
this.runningPromise = (async () => {
|
|
9310
9484
|
try {
|
|
9311
9485
|
if (!this.pool) {
|
|
@@ -9337,6 +9511,7 @@ class Vitest {
|
|
|
9337
9511
|
unhandledErrors: this.state.getUnhandledErrors()
|
|
9338
9512
|
};
|
|
9339
9513
|
} finally {
|
|
9514
|
+
// TODO: wait for coverage only if `onFinished` is defined
|
|
9340
9515
|
const coverage = await this.coverageProvider?.generateCoverage({ allTestsRun });
|
|
9341
9516
|
const errors = this.state.getUnhandledErrors();
|
|
9342
9517
|
this._checkUnhandledErrors(errors);
|
|
@@ -9346,6 +9521,7 @@ class Vitest {
|
|
|
9346
9521
|
})().finally(() => {
|
|
9347
9522
|
this.runningPromise = undefined;
|
|
9348
9523
|
this.isFirstRun = false;
|
|
9524
|
+
// all subsequent runs will treat this as a fresh run
|
|
9349
9525
|
this.config.changed = false;
|
|
9350
9526
|
this.config.related = undefined;
|
|
9351
9527
|
});
|
|
@@ -9358,9 +9534,11 @@ class Vitest {
|
|
|
9358
9534
|
async collectTests(specifications) {
|
|
9359
9535
|
const filepaths = specifications.map((spec) => spec.moduleId);
|
|
9360
9536
|
this.state.collectPaths(filepaths);
|
|
9537
|
+
// previous run
|
|
9361
9538
|
await this.runningPromise;
|
|
9362
9539
|
this._onCancelListeners = [];
|
|
9363
9540
|
this.isCancelling = false;
|
|
9541
|
+
// schedule the new run
|
|
9364
9542
|
this.runningPromise = (async () => {
|
|
9365
9543
|
if (!this.pool) {
|
|
9366
9544
|
this.pool = createPool(this);
|
|
@@ -9376,6 +9554,8 @@ class Vitest {
|
|
|
9376
9554
|
this.state.catchError(err, "Unhandled Error");
|
|
9377
9555
|
}
|
|
9378
9556
|
const files = this.state.getFiles();
|
|
9557
|
+
// can only happen if there was a syntax error in describe block
|
|
9558
|
+
// or there was an error importing a file
|
|
9379
9559
|
if (hasFailed(files)) {
|
|
9380
9560
|
process.exitCode = 1;
|
|
9381
9561
|
}
|
|
@@ -9385,6 +9565,7 @@ class Vitest {
|
|
|
9385
9565
|
};
|
|
9386
9566
|
})().finally(() => {
|
|
9387
9567
|
this.runningPromise = undefined;
|
|
9568
|
+
// all subsequent runs will treat this as a fresh run
|
|
9388
9569
|
this.config.changed = false;
|
|
9389
9570
|
this.config.related = undefined;
|
|
9390
9571
|
});
|
|
@@ -9447,11 +9628,13 @@ class Vitest {
|
|
|
9447
9628
|
}
|
|
9448
9629
|
/** @internal */
|
|
9449
9630
|
async changeNamePattern(pattern, files = this.state.getFilepaths(), trigger) {
|
|
9631
|
+
// Empty test name pattern should reset filename pattern as well
|
|
9450
9632
|
if (pattern === "") {
|
|
9451
9633
|
this.filenamePattern = undefined;
|
|
9452
9634
|
}
|
|
9453
9635
|
const testNamePattern = pattern ? new RegExp(pattern) : undefined;
|
|
9454
9636
|
this.configOverride.testNamePattern = testNamePattern;
|
|
9637
|
+
// filter only test files that have tests matching the pattern
|
|
9455
9638
|
if (testNamePattern) {
|
|
9456
9639
|
files = files.filter((filepath) => {
|
|
9457
9640
|
const files = this.state.getFiles([filepath]);
|
|
@@ -9478,6 +9661,7 @@ class Vitest {
|
|
|
9478
9661
|
* @param files The list of files on the file system
|
|
9479
9662
|
*/
|
|
9480
9663
|
async updateSnapshot(files) {
|
|
9664
|
+
// default to failed files
|
|
9481
9665
|
files = files || [...this.state.getFailedFilepaths(), ...this.snapshot.summary.uncheckedKeysByFile.map((s) => s.filePath)];
|
|
9482
9666
|
this.enableSnapshotUpdate();
|
|
9483
9667
|
try {
|
|
@@ -9525,11 +9709,13 @@ class Vitest {
|
|
|
9525
9709
|
this.configOverride.testNamePattern = undefined;
|
|
9526
9710
|
}
|
|
9527
9711
|
_rerunTimer;
|
|
9712
|
+
// we can't use a single `triggerId` yet because vscode extension relies on this
|
|
9528
9713
|
async scheduleRerun(triggerId) {
|
|
9529
9714
|
const currentCount = this.restartsCount;
|
|
9530
9715
|
clearTimeout(this._rerunTimer);
|
|
9531
9716
|
await this.runningPromise;
|
|
9532
9717
|
clearTimeout(this._rerunTimer);
|
|
9718
|
+
// server restarted
|
|
9533
9719
|
if (this.restartsCount !== currentCount) {
|
|
9534
9720
|
return;
|
|
9535
9721
|
}
|
|
@@ -9538,6 +9724,7 @@ class Vitest {
|
|
|
9538
9724
|
this.watcher.invalidates.clear();
|
|
9539
9725
|
return;
|
|
9540
9726
|
}
|
|
9727
|
+
// server restarted
|
|
9541
9728
|
if (this.restartsCount !== currentCount) {
|
|
9542
9729
|
return;
|
|
9543
9730
|
}
|
|
@@ -9547,6 +9734,7 @@ class Vitest {
|
|
|
9547
9734
|
if (this.filenamePattern) {
|
|
9548
9735
|
const filteredFiles = await this.globTestSpecifications(this.filenamePattern);
|
|
9549
9736
|
files = files.filter((file) => filteredFiles.some((f) => f.moduleId === file));
|
|
9737
|
+
// A file that does not match the current filename pattern was changed
|
|
9550
9738
|
if (files.length === 0) {
|
|
9551
9739
|
return;
|
|
9552
9740
|
}
|
|
@@ -9554,6 +9742,7 @@ class Vitest {
|
|
|
9554
9742
|
this.watcher.changedTests.clear();
|
|
9555
9743
|
const triggerIds = new Set(triggerId.map((id) => relative(this.config.root, id)));
|
|
9556
9744
|
const triggerLabel = Array.from(triggerIds).join(", ");
|
|
9745
|
+
// get file specifications and filter them if needed
|
|
9557
9746
|
const specifications = files.flatMap((file) => this.getModuleSpecifications(file)).filter((specification) => {
|
|
9558
9747
|
if (this._onFilterWatchedSpecification.length === 0) {
|
|
9559
9748
|
return true;
|
|
@@ -9597,6 +9786,7 @@ class Vitest {
|
|
|
9597
9786
|
}
|
|
9598
9787
|
if (this.coverageProvider) {
|
|
9599
9788
|
await this.coverageProvider.reportCoverage(coverage, { allTestsRun });
|
|
9789
|
+
// notify coverage iframe reload
|
|
9600
9790
|
for (const reporter of this.reporters) {
|
|
9601
9791
|
if (reporter instanceof WebSocketReporter) {
|
|
9602
9792
|
reporter.onFinishedReportCoverage();
|
|
@@ -9615,10 +9805,13 @@ class Vitest {
|
|
|
9615
9805
|
if (this.coreWorkspaceProject && !teardownProjects.includes(this.coreWorkspaceProject)) {
|
|
9616
9806
|
teardownProjects.push(this.coreWorkspaceProject);
|
|
9617
9807
|
}
|
|
9808
|
+
// do teardown before closing the server
|
|
9618
9809
|
for (const project of teardownProjects.reverse()) {
|
|
9619
9810
|
await project._teardownGlobalSetup();
|
|
9620
9811
|
}
|
|
9621
9812
|
const closePromises = this.projects.map((w) => w.close());
|
|
9813
|
+
// close the core workspace server only once
|
|
9814
|
+
// it's possible that it's not initialized at all because it's not running any tests
|
|
9622
9815
|
if (this.coreWorkspaceProject && !this.projects.includes(this.coreWorkspaceProject)) {
|
|
9623
9816
|
closePromises.push(this.coreWorkspaceProject.close().then(() => this._vite = undefined));
|
|
9624
9817
|
}
|
|
@@ -9675,7 +9868,7 @@ class Vitest {
|
|
|
9675
9868
|
await Promise.all(this.reporters.map((r) => r[name]?.(
|
|
9676
9869
|
// @ts-expect-error let me go
|
|
9677
9870
|
...args
|
|
9678
|
-
)));
|
|
9871
|
+
)));
|
|
9679
9872
|
}
|
|
9680
9873
|
/** @internal */
|
|
9681
9874
|
async _globTestFilepaths() {
|
|
@@ -9698,6 +9891,7 @@ class Vitest {
|
|
|
9698
9891
|
getModuleProjects(filepath) {
|
|
9699
9892
|
return this.projects.filter((project) => {
|
|
9700
9893
|
return project.getModulesByFilepath(filepath).size;
|
|
9894
|
+
// TODO: reevaluate || project.browser?.moduleGraph.getModulesByFile(id)?.size
|
|
9701
9895
|
});
|
|
9702
9896
|
}
|
|
9703
9897
|
/**
|
|
@@ -9748,7 +9942,8 @@ class Vitest {
|
|
|
9748
9942
|
* Check if the project with a given name should be included.
|
|
9749
9943
|
*/
|
|
9750
9944
|
matchesProjectFilter(name) {
|
|
9751
|
-
const projects = this._config?.project || this.
|
|
9945
|
+
const projects = this._config?.project || this._cliOptions?.project;
|
|
9946
|
+
// no filters applied, any project can be included
|
|
9752
9947
|
if (!projects || !projects.length) {
|
|
9753
9948
|
return true;
|
|
9754
9949
|
}
|
|
@@ -9764,11 +9959,11 @@ function assert(condition, property, name = property) {
|
|
|
9764
9959
|
}
|
|
9765
9960
|
}
|
|
9766
9961
|
|
|
9767
|
-
async function VitestPlugin(options = {},
|
|
9962
|
+
async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(options))) {
|
|
9768
9963
|
const userConfig = deepMerge({}, options);
|
|
9769
9964
|
async function UIPlugin() {
|
|
9770
|
-
await
|
|
9771
|
-
return (await import('@vitest/ui')).default(
|
|
9965
|
+
await vitest.packageInstaller.ensureInstalled("@vitest/ui", options.root || process.cwd(), vitest.version);
|
|
9966
|
+
return (await import('@vitest/ui')).default(vitest);
|
|
9772
9967
|
}
|
|
9773
9968
|
return [
|
|
9774
9969
|
{
|
|
@@ -9779,10 +9974,17 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9779
9974
|
},
|
|
9780
9975
|
async config(viteConfig) {
|
|
9781
9976
|
if (options.watch) {
|
|
9977
|
+
// Earlier runs have overwritten values of the `options`.
|
|
9978
|
+
// Reset it back to initial user config before setting up the server again.
|
|
9782
9979
|
options = deepMerge({}, userConfig);
|
|
9783
9980
|
}
|
|
9981
|
+
// preliminary merge of options to be able to create server options for vite
|
|
9982
|
+
// however to allow vitest plugins to modify vitest config values
|
|
9983
|
+
// this is repeated in configResolved where the config is final
|
|
9784
9984
|
const testConfig = deepMerge({}, configDefaults, removeUndefinedValues(viteConfig.test ?? {}), options);
|
|
9785
9985
|
testConfig.api = resolveApiServerConfig(testConfig, defaultPort);
|
|
9986
|
+
// store defines for globalThis to make them
|
|
9987
|
+
// reassignable when running in worker in src/runtime/setup.ts
|
|
9786
9988
|
const defines = deleteDefineConfig(viteConfig);
|
|
9787
9989
|
options.defines = defines;
|
|
9788
9990
|
let open = false;
|
|
@@ -9824,11 +10026,13 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9824
10026
|
deps: testConfig.deps ?? viteConfig.test?.deps
|
|
9825
10027
|
}
|
|
9826
10028
|
};
|
|
9827
|
-
if (
|
|
9828
|
-
|
|
10029
|
+
if (vitest.configOverride.project) {
|
|
10030
|
+
// project filter was set by the user, so we need to filter the project
|
|
10031
|
+
options.project = vitest.configOverride.project;
|
|
9829
10032
|
}
|
|
9830
|
-
config.customLogger = createViteLogger(
|
|
10033
|
+
config.customLogger = createViteLogger(vitest.logger, viteConfig.logLevel || "warn", { allowClearScreen: false });
|
|
9831
10034
|
config.customLogger = silenceImportViteIgnoreWarning(config.customLogger);
|
|
10035
|
+
// we want inline dependencies to be resolved by analyser plugin so module graph is populated correctly
|
|
9832
10036
|
if (viteConfig.ssr?.noExternal !== true) {
|
|
9833
10037
|
const inline = testConfig.server?.deps?.inline;
|
|
9834
10038
|
if (inline === true) {
|
|
@@ -9836,13 +10040,17 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9836
10040
|
} else {
|
|
9837
10041
|
const noExternal = viteConfig.ssr?.noExternal;
|
|
9838
10042
|
const noExternalArray = typeof noExternal !== "undefined" ? toArray(noExternal) : undefined;
|
|
10043
|
+
// filter the same packages
|
|
9839
10044
|
const uniqueInline = inline && noExternalArray ? inline.filter((dep) => !noExternalArray.includes(dep)) : inline;
|
|
9840
10045
|
config.ssr = { noExternal: uniqueInline };
|
|
9841
10046
|
}
|
|
9842
10047
|
}
|
|
10048
|
+
// chokidar fsevents is unstable on macos when emitting "ready" event
|
|
9843
10049
|
if (process.platform === "darwin" && process.env.VITE_TEST_WATCHER_DEBUG) {
|
|
9844
10050
|
const watch = config.server.watch;
|
|
9845
10051
|
if (watch) {
|
|
10052
|
+
// eslint-disable-next-line ts/ban-ts-comment
|
|
10053
|
+
// @ts-ignore Vite 6 compat
|
|
9846
10054
|
watch.useFsEvents = false;
|
|
9847
10055
|
watch.usePolling = false;
|
|
9848
10056
|
}
|
|
@@ -9853,7 +10061,7 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9853
10061
|
config.css.modules ??= {};
|
|
9854
10062
|
if (config.css.modules) {
|
|
9855
10063
|
config.css.modules.generateScopedName = (name, filename) => {
|
|
9856
|
-
const root =
|
|
10064
|
+
const root = vitest.config.root || options.root || process.cwd();
|
|
9857
10065
|
return generateScopedClassName(classNameStrategy, name, relative(root, filename));
|
|
9858
10066
|
};
|
|
9859
10067
|
}
|
|
@@ -9868,14 +10076,20 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9868
10076
|
if ("alias" in viteConfigTest) {
|
|
9869
10077
|
delete viteConfigTest.alias;
|
|
9870
10078
|
}
|
|
10079
|
+
// viteConfig.test is final now, merge it for real
|
|
9871
10080
|
options = deepMerge({}, configDefaults, viteConfigTest, options);
|
|
9872
10081
|
options.api = resolveApiServerConfig(options, defaultPort);
|
|
10082
|
+
// we replace every "import.meta.env" with "process.env"
|
|
10083
|
+
// to allow reassigning, so we need to put all envs on process.env
|
|
9873
10084
|
const { PROD, DEV,...envs } = viteConfig.env;
|
|
10085
|
+
// process.env can have only string values and will cast string on it if we pass other type,
|
|
10086
|
+
// so we are making them truthy
|
|
9874
10087
|
process.env.PROD ??= PROD ? "1" : "";
|
|
9875
10088
|
process.env.DEV ??= DEV ? "1" : "";
|
|
9876
10089
|
for (const name in envs) {
|
|
9877
10090
|
process.env[name] ??= envs[name];
|
|
9878
10091
|
}
|
|
10092
|
+
// don't watch files in run mode
|
|
9879
10093
|
if (!options.watch) {
|
|
9880
10094
|
viteConfig.server.watch = null;
|
|
9881
10095
|
}
|
|
@@ -9885,7 +10099,7 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9885
10099
|
configurable: true
|
|
9886
10100
|
});
|
|
9887
10101
|
const originalName = options.name;
|
|
9888
|
-
if (options.browser?.
|
|
10102
|
+
if (options.browser?.instances) {
|
|
9889
10103
|
options.browser.instances.forEach((instance) => {
|
|
9890
10104
|
instance.name ??= originalName ? `${originalName} (${instance.browser})` : instance.browser;
|
|
9891
10105
|
});
|
|
@@ -9896,13 +10110,15 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9896
10110
|
async handler(server) {
|
|
9897
10111
|
if (options.watch && process.env.VITE_TEST_WATCHER_DEBUG) {
|
|
9898
10112
|
server.watcher.on("ready", () => {
|
|
10113
|
+
// eslint-disable-next-line no-console
|
|
9899
10114
|
console.log("[debug] watcher is ready");
|
|
9900
10115
|
});
|
|
9901
10116
|
}
|
|
9902
|
-
await
|
|
10117
|
+
await vitest._setServer(options, server);
|
|
9903
10118
|
if (options.api && options.watch) {
|
|
9904
|
-
(await Promise.resolve().then(function () { return setup$1; })).setup(
|
|
10119
|
+
(await Promise.resolve().then(function () { return setup$1; })).setup(vitest);
|
|
9905
10120
|
}
|
|
10121
|
+
// #415, in run mode we don't need the watcher, close it would improve the performance
|
|
9906
10122
|
if (!options.watch) {
|
|
9907
10123
|
await server.watcher.close();
|
|
9908
10124
|
}
|
|
@@ -9910,9 +10126,9 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9910
10126
|
}
|
|
9911
10127
|
},
|
|
9912
10128
|
SsrReplacerPlugin(),
|
|
9913
|
-
...CSSEnablerPlugin(
|
|
9914
|
-
CoverageTransform(
|
|
9915
|
-
VitestCoreResolver(
|
|
10129
|
+
...CSSEnablerPlugin(vitest),
|
|
10130
|
+
CoverageTransform(vitest),
|
|
10131
|
+
VitestCoreResolver(vitest),
|
|
9916
10132
|
options.ui ? await UIPlugin() : null,
|
|
9917
10133
|
...MocksPlugins(),
|
|
9918
10134
|
VitestOptimizer(),
|
|
@@ -9929,15 +10145,16 @@ function removeUndefinedValues(obj) {
|
|
|
9929
10145
|
}
|
|
9930
10146
|
|
|
9931
10147
|
async function createVitest(mode, options, viteOverrides = {}, vitestOptions = {}) {
|
|
9932
|
-
const ctx = new Vitest(mode, vitestOptions);
|
|
10148
|
+
const ctx = new Vitest(mode, deepClone(options), vitestOptions);
|
|
9933
10149
|
const root = slash(resolve$1(options.root || process.cwd()));
|
|
9934
10150
|
const configPath = options.config === false ? false : options.config ? resolve$1(root, options.config) : await findUp(configFiles, { cwd: root });
|
|
9935
10151
|
options.config = configPath;
|
|
10152
|
+
const { browser: _removeBrowser,...restOptions } = options;
|
|
9936
10153
|
const config = {
|
|
9937
10154
|
configFile: configPath,
|
|
9938
10155
|
configLoader: options.configLoader,
|
|
9939
10156
|
mode: options.mode || mode,
|
|
9940
|
-
plugins: await VitestPlugin(
|
|
10157
|
+
plugins: await VitestPlugin(restOptions, ctx)
|
|
9941
10158
|
};
|
|
9942
10159
|
const server = await createViteServer(mergeConfig(config, mergeConfig(viteOverrides, { root: options.root })));
|
|
9943
10160
|
if (ctx.config.api?.port) {
|
|
@@ -10068,6 +10285,7 @@ class WatchFilter {
|
|
|
10068
10285
|
const lines = str.split(/\r?\n/);
|
|
10069
10286
|
for (const line of lines) {
|
|
10070
10287
|
const columns = "columns" in this.stdout ? this.stdout.columns : 80;
|
|
10288
|
+
// We have to take care of screen width in case of long lines
|
|
10071
10289
|
rows += 1 + Math.floor(Math.max(stripVTControlCharacters(line).length - 1, 0) / columns);
|
|
10072
10290
|
}
|
|
10073
10291
|
this.write(`${ESC}1G`);
|
|
@@ -10122,6 +10340,8 @@ ${keys.map((i) => c.dim(" press ") + c.reset([i[0]].flat().map(c.bold).join(",
|
|
|
10122
10340
|
function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
10123
10341
|
let latestFilename = "";
|
|
10124
10342
|
async function _keypressHandler(str, key) {
|
|
10343
|
+
// Cancel run and exit when ctrl-c or esc is pressed.
|
|
10344
|
+
// If cancelling takes long and key is pressed multiple times, exit forcefully.
|
|
10125
10345
|
if (str === "" || str === "\x1B" || key && key.ctrl && key.name === "c") {
|
|
10126
10346
|
if (!ctx.isCancelling) {
|
|
10127
10347
|
ctx.logger.log(c.red("Cancelling test run. Press CTRL+c again to exit forcefully.\n"));
|
|
@@ -10130,6 +10350,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
10130
10350
|
}
|
|
10131
10351
|
return ctx.exit(true);
|
|
10132
10352
|
}
|
|
10353
|
+
// window not support suspend
|
|
10133
10354
|
if (!isWindows && key && key.ctrl && key.name === "z") {
|
|
10134
10355
|
process.kill(process.ppid, "SIGTSTP");
|
|
10135
10356
|
process.kill(process.pid, "SIGTSTP");
|
|
@@ -10142,31 +10363,40 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
10142
10363
|
}
|
|
10143
10364
|
return;
|
|
10144
10365
|
}
|
|
10366
|
+
// quit
|
|
10145
10367
|
if (name === "q") {
|
|
10146
10368
|
return ctx.exit(true);
|
|
10147
10369
|
}
|
|
10370
|
+
// help
|
|
10148
10371
|
if (name === "h") {
|
|
10149
10372
|
return printShortcutsHelp();
|
|
10150
10373
|
}
|
|
10374
|
+
// update snapshot
|
|
10151
10375
|
if (name === "u") {
|
|
10152
10376
|
return ctx.updateSnapshot();
|
|
10153
10377
|
}
|
|
10378
|
+
// rerun all tests
|
|
10154
10379
|
if (name === "a" || name === "return") {
|
|
10155
10380
|
const files = await ctx._globTestFilepaths();
|
|
10156
10381
|
return ctx.changeNamePattern("", files, "rerun all tests");
|
|
10157
10382
|
}
|
|
10383
|
+
// rerun current pattern tests
|
|
10158
10384
|
if (name === "r") {
|
|
10159
10385
|
return ctx.rerunFiles();
|
|
10160
10386
|
}
|
|
10387
|
+
// rerun only failed tests
|
|
10161
10388
|
if (name === "f") {
|
|
10162
10389
|
return ctx.rerunFailed();
|
|
10163
10390
|
}
|
|
10391
|
+
// change project filter
|
|
10164
10392
|
if (name === "w") {
|
|
10165
10393
|
return inputProjectName();
|
|
10166
10394
|
}
|
|
10395
|
+
// change testNamePattern
|
|
10167
10396
|
if (name === "t") {
|
|
10168
10397
|
return inputNamePattern();
|
|
10169
10398
|
}
|
|
10399
|
+
// change fileNamePattern
|
|
10170
10400
|
if (name === "p") {
|
|
10171
10401
|
return inputFilePattern();
|
|
10172
10402
|
}
|
|
@@ -10192,6 +10422,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
10192
10422
|
const reg = new RegExp(str);
|
|
10193
10423
|
return tests.map((test) => test.name).filter((testName) => testName.match(reg));
|
|
10194
10424
|
} catch {
|
|
10425
|
+
// `new RegExp` may throw error when input is invalid regexp
|
|
10195
10426
|
return [];
|
|
10196
10427
|
}
|
|
10197
10428
|
});
|
|
@@ -10200,6 +10431,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
10200
10431
|
return;
|
|
10201
10432
|
}
|
|
10202
10433
|
const files = ctx.state.getFilepaths();
|
|
10434
|
+
// if running in standalone mode, Vitest instance doesn't know about any test file
|
|
10203
10435
|
const cliFiles = ctx.config.standalone && !files.length ? await ctx._globTestFilepaths() : undefined;
|
|
10204
10436
|
await ctx.changeNamePattern(filter?.trim() || "", cliFiles, "change pattern");
|
|
10205
10437
|
}
|
|
@@ -10329,6 +10561,7 @@ async function prepareVitest(mode, options = {}, viteOverrides, vitestOptions) {
|
|
|
10329
10561
|
if (options.run) {
|
|
10330
10562
|
options.watch = false;
|
|
10331
10563
|
}
|
|
10564
|
+
// this shouldn't affect _application root_ that can be changed inside config
|
|
10332
10565
|
const root = resolve(options.root || process.cwd());
|
|
10333
10566
|
const ctx = await createVitest(mode, options, viteOverrides, vitestOptions);
|
|
10334
10567
|
const environmentPackage = getEnvPackageName(ctx.config.environment);
|