@voidzero-dev/vite-plus-test 0.0.0-0bfcc90f.20260209-0731
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 +691 -0
- package/browser/context.d.ts +4 -0
- package/browser/context.js +20 -0
- package/config.d.ts +3 -0
- package/coverage.d.ts +1 -0
- package/dist/@vitest/browser/client/.vite/manifest.json +24 -0
- package/dist/@vitest/browser/client/__vitest__/assets/index-BUCFJtth.js +57 -0
- package/dist/@vitest/browser/client/__vitest__/assets/index-DlhE0rqZ.css +1 -0
- package/dist/@vitest/browser/client/__vitest__/bg.png +0 -0
- package/dist/@vitest/browser/client/__vitest__/favicon.ico +0 -0
- package/dist/@vitest/browser/client/__vitest__/favicon.svg +5 -0
- package/dist/@vitest/browser/client/__vitest__/index.html +32 -0
- package/dist/@vitest/browser/client/__vitest_browser__/orchestrator-S_3e_uzt.js +345 -0
- package/dist/@vitest/browser/client/__vitest_browser__/tester-k74mgIRa.js +2171 -0
- package/dist/@vitest/browser/client/__vitest_browser__/utils-uxqdqUz8.js +223 -0
- package/dist/@vitest/browser/client/error-catcher.js +82 -0
- package/dist/@vitest/browser/client/esm-client-injector.js +67 -0
- package/dist/@vitest/browser/client/favicon.svg +5 -0
- package/dist/@vitest/browser/client/orchestrator.html +35 -0
- package/dist/@vitest/browser/client/tester/tester.html +13 -0
- package/dist/@vitest/browser/client.js +456 -0
- package/dist/@vitest/browser/context.d.ts +792 -0
- package/dist/@vitest/browser/context.js +541 -0
- package/dist/@vitest/browser/expect-element.js +32 -0
- package/dist/@vitest/browser/index-D6m36C6U.js +11 -0
- package/dist/@vitest/browser/index.d.ts +73 -0
- package/dist/@vitest/browser/index.js +3513 -0
- package/dist/@vitest/browser/jest-dom.d.ts +724 -0
- package/dist/@vitest/browser/locators.d.ts +354 -0
- package/dist/@vitest/browser/locators.js +1 -0
- package/dist/@vitest/browser/matchers.d.ts +29 -0
- package/dist/@vitest/browser/shared/screenshotMatcher/types.d.ts +22 -0
- package/dist/@vitest/browser/state.js +280 -0
- package/dist/@vitest/browser/types.d.ts +69 -0
- package/dist/@vitest/browser-playwright/context.d.ts +1 -0
- package/dist/@vitest/browser-playwright/index.d.ts +106 -0
- package/dist/@vitest/browser-playwright/index.js +1111 -0
- package/dist/@vitest/browser-playwright/locators.js +114 -0
- package/dist/@vitest/browser-preview/context.d.ts +1 -0
- package/dist/@vitest/browser-preview/index.d.ts +19 -0
- package/dist/@vitest/browser-preview/index.js +148 -0
- package/dist/@vitest/browser-preview/locators.js +79 -0
- package/dist/@vitest/browser-webdriverio/context.d.ts +1 -0
- package/dist/@vitest/browser-webdriverio/index.d.ts +63 -0
- package/dist/@vitest/browser-webdriverio/index.js +600 -0
- package/dist/@vitest/browser-webdriverio/locators.js +163 -0
- package/dist/@vitest/expect/index.d.ts +807 -0
- package/dist/@vitest/expect/index.js +1875 -0
- package/dist/@vitest/mocker/auto-register.d.ts +2 -0
- package/dist/@vitest/mocker/auto-register.js +9 -0
- package/dist/@vitest/mocker/automock.d.ts +12 -0
- package/dist/@vitest/mocker/automock.js +1 -0
- package/dist/@vitest/mocker/browser.d.ts +53 -0
- package/dist/@vitest/mocker/browser.js +91 -0
- package/dist/@vitest/mocker/chunk-automock.js +354 -0
- package/dist/@vitest/mocker/chunk-interceptor-native.js +15 -0
- package/dist/@vitest/mocker/chunk-mocker.js +521 -0
- package/dist/@vitest/mocker/chunk-pathe.M-eThtNZ.js +174 -0
- package/dist/@vitest/mocker/chunk-registry.js +185 -0
- package/dist/@vitest/mocker/chunk-utils.js +16 -0
- package/dist/@vitest/mocker/index.d-C-sLYZi-.d.ts +25 -0
- package/dist/@vitest/mocker/index.d.ts +2 -0
- package/dist/@vitest/mocker/index.js +185 -0
- package/dist/@vitest/mocker/mocker.d-TnKRhz7N.d.ts +81 -0
- package/dist/@vitest/mocker/node.d.ts +800 -0
- package/dist/@vitest/mocker/node.js +966 -0
- package/dist/@vitest/mocker/redirect.d.ts +3 -0
- package/dist/@vitest/mocker/redirect.js +79 -0
- package/dist/@vitest/mocker/register.d.ts +9 -0
- package/dist/@vitest/mocker/register.js +41 -0
- package/dist/@vitest/mocker/types.d-B8CCKmHt.d.ts +107 -0
- package/dist/@vitest/pretty-format/index.d.ts +124 -0
- package/dist/@vitest/pretty-format/index.js +1022 -0
- package/dist/@vitest/runner/chunk-tasks.js +340 -0
- package/dist/@vitest/runner/index.d.ts +180 -0
- package/dist/@vitest/runner/index.js +2114 -0
- package/dist/@vitest/runner/tasks.d-C7UxawJ9.d.ts +834 -0
- package/dist/@vitest/runner/types.d.ts +183 -0
- package/dist/@vitest/runner/types.js +1 -0
- package/dist/@vitest/runner/utils.d.ts +45 -0
- package/dist/@vitest/runner/utils.js +5 -0
- package/dist/@vitest/snapshot/environment.d-DHdQ1Csl.d.ts +22 -0
- package/dist/@vitest/snapshot/environment.d.ts +16 -0
- package/dist/@vitest/snapshot/environment.js +40 -0
- package/dist/@vitest/snapshot/index.d.ts +130 -0
- package/dist/@vitest/snapshot/index.js +1437 -0
- package/dist/@vitest/snapshot/manager.d.ts +18 -0
- package/dist/@vitest/snapshot/manager.js +73 -0
- package/dist/@vitest/snapshot/rawSnapshot.d-lFsMJFUd.d.ts +61 -0
- package/dist/@vitest/spy/index.d.ts +384 -0
- package/dist/@vitest/spy/index.js +433 -0
- package/dist/@vitest/utils/chunk-_commonjsHelpers.js +5 -0
- package/dist/@vitest/utils/chunk-pathe.M-eThtNZ.js +156 -0
- package/dist/@vitest/utils/constants.d.ts +21 -0
- package/dist/@vitest/utils/constants.js +49 -0
- package/dist/@vitest/utils/diff.d.ts +93 -0
- package/dist/@vitest/utils/diff.js +2199 -0
- package/dist/@vitest/utils/display.d.ts +29 -0
- package/dist/@vitest/utils/display.js +742 -0
- package/dist/@vitest/utils/error.d.ts +7 -0
- package/dist/@vitest/utils/error.js +42 -0
- package/dist/@vitest/utils/helpers.d.ts +73 -0
- package/dist/@vitest/utils/helpers.js +295 -0
- package/dist/@vitest/utils/highlight.d.ts +9 -0
- package/dist/@vitest/utils/highlight.js +538 -0
- package/dist/@vitest/utils/index.d.ts +5 -0
- package/dist/@vitest/utils/index.js +1 -0
- package/dist/@vitest/utils/offset.d.ts +5 -0
- package/dist/@vitest/utils/offset.js +32 -0
- package/dist/@vitest/utils/resolver.d.ts +7 -0
- package/dist/@vitest/utils/resolver.js +71 -0
- package/dist/@vitest/utils/serialize.d.ts +3 -0
- package/dist/@vitest/utils/serialize.js +118 -0
- package/dist/@vitest/utils/source-map.d.ts +55 -0
- package/dist/@vitest/utils/source-map.js +478 -0
- package/dist/@vitest/utils/timers.d.ts +33 -0
- package/dist/@vitest/utils/timers.js +49 -0
- package/dist/@vitest/utils/types.d-BCElaP-c.d.ts +53 -0
- package/dist/@vitest/utils/types.d.ts +34 -0
- package/dist/@vitest/utils/types.js +1 -0
- package/dist/browser-compat.js +3 -0
- package/dist/browser.d.ts +46 -0
- package/dist/browser.js +20 -0
- package/dist/chunks/_commonjsHelpers.D26ty3Ew.js +6 -0
- package/dist/chunks/base.CJ0Y4ePK.js +165 -0
- package/dist/chunks/benchmark.B3N2zMcH.js +40 -0
- package/dist/chunks/benchmark.d.DAaHLpsq.d.ts +24 -0
- package/dist/chunks/browser.d.ChKACdzH.d.ts +59 -0
- package/dist/chunks/cac.DVeoLl0M.js +1409 -0
- package/dist/chunks/cli-api.B7PN_QUv.js +13672 -0
- package/dist/chunks/config.d.Cy95HiCx.d.ts +210 -0
- package/dist/chunks/console.Cf-YriPC.js +146 -0
- package/dist/chunks/constants.D_Q9UYh-.js +36 -0
- package/dist/chunks/coverage.AVPTjMgw.js +3292 -0
- package/dist/chunks/coverage.D_JHT54q.js +25 -0
- package/dist/chunks/coverage.d.BZtK59WP.d.ts +37 -0
- package/dist/chunks/creator.DAmOKTvJ.js +673 -0
- package/dist/chunks/date.Bq6ZW5rf.js +73 -0
- package/dist/chunks/defaults.BOqNVLsY.js +74 -0
- package/dist/chunks/env.D4Lgay0q.js +8 -0
- package/dist/chunks/environment.d.CrsxCzP1.d.ts +29 -0
- package/dist/chunks/evaluatedModules.Dg1zASAC.js +17 -0
- package/dist/chunks/evaluatedModules.d.BxJ5omdx.d.ts +7 -0
- package/dist/chunks/git.Bm2pzPAa.js +71 -0
- package/dist/chunks/global.d.B15mdLcR.d.ts +99 -0
- package/dist/chunks/globals.DOayXfHP.js +30 -0
- package/dist/chunks/index.6Qv1eEA6.js +109 -0
- package/dist/chunks/index.C5r1PdPD.js +231 -0
- package/dist/chunks/index.Chj8NDwU.js +206 -0
- package/dist/chunks/index.CyBMJtT7.js +727 -0
- package/dist/chunks/index.D3XRDfWc.js +213 -0
- package/dist/chunks/index.D4KonVSU.js +6343 -0
- package/dist/chunks/index.M8mOzt4Y.js +3839 -0
- package/dist/chunks/index.Z5E_ObnR.js +37 -0
- package/dist/chunks/init-forks._y3TW739.js +41 -0
- package/dist/chunks/init-threads.DBO2kn-p.js +18 -0
- package/dist/chunks/init.B6MLFIaN.js +334 -0
- package/dist/chunks/inspector.CvyFGlXm.js +53 -0
- package/dist/chunks/modules.BJuCwlRJ.js +36 -0
- package/dist/chunks/node.Ce0vMQM7.js +14 -0
- package/dist/chunks/plugin.d.CtqpEehP.d.ts +38 -0
- package/dist/chunks/reporters.d.CWXNI2jG.d.ts +3271 -0
- package/dist/chunks/rpc.BoxB0q7B.js +76 -0
- package/dist/chunks/rpc.d.RH3apGEf.d.ts +64 -0
- package/dist/chunks/setup-common.Cm-kSBVi.js +60 -0
- package/dist/chunks/startModuleRunner.DEj0jb3e.js +861 -0
- package/dist/chunks/suite.d.BJWk38HB.d.ts +10 -0
- package/dist/chunks/test.B8ej_ZHS.js +254 -0
- package/dist/chunks/traces.CCmnQaNT.js +217 -0
- package/dist/chunks/traces.d.402V_yFI.d.ts +18 -0
- package/dist/chunks/utils.DvEY5TfP.js +52 -0
- package/dist/chunks/vi.2VT5v0um.js +3919 -0
- package/dist/chunks/vm.D3epNOPZ.js +744 -0
- package/dist/chunks/worker.d.Dyxm8DEL.d.ts +255 -0
- package/dist/cli.js +28 -0
- package/dist/client/.vite/manifest.json +24 -0
- package/dist/client/__vitest__/assets/index-BUCFJtth.js +57 -0
- package/dist/client/__vitest__/assets/index-DlhE0rqZ.css +1 -0
- package/dist/client/__vitest__/bg.png +0 -0
- package/dist/client/__vitest__/favicon.ico +0 -0
- package/dist/client/__vitest__/favicon.svg +5 -0
- package/dist/client/__vitest__/index.html +32 -0
- package/dist/client/__vitest_browser__/orchestrator-S_3e_uzt.js +345 -0
- package/dist/client/__vitest_browser__/tester-k74mgIRa.js +2171 -0
- package/dist/client/__vitest_browser__/utils-uxqdqUz8.js +223 -0
- package/dist/client/error-catcher.js +82 -0
- package/dist/client/esm-client-injector.js +67 -0
- package/dist/client/favicon.svg +5 -0
- package/dist/client/orchestrator.html +35 -0
- package/dist/client/tester/tester.html +13 -0
- package/dist/client.js +456 -0
- package/dist/config.cjs +94 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.js +15 -0
- package/dist/context.js +541 -0
- package/dist/coverage.d.ts +118 -0
- package/dist/coverage.js +23 -0
- package/dist/dummy.js +2 -0
- package/dist/environments.d.ts +22 -0
- package/dist/environments.js +3 -0
- package/dist/expect-element.js +27 -0
- package/dist/index-D6m36C6U.js +6 -0
- package/dist/index-node.js +7 -0
- package/dist/index.d.ts +510 -0
- package/dist/index.js +19 -0
- package/dist/locators.d.ts +354 -0
- package/dist/locators.js +1 -0
- package/dist/mocker.d.ts +1 -0
- package/dist/mocker.js +1 -0
- package/dist/module-evaluator.d.ts +124 -0
- package/dist/module-evaluator.js +343 -0
- package/dist/module-runner-stub.js +44 -0
- package/dist/module-runner.js +17 -0
- package/dist/node.d.ts +251 -0
- package/dist/node.js +98 -0
- package/dist/path.js +7 -0
- package/dist/plugins/browser-client.mjs +2 -0
- package/dist/plugins/browser-context.mjs +2 -0
- package/dist/plugins/browser-locators.mjs +2 -0
- package/dist/plugins/browser-playwright.mjs +2 -0
- package/dist/plugins/browser-preview.mjs +2 -0
- package/dist/plugins/browser-webdriverio.mjs +2 -0
- package/dist/plugins/browser.mjs +2 -0
- package/dist/plugins/expect.mjs +2 -0
- package/dist/plugins/mocker-automock.mjs +2 -0
- package/dist/plugins/mocker-browser.mjs +2 -0
- package/dist/plugins/mocker-node.mjs +2 -0
- package/dist/plugins/mocker-redirect.mjs +2 -0
- package/dist/plugins/mocker-register.mjs +2 -0
- package/dist/plugins/mocker.mjs +2 -0
- package/dist/plugins/pretty-format.mjs +2 -0
- package/dist/plugins/runner-types.mjs +2 -0
- package/dist/plugins/runner-utils.mjs +2 -0
- package/dist/plugins/runner.mjs +2 -0
- package/dist/plugins/snapshot-environment.mjs +2 -0
- package/dist/plugins/snapshot-manager.mjs +2 -0
- package/dist/plugins/snapshot.mjs +2 -0
- package/dist/plugins/spy.mjs +2 -0
- package/dist/plugins/utils-constants.mjs +2 -0
- package/dist/plugins/utils-diff.mjs +2 -0
- package/dist/plugins/utils-display.mjs +2 -0
- package/dist/plugins/utils-error.mjs +2 -0
- package/dist/plugins/utils-helpers.mjs +2 -0
- package/dist/plugins/utils-highlight.mjs +2 -0
- package/dist/plugins/utils-offset.mjs +2 -0
- package/dist/plugins/utils-resolver.mjs +2 -0
- package/dist/plugins/utils-serialize.mjs +2 -0
- package/dist/plugins/utils-source-map.mjs +2 -0
- package/dist/plugins/utils-timers.mjs +2 -0
- package/dist/plugins/utils.mjs +2 -0
- package/dist/reporters.d.ts +27 -0
- package/dist/reporters.js +24 -0
- package/dist/runners.d.ts +50 -0
- package/dist/runners.js +19 -0
- package/dist/shared/screenshotMatcher/types.d.ts +22 -0
- package/dist/snapshot.d.ts +9 -0
- package/dist/snapshot.js +4 -0
- package/dist/spy.js +1 -0
- package/dist/state.js +280 -0
- package/dist/suite.d.ts +5 -0
- package/dist/suite.js +6 -0
- package/dist/types.d.ts +69 -0
- package/dist/vendor/chai.d.mts +1 -0
- package/dist/vendor/chai.mjs +3577 -0
- package/dist/vendor/es-module-lexer.d.mts +193 -0
- package/dist/vendor/es-module-lexer.mjs +79 -0
- package/dist/vendor/estree-walker.d.mts +583 -0
- package/dist/vendor/estree-walker.mjs +339 -0
- package/dist/vendor/expect-type.d.mts +1574 -0
- package/dist/vendor/expect-type.mjs +214 -0
- package/dist/vendor/magic-string.d.mts +261 -0
- package/dist/vendor/magic-string.mjs +1700 -0
- package/dist/vendor/obug.d.mts +56 -0
- package/dist/vendor/obug.mjs +276 -0
- package/dist/vendor/pathe.d.mts +46 -0
- package/dist/vendor/pathe.mjs +496 -0
- package/dist/vendor/picomatch.d.mts +1 -0
- package/dist/vendor/picomatch.mjs +1855 -0
- package/dist/vendor/shared-3g9mwCWP.mjs +31 -0
- package/dist/vendor/std-env.d.mts +88 -0
- package/dist/vendor/std-env.mjs +159 -0
- package/dist/vendor/tinybench.d.mts +317 -0
- package/dist/vendor/tinybench.mjs +504 -0
- package/dist/vendor/tinyexec.d.mts +72 -0
- package/dist/vendor/tinyexec.mjs +637 -0
- package/dist/vendor/tinyglobby.d.mts +157 -0
- package/dist/vendor/tinyglobby.mjs +832 -0
- package/dist/vendor/tinyrainbow.d.mts +60 -0
- package/dist/vendor/tinyrainbow.mjs +93 -0
- package/dist/vendor/vitest_browser.mjs +2 -0
- package/dist/vendor/vitest_internal_browser.mjs +2 -0
- package/dist/vendor/vitest_runner.mjs +2 -0
- package/dist/vendor/vitest_runners.mjs +2 -0
- package/dist/worker.d.ts +32 -0
- package/dist/worker.js +48 -0
- package/dist/workers/forks.js +54 -0
- package/dist/workers/runVmTests.js +95 -0
- package/dist/workers/threads.js +55 -0
- package/dist/workers/vmForks.js +36 -0
- package/dist/workers/vmThreads.js +37 -0
- package/environments.d.ts +1 -0
- package/globals.d.ts +20 -0
- package/import-meta.d.ts +5 -0
- package/importMeta.d.ts +4 -0
- package/index.cjs +5 -0
- package/index.d.cts +1 -0
- package/jsdom.d.ts +6 -0
- package/mocker.d.ts +1 -0
- package/node.d.ts +1 -0
- package/optional-types.d.ts +7 -0
- package/package.json +335 -0
- package/reporters.d.ts +1 -0
- package/runners.d.ts +1 -0
- package/snapshot.d.ts +1 -0
- package/suite.d.ts +1 -0
- package/suppress-warnings.cjs +21 -0
- package/vitest.mjs +2 -0
- package/worker.d.ts +1 -0
|
@@ -0,0 +1,3513 @@
|
|
|
1
|
+
import { ManualMockedModule, RedirectedModule, AutomockedModule, AutospiedModule, MockerRegistry } from '../mocker/index.js';
|
|
2
|
+
import { dynamicImportPlugin, ServerMockResolver, interceptorPlugin } from '../mocker/node.js';
|
|
3
|
+
import c from '../../vendor/tinyrainbow.mjs';
|
|
4
|
+
import { isValidApiRequest, isFileServingAllowed, distDir, resolveApiServerConfig, resolveFsAllow, rolldownVersion, createDebugger, createViteLogger, createViteServer } from '../../node.js';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import fs, { readFileSync, lstatSync, createReadStream, promises, existsSync } from 'node:fs';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { slash as slash$1, toArray, deepMerge } from '../utils/helpers.js';
|
|
9
|
+
import MagicString from '../../vendor/magic-string.mjs';
|
|
10
|
+
import sirv from 'sirv';
|
|
11
|
+
import { coverageConfigDefaults } from '../../config.js';
|
|
12
|
+
import crypto from 'node:crypto';
|
|
13
|
+
import { readFile as readFile$1, mkdir, writeFile as writeFile$1 } from 'node:fs/promises';
|
|
14
|
+
import { parseErrorStacktrace, parseStacktrace } from '../utils/source-map.js';
|
|
15
|
+
import { resolve as resolve$1, basename as basename$1, dirname as dirname$1 } from 'node:path';
|
|
16
|
+
import { platform } from 'node:os';
|
|
17
|
+
import { PNG } from 'pngjs';
|
|
18
|
+
import pm from 'pixelmatch';
|
|
19
|
+
import { WebSocketServer } from 'ws';
|
|
20
|
+
|
|
21
|
+
var version = "4.0.18";
|
|
22
|
+
|
|
23
|
+
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
24
|
+
function normalizeWindowsPath(input = "") {
|
|
25
|
+
if (!input) {
|
|
26
|
+
return input;
|
|
27
|
+
}
|
|
28
|
+
return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const _UNC_REGEX = /^[/\\]{2}/;
|
|
32
|
+
const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
|
|
33
|
+
const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
|
|
34
|
+
const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
|
|
35
|
+
const _EXTNAME_RE = /.(\.[^./]+|\.)$/;
|
|
36
|
+
const normalize = function(path) {
|
|
37
|
+
if (path.length === 0) {
|
|
38
|
+
return ".";
|
|
39
|
+
}
|
|
40
|
+
path = normalizeWindowsPath(path);
|
|
41
|
+
const isUNCPath = path.match(_UNC_REGEX);
|
|
42
|
+
const isPathAbsolute = isAbsolute(path);
|
|
43
|
+
const trailingSeparator = path[path.length - 1] === "/";
|
|
44
|
+
path = normalizeString(path, !isPathAbsolute);
|
|
45
|
+
if (path.length === 0) {
|
|
46
|
+
if (isPathAbsolute) {
|
|
47
|
+
return "/";
|
|
48
|
+
}
|
|
49
|
+
return trailingSeparator ? "./" : ".";
|
|
50
|
+
}
|
|
51
|
+
if (trailingSeparator) {
|
|
52
|
+
path += "/";
|
|
53
|
+
}
|
|
54
|
+
if (_DRIVE_LETTER_RE.test(path)) {
|
|
55
|
+
path += "/";
|
|
56
|
+
}
|
|
57
|
+
if (isUNCPath) {
|
|
58
|
+
if (!isPathAbsolute) {
|
|
59
|
+
return `//./${path}`;
|
|
60
|
+
}
|
|
61
|
+
return `//${path}`;
|
|
62
|
+
}
|
|
63
|
+
return isPathAbsolute && !isAbsolute(path) ? `/${path}` : path;
|
|
64
|
+
};
|
|
65
|
+
const join = function(...segments) {
|
|
66
|
+
let path = "";
|
|
67
|
+
for (const seg of segments) {
|
|
68
|
+
if (!seg) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (path.length > 0) {
|
|
72
|
+
const pathTrailing = path[path.length - 1] === "/";
|
|
73
|
+
const segLeading = seg[0] === "/";
|
|
74
|
+
const both = pathTrailing && segLeading;
|
|
75
|
+
if (both) {
|
|
76
|
+
path += seg.slice(1);
|
|
77
|
+
} else {
|
|
78
|
+
path += pathTrailing || segLeading ? seg : `/${seg}`;
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
path += seg;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return normalize(path);
|
|
85
|
+
};
|
|
86
|
+
function cwd() {
|
|
87
|
+
if (typeof process !== "undefined" && typeof process.cwd === "function") {
|
|
88
|
+
return process.cwd().replace(/\\/g, "/");
|
|
89
|
+
}
|
|
90
|
+
return "/";
|
|
91
|
+
}
|
|
92
|
+
const resolve = function(...arguments_) {
|
|
93
|
+
arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
|
|
94
|
+
let resolvedPath = "";
|
|
95
|
+
let resolvedAbsolute = false;
|
|
96
|
+
for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
|
|
97
|
+
const path = index >= 0 ? arguments_[index] : cwd();
|
|
98
|
+
if (!path || path.length === 0) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
resolvedPath = `${path}/${resolvedPath}`;
|
|
102
|
+
resolvedAbsolute = isAbsolute(path);
|
|
103
|
+
}
|
|
104
|
+
resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
|
|
105
|
+
if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
|
|
106
|
+
return `/${resolvedPath}`;
|
|
107
|
+
}
|
|
108
|
+
return resolvedPath.length > 0 ? resolvedPath : ".";
|
|
109
|
+
};
|
|
110
|
+
function normalizeString(path, allowAboveRoot) {
|
|
111
|
+
let res = "";
|
|
112
|
+
let lastSegmentLength = 0;
|
|
113
|
+
let lastSlash = -1;
|
|
114
|
+
let dots = 0;
|
|
115
|
+
let char = null;
|
|
116
|
+
for (let index = 0; index <= path.length; ++index) {
|
|
117
|
+
if (index < path.length) {
|
|
118
|
+
char = path[index];
|
|
119
|
+
} else if (char === "/") {
|
|
120
|
+
break;
|
|
121
|
+
} else {
|
|
122
|
+
char = "/";
|
|
123
|
+
}
|
|
124
|
+
if (char === "/") {
|
|
125
|
+
if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) {
|
|
126
|
+
if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
|
|
127
|
+
if (res.length > 2) {
|
|
128
|
+
const lastSlashIndex = res.lastIndexOf("/");
|
|
129
|
+
if (lastSlashIndex === -1) {
|
|
130
|
+
res = "";
|
|
131
|
+
lastSegmentLength = 0;
|
|
132
|
+
} else {
|
|
133
|
+
res = res.slice(0, lastSlashIndex);
|
|
134
|
+
lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
|
|
135
|
+
}
|
|
136
|
+
lastSlash = index;
|
|
137
|
+
dots = 0;
|
|
138
|
+
continue;
|
|
139
|
+
} else if (res.length > 0) {
|
|
140
|
+
res = "";
|
|
141
|
+
lastSegmentLength = 0;
|
|
142
|
+
lastSlash = index;
|
|
143
|
+
dots = 0;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (allowAboveRoot) {
|
|
148
|
+
res += res.length > 0 ? "/.." : "..";
|
|
149
|
+
lastSegmentLength = 2;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
if (res.length > 0) {
|
|
153
|
+
res += `/${path.slice(lastSlash + 1, index)}`;
|
|
154
|
+
} else {
|
|
155
|
+
res = path.slice(lastSlash + 1, index);
|
|
156
|
+
}
|
|
157
|
+
lastSegmentLength = index - lastSlash - 1;
|
|
158
|
+
}
|
|
159
|
+
lastSlash = index;
|
|
160
|
+
dots = 0;
|
|
161
|
+
} else if (char === "." && dots !== -1) {
|
|
162
|
+
++dots;
|
|
163
|
+
} else {
|
|
164
|
+
dots = -1;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return res;
|
|
168
|
+
}
|
|
169
|
+
const isAbsolute = function(p) {
|
|
170
|
+
return _IS_ABSOLUTE_RE.test(p);
|
|
171
|
+
};
|
|
172
|
+
const extname = function(p) {
|
|
173
|
+
if (p === "..") return "";
|
|
174
|
+
const match = _EXTNAME_RE.exec(normalizeWindowsPath(p));
|
|
175
|
+
return match && match[1] || "";
|
|
176
|
+
};
|
|
177
|
+
const relative = function(from, to) {
|
|
178
|
+
const _from = resolve(from).replace(_ROOT_FOLDER_RE, "$1").split("/");
|
|
179
|
+
const _to = resolve(to).replace(_ROOT_FOLDER_RE, "$1").split("/");
|
|
180
|
+
if (_to[0][1] === ":" && _from[0][1] === ":" && _from[0] !== _to[0]) {
|
|
181
|
+
return _to.join("/");
|
|
182
|
+
}
|
|
183
|
+
const _fromCopy = [..._from];
|
|
184
|
+
for (const segment of _fromCopy) {
|
|
185
|
+
if (_to[0] !== segment) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
_from.shift();
|
|
189
|
+
_to.shift();
|
|
190
|
+
}
|
|
191
|
+
return [..._from.map(() => ".."), ..._to].join("/");
|
|
192
|
+
};
|
|
193
|
+
const dirname = function(p) {
|
|
194
|
+
const segments = normalizeWindowsPath(p).replace(/\/$/, "").split("/").slice(0, -1);
|
|
195
|
+
if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {
|
|
196
|
+
segments[0] += "/";
|
|
197
|
+
}
|
|
198
|
+
return segments.join("/") || (isAbsolute(p) ? "/" : ".");
|
|
199
|
+
};
|
|
200
|
+
const basename = function(p, extension) {
|
|
201
|
+
const segments = normalizeWindowsPath(p).split("/");
|
|
202
|
+
let lastSegment = "";
|
|
203
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
204
|
+
const val = segments[i];
|
|
205
|
+
if (val) {
|
|
206
|
+
lastSegment = val;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const distRoot = dirname(fileURLToPath(import.meta.url));
|
|
214
|
+
|
|
215
|
+
/// <reference types="../types/index.d.ts" />
|
|
216
|
+
|
|
217
|
+
// (c) 2020-present Andrea Giammarchi
|
|
218
|
+
|
|
219
|
+
const {parse: $parse, stringify: $stringify} = JSON;
|
|
220
|
+
const {keys} = Object;
|
|
221
|
+
|
|
222
|
+
const Primitive = String; // it could be Number
|
|
223
|
+
const primitive = 'string'; // it could be 'number'
|
|
224
|
+
|
|
225
|
+
const ignore = {};
|
|
226
|
+
const object = 'object';
|
|
227
|
+
|
|
228
|
+
const noop = (_, value) => value;
|
|
229
|
+
|
|
230
|
+
const primitives = value => (
|
|
231
|
+
value instanceof Primitive ? Primitive(value) : value
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const Primitives = (_, value) => (
|
|
235
|
+
typeof value === primitive ? new Primitive(value) : value
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const revive = (input, parsed, output, $) => {
|
|
239
|
+
const lazy = [];
|
|
240
|
+
for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
|
|
241
|
+
const k = ke[y];
|
|
242
|
+
const value = output[k];
|
|
243
|
+
if (value instanceof Primitive) {
|
|
244
|
+
const tmp = input[value];
|
|
245
|
+
if (typeof tmp === object && !parsed.has(tmp)) {
|
|
246
|
+
parsed.add(tmp);
|
|
247
|
+
output[k] = ignore;
|
|
248
|
+
lazy.push({k, a: [input, parsed, tmp, $]});
|
|
249
|
+
}
|
|
250
|
+
else
|
|
251
|
+
output[k] = $.call(output, k, tmp);
|
|
252
|
+
}
|
|
253
|
+
else if (output[k] !== ignore)
|
|
254
|
+
output[k] = $.call(output, k, value);
|
|
255
|
+
}
|
|
256
|
+
for (let {length} = lazy, i = 0; i < length; i++) {
|
|
257
|
+
const {k, a} = lazy[i];
|
|
258
|
+
output[k] = $.call(output, k, revive.apply(null, a));
|
|
259
|
+
}
|
|
260
|
+
return output;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const set = (known, input, value) => {
|
|
264
|
+
const index = Primitive(input.push(value) - 1);
|
|
265
|
+
known.set(value, index);
|
|
266
|
+
return index;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Converts a specialized flatted string into a JS value.
|
|
271
|
+
* @param {string} text
|
|
272
|
+
* @param {(this: any, key: string, value: any) => any} [reviver]
|
|
273
|
+
* @returns {any}
|
|
274
|
+
*/
|
|
275
|
+
const parse = (text, reviver) => {
|
|
276
|
+
const input = $parse(text, Primitives).map(primitives);
|
|
277
|
+
const value = input[0];
|
|
278
|
+
const $ = reviver || noop;
|
|
279
|
+
const tmp = typeof value === object && value ?
|
|
280
|
+
revive(input, new Set, value, $) :
|
|
281
|
+
value;
|
|
282
|
+
return $.call({'': tmp}, '', tmp);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Converts a JS value into a specialized flatted string.
|
|
287
|
+
* @param {any} value
|
|
288
|
+
* @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
|
|
289
|
+
* @param {string | number | undefined} [space]
|
|
290
|
+
* @returns {string}
|
|
291
|
+
*/
|
|
292
|
+
const stringify = (value, replacer, space) => {
|
|
293
|
+
const $ = replacer && typeof replacer === object ?
|
|
294
|
+
(k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
|
|
295
|
+
(replacer || noop);
|
|
296
|
+
const known = new Map;
|
|
297
|
+
const input = [];
|
|
298
|
+
const output = [];
|
|
299
|
+
let i = +set(known, input, $.call({'': value}, '', value));
|
|
300
|
+
let firstRun = !i;
|
|
301
|
+
while (i < input.length) {
|
|
302
|
+
firstRun = true;
|
|
303
|
+
output[i] = $stringify(input[i++], replace, space);
|
|
304
|
+
}
|
|
305
|
+
return '[' + output.join(',') + ']';
|
|
306
|
+
function replace(key, value) {
|
|
307
|
+
if (firstRun) {
|
|
308
|
+
firstRun = !firstRun;
|
|
309
|
+
return value;
|
|
310
|
+
}
|
|
311
|
+
const after = $.call(this, key, value);
|
|
312
|
+
switch (typeof after) {
|
|
313
|
+
case object:
|
|
314
|
+
if (after === null) return after;
|
|
315
|
+
case primitive:
|
|
316
|
+
return known.get(after) || set(known, input, after);
|
|
317
|
+
}
|
|
318
|
+
return after;
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
var DOM_KEY_LOCATION = /*#__PURE__*/ function(DOM_KEY_LOCATION) {
|
|
323
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["STANDARD"] = 0] = "STANDARD";
|
|
324
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["LEFT"] = 1] = "LEFT";
|
|
325
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["RIGHT"] = 2] = "RIGHT";
|
|
326
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["NUMPAD"] = 3] = "NUMPAD";
|
|
327
|
+
return DOM_KEY_LOCATION;
|
|
328
|
+
}({});
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Mapping for a default US-104-QWERTY keyboard
|
|
332
|
+
*/ const defaultKeyMap = [
|
|
333
|
+
// alphanumeric block - writing system
|
|
334
|
+
...'0123456789'.split('').map((c)=>({
|
|
335
|
+
code: `Digit${c}`,
|
|
336
|
+
key: c
|
|
337
|
+
})),
|
|
338
|
+
...')!@#$%^&*('.split('').map((c, i)=>({
|
|
339
|
+
code: `Digit${i}`,
|
|
340
|
+
key: c,
|
|
341
|
+
shiftKey: true
|
|
342
|
+
})),
|
|
343
|
+
...'abcdefghijklmnopqrstuvwxyz'.split('').map((c)=>({
|
|
344
|
+
code: `Key${c.toUpperCase()}`,
|
|
345
|
+
key: c
|
|
346
|
+
})),
|
|
347
|
+
...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((c)=>({
|
|
348
|
+
code: `Key${c}`,
|
|
349
|
+
key: c,
|
|
350
|
+
shiftKey: true
|
|
351
|
+
})),
|
|
352
|
+
{
|
|
353
|
+
code: 'BracketLeft',
|
|
354
|
+
key: '['
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
code: 'BracketLeft',
|
|
358
|
+
key: '{',
|
|
359
|
+
shiftKey: true
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
code: 'BracketRight',
|
|
363
|
+
key: ']'
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
code: 'BracketRight',
|
|
367
|
+
key: '}',
|
|
368
|
+
shiftKey: true
|
|
369
|
+
},
|
|
370
|
+
// alphanumeric block - functional
|
|
371
|
+
{
|
|
372
|
+
code: 'Space',
|
|
373
|
+
key: ' '
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
code: 'AltLeft',
|
|
377
|
+
key: 'Alt',
|
|
378
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
code: 'AltRight',
|
|
382
|
+
key: 'Alt',
|
|
383
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
code: 'ShiftLeft',
|
|
387
|
+
key: 'Shift',
|
|
388
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
code: 'ShiftRight',
|
|
392
|
+
key: 'Shift',
|
|
393
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
code: 'ControlLeft',
|
|
397
|
+
key: 'Control',
|
|
398
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
code: 'ControlRight',
|
|
402
|
+
key: 'Control',
|
|
403
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
code: 'MetaLeft',
|
|
407
|
+
key: 'Meta',
|
|
408
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
code: 'MetaRight',
|
|
412
|
+
key: 'Meta',
|
|
413
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
code: 'OSLeft',
|
|
417
|
+
key: 'OS',
|
|
418
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
code: 'OSRight',
|
|
422
|
+
key: 'OS',
|
|
423
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
code: 'ContextMenu',
|
|
427
|
+
key: 'ContextMenu'
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
code: 'Tab',
|
|
431
|
+
key: 'Tab'
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
code: 'CapsLock',
|
|
435
|
+
key: 'CapsLock'
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
code: 'Backspace',
|
|
439
|
+
key: 'Backspace'
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
code: 'Enter',
|
|
443
|
+
key: 'Enter'
|
|
444
|
+
},
|
|
445
|
+
// function
|
|
446
|
+
{
|
|
447
|
+
code: 'Escape',
|
|
448
|
+
key: 'Escape'
|
|
449
|
+
},
|
|
450
|
+
// arrows
|
|
451
|
+
{
|
|
452
|
+
code: 'ArrowUp',
|
|
453
|
+
key: 'ArrowUp'
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
code: 'ArrowDown',
|
|
457
|
+
key: 'ArrowDown'
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
code: 'ArrowLeft',
|
|
461
|
+
key: 'ArrowLeft'
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
code: 'ArrowRight',
|
|
465
|
+
key: 'ArrowRight'
|
|
466
|
+
},
|
|
467
|
+
// control pad
|
|
468
|
+
{
|
|
469
|
+
code: 'Home',
|
|
470
|
+
key: 'Home'
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
code: 'End',
|
|
474
|
+
key: 'End'
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
code: 'Delete',
|
|
478
|
+
key: 'Delete'
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
code: 'PageUp',
|
|
482
|
+
key: 'PageUp'
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
code: 'PageDown',
|
|
486
|
+
key: 'PageDown'
|
|
487
|
+
},
|
|
488
|
+
// Special keys that are not part of a default US-layout but included for specific behavior
|
|
489
|
+
{
|
|
490
|
+
code: 'Fn',
|
|
491
|
+
key: 'Fn'
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
code: 'Symbol',
|
|
495
|
+
key: 'Symbol'
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
code: 'AltRight',
|
|
499
|
+
key: 'AltGraph'
|
|
500
|
+
}
|
|
501
|
+
];
|
|
502
|
+
|
|
503
|
+
var bracketDict = /*#__PURE__*/ function(bracketDict) {
|
|
504
|
+
bracketDict["{"] = "}";
|
|
505
|
+
bracketDict["["] = "]";
|
|
506
|
+
return bracketDict;
|
|
507
|
+
}(bracketDict || {});
|
|
508
|
+
/**
|
|
509
|
+
* Read the next key definition from user input
|
|
510
|
+
*
|
|
511
|
+
* Describe key per `{descriptor}` or `[descriptor]`.
|
|
512
|
+
* Everything else will be interpreted as a single character as descriptor - e.g. `a`.
|
|
513
|
+
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
|
|
514
|
+
* A previously pressed key can be released per `{/descriptor}`.
|
|
515
|
+
* Keeping the key pressed can be written as `{descriptor>}`.
|
|
516
|
+
* When keeping the key pressed you can choose how long the key is pressed `{descriptor>3}`.
|
|
517
|
+
* You can then release the key per `{descriptor>3/}` or keep it pressed and continue with the next key.
|
|
518
|
+
*/ function readNextDescriptor(text, context) {
|
|
519
|
+
let pos = 0;
|
|
520
|
+
const startBracket = text[pos] in bracketDict ? text[pos] : '';
|
|
521
|
+
pos += startBracket.length;
|
|
522
|
+
const isEscapedChar = new RegExp(`^\\${startBracket}{2}`).test(text);
|
|
523
|
+
const type = isEscapedChar ? '' : startBracket;
|
|
524
|
+
return {
|
|
525
|
+
type,
|
|
526
|
+
...type === '' ? readPrintableChar(text, pos) : readTag(text, pos, type)
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function readPrintableChar(text, pos, context) {
|
|
530
|
+
const descriptor = text[pos];
|
|
531
|
+
assertDescriptor(descriptor, text, pos);
|
|
532
|
+
pos += descriptor.length;
|
|
533
|
+
return {
|
|
534
|
+
consumedLength: pos,
|
|
535
|
+
descriptor,
|
|
536
|
+
releasePrevious: false,
|
|
537
|
+
releaseSelf: true,
|
|
538
|
+
repeat: 1
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function readTag(text, pos, startBracket, context) {
|
|
542
|
+
var _text_slice_match, _text_slice_match1;
|
|
543
|
+
const releasePreviousModifier = text[pos] === '/' ? '/' : '';
|
|
544
|
+
pos += releasePreviousModifier.length;
|
|
545
|
+
const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
|
|
546
|
+
pos += Number(escapedDescriptor);
|
|
547
|
+
const descriptor = escapedDescriptor ? text[pos] : (_text_slice_match = text.slice(pos).match(startBracket === '{' ? /^\w+|^[^}>/]/ : /^\w+/)) === null || _text_slice_match === undefined ? undefined : _text_slice_match[0];
|
|
548
|
+
assertDescriptor(descriptor, text, pos);
|
|
549
|
+
pos += descriptor.length;
|
|
550
|
+
var _text_slice_match_;
|
|
551
|
+
const repeatModifier = (_text_slice_match_ = (_text_slice_match1 = text.slice(pos).match(/^>\d+/)) === null || _text_slice_match1 === undefined ? undefined : _text_slice_match1[0]) !== null && _text_slice_match_ !== undefined ? _text_slice_match_ : '';
|
|
552
|
+
pos += repeatModifier.length;
|
|
553
|
+
const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
|
|
554
|
+
pos += releaseSelfModifier.length;
|
|
555
|
+
const expectedEndBracket = bracketDict[startBracket];
|
|
556
|
+
const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';
|
|
557
|
+
if (!endBracket) {
|
|
558
|
+
throw new Error(getErrorMessage([
|
|
559
|
+
!repeatModifier && 'repeat modifier',
|
|
560
|
+
!releaseSelfModifier && 'release modifier',
|
|
561
|
+
`"${expectedEndBracket}"`
|
|
562
|
+
].filter(Boolean).join(' or '), text[pos], text));
|
|
563
|
+
}
|
|
564
|
+
pos += endBracket.length;
|
|
565
|
+
return {
|
|
566
|
+
consumedLength: pos,
|
|
567
|
+
descriptor,
|
|
568
|
+
releasePrevious: !!releasePreviousModifier,
|
|
569
|
+
repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
|
|
570
|
+
releaseSelf: hasReleaseSelf(releaseSelfModifier, repeatModifier)
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function assertDescriptor(descriptor, text, pos, context) {
|
|
574
|
+
if (!descriptor) {
|
|
575
|
+
throw new Error(getErrorMessage('key descriptor', text[pos], text));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
|
|
579
|
+
if (releaseSelfModifier) {
|
|
580
|
+
return releaseSelfModifier === '/';
|
|
581
|
+
}
|
|
582
|
+
if (repeatModifier) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function getErrorMessage(expected, found, text, context) {
|
|
587
|
+
return `Expected ${expected} but found "${found !== null && found !== undefined ? found : ''}" in "${text}"
|
|
588
|
+
See ${`https://testing-library.com/docs/user-event/keyboard`}
|
|
589
|
+
for more information about how userEvent parses your input.`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Parse key definitions per `keyboardMap`
|
|
594
|
+
*
|
|
595
|
+
* Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`.
|
|
596
|
+
* Everything else will be interpreted as a typed character - e.g. `a`.
|
|
597
|
+
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
|
|
598
|
+
* Keeping the key pressed can be written as `{key>}`.
|
|
599
|
+
* When keeping the key pressed you can choose how long (how many keydown and keypress) the key is pressed `{key>3}`.
|
|
600
|
+
* You can then release the key per `{key>3/}` or keep it pressed and continue with the next key.
|
|
601
|
+
*/ function parseKeyDef$1(keyboardMap, text) {
|
|
602
|
+
const defs = [];
|
|
603
|
+
do {
|
|
604
|
+
const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
|
|
605
|
+
var _keyboardMap_find;
|
|
606
|
+
const keyDef = (_keyboardMap_find = keyboardMap.find((def)=>{
|
|
607
|
+
if (type === '[') {
|
|
608
|
+
var _def_code;
|
|
609
|
+
return ((_def_code = def.code) === null || _def_code === undefined ? undefined : _def_code.toLowerCase()) === descriptor.toLowerCase();
|
|
610
|
+
} else if (type === '{') {
|
|
611
|
+
var _def_key;
|
|
612
|
+
return ((_def_key = def.key) === null || _def_key === undefined ? undefined : _def_key.toLowerCase()) === descriptor.toLowerCase();
|
|
613
|
+
}
|
|
614
|
+
return def.key === descriptor;
|
|
615
|
+
})) !== null && _keyboardMap_find !== undefined ? _keyboardMap_find : {
|
|
616
|
+
key: 'Unknown',
|
|
617
|
+
code: 'Unknown',
|
|
618
|
+
[type === '[' ? 'code' : 'key']: descriptor
|
|
619
|
+
};
|
|
620
|
+
defs.push({
|
|
621
|
+
keyDef,
|
|
622
|
+
releasePrevious,
|
|
623
|
+
releaseSelf,
|
|
624
|
+
repeat
|
|
625
|
+
});
|
|
626
|
+
text = text.slice(consumedLength);
|
|
627
|
+
}while (text)
|
|
628
|
+
return defs;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function parseKeyDef(text) {
|
|
632
|
+
return parseKeyDef$1(defaultKeyMap, text);
|
|
633
|
+
}
|
|
634
|
+
function replacer(code, values) {
|
|
635
|
+
return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? _);
|
|
636
|
+
}
|
|
637
|
+
function resolveScreenshotPath(testPath, name, config, customPath) {
|
|
638
|
+
if (customPath) {
|
|
639
|
+
return resolve(dirname(testPath), customPath);
|
|
640
|
+
}
|
|
641
|
+
const dir = dirname(testPath);
|
|
642
|
+
const base = basename(testPath);
|
|
643
|
+
if (config.browser.screenshotDirectory) {
|
|
644
|
+
return resolve(config.browser.screenshotDirectory, relative(config.root, dir), base, name);
|
|
645
|
+
}
|
|
646
|
+
return resolve(dir, "__screenshots__", base, name);
|
|
647
|
+
}
|
|
648
|
+
async function getBrowserProvider(options, project) {
|
|
649
|
+
const browser = project.config.browser.name;
|
|
650
|
+
const name = project.name ? `[${project.name}] ` : "";
|
|
651
|
+
if (!browser) {
|
|
652
|
+
throw new Error(`${name}Browser name is required. Please, set \`test.browser.instances[].browser\` option manually.`);
|
|
653
|
+
}
|
|
654
|
+
if (options.provider == null) {
|
|
655
|
+
throw new Error(`Browser Mode requires the "provider" to always be specified.`);
|
|
656
|
+
}
|
|
657
|
+
const supportedBrowsers = options.provider.supportedBrowser || [];
|
|
658
|
+
if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
|
|
659
|
+
throw new Error(`${name}Browser "${browser}" is not supported by the browser provider "${options.provider.name}". Supported browsers: ${supportedBrowsers.join(", ")}.`);
|
|
660
|
+
}
|
|
661
|
+
if (typeof options.provider.providerFactory !== "function") {
|
|
662
|
+
throw new TypeError(`The "${name}" browser provider does not provide a "providerFactory" function. Received ${typeof options.provider.providerFactory}.`);
|
|
663
|
+
}
|
|
664
|
+
return options.provider.providerFactory(project);
|
|
665
|
+
}
|
|
666
|
+
function slash(path) {
|
|
667
|
+
return path.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
async function resolveOrchestrator(globalServer, url, res) {
|
|
671
|
+
let sessionId = url.searchParams.get("sessionId");
|
|
672
|
+
// it's possible to open the page without a context
|
|
673
|
+
if (!sessionId) {
|
|
674
|
+
const contexts = [...globalServer.children].flatMap((p) => [...p.state.orchestrators.keys()]);
|
|
675
|
+
sessionId = contexts.at(-1) ?? "none";
|
|
676
|
+
}
|
|
677
|
+
// it's ok to not have a session here, especially in the preview provider
|
|
678
|
+
// because the user could refresh the page which would remove the session id from the url
|
|
679
|
+
const session = globalServer.vitest._browserSessions.getSession(sessionId);
|
|
680
|
+
const browserProject = session?.project.browser || [...globalServer.children][0];
|
|
681
|
+
if (!browserProject) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
// ignore unknown pages
|
|
685
|
+
if (sessionId && sessionId !== "none" && !globalServer.vitest._browserSessions.sessionIds.has(sessionId)) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
|
|
689
|
+
const injector = replacer(injectorJs, {
|
|
690
|
+
__VITEST_PROVIDER__: JSON.stringify(browserProject.config.browser.provider?.name || "preview"),
|
|
691
|
+
__VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
|
|
692
|
+
__VITEST_VITE_CONFIG__: JSON.stringify({ root: browserProject.vite.config.root }),
|
|
693
|
+
__VITEST_METHOD__: JSON.stringify("orchestrate"),
|
|
694
|
+
__VITEST_TYPE__: "\"orchestrator\"",
|
|
695
|
+
__VITEST_SESSION_ID__: JSON.stringify(sessionId),
|
|
696
|
+
__VITEST_TESTER_ID__: "\"none\"",
|
|
697
|
+
__VITEST_OTEL_CARRIER__: url.searchParams.get("otelCarrier") ?? "null",
|
|
698
|
+
__VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(browserProject.project.getProvidedContext())),
|
|
699
|
+
__VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
|
|
700
|
+
});
|
|
701
|
+
// disable CSP for the orchestrator as we are the ones controlling it
|
|
702
|
+
res.removeHeader("Content-Security-Policy");
|
|
703
|
+
if (!globalServer.orchestratorScripts) {
|
|
704
|
+
globalServer.orchestratorScripts = (await globalServer.formatScripts(globalServer.config.browser.orchestratorScripts)).map((script) => {
|
|
705
|
+
let html = "<script ";
|
|
706
|
+
for (const attr in script.attrs || {}) {
|
|
707
|
+
html += `${attr}="${script.attrs[attr]}" `;
|
|
708
|
+
}
|
|
709
|
+
html += `>${script.children}<\/script>`;
|
|
710
|
+
return html;
|
|
711
|
+
}).join("\n");
|
|
712
|
+
}
|
|
713
|
+
let baseHtml = typeof globalServer.orchestratorHtml === "string" ? globalServer.orchestratorHtml : await globalServer.orchestratorHtml;
|
|
714
|
+
// if UI is enabled, use UI HTML and inject the orchestrator script
|
|
715
|
+
if (globalServer.config.browser.ui) {
|
|
716
|
+
const manifestContent = globalServer.manifest instanceof Promise ? await globalServer.manifest : globalServer.manifest;
|
|
717
|
+
const jsEntry = manifestContent["orchestrator.html"].file;
|
|
718
|
+
const base = browserProject.parent.vite.config.base || "/";
|
|
719
|
+
baseHtml = baseHtml.replace("href=\"./favicon.ico\"", `href="${base}__vitest__/favicon.ico"`).replace("href=\"./favicon.svg\"", `href="${base}__vitest__/favicon.svg"`).replaceAll("./assets/", `${base}__vitest__/assets/`).replace("<!-- !LOAD_METADATA! -->", [
|
|
720
|
+
"{__VITEST_INJECTOR__}",
|
|
721
|
+
"{__VITEST_ERROR_CATCHER__}",
|
|
722
|
+
"{__VITEST_SCRIPTS__}",
|
|
723
|
+
`<script type="module" crossorigin src="${base}${jsEntry}"><\/script>`
|
|
724
|
+
].join("\n"));
|
|
725
|
+
}
|
|
726
|
+
return replacer(baseHtml, {
|
|
727
|
+
__VITEST_FAVICON__: globalServer.faviconUrl,
|
|
728
|
+
__VITEST_TITLE__: "Vitest Browser Runner",
|
|
729
|
+
__VITEST_SCRIPTS__: globalServer.orchestratorScripts,
|
|
730
|
+
__VITEST_INJECTOR__: `<script type="module">${injector}<\/script>`,
|
|
731
|
+
__VITEST_ERROR_CATCHER__: `<script type="module" src="${globalServer.errorCatcherUrl}"><\/script>`,
|
|
732
|
+
__VITEST_SESSION_ID__: JSON.stringify(sessionId)
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function disableCache(res) {
|
|
737
|
+
res.setHeader("Cache-Control", "no-cache, max-age=0, must-revalidate");
|
|
738
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
739
|
+
}
|
|
740
|
+
function allowIframes(res) {
|
|
741
|
+
// remove custom iframe related headers to allow the iframe to load
|
|
742
|
+
res.removeHeader("X-Frame-Options");
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function createOrchestratorMiddleware(parentServer) {
|
|
746
|
+
return async function vitestOrchestratorMiddleware(req, res, next) {
|
|
747
|
+
if (!req.url) {
|
|
748
|
+
return next();
|
|
749
|
+
}
|
|
750
|
+
const url = new URL(req.url, "http://localhost");
|
|
751
|
+
if (url.pathname !== parentServer.prefixOrchestratorUrl) {
|
|
752
|
+
return next();
|
|
753
|
+
}
|
|
754
|
+
const html = await resolveOrchestrator(parentServer, url, res);
|
|
755
|
+
if (html) {
|
|
756
|
+
disableCache(res);
|
|
757
|
+
allowIframes(res);
|
|
758
|
+
res.write(html, "utf-8");
|
|
759
|
+
res.end();
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
async function resolveTester(globalServer, url, res, next) {
|
|
765
|
+
const csp = res.getHeader("Content-Security-Policy");
|
|
766
|
+
if (typeof csp === "string") {
|
|
767
|
+
// add frame-ancestors to allow the iframe to be loaded by Vitest,
|
|
768
|
+
// but keep the rest of the CSP
|
|
769
|
+
res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
|
|
770
|
+
}
|
|
771
|
+
const sessionId = url.searchParams.get("sessionId") || "none";
|
|
772
|
+
const session = globalServer.vitest._browserSessions.getSession(sessionId);
|
|
773
|
+
if (!session) {
|
|
774
|
+
res.statusCode = 400;
|
|
775
|
+
res.end("Invalid session ID");
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const project = globalServer.vitest.getProjectByName(session.project.name || "");
|
|
779
|
+
const browserProject = project.browser || [...globalServer.children][0];
|
|
780
|
+
if (!browserProject) {
|
|
781
|
+
res.statusCode = 400;
|
|
782
|
+
res.end("Invalid session ID");
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
|
|
786
|
+
const injector = replacer(injectorJs, {
|
|
787
|
+
__VITEST_PROVIDER__: JSON.stringify(project.browser.provider.name),
|
|
788
|
+
__VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
|
|
789
|
+
__VITEST_VITE_CONFIG__: JSON.stringify({ root: browserProject.vite.config.root }),
|
|
790
|
+
__VITEST_TYPE__: "\"tester\"",
|
|
791
|
+
__VITEST_METHOD__: JSON.stringify("none"),
|
|
792
|
+
__VITEST_SESSION_ID__: JSON.stringify(sessionId),
|
|
793
|
+
__VITEST_OTEL_CARRIER__: JSON.stringify(null),
|
|
794
|
+
__VITEST_TESTER_ID__: JSON.stringify(crypto.randomUUID()),
|
|
795
|
+
__VITEST_PROVIDED_CONTEXT__: "{}",
|
|
796
|
+
__VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
|
|
797
|
+
});
|
|
798
|
+
const testerHtml = typeof browserProject.testerHtml === "string" ? browserProject.testerHtml : await browserProject.testerHtml;
|
|
799
|
+
try {
|
|
800
|
+
const url = join("/@fs/", browserProject.testerFilepath);
|
|
801
|
+
const indexhtml = await browserProject.vite.transformIndexHtml(url, testerHtml);
|
|
802
|
+
const html = replacer(indexhtml, {
|
|
803
|
+
__VITEST_FAVICON__: globalServer.faviconUrl,
|
|
804
|
+
__VITEST_INJECTOR__: injector
|
|
805
|
+
});
|
|
806
|
+
return html;
|
|
807
|
+
} catch (err) {
|
|
808
|
+
session.fail(err);
|
|
809
|
+
next(err);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function createTesterMiddleware(browserServer) {
|
|
814
|
+
return async function vitestTesterMiddleware(req, res, next) {
|
|
815
|
+
if (!req.url) {
|
|
816
|
+
return next();
|
|
817
|
+
}
|
|
818
|
+
const url = new URL(req.url, "http://localhost");
|
|
819
|
+
if (url.pathname !== browserServer.prefixTesterUrl || !url.searchParams.has("sessionId")) {
|
|
820
|
+
return next();
|
|
821
|
+
}
|
|
822
|
+
const html = await resolveTester(browserServer, url, res, next);
|
|
823
|
+
if (html) {
|
|
824
|
+
disableCache(res);
|
|
825
|
+
allowIframes(res);
|
|
826
|
+
res.write(html, "utf-8");
|
|
827
|
+
res.end();
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const VIRTUAL_ID_CONTEXT = "\0vitest/browser";
|
|
833
|
+
const ID_CONTEXT = "vitest/browser";
|
|
834
|
+
// for libraries that use an older import but are not type checked
|
|
835
|
+
const DEPRECATED_ID_CONTEXT = "@vitest/browser/context";
|
|
836
|
+
const DEPRECATED_VIRTUAL_ID_UTILS = "\0@vitest/browser/utils";
|
|
837
|
+
const DEPRECATED_ID_UTILS = "@vitest/browser/utils";
|
|
838
|
+
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
839
|
+
function BrowserContext(globalServer) {
|
|
840
|
+
return {
|
|
841
|
+
name: "vitest:browser:virtual-module:context",
|
|
842
|
+
enforce: "pre",
|
|
843
|
+
resolveId(id, importer) {
|
|
844
|
+
if (id === ID_CONTEXT || id === "@voidzero-dev/vite-plus-test/browser" || id === "vite-plus/test/browser") {
|
|
845
|
+
return VIRTUAL_ID_CONTEXT;
|
|
846
|
+
}
|
|
847
|
+
if (id === DEPRECATED_ID_CONTEXT) {
|
|
848
|
+
if (importer && !importer.includes("/node_modules/")) {
|
|
849
|
+
globalServer.vitest.logger.deprecate(`${importer} tries to load a deprecated "${id}" module. ` + `This import will stop working in the next major version. ` + `Please, use "vitest/browser" instead.`);
|
|
850
|
+
}
|
|
851
|
+
return VIRTUAL_ID_CONTEXT;
|
|
852
|
+
}
|
|
853
|
+
if (id === DEPRECATED_ID_UTILS) {
|
|
854
|
+
return DEPRECATED_VIRTUAL_ID_UTILS;
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
load(id) {
|
|
858
|
+
if (id === VIRTUAL_ID_CONTEXT) {
|
|
859
|
+
return generateContextFile.call(this, globalServer);
|
|
860
|
+
}
|
|
861
|
+
if (id === DEPRECATED_VIRTUAL_ID_UTILS) {
|
|
862
|
+
return `
|
|
863
|
+
import { utils } from 'vitest/browser'
|
|
864
|
+
export const getElementLocatorSelectors = utils.getElementLocatorSelectors
|
|
865
|
+
export const debug = utils.debug
|
|
866
|
+
export const prettyDOM = utils.prettyDOM
|
|
867
|
+
export const getElementError = utils.getElementError
|
|
868
|
+
`;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
async function generateContextFile(globalServer) {
|
|
874
|
+
const commands = Object.keys(globalServer.commands);
|
|
875
|
+
const provider = [...globalServer.children][0].provider;
|
|
876
|
+
const providerName = provider?.name || "preview";
|
|
877
|
+
const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
|
|
878
|
+
return ` ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`;
|
|
879
|
+
}).join("\n");
|
|
880
|
+
const userEventNonProviderImport = await getUserEventImport(provider, this.resolve.bind(this));
|
|
881
|
+
const distContextPath = slash$1(`/@fs/${resolve(__dirname$1, "context.js")}`);
|
|
882
|
+
return `
|
|
883
|
+
import { page, createUserEvent, cdp, locators, utils } from '${distContextPath}'
|
|
884
|
+
${userEventNonProviderImport}
|
|
885
|
+
|
|
886
|
+
export const server = {
|
|
887
|
+
platform: ${JSON.stringify(process.platform)},
|
|
888
|
+
version: ${JSON.stringify(process.version)},
|
|
889
|
+
provider: ${JSON.stringify(providerName)},
|
|
890
|
+
browser: __vitest_browser_runner__.config.browser.name,
|
|
891
|
+
commands: {
|
|
892
|
+
${commandsCode}
|
|
893
|
+
},
|
|
894
|
+
config: __vitest_browser_runner__.config,
|
|
895
|
+
}
|
|
896
|
+
export const commands = server.commands
|
|
897
|
+
export const userEvent = createUserEvent(_userEventSetup)
|
|
898
|
+
export { page, cdp, locators, utils }
|
|
899
|
+
`;
|
|
900
|
+
}
|
|
901
|
+
async function getUserEventImport(provider, resolve) {
|
|
902
|
+
if (!provider || provider.name !== "preview") {
|
|
903
|
+
return "const _userEventSetup = undefined";
|
|
904
|
+
}
|
|
905
|
+
const previewDistRoot = provider.distRoot;
|
|
906
|
+
const resolved = await resolve("@testing-library/user-event", previewDistRoot);
|
|
907
|
+
if (!resolved) {
|
|
908
|
+
throw new Error(`Failed to resolve user-event package from ${previewDistRoot}`);
|
|
909
|
+
}
|
|
910
|
+
return `\
|
|
911
|
+
import { userEvent as __vitest_user_event__ } from '${slash$1(`/@fs/${resolved.id}`)}'
|
|
912
|
+
const _userEventSetup = __vitest_user_event__
|
|
913
|
+
`;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const versionRegexp = /(?:\?|&)v=\w{8}/;
|
|
917
|
+
var BrowserPlugin = (parentServer, base = "/") => {
|
|
918
|
+
function isPackageExists(pkg, root) {
|
|
919
|
+
return parentServer.vitest.packageInstaller.isPackageExists?.(pkg, { paths: [root] });
|
|
920
|
+
}
|
|
921
|
+
return [
|
|
922
|
+
{
|
|
923
|
+
name: 'vitest:vendor-aliases',
|
|
924
|
+
enforce: 'pre',
|
|
925
|
+
resolveId(id) {
|
|
926
|
+
// distRoot is @vitest/browser/, packageRoot is the actual dist/ directory
|
|
927
|
+
const packageRoot = resolve(distRoot, '../..');
|
|
928
|
+
// Resolve module-runner to a browser-safe stub
|
|
929
|
+
// This is critical: module-runner contains Node.js-only code (process.platform, etc.)
|
|
930
|
+
// that causes browsers to hang when loaded
|
|
931
|
+
if (id === '@voidzero-dev/vite-plus-core/module-runner' || id === 'vite/module-runner') {
|
|
932
|
+
return resolve(packageRoot, 'module-runner-stub.js');
|
|
933
|
+
}
|
|
934
|
+
// Mark vite/core as external to prevent Node.js-only code from being bundled
|
|
935
|
+
// This prevents __vite__injectQuery duplication errors in browser tests
|
|
936
|
+
if (id === '@voidzero-dev/vite-plus-core' || id === 'vite') {
|
|
937
|
+
return { id, external: true };
|
|
938
|
+
}
|
|
939
|
+
// Handle vitest/browser and package aliases
|
|
940
|
+
// Return virtual module ID so BrowserContext plugin can load it
|
|
941
|
+
// Supports: vitest/browser, @voidzero-dev/vite-plus-test/browser, vite-plus/test/browser
|
|
942
|
+
if (id === 'vitest/browser' || id === '@voidzero-dev/vite-plus-test/browser' || id === 'vite-plus/test/browser') {
|
|
943
|
+
return '\0vitest/browser';
|
|
944
|
+
}
|
|
945
|
+
// Handle vitest/* subpaths (resolve to our dist files)
|
|
946
|
+
// Also handle @voidzero-dev package aliases that resolve to the same files
|
|
947
|
+
const vitestSubpathMap = {
|
|
948
|
+
'vitest': resolve(packageRoot, 'index.js'),
|
|
949
|
+
'@voidzero-dev/vite-plus-test': resolve(packageRoot, 'index.js'),
|
|
950
|
+
'vite-plus/test': resolve(packageRoot, 'index.js'),
|
|
951
|
+
'vitest/node': resolve(packageRoot, 'node.js'),
|
|
952
|
+
'vitest/config': resolve(packageRoot, 'config.js'),
|
|
953
|
+
'vitest/internal/browser': resolve(packageRoot, 'browser.js'),
|
|
954
|
+
'vitest/runners': resolve(packageRoot, 'runners.js'),
|
|
955
|
+
'vitest/suite': resolve(packageRoot, 'suite.js'),
|
|
956
|
+
'vitest/environments': resolve(packageRoot, 'environments.js'),
|
|
957
|
+
'vitest/coverage': resolve(packageRoot, 'coverage.js'),
|
|
958
|
+
'vitest/reporters': resolve(packageRoot, 'reporters.js'),
|
|
959
|
+
'vitest/snapshot': resolve(packageRoot, 'snapshot.js'),
|
|
960
|
+
'vitest/mocker': resolve(packageRoot, 'mocker.js'),
|
|
961
|
+
// Browser providers - resolve to our bundled @vitest/browser-* packages
|
|
962
|
+
'vitest/browser-playwright': resolve(packageRoot, '@vitest/browser-playwright/index.js'),
|
|
963
|
+
'vitest/browser-webdriverio': resolve(packageRoot, '@vitest/browser-webdriverio/index.js'),
|
|
964
|
+
'vitest/browser-preview': resolve(packageRoot, '@vitest/browser-preview/index.js'),
|
|
965
|
+
};
|
|
966
|
+
if (vitestSubpathMap[id]) {
|
|
967
|
+
return vitestSubpathMap[id];
|
|
968
|
+
}
|
|
969
|
+
// Handle @voidzero-dev/vite-plus-test/* subpaths (same as vitest/*)
|
|
970
|
+
if (id.startsWith('@voidzero-dev/vite-plus-test/')) {
|
|
971
|
+
const subpath = id.slice('@voidzero-dev/vite-plus-test/'.length);
|
|
972
|
+
const vitestEquiv = 'vitest/' + subpath;
|
|
973
|
+
if (vitestSubpathMap[vitestEquiv]) {
|
|
974
|
+
return vitestSubpathMap[vitestEquiv];
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
// Handle vite-plus/test/* subpaths (CLI package paths, same as vitest/*)
|
|
978
|
+
if (id.startsWith('vite-plus/test/')) {
|
|
979
|
+
const subpath = id.slice('vite-plus/test/'.length);
|
|
980
|
+
const vitestEquiv = 'vitest/' + subpath;
|
|
981
|
+
if (vitestSubpathMap[vitestEquiv]) {
|
|
982
|
+
return vitestSubpathMap[vitestEquiv];
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
// Handle @vitest/* packages (resolve to our copied files)
|
|
986
|
+
const vendorMap = {
|
|
987
|
+
'@vitest/runner': resolve(distRoot, '@vitest/runner/index.js'),
|
|
988
|
+
'@vitest/runner/utils': resolve(distRoot, '@vitest/runner/utils.js'),
|
|
989
|
+
'@vitest/runner/types': resolve(distRoot, '@vitest/runner/types.js'),
|
|
990
|
+
'@vitest/utils': resolve(distRoot, '@vitest/utils/index.js'),
|
|
991
|
+
'@vitest/utils/source-map': resolve(distRoot, '@vitest/utils/source-map.js'),
|
|
992
|
+
'@vitest/utils/error': resolve(distRoot, '@vitest/utils/error.js'),
|
|
993
|
+
'@vitest/utils/helpers': resolve(distRoot, '@vitest/utils/helpers.js'),
|
|
994
|
+
'@vitest/utils/display': resolve(distRoot, '@vitest/utils/display.js'),
|
|
995
|
+
'@vitest/utils/timers': resolve(distRoot, '@vitest/utils/timers.js'),
|
|
996
|
+
'@vitest/utils/highlight': resolve(distRoot, '@vitest/utils/highlight.js'),
|
|
997
|
+
'@vitest/utils/offset': resolve(distRoot, '@vitest/utils/offset.js'),
|
|
998
|
+
'@vitest/utils/resolver': resolve(distRoot, '@vitest/utils/resolver.js'),
|
|
999
|
+
'@vitest/utils/serialize': resolve(distRoot, '@vitest/utils/serialize.js'),
|
|
1000
|
+
'@vitest/utils/constants': resolve(distRoot, '@vitest/utils/constants.js'),
|
|
1001
|
+
'@vitest/utils/diff': resolve(distRoot, '@vitest/utils/diff.js'),
|
|
1002
|
+
'@vitest/spy': resolve(distRoot, '@vitest/spy/index.js'),
|
|
1003
|
+
'@vitest/expect': resolve(distRoot, '@vitest/expect/index.js'),
|
|
1004
|
+
'@vitest/snapshot': resolve(distRoot, '@vitest/snapshot/index.js'),
|
|
1005
|
+
'@vitest/snapshot/environment': resolve(distRoot, '@vitest/snapshot/environment.js'),
|
|
1006
|
+
'@vitest/snapshot/manager': resolve(distRoot, '@vitest/snapshot/manager.js'),
|
|
1007
|
+
'@vitest/mocker': resolve(distRoot, '@vitest/mocker/index.js'),
|
|
1008
|
+
'@vitest/mocker/node': resolve(distRoot, '@vitest/mocker/node.js'),
|
|
1009
|
+
'@vitest/mocker/browser': resolve(distRoot, '@vitest/mocker/browser.js'),
|
|
1010
|
+
'@vitest/mocker/redirect': resolve(distRoot, '@vitest/mocker/redirect.js'),
|
|
1011
|
+
'@vitest/mocker/automock': resolve(distRoot, '@vitest/mocker/automock.js'),
|
|
1012
|
+
'@vitest/mocker/register': resolve(distRoot, '@vitest/mocker/register.js'),
|
|
1013
|
+
'@vitest/pretty-format': resolve(distRoot, '@vitest/pretty-format/index.js'),
|
|
1014
|
+
'@vitest/browser': resolve(distRoot, '@vitest/browser/index.js'),
|
|
1015
|
+
'@vitest/browser/context': resolve(distRoot, '@vitest/browser/context.js'),
|
|
1016
|
+
'@vitest/browser/client': resolve(distRoot, '@vitest/browser/client.js'),
|
|
1017
|
+
'@vitest/browser/locators': resolve(distRoot, '@vitest/browser/locators.js'),
|
|
1018
|
+
'@vitest/browser-playwright': resolve(distRoot, '@vitest/browser-playwright/index.js'),
|
|
1019
|
+
'@vitest/browser-webdriverio': resolve(distRoot, '@vitest/browser-webdriverio/index.js'),
|
|
1020
|
+
'@vitest/browser-preview': resolve(distRoot, '@vitest/browser-preview/index.js')
|
|
1021
|
+
};
|
|
1022
|
+
if (vendorMap[id]) {
|
|
1023
|
+
return vendorMap[id];
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
enforce: "pre",
|
|
1029
|
+
name: "vitest:browser",
|
|
1030
|
+
async configureServer(server) {
|
|
1031
|
+
parentServer.setServer(server);
|
|
1032
|
+
// eslint-disable-next-line prefer-arrow-callback
|
|
1033
|
+
server.middlewares.use(function vitestHeaders(_req, res, next) {
|
|
1034
|
+
const headers = server.config.server.headers;
|
|
1035
|
+
if (headers) {
|
|
1036
|
+
for (const name in headers) {
|
|
1037
|
+
res.setHeader(name, headers[name]);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
next();
|
|
1041
|
+
});
|
|
1042
|
+
server.middlewares.use(createOrchestratorMiddleware(parentServer));
|
|
1043
|
+
server.middlewares.use(createTesterMiddleware(parentServer));
|
|
1044
|
+
server.middlewares.use(`${base}favicon.svg`, (_, res) => {
|
|
1045
|
+
const content = readFileSync(resolve(distRoot, "client/favicon.svg"));
|
|
1046
|
+
res.write(content, "utf-8");
|
|
1047
|
+
res.end();
|
|
1048
|
+
});
|
|
1049
|
+
const coverageFolder = resolveCoverageFolder(parentServer.vitest);
|
|
1050
|
+
const coveragePath = coverageFolder ? coverageFolder[1] : undefined;
|
|
1051
|
+
if (coveragePath && base === coveragePath) {
|
|
1052
|
+
throw new Error(`The ui base path and the coverage path cannot be the same: ${base}, change coverage.reportsDirectory`);
|
|
1053
|
+
}
|
|
1054
|
+
if (coverageFolder) {
|
|
1055
|
+
server.middlewares.use(coveragePath, sirv(coverageFolder[0], {
|
|
1056
|
+
single: true,
|
|
1057
|
+
dev: true,
|
|
1058
|
+
setHeaders: (res) => {
|
|
1059
|
+
const csp = res.getHeader("Content-Security-Policy");
|
|
1060
|
+
if (typeof csp === "string") {
|
|
1061
|
+
// add frame-ancestors to allow the iframe to be loaded by Vitest,
|
|
1062
|
+
// but keep the rest of the CSP
|
|
1063
|
+
res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
|
|
1064
|
+
}
|
|
1065
|
+
res.setHeader("Cache-Control", "public,max-age=0,must-revalidate");
|
|
1066
|
+
}
|
|
1067
|
+
}));
|
|
1068
|
+
}
|
|
1069
|
+
const uiEnabled = parentServer.config.browser.ui;
|
|
1070
|
+
if (uiEnabled) {
|
|
1071
|
+
// eslint-disable-next-line prefer-arrow-callback
|
|
1072
|
+
server.middlewares.use(`${base}__screenshot-error`, function vitestBrowserScreenshotError(req, res) {
|
|
1073
|
+
if (!req.url) {
|
|
1074
|
+
res.statusCode = 404;
|
|
1075
|
+
res.end();
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const url = new URL(req.url, "http://localhost");
|
|
1079
|
+
const id = url.searchParams.get("id");
|
|
1080
|
+
if (!id) {
|
|
1081
|
+
res.statusCode = 404;
|
|
1082
|
+
res.end();
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
const task = parentServer.vitest.state.idMap.get(id);
|
|
1086
|
+
const file = task?.meta.failScreenshotPath;
|
|
1087
|
+
if (!file) {
|
|
1088
|
+
res.statusCode = 404;
|
|
1089
|
+
res.end();
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
let stat;
|
|
1093
|
+
try {
|
|
1094
|
+
stat = lstatSync(file);
|
|
1095
|
+
} catch {}
|
|
1096
|
+
if (!stat?.isFile()) {
|
|
1097
|
+
res.statusCode = 404;
|
|
1098
|
+
res.end();
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const ext = extname(file);
|
|
1102
|
+
const buffer = readFileSync(file);
|
|
1103
|
+
res.setHeader("Cache-Control", "public,max-age=0,must-revalidate");
|
|
1104
|
+
res.setHeader("Content-Length", buffer.length);
|
|
1105
|
+
res.setHeader("Content-Type", ext === "jpeg" || ext === "jpg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png");
|
|
1106
|
+
res.end(buffer);
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
server.middlewares.use((req, res, next) => {
|
|
1110
|
+
// 9000 mega head move
|
|
1111
|
+
// Vite always caches optimized dependencies, but users might mock
|
|
1112
|
+
// them in _some_ tests, while keeping original modules in others
|
|
1113
|
+
// there is no way to configure that in Vite, so we patch it here
|
|
1114
|
+
// to always ignore the cache-control set by Vite in the next middleware
|
|
1115
|
+
if (req.url && versionRegexp.test(req.url) && !req.url.includes("chunk-")) {
|
|
1116
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1117
|
+
const setHeader = res.setHeader.bind(res);
|
|
1118
|
+
res.setHeader = function(name, value) {
|
|
1119
|
+
if (name === "Cache-Control") {
|
|
1120
|
+
return res;
|
|
1121
|
+
}
|
|
1122
|
+
return setHeader(name, value);
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
next();
|
|
1126
|
+
});
|
|
1127
|
+
// handle attachments the same way as in packages/ui/node/index.ts
|
|
1128
|
+
server.middlewares.use((req, res, next) => {
|
|
1129
|
+
if (!req.url) {
|
|
1130
|
+
return next();
|
|
1131
|
+
}
|
|
1132
|
+
const url = new URL(req.url, "http://localhost");
|
|
1133
|
+
if (url.pathname !== "/__vitest_attachment__") {
|
|
1134
|
+
return next();
|
|
1135
|
+
}
|
|
1136
|
+
const path = url.searchParams.get("path");
|
|
1137
|
+
const contentType = url.searchParams.get("contentType");
|
|
1138
|
+
if (!isValidApiRequest(parentServer.config, req) || !contentType || !path) {
|
|
1139
|
+
return next();
|
|
1140
|
+
}
|
|
1141
|
+
const fsPath = decodeURIComponent(path);
|
|
1142
|
+
if (!isFileServingAllowed(parentServer.vite.config, fsPath)) {
|
|
1143
|
+
return next();
|
|
1144
|
+
}
|
|
1145
|
+
try {
|
|
1146
|
+
res.setHeader("content-type", contentType);
|
|
1147
|
+
return createReadStream(fsPath).pipe(res).on("close", () => res.end());
|
|
1148
|
+
} catch (err) {
|
|
1149
|
+
return next(err);
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
name: "vitest:browser:tests",
|
|
1156
|
+
enforce: "pre",
|
|
1157
|
+
async config() {
|
|
1158
|
+
// this plugin can be used in different projects, but all of them
|
|
1159
|
+
// have the same `include` pattern, so it doesn't matter which project we use
|
|
1160
|
+
const project = parentServer.project;
|
|
1161
|
+
const { testFiles: browserTestFiles } = await project.globTestFiles();
|
|
1162
|
+
const setupFiles = toArray(project.config.setupFiles);
|
|
1163
|
+
// replace env values - cannot be reassign at runtime
|
|
1164
|
+
const define = {};
|
|
1165
|
+
for (const env in project.config.env || {}) {
|
|
1166
|
+
const stringValue = JSON.stringify(project.config.env[env]);
|
|
1167
|
+
define[`import.meta.env.${env}`] = stringValue;
|
|
1168
|
+
}
|
|
1169
|
+
const entries = [
|
|
1170
|
+
...browserTestFiles,
|
|
1171
|
+
...setupFiles,
|
|
1172
|
+
resolve(distDir, "index.js"),
|
|
1173
|
+
resolve(distDir, "browser.js"),
|
|
1174
|
+
resolve(distDir, "runners.js"),
|
|
1175
|
+
resolve(distDir, "utils.js"),
|
|
1176
|
+
...project.config.snapshotSerializers || []
|
|
1177
|
+
];
|
|
1178
|
+
const exclude = [
|
|
1179
|
+
"@vitest/browser",
|
|
1180
|
+
"@vitest/ui",
|
|
1181
|
+
"@vitest/ui/reporter",
|
|
1182
|
+
"@vitest/mocker/node",
|
|
1183
|
+
"@voidzero-dev/vite-plus-test",
|
|
1184
|
+
"@voidzero-dev/vite-plus-test/browser",
|
|
1185
|
+
"@voidzero-dev/vite-plus-test/browser/context",
|
|
1186
|
+
"vite-plus/test",
|
|
1187
|
+
"vite-plus/test/browser",
|
|
1188
|
+
"vite-plus/test/browser/context",
|
|
1189
|
+
"vite",
|
|
1190
|
+
"@voidzero-dev/vite-plus-core",
|
|
1191
|
+
"@voidzero-dev/vite-plus-core/module-runner",
|
|
1192
|
+
"lightningcss",
|
|
1193
|
+
"@tailwindcss/oxide",
|
|
1194
|
+
"tailwindcss",
|
|
1195
|
+
"vitest",
|
|
1196
|
+
"vitest/browser",
|
|
1197
|
+
"vitest/internal/browser",
|
|
1198
|
+
"vitest/runners",
|
|
1199
|
+
"vite/module-runner",
|
|
1200
|
+
"@vitest/browser/utils",
|
|
1201
|
+
"@vitest/browser/context",
|
|
1202
|
+
"@vitest/browser/client",
|
|
1203
|
+
"@vitest/utils",
|
|
1204
|
+
"@vitest/utils/source-map",
|
|
1205
|
+
"@vitest/runner",
|
|
1206
|
+
"@vitest/spy",
|
|
1207
|
+
"@vitest/utils/error",
|
|
1208
|
+
"@vitest/snapshot",
|
|
1209
|
+
"@vitest/expect",
|
|
1210
|
+
"std-env",
|
|
1211
|
+
"tinybench",
|
|
1212
|
+
"tinyspy",
|
|
1213
|
+
"tinyrainbow",
|
|
1214
|
+
"pathe",
|
|
1215
|
+
"msw",
|
|
1216
|
+
"msw/browser"
|
|
1217
|
+
];
|
|
1218
|
+
if (typeof project.config.diff === "string") {
|
|
1219
|
+
entries.push(project.config.diff);
|
|
1220
|
+
}
|
|
1221
|
+
if (parentServer.vitest.coverageProvider) {
|
|
1222
|
+
const coverage = parentServer.vitest._coverageOptions;
|
|
1223
|
+
const provider = coverage.provider;
|
|
1224
|
+
if (provider === "v8") {
|
|
1225
|
+
const path = tryResolve("@vitest/coverage-v8", [parentServer.config.root]);
|
|
1226
|
+
if (path) {
|
|
1227
|
+
entries.push(path);
|
|
1228
|
+
exclude.push("@vitest/coverage-v8/browser");
|
|
1229
|
+
}
|
|
1230
|
+
} else if (provider === "istanbul") {
|
|
1231
|
+
const path = tryResolve("@vitest/coverage-istanbul", [parentServer.config.root]);
|
|
1232
|
+
if (path) {
|
|
1233
|
+
entries.push(path);
|
|
1234
|
+
exclude.push("@vitest/coverage-istanbul");
|
|
1235
|
+
}
|
|
1236
|
+
} else if (provider === "custom" && coverage.customProviderModule) {
|
|
1237
|
+
entries.push(coverage.customProviderModule);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
const include = [
|
|
1241
|
+
];
|
|
1242
|
+
const provider = parentServer.config.browser.provider || [...parentServer.children][0]?.provider;
|
|
1243
|
+
if (provider?.name === "preview") {
|
|
1244
|
+
include.push("@vitest/browser-preview > @testing-library/user-event", "@vitest/browser-preview > @testing-library/dom");
|
|
1245
|
+
}
|
|
1246
|
+
const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root;
|
|
1247
|
+
const svelte = isPackageExists("vitest-browser-svelte", fileRoot);
|
|
1248
|
+
if (svelte) {
|
|
1249
|
+
exclude.push("vitest-browser-svelte");
|
|
1250
|
+
}
|
|
1251
|
+
// since we override the resolution in the esbuild plugin, Vite can no longer optimizer it
|
|
1252
|
+
const vue = isPackageExists("vitest-browser-vue", fileRoot);
|
|
1253
|
+
if (vue) {
|
|
1254
|
+
// we override them in the esbuild plugin so optimizer can no longer intercept it
|
|
1255
|
+
include.push("vitest-browser-vue", "vitest-browser-vue > @vue/test-utils", "vitest-browser-vue > @vue/test-utils > @vue/compiler-core");
|
|
1256
|
+
}
|
|
1257
|
+
const vueTestUtils = isPackageExists("@vue/test-utils", fileRoot);
|
|
1258
|
+
if (vueTestUtils) {
|
|
1259
|
+
include.push("@vue/test-utils");
|
|
1260
|
+
}
|
|
1261
|
+
const otelConfig = project.config.experimental.openTelemetry;
|
|
1262
|
+
if (otelConfig?.enabled && otelConfig.browserSdkPath) {
|
|
1263
|
+
entries.push(otelConfig.browserSdkPath);
|
|
1264
|
+
include.push("@opentelemetry/api");
|
|
1265
|
+
}
|
|
1266
|
+
return {
|
|
1267
|
+
define,
|
|
1268
|
+
resolve: { dedupe: ["vitest"] },
|
|
1269
|
+
optimizeDeps: {
|
|
1270
|
+
entries,
|
|
1271
|
+
exclude,
|
|
1272
|
+
include
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
},
|
|
1276
|
+
async resolveId(id) {
|
|
1277
|
+
if (!/\?browserv=\w+$/.test(id)) {
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
let useId = id.slice(0, id.lastIndexOf("?"));
|
|
1281
|
+
if (useId.startsWith("/@fs/")) {
|
|
1282
|
+
useId = useId.slice(5);
|
|
1283
|
+
}
|
|
1284
|
+
if (/^\w:/.test(useId)) {
|
|
1285
|
+
useId = useId.replace(/\\/g, "/");
|
|
1286
|
+
}
|
|
1287
|
+
return useId;
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
name: "vitest:browser:resolve-virtual",
|
|
1292
|
+
async resolveId(rawId) {
|
|
1293
|
+
if (rawId === "/mockServiceWorker.js") {
|
|
1294
|
+
return this.resolve("msw/mockServiceWorker.js", distRoot, { skipSelf: true });
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
name: "vitest:browser:assets",
|
|
1300
|
+
configureServer(server) {
|
|
1301
|
+
server.middlewares.use("/__vitest__", sirv(resolve(distRoot, "client/__vitest__")));
|
|
1302
|
+
},
|
|
1303
|
+
resolveId(id) {
|
|
1304
|
+
if (id.startsWith("/__vitest_browser__/")) {
|
|
1305
|
+
return resolve(distRoot, "client", id.slice(1));
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
transform(code, id) {
|
|
1309
|
+
if (id.includes(parentServer.vite.config.cacheDir) && id.includes("loupe.js")) {
|
|
1310
|
+
// loupe bundle has a nastry require('util') call that leaves a warning in the console
|
|
1311
|
+
const utilRequire = "nodeUtil = require_util();";
|
|
1312
|
+
return code.replace(utilRequire, " ".repeat(utilRequire.length));
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
BrowserContext(parentServer),
|
|
1317
|
+
dynamicImportPlugin({
|
|
1318
|
+
globalThisAccessor: "\"__vitest_browser_runner__\"",
|
|
1319
|
+
filter(id) {
|
|
1320
|
+
if (id.includes(distRoot)) {
|
|
1321
|
+
return false;
|
|
1322
|
+
}
|
|
1323
|
+
return true;
|
|
1324
|
+
}
|
|
1325
|
+
}),
|
|
1326
|
+
{
|
|
1327
|
+
name: "vitest:browser:config",
|
|
1328
|
+
enforce: "post",
|
|
1329
|
+
async config(viteConfig) {
|
|
1330
|
+
// Enables using ignore hint for coverage providers with @preserve keyword
|
|
1331
|
+
if (viteConfig.esbuild !== false) {
|
|
1332
|
+
viteConfig.esbuild ||= {};
|
|
1333
|
+
viteConfig.esbuild.legalComments = "inline";
|
|
1334
|
+
}
|
|
1335
|
+
const defaultPort = parentServer.vitest.state._data.browserLastPort++;
|
|
1336
|
+
const api = resolveApiServerConfig(viteConfig.test?.browser || {}, defaultPort);
|
|
1337
|
+
viteConfig.server = {
|
|
1338
|
+
...viteConfig.server,
|
|
1339
|
+
port: defaultPort,
|
|
1340
|
+
...api,
|
|
1341
|
+
middlewareMode: false,
|
|
1342
|
+
open: false
|
|
1343
|
+
};
|
|
1344
|
+
viteConfig.server.fs ??= {};
|
|
1345
|
+
viteConfig.server.fs.allow = viteConfig.server.fs.allow || [];
|
|
1346
|
+
viteConfig.server.fs.allow.push(...resolveFsAllow(parentServer.vitest.config.root, parentServer.vitest.vite.config.configFile), distRoot);
|
|
1347
|
+
return { resolve: { alias: viteConfig.test?.alias } };
|
|
1348
|
+
}
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
name: "vitest:browser:in-source-tests",
|
|
1352
|
+
transform: {
|
|
1353
|
+
filter: { code: /import\.meta\.vitest/ },
|
|
1354
|
+
handler(code, id) {
|
|
1355
|
+
const filename = cleanUrl(id);
|
|
1356
|
+
if (!code.includes("import.meta.vitest")) {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
const s = new MagicString(code, { filename });
|
|
1360
|
+
s.prepend(`Object.defineProperty(import.meta, 'vitest', { get() { return typeof __vitest_worker__ !== 'undefined' && __vitest_worker__.filepath === "${filename.replace(/"/g, "\\\"")}" ? __vitest_index__ : undefined } });\n`);
|
|
1361
|
+
return {
|
|
1362
|
+
code: s.toString(),
|
|
1363
|
+
map: s.generateMap({ hires: true })
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
name: "vitest:browser:worker",
|
|
1370
|
+
transform(code, id, _options) {
|
|
1371
|
+
// https://github.com/vitejs/vite/blob/ba56cf43b5480f8519349f7d7fe60718e9af5f1a/packages/vite/src/node/plugins/worker.ts#L46
|
|
1372
|
+
if (/(?:\?|&)worker_file&type=\w+(?:&|$)/.test(id)) {
|
|
1373
|
+
const s = new MagicString(code);
|
|
1374
|
+
s.prepend("globalThis.__vitest_browser_runner__ = { wrapDynamicImport: f => f() };\n");
|
|
1375
|
+
return {
|
|
1376
|
+
code: s.toString(),
|
|
1377
|
+
map: s.generateMap({ hires: "boundary" })
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
name: "vitest:browser:transform-tester-html",
|
|
1384
|
+
enforce: "pre",
|
|
1385
|
+
async transformIndexHtml(html, ctx) {
|
|
1386
|
+
const projectBrowser = [...parentServer.children].find((server) => {
|
|
1387
|
+
return ctx.filename === server.testerFilepath;
|
|
1388
|
+
});
|
|
1389
|
+
if (!projectBrowser) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
const stateJs = typeof parentServer.stateJs === "string" ? parentServer.stateJs : await parentServer.stateJs;
|
|
1393
|
+
const testerTags = [];
|
|
1394
|
+
const isDefaultTemplate = resolve(distRoot, "client/tester/tester.html") === projectBrowser.testerFilepath;
|
|
1395
|
+
if (!isDefaultTemplate) {
|
|
1396
|
+
const manifestContent = parentServer.manifest instanceof Promise ? await parentServer.manifest : parentServer.manifest;
|
|
1397
|
+
const testerEntry = manifestContent["tester/tester.html"];
|
|
1398
|
+
testerTags.push({
|
|
1399
|
+
tag: "script",
|
|
1400
|
+
attrs: {
|
|
1401
|
+
type: "module",
|
|
1402
|
+
crossorigin: "",
|
|
1403
|
+
src: `${parentServer.base}${testerEntry.file}`
|
|
1404
|
+
},
|
|
1405
|
+
injectTo: "head"
|
|
1406
|
+
});
|
|
1407
|
+
for (const importName of testerEntry.imports || []) {
|
|
1408
|
+
const entryManifest = manifestContent[importName];
|
|
1409
|
+
if (entryManifest) {
|
|
1410
|
+
testerTags.push({
|
|
1411
|
+
tag: "link",
|
|
1412
|
+
attrs: {
|
|
1413
|
+
href: `${parentServer.base}${entryManifest.file}`,
|
|
1414
|
+
rel: "modulepreload",
|
|
1415
|
+
crossorigin: ""
|
|
1416
|
+
},
|
|
1417
|
+
injectTo: "head"
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
} else {
|
|
1422
|
+
// inject the reset style only in the default template,
|
|
1423
|
+
// allowing users to customize the style in their own template
|
|
1424
|
+
testerTags.push({
|
|
1425
|
+
tag: "style",
|
|
1426
|
+
children: `
|
|
1427
|
+
html {
|
|
1428
|
+
padding: 0;
|
|
1429
|
+
margin: 0;
|
|
1430
|
+
}
|
|
1431
|
+
body {
|
|
1432
|
+
padding: 0;
|
|
1433
|
+
margin: 0;
|
|
1434
|
+
min-height: 100vh;
|
|
1435
|
+
}`,
|
|
1436
|
+
injectTo: "head"
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
return [
|
|
1440
|
+
{
|
|
1441
|
+
tag: "script",
|
|
1442
|
+
children: "{__VITEST_INJECTOR__}",
|
|
1443
|
+
injectTo: "head-prepend"
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
tag: "script",
|
|
1447
|
+
children: stateJs,
|
|
1448
|
+
injectTo: "head-prepend"
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
tag: "script",
|
|
1452
|
+
attrs: {
|
|
1453
|
+
type: "module",
|
|
1454
|
+
src: parentServer.errorCatcherUrl
|
|
1455
|
+
},
|
|
1456
|
+
injectTo: "head"
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
tag: "script",
|
|
1460
|
+
attrs: {
|
|
1461
|
+
type: "module",
|
|
1462
|
+
src: parentServer.matchersUrl
|
|
1463
|
+
},
|
|
1464
|
+
injectTo: "head"
|
|
1465
|
+
},
|
|
1466
|
+
...parentServer.initScripts.map((script) => ({
|
|
1467
|
+
tag: "script",
|
|
1468
|
+
attrs: {
|
|
1469
|
+
type: "module",
|
|
1470
|
+
src: join("/@fs/", script)
|
|
1471
|
+
},
|
|
1472
|
+
injectTo: "head"
|
|
1473
|
+
})),
|
|
1474
|
+
...testerTags
|
|
1475
|
+
].filter((s) => s != null);
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
{
|
|
1479
|
+
name: "vitest:browser:support-testing-library",
|
|
1480
|
+
enforce: "pre",
|
|
1481
|
+
config() {
|
|
1482
|
+
const rolldownPlugin = {
|
|
1483
|
+
name: "vue-test-utils-rewrite",
|
|
1484
|
+
resolveId: {
|
|
1485
|
+
filter: { id: /^@vue\/(test-utils|compiler-core)$/ },
|
|
1486
|
+
handler(source, importer) {
|
|
1487
|
+
const resolved = getRequire().resolve(source, { paths: [importer] });
|
|
1488
|
+
return resolved;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
const esbuildPlugin = {
|
|
1493
|
+
name: "test-utils-rewrite",
|
|
1494
|
+
setup(build) {
|
|
1495
|
+
// test-utils: resolve to CJS instead of the browser because the browser version expects a global Vue object
|
|
1496
|
+
// compiler-core: only CJS version allows slots as strings
|
|
1497
|
+
build.onResolve({ filter: /^@vue\/(test-utils|compiler-core)$/ }, (args) => {
|
|
1498
|
+
const resolved = getRequire().resolve(args.path, { paths: [args.importer] });
|
|
1499
|
+
return { path: resolved };
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
return { optimizeDeps: rolldownVersion ? { rolldownOptions: { plugins: [rolldownPlugin] } } : { esbuildOptions: { plugins: [esbuildPlugin] } } };
|
|
1504
|
+
}
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
name: "vitest:browser:__vitest_browser_import_meta_env_init__",
|
|
1508
|
+
transform: { handler(code) {
|
|
1509
|
+
// this transform runs after `vitest:meta-env-replacer` so that
|
|
1510
|
+
// `import.meta.env` will be handled by Vite import analysis to match behavior.
|
|
1511
|
+
if (code.includes("__vitest_browser_import_meta_env_init__")) {
|
|
1512
|
+
return code.replace("__vitest_browser_import_meta_env_init__", "import.meta.env");
|
|
1513
|
+
}
|
|
1514
|
+
} }
|
|
1515
|
+
}
|
|
1516
|
+
];
|
|
1517
|
+
};
|
|
1518
|
+
function tryResolve(path, paths) {
|
|
1519
|
+
try {
|
|
1520
|
+
const _require = getRequire();
|
|
1521
|
+
return _require.resolve(path, { paths });
|
|
1522
|
+
} catch {
|
|
1523
|
+
return undefined;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
let _require;
|
|
1527
|
+
function getRequire() {
|
|
1528
|
+
if (!_require) {
|
|
1529
|
+
_require = createRequire(import.meta.url);
|
|
1530
|
+
}
|
|
1531
|
+
return _require;
|
|
1532
|
+
}
|
|
1533
|
+
function resolveCoverageFolder(vitest) {
|
|
1534
|
+
const options = vitest.config;
|
|
1535
|
+
const coverageOptions = vitest._coverageOptions;
|
|
1536
|
+
const htmlReporter = coverageOptions?.enabled ? toArray(options.coverage.reporter).find((reporter) => {
|
|
1537
|
+
if (typeof reporter === "string") {
|
|
1538
|
+
return reporter === "html";
|
|
1539
|
+
}
|
|
1540
|
+
return reporter[0] === "html";
|
|
1541
|
+
}) : undefined;
|
|
1542
|
+
if (!htmlReporter) {
|
|
1543
|
+
return undefined;
|
|
1544
|
+
}
|
|
1545
|
+
// reportsDirectory not resolved yet
|
|
1546
|
+
const root = resolve(options.root || process.cwd(), coverageOptions.reportsDirectory || coverageConfigDefaults.reportsDirectory);
|
|
1547
|
+
const subdir = Array.isArray(htmlReporter) && htmlReporter.length > 1 && "subdir" in htmlReporter[1] ? htmlReporter[1].subdir : undefined;
|
|
1548
|
+
if (!subdir || typeof subdir !== "string") {
|
|
1549
|
+
return [root, `/${basename(root)}/`];
|
|
1550
|
+
}
|
|
1551
|
+
return [resolve(root, subdir), `/${basename(root)}/${subdir}/`];
|
|
1552
|
+
}
|
|
1553
|
+
const postfixRE = /[?#].*$/;
|
|
1554
|
+
function cleanUrl(url) {
|
|
1555
|
+
return url.replace(postfixRE, "");
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
class BrowserServerCDPHandler {
|
|
1559
|
+
listenerIds = {};
|
|
1560
|
+
listeners = {};
|
|
1561
|
+
constructor(session, tester) {
|
|
1562
|
+
this.session = session;
|
|
1563
|
+
this.tester = tester;
|
|
1564
|
+
}
|
|
1565
|
+
send(method, params) {
|
|
1566
|
+
return this.session.send(method, params);
|
|
1567
|
+
}
|
|
1568
|
+
on(event, id, once = false) {
|
|
1569
|
+
if (!this.listenerIds[event]) {
|
|
1570
|
+
this.listenerIds[event] = [];
|
|
1571
|
+
}
|
|
1572
|
+
this.listenerIds[event].push(id);
|
|
1573
|
+
if (!this.listeners[event]) {
|
|
1574
|
+
this.listeners[event] = (payload) => {
|
|
1575
|
+
this.tester.cdpEvent(event, payload);
|
|
1576
|
+
if (once) {
|
|
1577
|
+
this.off(event, id);
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
this.session.on(event, this.listeners[event]);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
off(event, id) {
|
|
1584
|
+
if (!this.listenerIds[event]) {
|
|
1585
|
+
this.listenerIds[event] = [];
|
|
1586
|
+
}
|
|
1587
|
+
this.listenerIds[event] = this.listenerIds[event].filter((l) => l !== id);
|
|
1588
|
+
if (!this.listenerIds[event].length) {
|
|
1589
|
+
this.session.off(event, this.listeners[event]);
|
|
1590
|
+
delete this.listeners[event];
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
once(event, listener) {
|
|
1594
|
+
this.on(event, listener, true);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
const types = {
|
|
1599
|
+
'application/andrew-inset': ['ez'],
|
|
1600
|
+
'application/appinstaller': ['appinstaller'],
|
|
1601
|
+
'application/applixware': ['aw'],
|
|
1602
|
+
'application/appx': ['appx'],
|
|
1603
|
+
'application/appxbundle': ['appxbundle'],
|
|
1604
|
+
'application/atom+xml': ['atom'],
|
|
1605
|
+
'application/atomcat+xml': ['atomcat'],
|
|
1606
|
+
'application/atomdeleted+xml': ['atomdeleted'],
|
|
1607
|
+
'application/atomsvc+xml': ['atomsvc'],
|
|
1608
|
+
'application/atsc-dwd+xml': ['dwd'],
|
|
1609
|
+
'application/atsc-held+xml': ['held'],
|
|
1610
|
+
'application/atsc-rsat+xml': ['rsat'],
|
|
1611
|
+
'application/automationml-aml+xml': ['aml'],
|
|
1612
|
+
'application/automationml-amlx+zip': ['amlx'],
|
|
1613
|
+
'application/bdoc': ['bdoc'],
|
|
1614
|
+
'application/calendar+xml': ['xcs'],
|
|
1615
|
+
'application/ccxml+xml': ['ccxml'],
|
|
1616
|
+
'application/cdfx+xml': ['cdfx'],
|
|
1617
|
+
'application/cdmi-capability': ['cdmia'],
|
|
1618
|
+
'application/cdmi-container': ['cdmic'],
|
|
1619
|
+
'application/cdmi-domain': ['cdmid'],
|
|
1620
|
+
'application/cdmi-object': ['cdmio'],
|
|
1621
|
+
'application/cdmi-queue': ['cdmiq'],
|
|
1622
|
+
'application/cpl+xml': ['cpl'],
|
|
1623
|
+
'application/cu-seeme': ['cu'],
|
|
1624
|
+
'application/cwl': ['cwl'],
|
|
1625
|
+
'application/dash+xml': ['mpd'],
|
|
1626
|
+
'application/dash-patch+xml': ['mpp'],
|
|
1627
|
+
'application/davmount+xml': ['davmount'],
|
|
1628
|
+
'application/dicom': ['dcm'],
|
|
1629
|
+
'application/docbook+xml': ['dbk'],
|
|
1630
|
+
'application/dssc+der': ['dssc'],
|
|
1631
|
+
'application/dssc+xml': ['xdssc'],
|
|
1632
|
+
'application/ecmascript': ['ecma'],
|
|
1633
|
+
'application/emma+xml': ['emma'],
|
|
1634
|
+
'application/emotionml+xml': ['emotionml'],
|
|
1635
|
+
'application/epub+zip': ['epub'],
|
|
1636
|
+
'application/exi': ['exi'],
|
|
1637
|
+
'application/express': ['exp'],
|
|
1638
|
+
'application/fdf': ['fdf'],
|
|
1639
|
+
'application/fdt+xml': ['fdt'],
|
|
1640
|
+
'application/font-tdpfr': ['pfr'],
|
|
1641
|
+
'application/geo+json': ['geojson'],
|
|
1642
|
+
'application/gml+xml': ['gml'],
|
|
1643
|
+
'application/gpx+xml': ['gpx'],
|
|
1644
|
+
'application/gxf': ['gxf'],
|
|
1645
|
+
'application/gzip': ['gz'],
|
|
1646
|
+
'application/hjson': ['hjson'],
|
|
1647
|
+
'application/hyperstudio': ['stk'],
|
|
1648
|
+
'application/inkml+xml': ['ink', 'inkml'],
|
|
1649
|
+
'application/ipfix': ['ipfix'],
|
|
1650
|
+
'application/its+xml': ['its'],
|
|
1651
|
+
'application/java-archive': ['jar', 'war', 'ear'],
|
|
1652
|
+
'application/java-serialized-object': ['ser'],
|
|
1653
|
+
'application/java-vm': ['class'],
|
|
1654
|
+
'application/javascript': ['*js'],
|
|
1655
|
+
'application/json': ['json', 'map'],
|
|
1656
|
+
'application/json5': ['json5'],
|
|
1657
|
+
'application/jsonml+json': ['jsonml'],
|
|
1658
|
+
'application/ld+json': ['jsonld'],
|
|
1659
|
+
'application/lgr+xml': ['lgr'],
|
|
1660
|
+
'application/lost+xml': ['lostxml'],
|
|
1661
|
+
'application/mac-binhex40': ['hqx'],
|
|
1662
|
+
'application/mac-compactpro': ['cpt'],
|
|
1663
|
+
'application/mads+xml': ['mads'],
|
|
1664
|
+
'application/manifest+json': ['webmanifest'],
|
|
1665
|
+
'application/marc': ['mrc'],
|
|
1666
|
+
'application/marcxml+xml': ['mrcx'],
|
|
1667
|
+
'application/mathematica': ['ma', 'nb', 'mb'],
|
|
1668
|
+
'application/mathml+xml': ['mathml'],
|
|
1669
|
+
'application/mbox': ['mbox'],
|
|
1670
|
+
'application/media-policy-dataset+xml': ['mpf'],
|
|
1671
|
+
'application/mediaservercontrol+xml': ['mscml'],
|
|
1672
|
+
'application/metalink+xml': ['metalink'],
|
|
1673
|
+
'application/metalink4+xml': ['meta4'],
|
|
1674
|
+
'application/mets+xml': ['mets'],
|
|
1675
|
+
'application/mmt-aei+xml': ['maei'],
|
|
1676
|
+
'application/mmt-usd+xml': ['musd'],
|
|
1677
|
+
'application/mods+xml': ['mods'],
|
|
1678
|
+
'application/mp21': ['m21', 'mp21'],
|
|
1679
|
+
'application/mp4': ['*mp4', '*mpg4', 'mp4s', 'm4p'],
|
|
1680
|
+
'application/msix': ['msix'],
|
|
1681
|
+
'application/msixbundle': ['msixbundle'],
|
|
1682
|
+
'application/msword': ['doc', 'dot'],
|
|
1683
|
+
'application/mxf': ['mxf'],
|
|
1684
|
+
'application/n-quads': ['nq'],
|
|
1685
|
+
'application/n-triples': ['nt'],
|
|
1686
|
+
'application/node': ['cjs'],
|
|
1687
|
+
'application/octet-stream': [
|
|
1688
|
+
'bin',
|
|
1689
|
+
'dms',
|
|
1690
|
+
'lrf',
|
|
1691
|
+
'mar',
|
|
1692
|
+
'so',
|
|
1693
|
+
'dist',
|
|
1694
|
+
'distz',
|
|
1695
|
+
'pkg',
|
|
1696
|
+
'bpk',
|
|
1697
|
+
'dump',
|
|
1698
|
+
'elc',
|
|
1699
|
+
'deploy',
|
|
1700
|
+
'exe',
|
|
1701
|
+
'dll',
|
|
1702
|
+
'deb',
|
|
1703
|
+
'dmg',
|
|
1704
|
+
'iso',
|
|
1705
|
+
'img',
|
|
1706
|
+
'msi',
|
|
1707
|
+
'msp',
|
|
1708
|
+
'msm',
|
|
1709
|
+
'buffer',
|
|
1710
|
+
],
|
|
1711
|
+
'application/oda': ['oda'],
|
|
1712
|
+
'application/oebps-package+xml': ['opf'],
|
|
1713
|
+
'application/ogg': ['ogx'],
|
|
1714
|
+
'application/omdoc+xml': ['omdoc'],
|
|
1715
|
+
'application/onenote': [
|
|
1716
|
+
'onetoc',
|
|
1717
|
+
'onetoc2',
|
|
1718
|
+
'onetmp',
|
|
1719
|
+
'onepkg',
|
|
1720
|
+
'one',
|
|
1721
|
+
'onea',
|
|
1722
|
+
],
|
|
1723
|
+
'application/oxps': ['oxps'],
|
|
1724
|
+
'application/p2p-overlay+xml': ['relo'],
|
|
1725
|
+
'application/patch-ops-error+xml': ['xer'],
|
|
1726
|
+
'application/pdf': ['pdf'],
|
|
1727
|
+
'application/pgp-encrypted': ['pgp'],
|
|
1728
|
+
'application/pgp-keys': ['asc'],
|
|
1729
|
+
'application/pgp-signature': ['sig', '*asc'],
|
|
1730
|
+
'application/pics-rules': ['prf'],
|
|
1731
|
+
'application/pkcs10': ['p10'],
|
|
1732
|
+
'application/pkcs7-mime': ['p7m', 'p7c'],
|
|
1733
|
+
'application/pkcs7-signature': ['p7s'],
|
|
1734
|
+
'application/pkcs8': ['p8'],
|
|
1735
|
+
'application/pkix-attr-cert': ['ac'],
|
|
1736
|
+
'application/pkix-cert': ['cer'],
|
|
1737
|
+
'application/pkix-crl': ['crl'],
|
|
1738
|
+
'application/pkix-pkipath': ['pkipath'],
|
|
1739
|
+
'application/pkixcmp': ['pki'],
|
|
1740
|
+
'application/pls+xml': ['pls'],
|
|
1741
|
+
'application/postscript': ['ai', 'eps', 'ps'],
|
|
1742
|
+
'application/provenance+xml': ['provx'],
|
|
1743
|
+
'application/pskc+xml': ['pskcxml'],
|
|
1744
|
+
'application/raml+yaml': ['raml'],
|
|
1745
|
+
'application/rdf+xml': ['rdf', 'owl'],
|
|
1746
|
+
'application/reginfo+xml': ['rif'],
|
|
1747
|
+
'application/relax-ng-compact-syntax': ['rnc'],
|
|
1748
|
+
'application/resource-lists+xml': ['rl'],
|
|
1749
|
+
'application/resource-lists-diff+xml': ['rld'],
|
|
1750
|
+
'application/rls-services+xml': ['rs'],
|
|
1751
|
+
'application/route-apd+xml': ['rapd'],
|
|
1752
|
+
'application/route-s-tsid+xml': ['sls'],
|
|
1753
|
+
'application/route-usd+xml': ['rusd'],
|
|
1754
|
+
'application/rpki-ghostbusters': ['gbr'],
|
|
1755
|
+
'application/rpki-manifest': ['mft'],
|
|
1756
|
+
'application/rpki-roa': ['roa'],
|
|
1757
|
+
'application/rsd+xml': ['rsd'],
|
|
1758
|
+
'application/rss+xml': ['rss'],
|
|
1759
|
+
'application/rtf': ['rtf'],
|
|
1760
|
+
'application/sbml+xml': ['sbml'],
|
|
1761
|
+
'application/scvp-cv-request': ['scq'],
|
|
1762
|
+
'application/scvp-cv-response': ['scs'],
|
|
1763
|
+
'application/scvp-vp-request': ['spq'],
|
|
1764
|
+
'application/scvp-vp-response': ['spp'],
|
|
1765
|
+
'application/sdp': ['sdp'],
|
|
1766
|
+
'application/senml+xml': ['senmlx'],
|
|
1767
|
+
'application/sensml+xml': ['sensmlx'],
|
|
1768
|
+
'application/set-payment-initiation': ['setpay'],
|
|
1769
|
+
'application/set-registration-initiation': ['setreg'],
|
|
1770
|
+
'application/shf+xml': ['shf'],
|
|
1771
|
+
'application/sieve': ['siv', 'sieve'],
|
|
1772
|
+
'application/smil+xml': ['smi', 'smil'],
|
|
1773
|
+
'application/sparql-query': ['rq'],
|
|
1774
|
+
'application/sparql-results+xml': ['srx'],
|
|
1775
|
+
'application/sql': ['sql'],
|
|
1776
|
+
'application/srgs': ['gram'],
|
|
1777
|
+
'application/srgs+xml': ['grxml'],
|
|
1778
|
+
'application/sru+xml': ['sru'],
|
|
1779
|
+
'application/ssdl+xml': ['ssdl'],
|
|
1780
|
+
'application/ssml+xml': ['ssml'],
|
|
1781
|
+
'application/swid+xml': ['swidtag'],
|
|
1782
|
+
'application/tei+xml': ['tei', 'teicorpus'],
|
|
1783
|
+
'application/thraud+xml': ['tfi'],
|
|
1784
|
+
'application/timestamped-data': ['tsd'],
|
|
1785
|
+
'application/toml': ['toml'],
|
|
1786
|
+
'application/trig': ['trig'],
|
|
1787
|
+
'application/ttml+xml': ['ttml'],
|
|
1788
|
+
'application/ubjson': ['ubj'],
|
|
1789
|
+
'application/urc-ressheet+xml': ['rsheet'],
|
|
1790
|
+
'application/urc-targetdesc+xml': ['td'],
|
|
1791
|
+
'application/voicexml+xml': ['vxml'],
|
|
1792
|
+
'application/wasm': ['wasm'],
|
|
1793
|
+
'application/watcherinfo+xml': ['wif'],
|
|
1794
|
+
'application/widget': ['wgt'],
|
|
1795
|
+
'application/winhlp': ['hlp'],
|
|
1796
|
+
'application/wsdl+xml': ['wsdl'],
|
|
1797
|
+
'application/wspolicy+xml': ['wspolicy'],
|
|
1798
|
+
'application/xaml+xml': ['xaml'],
|
|
1799
|
+
'application/xcap-att+xml': ['xav'],
|
|
1800
|
+
'application/xcap-caps+xml': ['xca'],
|
|
1801
|
+
'application/xcap-diff+xml': ['xdf'],
|
|
1802
|
+
'application/xcap-el+xml': ['xel'],
|
|
1803
|
+
'application/xcap-ns+xml': ['xns'],
|
|
1804
|
+
'application/xenc+xml': ['xenc'],
|
|
1805
|
+
'application/xfdf': ['xfdf'],
|
|
1806
|
+
'application/xhtml+xml': ['xhtml', 'xht'],
|
|
1807
|
+
'application/xliff+xml': ['xlf'],
|
|
1808
|
+
'application/xml': ['xml', 'xsl', 'xsd', 'rng'],
|
|
1809
|
+
'application/xml-dtd': ['dtd'],
|
|
1810
|
+
'application/xop+xml': ['xop'],
|
|
1811
|
+
'application/xproc+xml': ['xpl'],
|
|
1812
|
+
'application/xslt+xml': ['*xsl', 'xslt'],
|
|
1813
|
+
'application/xspf+xml': ['xspf'],
|
|
1814
|
+
'application/xv+xml': ['mxml', 'xhvml', 'xvml', 'xvm'],
|
|
1815
|
+
'application/yang': ['yang'],
|
|
1816
|
+
'application/yin+xml': ['yin'],
|
|
1817
|
+
'application/zip': ['zip'],
|
|
1818
|
+
'application/zip+dotlottie': ['lottie'],
|
|
1819
|
+
'audio/3gpp': ['*3gpp'],
|
|
1820
|
+
'audio/aac': ['adts', 'aac'],
|
|
1821
|
+
'audio/adpcm': ['adp'],
|
|
1822
|
+
'audio/amr': ['amr'],
|
|
1823
|
+
'audio/basic': ['au', 'snd'],
|
|
1824
|
+
'audio/midi': ['mid', 'midi', 'kar', 'rmi'],
|
|
1825
|
+
'audio/mobile-xmf': ['mxmf'],
|
|
1826
|
+
'audio/mp3': ['*mp3'],
|
|
1827
|
+
'audio/mp4': ['m4a', 'mp4a', 'm4b'],
|
|
1828
|
+
'audio/mpeg': ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'],
|
|
1829
|
+
'audio/ogg': ['oga', 'ogg', 'spx', 'opus'],
|
|
1830
|
+
'audio/s3m': ['s3m'],
|
|
1831
|
+
'audio/silk': ['sil'],
|
|
1832
|
+
'audio/wav': ['wav'],
|
|
1833
|
+
'audio/wave': ['*wav'],
|
|
1834
|
+
'audio/webm': ['weba'],
|
|
1835
|
+
'audio/xm': ['xm'],
|
|
1836
|
+
'font/collection': ['ttc'],
|
|
1837
|
+
'font/otf': ['otf'],
|
|
1838
|
+
'font/ttf': ['ttf'],
|
|
1839
|
+
'font/woff': ['woff'],
|
|
1840
|
+
'font/woff2': ['woff2'],
|
|
1841
|
+
'image/aces': ['exr'],
|
|
1842
|
+
'image/apng': ['apng'],
|
|
1843
|
+
'image/avci': ['avci'],
|
|
1844
|
+
'image/avcs': ['avcs'],
|
|
1845
|
+
'image/avif': ['avif'],
|
|
1846
|
+
'image/bmp': ['bmp', 'dib'],
|
|
1847
|
+
'image/cgm': ['cgm'],
|
|
1848
|
+
'image/dicom-rle': ['drle'],
|
|
1849
|
+
'image/dpx': ['dpx'],
|
|
1850
|
+
'image/emf': ['emf'],
|
|
1851
|
+
'image/fits': ['fits'],
|
|
1852
|
+
'image/g3fax': ['g3'],
|
|
1853
|
+
'image/gif': ['gif'],
|
|
1854
|
+
'image/heic': ['heic'],
|
|
1855
|
+
'image/heic-sequence': ['heics'],
|
|
1856
|
+
'image/heif': ['heif'],
|
|
1857
|
+
'image/heif-sequence': ['heifs'],
|
|
1858
|
+
'image/hej2k': ['hej2'],
|
|
1859
|
+
'image/ief': ['ief'],
|
|
1860
|
+
'image/jaii': ['jaii'],
|
|
1861
|
+
'image/jais': ['jais'],
|
|
1862
|
+
'image/jls': ['jls'],
|
|
1863
|
+
'image/jp2': ['jp2', 'jpg2'],
|
|
1864
|
+
'image/jpeg': ['jpg', 'jpeg', 'jpe'],
|
|
1865
|
+
'image/jph': ['jph'],
|
|
1866
|
+
'image/jphc': ['jhc'],
|
|
1867
|
+
'image/jpm': ['jpm', 'jpgm'],
|
|
1868
|
+
'image/jpx': ['jpx', 'jpf'],
|
|
1869
|
+
'image/jxl': ['jxl'],
|
|
1870
|
+
'image/jxr': ['jxr'],
|
|
1871
|
+
'image/jxra': ['jxra'],
|
|
1872
|
+
'image/jxrs': ['jxrs'],
|
|
1873
|
+
'image/jxs': ['jxs'],
|
|
1874
|
+
'image/jxsc': ['jxsc'],
|
|
1875
|
+
'image/jxsi': ['jxsi'],
|
|
1876
|
+
'image/jxss': ['jxss'],
|
|
1877
|
+
'image/ktx': ['ktx'],
|
|
1878
|
+
'image/ktx2': ['ktx2'],
|
|
1879
|
+
'image/pjpeg': ['jfif'],
|
|
1880
|
+
'image/png': ['png'],
|
|
1881
|
+
'image/sgi': ['sgi'],
|
|
1882
|
+
'image/svg+xml': ['svg', 'svgz'],
|
|
1883
|
+
'image/t38': ['t38'],
|
|
1884
|
+
'image/tiff': ['tif', 'tiff'],
|
|
1885
|
+
'image/tiff-fx': ['tfx'],
|
|
1886
|
+
'image/webp': ['webp'],
|
|
1887
|
+
'image/wmf': ['wmf'],
|
|
1888
|
+
'message/disposition-notification': ['disposition-notification'],
|
|
1889
|
+
'message/global': ['u8msg'],
|
|
1890
|
+
'message/global-delivery-status': ['u8dsn'],
|
|
1891
|
+
'message/global-disposition-notification': ['u8mdn'],
|
|
1892
|
+
'message/global-headers': ['u8hdr'],
|
|
1893
|
+
'message/rfc822': ['eml', 'mime', 'mht', 'mhtml'],
|
|
1894
|
+
'model/3mf': ['3mf'],
|
|
1895
|
+
'model/gltf+json': ['gltf'],
|
|
1896
|
+
'model/gltf-binary': ['glb'],
|
|
1897
|
+
'model/iges': ['igs', 'iges'],
|
|
1898
|
+
'model/jt': ['jt'],
|
|
1899
|
+
'model/mesh': ['msh', 'mesh', 'silo'],
|
|
1900
|
+
'model/mtl': ['mtl'],
|
|
1901
|
+
'model/obj': ['obj'],
|
|
1902
|
+
'model/prc': ['prc'],
|
|
1903
|
+
'model/step': ['step', 'stp', 'stpnc', 'p21', '210'],
|
|
1904
|
+
'model/step+xml': ['stpx'],
|
|
1905
|
+
'model/step+zip': ['stpz'],
|
|
1906
|
+
'model/step-xml+zip': ['stpxz'],
|
|
1907
|
+
'model/stl': ['stl'],
|
|
1908
|
+
'model/u3d': ['u3d'],
|
|
1909
|
+
'model/vrml': ['wrl', 'vrml'],
|
|
1910
|
+
'model/x3d+binary': ['*x3db', 'x3dbz'],
|
|
1911
|
+
'model/x3d+fastinfoset': ['x3db'],
|
|
1912
|
+
'model/x3d+vrml': ['*x3dv', 'x3dvz'],
|
|
1913
|
+
'model/x3d+xml': ['x3d', 'x3dz'],
|
|
1914
|
+
'model/x3d-vrml': ['x3dv'],
|
|
1915
|
+
'text/cache-manifest': ['appcache', 'manifest'],
|
|
1916
|
+
'text/calendar': ['ics', 'ifb'],
|
|
1917
|
+
'text/coffeescript': ['coffee', 'litcoffee'],
|
|
1918
|
+
'text/css': ['css'],
|
|
1919
|
+
'text/csv': ['csv'],
|
|
1920
|
+
'text/html': ['html', 'htm', 'shtml'],
|
|
1921
|
+
'text/jade': ['jade'],
|
|
1922
|
+
'text/javascript': ['js', 'mjs'],
|
|
1923
|
+
'text/jsx': ['jsx'],
|
|
1924
|
+
'text/less': ['less'],
|
|
1925
|
+
'text/markdown': ['md', 'markdown'],
|
|
1926
|
+
'text/mathml': ['mml'],
|
|
1927
|
+
'text/mdx': ['mdx'],
|
|
1928
|
+
'text/n3': ['n3'],
|
|
1929
|
+
'text/plain': ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'ini'],
|
|
1930
|
+
'text/richtext': ['rtx'],
|
|
1931
|
+
'text/rtf': ['*rtf'],
|
|
1932
|
+
'text/sgml': ['sgml', 'sgm'],
|
|
1933
|
+
'text/shex': ['shex'],
|
|
1934
|
+
'text/slim': ['slim', 'slm'],
|
|
1935
|
+
'text/spdx': ['spdx'],
|
|
1936
|
+
'text/stylus': ['stylus', 'styl'],
|
|
1937
|
+
'text/tab-separated-values': ['tsv'],
|
|
1938
|
+
'text/troff': ['t', 'tr', 'roff', 'man', 'me', 'ms'],
|
|
1939
|
+
'text/turtle': ['ttl'],
|
|
1940
|
+
'text/uri-list': ['uri', 'uris', 'urls'],
|
|
1941
|
+
'text/vcard': ['vcard'],
|
|
1942
|
+
'text/vtt': ['vtt'],
|
|
1943
|
+
'text/wgsl': ['wgsl'],
|
|
1944
|
+
'text/xml': ['*xml'],
|
|
1945
|
+
'text/yaml': ['yaml', 'yml'],
|
|
1946
|
+
'video/3gpp': ['3gp', '3gpp'],
|
|
1947
|
+
'video/3gpp2': ['3g2'],
|
|
1948
|
+
'video/h261': ['h261'],
|
|
1949
|
+
'video/h263': ['h263'],
|
|
1950
|
+
'video/h264': ['h264'],
|
|
1951
|
+
'video/iso.segment': ['m4s'],
|
|
1952
|
+
'video/jpeg': ['jpgv'],
|
|
1953
|
+
'video/jpm': ['*jpm', '*jpgm'],
|
|
1954
|
+
'video/mj2': ['mj2', 'mjp2'],
|
|
1955
|
+
'video/mp2t': ['ts', 'm2t', 'm2ts', 'mts'],
|
|
1956
|
+
'video/mp4': ['mp4', 'mp4v', 'mpg4'],
|
|
1957
|
+
'video/mpeg': ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v'],
|
|
1958
|
+
'video/ogg': ['ogv'],
|
|
1959
|
+
'video/quicktime': ['qt', 'mov'],
|
|
1960
|
+
'video/webm': ['webm'],
|
|
1961
|
+
};
|
|
1962
|
+
Object.freeze(types);
|
|
1963
|
+
|
|
1964
|
+
var __classPrivateFieldGet = (null && null.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
1965
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
1966
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
1967
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
1968
|
+
};
|
|
1969
|
+
var _Mime_extensionToType, _Mime_typeToExtension, _Mime_typeToExtensions;
|
|
1970
|
+
class Mime {
|
|
1971
|
+
constructor(...args) {
|
|
1972
|
+
_Mime_extensionToType.set(this, new Map());
|
|
1973
|
+
_Mime_typeToExtension.set(this, new Map());
|
|
1974
|
+
_Mime_typeToExtensions.set(this, new Map());
|
|
1975
|
+
for (const arg of args) {
|
|
1976
|
+
this.define(arg);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
define(typeMap, force = false) {
|
|
1980
|
+
for (let [type, extensions] of Object.entries(typeMap)) {
|
|
1981
|
+
type = type.toLowerCase();
|
|
1982
|
+
extensions = extensions.map((ext) => ext.toLowerCase());
|
|
1983
|
+
if (!__classPrivateFieldGet(this, _Mime_typeToExtensions, "f").has(type)) {
|
|
1984
|
+
__classPrivateFieldGet(this, _Mime_typeToExtensions, "f").set(type, new Set());
|
|
1985
|
+
}
|
|
1986
|
+
const allExtensions = __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").get(type);
|
|
1987
|
+
let first = true;
|
|
1988
|
+
for (let extension of extensions) {
|
|
1989
|
+
const starred = extension.startsWith('*');
|
|
1990
|
+
extension = starred ? extension.slice(1) : extension;
|
|
1991
|
+
allExtensions?.add(extension);
|
|
1992
|
+
if (first) {
|
|
1993
|
+
__classPrivateFieldGet(this, _Mime_typeToExtension, "f").set(type, extension);
|
|
1994
|
+
}
|
|
1995
|
+
first = false;
|
|
1996
|
+
if (starred)
|
|
1997
|
+
continue;
|
|
1998
|
+
const currentType = __classPrivateFieldGet(this, _Mime_extensionToType, "f").get(extension);
|
|
1999
|
+
if (currentType && currentType != type && !force) {
|
|
2000
|
+
throw new Error(`"${type} -> ${extension}" conflicts with "${currentType} -> ${extension}". Pass \`force=true\` to override this definition.`);
|
|
2001
|
+
}
|
|
2002
|
+
__classPrivateFieldGet(this, _Mime_extensionToType, "f").set(extension, type);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
return this;
|
|
2006
|
+
}
|
|
2007
|
+
getType(path) {
|
|
2008
|
+
if (typeof path !== 'string')
|
|
2009
|
+
return null;
|
|
2010
|
+
const last = path.replace(/^.*[/\\]/s, '').toLowerCase();
|
|
2011
|
+
const ext = last.replace(/^.*\./s, '').toLowerCase();
|
|
2012
|
+
const hasPath = last.length < path.length;
|
|
2013
|
+
const hasDot = ext.length < last.length - 1;
|
|
2014
|
+
if (!hasDot && hasPath)
|
|
2015
|
+
return null;
|
|
2016
|
+
return __classPrivateFieldGet(this, _Mime_extensionToType, "f").get(ext) ?? null;
|
|
2017
|
+
}
|
|
2018
|
+
getExtension(type) {
|
|
2019
|
+
if (typeof type !== 'string')
|
|
2020
|
+
return null;
|
|
2021
|
+
type = type?.split?.(';')[0];
|
|
2022
|
+
return ((type && __classPrivateFieldGet(this, _Mime_typeToExtension, "f").get(type.trim().toLowerCase())) ?? null);
|
|
2023
|
+
}
|
|
2024
|
+
getAllExtensions(type) {
|
|
2025
|
+
if (typeof type !== 'string')
|
|
2026
|
+
return null;
|
|
2027
|
+
return __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").get(type.toLowerCase()) ?? null;
|
|
2028
|
+
}
|
|
2029
|
+
_freeze() {
|
|
2030
|
+
this.define = () => {
|
|
2031
|
+
throw new Error('define() not allowed for built-in Mime objects. See https://github.com/broofa/mime/blob/main/README.md#custom-mime-instances');
|
|
2032
|
+
};
|
|
2033
|
+
Object.freeze(this);
|
|
2034
|
+
for (const extensions of __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").values()) {
|
|
2035
|
+
Object.freeze(extensions);
|
|
2036
|
+
}
|
|
2037
|
+
return this;
|
|
2038
|
+
}
|
|
2039
|
+
_getTestState() {
|
|
2040
|
+
return {
|
|
2041
|
+
types: __classPrivateFieldGet(this, _Mime_extensionToType, "f"),
|
|
2042
|
+
extensions: __classPrivateFieldGet(this, _Mime_typeToExtension, "f"),
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
_Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _Mime_typeToExtensions = new WeakMap();
|
|
2047
|
+
|
|
2048
|
+
var mime = new Mime(types)._freeze();
|
|
2049
|
+
|
|
2050
|
+
function assertFileAccess(path, project) {
|
|
2051
|
+
if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.vite)) {
|
|
2052
|
+
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
const readFile = async ({ project }, path, options = {}) => {
|
|
2056
|
+
const filepath = resolve$1(project.config.root, path);
|
|
2057
|
+
assertFileAccess(filepath, project);
|
|
2058
|
+
// never return a Buffer
|
|
2059
|
+
if (typeof options === "object" && !options.encoding) {
|
|
2060
|
+
options.encoding = "utf-8";
|
|
2061
|
+
}
|
|
2062
|
+
return promises.readFile(filepath, options);
|
|
2063
|
+
};
|
|
2064
|
+
const writeFile = async ({ project }, path, data, options) => {
|
|
2065
|
+
const filepath = resolve$1(project.config.root, path);
|
|
2066
|
+
assertFileAccess(filepath, project);
|
|
2067
|
+
const dir = dirname$1(filepath);
|
|
2068
|
+
if (!fs.existsSync(dir)) {
|
|
2069
|
+
await promises.mkdir(dir, { recursive: true });
|
|
2070
|
+
}
|
|
2071
|
+
await promises.writeFile(filepath, data, options);
|
|
2072
|
+
};
|
|
2073
|
+
const removeFile = async ({ project }, path) => {
|
|
2074
|
+
const filepath = resolve$1(project.config.root, path);
|
|
2075
|
+
assertFileAccess(filepath, project);
|
|
2076
|
+
await promises.rm(filepath);
|
|
2077
|
+
};
|
|
2078
|
+
const _fileInfo = async ({ project }, path, encoding) => {
|
|
2079
|
+
const filepath = resolve$1(project.config.root, path);
|
|
2080
|
+
assertFileAccess(filepath, project);
|
|
2081
|
+
const content = await promises.readFile(filepath, encoding || "base64");
|
|
2082
|
+
return {
|
|
2083
|
+
content,
|
|
2084
|
+
basename: basename$1(filepath),
|
|
2085
|
+
mime: mime.getType(filepath)
|
|
2086
|
+
};
|
|
2087
|
+
};
|
|
2088
|
+
|
|
2089
|
+
const screenshot = async (context, name, options = {}) => {
|
|
2090
|
+
options.save ??= true;
|
|
2091
|
+
if (!options.save) {
|
|
2092
|
+
options.base64 = true;
|
|
2093
|
+
}
|
|
2094
|
+
const { buffer, path } = await context.triggerCommand("__vitest_takeScreenshot", name, options);
|
|
2095
|
+
return returnResult(options, path, buffer);
|
|
2096
|
+
};
|
|
2097
|
+
function returnResult(options, path, buffer) {
|
|
2098
|
+
if (!options.save) {
|
|
2099
|
+
return buffer.toString("base64");
|
|
2100
|
+
}
|
|
2101
|
+
if (options.base64) {
|
|
2102
|
+
return {
|
|
2103
|
+
path,
|
|
2104
|
+
base64: buffer.toString("base64")
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
return path;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
const codec = {
|
|
2111
|
+
decode: (buffer, options) => {
|
|
2112
|
+
const { data, alpha, bpp, color, colorType, depth, height, interlace, palette, width } = PNG.sync.read(Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer), options);
|
|
2113
|
+
return {
|
|
2114
|
+
metadata: {
|
|
2115
|
+
alpha,
|
|
2116
|
+
bpp,
|
|
2117
|
+
color,
|
|
2118
|
+
colorType,
|
|
2119
|
+
depth,
|
|
2120
|
+
height,
|
|
2121
|
+
interlace,
|
|
2122
|
+
palette,
|
|
2123
|
+
width
|
|
2124
|
+
},
|
|
2125
|
+
data
|
|
2126
|
+
};
|
|
2127
|
+
},
|
|
2128
|
+
encode: ({ data, metadata: { height, width } }, options) => {
|
|
2129
|
+
const png = new PNG({
|
|
2130
|
+
height,
|
|
2131
|
+
width
|
|
2132
|
+
});
|
|
2133
|
+
png.data = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
2134
|
+
return PNG.sync.write(png, options);
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
|
|
2138
|
+
function getCodec(type) {
|
|
2139
|
+
switch (type) {
|
|
2140
|
+
case "png": return codec;
|
|
2141
|
+
default: throw new Error(`No codec found for type ${type}`);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
const defaultOptions$1 = {
|
|
2146
|
+
allowedMismatchedPixelRatio: undefined,
|
|
2147
|
+
allowedMismatchedPixels: undefined,
|
|
2148
|
+
threshold: .1,
|
|
2149
|
+
includeAA: false,
|
|
2150
|
+
alpha: .1,
|
|
2151
|
+
aaColor: [
|
|
2152
|
+
255,
|
|
2153
|
+
255,
|
|
2154
|
+
0
|
|
2155
|
+
],
|
|
2156
|
+
diffColor: [
|
|
2157
|
+
255,
|
|
2158
|
+
0,
|
|
2159
|
+
0
|
|
2160
|
+
],
|
|
2161
|
+
diffColorAlt: undefined,
|
|
2162
|
+
diffMask: false
|
|
2163
|
+
};
|
|
2164
|
+
const pixelmatch = (reference, actual, { createDiff, ...options }) => {
|
|
2165
|
+
if (reference.metadata.height !== actual.metadata.height || reference.metadata.width !== actual.metadata.width) {
|
|
2166
|
+
return {
|
|
2167
|
+
pass: false,
|
|
2168
|
+
diff: null,
|
|
2169
|
+
message: `Expected image dimensions to be ${reference.metadata.width}×${reference.metadata.height}px, but received ${actual.metadata.width}×${actual.metadata.height}px.`
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
const optionsWithDefaults = {
|
|
2173
|
+
...defaultOptions$1,
|
|
2174
|
+
...options
|
|
2175
|
+
};
|
|
2176
|
+
const diffBuffer = createDiff ? new Uint8Array(reference.data.length) : undefined;
|
|
2177
|
+
const mismatchedPixels = pm(reference.data, actual.data, diffBuffer, reference.metadata.width, reference.metadata.height, optionsWithDefaults);
|
|
2178
|
+
const imageArea = reference.metadata.width * reference.metadata.height;
|
|
2179
|
+
let allowedMismatchedPixels = Math.min(optionsWithDefaults.allowedMismatchedPixels ?? Number.POSITIVE_INFINITY, (optionsWithDefaults.allowedMismatchedPixelRatio ?? Number.POSITIVE_INFINITY) * imageArea);
|
|
2180
|
+
if (allowedMismatchedPixels === Number.POSITIVE_INFINITY) {
|
|
2181
|
+
allowedMismatchedPixels = 0;
|
|
2182
|
+
}
|
|
2183
|
+
const pass = mismatchedPixels <= allowedMismatchedPixels;
|
|
2184
|
+
return {
|
|
2185
|
+
pass,
|
|
2186
|
+
diff: diffBuffer ?? null,
|
|
2187
|
+
message: pass ? null : `${mismatchedPixels} pixels (ratio ${(Math.ceil(mismatchedPixels / imageArea * 100) / 100).toFixed(2)}) differ.`
|
|
2188
|
+
};
|
|
2189
|
+
};
|
|
2190
|
+
|
|
2191
|
+
const comparators = { pixelmatch };
|
|
2192
|
+
function getComparator(comparator, context) {
|
|
2193
|
+
if (comparator in comparators) {
|
|
2194
|
+
return comparators[comparator];
|
|
2195
|
+
}
|
|
2196
|
+
const customComparators = context.project.config.browser.expect?.toMatchScreenshot?.comparators;
|
|
2197
|
+
if (customComparators && comparator in customComparators) {
|
|
2198
|
+
return customComparators[comparator];
|
|
2199
|
+
}
|
|
2200
|
+
throw new Error(`Unrecognized comparator ${comparator}`);
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
const defaultOptions = {
|
|
2204
|
+
comparatorName: "pixelmatch",
|
|
2205
|
+
comparatorOptions: {},
|
|
2206
|
+
screenshotOptions: {
|
|
2207
|
+
animations: "disabled",
|
|
2208
|
+
caret: "hide",
|
|
2209
|
+
fullPage: false,
|
|
2210
|
+
maskColor: "#ff00ff",
|
|
2211
|
+
omitBackground: false,
|
|
2212
|
+
scale: "device"
|
|
2213
|
+
},
|
|
2214
|
+
timeout: 5e3,
|
|
2215
|
+
resolveDiffPath: ({ arg, ext, root, attachmentsDir, browserName, platform, testFileDirectory, testFileName }) => resolve(root, attachmentsDir, testFileDirectory, testFileName, `${arg}-${browserName}-${platform}${ext}`),
|
|
2216
|
+
resolveScreenshotPath: ({ arg, ext, root, screenshotDirectory, testFileDirectory, testFileName, browserName }) => resolve(root, testFileDirectory, screenshotDirectory, testFileName, `${arg}-${browserName}-${platform}${ext}`)
|
|
2217
|
+
};
|
|
2218
|
+
const supportedExtensions = ["png"];
|
|
2219
|
+
function resolveOptions({ context, name, options, testName }) {
|
|
2220
|
+
if (context.testPath === undefined) {
|
|
2221
|
+
throw new Error("`resolveOptions` has to be used in a test file");
|
|
2222
|
+
}
|
|
2223
|
+
const resolvedOptions = deepMerge(Object.create(null), defaultOptions, context.project.config.browser.expect?.toMatchScreenshot ?? {}, options);
|
|
2224
|
+
const extensionFromName = extname(name);
|
|
2225
|
+
// technically the type is a lie, but we check beneath and reassign otherwise
|
|
2226
|
+
let extension = extensionFromName.replace(/^\./, "");
|
|
2227
|
+
// when `type` will be supported in `screenshotOptions`:
|
|
2228
|
+
// - `'png'` should end up in `defaultOptions.screenshotOptions.type`
|
|
2229
|
+
// - this condition should be switched around
|
|
2230
|
+
// - the assignment should be `resolvedOptions.screenshotOptions.type = extension`
|
|
2231
|
+
// - everything using `extension` should use `resolvedOptions.screenshotOptions.type`
|
|
2232
|
+
if (supportedExtensions.includes(extension) === false) {
|
|
2233
|
+
extension = "png";
|
|
2234
|
+
}
|
|
2235
|
+
const { root } = context.project.serializedConfig;
|
|
2236
|
+
const resolvePathData = {
|
|
2237
|
+
arg: sanitizeArg(
|
|
2238
|
+
// remove the extension only if it ends up being used
|
|
2239
|
+
extensionFromName.endsWith(extension) ? basename(name, extensionFromName) : name
|
|
2240
|
+
),
|
|
2241
|
+
ext: `.${extension}`,
|
|
2242
|
+
platform: platform(),
|
|
2243
|
+
root,
|
|
2244
|
+
screenshotDirectory: relative(root, join(root, context.project.config.browser.screenshotDirectory ?? "__screenshots__")),
|
|
2245
|
+
attachmentsDir: relative(root, context.project.config.attachmentsDir),
|
|
2246
|
+
testFileDirectory: relative(root, dirname(context.testPath)),
|
|
2247
|
+
testFileName: basename(context.testPath),
|
|
2248
|
+
testName: sanitize(testName, false),
|
|
2249
|
+
browserName: context.project.config.browser.name
|
|
2250
|
+
};
|
|
2251
|
+
return {
|
|
2252
|
+
codec: getCodec(extension),
|
|
2253
|
+
comparator: getComparator(resolvedOptions.comparatorName, context),
|
|
2254
|
+
resolvedOptions,
|
|
2255
|
+
paths: {
|
|
2256
|
+
reference: resolvedOptions.resolveScreenshotPath(resolvePathData),
|
|
2257
|
+
get diffs() {
|
|
2258
|
+
const diffs = {
|
|
2259
|
+
reference: resolvedOptions.resolveDiffPath({
|
|
2260
|
+
...resolvePathData,
|
|
2261
|
+
arg: `${resolvePathData.arg}-reference`
|
|
2262
|
+
}),
|
|
2263
|
+
actual: resolvedOptions.resolveDiffPath({
|
|
2264
|
+
...resolvePathData,
|
|
2265
|
+
arg: `${resolvePathData.arg}-actual`
|
|
2266
|
+
}),
|
|
2267
|
+
diff: resolvedOptions.resolveDiffPath({
|
|
2268
|
+
...resolvePathData,
|
|
2269
|
+
arg: `${resolvePathData.arg}-diff`
|
|
2270
|
+
})
|
|
2271
|
+
};
|
|
2272
|
+
Object.defineProperty(this, "diffs", { value: diffs });
|
|
2273
|
+
return diffs;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Sanitizes a string by removing or transforming characters to ensure it is
|
|
2280
|
+
* safe for use as a filename or path segment. It supports two modes:
|
|
2281
|
+
*
|
|
2282
|
+
* 1. Non-path mode (`keepPaths === false`):
|
|
2283
|
+
* - Replaces one or more whitespace characters (`\s+`) with a single hyphen (`-`).
|
|
2284
|
+
* - Removes any character that is not a word character (`\w`) or a hyphen (`-`).
|
|
2285
|
+
* - Collapses multiple consecutive hyphens (`-{2,}`) into a single hyphen.
|
|
2286
|
+
*
|
|
2287
|
+
* 2. Path-preserving mode (`keepPaths === true`):
|
|
2288
|
+
* - Splits the input string on the path separator.
|
|
2289
|
+
* - Sanitizes each path segment individually in non-path mode.
|
|
2290
|
+
* - Joins the sanitized segments back together.
|
|
2291
|
+
*
|
|
2292
|
+
* @param input - The raw string to sanitize.
|
|
2293
|
+
* @param keepPaths - If `false`, performs a flat sanitization (drops path segments).
|
|
2294
|
+
* If `true`, treats `input` as a path: each segment is sanitized independently,
|
|
2295
|
+
* preserving separators.
|
|
2296
|
+
*/
|
|
2297
|
+
function sanitize(input, keepPaths) {
|
|
2298
|
+
if (keepPaths === false) {
|
|
2299
|
+
return input.replace(/\s+/g, "-").replace(/[^\w-]+/g, "").replace(/-{2,}/g, "-");
|
|
2300
|
+
}
|
|
2301
|
+
return input.split("/").map((path) => sanitize(path, false)).join("/");
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Takes a string, treats it as a potential path or filename, and ensures it cannot
|
|
2305
|
+
* escape the root directory or contain invalid characters. Internally, it:
|
|
2306
|
+
*
|
|
2307
|
+
* 1. Prepends the path separator to the raw input to form a path-like string.
|
|
2308
|
+
* 2. Uses {@linkcode relative|relative('/', <that-path>)} to compute a relative
|
|
2309
|
+
* path from the root, which effectively strips any leading separators and prevents
|
|
2310
|
+
* traversal above the root.
|
|
2311
|
+
* 3. Passes the resulting relative path into {@linkcode sanitize|sanitize(..., true)},
|
|
2312
|
+
* preserving any path separators but sanitizing each segment.
|
|
2313
|
+
*
|
|
2314
|
+
* @param input - The raw string to clean.
|
|
2315
|
+
*/
|
|
2316
|
+
function sanitizeArg(input) {
|
|
2317
|
+
return sanitize(relative("/", join("/", input)), true);
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Takes a screenshot and decodes it using the provided codec.
|
|
2321
|
+
*
|
|
2322
|
+
* The screenshot is taken as a base64 string and then decoded into the format
|
|
2323
|
+
* expected by the comparator.
|
|
2324
|
+
*
|
|
2325
|
+
* @returns `Promise` resolving to the decoded screenshot data
|
|
2326
|
+
*/
|
|
2327
|
+
function takeDecodedScreenshot({ codec, context, element, name, screenshotOptions }) {
|
|
2328
|
+
return context.triggerCommand("__vitest_takeScreenshot", name, {
|
|
2329
|
+
...screenshotOptions,
|
|
2330
|
+
save: false,
|
|
2331
|
+
element
|
|
2332
|
+
}).then(({ buffer }) => codec.decode(buffer, {}));
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Creates a promise that resolves to `null` after the specified timeout.
|
|
2336
|
+
* If the timeout is `0`, the promise resolves immediately.
|
|
2337
|
+
*
|
|
2338
|
+
* @param timeout - The delay in milliseconds before the promise resolves
|
|
2339
|
+
* @returns `Promise` that resolves to `null` after the timeout
|
|
2340
|
+
*/
|
|
2341
|
+
function asyncTimeout(timeout) {
|
|
2342
|
+
return new Promise((resolve) => {
|
|
2343
|
+
if (timeout === 0) {
|
|
2344
|
+
resolve(null);
|
|
2345
|
+
} else {
|
|
2346
|
+
setTimeout(() => resolve(null), timeout);
|
|
2347
|
+
}
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
/**
|
|
2352
|
+
* Browser command that compares a screenshot against a stored reference.
|
|
2353
|
+
*
|
|
2354
|
+
* The comparison workflow is organized as follows:
|
|
2355
|
+
*
|
|
2356
|
+
* 1. Load existing reference (if any)
|
|
2357
|
+
* 2. Capture a stable screenshot (retrying until the page stops changing)
|
|
2358
|
+
* 3. Determine the outcome based on capture results and update settings
|
|
2359
|
+
* 4. Write any necessary files (new references, diffs)
|
|
2360
|
+
* 5. Return result for the test runner
|
|
2361
|
+
*/
|
|
2362
|
+
const screenshotMatcher = async (context, name, testName, options) => {
|
|
2363
|
+
if (!context.testPath) {
|
|
2364
|
+
throw new Error("Cannot compare screenshots without a test path");
|
|
2365
|
+
}
|
|
2366
|
+
const { element } = options;
|
|
2367
|
+
const { codec, comparator, paths, resolvedOptions: { comparatorOptions, screenshotOptions, timeout } } = resolveOptions({
|
|
2368
|
+
context,
|
|
2369
|
+
name,
|
|
2370
|
+
testName,
|
|
2371
|
+
options
|
|
2372
|
+
});
|
|
2373
|
+
const referenceFile = await readFile$1(paths.reference).catch(() => null);
|
|
2374
|
+
const reference = referenceFile && await codec.decode(referenceFile, {});
|
|
2375
|
+
const screenshotResult = await waitForStableScreenshot({
|
|
2376
|
+
codec,
|
|
2377
|
+
comparator,
|
|
2378
|
+
comparatorOptions,
|
|
2379
|
+
context,
|
|
2380
|
+
element,
|
|
2381
|
+
name: `${Date.now()}-${basename(paths.reference)}`,
|
|
2382
|
+
reference,
|
|
2383
|
+
screenshotOptions
|
|
2384
|
+
}, timeout);
|
|
2385
|
+
const outcome = await determineOutcome({
|
|
2386
|
+
reference,
|
|
2387
|
+
screenshot: screenshotResult && screenshotResult.actual,
|
|
2388
|
+
retries: screenshotResult?.retries ?? 0,
|
|
2389
|
+
updateSnapshot: context.project.serializedConfig.snapshotOptions.updateSnapshot,
|
|
2390
|
+
paths,
|
|
2391
|
+
comparator,
|
|
2392
|
+
comparatorOptions
|
|
2393
|
+
});
|
|
2394
|
+
await performSideEffects(outcome, codec);
|
|
2395
|
+
return buildOutput(outcome, timeout);
|
|
2396
|
+
};
|
|
2397
|
+
/**
|
|
2398
|
+
* Core comparison logic that produces a {@linkcode MatchOutcome}.
|
|
2399
|
+
*
|
|
2400
|
+
* All branching logic lives here. This is the single source of truth for "what happened".
|
|
2401
|
+
*
|
|
2402
|
+
* The outcome carries all data needed by {@linkcode performSideEffects} and {@linkcode buildOutput}.
|
|
2403
|
+
*/
|
|
2404
|
+
async function determineOutcome({ comparator, comparatorOptions, paths, reference, retries, screenshot, updateSnapshot }) {
|
|
2405
|
+
if (screenshot === null) {
|
|
2406
|
+
return {
|
|
2407
|
+
type: "unstable-screenshot",
|
|
2408
|
+
reference: reference && {
|
|
2409
|
+
image: reference,
|
|
2410
|
+
path: paths.reference
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
// no reference to compare against - create one based on update settings
|
|
2415
|
+
if (reference === null || updateSnapshot === "all") {
|
|
2416
|
+
if (updateSnapshot === "all") {
|
|
2417
|
+
return {
|
|
2418
|
+
type: "update-reference",
|
|
2419
|
+
reference: {
|
|
2420
|
+
image: screenshot,
|
|
2421
|
+
path: paths.reference
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
const location = updateSnapshot === "none" ? "diffs" : "reference";
|
|
2426
|
+
return {
|
|
2427
|
+
type: "missing-reference",
|
|
2428
|
+
location,
|
|
2429
|
+
reference: {
|
|
2430
|
+
image: screenshot,
|
|
2431
|
+
path: location === "reference" ? paths.reference : paths.diffs.reference
|
|
2432
|
+
}
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
// first capture matched reference (used as baseline) - no further comparison needed
|
|
2436
|
+
if (retries === 0) {
|
|
2437
|
+
return { type: "matched-immediately" };
|
|
2438
|
+
}
|
|
2439
|
+
const comparisonResult = await comparator(reference, screenshot, {
|
|
2440
|
+
createDiff: true,
|
|
2441
|
+
...comparatorOptions
|
|
2442
|
+
});
|
|
2443
|
+
if (comparisonResult.pass) {
|
|
2444
|
+
return { type: "matched-after-comparison" };
|
|
2445
|
+
}
|
|
2446
|
+
return {
|
|
2447
|
+
type: "mismatch",
|
|
2448
|
+
reference: {
|
|
2449
|
+
image: reference,
|
|
2450
|
+
path: paths.reference
|
|
2451
|
+
},
|
|
2452
|
+
actual: {
|
|
2453
|
+
image: screenshot,
|
|
2454
|
+
path: paths.diffs.actual
|
|
2455
|
+
},
|
|
2456
|
+
diff: comparisonResult.diff && {
|
|
2457
|
+
image: {
|
|
2458
|
+
data: comparisonResult.diff,
|
|
2459
|
+
metadata: reference.metadata
|
|
2460
|
+
},
|
|
2461
|
+
path: paths.diffs.diff
|
|
2462
|
+
},
|
|
2463
|
+
message: comparisonResult.message
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
/**
|
|
2467
|
+
* Writes files to disk based on the outcome.
|
|
2468
|
+
*
|
|
2469
|
+
* Only `missing-reference`, `update-reference`, and `mismatch` write files. Successful matches produce no side effects.
|
|
2470
|
+
*/
|
|
2471
|
+
async function performSideEffects(outcome, codec) {
|
|
2472
|
+
switch (outcome.type) {
|
|
2473
|
+
case "missing-reference":
|
|
2474
|
+
case "update-reference": {
|
|
2475
|
+
await writeScreenshot(outcome.reference.path, await codec.encode(outcome.reference.image, {}));
|
|
2476
|
+
break;
|
|
2477
|
+
}
|
|
2478
|
+
case "mismatch": {
|
|
2479
|
+
await writeScreenshot(outcome.actual.path, await codec.encode(outcome.actual.image, {}));
|
|
2480
|
+
if (outcome.diff) {
|
|
2481
|
+
await writeScreenshot(outcome.diff.path, await codec.encode(outcome.diff.image, {}));
|
|
2482
|
+
}
|
|
2483
|
+
break;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Transforms a {@linkcode MatchOutcome} into the output format expected by the test runner.
|
|
2489
|
+
*
|
|
2490
|
+
* Maps each outcome to a pass/fail result with metadata and error messages.
|
|
2491
|
+
*/
|
|
2492
|
+
function buildOutput(outcome, timeout) {
|
|
2493
|
+
switch (outcome.type) {
|
|
2494
|
+
case "unstable-screenshot": return {
|
|
2495
|
+
pass: false,
|
|
2496
|
+
reference: outcome.reference && {
|
|
2497
|
+
path: outcome.reference.path,
|
|
2498
|
+
width: outcome.reference.image.metadata.width,
|
|
2499
|
+
height: outcome.reference.image.metadata.height
|
|
2500
|
+
},
|
|
2501
|
+
actual: null,
|
|
2502
|
+
diff: null,
|
|
2503
|
+
message: `Could not capture a stable screenshot within ${timeout}ms.`
|
|
2504
|
+
};
|
|
2505
|
+
case "missing-reference": {
|
|
2506
|
+
return {
|
|
2507
|
+
pass: false,
|
|
2508
|
+
reference: {
|
|
2509
|
+
path: outcome.reference.path,
|
|
2510
|
+
width: outcome.reference.image.metadata.width,
|
|
2511
|
+
height: outcome.reference.image.metadata.height
|
|
2512
|
+
},
|
|
2513
|
+
actual: null,
|
|
2514
|
+
diff: null,
|
|
2515
|
+
message: outcome.location === "reference" ? "No existing reference screenshot found; a new one was created. Review it before running tests again." : "No existing reference screenshot found."
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
case "update-reference":
|
|
2519
|
+
case "matched-immediately":
|
|
2520
|
+
case "matched-after-comparison": return { pass: true };
|
|
2521
|
+
case "mismatch": return {
|
|
2522
|
+
pass: false,
|
|
2523
|
+
reference: {
|
|
2524
|
+
path: outcome.reference.path,
|
|
2525
|
+
width: outcome.reference.image.metadata.width,
|
|
2526
|
+
height: outcome.reference.image.metadata.height
|
|
2527
|
+
},
|
|
2528
|
+
actual: {
|
|
2529
|
+
path: outcome.actual.path,
|
|
2530
|
+
width: outcome.actual.image.metadata.width,
|
|
2531
|
+
height: outcome.actual.image.metadata.height
|
|
2532
|
+
},
|
|
2533
|
+
diff: outcome.diff && {
|
|
2534
|
+
path: outcome.diff.path,
|
|
2535
|
+
width: outcome.diff.image.metadata.width,
|
|
2536
|
+
height: outcome.diff.image.metadata.height
|
|
2537
|
+
},
|
|
2538
|
+
message: `Screenshot does not match the stored reference.${outcome.message ? `\n${outcome.message}` : ""}`
|
|
2539
|
+
};
|
|
2540
|
+
default: {
|
|
2541
|
+
return {
|
|
2542
|
+
pass: false,
|
|
2543
|
+
actual: null,
|
|
2544
|
+
reference: null,
|
|
2545
|
+
diff: null,
|
|
2546
|
+
message: `Outcome (${outcome.type}) not handled. This is a bug in Vitest. Please, open an issue with reproduction.`
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Captures a stable screenshot with timeout handling.
|
|
2553
|
+
*
|
|
2554
|
+
* Wraps {@linkcode getStableScreenshot} with an abort controller that triggers when the timeout expires. Returns `null` if the page never stabilizes.
|
|
2555
|
+
*/
|
|
2556
|
+
async function waitForStableScreenshot(options, timeout) {
|
|
2557
|
+
const abortController = new AbortController();
|
|
2558
|
+
const stableScreenshot = getStableScreenshot(options, abortController.signal);
|
|
2559
|
+
const result = await (timeout === 0 ? stableScreenshot : Promise.race([stableScreenshot, asyncTimeout(timeout).finally(() => abortController.abort())]));
|
|
2560
|
+
return result;
|
|
2561
|
+
}
|
|
2562
|
+
/**
|
|
2563
|
+
* Takes screenshots repeatedly until the page reaches a visually stable state.
|
|
2564
|
+
*
|
|
2565
|
+
* This function compares consecutive screenshots and continues taking new ones until two consecutive screenshots match according to the provided comparator.
|
|
2566
|
+
*
|
|
2567
|
+
* The process works as follows:
|
|
2568
|
+
*
|
|
2569
|
+
* 1. Uses as baseline an optional reference screenshot or takes a new screenshot
|
|
2570
|
+
* 2. Takes a screenshot and compares with baseline
|
|
2571
|
+
* 3. If they match, the page is considered stable and the function returns
|
|
2572
|
+
* 4. If they don't match, it continues with the newer screenshot as the baseline
|
|
2573
|
+
* 5. Repeats until stability is achieved or the operation is aborted
|
|
2574
|
+
*
|
|
2575
|
+
* @returns `Promise` resolving to an object containing the retry count and final screenshot
|
|
2576
|
+
*/
|
|
2577
|
+
async function getStableScreenshot({ codec, context, comparator, comparatorOptions, element, name, reference, screenshotOptions }, signal) {
|
|
2578
|
+
const screenshotArgument = {
|
|
2579
|
+
codec,
|
|
2580
|
+
context,
|
|
2581
|
+
element,
|
|
2582
|
+
name,
|
|
2583
|
+
screenshotOptions
|
|
2584
|
+
};
|
|
2585
|
+
let retries = 0;
|
|
2586
|
+
let decodedBaseline = reference;
|
|
2587
|
+
while (signal.aborted === false) {
|
|
2588
|
+
if (decodedBaseline === null) {
|
|
2589
|
+
decodedBaseline = takeDecodedScreenshot(screenshotArgument);
|
|
2590
|
+
}
|
|
2591
|
+
const [image1, image2] = await Promise.all([decodedBaseline, takeDecodedScreenshot(screenshotArgument)]);
|
|
2592
|
+
const isStable = (await comparator(image1, image2, {
|
|
2593
|
+
...comparatorOptions,
|
|
2594
|
+
createDiff: false
|
|
2595
|
+
})).pass;
|
|
2596
|
+
decodedBaseline = image2;
|
|
2597
|
+
if (isStable) {
|
|
2598
|
+
break;
|
|
2599
|
+
}
|
|
2600
|
+
retries += 1;
|
|
2601
|
+
}
|
|
2602
|
+
return {
|
|
2603
|
+
retries,
|
|
2604
|
+
actual: await decodedBaseline
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
/** Writes encoded images to disk, creating parent directories as needed. */
|
|
2608
|
+
async function writeScreenshot(path, image) {
|
|
2609
|
+
try {
|
|
2610
|
+
await mkdir(dirname(path), { recursive: true });
|
|
2611
|
+
await writeFile$1(path, image);
|
|
2612
|
+
} catch (cause) {
|
|
2613
|
+
throw new Error("Couldn't write file to fs", { cause });
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
var builtinCommands = {
|
|
2618
|
+
readFile,
|
|
2619
|
+
removeFile,
|
|
2620
|
+
writeFile,
|
|
2621
|
+
__vitest_fileInfo: _fileInfo,
|
|
2622
|
+
__vitest_screenshot: screenshot,
|
|
2623
|
+
__vitest_screenshotMatcher: screenshotMatcher
|
|
2624
|
+
};
|
|
2625
|
+
|
|
2626
|
+
class BrowserServerState {
|
|
2627
|
+
orchestrators = new Map();
|
|
2628
|
+
testers = new Map();
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
class ProjectBrowser {
|
|
2632
|
+
testerHtml;
|
|
2633
|
+
testerFilepath;
|
|
2634
|
+
provider;
|
|
2635
|
+
vitest;
|
|
2636
|
+
config;
|
|
2637
|
+
children = new Set();
|
|
2638
|
+
parent;
|
|
2639
|
+
state = new BrowserServerState();
|
|
2640
|
+
constructor(project, base) {
|
|
2641
|
+
this.project = project;
|
|
2642
|
+
this.base = base;
|
|
2643
|
+
this.vitest = project.vitest;
|
|
2644
|
+
this.config = project.config;
|
|
2645
|
+
const distRoot = dirname(fileURLToPath(import.meta.url));
|
|
2646
|
+
const testerHtmlPath = project.config.browser.testerHtmlPath ? resolve(project.config.root, project.config.browser.testerHtmlPath) : resolve(distRoot, "client/tester/tester.html");
|
|
2647
|
+
if (!existsSync(testerHtmlPath)) {
|
|
2648
|
+
throw new Error(`Tester HTML file "${testerHtmlPath}" doesn't exist.`);
|
|
2649
|
+
}
|
|
2650
|
+
this.testerFilepath = testerHtmlPath;
|
|
2651
|
+
this.testerHtml = readFile$1(testerHtmlPath, "utf8").then((html) => this.testerHtml = html);
|
|
2652
|
+
}
|
|
2653
|
+
get vite() {
|
|
2654
|
+
return this.parent.vite;
|
|
2655
|
+
}
|
|
2656
|
+
commands = {};
|
|
2657
|
+
registerCommand(name, cb) {
|
|
2658
|
+
if (!/^[a-z_$][\w$]*$/i.test(name)) {
|
|
2659
|
+
throw new Error(`Invalid command name "${name}". Only alphanumeric characters, $ and _ are allowed.`);
|
|
2660
|
+
}
|
|
2661
|
+
this.commands[name] = cb;
|
|
2662
|
+
}
|
|
2663
|
+
triggerCommand = ((name, context, ...args) => {
|
|
2664
|
+
if (name in this.commands) {
|
|
2665
|
+
return this.commands[name](context, ...args);
|
|
2666
|
+
}
|
|
2667
|
+
if (name in this.parent.commands) {
|
|
2668
|
+
return this.parent.commands[name](context, ...args);
|
|
2669
|
+
}
|
|
2670
|
+
throw new Error(`Provider ${this.provider.name} does not support command "${name}".`);
|
|
2671
|
+
});
|
|
2672
|
+
wrapSerializedConfig() {
|
|
2673
|
+
const config = wrapConfig(this.project.serializedConfig);
|
|
2674
|
+
config.env ??= {};
|
|
2675
|
+
config.env.VITEST_BROWSER_DEBUG = process.env.VITEST_BROWSER_DEBUG || "";
|
|
2676
|
+
return config;
|
|
2677
|
+
}
|
|
2678
|
+
async initBrowserProvider(project) {
|
|
2679
|
+
if (this.provider) {
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2682
|
+
this.provider = await getBrowserProvider(project.config.browser, project);
|
|
2683
|
+
if (this.provider.initScripts) {
|
|
2684
|
+
this.parent.initScripts = this.provider.initScripts;
|
|
2685
|
+
// make sure the script can be imported
|
|
2686
|
+
const allow = this.parent.vite.config.server.fs.allow;
|
|
2687
|
+
this.provider.initScripts.forEach((script) => {
|
|
2688
|
+
if (!allow.includes(script)) {
|
|
2689
|
+
allow.push(script);
|
|
2690
|
+
}
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
parseErrorStacktrace(e, options = {}) {
|
|
2695
|
+
return this.parent.parseErrorStacktrace(e, options);
|
|
2696
|
+
}
|
|
2697
|
+
parseStacktrace(trace, options = {}) {
|
|
2698
|
+
return this.parent.parseStacktrace(trace, options);
|
|
2699
|
+
}
|
|
2700
|
+
async close() {
|
|
2701
|
+
await this.parent.vite.close();
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
function wrapConfig(config) {
|
|
2705
|
+
return {
|
|
2706
|
+
...config,
|
|
2707
|
+
testNamePattern: config.testNamePattern ? config.testNamePattern.toString() : undefined
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
class ParentBrowserProject {
|
|
2712
|
+
orchestratorScripts;
|
|
2713
|
+
faviconUrl;
|
|
2714
|
+
prefixOrchestratorUrl;
|
|
2715
|
+
prefixTesterUrl;
|
|
2716
|
+
manifest;
|
|
2717
|
+
vite;
|
|
2718
|
+
stackTraceOptions;
|
|
2719
|
+
orchestratorHtml;
|
|
2720
|
+
injectorJs;
|
|
2721
|
+
errorCatcherUrl;
|
|
2722
|
+
locatorsUrl;
|
|
2723
|
+
matchersUrl;
|
|
2724
|
+
stateJs;
|
|
2725
|
+
initScripts = [];
|
|
2726
|
+
commands = {};
|
|
2727
|
+
children = new Set();
|
|
2728
|
+
vitest;
|
|
2729
|
+
config;
|
|
2730
|
+
// cache for non-vite source maps
|
|
2731
|
+
sourceMapCache = new Map();
|
|
2732
|
+
constructor(project, base) {
|
|
2733
|
+
this.project = project;
|
|
2734
|
+
this.base = base;
|
|
2735
|
+
this.vitest = project.vitest;
|
|
2736
|
+
this.config = project.config;
|
|
2737
|
+
this.stackTraceOptions = {
|
|
2738
|
+
frameFilter: project.config.onStackTrace,
|
|
2739
|
+
getSourceMap: (id) => {
|
|
2740
|
+
if (this.sourceMapCache.has(id)) {
|
|
2741
|
+
return this.sourceMapCache.get(id);
|
|
2742
|
+
}
|
|
2743
|
+
const result = this.vite.moduleGraph.getModuleById(id)?.transformResult;
|
|
2744
|
+
// this can happen for bundled dependencies in node_modules/.vite
|
|
2745
|
+
if (result && !result.map) {
|
|
2746
|
+
const sourceMapUrl = this.retrieveSourceMapURL(result.code);
|
|
2747
|
+
if (!sourceMapUrl) {
|
|
2748
|
+
return null;
|
|
2749
|
+
}
|
|
2750
|
+
const filepathDir = dirname(id);
|
|
2751
|
+
const sourceMapPath = resolve(filepathDir, sourceMapUrl);
|
|
2752
|
+
const map = JSON.parse(readFileSync(sourceMapPath, "utf-8"));
|
|
2753
|
+
this.sourceMapCache.set(id, map);
|
|
2754
|
+
return map;
|
|
2755
|
+
}
|
|
2756
|
+
return result?.map;
|
|
2757
|
+
},
|
|
2758
|
+
getUrlId: (id) => {
|
|
2759
|
+
const moduleGraph = this.vite.environments.client.moduleGraph;
|
|
2760
|
+
const mod = moduleGraph.getModuleById(id);
|
|
2761
|
+
if (mod) {
|
|
2762
|
+
return id;
|
|
2763
|
+
}
|
|
2764
|
+
const resolvedPath = resolve(this.vite.config.root, id.slice(1));
|
|
2765
|
+
const modUrl = moduleGraph.getModuleById(resolvedPath);
|
|
2766
|
+
if (modUrl) {
|
|
2767
|
+
return resolvedPath;
|
|
2768
|
+
}
|
|
2769
|
+
// some browsers (looking at you, safari) don't report queries in stack traces
|
|
2770
|
+
// the next best thing is to try the first id that this file resolves to
|
|
2771
|
+
const files = moduleGraph.getModulesByFile(resolvedPath);
|
|
2772
|
+
if (files && files.size) {
|
|
2773
|
+
return files.values().next().value.id;
|
|
2774
|
+
}
|
|
2775
|
+
return id;
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
for (const [name, command] of Object.entries(builtinCommands)) {
|
|
2779
|
+
this.commands[name] ??= command;
|
|
2780
|
+
}
|
|
2781
|
+
// validate names because they can't be used as identifiers
|
|
2782
|
+
for (const command in project.config.browser.commands) {
|
|
2783
|
+
if (!/^[a-z_$][\w$]*$/i.test(command)) {
|
|
2784
|
+
throw new Error(`Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`);
|
|
2785
|
+
}
|
|
2786
|
+
this.commands[command] = project.config.browser.commands[command];
|
|
2787
|
+
}
|
|
2788
|
+
this.prefixTesterUrl = `${base || "/"}`;
|
|
2789
|
+
this.prefixOrchestratorUrl = `${base}__vitest_test__/`;
|
|
2790
|
+
this.faviconUrl = `${base}__vitest__/favicon.svg`;
|
|
2791
|
+
this.manifest = (async () => {
|
|
2792
|
+
return JSON.parse(await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8"));
|
|
2793
|
+
})().then((manifest) => this.manifest = manifest);
|
|
2794
|
+
this.orchestratorHtml = (project.config.browser.ui ? readFile$1(resolve(distRoot, "client/__vitest__/index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8")).then((html) => this.orchestratorHtml = html);
|
|
2795
|
+
this.injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8").then((js) => this.injectorJs = js);
|
|
2796
|
+
this.errorCatcherUrl = join("/@fs/", resolve(distRoot, "client/error-catcher.js"));
|
|
2797
|
+
this.matchersUrl = join("/@fs/", distRoot, "expect-element.js");
|
|
2798
|
+
this.stateJs = readFile$1(resolve(distRoot, "state.js"), "utf-8").then((js) => this.stateJs = js);
|
|
2799
|
+
}
|
|
2800
|
+
setServer(vite) {
|
|
2801
|
+
this.vite = vite;
|
|
2802
|
+
}
|
|
2803
|
+
spawn(project) {
|
|
2804
|
+
if (!this.vite) {
|
|
2805
|
+
throw new Error(`Cannot spawn child server without a parent dev server.`);
|
|
2806
|
+
}
|
|
2807
|
+
const clone = new ProjectBrowser(project, "/");
|
|
2808
|
+
clone.parent = this;
|
|
2809
|
+
this.children.add(clone);
|
|
2810
|
+
return clone;
|
|
2811
|
+
}
|
|
2812
|
+
parseErrorStacktrace(e, options = {}) {
|
|
2813
|
+
return parseErrorStacktrace(e, {
|
|
2814
|
+
...this.stackTraceOptions,
|
|
2815
|
+
...options
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
parseStacktrace(trace, options = {}) {
|
|
2819
|
+
return parseStacktrace(trace, {
|
|
2820
|
+
...this.stackTraceOptions,
|
|
2821
|
+
...options
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
cdps = new Map();
|
|
2825
|
+
cdpSessionsPromises = new Map();
|
|
2826
|
+
async ensureCDPHandler(sessionId, rpcId) {
|
|
2827
|
+
const cachedHandler = this.cdps.get(rpcId);
|
|
2828
|
+
if (cachedHandler) {
|
|
2829
|
+
return cachedHandler;
|
|
2830
|
+
}
|
|
2831
|
+
const browserSession = this.vitest._browserSessions.getSession(sessionId);
|
|
2832
|
+
if (!browserSession) {
|
|
2833
|
+
throw new Error(`Session "${sessionId}" not found.`);
|
|
2834
|
+
}
|
|
2835
|
+
const browser = browserSession.project.browser;
|
|
2836
|
+
const provider = browser.provider;
|
|
2837
|
+
if (!provider) {
|
|
2838
|
+
throw new Error(`Browser provider is not defined for the project "${browserSession.project.name}".`);
|
|
2839
|
+
}
|
|
2840
|
+
if (!provider.getCDPSession) {
|
|
2841
|
+
throw new Error(`CDP is not supported by the provider "${provider.name}".`);
|
|
2842
|
+
}
|
|
2843
|
+
const session = await this.cdpSessionsPromises.get(rpcId) ?? await (async () => {
|
|
2844
|
+
const promise = provider.getCDPSession(sessionId).finally(() => {
|
|
2845
|
+
this.cdpSessionsPromises.delete(rpcId);
|
|
2846
|
+
});
|
|
2847
|
+
this.cdpSessionsPromises.set(rpcId, promise);
|
|
2848
|
+
return promise;
|
|
2849
|
+
})();
|
|
2850
|
+
const rpc = browser.state.testers.get(rpcId);
|
|
2851
|
+
if (!rpc) {
|
|
2852
|
+
throw new Error(`Tester RPC "${rpcId}" was not established.`);
|
|
2853
|
+
}
|
|
2854
|
+
const handler = new BrowserServerCDPHandler(session, rpc);
|
|
2855
|
+
this.cdps.set(rpcId, handler);
|
|
2856
|
+
return handler;
|
|
2857
|
+
}
|
|
2858
|
+
removeCDPHandler(sessionId) {
|
|
2859
|
+
this.cdps.delete(sessionId);
|
|
2860
|
+
}
|
|
2861
|
+
async formatScripts(scripts) {
|
|
2862
|
+
if (!scripts?.length) {
|
|
2863
|
+
return [];
|
|
2864
|
+
}
|
|
2865
|
+
const server = this.vite;
|
|
2866
|
+
const promises = scripts.map(async ({ content, src, async, id, type = "module" }, index) => {
|
|
2867
|
+
const srcLink = (src ? (await server.pluginContainer.resolveId(src))?.id : undefined) || src;
|
|
2868
|
+
const transformId = srcLink || join(server.config.root, `virtual__${id || `injected-${index}.js`}`);
|
|
2869
|
+
await server.moduleGraph.ensureEntryFromUrl(transformId);
|
|
2870
|
+
const contentProcessed = content && type === "module" ? (await server.pluginContainer.transform(content, transformId)).code : content;
|
|
2871
|
+
return {
|
|
2872
|
+
tag: "script",
|
|
2873
|
+
attrs: {
|
|
2874
|
+
type,
|
|
2875
|
+
...async ? { async: "" } : {},
|
|
2876
|
+
...srcLink ? { src: srcLink.startsWith("http") ? srcLink : slash(`/@fs/${srcLink}`) } : {}
|
|
2877
|
+
},
|
|
2878
|
+
injectTo: "head",
|
|
2879
|
+
children: contentProcessed || ""
|
|
2880
|
+
};
|
|
2881
|
+
});
|
|
2882
|
+
return await Promise.all(promises);
|
|
2883
|
+
}
|
|
2884
|
+
resolveTesterUrl(pathname) {
|
|
2885
|
+
const [sessionId, testFile] = pathname.slice(this.prefixTesterUrl.length).split("/");
|
|
2886
|
+
const decodedTestFile = decodeURIComponent(testFile);
|
|
2887
|
+
return {
|
|
2888
|
+
sessionId,
|
|
2889
|
+
testFile: decodedTestFile
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
retrieveSourceMapURL(source) {
|
|
2893
|
+
const re = /\/\/[@#]\s*sourceMappingURL=([^\s'"]+)\s*$|\/\*[@#]\s*sourceMappingURL=[^\s*'"]+\s*\*\/\s*$/gm;
|
|
2894
|
+
// Keep executing the search to find the *last* sourceMappingURL to avoid
|
|
2895
|
+
// picking up sourceMappingURLs from comments, strings, etc.
|
|
2896
|
+
let lastMatch, match;
|
|
2897
|
+
// eslint-disable-next-line no-cond-assign
|
|
2898
|
+
while (match = re.exec(source)) {
|
|
2899
|
+
lastMatch = match;
|
|
2900
|
+
}
|
|
2901
|
+
if (!lastMatch) {
|
|
2902
|
+
return null;
|
|
2903
|
+
}
|
|
2904
|
+
return lastMatch[1];
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
//#region src/messages.ts
|
|
2909
|
+
const TYPE_REQUEST = "q";
|
|
2910
|
+
const TYPE_RESPONSE = "s";
|
|
2911
|
+
|
|
2912
|
+
//#endregion
|
|
2913
|
+
//#region src/utils.ts
|
|
2914
|
+
function createPromiseWithResolvers() {
|
|
2915
|
+
let resolve;
|
|
2916
|
+
let reject;
|
|
2917
|
+
return {
|
|
2918
|
+
promise: new Promise((res, rej) => {
|
|
2919
|
+
resolve = res;
|
|
2920
|
+
reject = rej;
|
|
2921
|
+
}),
|
|
2922
|
+
resolve,
|
|
2923
|
+
reject
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
const random = Math.random.bind(Math);
|
|
2927
|
+
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
2928
|
+
function nanoid(size = 21) {
|
|
2929
|
+
let id = "";
|
|
2930
|
+
let i = size;
|
|
2931
|
+
while (i--) id += urlAlphabet[random() * 64 | 0];
|
|
2932
|
+
return id;
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
//#endregion
|
|
2936
|
+
//#region src/main.ts
|
|
2937
|
+
const DEFAULT_TIMEOUT = 6e4;
|
|
2938
|
+
const defaultSerialize = (i) => i;
|
|
2939
|
+
const defaultDeserialize = defaultSerialize;
|
|
2940
|
+
const { clearTimeout, setTimeout: setTimeout$1 } = globalThis;
|
|
2941
|
+
function createBirpc($functions, options) {
|
|
2942
|
+
const { post, on, off = () => {}, eventNames = [], serialize = defaultSerialize, deserialize = defaultDeserialize, resolver, bind = "rpc", timeout = DEFAULT_TIMEOUT, proxify = true } = options;
|
|
2943
|
+
let $closed = false;
|
|
2944
|
+
const _rpcPromiseMap = /* @__PURE__ */ new Map();
|
|
2945
|
+
let _promiseInit;
|
|
2946
|
+
let rpc;
|
|
2947
|
+
async function _call(method, args, event, optional) {
|
|
2948
|
+
if ($closed) throw new Error(`[birpc] rpc is closed, cannot call "${method}"`);
|
|
2949
|
+
const req = {
|
|
2950
|
+
m: method,
|
|
2951
|
+
a: args,
|
|
2952
|
+
t: TYPE_REQUEST
|
|
2953
|
+
};
|
|
2954
|
+
if (optional) req.o = true;
|
|
2955
|
+
const send = async (_req) => post(serialize(_req));
|
|
2956
|
+
if (event) {
|
|
2957
|
+
await send(req);
|
|
2958
|
+
return;
|
|
2959
|
+
}
|
|
2960
|
+
if (_promiseInit) try {
|
|
2961
|
+
await _promiseInit;
|
|
2962
|
+
} finally {
|
|
2963
|
+
_promiseInit = void 0;
|
|
2964
|
+
}
|
|
2965
|
+
let { promise, resolve, reject } = createPromiseWithResolvers();
|
|
2966
|
+
const id = nanoid();
|
|
2967
|
+
req.i = id;
|
|
2968
|
+
let timeoutId;
|
|
2969
|
+
async function handler(newReq = req) {
|
|
2970
|
+
if (timeout >= 0) {
|
|
2971
|
+
timeoutId = setTimeout$1(() => {
|
|
2972
|
+
try {
|
|
2973
|
+
if (options.onTimeoutError?.call(rpc, method, args) !== true) throw new Error(`[birpc] timeout on calling "${method}"`);
|
|
2974
|
+
} catch (e) {
|
|
2975
|
+
reject(e);
|
|
2976
|
+
}
|
|
2977
|
+
_rpcPromiseMap.delete(id);
|
|
2978
|
+
}, timeout);
|
|
2979
|
+
if (typeof timeoutId === "object") timeoutId = timeoutId.unref?.();
|
|
2980
|
+
}
|
|
2981
|
+
_rpcPromiseMap.set(id, {
|
|
2982
|
+
resolve,
|
|
2983
|
+
reject,
|
|
2984
|
+
timeoutId,
|
|
2985
|
+
method
|
|
2986
|
+
});
|
|
2987
|
+
await send(newReq);
|
|
2988
|
+
return promise;
|
|
2989
|
+
}
|
|
2990
|
+
try {
|
|
2991
|
+
if (options.onRequest) await options.onRequest.call(rpc, req, handler, resolve);
|
|
2992
|
+
else await handler();
|
|
2993
|
+
} catch (e) {
|
|
2994
|
+
if (options.onGeneralError?.call(rpc, e) !== true) throw e;
|
|
2995
|
+
return;
|
|
2996
|
+
} finally {
|
|
2997
|
+
clearTimeout(timeoutId);
|
|
2998
|
+
_rpcPromiseMap.delete(id);
|
|
2999
|
+
}
|
|
3000
|
+
return promise;
|
|
3001
|
+
}
|
|
3002
|
+
const builtinMethods = {
|
|
3003
|
+
$call: (method, ...args) => _call(method, args, false),
|
|
3004
|
+
$callOptional: (method, ...args) => _call(method, args, false, true),
|
|
3005
|
+
$callEvent: (method, ...args) => _call(method, args, true),
|
|
3006
|
+
$callRaw: (options$1) => _call(options$1.method, options$1.args, options$1.event, options$1.optional),
|
|
3007
|
+
$rejectPendingCalls,
|
|
3008
|
+
get $closed() {
|
|
3009
|
+
return $closed;
|
|
3010
|
+
},
|
|
3011
|
+
get $meta() {
|
|
3012
|
+
return options.meta;
|
|
3013
|
+
},
|
|
3014
|
+
$close,
|
|
3015
|
+
$functions
|
|
3016
|
+
};
|
|
3017
|
+
if (proxify) rpc = new Proxy({}, { get(_, method) {
|
|
3018
|
+
if (Object.prototype.hasOwnProperty.call(builtinMethods, method)) return builtinMethods[method];
|
|
3019
|
+
if (method === "then" && !eventNames.includes("then") && !("then" in $functions)) return void 0;
|
|
3020
|
+
const sendEvent = (...args) => _call(method, args, true);
|
|
3021
|
+
if (eventNames.includes(method)) {
|
|
3022
|
+
sendEvent.asEvent = sendEvent;
|
|
3023
|
+
return sendEvent;
|
|
3024
|
+
}
|
|
3025
|
+
const sendCall = (...args) => _call(method, args, false);
|
|
3026
|
+
sendCall.asEvent = sendEvent;
|
|
3027
|
+
return sendCall;
|
|
3028
|
+
} });
|
|
3029
|
+
else rpc = builtinMethods;
|
|
3030
|
+
function $close(customError) {
|
|
3031
|
+
$closed = true;
|
|
3032
|
+
_rpcPromiseMap.forEach(({ reject, method }) => {
|
|
3033
|
+
const error = /* @__PURE__ */ new Error(`[birpc] rpc is closed, cannot call "${method}"`);
|
|
3034
|
+
if (customError) {
|
|
3035
|
+
customError.cause ??= error;
|
|
3036
|
+
return reject(customError);
|
|
3037
|
+
}
|
|
3038
|
+
reject(error);
|
|
3039
|
+
});
|
|
3040
|
+
_rpcPromiseMap.clear();
|
|
3041
|
+
off(onMessage);
|
|
3042
|
+
}
|
|
3043
|
+
function $rejectPendingCalls(handler) {
|
|
3044
|
+
const handlerResults = Array.from(_rpcPromiseMap.values()).map(({ method, reject }) => {
|
|
3045
|
+
if (!handler) return reject(/* @__PURE__ */ new Error(`[birpc]: rejected pending call "${method}".`));
|
|
3046
|
+
return handler({
|
|
3047
|
+
method,
|
|
3048
|
+
reject
|
|
3049
|
+
});
|
|
3050
|
+
});
|
|
3051
|
+
_rpcPromiseMap.clear();
|
|
3052
|
+
return handlerResults;
|
|
3053
|
+
}
|
|
3054
|
+
async function onMessage(data, ...extra) {
|
|
3055
|
+
let msg;
|
|
3056
|
+
try {
|
|
3057
|
+
msg = deserialize(data);
|
|
3058
|
+
} catch (e) {
|
|
3059
|
+
if (options.onGeneralError?.call(rpc, e) !== true) throw e;
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
if (msg.t === TYPE_REQUEST) {
|
|
3063
|
+
const { m: method, a: args, o: optional } = msg;
|
|
3064
|
+
let result, error;
|
|
3065
|
+
let fn = await (resolver ? resolver.call(rpc, method, $functions[method]) : $functions[method]);
|
|
3066
|
+
if (optional) fn ||= () => void 0;
|
|
3067
|
+
if (!fn) error = /* @__PURE__ */ new Error(`[birpc] function "${method}" not found`);
|
|
3068
|
+
else try {
|
|
3069
|
+
result = await fn.apply(bind === "rpc" ? rpc : $functions, args);
|
|
3070
|
+
} catch (e) {
|
|
3071
|
+
error = e;
|
|
3072
|
+
}
|
|
3073
|
+
if (msg.i) {
|
|
3074
|
+
if (error && options.onFunctionError) {
|
|
3075
|
+
if (options.onFunctionError.call(rpc, error, method, args) === true) return;
|
|
3076
|
+
}
|
|
3077
|
+
if (!error) try {
|
|
3078
|
+
await post(serialize({
|
|
3079
|
+
t: TYPE_RESPONSE,
|
|
3080
|
+
i: msg.i,
|
|
3081
|
+
r: result
|
|
3082
|
+
}), ...extra);
|
|
3083
|
+
return;
|
|
3084
|
+
} catch (e) {
|
|
3085
|
+
error = e;
|
|
3086
|
+
if (options.onGeneralError?.call(rpc, e, method, args) !== true) throw e;
|
|
3087
|
+
}
|
|
3088
|
+
try {
|
|
3089
|
+
await post(serialize({
|
|
3090
|
+
t: TYPE_RESPONSE,
|
|
3091
|
+
i: msg.i,
|
|
3092
|
+
e: error
|
|
3093
|
+
}), ...extra);
|
|
3094
|
+
} catch (e) {
|
|
3095
|
+
if (options.onGeneralError?.call(rpc, e, method, args) !== true) throw e;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
} else {
|
|
3099
|
+
const { i: ack, r: result, e: error } = msg;
|
|
3100
|
+
const promise = _rpcPromiseMap.get(ack);
|
|
3101
|
+
if (promise) {
|
|
3102
|
+
clearTimeout(promise.timeoutId);
|
|
3103
|
+
if (error) promise.reject(error);
|
|
3104
|
+
else promise.resolve(result);
|
|
3105
|
+
}
|
|
3106
|
+
_rpcPromiseMap.delete(ack);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
_promiseInit = on(onMessage);
|
|
3110
|
+
return rpc;
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
const debug = createDebugger("vitest:browser:api");
|
|
3114
|
+
const BROWSER_API_PATH = "/__vitest_browser_api__";
|
|
3115
|
+
function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
3116
|
+
const vite = globalServer.vite;
|
|
3117
|
+
const vitest = globalServer.vitest;
|
|
3118
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
3119
|
+
vite.httpServer?.on("upgrade", (request, socket, head) => {
|
|
3120
|
+
if (!request.url) {
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
const { pathname, searchParams } = new URL(request.url, "http://localhost");
|
|
3124
|
+
if (pathname !== BROWSER_API_PATH) {
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
if (!isValidApiRequest(vitest.config, request)) {
|
|
3128
|
+
socket.destroy();
|
|
3129
|
+
return;
|
|
3130
|
+
}
|
|
3131
|
+
const type = searchParams.get("type");
|
|
3132
|
+
const rpcId = searchParams.get("rpcId");
|
|
3133
|
+
const sessionId = searchParams.get("sessionId");
|
|
3134
|
+
const projectName = searchParams.get("projectName");
|
|
3135
|
+
if (type !== "tester" && type !== "orchestrator") {
|
|
3136
|
+
return error(new Error(`[vitest] Type query in ${request.url} is invalid. Type should be either "tester" or "orchestrator".`));
|
|
3137
|
+
}
|
|
3138
|
+
if (!sessionId || !rpcId || projectName == null) {
|
|
3139
|
+
return error(new Error(`[vitest] Invalid URL ${request.url}. "projectName", "sessionId" and "rpcId" queries are required.`));
|
|
3140
|
+
}
|
|
3141
|
+
const sessions = vitest._browserSessions;
|
|
3142
|
+
if (!sessions.sessionIds.has(sessionId)) {
|
|
3143
|
+
const ids = [...sessions.sessionIds].join(", ");
|
|
3144
|
+
return error(new Error(`[vitest] Unknown session id "${sessionId}". Expected one of ${ids}.`));
|
|
3145
|
+
}
|
|
3146
|
+
if (type === "orchestrator") {
|
|
3147
|
+
const session = sessions.getSession(sessionId);
|
|
3148
|
+
// it's possible the session was already resolved by the preview provider
|
|
3149
|
+
session?.connected();
|
|
3150
|
+
}
|
|
3151
|
+
const project = vitest.getProjectByName(projectName);
|
|
3152
|
+
if (!project) {
|
|
3153
|
+
return error(new Error(`[vitest] Project "${projectName}" not found.`));
|
|
3154
|
+
}
|
|
3155
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
3156
|
+
wss.emit("connection", ws, request);
|
|
3157
|
+
const { rpc, offCancel } = setupClient(project, rpcId, ws);
|
|
3158
|
+
const state = project.browser.state;
|
|
3159
|
+
const clients = type === "tester" ? state.testers : state.orchestrators;
|
|
3160
|
+
clients.set(rpcId, rpc);
|
|
3161
|
+
debug?.("[%s] Browser API connected to %s", rpcId, type);
|
|
3162
|
+
ws.on("close", () => {
|
|
3163
|
+
debug?.("[%s] Browser API disconnected from %s", rpcId, type);
|
|
3164
|
+
offCancel();
|
|
3165
|
+
clients.delete(rpcId);
|
|
3166
|
+
globalServer.removeCDPHandler(rpcId);
|
|
3167
|
+
if (type === "orchestrator") {
|
|
3168
|
+
sessions.destroySession(sessionId);
|
|
3169
|
+
}
|
|
3170
|
+
// this will reject any hanging methods if there are any
|
|
3171
|
+
rpc.$close(new Error(`[vitest] Browser connection was closed while running tests. Was the page closed unexpectedly?`));
|
|
3172
|
+
});
|
|
3173
|
+
});
|
|
3174
|
+
});
|
|
3175
|
+
// we don't throw an error inside a stream because this can segfault the process
|
|
3176
|
+
function error(err) {
|
|
3177
|
+
console.error(err);
|
|
3178
|
+
vitest.state.catchError(err, "RPC Error");
|
|
3179
|
+
}
|
|
3180
|
+
function checkFileAccess(path) {
|
|
3181
|
+
if (!isFileServingAllowed(path, vite)) {
|
|
3182
|
+
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
function setupClient(project, rpcId, ws) {
|
|
3186
|
+
const mockResolver = new ServerMockResolver(globalServer.vite, { moduleDirectories: project.config?.deps?.moduleDirectories });
|
|
3187
|
+
const mocker = project.browser?.provider.mocker;
|
|
3188
|
+
const rpc = createBirpc({
|
|
3189
|
+
async onUnhandledError(error, type) {
|
|
3190
|
+
if (error && typeof error === "object") {
|
|
3191
|
+
const _error = error;
|
|
3192
|
+
_error.stacks = globalServer.parseErrorStacktrace(_error);
|
|
3193
|
+
}
|
|
3194
|
+
vitest.state.catchError(error, type);
|
|
3195
|
+
},
|
|
3196
|
+
async onQueued(method, file) {
|
|
3197
|
+
if (method === "collect") {
|
|
3198
|
+
vitest.state.collectFiles(project, [file]);
|
|
3199
|
+
} else {
|
|
3200
|
+
await vitest._testRun.enqueued(project, file);
|
|
3201
|
+
}
|
|
3202
|
+
},
|
|
3203
|
+
async onCollected(method, files) {
|
|
3204
|
+
if (method === "collect") {
|
|
3205
|
+
vitest.state.collectFiles(project, files);
|
|
3206
|
+
} else {
|
|
3207
|
+
await vitest._testRun.collected(project, files);
|
|
3208
|
+
}
|
|
3209
|
+
},
|
|
3210
|
+
async onTaskArtifactRecord(id, artifact) {
|
|
3211
|
+
return vitest._testRun.recordArtifact(id, artifact);
|
|
3212
|
+
},
|
|
3213
|
+
async onTaskUpdate(method, packs, events) {
|
|
3214
|
+
if (method === "collect") {
|
|
3215
|
+
vitest.state.updateTasks(packs);
|
|
3216
|
+
} else {
|
|
3217
|
+
await vitest._testRun.updated(packs, events);
|
|
3218
|
+
}
|
|
3219
|
+
},
|
|
3220
|
+
onAfterSuiteRun(meta) {
|
|
3221
|
+
vitest.coverageProvider?.onAfterSuiteRun(meta);
|
|
3222
|
+
},
|
|
3223
|
+
async sendLog(method, log) {
|
|
3224
|
+
if (method === "collect") {
|
|
3225
|
+
vitest.state.updateUserLog(log);
|
|
3226
|
+
} else {
|
|
3227
|
+
await vitest._testRun.log(log);
|
|
3228
|
+
}
|
|
3229
|
+
},
|
|
3230
|
+
resolveSnapshotPath(testPath) {
|
|
3231
|
+
return vitest.snapshot.resolvePath(testPath, { config: project.serializedConfig });
|
|
3232
|
+
},
|
|
3233
|
+
resolveSnapshotRawPath(testPath, rawPath) {
|
|
3234
|
+
return vitest.snapshot.resolveRawPath(testPath, rawPath);
|
|
3235
|
+
},
|
|
3236
|
+
snapshotSaved(snapshot) {
|
|
3237
|
+
vitest.snapshot.add(snapshot);
|
|
3238
|
+
},
|
|
3239
|
+
async readSnapshotFile(snapshotPath) {
|
|
3240
|
+
checkFileAccess(snapshotPath);
|
|
3241
|
+
if (!existsSync(snapshotPath)) {
|
|
3242
|
+
return null;
|
|
3243
|
+
}
|
|
3244
|
+
return promises.readFile(snapshotPath, "utf-8");
|
|
3245
|
+
},
|
|
3246
|
+
async saveSnapshotFile(id, content) {
|
|
3247
|
+
checkFileAccess(id);
|
|
3248
|
+
await promises.mkdir(dirname(id), { recursive: true });
|
|
3249
|
+
return promises.writeFile(id, content, "utf-8");
|
|
3250
|
+
},
|
|
3251
|
+
async removeSnapshotFile(id) {
|
|
3252
|
+
checkFileAccess(id);
|
|
3253
|
+
if (!existsSync(id)) {
|
|
3254
|
+
throw new Error(`Snapshot file "${id}" does not exist.`);
|
|
3255
|
+
}
|
|
3256
|
+
return promises.unlink(id);
|
|
3257
|
+
},
|
|
3258
|
+
getBrowserFileSourceMap(id) {
|
|
3259
|
+
const mod = globalServer.vite.moduleGraph.getModuleById(id);
|
|
3260
|
+
const result = mod?.transformResult;
|
|
3261
|
+
// this can happen for bundled dependencies in node_modules/.vite
|
|
3262
|
+
if (result && !result.map) {
|
|
3263
|
+
const sourceMapUrl = retrieveSourceMapURL(result.code);
|
|
3264
|
+
if (!sourceMapUrl) {
|
|
3265
|
+
return null;
|
|
3266
|
+
}
|
|
3267
|
+
const filepathDir = dirname(id);
|
|
3268
|
+
const sourceMapPath = resolve(filepathDir, sourceMapUrl);
|
|
3269
|
+
try {
|
|
3270
|
+
const map = JSON.parse(readFileSync(sourceMapPath, "utf-8"));
|
|
3271
|
+
return map;
|
|
3272
|
+
} catch {
|
|
3273
|
+
return null;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
return result?.map;
|
|
3277
|
+
},
|
|
3278
|
+
cancelCurrentRun(reason) {
|
|
3279
|
+
vitest.cancelCurrentRun(reason);
|
|
3280
|
+
},
|
|
3281
|
+
async resolveId(id, importer) {
|
|
3282
|
+
return mockResolver.resolveId(id, importer);
|
|
3283
|
+
},
|
|
3284
|
+
debug(...args) {
|
|
3285
|
+
vitest.logger.console.debug(...args);
|
|
3286
|
+
},
|
|
3287
|
+
getCountOfFailedTests() {
|
|
3288
|
+
return vitest.state.getCountOfFailedTests();
|
|
3289
|
+
},
|
|
3290
|
+
async wdioSwitchContext(direction) {
|
|
3291
|
+
const provider = project.browser.provider;
|
|
3292
|
+
if (!provider) {
|
|
3293
|
+
throw new Error("Commands are only available for browser tests.");
|
|
3294
|
+
}
|
|
3295
|
+
if (provider.name !== "webdriverio") {
|
|
3296
|
+
throw new Error("Switch context is only available for WebDriverIO provider.");
|
|
3297
|
+
}
|
|
3298
|
+
if (direction === "iframe") {
|
|
3299
|
+
await provider.switchToTestFrame();
|
|
3300
|
+
} else {
|
|
3301
|
+
await provider.switchToMainFrame();
|
|
3302
|
+
}
|
|
3303
|
+
},
|
|
3304
|
+
async triggerCommand(sessionId, command, testPath, payload) {
|
|
3305
|
+
debug?.("[%s] Triggering command \"%s\"", sessionId, command);
|
|
3306
|
+
const provider = project.browser.provider;
|
|
3307
|
+
if (!provider) {
|
|
3308
|
+
throw new Error("Commands are only available for browser tests.");
|
|
3309
|
+
}
|
|
3310
|
+
const context = Object.assign({
|
|
3311
|
+
testPath,
|
|
3312
|
+
project,
|
|
3313
|
+
provider,
|
|
3314
|
+
contextId: sessionId,
|
|
3315
|
+
sessionId,
|
|
3316
|
+
triggerCommand: (name, ...args) => {
|
|
3317
|
+
return project.browser.triggerCommand(name, context, ...args);
|
|
3318
|
+
}
|
|
3319
|
+
}, provider.getCommandsContext(sessionId));
|
|
3320
|
+
return await project.browser.triggerCommand(command, context, ...payload);
|
|
3321
|
+
},
|
|
3322
|
+
resolveMock(rawId, importer, options) {
|
|
3323
|
+
return mockResolver.resolveMock(rawId, importer, options);
|
|
3324
|
+
},
|
|
3325
|
+
invalidate(ids) {
|
|
3326
|
+
return mockResolver.invalidate(ids);
|
|
3327
|
+
},
|
|
3328
|
+
async registerMock(sessionId, module) {
|
|
3329
|
+
if (!mocker) {
|
|
3330
|
+
// make sure modules are not processed yet in case they were imported before
|
|
3331
|
+
// and were not mocked
|
|
3332
|
+
mockResolver.invalidate([module.id]);
|
|
3333
|
+
if (module.type === "manual") {
|
|
3334
|
+
const mock = ManualMockedModule.fromJSON(module, async () => {
|
|
3335
|
+
try {
|
|
3336
|
+
const { keys } = await rpc.resolveManualMock(module.url);
|
|
3337
|
+
return Object.fromEntries(keys.map((key) => [key, null]));
|
|
3338
|
+
} catch (err) {
|
|
3339
|
+
vitest.state.catchError(err, "Manual Mock Resolver Error");
|
|
3340
|
+
return {};
|
|
3341
|
+
}
|
|
3342
|
+
});
|
|
3343
|
+
defaultMockerRegistry.add(mock);
|
|
3344
|
+
} else {
|
|
3345
|
+
if (module.type === "redirect") {
|
|
3346
|
+
const redirectUrl = new URL(module.redirect);
|
|
3347
|
+
module.redirect = join(vite.config.root, redirectUrl.pathname);
|
|
3348
|
+
}
|
|
3349
|
+
defaultMockerRegistry.register(module);
|
|
3350
|
+
}
|
|
3351
|
+
return;
|
|
3352
|
+
}
|
|
3353
|
+
if (module.type === "manual") {
|
|
3354
|
+
const manualModule = ManualMockedModule.fromJSON(module, async () => {
|
|
3355
|
+
const { keys } = await rpc.resolveManualMock(module.url);
|
|
3356
|
+
return Object.fromEntries(keys.map((key) => [key, null]));
|
|
3357
|
+
});
|
|
3358
|
+
await mocker.register(sessionId, manualModule);
|
|
3359
|
+
} else if (module.type === "redirect") {
|
|
3360
|
+
await mocker.register(sessionId, RedirectedModule.fromJSON(module));
|
|
3361
|
+
} else if (module.type === "automock") {
|
|
3362
|
+
await mocker.register(sessionId, AutomockedModule.fromJSON(module));
|
|
3363
|
+
} else if (module.type === "autospy") {
|
|
3364
|
+
await mocker.register(sessionId, AutospiedModule.fromJSON(module));
|
|
3365
|
+
}
|
|
3366
|
+
},
|
|
3367
|
+
clearMocks(sessionId) {
|
|
3368
|
+
if (!mocker) {
|
|
3369
|
+
return defaultMockerRegistry.clear();
|
|
3370
|
+
}
|
|
3371
|
+
return mocker.clear(sessionId);
|
|
3372
|
+
},
|
|
3373
|
+
unregisterMock(sessionId, id) {
|
|
3374
|
+
if (!mocker) {
|
|
3375
|
+
return defaultMockerRegistry.delete(id);
|
|
3376
|
+
}
|
|
3377
|
+
return mocker.delete(sessionId, id);
|
|
3378
|
+
},
|
|
3379
|
+
async sendCdpEvent(sessionId, event, payload) {
|
|
3380
|
+
const cdp = await globalServer.ensureCDPHandler(sessionId, rpcId);
|
|
3381
|
+
return cdp.send(event, payload);
|
|
3382
|
+
},
|
|
3383
|
+
async trackCdpEvent(sessionId, type, event, listenerId) {
|
|
3384
|
+
const cdp = await globalServer.ensureCDPHandler(sessionId, rpcId);
|
|
3385
|
+
cdp[type](event, listenerId);
|
|
3386
|
+
}
|
|
3387
|
+
}, {
|
|
3388
|
+
post: (msg) => ws.send(msg),
|
|
3389
|
+
on: (fn) => ws.on("message", fn),
|
|
3390
|
+
eventNames: ["onCancel", "cdpEvent"],
|
|
3391
|
+
serialize: (data) => stringify(data, stringifyReplace),
|
|
3392
|
+
deserialize: parse,
|
|
3393
|
+
timeout: -1
|
|
3394
|
+
});
|
|
3395
|
+
const offCancel = vitest.onCancel((reason) => rpc.onCancel(reason));
|
|
3396
|
+
return {
|
|
3397
|
+
rpc,
|
|
3398
|
+
offCancel
|
|
3399
|
+
};
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
function retrieveSourceMapURL(source) {
|
|
3403
|
+
const re = /\/\/[@#]\s*sourceMappingURL=([^\s'"]+)\s*$|\/\*[@#]\s*sourceMappingURL=[^\s*'"]+\s*\*\/\s*$/gm;
|
|
3404
|
+
// keep executing the search to find the *last* sourceMappingURL to avoid
|
|
3405
|
+
// picking up sourceMappingURLs from comments, strings, etc.
|
|
3406
|
+
let lastMatch, match;
|
|
3407
|
+
// eslint-disable-next-line no-cond-assign
|
|
3408
|
+
while (match = re.exec(source)) {
|
|
3409
|
+
lastMatch = match;
|
|
3410
|
+
}
|
|
3411
|
+
if (!lastMatch) {
|
|
3412
|
+
return null;
|
|
3413
|
+
}
|
|
3414
|
+
return lastMatch[1];
|
|
3415
|
+
}
|
|
3416
|
+
// Serialization support utils.
|
|
3417
|
+
function cloneByOwnProperties(value) {
|
|
3418
|
+
// Clones the value's properties into a new Object. The simpler approach of
|
|
3419
|
+
// Object.assign() won't work in the case that properties are not enumerable.
|
|
3420
|
+
return Object.getOwnPropertyNames(value).reduce((clone, prop) => ({
|
|
3421
|
+
...clone,
|
|
3422
|
+
[prop]: value[prop]
|
|
3423
|
+
}), {});
|
|
3424
|
+
}
|
|
3425
|
+
/**
|
|
3426
|
+
* Replacer function for serialization methods such as JS.stringify() or
|
|
3427
|
+
* flatted.stringify().
|
|
3428
|
+
*/
|
|
3429
|
+
function stringifyReplace(key, value) {
|
|
3430
|
+
if (value instanceof Error) {
|
|
3431
|
+
const cloned = cloneByOwnProperties(value);
|
|
3432
|
+
return {
|
|
3433
|
+
name: value.name,
|
|
3434
|
+
message: value.message,
|
|
3435
|
+
stack: value.stack,
|
|
3436
|
+
...cloned
|
|
3437
|
+
};
|
|
3438
|
+
} else {
|
|
3439
|
+
return value;
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
function defineBrowserCommand(fn) {
|
|
3444
|
+
return fn;
|
|
3445
|
+
}
|
|
3446
|
+
const createBrowserServer = async (options) => {
|
|
3447
|
+
const project = options.project;
|
|
3448
|
+
const configFile = project.vite.config.configFile;
|
|
3449
|
+
if (project.vitest.version !== version) {
|
|
3450
|
+
project.vitest.logger.warn(c.yellow(`Loaded ${c.inverse(c.yellow(` vitest@${project.vitest.version} `))} and ${c.inverse(c.yellow(` @vitest/browser@${version} `))}.` + "\nRunning mixed versions is not supported and may lead into bugs" + "\nUpdate your dependencies and make sure the versions match."));
|
|
3451
|
+
}
|
|
3452
|
+
const server = new ParentBrowserProject(project, "/");
|
|
3453
|
+
const configPath = typeof configFile === "string" ? configFile : false;
|
|
3454
|
+
const logLevel = process.env.VITEST_BROWSER_DEBUG ?? "info";
|
|
3455
|
+
const logger = createViteLogger(project.vitest.logger, logLevel, { allowClearScreen: false });
|
|
3456
|
+
const mockerRegistry = new MockerRegistry();
|
|
3457
|
+
let cacheDir;
|
|
3458
|
+
const vite = await createViteServer({
|
|
3459
|
+
...project.options,
|
|
3460
|
+
define: project.config.viteDefine,
|
|
3461
|
+
base: "/",
|
|
3462
|
+
root: project.config.root,
|
|
3463
|
+
logLevel,
|
|
3464
|
+
customLogger: {
|
|
3465
|
+
...logger,
|
|
3466
|
+
info(msg, options) {
|
|
3467
|
+
logger.info(msg, options);
|
|
3468
|
+
if (msg.includes("optimized dependencies changed. reloading")) {
|
|
3469
|
+
logger.warn([c.yellow(`\n${c.bold("[vitest]")} Vite unexpectedly reloaded a test. This may cause tests to fail, lead to flaky behaviour or duplicated test runs.\n`), c.yellow(`For a stable experience, please add mentioned dependencies to your config\'s ${c.bold("`optimizeDeps.include`")} field manually.\n\n`)].join(""));
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
},
|
|
3473
|
+
mode: project.config.mode,
|
|
3474
|
+
configFile: configPath,
|
|
3475
|
+
configLoader: project.vite.config.inlineConfig.configLoader,
|
|
3476
|
+
server: {
|
|
3477
|
+
hmr: false,
|
|
3478
|
+
watch: null
|
|
3479
|
+
},
|
|
3480
|
+
cacheDir: project.vite.config.cacheDir,
|
|
3481
|
+
plugins: [
|
|
3482
|
+
{
|
|
3483
|
+
name: "vitest-internal:browser-cacheDir",
|
|
3484
|
+
configResolved(config) {
|
|
3485
|
+
cacheDir = config.cacheDir;
|
|
3486
|
+
}
|
|
3487
|
+
},
|
|
3488
|
+
...options.mocksPlugins({ filter(id) {
|
|
3489
|
+
if (id.includes(distRoot) || id.includes(cacheDir)) {
|
|
3490
|
+
return false;
|
|
3491
|
+
}
|
|
3492
|
+
return true;
|
|
3493
|
+
} }),
|
|
3494
|
+
options.metaEnvReplacer(),
|
|
3495
|
+
...project.options?.plugins || [],
|
|
3496
|
+
BrowserPlugin(server),
|
|
3497
|
+
interceptorPlugin({ registry: mockerRegistry }),
|
|
3498
|
+
options.coveragePlugin()
|
|
3499
|
+
]
|
|
3500
|
+
});
|
|
3501
|
+
await vite.listen();
|
|
3502
|
+
setupBrowserRpc(server, mockerRegistry);
|
|
3503
|
+
return server;
|
|
3504
|
+
};
|
|
3505
|
+
function defineBrowserProvider(options) {
|
|
3506
|
+
return {
|
|
3507
|
+
...options,
|
|
3508
|
+
options: options.options || {},
|
|
3509
|
+
serverFactory: createBrowserServer
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
export { createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };
|