@vitest/browser 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,108 @@
1
1
  import { fileURLToPath } from 'node:url';
2
- import { resolve } from 'node:path';
3
- import { builtinModules } from 'node:module';
2
+ import { readFile } from 'node:fs/promises';
4
3
  import sirv from 'sirv';
4
+ import { coverageConfigDefaults } from 'vitest/config';
5
5
  import MagicString from 'magic-string';
6
6
  import { esmWalker } from '@vitest/utils/ast';
7
7
 
8
+ function normalizeWindowsPath(input = "") {
9
+ if (!input || !input.includes("\\")) {
10
+ return input;
11
+ }
12
+ return input.replace(/\\/g, "/");
13
+ }
14
+ const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
15
+ function cwd() {
16
+ if (typeof process !== "undefined") {
17
+ return process.cwd().replace(/\\/g, "/");
18
+ }
19
+ return "/";
20
+ }
21
+ const resolve = function(...arguments_) {
22
+ arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
23
+ let resolvedPath = "";
24
+ let resolvedAbsolute = false;
25
+ for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
26
+ const path = index >= 0 ? arguments_[index] : cwd();
27
+ if (!path || path.length === 0) {
28
+ continue;
29
+ }
30
+ resolvedPath = `${path}/${resolvedPath}`;
31
+ resolvedAbsolute = isAbsolute(path);
32
+ }
33
+ resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
34
+ if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
35
+ return `/${resolvedPath}`;
36
+ }
37
+ return resolvedPath.length > 0 ? resolvedPath : ".";
38
+ };
39
+ function normalizeString(path, allowAboveRoot) {
40
+ let res = "";
41
+ let lastSegmentLength = 0;
42
+ let lastSlash = -1;
43
+ let dots = 0;
44
+ let char = null;
45
+ for (let index = 0; index <= path.length; ++index) {
46
+ if (index < path.length) {
47
+ char = path[index];
48
+ } else if (char === "/") {
49
+ break;
50
+ } else {
51
+ char = "/";
52
+ }
53
+ if (char === "/") {
54
+ if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) {
55
+ if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
56
+ if (res.length > 2) {
57
+ const lastSlashIndex = res.lastIndexOf("/");
58
+ if (lastSlashIndex === -1) {
59
+ res = "";
60
+ lastSegmentLength = 0;
61
+ } else {
62
+ res = res.slice(0, lastSlashIndex);
63
+ lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
64
+ }
65
+ lastSlash = index;
66
+ dots = 0;
67
+ continue;
68
+ } else if (res.length > 0) {
69
+ res = "";
70
+ lastSegmentLength = 0;
71
+ lastSlash = index;
72
+ dots = 0;
73
+ continue;
74
+ }
75
+ }
76
+ if (allowAboveRoot) {
77
+ res += res.length > 0 ? "/.." : "..";
78
+ lastSegmentLength = 2;
79
+ }
80
+ } else {
81
+ if (res.length > 0) {
82
+ res += `/${path.slice(lastSlash + 1, index)}`;
83
+ } else {
84
+ res = path.slice(lastSlash + 1, index);
85
+ }
86
+ lastSegmentLength = index - lastSlash - 1;
87
+ }
88
+ lastSlash = index;
89
+ dots = 0;
90
+ } else if (char === "." && dots !== -1) {
91
+ ++dots;
92
+ } else {
93
+ dots = -1;
94
+ }
95
+ }
96
+ return res;
97
+ }
98
+ const isAbsolute = function(p) {
99
+ return _IS_ABSOLUTE_RE.test(p);
100
+ };
101
+ const basename = function(p, extension) {
102
+ const lastSegment = normalizeWindowsPath(p).split("/").pop();
103
+ return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
104
+ };
105
+
8
106
  /**
9
107
  * @param {import('estree').Node} param
10
108
  * @returns {string[]}
@@ -63,7 +161,7 @@ function extract_identifiers(param, nodes = []) {
63
161
  }
64
162
 
65
163
  const viInjectedKey = "__vi_inject__";
66
- const viExportAllHelper = "__vi_export_all__";
164
+ const viExportAllHelper = "__vitest_browser_runner__.exportAll";
67
165
  const skipHijack = [
68
166
  "/@vite/client",
69
167
  "/@vite/env",
@@ -85,7 +183,6 @@ ${err.message}`);
85
183
  const idToImportMap = /* @__PURE__ */ new Map();
