@vitest/browser 2.0.0-beta.1 → 2.0.0-beta.10
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/context.d.ts +127 -0
- package/dist/client/.vite/manifest.json +24 -0
- package/dist/client/__vitest__/assets/index-BMAciMM5.js +51 -0
- package/dist/client/__vitest__/assets/index-BcFb8Rbc.css +1 -0
- package/dist/client/__vitest__/index.html +2 -2
- package/dist/client/__vitest_browser__/orchestrator-BCpOi5ot.js +301 -0
- package/dist/client/__vitest_browser__/{rpc-By4jD8av.js → rpc-CImfI7bO.js} +279 -388
- package/dist/client/__vitest_browser__/{tester-CrKhlp5g.js → tester-e70VCOgC.js} +351 -44
- package/dist/client/esm-client-injector.js +14 -28
- package/dist/client/{index.html → orchestrator.html} +8 -7
- package/dist/client/tester.html +4 -3
- package/dist/context.js +87 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +402 -265
- package/dist/providers.js +12 -112
- package/dist/webdriver-CXn0ag9T.js +170 -0
- package/package.json +21 -11
- package/providers/playwright.d.ts +14 -2
- package/providers/webdriverio.d.ts +4 -0
- package/providers.d.ts +5 -5
- package/dist/client/__vitest__/assets/index-BMbN3lBu.js +0 -51
- package/dist/client/__vitest__/assets/index-D0Wp7rbG.css +0 -1
- package/dist/client/__vitest_browser__/main-DmAU-Uff.js +0 -102
package/dist/index.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, readFile as readFile$1 } from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import sirv from 'sirv';
|
|
5
|
+
import { isFileServingAllowed, getFilePoolName, distDir } from 'vitest/node';
|
|
4
6
|
import { coverageConfigDefaults } from 'vitest/config';
|
|
5
|
-
import { slash } from '@vitest/utils';
|
|
7
|
+
import { slash, toArray } from '@vitest/utils';
|
|
8
|
+
import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-CXn0ag9T.js';
|
|
9
|
+
import fs, { promises } from 'node:fs';
|
|
10
|
+
import { resolve as resolve$1, dirname as dirname$1, normalize as normalize$1 } from 'node:path';
|
|
6
11
|
import MagicString from 'magic-string';
|
|
7
12
|
import { esmWalker } from '@vitest/utils/ast';
|
|
8
13
|
|
|
@@ -17,6 +22,7 @@ function normalizeWindowsPath(input = "") {
|
|
|
17
22
|
const _UNC_REGEX = /^[/\\]{2}/;
|
|
18
23
|
const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
|
|
19
24
|
const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
|
|
25
|
+
const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
|
|
20
26
|
const normalize = function(path) {
|
|
21
27
|
if (path.length === 0) {
|
|
22
28
|
return ".";
|
|
@@ -151,78 +157,261 @@ function normalizeString(path, allowAboveRoot) {
|
|
|
151
157
|
const isAbsolute = function(p) {
|
|
152
158
|
return _IS_ABSOLUTE_RE.test(p);
|
|
153
159
|
};
|
|
160
|
+
const relative = function(from, to) {
|
|
161
|
+
const _from = resolve(from).replace(_ROOT_FOLDER_RE, "$1").split("/");
|
|
162
|
+
const _to = resolve(to).replace(_ROOT_FOLDER_RE, "$1").split("/");
|
|
163
|
+
if (_to[0][1] === ":" && _from[0][1] === ":" && _from[0] !== _to[0]) {
|
|
164
|
+
return _to.join("/");
|
|
165
|
+
}
|
|
166
|
+
const _fromCopy = [..._from];
|
|
167
|
+
for (const segment of _fromCopy) {
|
|
168
|
+
if (_to[0] !== segment) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
_from.shift();
|
|
172
|
+
_to.shift();
|
|
173
|
+
}
|
|
174
|
+
return [..._from.map(() => ".."), ..._to].join("/");
|
|
175
|
+
};
|
|
176
|
+
const dirname = function(p) {
|
|
177
|
+
const segments = normalizeWindowsPath(p).replace(/\/$/, "").split("/").slice(0, -1);
|
|
178
|
+
if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {
|
|
179
|
+
segments[0] += "/";
|
|
180
|
+
}
|
|
181
|
+
return segments.join("/") || (isAbsolute(p) ? "/" : ".");
|
|
182
|
+
};
|
|
154
183
|
const basename = function(p, extension) {
|
|
155
184
|
const lastSegment = normalizeWindowsPath(p).split("/").pop();
|
|
156
185
|
return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
|
|
157
186
|
};
|
|
158
187
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
nodes.push(param);
|
|
176
|
-
break;
|
|
177
|
-
|
|
178
|
-
case 'MemberExpression':
|
|
179
|
-
let object = param;
|
|
180
|
-
while (object.type === 'MemberExpression') {
|
|
181
|
-
object = /** @type {any} */ (object.object);
|
|
182
|
-
}
|
|
183
|
-
nodes.push(/** @type {any} */ (object));
|
|
184
|
-
break;
|
|
185
|
-
|
|
186
|
-
case 'ObjectPattern':
|
|
187
|
-
for (const prop of param.properties) {
|
|
188
|
-
if (prop.type === 'RestElement') {
|
|
189
|
-
extract_identifiers(prop.argument, nodes);
|
|
190
|
-
} else {
|
|
191
|
-
extract_identifiers(prop.value, nodes);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
188
|
+
const click = async (context, xpath, options = {}) => {
|
|
189
|
+
const provider = context.provider;
|
|
190
|
+
if (provider instanceof PlaywrightBrowserProvider) {
|
|
191
|
+
const tester = context.tester;
|
|
192
|
+
await tester.locator(`xpath=${xpath}`).click(options);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (provider instanceof WebdriverBrowserProvider) {
|
|
196
|
+
const page = provider.browser;
|
|
197
|
+
const markedXpath = `//${xpath}`;
|
|
198
|
+
const element = await page.$(markedXpath);
|
|
199
|
+
await element.click(options);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
throw new Error(`Provider "${provider.name}" doesn't support click command`);
|
|
203
|
+
};
|
|
194
204
|
|
|
195
|
-
|
|
205
|
+
function assertFileAccess(path, project) {
|
|
206
|
+
if (!isFileServingAllowed(path, project.server) && !isFileServingAllowed(path, project.ctx.server))
|
|
207
|
+
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
208
|
+
}
|
|
209
|
+
const readFile = async ({ project, testPath = process.cwd() }, path, options = {}) => {
|
|
210
|
+
const filepath = resolve$1(dirname$1(testPath), path);
|
|
211
|
+
assertFileAccess(filepath, project);
|
|
212
|
+
if (typeof options === "object" && !options.encoding)
|
|
213
|
+
options.encoding = "utf-8";
|
|
214
|
+
return promises.readFile(filepath, options);
|
|
215
|
+
};
|
|
216
|
+
const writeFile = async ({ project, testPath = process.cwd() }, path, data, options) => {
|
|
217
|
+
const filepath = resolve$1(dirname$1(testPath), path);
|
|
218
|
+
assertFileAccess(filepath, project);
|
|
219
|
+
const dir = dirname$1(filepath);
|
|
220
|
+
if (!fs.existsSync(dir))
|
|
221
|
+
await promises.mkdir(dir, { recursive: true });
|
|
222
|
+
await promises.writeFile(filepath, data, options);
|
|
223
|
+
};
|
|
224
|
+
const removeFile = async ({ project, testPath = process.cwd() }, path) => {
|
|
225
|
+
const filepath = resolve$1(dirname$1(testPath), path);
|
|
226
|
+
assertFileAccess(filepath, project);
|
|
227
|
+
await promises.rm(filepath);
|
|
228
|
+
};
|
|
196
229
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
230
|
+
function isObject(payload) {
|
|
231
|
+
return payload != null && typeof payload === "object";
|
|
232
|
+
}
|
|
233
|
+
function isSendKeysPayload(payload) {
|
|
234
|
+
const validOptions = ["type", "press", "down", "up"];
|
|
235
|
+
if (!isObject(payload))
|
|
236
|
+
throw new Error("You must provide a `SendKeysPayload` object");
|
|
237
|
+
const numberOfValidOptions = Object.keys(payload).filter(
|
|
238
|
+
(key) => validOptions.includes(key)
|
|
239
|
+
).length;
|
|
240
|
+
const unknownOptions = Object.keys(payload).filter((key) => !validOptions.includes(key));
|
|
241
|
+
if (numberOfValidOptions > 1) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`You must provide ONLY one of the following properties to pass to the browser runner: ${validOptions.join(
|
|
244
|
+
", "
|
|
245
|
+
)}.`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (numberOfValidOptions === 0) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`You must provide one of the following properties to pass to the browser runner: ${validOptions.join(
|
|
251
|
+
", "
|
|
252
|
+
)}.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (unknownOptions.length > 0)
|
|
256
|
+
throw new Error(`Unknown options \`${unknownOptions.join(", ")}\` present.`);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
function isTypePayload(payload) {
|
|
260
|
+
return "type" in payload;
|
|
261
|
+
}
|
|
262
|
+
function isPressPayload(payload) {
|
|
263
|
+
return "press" in payload;
|
|
264
|
+
}
|
|
265
|
+
function isDownPayload(payload) {
|
|
266
|
+
return "down" in payload;
|
|
267
|
+
}
|
|
268
|
+
function isUpPayload(payload) {
|
|
269
|
+
return "up" in payload;
|
|
270
|
+
}
|
|
271
|
+
const sendKeys = async ({ provider, contextId }, payload) => {
|
|
272
|
+
if (!isSendKeysPayload(payload) || !payload)
|
|
273
|
+
throw new Error("You must provide a `SendKeysPayload` object");
|
|
274
|
+
if (provider instanceof PlaywrightBrowserProvider) {
|
|
275
|
+
const page = provider.getPage(contextId);
|
|
276
|
+
if (isTypePayload(payload))
|
|
277
|
+
await page.keyboard.type(payload.type);
|
|
278
|
+
else if (isPressPayload(payload))
|
|
279
|
+
await page.keyboard.press(payload.press);
|
|
280
|
+
else if (isDownPayload(payload))
|
|
281
|
+
await page.keyboard.down(payload.down);
|
|
282
|
+
else if (isUpPayload(payload))
|
|
283
|
+
await page.keyboard.up(payload.up);
|
|
284
|
+
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
285
|
+
const browser = provider.browser;
|
|
286
|
+
if (isTypePayload(payload))
|
|
287
|
+
await browser.keys(payload.type.split(""));
|
|
288
|
+
else if (isPressPayload(payload))
|
|
289
|
+
await browser.keys([payload.press]);
|
|
290
|
+
else
|
|
291
|
+
throw new Error('Only "press" and "type" are supported by webdriverio.');
|
|
292
|
+
} else {
|
|
293
|
+
throw new TypeError(`"sendKeys" is not supported for ${provider.name} browser provider.`);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
201
296
|
|
|
202
|
-
|
|
297
|
+
const screenshot = async (context, name, options = {}) => {
|
|
298
|
+
if (!context.testPath)
|
|
299
|
+
throw new Error(`Cannot take a screenshot without a test path`);
|
|
300
|
+
const path = resolveScreenshotPath(context.testPath, name, context.project.config);
|
|
301
|
+
const savePath = normalize$1(path);
|
|
302
|
+
await mkdir(dirname(path), { recursive: true });
|
|
303
|
+
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
304
|
+
if (options.element) {
|
|
305
|
+
const { element: elementXpath, ...config } = options;
|
|
306
|
+
const iframe = context.tester;
|
|
307
|
+
const element = iframe.locator(`xpath=${elementXpath}`);
|
|
308
|
+
await element.screenshot({ ...config, path: savePath });
|
|
309
|
+
} else {
|
|
310
|
+
await context.body.screenshot({ ...options, path: savePath });
|
|
311
|
+
}
|
|
312
|
+
return path;
|
|
313
|
+
}
|
|
314
|
+
if (context.provider instanceof WebdriverBrowserProvider) {
|
|
315
|
+
const page = context.provider.browser;
|
|
316
|
+
if (!options.element) {
|
|
317
|
+
const body = await page.$("body");
|
|
318
|
+
await body.saveScreenshot(savePath);
|
|
319
|
+
return path;
|
|
320
|
+
}
|
|
321
|
+
const xpath = `//${options.element}`;
|
|
322
|
+
const element = await page.$(xpath);
|
|
323
|
+
await element.saveScreenshot(savePath);
|
|
324
|
+
return path;
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Provider "${context.provider.name}" does not support screenshots`);
|
|
327
|
+
};
|
|
328
|
+
function resolveScreenshotPath(testPath, name, config) {
|
|
329
|
+
const dir = dirname(testPath);
|
|
330
|
+
const base = basename(testPath);
|
|
331
|
+
if (config.browser.screenshotDirectory) {
|
|
332
|
+
return resolve(
|
|
333
|
+
config.browser.screenshotDirectory,
|
|
334
|
+
relative(config.root, dir),
|
|
335
|
+
base,
|
|
336
|
+
name
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
return resolve(dir, "__screenshots__", base, name);
|
|
340
|
+
}
|
|
203
341
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
342
|
+
var builtinCommands = {
|
|
343
|
+
readFile,
|
|
344
|
+
removeFile,
|
|
345
|
+
writeFile,
|
|
346
|
+
sendKeys,
|
|
347
|
+
__vitest_click: click,
|
|
348
|
+
__vitest_screenshot: screenshot
|
|
349
|
+
};
|
|
207
350
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
351
|
+
const VIRTUAL_ID_CONTEXT = "\0@vitest/browser/context";
|
|
352
|
+
const ID_CONTEXT = "@vitest/browser/context";
|
|
353
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
354
|
+
function BrowserContext(project) {
|
|
355
|
+
project.config.browser.commands ??= {};
|
|
356
|
+
for (const [name, command] of Object.entries(builtinCommands))
|
|
357
|
+
project.config.browser.commands[name] ??= command;
|
|
358
|
+
for (const command in project.config.browser.commands) {
|
|
359
|
+
if (!/^[a-z_$][\w$]*$/i.test(command))
|
|
360
|
+
throw new Error(`Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
name: "vitest:browser:virtual-module:context",
|
|
364
|
+
enforce: "pre",
|
|
365
|
+
resolveId(id) {
|
|
366
|
+
if (id === ID_CONTEXT)
|
|
367
|
+
return VIRTUAL_ID_CONTEXT;
|
|
368
|
+
},
|
|
369
|
+
load(id) {
|
|
370
|
+
if (id === VIRTUAL_ID_CONTEXT)
|
|
371
|
+
return generateContextFile.call(this, project);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
async function generateContextFile(project) {
|
|
376
|
+
const commands = Object.keys(project.config.browser.commands ?? {});
|
|
377
|
+
const filepathCode = "__vitest_worker__.filepath || __vitest_worker__.current?.file?.filepath || undefined";
|
|
378
|
+
const provider = project.browserProvider;
|
|
379
|
+
const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
|
|
380
|
+
return ` ["${command}"]: (...args) => rpc().triggerCommand(contextId, "${command}", filepath(), args),`;
|
|
381
|
+
}).join("\n");
|
|
382
|
+
const userEventNonProviderImport = await getUserEventImport(provider, this.resolve.bind(this));
|
|
383
|
+
const distContextPath = slash(`/@fs/${resolve(__dirname, "context.js")}`);
|
|
384
|
+
return `
|
|
385
|
+
import { page, userEvent as __userEvent_CDP__ } from '${distContextPath}'
|
|
386
|
+
${userEventNonProviderImport}
|
|
387
|
+
const filepath = () => ${filepathCode}
|
|
388
|
+
const rpc = () => __vitest_worker__.rpc
|
|
389
|
+
const contextId = __vitest_browser_runner__.contextId
|
|
212
390
|
|
|
213
|
-
|
|
391
|
+
export const server = {
|
|
392
|
+
platform: ${JSON.stringify(process.platform)},
|
|
393
|
+
version: ${JSON.stringify(process.version)},
|
|
394
|
+
provider: ${JSON.stringify(provider.name)},
|
|
395
|
+
browser: ${JSON.stringify(project.config.browser.name)},
|
|
396
|
+
commands: {
|
|
397
|
+
${commandsCode}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
export const commands = server.commands
|
|
401
|
+
export const userEvent = ${provider.name === "preview" ? "__vitest_user_event__" : "__userEvent_CDP__"}
|
|
402
|
+
export { page }
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
405
|
+
async function getUserEventImport(provider, resolve2) {
|
|
406
|
+
if (provider.name !== "preview")
|
|
407
|
+
return "";
|
|
408
|
+
const resolved = await resolve2("@testing-library/user-event", __dirname);
|
|
409
|
+
if (!resolved)
|
|
410
|
+
throw new Error(`Failed to resolve user-event package from ${__dirname}`);
|
|
411
|
+
return `import { userEvent as __vitest_user_event__ } from '${slash(`/@fs/${resolved.id}`)}'`;
|
|
214
412
|
}
|
|
215
413
|
|
|
216
|
-
|
|
217
|
-
const viExportAllHelper = "__vitest_browser_runner__.exportAll";
|
|
218
|
-
const skipHijack = [
|
|
219
|
-
"/@vite/client",
|
|
220
|
-
"/@vite/env",
|
|
221
|
-
/vite\/dist\/client/
|
|
222
|
-
];
|
|
223
|
-
function injectVitestModule(code, id, parse) {
|
|
224
|
-
if (skipHijack.some((skip) => id.match(skip)))
|
|
225
|
-
return;
|
|
414
|
+
function injectDynamicImport(code, id, parse) {
|
|
226
415
|
const s = new MagicString(code);
|
|
227
416
|
let ast;
|
|
228
417
|
try {
|
|
@@ -232,165 +421,16 @@ function injectVitestModule(code, id, parse) {
|
|
|
232
421
|
${err.message}`);
|
|
233
422
|
return;
|
|
234
423
|
}
|
|
235
|
-
let uid = 0;
|
|
236
|
-
const idToImportMap = /* @__PURE__ */ new Map();
|
|
237
|
-
const declaredConst = /* @__PURE__ */ new Set();
|
|
238
|
-
const hoistIndex = 0;
|
|
239
|
-
const transformImportDeclaration = (node) => {
|
|
240
|
-
const source = node.source.value;
|
|
241
|
-
if (skipHijack.some((skip) => source.match(skip)))
|
|
242
|
-
return null;
|
|
243
|
-
const importId = `__vi_esm_${uid++}__`;
|
|
244
|
-
const hasSpecifiers = node.specifiers.length > 0;
|
|
245
|
-
const code2 = hasSpecifiers ? `import { ${viInjectedKey} as ${importId} } from '${source}'
|
|
246
|
-
` : `import '${source}'
|
|
247
|
-
`;
|
|
248
|
-
return {
|
|
249
|
-
code: code2,
|
|
250
|
-
id: importId
|
|
251
|
-
};
|
|
252
|
-
};
|
|
253
|
-
function defineImport(node) {
|
|
254
|
-
const declaration = transformImportDeclaration(node);
|
|
255
|
-
if (!declaration)
|
|
256
|
-
return null;
|
|
257
|
-
s.appendLeft(hoistIndex, declaration.code);
|
|
258
|
-
return declaration.id;
|
|
259
|
-
}
|
|
260
|
-
function defineImportAll(source) {
|
|
261
|
-
const importId = `__vi_esm_${uid++}__`;
|
|
262
|
-
s.appendLeft(hoistIndex, `const { ${viInjectedKey}: ${importId} } = await import(${JSON.stringify(source)});
|
|
263
|
-
`);
|
|
264
|
-
return importId;
|
|
265
|
-
}
|
|
266
|
-
function defineExport(position, name, local = name) {
|
|
267
|
-
s.appendLeft(
|
|
268
|
-
position,
|
|
269
|
-
`
|
|
270
|
-
Object.defineProperty(${viInjectedKey}, "${name}", { enumerable: true, configurable: true, get(){ return ${local} }});`
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
for (const node of ast.body) {
|
|
274
|
-
if (node.type === "ImportDeclaration") {
|
|
275
|
-
const importId = defineImport(node);
|
|
276
|
-
if (!importId)
|
|
277
|
-
continue;
|
|
278
|
-
s.remove(node.start, node.end);
|
|
279
|
-
for (const spec of node.specifiers) {
|
|
280
|
-
if (spec.type === "ImportSpecifier") {
|
|
281
|
-
idToImportMap.set(
|
|
282
|
-
spec.local.name,
|
|
283
|
-
`${importId}.${spec.imported.name}`
|
|
284
|
-
);
|
|
285
|
-
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
286
|
-
idToImportMap.set(spec.local.name, `${importId}.default`);
|
|
287
|
-
} else {
|
|
288
|
-
idToImportMap.set(spec.local.name, importId);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
for (const node of ast.body) {
|
|
294
|
-
if (node.type === "ExportNamedDeclaration") {
|
|
295
|
-
if (node.declaration) {
|
|
296
|
-
if (node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration") {
|
|
297
|
-
defineExport(node.end, node.declaration.id.name);
|
|
298
|
-
} else {
|
|
299
|
-
for (const declaration of node.declaration.declarations) {
|
|
300
|
-
const names = extract_names(declaration.id);
|
|
301
|
-
for (const name of names)
|
|
302
|
-
defineExport(node.end, name);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
s.remove(node.start, node.declaration.start);
|
|
306
|
-
} else {
|
|
307
|
-
s.remove(node.start, node.end);
|
|
308
|
-
if (node.source) {
|
|
309
|
-
const importId = defineImportAll(node.source.value);
|
|
310
|
-
for (const spec of node.specifiers) {
|
|
311
|
-
defineExport(
|
|
312
|
-
hoistIndex,
|
|
313
|
-
spec.exported.name,
|
|
314
|
-
`${importId}.${spec.local.name}`
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
} else {
|
|
318
|
-
for (const spec of node.specifiers) {
|
|
319
|
-
const local = spec.local.name;
|
|
320
|
-
const binding = idToImportMap.get(local);
|
|
321
|
-
defineExport(node.end, spec.exported.name, binding || local);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (node.type === "ExportDefaultDeclaration") {
|
|
327
|
-
const expressionTypes = ["FunctionExpression", "ClassExpression"];
|
|
328
|
-
if ("id" in node.declaration && node.declaration.id && !expressionTypes.includes(node.declaration.type)) {
|
|
329
|
-
const { name } = node.declaration.id;
|
|
330
|
-
s.remove(
|
|
331
|
-
node.start,
|
|
332
|
-
node.start + 15
|
|
333
|
-
/* 'export default '.length */
|
|
334
|
-
);
|
|
335
|
-
s.append(
|
|
336
|
-
`
|
|
337
|
-
Object.defineProperty(${viInjectedKey}, "default", { enumerable: true, configurable: true, value: ${name} });`
|
|
338
|
-
);
|
|
339
|
-
} else {
|
|
340
|
-
s.update(
|
|
341
|
-
node.start,
|
|
342
|
-
node.start + 14,
|
|
343
|
-
`${viInjectedKey}.default =`
|
|
344
|
-
);
|
|
345
|
-
s.append(`
|
|
346
|
-
export default { ${viInjectedKey}: ${viInjectedKey}.default };
|
|
347
|
-
`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
if (node.type === "ExportAllDeclaration") {
|
|
351
|
-
s.remove(node.start, node.end);
|
|
352
|
-
const importId = defineImportAll(node.source.value);
|
|
353
|
-
if (node.exported)
|
|
354
|
-
defineExport(hoistIndex, node.exported.name, `${importId}`);
|
|
355
|
-
else
|
|
356
|
-
s.appendLeft(hoistIndex, `${viExportAllHelper}(${viInjectedKey}, ${importId});
|
|
357
|
-
`);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
424
|
esmWalker(ast, {
|
|
361
|
-
onIdentifier(id2, info, parentStack) {
|
|
362
|
-
const binding = idToImportMap.get(id2.name);
|
|
363
|
-
if (!binding)
|
|
364
|
-
return;
|
|
365
|
-
if (info.hasBindingShortcut) {
|
|
366
|
-
s.appendLeft(id2.end, `: ${binding}`);
|
|
367
|
-
} else if (info.classDeclaration) {
|
|
368
|
-
if (!declaredConst.has(id2.name)) {
|
|
369
|
-
declaredConst.add(id2.name);
|
|
370
|
-
const topNode = parentStack[parentStack.length - 2];
|
|
371
|
-
s.prependRight(topNode.start, `const ${id2.name} = ${binding};
|
|
372
|
-
`);
|
|
373
|
-
}
|
|
374
|
-
} else if (
|
|
375
|
-
// don't transform class name identifier
|
|
376
|
-
!info.classExpression
|
|
377
|
-
) {
|
|
378
|
-
s.update(id2.start, id2.end, binding);
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
425
|
// TODO: make env updatable
|
|
382
426
|
onImportMeta() {
|
|
383
427
|
},
|
|
384
428
|
onDynamicImport(node) {
|
|
385
|
-
const replace = "__vitest_browser_runner__.wrapModule(import(";
|
|
429
|
+
const replace = "__vitest_browser_runner__.wrapModule(() => import(";
|
|
386
430
|
s.overwrite(node.start, node.source.start, replace);
|
|
387
431
|
s.overwrite(node.end - 1, node.end, "))");
|
|
388
432
|
}
|
|
389
433
|
});
|
|
390
|
-
s.prepend(`const ${viInjectedKey} = { [Symbol.toStringTag]: "Module" };
|
|
391
|
-
`);
|
|
392
|
-
s.append(`
|
|
393
|
-
export { ${viInjectedKey} }`);
|
|
394
434
|
return {
|
|
395
435
|
ast,
|
|
396
436
|
code: s.toString(),
|
|
@@ -398,6 +438,23 @@ export { ${viInjectedKey} }`);
|
|
|
398
438
|
};
|
|
399
439
|
}
|
|
400
440
|
|
|
441
|
+
const regexDynamicImport = /import\s*\(/;
|
|
442
|
+
var DynamicImport = () => {
|
|
443
|
+
return {
|
|
444
|
+
name: "vitest:browser:esm-injector",
|
|
445
|
+
enforce: "post",
|
|
446
|
+
transform(source, id) {
|
|
447
|
+
if (!regexDynamicImport.test(source))
|
|
448
|
+
return;
|
|
449
|
+
return injectDynamicImport(source, id, this.parse);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
function defineBrowserCommand(fn) {
|
|
455
|
+
return fn;
|
|
456
|
+
}
|
|
457
|
+
|
|
401
458
|
var index = (project, base = "/") => {
|
|
402
459
|
const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
|
|
403
460
|
const distRoot = resolve(pkgRoot, "dist");
|
|
@@ -413,9 +470,12 @@ var index = (project, base = "/") => {
|
|
|
413
470
|
}
|
|
414
471
|
},
|
|
415
472
|
async configureServer(server) {
|
|
416
|
-
const testerHtml = readFile(resolve(distRoot, "client/tester.html"), "utf8");
|
|
417
|
-
const
|
|
418
|
-
const injectorJs = readFile(resolve(distRoot, "client/esm-client-injector.js"), "utf8");
|
|
473
|
+
const testerHtml = readFile$1(resolve(distRoot, "client/tester.html"), "utf8");
|
|
474
|
+
const orchestratorHtml = project.config.browser.ui ? readFile$1(resolve(distRoot, "client/__vitest__/index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8");
|
|
475
|
+
const injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8");
|
|
476
|
+
const manifest = (async () => {
|
|
477
|
+
return JSON.parse(await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8"));
|
|
478
|
+
})();
|
|
419
479
|
const favicon = `${base}favicon.svg`;
|
|
420
480
|
const testerPrefix = `${base}__vitest_test__/__test__/`;
|
|
421
481
|
server.middlewares.use((_req, res, next) => {
|
|
@@ -426,7 +486,7 @@ var index = (project, base = "/") => {
|
|
|
426
486
|
}
|
|
427
487
|
next();
|
|
428
488
|
});
|
|
429
|
-
let
|
|
489
|
+
let orchestratorScripts;
|
|
430
490
|
let testerScripts;
|
|
431
491
|
server.middlewares.use(async (req, res, next) => {
|
|
432
492
|
if (!req.url)
|
|
@@ -436,29 +496,70 @@ var index = (project, base = "/") => {
|
|
|
436
496
|
return next();
|
|
437
497
|
res.setHeader("Cache-Control", "no-cache, max-age=0, must-revalidate");
|
|
438
498
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
439
|
-
const files = project.browserState?.files ?? [];
|
|
440
499
|
const config = wrapConfig(project.getSerializableConfig());
|
|
441
500
|
config.env ??= {};
|
|
442
501
|
config.env.VITEST_BROWSER_DEBUG = process.env.VITEST_BROWSER_DEBUG || "";
|
|
443
|
-
|
|
444
|
-
__VITEST_CONFIG__: JSON.stringify(config),
|
|
445
|
-
__VITEST_FILES__: JSON.stringify(files)
|
|
446
|
-
});
|
|
502
|
+
res.removeHeader("X-Frame-Options");
|
|
447
503
|
if (url.pathname === base) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
504
|
+
let contextId2 = url.searchParams.get("contextId");
|
|
505
|
+
if (!contextId2)
|
|
506
|
+
contextId2 = project.browserState.keys().next().value ?? "none";
|
|
507
|
+
const files2 = project.browserState.get(contextId2)?.files ?? [];
|
|
508
|
+
const injector2 = replacer(await injectorJs, {
|
|
509
|
+
__VITEST_CONFIG__: JSON.stringify(config),
|
|
510
|
+
__VITEST_VITE_CONFIG__: JSON.stringify({
|
|
511
|
+
root: project.browser.config.root
|
|
512
|
+
}),
|
|
513
|
+
__VITEST_FILES__: JSON.stringify(files2),
|
|
514
|
+
__VITEST_TYPE__: url.pathname === base ? '"orchestrator"' : '"tester"',
|
|
515
|
+
__VITEST_CONTEXT_ID__: JSON.stringify(contextId2)
|
|
516
|
+
});
|
|
517
|
+
res.removeHeader("Content-Security-Policy");
|
|
518
|
+
if (!orchestratorScripts)
|
|
519
|
+
orchestratorScripts = await formatScripts(project.config.browser.orchestratorScripts, server);
|
|
520
|
+
let baseHtml = await orchestratorHtml;
|
|
521
|
+
if (project.config.browser.ui) {
|
|
522
|
+
const manifestContent = await manifest;
|
|
523
|
+
const jsEntry = manifestContent["orchestrator.html"].file;
|
|
524
|
+
baseHtml = baseHtml.replaceAll("./assets/", `${base}__vitest__/assets/`).replace(
|
|
525
|
+
"<!-- !LOAD_METADATA! -->",
|
|
526
|
+
[
|
|
527
|
+
"<script>{__VITEST_INJECTOR__}<\/script>",
|
|
528
|
+
"{__VITEST_SCRIPTS__}",
|
|
529
|
+
`<script type="module" crossorigin src="${jsEntry}"><\/script>`
|
|
530
|
+
].join("\n")
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
const html2 = replacer(baseHtml, {
|
|
451
534
|
__VITEST_FAVICON__: favicon,
|
|
452
535
|
__VITEST_TITLE__: "Vitest Browser Runner",
|
|
453
|
-
__VITEST_SCRIPTS__:
|
|
454
|
-
__VITEST_INJECTOR__:
|
|
536
|
+
__VITEST_SCRIPTS__: orchestratorScripts,
|
|
537
|
+
__VITEST_INJECTOR__: injector2,
|
|
538
|
+
__VITEST_CONTEXT_ID__: JSON.stringify(contextId2)
|
|
455
539
|
});
|
|
456
540
|
res.write(html2, "utf-8");
|
|
457
541
|
res.end();
|
|
458
542
|
return;
|
|
459
543
|
}
|
|
460
|
-
const
|
|
461
|
-
|
|
544
|
+
const csp = res.getHeader("Content-Security-Policy");
|
|
545
|
+
if (typeof csp === "string") {
|
|
546
|
+
res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
|
|
547
|
+
}
|
|
548
|
+
const [contextId, testFile] = url.pathname.slice(testerPrefix.length).split("/");
|
|
549
|
+
const decodedTestFile = decodeURIComponent(testFile);
|
|
550
|
+
const testFiles = await project.globTestFiles();
|
|
551
|
+
const tests = decodedTestFile === "__vitest_all__" || !testFiles.includes(decodedTestFile) ? "__vitest_browser_runner__.files" : JSON.stringify([decodedTestFile]);
|
|
552
|
+
const iframeId = JSON.stringify(decodedTestFile);
|
|
553
|
+
const files = project.browserState.get(contextId)?.files ?? [];
|
|
554
|
+
const injector = replacer(await injectorJs, {
|
|
555
|
+
__VITEST_CONFIG__: JSON.stringify(config),
|
|
556
|
+
__VITEST_FILES__: JSON.stringify(files),
|
|
557
|
+
__VITEST_VITE_CONFIG__: JSON.stringify({
|
|
558
|
+
root: project.browser.config.root
|
|
559
|
+
}),
|
|
560
|
+
__VITEST_TYPE__: url.pathname === base ? '"orchestrator"' : '"tester"',
|
|
561
|
+
__VITEST_CONTEXT_ID__: JSON.stringify(contextId)
|
|
562
|
+
});
|
|
462
563
|
if (!testerScripts)
|
|
463
564
|
testerScripts = await formatScripts(project.config.browser.testerScripts, server);
|
|
464
565
|
const html = replacer(await testerHtml, {
|
|
@@ -470,6 +571,7 @@ var index = (project, base = "/") => {
|
|
|
470
571
|
// TODO: have only a single global variable to not pollute the global scope
|
|
471
572
|
`<script type="module">
|
|
472
573
|
__vitest_browser_runner__.runningFiles = ${tests}
|
|
574
|
+
__vitest_browser_runner__.iframeId = ${iframeId}
|
|
473
575
|
__vitest_browser_runner__.runTests(__vitest_browser_runner__.runningFiles)
|
|
474
576
|
<\/script>`
|
|
475
577
|
)
|
|
@@ -501,24 +603,21 @@ var index = (project, base = "/") => {
|
|
|
501
603
|
name: "vitest:browser:tests",
|
|
502
604
|
enforce: "pre",
|
|
503
605
|
async config() {
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
606
|
+
const allTestFiles = await project.globTestFiles();
|
|
607
|
+
const browserTestFiles = allTestFiles.filter((file) => getFilePoolName(project, file) === "browser");
|
|
608
|
+
const setupFiles = toArray(project.config.setupFiles);
|
|
609
|
+
const vitestPaths = [
|
|
610
|
+
resolve(distDir, "index.js"),
|
|
611
|
+
resolve(distDir, "browser.js"),
|
|
612
|
+
resolve(distDir, "runners.js"),
|
|
613
|
+
resolve(distDir, "utils.js")
|
|
614
|
+
];
|
|
513
615
|
return {
|
|
514
616
|
optimizeDeps: {
|
|
515
617
|
entries: [
|
|
516
|
-
...
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
"vitest/browser",
|
|
520
|
-
"vitest/runners",
|
|
521
|
-
"@vitest/utils"
|
|
618
|
+
...browserTestFiles,
|
|
619
|
+
...setupFiles,
|
|
620
|
+
...vitestPaths
|
|
522
621
|
],
|
|
523
622
|
exclude: [
|
|
524
623
|
"vitest",
|
|
@@ -526,31 +625,35 @@ var index = (project, base = "/") => {
|
|
|
526
625
|
"vitest/browser",
|
|
527
626
|
"vitest/runners",
|
|
528
627
|
"@vitest/utils",
|
|
529
|
-
|
|
530
|
-
"
|
|
628
|
+
"@vitest/runner",
|
|
629
|
+
"@vitest/spy",
|
|
630
|
+
"@vitest/utils/error",
|
|
631
|
+
"@vitest/snapshot",
|
|
632
|
+
"@vitest/expect",
|
|
633
|
+
"std-env",
|
|
634
|
+
"tinybench",
|
|
635
|
+
"tinyspy",
|
|
636
|
+
"pathe",
|
|
637
|
+
"msw",
|
|
638
|
+
"msw/browser"
|
|
531
639
|
],
|
|
532
640
|
include: [
|
|
533
641
|
"vitest > @vitest/utils > pretty-format",
|
|
534
642
|
"vitest > @vitest/snapshot > pretty-format",
|
|
535
643
|
"vitest > @vitest/snapshot > magic-string",
|
|
536
|
-
"vitest > diff-sequences",
|
|
537
644
|
"vitest > pretty-format",
|
|
538
645
|
"vitest > pretty-format > ansi-styles",
|
|
539
646
|
"vitest > pretty-format > ansi-regex",
|
|
540
|
-
"vitest > chai"
|
|
647
|
+
"vitest > chai",
|
|
648
|
+
"vitest > chai > loupe",
|
|
649
|
+
"vitest > @vitest/runner > p-limit",
|
|
650
|
+
"vitest > @vitest/utils > diff-sequences",
|
|
651
|
+
"@vitest/browser > @testing-library/user-event",
|
|
652
|
+
"@vitest/browser > @testing-library/dom"
|
|
541
653
|
]
|
|
542
654
|
}
|
|
543
655
|
};
|
|
544
656
|
},
|
|
545
|
-
transform(code, id) {
|
|
546
|
-
if (id.includes("loupe/loupe.js")) {
|
|
547
|
-
const exportsList = ["custom", "inspect", "registerConstructor", "registerStringTag"];
|
|
548
|
-
const codeAppend = exportsList.map((i) => `export const ${i} = globalThis.loupe.${i}`).join("\n");
|
|
549
|
-
return `${code}
|
|
550
|
-
${codeAppend}
|
|
551
|
-
export default globalThis.loupe`;
|
|
552
|
-
}
|
|
553
|
-
},
|
|
554
657
|
async resolveId(id) {
|
|
555
658
|
if (!/\?browserv=\w+$/.test(id))
|
|
556
659
|
return;
|
|
@@ -563,13 +666,47 @@ export default globalThis.loupe`;
|
|
|
563
666
|
}
|
|
564
667
|
},
|
|
565
668
|
{
|
|
566
|
-
name: "vitest:browser:
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
669
|
+
name: "vitest:browser:resolve-virtual",
|
|
670
|
+
async resolveId(rawId) {
|
|
671
|
+
if (rawId.startsWith("/__virtual_vitest__:")) {
|
|
672
|
+
let id = rawId.slice("/__virtual_vitest__:".length);
|
|
673
|
+
if (id === "mocker-worker.js")
|
|
674
|
+
id = "msw/mockServiceWorker.js";
|
|
675
|
+
const resolved = await this.resolve(
|
|
676
|
+
id,
|
|
677
|
+
distRoot,
|
|
678
|
+
{
|
|
679
|
+
skipSelf: true
|
|
680
|
+
}
|
|
681
|
+
);
|
|
682
|
+
return resolved;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
BrowserContext(project),
|
|
687
|
+
DynamicImport(),
|
|
688
|
+
// TODO: remove this when @testing-library/vue supports ESM
|
|
689
|
+
{
|
|
690
|
+
name: "vitest:browser:support-vue-testing-library",
|
|
691
|
+
config() {
|
|
692
|
+
return {
|
|
693
|
+
optimizeDeps: {
|
|
694
|
+
esbuildOptions: {
|
|
695
|
+
plugins: [
|
|
696
|
+
{
|
|
697
|
+
name: "test-utils-rewrite",
|
|
698
|
+
setup(build) {
|
|
699
|
+
const _require = createRequire(import.meta.url);
|
|
700
|
+
build.onResolve({ filter: /@vue\/test-utils/ }, (args) => {
|
|
701
|
+
const resolved = _require.resolve(args.path, { paths: [args.importer] });
|
|
702
|
+
return { path: resolved };
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
]
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
};
|
|
573
710
|
}
|
|
574
711
|
}
|
|
575
712
|
];
|
|
@@ -584,7 +721,7 @@ function resolveCoverageFolder(project) {
|
|
|
584
721
|
if (!htmlReporter)
|
|
585
722
|
return void 0;
|
|
586
723
|
const root = resolve(
|
|
587
|
-
options.root ||
|
|
724
|
+
options.root || process.cwd(),
|
|
588
725
|
options.coverage.reportsDirectory || coverageConfigDefaults.reportsDirectory
|
|
589
726
|
);
|
|
590
727
|
const subdir = Array.isArray(htmlReporter) && htmlReporter.length > 1 && "subdir" in htmlReporter[1] ? htmlReporter[1].subdir : void 0;
|
|
@@ -600,7 +737,7 @@ function wrapConfig(config) {
|
|
|
600
737
|
};
|
|
601
738
|
}
|
|
602
739
|
function replacer(code, values) {
|
|
603
|
-
return code.replace(
|
|
740
|
+
return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? "");
|
|
604
741
|
}
|
|
605
742
|
async function formatScripts(scripts, server) {
|
|
606
743
|
if (!scripts?.length)
|
|
@@ -615,4 +752,4 @@ async function formatScripts(scripts, server) {
|
|
|
615
752
|
return (await Promise.all(promises)).join("\n");
|
|
616
753
|
}
|
|
617
754
|
|
|
618
|
-
export { index as default };
|
|
755
|
+
export { index as default, defineBrowserCommand };
|