vitest 3.2.0-beta.2 → 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/dist/browser.d.ts +3 -3
- package/dist/browser.js +1 -1
- package/dist/chunks/{base.DwtwORaC.js → base.D4119yLM.js} +4 -3
- package/dist/chunks/{benchmark.BoF7jW0Q.js → benchmark.Cf_PACH1.js} +1 -1
- package/dist/chunks/{cac.I9MLYfT-.js → cac.DWaWHIIE.js} +18 -15
- package/dist/chunks/{cli-api.d6IK1pnk.js → cli-api.CnmEXkxs.js} +250 -49
- 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.OGU09Jbh.js → coverage.C73DaDgS.js} +116 -12
- package/dist/chunks/{creator.DGAdZ4Hj.js → creator.C8WKy2eW.js} +10 -7
- 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.JlGHLJZT.js → execute.B3q-2LPV.js} +25 -0
- package/dist/chunks/{global.d.BPa1eL3O.d.ts → global.d.BNLIi6yo.d.ts} +3 -1
- package/dist/chunks/{globals.CpxW8ccg.js → globals.CI21aWXF.js} +7 -6
- package/dist/chunks/{index.DFXFpH3w.js → index.2jgTs_Q5.js} +19 -1
- package/dist/chunks/{index.CV36oG_L.js → index.Bter3jj9.js} +83 -16
- package/dist/chunks/{index.DswW_LEs.js → index.CbT4iuwc.js} +7 -4
- package/dist/chunks/index.D3XRDfWc.js +213 -0
- package/dist/chunks/{index.CfXMNXHg.js → index.DNgLEKsQ.js} +4 -2
- package/dist/chunks/{index.CmC5OK9L.js → index.JOzufsrU.js} +2 -1
- 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.CLC9rhKy.d.ts → reporters.d.Bt4IGtsa.d.ts} +24 -6
- package/dist/chunks/{rpc.D9_013TY.js → rpc.BKExFSRG.js} +2 -1
- package/dist/chunks/{runBaseTests.Dn2vyej_.js → runBaseTests.B_M1TTsK.js} +19 -10
- package/dist/chunks/{setup-common.CYo3Y0dD.js → setup-common.CF-O-dZX.js} +2 -1
- package/dist/chunks/{typechecker.DnTrplSJ.js → typechecker.BgzF-6iO.js} +78 -21
- 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.CBZ3M_ru.d.ts → vite.d.B-Kx3KCF.d.ts} +3 -1
- package/dist/chunks/{vm.C1HHjtNS.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 +4 -4
- 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 +5 -5
- 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 +24 -12
- package/dist/index.js +5 -5
- package/dist/node.d.ts +18 -10
- package/dist/node.js +14 -12
- package/dist/reporters.d.ts +4 -4
- package/dist/reporters.js +3 -3
- 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 -9
- 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 +18 -18
- package/dist/chunks/index.CK1YOQaa.js +0 -143
|
@@ -5,15 +5,15 @@ 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
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, 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.
|
|
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,8 +28,8 @@ 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, r as resolveConfig, e as groupBy, f as getCoverageProvider, j as createPool, w as wildcardPatternToRegExp, a as resolveApiServerConfig, s as stdout } from './coverage.
|
|
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
35
|
import { createRequire } from 'node:module';
|
|
@@ -41,9 +41,9 @@ 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.
|
|
46
|
+
import { a as BenchmarkReportsMap } from './index.JOzufsrU.js';
|
|
47
47
|
import assert$1 from 'node:assert';
|
|
48
48
|
import { serializeError } from '@vitest/utils/error';
|
|
49
49
|
import readline from 'node:readline';
|
|
@@ -779,6 +779,14 @@ function requirePermessageDeflate () {
|
|
|
779
779
|
this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
|
|
780
780
|
this[kError][kStatusCode] = 1009;
|
|
781
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
|
+
//
|
|
782
790
|
this.reset();
|
|
783
791
|
}
|
|
784
792
|
|
|
@@ -794,6 +802,12 @@ function requirePermessageDeflate () {
|
|
|
794
802
|
// closed when an error is emitted.
|
|
795
803
|
//
|
|
796
804
|
this[kPerMessageDeflate]._inflate = null;
|
|
805
|
+
|
|
806
|
+
if (this[kError]) {
|
|
807
|
+
this[kCallback](this[kError]);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
|
|
797
811
|
err[kStatusCode] = 1007;
|
|
798
812
|
this[kCallback](err);
|
|
799
813
|
}
|
|
@@ -3511,7 +3525,7 @@ function requireWebsocket () {
|
|
|
3511
3525
|
if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) {
|
|
3512
3526
|
invalidUrlMessage =
|
|
3513
3527
|
'The URL\'s protocol must be one of "ws:", "wss:", ' +
|
|
3514
|
-
'"http:", "https", or "ws+unix:"';
|
|
3528
|
+
'"http:", "https:", or "ws+unix:"';
|
|
3515
3529
|
} else if (isIpcUrl && !parsedUrl.pathname) {
|
|
3516
3530
|
invalidUrlMessage = "The URL's pathname is empty";
|
|
3517
3531
|
} else if (parsedUrl.hash) {
|
|
@@ -5035,7 +5049,10 @@ function clearId(id) {
|
|
|
5035
5049
|
return id?.replace(/\?v=\w+$/, "") || "";
|
|
5036
5050
|
}
|
|
5037
5051
|
|
|
5052
|
+
// Serialization support utils.
|
|
5038
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.
|
|
5039
5056
|
return Object.getOwnPropertyNames(value).reduce((clone, prop) => ({
|
|
5040
5057
|
...clone,
|
|
5041
5058
|
[prop]: value[prop]
|
|
@@ -5061,12 +5078,15 @@ function stringifyReplace(key, value) {
|
|
|
5061
5078
|
|
|
5062
5079
|
function isValidApiRequest(config, req) {
|
|
5063
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.
|
|
5064
5082
|
try {
|
|
5065
5083
|
const token = url.searchParams.get("token");
|
|
5066
5084
|
if (token && crypto.timingSafeEqual(Buffer.from(token), Buffer.from(config.api.token))) {
|
|
5067
5085
|
return true;
|
|
5068
5086
|
}
|
|
5069
|
-
}
|
|
5087
|
+
}
|
|
5088
|
+
// an error is thrown when the length is incorrect
|
|
5089
|
+
catch {}
|
|
5070
5090
|
return false;
|
|
5071
5091
|
}
|
|
5072
5092
|
|
|
@@ -5267,6 +5287,7 @@ class BrowserSessions {
|
|
|
5267
5287
|
this.sessions.delete(sessionId);
|
|
5268
5288
|
}
|
|
5269
5289
|
createSession(sessionId, project, pool) {
|
|
5290
|
+
// this promise only waits for the WS connection with the orhcestrator to be established
|
|
5270
5291
|
const defer = createDefer();
|
|
5271
5292
|
const timeout = setTimeout(() => {
|
|
5272
5293
|
defer.reject(new Error(`Failed to connect to the browser session "${sessionId}" [${project.name}] within the timeout.`));
|
|
@@ -5489,6 +5510,7 @@ class Logger {
|
|
|
5489
5510
|
this.log(PAD + c.dim(c.green(`UI started at http://${host}:${c.bold(port)}${base}`)));
|
|
5490
5511
|
} else if (this.ctx.config.api?.port) {
|
|
5491
5512
|
const resolvedUrls = this.ctx.server.resolvedUrls;
|
|
5513
|
+
// workaround for https://github.com/vitejs/vite/issues/15438, it was fixed in vite 5.1
|
|
5492
5514
|
const fallbackUrl = `http://${this.ctx.config.api.host || "localhost"}:${this.ctx.config.api.port}`;
|
|
5493
5515
|
const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0] ?? fallbackUrl;
|
|
5494
5516
|
this.log(PAD + c.dim(c.green(`API started at ${new URL("/", origin)}`)));
|
|
@@ -5552,6 +5574,8 @@ class Logger {
|
|
|
5552
5574
|
};
|
|
5553
5575
|
const onExit = (signal, exitCode) => {
|
|
5554
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
|
|
5555
5579
|
if (process.exitCode === undefined) {
|
|
5556
5580
|
process.exitCode = exitCode !== undefined ? 128 + exitCode : Number(signal);
|
|
5557
5581
|
}
|
|
@@ -5615,7 +5639,8 @@ class VitestPackageInstaller {
|
|
|
5615
5639
|
});
|
|
5616
5640
|
if (install) {
|
|
5617
5641
|
const packageName = version ? `${dependency}@${version}` : dependency;
|
|
5618
|
-
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
|
|
5619
5644
|
process.stderr.write(c.yellow(`\nPackage ${packageName} installed, re-run the command to start.\n`));
|
|
5620
5645
|
process.exit();
|
|
5621
5646
|
return true;
|
|
@@ -5627,6 +5652,7 @@ class VitestPackageInstaller {
|
|
|
5627
5652
|
function serializeConfig(config, coreConfig, viteConfig) {
|
|
5628
5653
|
const optimizer = config.deps?.optimizer;
|
|
5629
5654
|
const poolOptions = config.poolOptions;
|
|
5655
|
+
// Resolve from server.config to avoid comparing against default value
|
|
5630
5656
|
const isolate = viteConfig?.test?.isolate;
|
|
5631
5657
|
return {
|
|
5632
5658
|
environmentOptions: config.environmentOptions,
|
|
@@ -5799,6 +5825,7 @@ function generateCssFilenameHash(filepath) {
|
|
|
5799
5825
|
return hash("md5", filepath, "hex").slice(0, 6);
|
|
5800
5826
|
}
|
|
5801
5827
|
function generateScopedClassName(strategy, name, filename) {
|
|
5828
|
+
// should be configured by Vite defaults
|
|
5802
5829
|
if (strategy === "scoped") {
|
|
5803
5830
|
return null;
|
|
5804
5831
|
}
|
|
@@ -5823,6 +5850,8 @@ function clearScreen(logger) {
|
|
|
5823
5850
|
let lastType;
|
|
5824
5851
|
let lastMsg;
|
|
5825
5852
|
let sameCount = 0;
|
|
5853
|
+
// Only initialize the timeFormatter when the timestamp option is used, and
|
|
5854
|
+
// reuse it across all loggers
|
|
5826
5855
|
let timeFormatter;
|
|
5827
5856
|
function getTimeFormatter() {
|
|
5828
5857
|
timeFormatter ??= new Intl.DateTimeFormat(undefined, {
|
|
@@ -5832,6 +5861,10 @@ function getTimeFormatter() {
|
|
|
5832
5861
|
});
|
|
5833
5862
|
return timeFormatter;
|
|
5834
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
|
|
5835
5868
|
function createViteLogger(console, level = "info", options = {}) {
|
|
5836
5869
|
const loggedErrors = new WeakSet();
|
|
5837
5870
|
const { prefix = "[vite]", allowClearScreen = true } = options;
|
|
@@ -5912,6 +5945,7 @@ function createViteLogger(console, level = "info", options = {}) {
|
|
|
5912
5945
|
};
|
|
5913
5946
|
return logger;
|
|
5914
5947
|
}
|
|
5948
|
+
// silence warning by Vite for statically not analyzable dynamic import
|
|
5915
5949
|
function silenceImportViteIgnoreWarning(logger) {
|
|
5916
5950
|
return {
|
|
5917
5951
|
...logger,
|
|
@@ -5934,6 +5968,8 @@ function isCSS(id) {
|
|
|
5934
5968
|
function isCSSModule(id) {
|
|
5935
5969
|
return cssModuleRE.test(id);
|
|
5936
5970
|
}
|
|
5971
|
+
// inline css requests are expected to just return the
|
|
5972
|
+
// string content directly and not the proxy module
|
|
5937
5973
|
function isInline(id) {
|
|
5938
5974
|
return cssInlineRE.test(id);
|
|
5939
5975
|
}
|
|
@@ -5965,6 +6001,8 @@ function CSSEnablerPlugin(ctx) {
|
|
|
5965
6001
|
if (!isCSS(id)) {
|
|
5966
6002
|
return;
|
|
5967
6003
|
}
|
|
6004
|
+
// css plugin inside Vite won't do anything if the code is empty
|
|
6005
|
+
// but it will put __vite__updateStyle anyway
|
|
5968
6006
|
if (!shouldProcessCSS(id)) {
|
|
5969
6007
|
return { code: "" };
|
|
5970
6008
|
}
|
|
@@ -5977,6 +6015,9 @@ function CSSEnablerPlugin(ctx) {
|
|
|
5977
6015
|
return;
|
|
5978
6016
|
}
|
|
5979
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
|
|
5980
6021
|
const scopeStrategy = typeof ctx.config.css !== "boolean" && ctx.config.css.modules?.classNameStrategy || "stable";
|
|
5981
6022
|
const proxyReturn = getCSSModuleProxyReturn(scopeStrategy, relative(ctx.config.root, id));
|
|
5982
6023
|
const code = `export default new Proxy(Object.create(null), {
|
|
@@ -6477,6 +6518,10 @@ function stripLiteralDetailed(code, options) {
|
|
|
6477
6518
|
|
|
6478
6519
|
const metaUrlLength = "import.meta.url".length;
|
|
6479
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.
|
|
6480
6525
|
function NormalizeURLPlugin() {
|
|
6481
6526
|
return {
|
|
6482
6527
|
name: "vitest:normalize-url",
|
|
@@ -6490,6 +6535,7 @@ function NormalizeURLPlugin() {
|
|
|
6490
6535
|
const assetImportMetaUrlRE = /\bnew\s+URL\s*\(\s*(?:'[^']+'|"[^"]+"|`[^`]+`)\s*,\s*(?:'' \+ )?import\.meta\.url\s*(?:,\s*)?\)/g;
|
|
6491
6536
|
let updatedCode = code;
|
|
6492
6537
|
let match;
|
|
6538
|
+
// eslint-disable-next-line no-cond-assign
|
|
6493
6539
|
while (match = assetImportMetaUrlRE.exec(cleanString)) {
|
|
6494
6540
|
const { 0: exp, index } = match;
|
|
6495
6541
|
const metaUrlIndex = index + exp.indexOf("import.meta.url");
|
|
@@ -6543,6 +6589,8 @@ function resolveOptimizerConfig(_testOptions, viteOptions, testConfig, viteCache
|
|
|
6543
6589
|
include
|
|
6544
6590
|
};
|
|
6545
6591
|
}
|
|
6592
|
+
// `optimizeDeps.disabled` is deprecated since v5.1.0-beta.1
|
|
6593
|
+
// https://github.com/vitejs/vite/pull/15184
|
|
6546
6594
|
if (major >= 5 && minor >= 1 || major >= 6) {
|
|
6547
6595
|
if (newConfig.optimizeDeps.disabled) {
|
|
6548
6596
|
newConfig.optimizeDeps.noDiscovery = true;
|
|
@@ -6566,6 +6614,8 @@ function deleteDefineConfig(viteConfig) {
|
|
|
6566
6614
|
try {
|
|
6567
6615
|
replacement = typeof val === "string" ? JSON.parse(val) : val;
|
|
6568
6616
|
} catch {
|
|
6617
|
+
// probably means it contains reference to some variable,
|
|
6618
|
+
// like this: "__VAR__": "process.env.VAR"
|
|
6569
6619
|
continue;
|
|
6570
6620
|
}
|
|
6571
6621
|
if (key.startsWith("import.meta.env.")) {
|
|
@@ -6626,6 +6676,8 @@ function VitestOptimizer() {
|
|
|
6626
6676
|
};
|
|
6627
6677
|
}
|
|
6628
6678
|
|
|
6679
|
+
// so people can reassign envs at runtime
|
|
6680
|
+
// import.meta.env.VITE_NAME = 'app' -> process.env.VITE_NAME = 'app'
|
|
6629
6681
|
function SsrReplacerPlugin() {
|
|
6630
6682
|
return {
|
|
6631
6683
|
name: "vitest:ssr-replacer",
|
|
@@ -6662,6 +6714,8 @@ function VitestProjectResolver(ctx) {
|
|
|
6662
6714
|
enforce: "pre",
|
|
6663
6715
|
async resolveId(id, _, { ssr }) {
|
|
6664
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
|
|
6665
6719
|
const resolved = await ctx.server.pluginContainer.resolveId(id, undefined, {
|
|
6666
6720
|
skip: new Set([plugin]),
|
|
6667
6721
|
ssr
|
|
@@ -6681,6 +6735,7 @@ function VitestCoreResolver(ctx) {
|
|
|
6681
6735
|
return resolve(distDir, "index.js");
|
|
6682
6736
|
}
|
|
6683
6737
|
if (id.startsWith("@vitest/") || id.startsWith("vitest/")) {
|
|
6738
|
+
// ignore actual importer, we want it to be resolved relative to the root
|
|
6684
6739
|
return this.resolve(id, join(ctx.config.root, "index.html"), { skipSelf: true });
|
|
6685
6740
|
}
|
|
6686
6741
|
}
|
|
@@ -6705,6 +6760,7 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
6705
6760
|
};
|
|
6706
6761
|
if (!name) {
|
|
6707
6762
|
if (typeof options.workspacePath === "string") {
|
|
6763
|
+
// if there is a package.json, read the name from it
|
|
6708
6764
|
const dir = options.workspacePath.endsWith("/") ? options.workspacePath.slice(0, -1) : dirname(options.workspacePath);
|
|
6709
6765
|
const pkgJsonPath = resolve(dir, "package.json");
|
|
6710
6766
|
if (existsSync(pkgJsonPath)) {
|
|
@@ -6745,22 +6801,27 @@ function WorkspaceVitestPlugin(project, options) {
|
|
|
6745
6801
|
color
|
|
6746
6802
|
} }
|
|
6747
6803
|
};
|
|
6748
|
-
if (project.vitest._options.browser && viteConfig.test?.browser) {
|
|
6749
|
-
viteConfig.test.browser = mergeConfig(viteConfig.test.browser, project.vitest._options.browser);
|
|
6750
|
-
}
|
|
6751
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
|
|
6752
6808
|
const workspaceNames = [name];
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
}
|
|
6758
|
-
viteConfig.test.browser.instances?.forEach((instance) => {
|
|
6759
|
-
instance.name ??= name ? `${name} (${instance.browser})` : instance.browser;
|
|
6760
|
-
workspaceNames.push(instance.name);
|
|
6761
|
-
});
|
|
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);
|
|
6762
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
|
+
});
|
|
6763
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`
|
|
6764
6825
|
if (filters.length) {
|
|
6765
6826
|
const hasProject = workspaceNames.some((name) => {
|
|
6766
6827
|
return project.vitest.matchesProjectFilter(name);
|
|
@@ -6881,6 +6942,8 @@ class TestSpecification {
|
|
|
6881
6942
|
}
|
|
6882
6943
|
|
|
6883
6944
|
async function createViteServer(inlineConfig) {
|
|
6945
|
+
// Vite prints an error (https://github.com/vitejs/vite/issues/14328)
|
|
6946
|
+
// But Vitest works correctly either way
|
|
6884
6947
|
const error = console.error;
|
|
6885
6948
|
console.error = (...args) => {
|
|
6886
6949
|
if (typeof args[0] === "string" && args[0].includes("WebSocket server error:")) {
|
|
@@ -6942,6 +7005,8 @@ class TestProject {
|
|
|
6942
7005
|
}
|
|
6943
7006
|
return this._hash;
|
|
6944
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
|
|
6945
7010
|
/**
|
|
6946
7011
|
* Provide a value to the test context. This value will be available to all tests with `inject`.
|
|
6947
7012
|
*/
|
|
@@ -6951,6 +7016,7 @@ class TestProject {
|
|
|
6951
7016
|
} catch (err) {
|
|
6952
7017
|
throw new Error(`Cannot provide "${key}" because it's not serializable.`, { cause: err });
|
|
6953
7018
|
}
|
|
7019
|
+
// casting `any` because the default type is `never` since `ProvidedContext` is empty
|
|
6954
7020
|
this._provided[key] = value;
|
|
6955
7021
|
};
|
|
6956
7022
|
/**
|
|
@@ -6960,6 +7026,8 @@ class TestProject {
|
|
|
6960
7026
|
if (this.isRootProject()) {
|
|
6961
7027
|
return this._provided;
|
|
6962
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
|
|
6963
7031
|
return {
|
|
6964
7032
|
...this.vitest.getRootProject().getProvidedContext(),
|
|
6965
7033
|
...this._provided
|
|
@@ -6986,6 +7054,7 @@ class TestProject {
|
|
|
6986
7054
|
if (!this._vite) {
|
|
6987
7055
|
throw new Error("The server was not set. It means that `project.vite` was called before the Vite server was established.");
|
|
6988
7056
|
}
|
|
7057
|
+
// checking it once should be enough
|
|
6989
7058
|
Object.defineProperty(this, "vite", {
|
|
6990
7059
|
configurable: true,
|
|
6991
7060
|
writable: true,
|
|
@@ -7000,6 +7069,12 @@ class TestProject {
|
|
|
7000
7069
|
if (!this._config) {
|
|
7001
7070
|
throw new Error("The config was not set. It means that `project.config` was called before the Vite server was established.");
|
|
7002
7071
|
}
|
|
7072
|
+
// checking it once should be enough
|
|
7073
|
+
// Object.defineProperty(this, 'config', {
|
|
7074
|
+
// configurable: true,
|
|
7075
|
+
// writable: true,
|
|
7076
|
+
// value: this._config,
|
|
7077
|
+
// })
|
|
7003
7078
|
return this._config;
|
|
7004
7079
|
}
|
|
7005
7080
|
/**
|
|
@@ -7079,6 +7154,7 @@ class TestProject {
|
|
|
7079
7154
|
get logger() {
|
|
7080
7155
|
return this.vitest.logger;
|
|
7081
7156
|
}
|
|
7157
|
+
// it's possible that file path was imported with different queries (?raw, ?url, etc)
|
|
7082
7158
|
/** @deprecated use `.vite` or `.browser.vite` directly */
|
|
7083
7159
|
getModulesByFilepath(file) {
|
|
7084
7160
|
const set = this.server.moduleGraph.getModulesByFile(file) || this.browser?.vite.moduleGraph.getModulesByFile(file);
|
|
@@ -7172,6 +7248,9 @@ class TestProject {
|
|
|
7172
7248
|
expandDirectories: false
|
|
7173
7249
|
};
|
|
7174
7250
|
const files = await glob(include, globOptions);
|
|
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
|
|
7175
7254
|
return files.map((file) => slash(path.resolve(cwd, file)));
|
|
7176
7255
|
}
|
|
7177
7256
|
/**
|
|
@@ -7213,6 +7292,7 @@ class TestProject {
|
|
|
7213
7292
|
return testFiles.filter((t) => {
|
|
7214
7293
|
const testFile = relative(dir, t).toLocaleLowerCase();
|
|
7215
7294
|
return filters.some((f) => {
|
|
7295
|
+
// if filter is a full file path, we should include it if it's in the same folder
|
|
7216
7296
|
if (isAbsolute(f) && t.startsWith(f)) {
|
|
7217
7297
|
return true;
|
|
7218
7298
|
}
|
|
@@ -7303,6 +7383,7 @@ class TestProject {
|
|
|
7303
7383
|
this._setHash();
|
|
7304
7384
|
for (const _providedKey in this.config.provide) {
|
|
7305
7385
|
const providedKey = _providedKey;
|
|
7386
|
+
// type is very strict here, so we cast it to any
|
|
7306
7387
|
this.provide(providedKey, this.config.provide[providedKey]);
|
|
7307
7388
|
}
|
|
7308
7389
|
this.closingPromise = undefined;
|
|
@@ -7321,6 +7402,7 @@ class TestProject {
|
|
|
7321
7402
|
});
|
|
7322
7403
|
}
|
|
7323
7404
|
_serializeOverriddenConfig() {
|
|
7405
|
+
// TODO: serialize the config _once_ or when needed
|
|
7324
7406
|
const config = serializeConfig(this.config, this.vitest.config, this.vite.config);
|
|
7325
7407
|
if (!this.vitest.configOverride) {
|
|
7326
7408
|
return config;
|
|
@@ -7350,6 +7432,7 @@ class TestProject {
|
|
|
7350
7432
|
_provideObject(context) {
|
|
7351
7433
|
for (const _providedKey in context) {
|
|
7352
7434
|
const providedKey = _providedKey;
|
|
7435
|
+
// type is very strict here, so we cast it to any
|
|
7353
7436
|
this.provide(providedKey, context[providedKey]);
|
|
7354
7437
|
}
|
|
7355
7438
|
}
|
|
@@ -7419,6 +7502,8 @@ function generateHash(str) {
|
|
|
7419
7502
|
|
|
7420
7503
|
async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projectsDefinition, names) {
|
|
7421
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
|
|
7422
7507
|
const overridesOptions = [
|
|
7423
7508
|
"logHeapUsage",
|
|
7424
7509
|
"allowOnly",
|
|
@@ -7450,7 +7535,10 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7450
7535
|
const concurrent = limitConcurrency(nodeos__default.availableParallelism?.() || nodeos__default.cpus().length || 5);
|
|
7451
7536
|
projectConfigs.forEach((options, index) => {
|
|
7452
7537
|
const configRoot = workspaceConfigPath ? dirname(workspaceConfigPath) : vitest.config.root;
|
|
7538
|
+
// if extends a config file, resolve the file path
|
|
7453
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
|
|
7454
7542
|
const root = options.root ? resolve(configRoot, options.root) : vitest.config.root;
|
|
7455
7543
|
projectPromises.push(concurrent(() => initializeProject(index, vitest, {
|
|
7456
7544
|
...options,
|
|
@@ -7463,6 +7551,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7463
7551
|
})));
|
|
7464
7552
|
});
|
|
7465
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
|
|
7466
7555
|
if (vitest.vite.config.configFile === path) {
|
|
7467
7556
|
const project = getDefaultTestProject(vitest);
|
|
7468
7557
|
if (project) {
|
|
@@ -7478,6 +7567,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7478
7567
|
test: cliOverrides
|
|
7479
7568
|
})));
|
|
7480
7569
|
}
|
|
7570
|
+
// pretty rare case - the glob didn't match anything and there are no inline configs
|
|
7481
7571
|
if (!projectPromises.length) {
|
|
7482
7572
|
throw new Error([
|
|
7483
7573
|
"No projects were found. Make sure your configuration is correct. ",
|
|
@@ -7491,6 +7581,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7491
7581
|
for (const result of resolvedProjectsPromises) {
|
|
7492
7582
|
if (result.status === "rejected") {
|
|
7493
7583
|
if (result.reason instanceof VitestFilteredOutProjectError) {
|
|
7584
|
+
// filter out filtered out projects
|
|
7494
7585
|
continue;
|
|
7495
7586
|
}
|
|
7496
7587
|
errors.push(result.reason);
|
|
@@ -7501,6 +7592,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
|
|
|
7501
7592
|
if (errors.length) {
|
|
7502
7593
|
throw new AggregateError(errors, "Failed to initialize projects. There were errors during projects setup. See below for more details.");
|
|
7503
7594
|
}
|
|
7595
|
+
// project names are guaranteed to be unique
|
|
7504
7596
|
for (const project of resolvedProjects) {
|
|
7505
7597
|
const name = project.name;
|
|
7506
7598
|
if (names.has(name)) {
|
|
@@ -7545,10 +7637,12 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
|
|
|
7545
7637
|
].filter(Boolean).join("")));
|
|
7546
7638
|
}
|
|
7547
7639
|
const originalName = project.config.name;
|
|
7640
|
+
// if original name is in the --project=name filter, keep all instances
|
|
7548
7641
|
const filteredInstances = vitest.matchesProjectFilter(originalName) ? instances : instances.filter((instance) => {
|
|
7549
7642
|
const newName = instance.name;
|
|
7550
7643
|
return vitest.matchesProjectFilter(newName);
|
|
7551
7644
|
});
|
|
7645
|
+
// every project was filtered out
|
|
7552
7646
|
if (!filteredInstances.length) {
|
|
7553
7647
|
removeProjects.add(project);
|
|
7554
7648
|
return;
|
|
@@ -7628,13 +7722,19 @@ function cloneConfig(project, { browser,...config }) {
|
|
|
7628
7722
|
}, overrideConfig);
|
|
7629
7723
|
}
|
|
7630
7724
|
async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition) {
|
|
7725
|
+
// project configurations that were specified directly
|
|
7631
7726
|
const projectsOptions = [];
|
|
7727
|
+
// custom config files that were specified directly or resolved from a directory
|
|
7632
7728
|
const projectsConfigFiles = [];
|
|
7729
|
+
// custom glob matches that should be resolved as directories or config files
|
|
7633
7730
|
const projectsGlobMatches = [];
|
|
7731
|
+
// directories that don't have a config file inside, but should be treated as projects
|
|
7634
7732
|
const nonConfigProjectDirectories = [];
|
|
7635
7733
|
for (const definition of projectsDefinition) {
|
|
7636
7734
|
if (typeof definition === "string") {
|
|
7637
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']
|
|
7638
7738
|
if (!isDynamicPattern(stringOption)) {
|
|
7639
7739
|
const file = resolve(vitest.config.root, stringOption);
|
|
7640
7740
|
if (!existsSync(file)) {
|
|
@@ -7643,6 +7743,7 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7643
7743
|
throw new Error(`${note} references a non-existing file or a directory: ${file}`);
|
|
7644
7744
|
}
|
|
7645
7745
|
const stats = await promises.stat(file);
|
|
7746
|
+
// user can specify a config file directly
|
|
7646
7747
|
if (stats.isFile()) {
|
|
7647
7748
|
projectsConfigFiles.push(file);
|
|
7648
7749
|
} else if (stats.isDirectory()) {
|
|
@@ -7654,6 +7755,7 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7654
7755
|
nonConfigProjectDirectories.push(directory);
|
|
7655
7756
|
}
|
|
7656
7757
|
} else {
|
|
7758
|
+
// should never happen
|
|
7657
7759
|
throw new TypeError(`Unexpected file type: ${file}`);
|
|
7658
7760
|
}
|
|
7659
7761
|
} else {
|
|
@@ -7685,6 +7787,8 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7685
7787
|
};
|
|
7686
7788
|
const projectsFs = await glob(projectsGlobMatches, globOptions);
|
|
7687
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
|
|
7688
7792
|
if (path.endsWith("/")) {
|
|
7689
7793
|
const configFile = await resolveDirectoryConfig(path);
|
|
7690
7794
|
if (configFile) {
|
|
@@ -7706,6 +7810,8 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
|
|
|
7706
7810
|
}
|
|
7707
7811
|
async function resolveDirectoryConfig(directory) {
|
|
7708
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
|
|
7709
7815
|
const configFile = configFiles.find((file) => files.has(file));
|
|
7710
7816
|
if (configFile) {
|
|
7711
7817
|
return resolve(directory, configFile);
|
|
@@ -7718,6 +7824,7 @@ function getDefaultTestProject(vitest) {
|
|
|
7718
7824
|
if (!filter.length) {
|
|
7719
7825
|
return project;
|
|
7720
7826
|
}
|
|
7827
|
+
// check for the project name and browser names
|
|
7721
7828
|
const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
|
|
7722
7829
|
if (hasProjects) {
|
|
7723
7830
|
return project;
|
|
@@ -7839,6 +7946,7 @@ class VitestSpecifications {
|
|
|
7839
7946
|
const files = [];
|
|
7840
7947
|
const dir = process.cwd();
|
|
7841
7948
|
const parsedFilters = filters.map((f) => parseFilter(f));
|
|
7949
|
+
// Require includeTaskLocation when a location filter is passed
|
|
7842
7950
|
if (!this.vitest.config.includeTaskLocation && parsedFilters.some((f) => f.lineNumber !== undefined)) {
|
|
7843
7951
|
throw new IncludeTaskLocationDisabledError();
|
|
7844
7952
|
}
|
|
@@ -7846,6 +7954,7 @@ class VitestSpecifications {
|
|
|
7846
7954
|
...f,
|
|
7847
7955
|
filename: resolve(dir, f.filename)
|
|
7848
7956
|
})));
|
|
7957
|
+
// Key is file and val specifies whether we have matched this file with testLocation
|
|
7849
7958
|
const testLocHasMatch = {};
|
|
7850
7959
|
await Promise.all(this.vitest.projects.map(async (project) => {
|
|
7851
7960
|
const { testFiles, typecheckTestFiles } = await project.globTestFiles(parsedFilters.map((f) => f.filename));
|
|
@@ -7913,6 +8022,8 @@ class VitestSpecifications {
|
|
|
7913
8022
|
if (matcher && related.some((file) => matcher(file))) {
|
|
7914
8023
|
return specs;
|
|
7915
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
|
|
7916
8027
|
if (!this.vitest.config.watch && !related.length) {
|
|
7917
8028
|
return [];
|
|
7918
8029
|
}
|
|
@@ -7922,6 +8033,7 @@ class VitestSpecifications {
|
|
|
7922
8033
|
}));
|
|
7923
8034
|
const runningTests = [];
|
|
7924
8035
|
for (const [specification, deps] of testGraphs) {
|
|
8036
|
+
// if deps or the test itself were changed
|
|
7925
8037
|
if (related.some((path) => path === specification.moduleId || deps.has(path))) {
|
|
7926
8038
|
runningTests.push(specification);
|
|
7927
8039
|
}
|
|
@@ -8097,6 +8209,7 @@ class TestCase extends ReportedTaskImplementation {
|
|
|
8097
8209
|
*/
|
|
8098
8210
|
diagnostic() {
|
|
8099
8211
|
const result = this.task.result;
|
|
8212
|
+
// startTime should always be available if the test has properly finished
|
|
8100
8213
|
if (!result || !result.startTime) {
|
|
8101
8214
|
return undefined;
|
|
8102
8215
|
}
|
|
@@ -8418,6 +8531,7 @@ class StateManager {
|
|
|
8418
8531
|
return keys.map((key) => this.filesMap.get(key)).flat().filter((file) => file && !file.local);
|
|
8419
8532
|
}
|
|
8420
8533
|
return Array.from(this.filesMap.values()).flat().filter((file) => !file.local).sort((f1, f2) => {
|
|
8534
|
+
// print typecheck files first
|
|
8421
8535
|
if (f1.meta?.typecheck && f2.meta?.typecheck) {
|
|
8422
8536
|
return 0;
|
|
8423
8537
|
}
|
|
@@ -8446,6 +8560,8 @@ class StateManager {
|
|
|
8446
8560
|
const existing = this.filesMap.get(file.filepath) || [];
|
|
8447
8561
|
const otherFiles = existing.filter((i) => i.projectName !== file.projectName || i.meta.typecheck !== file.meta.typecheck);
|
|
8448
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
|
|
8449
8565
|
if (currentFile) {
|
|
8450
8566
|
file.logs = currentFile.logs;
|
|
8451
8567
|
}
|
|
@@ -8466,6 +8582,7 @@ class StateManager {
|
|
|
8466
8582
|
return;
|
|
8467
8583
|
}
|
|
8468
8584
|
const filtered = files.filter((file) => file.projectName !== project.config.name);
|
|
8585
|
+
// always keep a File task, so we can associate logs with it
|
|
8469
8586
|
if (!filtered.length) {
|
|
8470
8587
|
this.filesMap.set(path, [fileTask]);
|
|
8471
8588
|
} else {
|
|
@@ -8500,6 +8617,7 @@ class StateManager {
|
|
|
8500
8617
|
if (task) {
|
|
8501
8618
|
task.result = result;
|
|
8502
8619
|
task.meta = meta;
|
|
8620
|
+
// skipped with new PendingError
|
|
8503
8621
|
if (result?.state === "skip") {
|
|
8504
8622
|
task.mode = "skip";
|
|
8505
8623
|
}
|
|
@@ -8552,6 +8670,9 @@ class TestRun {
|
|
|
8552
8670
|
}
|
|
8553
8671
|
async updated(update, events) {
|
|
8554
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?
|
|
8555
8676
|
await this.vitest.report("onTaskUpdate", update);
|
|
8556
8677
|
for (const [id, event] of events) {
|
|
8557
8678
|
await this.reportEvent(id, event).catch((error) => {
|
|
@@ -8560,6 +8681,7 @@ class TestRun {
|
|
|
8560
8681
|
}
|
|
8561
8682
|
}
|
|
8562
8683
|
async end(specifications, errors, coverage) {
|
|
8684
|
+
// specification won't have the File task if they were filtered by the --shard command
|
|
8563
8685
|
const modules = specifications.map((spec) => spec.testModule).filter((s) => s != null);
|
|
8564
8686
|
const files = modules.map((m) => m.task);
|
|
8565
8687
|
const state = this.vitest.isCancelling ? "interrupted" : process.exitCode ? "failed" : "passed";
|
|
@@ -8584,6 +8706,9 @@ class TestRun {
|
|
|
8584
8706
|
if (event === "suite-finished") {
|
|
8585
8707
|
assert$1(entity.type === "suite" || entity.type === "module", "Entity type must be suite or module");
|
|
8586
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
|
|
8587
8712
|
await this.reportChildren(entity.children);
|
|
8588
8713
|
}
|
|
8589
8714
|
if (entity.type === "module") {
|
|
@@ -8738,6 +8863,7 @@ class VitestWatcher {
|
|
|
8738
8863
|
this.changedTests.add(id);
|
|
8739
8864
|
this.scheduleRerun(id);
|
|
8740
8865
|
} else {
|
|
8866
|
+
// it's possible that file was already there but watcher triggered "add" event instead
|
|
8741
8867
|
const needsRerun = this.handleFileChanged(id);
|
|
8742
8868
|
if (needsRerun) {
|
|
8743
8869
|
this.scheduleRerun(id);
|
|
@@ -8760,6 +8886,8 @@ class VitestWatcher {
|
|
|
8760
8886
|
return moduleGraph.getModulesByFile(filepath)?.size;
|
|
8761
8887
|
});
|
|
8762
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
|
|
8763
8891
|
if (this.vitest.state.filesMap.has(filepath) || this.vitest.projects.some((project) => project._isCachedTestFile(filepath))) {
|
|
8764
8892
|
this.changedTests.add(filepath);
|
|
8765
8893
|
return true;
|
|
@@ -8773,6 +8901,7 @@ class VitestWatcher {
|
|
|
8773
8901
|
continue;
|
|
8774
8902
|
}
|
|
8775
8903
|
this.invalidates.add(filepath);
|
|
8904
|
+
// one of test files that we already run, or one of test files that we can run
|
|
8776
8905
|
if (this.vitest.state.filesMap.has(filepath) || project._isCachedTestFile(filepath)) {
|
|
8777
8906
|
this.changedTests.add(filepath);
|
|
8778
8907
|
files.push(filepath);
|
|
@@ -8844,7 +8973,7 @@ class Vitest {
|
|
|
8844
8973
|
resolvedProjects = [];
|
|
8845
8974
|
/** @internal */ _browserLastPort = defaultBrowserPort;
|
|
8846
8975
|
/** @internal */ _browserSessions = new BrowserSessions();
|
|
8847
|
-
/** @internal */
|
|
8976
|
+
/** @internal */ _cliOptions = {};
|
|
8848
8977
|
/** @internal */ reporters = [];
|
|
8849
8978
|
/** @internal */ vitenode = undefined;
|
|
8850
8979
|
/** @internal */ runner = undefined;
|
|
@@ -8860,8 +8989,9 @@ class Vitest {
|
|
|
8860
8989
|
_cache;
|
|
8861
8990
|
_snapshot;
|
|
8862
8991
|
_workspaceConfigPath;
|
|
8863
|
-
constructor(mode, options = {}) {
|
|
8992
|
+
constructor(mode, cliOptions, options = {}) {
|
|
8864
8993
|
this.mode = mode;
|
|
8994
|
+
this._cliOptions = cliOptions;
|
|
8865
8995
|
this.logger = new Logger(this, options.stdout, options.stderr);
|
|
8866
8996
|
this.packageInstaller = options.packageInstaller || new VitestPackageInstaller();
|
|
8867
8997
|
this.specifications = new VitestSpecifications(this);
|
|
@@ -8922,12 +9052,11 @@ class Vitest {
|
|
|
8922
9052
|
return this._cache;
|
|
8923
9053
|
}
|
|
8924
9054
|
/** @deprecated internal */
|
|
8925
|
-
setServer(options, server
|
|
8926
|
-
return this._setServer(options, server
|
|
9055
|
+
setServer(options, server) {
|
|
9056
|
+
return this._setServer(options, server);
|
|
8927
9057
|
}
|
|
8928
9058
|
/** @internal */
|
|
8929
|
-
async _setServer(options, server
|
|
8930
|
-
this._options = options;
|
|
9059
|
+
async _setServer(options, server) {
|
|
8931
9060
|
this.watcher.unregisterWatcher();
|
|
8932
9061
|
clearTimeout(this._rerunTimer);
|
|
8933
9062
|
this.restartsCount += 1;
|
|
@@ -8966,6 +9095,7 @@ class Vitest {
|
|
|
8966
9095
|
}
|
|
8967
9096
|
});
|
|
8968
9097
|
if (this.config.watch) {
|
|
9098
|
+
// hijack server restart
|
|
8969
9099
|
const serverRestart = server.restart;
|
|
8970
9100
|
server.restart = async (...args) => {
|
|
8971
9101
|
await Promise.all(this._onRestartListeners.map((fn) => fn()));
|
|
@@ -8973,6 +9103,7 @@ class Vitest {
|
|
|
8973
9103
|
await this.close();
|
|
8974
9104
|
await serverRestart(...args);
|
|
8975
9105
|
};
|
|
9106
|
+
// since we set `server.hmr: false`, Vite does not auto restart itself
|
|
8976
9107
|
server.watcher.on("change", async (file) => {
|
|
8977
9108
|
file = normalize(file);
|
|
8978
9109
|
const isConfig = file === server.config.configFile || this.projects.some((p) => p.vite.config.configFile === file) || file === this._workspaceConfigPath;
|
|
@@ -8988,7 +9119,7 @@ class Vitest {
|
|
|
8988
9119
|
try {
|
|
8989
9120
|
await this.cache.results.readFromCache();
|
|
8990
9121
|
} catch {}
|
|
8991
|
-
const projects = await this.resolveProjects(
|
|
9122
|
+
const projects = await this.resolveProjects(this._cliOptions);
|
|
8992
9123
|
this.resolvedProjects = projects;
|
|
8993
9124
|
this.projects = projects;
|
|
8994
9125
|
await Promise.all(projects.flatMap((project) => {
|
|
@@ -8999,7 +9130,7 @@ class Vitest {
|
|
|
8999
9130
|
injectTestProjects: this.injectTestProject
|
|
9000
9131
|
}));
|
|
9001
9132
|
}));
|
|
9002
|
-
if (
|
|
9133
|
+
if (this._cliOptions.browser?.enabled) {
|
|
9003
9134
|
const browserProjects = this.projects.filter((p) => p.config.browser.enabled);
|
|
9004
9135
|
if (!browserProjects.length) {
|
|
9005
9136
|
throw new Error(`Vitest received --browser flag, but no project had a browser configuration.`);
|
|
@@ -9029,7 +9160,7 @@ class Vitest {
|
|
|
9029
9160
|
*/
|
|
9030
9161
|
injectTestProject = async (config) => {
|
|
9031
9162
|
const currentNames = new Set(this.projects.map((p) => p.name));
|
|
9032
|
-
const projects = await resolveProjects(this, this.
|
|
9163
|
+
const projects = await resolveProjects(this, this._cliOptions, undefined, Array.isArray(config) ? config : [config], currentNames);
|
|
9033
9164
|
this.projects.push(...projects);
|
|
9034
9165
|
return projects;
|
|
9035
9166
|
};
|
|
@@ -9116,7 +9247,10 @@ class Vitest {
|
|
|
9116
9247
|
}
|
|
9117
9248
|
const workspaceConfigPath = await this.resolveWorkspaceConfigPath();
|
|
9118
9249
|
this._workspaceConfigPath = workspaceConfigPath;
|
|
9250
|
+
// user doesn't have a workspace config, return default project
|
|
9119
9251
|
if (!workspaceConfigPath) {
|
|
9252
|
+
// user can filter projects with --project flag, `getDefaultTestProject`
|
|
9253
|
+
// returns the project only if it matches the filter
|
|
9120
9254
|
const project = getDefaultTestProject(this);
|
|
9121
9255
|
if (!project) {
|
|
9122
9256
|
return [];
|
|
@@ -9207,6 +9341,7 @@ class Vitest {
|
|
|
9207
9341
|
}
|
|
9208
9342
|
async collect(filters) {
|
|
9209
9343
|
const files = await this.specifications.getRelevantTestSpecifications(filters);
|
|
9344
|
+
// if run with --changed, don't exit if no tests are found
|
|
9210
9345
|
if (!files.length) {
|
|
9211
9346
|
return {
|
|
9212
9347
|
testModules: [],
|
|
@@ -9243,15 +9378,18 @@ class Vitest {
|
|
|
9243
9378
|
}
|
|
9244
9379
|
this.filenamePattern = filters && filters?.length > 0 ? filters : undefined;
|
|
9245
9380
|
const files = await this.specifications.getRelevantTestSpecifications(filters);
|
|
9381
|
+
// if run with --changed, don't exit if no tests are found
|
|
9246
9382
|
if (!files.length) {
|
|
9247
9383
|
const throwAnError = !this.config.watch || !(this.config.changed || this.config.related?.length);
|
|
9248
9384
|
await this._testRun.start([]);
|
|
9249
9385
|
const coverage = await this.coverageProvider?.generateCoverage?.({ allTestsRun: true });
|
|
9386
|
+
// set exit code before calling `onTestRunEnd` so the lifecycle is consistent
|
|
9250
9387
|
if (throwAnError) {
|
|
9251
9388
|
const exitCode = this.config.passWithNoTests ? 0 : 1;
|
|
9252
9389
|
process.exitCode = exitCode;
|
|
9253
9390
|
}
|
|
9254
9391
|
await this._testRun.end([], [], coverage);
|
|
9392
|
+
// Report coverage for uncovered files
|
|
9255
9393
|
await this.reportCoverage(coverage, true);
|
|
9256
9394
|
if (throwAnError) {
|
|
9257
9395
|
throw new FilesNotFoundError(this.mode);
|
|
@@ -9262,6 +9400,7 @@ class Vitest {
|
|
|
9262
9400
|
unhandledErrors: []
|
|
9263
9401
|
};
|
|
9264
9402
|
if (files.length) {
|
|
9403
|
+
// populate once, update cache on watch
|
|
9265
9404
|
await this.cache.stats.populateStats(this.config.root, files);
|
|
9266
9405
|
testModules = await this.runFiles(files, true);
|
|
9267
9406
|
}
|
|
@@ -9281,6 +9420,7 @@ class Vitest {
|
|
|
9281
9420
|
} finally {
|
|
9282
9421
|
await this.report("onInit", this);
|
|
9283
9422
|
}
|
|
9423
|
+
// populate test files cache so watch mode can trigger a file rerun
|
|
9284
9424
|
await this.globTestSpecifications();
|
|
9285
9425
|
if (this.config.watch) {
|
|
9286
9426
|
await this.report("onWatcherStart");
|
|
@@ -9335,9 +9475,11 @@ class Vitest {
|
|
|
9335
9475
|
}
|
|
9336
9476
|
async runFiles(specs, allTestsRun) {
|
|
9337
9477
|
await this._testRun.start(specs);
|
|
9478
|
+
// previous run
|
|
9338
9479
|
await this.runningPromise;
|
|
9339
9480
|
this._onCancelListeners = [];
|
|
9340
9481
|
this.isCancelling = false;
|
|
9482
|
+
// schedule the new run
|
|
9341
9483
|
this.runningPromise = (async () => {
|
|
9342
9484
|
try {
|
|
9343
9485
|
if (!this.pool) {
|
|
@@ -9369,6 +9511,7 @@ class Vitest {
|
|
|
9369
9511
|
unhandledErrors: this.state.getUnhandledErrors()
|
|
9370
9512
|
};
|
|
9371
9513
|
} finally {
|
|
9514
|
+
// TODO: wait for coverage only if `onFinished` is defined
|
|
9372
9515
|
const coverage = await this.coverageProvider?.generateCoverage({ allTestsRun });
|
|
9373
9516
|
const errors = this.state.getUnhandledErrors();
|
|
9374
9517
|
this._checkUnhandledErrors(errors);
|
|
@@ -9378,6 +9521,7 @@ class Vitest {
|
|
|
9378
9521
|
})().finally(() => {
|
|
9379
9522
|
this.runningPromise = undefined;
|
|
9380
9523
|
this.isFirstRun = false;
|
|
9524
|
+
// all subsequent runs will treat this as a fresh run
|
|
9381
9525
|
this.config.changed = false;
|
|
9382
9526
|
this.config.related = undefined;
|
|
9383
9527
|
});
|
|
@@ -9390,9 +9534,11 @@ class Vitest {
|
|
|
9390
9534
|
async collectTests(specifications) {
|
|
9391
9535
|
const filepaths = specifications.map((spec) => spec.moduleId);
|
|
9392
9536
|
this.state.collectPaths(filepaths);
|
|
9537
|
+
// previous run
|
|
9393
9538
|
await this.runningPromise;
|
|
9394
9539
|
this._onCancelListeners = [];
|
|
9395
9540
|
this.isCancelling = false;
|
|
9541
|
+
// schedule the new run
|
|
9396
9542
|
this.runningPromise = (async () => {
|
|
9397
9543
|
if (!this.pool) {
|
|
9398
9544
|
this.pool = createPool(this);
|
|
@@ -9408,6 +9554,8 @@ class Vitest {
|
|
|
9408
9554
|
this.state.catchError(err, "Unhandled Error");
|
|
9409
9555
|
}
|
|
9410
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
|
|
9411
9559
|
if (hasFailed(files)) {
|
|
9412
9560
|
process.exitCode = 1;
|
|
9413
9561
|
}
|
|
@@ -9417,6 +9565,7 @@ class Vitest {
|
|
|
9417
9565
|
};
|
|
9418
9566
|
})().finally(() => {
|
|
9419
9567
|
this.runningPromise = undefined;
|
|
9568
|
+
// all subsequent runs will treat this as a fresh run
|
|
9420
9569
|
this.config.changed = false;
|
|
9421
9570
|
this.config.related = undefined;
|
|
9422
9571
|
});
|
|
@@ -9479,11 +9628,13 @@ class Vitest {
|
|
|
9479
9628
|
}
|
|
9480
9629
|
/** @internal */
|
|
9481
9630
|
async changeNamePattern(pattern, files = this.state.getFilepaths(), trigger) {
|
|
9631
|
+
// Empty test name pattern should reset filename pattern as well
|
|
9482
9632
|
if (pattern === "") {
|
|
9483
9633
|
this.filenamePattern = undefined;
|
|
9484
9634
|
}
|
|
9485
9635
|
const testNamePattern = pattern ? new RegExp(pattern) : undefined;
|
|
9486
9636
|
this.configOverride.testNamePattern = testNamePattern;
|
|
9637
|
+
// filter only test files that have tests matching the pattern
|
|
9487
9638
|
if (testNamePattern) {
|
|
9488
9639
|
files = files.filter((filepath) => {
|
|
9489
9640
|
const files = this.state.getFiles([filepath]);
|
|
@@ -9510,6 +9661,7 @@ class Vitest {
|
|
|
9510
9661
|
* @param files The list of files on the file system
|
|
9511
9662
|
*/
|
|
9512
9663
|
async updateSnapshot(files) {
|
|
9664
|
+
// default to failed files
|
|
9513
9665
|
files = files || [...this.state.getFailedFilepaths(), ...this.snapshot.summary.uncheckedKeysByFile.map((s) => s.filePath)];
|
|
9514
9666
|
this.enableSnapshotUpdate();
|
|
9515
9667
|
try {
|
|
@@ -9557,11 +9709,13 @@ class Vitest {
|
|
|
9557
9709
|
this.configOverride.testNamePattern = undefined;
|
|
9558
9710
|
}
|
|
9559
9711
|
_rerunTimer;
|
|
9712
|
+
// we can't use a single `triggerId` yet because vscode extension relies on this
|
|
9560
9713
|
async scheduleRerun(triggerId) {
|
|
9561
9714
|
const currentCount = this.restartsCount;
|
|
9562
9715
|
clearTimeout(this._rerunTimer);
|
|
9563
9716
|
await this.runningPromise;
|
|
9564
9717
|
clearTimeout(this._rerunTimer);
|
|
9718
|
+
// server restarted
|
|
9565
9719
|
if (this.restartsCount !== currentCount) {
|
|
9566
9720
|
return;
|
|
9567
9721
|
}
|
|
@@ -9570,6 +9724,7 @@ class Vitest {
|
|
|
9570
9724
|
this.watcher.invalidates.clear();
|
|
9571
9725
|
return;
|
|
9572
9726
|
}
|
|
9727
|
+
// server restarted
|
|
9573
9728
|
if (this.restartsCount !== currentCount) {
|
|
9574
9729
|
return;
|
|
9575
9730
|
}
|
|
@@ -9579,6 +9734,7 @@ class Vitest {
|
|
|
9579
9734
|
if (this.filenamePattern) {
|
|
9580
9735
|
const filteredFiles = await this.globTestSpecifications(this.filenamePattern);
|
|
9581
9736
|
files = files.filter((file) => filteredFiles.some((f) => f.moduleId === file));
|
|
9737
|
+
// A file that does not match the current filename pattern was changed
|
|
9582
9738
|
if (files.length === 0) {
|
|
9583
9739
|
return;
|
|
9584
9740
|
}
|
|
@@ -9586,6 +9742,7 @@ class Vitest {
|
|
|
9586
9742
|
this.watcher.changedTests.clear();
|
|
9587
9743
|
const triggerIds = new Set(triggerId.map((id) => relative(this.config.root, id)));
|
|
9588
9744
|
const triggerLabel = Array.from(triggerIds).join(", ");
|
|
9745
|
+
// get file specifications and filter them if needed
|
|
9589
9746
|
const specifications = files.flatMap((file) => this.getModuleSpecifications(file)).filter((specification) => {
|
|
9590
9747
|
if (this._onFilterWatchedSpecification.length === 0) {
|
|
9591
9748
|
return true;
|
|
@@ -9629,6 +9786,7 @@ class Vitest {
|
|
|
9629
9786
|
}
|
|
9630
9787
|
if (this.coverageProvider) {
|
|
9631
9788
|
await this.coverageProvider.reportCoverage(coverage, { allTestsRun });
|
|
9789
|
+
// notify coverage iframe reload
|
|
9632
9790
|
for (const reporter of this.reporters) {
|
|
9633
9791
|
if (reporter instanceof WebSocketReporter) {
|
|
9634
9792
|
reporter.onFinishedReportCoverage();
|
|
@@ -9647,10 +9805,13 @@ class Vitest {
|
|
|
9647
9805
|
if (this.coreWorkspaceProject && !teardownProjects.includes(this.coreWorkspaceProject)) {
|
|
9648
9806
|
teardownProjects.push(this.coreWorkspaceProject);
|
|
9649
9807
|
}
|
|
9808
|
+
// do teardown before closing the server
|
|
9650
9809
|
for (const project of teardownProjects.reverse()) {
|
|
9651
9810
|
await project._teardownGlobalSetup();
|
|
9652
9811
|
}
|
|
9653
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
|
|
9654
9815
|
if (this.coreWorkspaceProject && !this.projects.includes(this.coreWorkspaceProject)) {
|
|
9655
9816
|
closePromises.push(this.coreWorkspaceProject.close().then(() => this._vite = undefined));
|
|
9656
9817
|
}
|
|
@@ -9707,7 +9868,7 @@ class Vitest {
|
|
|
9707
9868
|
await Promise.all(this.reporters.map((r) => r[name]?.(
|
|
9708
9869
|
// @ts-expect-error let me go
|
|
9709
9870
|
...args
|
|
9710
|
-
)));
|
|
9871
|
+
)));
|
|
9711
9872
|
}
|
|
9712
9873
|
/** @internal */
|
|
9713
9874
|
async _globTestFilepaths() {
|
|
@@ -9730,6 +9891,7 @@ class Vitest {
|
|
|
9730
9891
|
getModuleProjects(filepath) {
|
|
9731
9892
|
return this.projects.filter((project) => {
|
|
9732
9893
|
return project.getModulesByFilepath(filepath).size;
|
|
9894
|
+
// TODO: reevaluate || project.browser?.moduleGraph.getModulesByFile(id)?.size
|
|
9733
9895
|
});
|
|
9734
9896
|
}
|
|
9735
9897
|
/**
|
|
@@ -9780,7 +9942,8 @@ class Vitest {
|
|
|
9780
9942
|
* Check if the project with a given name should be included.
|
|
9781
9943
|
*/
|
|
9782
9944
|
matchesProjectFilter(name) {
|
|
9783
|
-
const projects = this._config?.project || this.
|
|
9945
|
+
const projects = this._config?.project || this._cliOptions?.project;
|
|
9946
|
+
// no filters applied, any project can be included
|
|
9784
9947
|
if (!projects || !projects.length) {
|
|
9785
9948
|
return true;
|
|
9786
9949
|
}
|
|
@@ -9796,11 +9959,11 @@ function assert(condition, property, name = property) {
|
|
|
9796
9959
|
}
|
|
9797
9960
|
}
|
|
9798
9961
|
|
|
9799
|
-
async function VitestPlugin(options = {},
|
|
9962
|
+
async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(options))) {
|
|
9800
9963
|
const userConfig = deepMerge({}, options);
|
|
9801
9964
|
async function UIPlugin() {
|
|
9802
|
-
await
|
|
9803
|
-
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);
|
|
9804
9967
|
}
|
|
9805
9968
|
return [
|
|
9806
9969
|
{
|
|
@@ -9811,10 +9974,17 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9811
9974
|
},
|
|
9812
9975
|
async config(viteConfig) {
|
|
9813
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.
|
|
9814
9979
|
options = deepMerge({}, userConfig);
|
|
9815
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
|
|
9816
9984
|
const testConfig = deepMerge({}, configDefaults, removeUndefinedValues(viteConfig.test ?? {}), options);
|
|
9817
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
|
|
9818
9988
|
const defines = deleteDefineConfig(viteConfig);
|
|
9819
9989
|
options.defines = defines;
|
|
9820
9990
|
let open = false;
|
|
@@ -9856,11 +10026,13 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9856
10026
|
deps: testConfig.deps ?? viteConfig.test?.deps
|
|
9857
10027
|
}
|
|
9858
10028
|
};
|
|
9859
|
-
if (
|
|
9860
|
-
|
|
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;
|
|
9861
10032
|
}
|
|
9862
|
-
config.customLogger = createViteLogger(
|
|
10033
|
+
config.customLogger = createViteLogger(vitest.logger, viteConfig.logLevel || "warn", { allowClearScreen: false });
|
|
9863
10034
|
config.customLogger = silenceImportViteIgnoreWarning(config.customLogger);
|
|
10035
|
+
// we want inline dependencies to be resolved by analyser plugin so module graph is populated correctly
|
|
9864
10036
|
if (viteConfig.ssr?.noExternal !== true) {
|
|
9865
10037
|
const inline = testConfig.server?.deps?.inline;
|
|
9866
10038
|
if (inline === true) {
|
|
@@ -9868,13 +10040,17 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9868
10040
|
} else {
|
|
9869
10041
|
const noExternal = viteConfig.ssr?.noExternal;
|
|
9870
10042
|
const noExternalArray = typeof noExternal !== "undefined" ? toArray(noExternal) : undefined;
|
|
10043
|
+
// filter the same packages
|
|
9871
10044
|
const uniqueInline = inline && noExternalArray ? inline.filter((dep) => !noExternalArray.includes(dep)) : inline;
|
|
9872
10045
|
config.ssr = { noExternal: uniqueInline };
|
|
9873
10046
|
}
|
|
9874
10047
|
}
|
|
10048
|
+
// chokidar fsevents is unstable on macos when emitting "ready" event
|
|
9875
10049
|
if (process.platform === "darwin" && process.env.VITE_TEST_WATCHER_DEBUG) {
|
|
9876
10050
|
const watch = config.server.watch;
|
|
9877
10051
|
if (watch) {
|
|
10052
|
+
// eslint-disable-next-line ts/ban-ts-comment
|
|
10053
|
+
// @ts-ignore Vite 6 compat
|
|
9878
10054
|
watch.useFsEvents = false;
|
|
9879
10055
|
watch.usePolling = false;
|
|
9880
10056
|
}
|
|
@@ -9885,7 +10061,7 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9885
10061
|
config.css.modules ??= {};
|
|
9886
10062
|
if (config.css.modules) {
|
|
9887
10063
|
config.css.modules.generateScopedName = (name, filename) => {
|
|
9888
|
-
const root =
|
|
10064
|
+
const root = vitest.config.root || options.root || process.cwd();
|
|
9889
10065
|
return generateScopedClassName(classNameStrategy, name, relative(root, filename));
|
|
9890
10066
|
};
|
|
9891
10067
|
}
|
|
@@ -9900,14 +10076,20 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9900
10076
|
if ("alias" in viteConfigTest) {
|
|
9901
10077
|
delete viteConfigTest.alias;
|
|
9902
10078
|
}
|
|
10079
|
+
// viteConfig.test is final now, merge it for real
|
|
9903
10080
|
options = deepMerge({}, configDefaults, viteConfigTest, options);
|
|
9904
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
|
|
9905
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
|
|
9906
10087
|
process.env.PROD ??= PROD ? "1" : "";
|
|
9907
10088
|
process.env.DEV ??= DEV ? "1" : "";
|
|
9908
10089
|
for (const name in envs) {
|
|
9909
10090
|
process.env[name] ??= envs[name];
|
|
9910
10091
|
}
|
|
10092
|
+
// don't watch files in run mode
|
|
9911
10093
|
if (!options.watch) {
|
|
9912
10094
|
viteConfig.server.watch = null;
|
|
9913
10095
|
}
|
|
@@ -9917,7 +10099,7 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9917
10099
|
configurable: true
|
|
9918
10100
|
});
|
|
9919
10101
|
const originalName = options.name;
|
|
9920
|
-
if (options.browser?.
|
|
10102
|
+
if (options.browser?.instances) {
|
|
9921
10103
|
options.browser.instances.forEach((instance) => {
|
|
9922
10104
|
instance.name ??= originalName ? `${originalName} (${instance.browser})` : instance.browser;
|
|
9923
10105
|
});
|
|
@@ -9928,13 +10110,15 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9928
10110
|
async handler(server) {
|
|
9929
10111
|
if (options.watch && process.env.VITE_TEST_WATCHER_DEBUG) {
|
|
9930
10112
|
server.watcher.on("ready", () => {
|
|
10113
|
+
// eslint-disable-next-line no-console
|
|
9931
10114
|
console.log("[debug] watcher is ready");
|
|
9932
10115
|
});
|
|
9933
10116
|
}
|
|
9934
|
-
await
|
|
10117
|
+
await vitest._setServer(options, server);
|
|
9935
10118
|
if (options.api && options.watch) {
|
|
9936
|
-
(await Promise.resolve().then(function () { return setup$1; })).setup(
|
|
10119
|
+
(await Promise.resolve().then(function () { return setup$1; })).setup(vitest);
|
|
9937
10120
|
}
|
|
10121
|
+
// #415, in run mode we don't need the watcher, close it would improve the performance
|
|
9938
10122
|
if (!options.watch) {
|
|
9939
10123
|
await server.watcher.close();
|
|
9940
10124
|
}
|
|
@@ -9942,9 +10126,9 @@ async function VitestPlugin(options = {}, ctx = new Vitest("test")) {
|
|
|
9942
10126
|
}
|
|
9943
10127
|
},
|
|
9944
10128
|
SsrReplacerPlugin(),
|
|
9945
|
-
...CSSEnablerPlugin(
|
|
9946
|
-
CoverageTransform(
|
|
9947
|
-
VitestCoreResolver(
|
|
10129
|
+
...CSSEnablerPlugin(vitest),
|
|
10130
|
+
CoverageTransform(vitest),
|
|
10131
|
+
VitestCoreResolver(vitest),
|
|
9948
10132
|
options.ui ? await UIPlugin() : null,
|
|
9949
10133
|
...MocksPlugins(),
|
|
9950
10134
|
VitestOptimizer(),
|
|
@@ -9961,15 +10145,16 @@ function removeUndefinedValues(obj) {
|
|
|
9961
10145
|
}
|
|
9962
10146
|
|
|
9963
10147
|
async function createVitest(mode, options, viteOverrides = {}, vitestOptions = {}) {
|
|
9964
|
-
const ctx = new Vitest(mode, vitestOptions);
|
|
10148
|
+
const ctx = new Vitest(mode, deepClone(options), vitestOptions);
|
|
9965
10149
|
const root = slash(resolve$1(options.root || process.cwd()));
|
|
9966
10150
|
const configPath = options.config === false ? false : options.config ? resolve$1(root, options.config) : await findUp(configFiles, { cwd: root });
|
|
9967
10151
|
options.config = configPath;
|
|
10152
|
+
const { browser: _removeBrowser,...restOptions } = options;
|
|
9968
10153
|
const config = {
|
|
9969
10154
|
configFile: configPath,
|
|
9970
10155
|
configLoader: options.configLoader,
|
|
9971
10156
|
mode: options.mode || mode,
|
|
9972
|
-
plugins: await VitestPlugin(
|
|
10157
|
+
plugins: await VitestPlugin(restOptions, ctx)
|
|
9973
10158
|
};
|
|
9974
10159
|
const server = await createViteServer(mergeConfig(config, mergeConfig(viteOverrides, { root: options.root })));
|
|
9975
10160
|
if (ctx.config.api?.port) {
|
|
@@ -10100,6 +10285,7 @@ class WatchFilter {
|
|
|
10100
10285
|
const lines = str.split(/\r?\n/);
|
|
10101
10286
|
for (const line of lines) {
|
|
10102
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
|
|
10103
10289
|
rows += 1 + Math.floor(Math.max(stripVTControlCharacters(line).length - 1, 0) / columns);
|
|
10104
10290
|
}
|
|
10105
10291
|
this.write(`${ESC}1G`);
|
|
@@ -10154,6 +10340,8 @@ ${keys.map((i) => c.dim(" press ") + c.reset([i[0]].flat().map(c.bold).join(",
|
|
|
10154
10340
|
function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
10155
10341
|
let latestFilename = "";
|
|
10156
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.
|
|
10157
10345
|
if (str === "" || str === "\x1B" || key && key.ctrl && key.name === "c") {
|
|
10158
10346
|
if (!ctx.isCancelling) {
|
|
10159
10347
|
ctx.logger.log(c.red("Cancelling test run. Press CTRL+c again to exit forcefully.\n"));
|
|
@@ -10162,6 +10350,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
10162
10350
|
}
|
|
10163
10351
|
return ctx.exit(true);
|
|
10164
10352
|
}
|
|
10353
|
+
// window not support suspend
|
|
10165
10354
|
if (!isWindows && key && key.ctrl && key.name === "z") {
|
|
10166
10355
|
process.kill(process.ppid, "SIGTSTP");
|
|
10167
10356
|
process.kill(process.pid, "SIGTSTP");
|
|
@@ -10174,31 +10363,40 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
10174
10363
|
}
|
|
10175
10364
|
return;
|
|
10176
10365
|
}
|
|
10366
|
+
// quit
|
|
10177
10367
|
if (name === "q") {
|
|
10178
10368
|
return ctx.exit(true);
|
|
10179
10369
|
}
|
|
10370
|
+
// help
|
|
10180
10371
|
if (name === "h") {
|
|
10181
10372
|
return printShortcutsHelp();
|
|
10182
10373
|
}
|
|
10374
|
+
// update snapshot
|
|
10183
10375
|
if (name === "u") {
|
|
10184
10376
|
return ctx.updateSnapshot();
|
|
10185
10377
|
}
|
|
10378
|
+
// rerun all tests
|
|
10186
10379
|
if (name === "a" || name === "return") {
|
|
10187
10380
|
const files = await ctx._globTestFilepaths();
|
|
10188
10381
|
return ctx.changeNamePattern("", files, "rerun all tests");
|
|
10189
10382
|
}
|
|
10383
|
+
// rerun current pattern tests
|
|
10190
10384
|
if (name === "r") {
|
|
10191
10385
|
return ctx.rerunFiles();
|
|
10192
10386
|
}
|
|
10387
|
+
// rerun only failed tests
|
|
10193
10388
|
if (name === "f") {
|
|
10194
10389
|
return ctx.rerunFailed();
|
|
10195
10390
|
}
|
|
10391
|
+
// change project filter
|
|
10196
10392
|
if (name === "w") {
|
|
10197
10393
|
return inputProjectName();
|
|
10198
10394
|
}
|
|
10395
|
+
// change testNamePattern
|
|
10199
10396
|
if (name === "t") {
|
|
10200
10397
|
return inputNamePattern();
|
|
10201
10398
|
}
|
|
10399
|
+
// change fileNamePattern
|
|
10202
10400
|
if (name === "p") {
|
|
10203
10401
|
return inputFilePattern();
|
|
10204
10402
|
}
|
|
@@ -10224,6 +10422,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
10224
10422
|
const reg = new RegExp(str);
|
|
10225
10423
|
return tests.map((test) => test.name).filter((testName) => testName.match(reg));
|
|
10226
10424
|
} catch {
|
|
10425
|
+
// `new RegExp` may throw error when input is invalid regexp
|
|
10227
10426
|
return [];
|
|
10228
10427
|
}
|
|
10229
10428
|
});
|
|
@@ -10232,6 +10431,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
|
|
|
10232
10431
|
return;
|
|
10233
10432
|
}
|
|
10234
10433
|
const files = ctx.state.getFilepaths();
|
|
10434
|
+
// if running in standalone mode, Vitest instance doesn't know about any test file
|
|
10235
10435
|
const cliFiles = ctx.config.standalone && !files.length ? await ctx._globTestFilepaths() : undefined;
|
|
10236
10436
|
await ctx.changeNamePattern(filter?.trim() || "", cliFiles, "change pattern");
|
|
10237
10437
|
}
|
|
@@ -10361,6 +10561,7 @@ async function prepareVitest(mode, options = {}, viteOverrides, vitestOptions) {
|
|
|
10361
10561
|
if (options.run) {
|
|
10362
10562
|
options.watch = false;
|
|
10363
10563
|
}
|
|
10564
|
+
// this shouldn't affect _application root_ that can be changed inside config
|
|
10364
10565
|
const root = resolve(options.root || process.cwd());
|
|
10365
10566
|
const ctx = await createVitest(mode, options, viteOverrides, vitestOptions);
|
|
10366
10567
|
const environmentPackage = getEnvPackageName(ctx.config.environment);
|