86
184
  const declaredConst = /* @__PURE__ */ new Set();
87
185
  const hoistIndex = 0;
88
- let hasInjected = false;
89
186
  const transformImportDeclaration = (node) => {
90
187
  const source = node.source.value;
91
188
  if (skipHijack.some((skip) => source.match(skip)))
@@ -114,7 +211,6 @@ ${err.message}`);
114
211
  return importId;
115
212
  }
116
213
  function defineExport(position, name, local = name) {
117
- hasInjected = true;
118
214
  s.appendLeft(
119
215
  position,
120
216
  `
@@ -177,7 +273,6 @@ Object.defineProperty(${viInjectedKey}, "${name}", { enumerable: true, configura
177
273
  if (node.type === "ExportDefaultDeclaration") {
178
274
  const expressionTypes = ["FunctionExpression", "ClassExpression"];
179
275
  if ("id" in node.declaration && node.declaration.id && !expressionTypes.includes(node.declaration.type)) {
180
- hasInjected = true;
181
276
  const { name } = node.declaration.id;
182
277
  s.remove(
183
278
  node.start,
@@ -189,7 +284,6 @@ Object.defineProperty(${viInjectedKey}, "${name}", { enumerable: true, configura
189
284
  Object.defineProperty(${viInjectedKey}, "default", { enumerable: true, configurable: true, value: ${name} });`
190
285
  );
191
286
  } else {
192
- hasInjected = true;
193
287
  s.update(
194
288
  node.start,
195
289
  node.start + 14,
@@ -203,13 +297,11 @@ export default { ${viInjectedKey}: ${viInjectedKey}.default };
203
297
  if (node.type === "ExportAllDeclaration") {
204
298
  s.remove(node.start, node.end);
205
299
  const importId = defineImportAll(node.source.value);
206
- if (node.exported) {
300
+ if (node.exported)
207
301
  defineExport(hoistIndex, node.exported.name, `${importId}`);
208
- } else {
209
- hasInjected = true;
302
+ else
210
303
  s.appendLeft(hoistIndex, `${viExportAllHelper}(${viInjectedKey}, ${importId});
211
304
  `);
212
- }
213
305
  }
214
306
  }
215
307
  esmWalker(ast, {
@@ -237,17 +329,15 @@ export default { ${viInjectedKey}: ${viInjectedKey}.default };
237
329
  onImportMeta() {
238
330
  },
239
331
  onDynamicImport(node) {
240
- const replace = "__vi_wrap_module__(import(";
332
+ const replace = "__vitest_browser_runner__.wrapModule(import(";
241
333
  s.overwrite(node.start, node.source.start, replace);
242
334
  s.overwrite(node.end - 1, node.end, "))");
243
335
  }
244
336
  });
245
- if (hasInjected) {
246
- s.prepend(`const ${viInjectedKey} = { [Symbol.toStringTag]: "Module" };
337
+ s.prepend(`const ${viInjectedKey} = { [Symbol.toStringTag]: "Module" };
247
338
  `);
248
- s.append(`
339
+ s.append(`
249
340
  export { ${viInjectedKey} }`);
250
- }
251
341
  return {
252
342
  ast,
253
343
  code: s.toString(),
@@ -255,6 +345,9 @@ export { ${viInjectedKey} }`);
255
345
  };
256
346
  }
257
347
 
348
+ function replacer(code, values) {
349
+ return code.replace(/{\s*(\w+)\s*}/g, (_, key) => values[key] ?? "");
350
+ }
258
351
  var index = (project, base = "/") => {
259
352
  const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
260
353
  const distRoot = resolve(pkgRoot, "dist");
@@ -270,20 +363,80 @@ var index = (project, base = "/") => {
270
363
  }
271
364
  },
272
365
  async configureServer(server) {
366
+ const testerHtml = readFile(resolve(distRoot, "client/tester.html"), "utf8");
367
+ const runnerHtml = readFile(resolve(distRoot, "client/index.html"), "utf8");
368
+ const injectorJs = readFile(resolve(distRoot, "client/esm-client-injector.js"), "utf8");
369
+ const favicon = `${base}favicon.svg`;
370
+ const testerPrefix = `${base}__vitest_test__/__test__/`;
371
+ server.middlewares.use((_req, res, next) => {
372
+ const headers = server.config.server.headers;
373
+ if (headers) {
374
+ for (const name in headers)
375
+ res.setHeader(name, headers[name]);
376
+ }
377
+ next();
378
+ });
379
+ server.middlewares.use(async (req, res, next) => {
380
+ if (!req.url)
381
+ return next();
382
+ const url = new URL(req.url, "http://localhost");
383
+ if (!url.pathname.startsWith(testerPrefix) && url.pathname !== base)
384
+ return next();
385
+ res.setHeader("Cache-Control", "no-cache, max-age=0, must-revalidate");
386
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
387
+ const files = project.browserState?.files ?? [];
388
+ const config = wrapConfig(project.getSerializableConfig());
389
+ config.env ??= {};
390
+ config.env.VITEST_BROWSER_DEBUG = process.env.VITEST_BROWSER_DEBUG || "";
391
+ const injector = replacer(await injectorJs, {
392
+ __VITEST_CONFIG__: JSON.stringify(config),
393
+ __VITEST_FILES__: JSON.stringify(files)
394
+ });
395
+ if (url.pathname === base) {
396
+ const html2 = replacer(await runnerHtml, {
397
+ __VITEST_FAVICON__: favicon,
398
+ __VITEST_TITLE__: "Vitest Browser Runner",
399
+ __VITEST_INJECTOR__: injector
400
+ });
401
+ res.write(html2, "utf-8");
402
+ res.end();
403
+ return;
404
+ }
405
+ const decodedTestFile = decodeURIComponent(url.pathname.slice(testerPrefix.length));
406
+ const tests = decodedTestFile === "__vitest_all__" || !files.includes(decodedTestFile) ? "__vitest_browser_runner__.files" : JSON.stringify([decodedTestFile]);
407
+ const html = replacer(await testerHtml, {
408
+ __VITEST_FAVICON__: favicon,
409
+ __VITEST_TITLE__: "Vitest Browser Tester",
410
+ __VITEST_INJECTOR__: injector,
411
+ __VITEST_APPEND__: (
412
+ // TODO: have only a single global variable to not pollute the global scope
413
+ `<script type="module">
414
+ __vitest_browser_runner__.runningFiles = ${tests}
415
+ __vitest_browser_runner__.runTests(__vitest_browser_runner__.runningFiles)
416
+ <\/script>`
417
+ )
418
+ });
419
+ res.write(html, "utf-8");
420
+ res.end();
421
+ });
273
422
  server.middlewares.use(
274
423
  base,
275
424
  sirv(resolve(distRoot, "client"), {
276
425
  single: false,
277
- dev: true,
278
- setHeaders(res, _pathname, _stats) {
279
- const headers = server.config.server.headers;
280
- if (headers) {
281
- for (const name in headers)
282
- res.setHeader(name, headers[name]);
283
- }
284
- }
426
+ dev: true
285
427
  })
286
428
  );
429
+ const coverageFolder = resolveCoverageFolder(project);
430
+ const coveragePath = coverageFolder ? coverageFolder[1] : void 0;
431
+ if (coveragePath && base === coveragePath)
432
+ throw new Error(`The ui base path and the coverage path cannot be the same: ${base}, change coverage.reportsDirectory`);
433
+ coverageFolder && server.middlewares.use(coveragePath, sirv(coverageFolder[0], {
434
+ single: true,
435
+ dev: true,
436
+ setHeaders: (res) => {
437
+ res.setHeader("Cache-Control", "public,max-age=0,must-revalidate");
438
+ }
439
+ }));
287
440
  }
288
441
  },
289
442
  {
@@ -303,23 +456,25 @@ var index = (project, base = "/") => {
303
456
  optimizeDeps: {
304
457
  entries: [
305
458
  ...entries,
459
+ "vitest",
306
460
  "vitest/utils",
307
461
  "vitest/browser",
308
- "vitest/runners"
462
+ "vitest/runners",
463
+ "@vitest/utils"
309
464
  ],
310
465
  exclude: [
311
- ...builtinModules,
312
466
  "vitest",
313
467
  "vitest/utils",
314
468
  "vitest/browser",
315
469
  "vitest/runners",
316
- "@vitest/utils"
470
+ "@vitest/utils",
471
+ // loupe is manually transformed
472
+ "loupe"
317
473
  ],
318
474
  include: [
319
475
  "vitest > @vitest/utils > pretty-format",
320
476
  "vitest > @vitest/snapshot > pretty-format",
321
477
  "vitest > diff-sequences",
322
- "vitest > loupe",
323
478
  "vitest > pretty-format",
324
479
  "vitest > pretty-format > ansi-styles",
325
480
  "vitest > pretty-format > ansi-regex",
@@ -328,6 +483,15 @@ var index = (project, base = "/") => {
328
483
  }
329
484
  };
330
485
  },
486
+ transform(code, id) {
487
+ if (id.includes("loupe/loupe.js")) {
488
+ const exportsList = ["custom", "inspect", "registerConstructor", "registerStringTag"];
489
+ const codeAppend = exportsList.map((i) => `export const ${i} = globalThis.loupe.${i}`).join("\n");
490
+ return `${code}
491
+ ${codeAppend}
492
+ export default globalThis.loupe`;
493
+ }
494
+ },
331
495
  async resolveId(id) {
332
496
  if (!/\?browserv=\w+$/.test(id))
333
497
  return;
@@ -351,5 +515,30 @@ var index = (project, base = "/") => {
351
515
  }
352
516
  ];
353
517
  };
518
+ function resolveCoverageFolder(project) {
519
+ const options = project.ctx.config;
520
+ const htmlReporter = options.coverage?.enabled ? options.coverage.reporter.find((reporter) => {
521
+ if (typeof reporter === "string")
522
+ return reporter === "html";
523
+ return reporter[0] === "html";
524
+ }) : void 0;
525
+ if (!htmlReporter)
526
+ return void 0;
527
+ const root = resolve(
528
+ options.root || options.root || process.cwd(),
529
+ options.coverage.reportsDirectory || coverageConfigDefaults.reportsDirectory
530
+ );
531
+ const subdir = Array.isArray(htmlReporter) && htmlReporter.length > 1 && "subdir" in htmlReporter[1] ? htmlReporter[1].subdir : void 0;
532
+ if (!subdir || typeof subdir !== "string")
533
+ return [root, `/${basename(root)}/`];
534
+ return [resolve(root, subdir), `/${basename(root)}/${subdir}/`];
535
+ }
536
+ function wrapConfig(config) {
537
+ return {
538
+ ...config,
539
+ // workaround RegExp serialization
540
+ testNamePattern: config.testNamePattern ? config.testNamePattern.toString() : void 0
541
+ };
542
+ }
354
543
 
355
544
  export { index as default };
package/dist/providers.js CHANGED
@@ -25,17 +25,8 @@ class PlaywrightBrowserProvider {
25
25
  });
26
26
  this.cachedBrowser = browser;
27
27
  this.cachedPage = await browser.newPage(this.options?.page);
28
- this.cachedPage.on("close", () => {
29
- browser.close();
30
- });
31
28
  return this.cachedPage;
32
29
  }
33
- catchError(cb) {
34
- this.cachedPage?.on("pageerror", cb);
35
- return () => {
36
- this.cachedPage?.off("pageerror", cb);
37
- };
38
- }
39
30
  async openPage(url) {
40
31
  const browserPage = await this.openBrowserPage();
41
32
  await browserPage.goto(url);
@@ -105,11 +96,6 @@ class WebdriverBrowserProvider {
105
96
  const browserInstance = await this.openBrowser();
106
97
  await browserInstance.url(url);
107
98
  }
108
- // TODO
109
- catchError(_cb) {
110
- return () => {
111
- };
112
- }
113
99
  async close() {
114
100
  await Promise.all([
115
101
  this.cachedBrowser?.sessionId ? this.cachedBrowser?.deleteSession?.() : null
@@ -134,10 +120,6 @@ class NoneBrowserProvider {
134
120
  if (ctx.config.browser.headless)
135
121
  throw new Error(`You've enabled headless mode for "none" provider but it doesn't support it.`);
136
122
  }
137
- catchError(_cb) {
138
- return () => {
139
- };
140
- }
141
123
  async openPage(_url) {
142
124
  this.open = true;
143
125
  if (!this.ctx.browser)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/browser",
3
3
  "type": "module",
4
- "version": "1.2.1",
4
+ "version": "1.3.0",
5
5
  "description": "Browser running for Vitest",
6
6
  "license": "MIT",
7
7
  "funding": "https://opencollective.com/vitest",
@@ -42,37 +42,37 @@
42
42
  ],
43
43
  "peerDependencies": {
44
44
  "playwright": "*",
45
- "vitest": "^1.0.0",
46
- "webdriverio": "*"
45
+ "webdriverio": "*",
46
+ "vitest": "1.3.0"
47
47
  },
48
48
  "peerDependenciesMeta": {
49
- "webdriverio": {
49
+ "playwright": {
50
50
  "optional": true
51
51
  },
52
52
  "safaridriver": {
53
53
  "optional": true
54
54
  },
55
- "playwright": {
55
+ "webdriverio": {
56
56
  "optional": true
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
60
  "magic-string": "^0.30.5",
61
61
  "sirv": "^2.0.4",
62
- "@vitest/utils": "1.2.1"
62
+ "@vitest/utils": "1.3.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/ws": "^8.5.9",
66
- "@wdio/protocols": "^8.22.0",
66
+ "@wdio/protocols": "^8.29.7",
67
67
  "periscopic": "^4.0.2",
68
68
  "playwright": "^1.41.0",
69
69
  "playwright-core": "^1.41.0",
70
70
  "safaridriver": "^0.1.2",
71
- "webdriverio": "^8.22.1",
72
- "@vitest/ws-client": "1.2.1",
73
- "@vitest/runner": "1.2.1",
74
- "@vitest/ui": "1.2.1",
75
- "vitest": "1.2.1"
71
+ "webdriverio": "^8.31.1",
72
+ "@vitest/runner": "1.3.0",
73
+ "@vitest/ui": "1.3.0",
74
+ "@vitest/ws-client": "1.3.0",
75
+ "vitest": "1.3.0"
76
76
  },
77
77
  "scripts": {
78
78
  "build": "rimraf dist && pnpm build:node && pnpm build:client",
@@ -80,6 +80,6 @@
80
80
  "build:node": "rollup -c",
81
81
  "dev:client": "vite build src/client --watch",
82
82
  "dev:node": "rollup -c --watch --watch.include 'src/node/**'",
83
- "dev": "rimraf dist && run-p dev:node dev:client"
83
+ "dev": "rimraf dist && pnpm run --stream '/^dev:/'"
84
84
  }
85
85
  }