diff --git a/demo/package.json b/demo/package.json index 7ba78c26..3e368534 100644 --- a/demo/package.json +++ b/demo/package.json @@ -19,13 +19,11 @@ "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", - "vitest": "^0.34.6", - "webextension-polyfill": "^0.10.0" + "vitest": "^1.1.0" }, "devDependencies": { "@types/react": "^18.2.34", "@types/react-dom": "^18.2.14", - "@types/webextension-polyfill": "^0.10.5", "sass": "^1.69.5", "wxt": "workspace:*" } diff --git a/demo/src/entrypoints/__tests__/background.test.ts b/demo/src/entrypoints/__tests__/background.test.ts index 455122e7..42bf30a7 100644 --- a/demo/src/entrypoints/__tests__/background.test.ts +++ b/demo/src/entrypoints/__tests__/background.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import background from '../background'; browser.i18n.getMessage = () => 'fake-message'; @@ -7,6 +7,10 @@ const logMock = vi.fn(); console.log = logMock; describe('Background Entrypoint', () => { + beforeEach(() => { + fakeBrowser.reset(); + }); + it("should log the extenion's runtime ID", () => { const id = 'some-id'; fakeBrowser.runtime.id = id; @@ -15,4 +19,11 @@ describe('Background Entrypoint', () => { expect(logMock).toBeCalledWith(id); }); + + it('should set the start time in storage', async () => { + background.main(); + await new Promise((res) => setTimeout(res)); + + expect(await storage.getItem('session:startTime')).toBeDefined(); + }); }); diff --git a/docs/guide/testing.md b/docs/guide/testing.md index 58824a37..6a255f90 100644 --- a/docs/guide/testing.md +++ b/docs/guide/testing.md @@ -1,162 +1,27 @@ # Testing -WXT provides several utils for writing tests. +## Official Frameworks -## Unit tests +WXT officially supports [Vitest](https://vitest.dev/) for unit tests and either [Playwright](https://playwright.dev/) or [Puppeteer](https://pptr.dev/) for E2E tests against Chromium browsers. -If you're using auto-imports (enabled by default), [Vitest](https://vitest.dev/) is the only testing framework that supports them. +For details setting up each testing framework, see the official examples: -If you want to use a different testing library/framework (like Jest, mocha, node:test, etc), you can keep using it, but you have two options: +- [Vitest example](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-vitest#readme) +- [Playwright example](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-playwright#readme) +- [Puppeteer example](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-puppeteer#readme) -1. Switch to Vitest (recommended) -2. Configure the testing library manually - - Disable auto-imports by setting `imports: false` in your `wxt.config.ts` file - - Manually add globals normally provided by WXT (like `__BROWSER__`) that you consume to the global scope before accessing them (`globalThis.__BROWSER__ = "chrome"`) +### Unofficial Frameworks -### Vitest Setup +Puppeteer and Playwright are the only E2E test runners that support Chrome Extensions. There are no other options at the time of writing. -Install vitest and add the `WxtVitest` plugin to your `vitest.config.ts` file. +There are other options for unit tests however, like [Jest](https://jestjs.io/), [Mocha](https://mochajs.org/), or [`node:test`](https://nodejs.org/api/test.html). **_WXT does not claim to support any of them_** because none of them support all of WXT's features, like TypeScript or auto-imports. -```sh -pnpm i -D vitest -``` +If you want to try to use a different framework for unit tests, you will need to configure the environment manually: -```ts -// /vitest.config.ts -import { defineConfig } from 'vitest/config'; -import { WxtVitest } from 'wxt/testing'; +- **Auto-imports**: Add `unimport` to your test environment or disable them by setting `imports: false` in your `wxt.config.ts` file +- **`browser` mock**: Mock the `webextension-polyfill` module globally with `wxt/dist/virtual/mock-browser.js` +- **[Remote Code Bundling](/guide/remote-code)**: If you use it, configure your environment to handle the `url:` module prefix +- **Global Variables**: If you consume them, manually define globals provided by WXT (like `__BROWSER__`) by adding them to the global scope before accessing them (`globalThis.__BROWSER__ = "chrome"`) +- **Import paths**: If you use the `@/` or `~/` path aliases, add them to your test environment -export default defineConfig({ - plugins: [WxtVitest()], - test: { - server: { - deps: { - // Add any dependencies that import webextension-polyfill here, otherwise tests will attempt to import the real polyfill, breaking the - // TODO: Auto-detect these dependencies inside `WxtVitest` so maintaining this list manually isn't necessary - inline: [...], - }, - }, - }, -}); -``` - -And that's it. You're ready to start writing tests. - -### Writing Tests - -Here's a very basic test, written with a few different testing libraries, with a few different approaches for mocking the `browser` global. - -:::code-group - -```ts [Vitest] -import { describe, it, expect, vi } from 'vitest'; - -function logRuntimeId() { - // Vitest automatically mocks "browser" with "fakeBrowser" - console.log(browser.runtime.id); -} - -describe('logRuntimeId', () => { - it("should log the extension's runtime ID", () => { - // Set a known ID on fakeBrowser for the test - const id = 'some-runtime-id'; - fakeBrowser.runtime.id = id; - const logSpy = vi.spyOn(console, 'log'); - - logRuntimeId(); - - expect(logSpy).toBeCalledWith(id); - }); -}); -``` - -```ts [Jest - Manual Mock] -import { fakeBrowser } from 'wxt/testing'; -import { browser } from 'wxt/browser'; - -function logRuntimeId() { - console.log(browser.runtime.id); -} - -// Manually mock -jest.mock('wxt/browser', () => { - const { fakeBrowser } = require('wxt/testing'); - return { browser: fakeBrowser }; -}); - -describe('logRuntimeId', () => { - it("should log the extension's runtime ID", () => { - // Set a known ID on fakeBrowser for the test - const id = 'some-runtime-id'; - fakeBrowser.runtime.id = id; - const logSpy = jest.spyOn(console, 'log'); - - logRuntimeId(); - - expect(logSpy).toBeCalledWith(id); - }); -}); -``` - -```ts [node:test - Parameterized] -import { describe, it, mock } from 'node:test'; -import { assert } from 'node:assert'; -import { fakeBrowser } from 'wxt/testing'; -import { browser } from 'wxt/browser'; - -// Add browser as a parameter so fakeBrowser can be passed instead of browser -function logRuntimeId(browser = browser) { - console.log(browser.runtime.id); -} - -describe('logRuntimeId', () => { - it("should log the extension's runtime ID", () => { - // Set a known ID on fakeBrowser for the test - const id = 'some-runtime-id'; - fakeBrowser.runtime.id = id; - console.log = mock.fn(); - - // pass in fakeBrowser during tests - logRuntimeId(fakeBrowser); - - assert.deepStrictEqual(console.log.mock.calls[0].arguments, [id]); - }); -}); -``` - -::: - -:::warning -Without mocking the `browser` variable, you'll see errors like this: - -``` -This script should only be loaded in a browser extension. -``` - -::: - -WXT provides an in-memory, partial implementation of `browser`, [`fakeBrowser`](/api/wxt/testing/variables/fakeBrowser), from the [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/guide/fake-browser/) package. `fakeBrowser` works with all testing frameworks/libraries. See their docs for a list of [implemented APIs](https://webext-core.aklinker1.io/guide/fake-browser/implemented-apis.html) and more example tests. - -## E2E Tests - -WXT does not provide any utils for running E2E tests. There are two libraries you can use to run E2E tests for any chrome extension. - -- [`playwright`](https://playwright.dev/docs/chrome-extensions) (recommended) - "A high-level API to automate web browsers" -- [`puppeteer`](https://pptr.dev/guides/chrome-extensions) - "A high-level API to control headless Chrome over the DevTools Protocol" - -:::info -Note that both only support running tests on Chrome. -::: - -Before running tests with either of these tools, you must build the extension with `wxt build` and then load the extension from the output directory in a new tab. - -To test an extension's UI, like the popup or options page, you'll need to know the extension's ID to open the URL directly. - -> _chrome-extension://`browser.runtime.id`/popup.html_ - -- Playwright provides an API to get your extension ID after it has been installed. [See their docs](https://playwright.dev/docs/chrome-extensions#testing). -- Puppeteer requires you know the ID before installing the extension, so you can hard code it into the URLs you open. Follow [Chrome's guide](https://developer.chrome.com/docs/extensions/mv3/manifest/key/) to setup a consistent runtime id. - -:::info -You cannot test popups in their normal popup window, you have to open them in a tab. -::: +[Here's how Vitest is configured](https://github.com/wxt-dev/wxt/blob/main/src/testing/wxt-vitest-plugin.ts) for reference. diff --git a/package.json b/package.json index 63d75eb8..7dfd8609 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "typedoc-vitepress-theme": "1.0.0-next.3", "typescript": "^5.3.2", "vitepress": "1.0.0-rc.31", - "vitest": "^1.0.0", + "vitest": "^1.1.0", "vitest-mock-extended": "^1.3.1", "vue": "^3.3.10" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4138a6f..f67c77ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -97,7 +97,7 @@ importers: version: 1.9.0 vite: specifier: ^5.0.0 - version: 5.0.5(@types/node@20.10.3) + version: 5.0.5(@types/node@20.10.3)(sass@1.69.5) web-ext-run: specifier: ^0.1.0 version: 0.1.0 @@ -128,7 +128,7 @@ importers: version: 2.4.9 '@vitest/coverage-v8': specifier: ^1.0.1 - version: 1.0.1(vitest@1.0.0) + version: 1.0.1(vitest@1.1.0) execa: specifier: ^8.0.1 version: 8.0.1 @@ -178,11 +178,11 @@ importers: specifier: 1.0.0-rc.31 version: 1.0.0-rc.31(@types/node@20.10.3)(typescript@5.3.2) vitest: - specifier: ^1.0.0 - version: 1.0.0(@types/node@20.10.3)(happy-dom@12.10.3) + specifier: ^1.1.0 + version: 1.1.0(@types/node@20.10.3)(happy-dom@12.10.3)(sass@1.69.5) vitest-mock-extended: specifier: ^1.3.1 - version: 1.3.1(typescript@5.3.2)(vitest@1.0.0) + version: 1.3.1(typescript@5.3.2)(vitest@1.1.0) vue: specifier: ^3.3.10 version: 3.3.10(typescript@5.3.2) @@ -196,11 +196,8 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) vitest: - specifier: ^0.34.6 - version: 0.34.6(happy-dom@12.10.3)(sass@1.69.5) - webextension-polyfill: - specifier: ^0.10.0 - version: 0.10.0 + specifier: ^1.1.0 + version: 1.1.0(@types/node@20.10.3)(happy-dom@12.10.3)(sass@1.69.5) devDependencies: '@types/react': specifier: ^18.2.34 @@ -208,9 +205,6 @@ importers: '@types/react-dom': specifier: ^18.2.14 version: 18.2.14 - '@types/webextension-polyfill': - specifier: ^0.10.5 - version: 0.10.5 sass: specifier: ^1.69.5 version: 1.69.5 @@ -1210,16 +1204,6 @@ packages: defer-to-connect: 2.0.1 dev: false - /@types/chai-subset@1.3.3: - resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} - dependencies: - '@types/chai': 4.3.5 - dev: false - - /@types/chai@4.3.5: - resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==} - dev: false - /@types/estree@1.0.4: resolution: {integrity: sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==} dev: false @@ -1291,12 +1275,6 @@ packages: dependencies: undici-types: 5.26.5 - /@types/node@20.8.10: - resolution: {integrity: sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==} - dependencies: - undici-types: 5.26.5 - dev: false - /@types/normalize-path@3.0.2: resolution: {integrity: sha512-DO++toKYPaFn0Z8hQ7Tx+3iT9t77IJo/nDiqTXilgEP+kPNIYdpS9kh3fXuc53ugqwp9pxC1PVjCpV1tQDyqMA==} dev: true @@ -1340,6 +1318,7 @@ packages: /@types/webextension-polyfill@0.10.5: resolution: {integrity: sha512-LlmbFLUB7+BDrb9nMuM0wlqtx9LZbBV2x3W98o02cD7Y8i10+sBenTlhG56vr47dzC7WIVXbURii+5jMJsyjLw==} + dev: false /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -1352,11 +1331,11 @@ packages: vite: ^4.0.0 || ^5.0.0 vue: ^3.2.25 dependencies: - vite: 5.0.5(@types/node@20.10.3) + vite: 5.0.5(@types/node@20.10.3)(sass@1.69.5) vue: 3.3.10(typescript@5.3.2) dev: true - /@vitest/coverage-v8@1.0.1(vitest@1.0.0): + /@vitest/coverage-v8@1.0.1(vitest@1.1.0): resolution: {integrity: sha512-Z4a7ig4VjUCT/P+LRB3IZrBRXb9xWRUM8rSBH9cKgfrU1Oe01/K2WJKtGshOnQwXZoSfQtwCGpbnHmB/qJwjcw==} peerDependencies: vitest: ^1.0.0 @@ -1374,86 +1353,43 @@ packages: std-env: 3.6.0 test-exclude: 6.0.0 v8-to-istanbul: 9.2.0 - vitest: 1.0.0(@types/node@20.10.3)(happy-dom@12.10.3) + vitest: 1.1.0(@types/node@20.10.3)(happy-dom@12.10.3)(sass@1.69.5) transitivePeerDependencies: - supports-color dev: true - /@vitest/expect@0.34.6: - resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} + /@vitest/expect@1.1.0: + resolution: {integrity: sha512-9IE2WWkcJo2BR9eqtY5MIo3TPmS50Pnwpm66A6neb2hvk/QSLfPXBz2qdiwUOQkwyFuuXEUj5380CbwfzW4+/w==} dependencies: - '@vitest/spy': 0.34.6 - '@vitest/utils': 0.34.6 + '@vitest/spy': 1.1.0 + '@vitest/utils': 1.1.0 chai: 4.3.10 - dev: false - /@vitest/expect@1.0.0: - resolution: {integrity: sha512-EbqHCSzQhAY8Su/uLsMCDXkC26LyqQO54kAqJy/DubBqwpRre1iMzvDMPWx+YPfNIN3w7/ydKaJWjH6qRoz0fA==} + /@vitest/runner@1.1.0: + resolution: {integrity: sha512-zdNLJ00pm5z/uhbWF6aeIJCGMSyTyWImy3Fcp9piRGvueERFlQFbUwCpzVce79OLm2UHk9iwaMSOaU9jVHgNVw==} dependencies: - '@vitest/spy': 1.0.0 - '@vitest/utils': 1.0.0 - chai: 4.3.10 - dev: true - - /@vitest/runner@0.34.6: - resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} - dependencies: - '@vitest/utils': 0.34.6 - p-limit: 4.0.0 - pathe: 1.1.1 - dev: false - - /@vitest/runner@1.0.0: - resolution: {integrity: sha512-1CaYs4knCexozpGxNiT89foiIxidOdU220QpU6CKMN0qU05e3K5XNH8f4pW9KyXH37o1Zin1cLHkoLr/k7NyrQ==} - dependencies: - '@vitest/utils': 1.0.0 + '@vitest/utils': 1.1.0 p-limit: 5.0.0 pathe: 1.1.1 - dev: true - /@vitest/snapshot@0.34.6: - resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} + /@vitest/snapshot@1.1.0: + resolution: {integrity: sha512-5O/wyZg09V5qmNmAlUgCBqflvn2ylgsWJRRuPrnHEfDNT6tQpQ8O1isNGgo+VxofISHqz961SG3iVvt3SPK/QQ==} dependencies: magic-string: 0.30.5 pathe: 1.1.1 pretty-format: 29.7.0 - dev: false - /@vitest/snapshot@1.0.0: - resolution: {integrity: sha512-kAcQJGsaHMBLrY0QC6kMe7S+JgiMielX2qHqgWFxlUir5IVekJGokJcYTzoOp+MRN1Gue3Q6H5fZD4aC0XHloA==} - dependencies: - magic-string: 0.30.5 - pathe: 1.1.1 - pretty-format: 29.7.0 - dev: true - - /@vitest/spy@0.34.6: - resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} + /@vitest/spy@1.1.0: + resolution: {integrity: sha512-sNOVSU/GE+7+P76qYo+VXdXhXffzWZcYIPQfmkiRxaNCSPiLANvQx5Mx6ZURJ/ndtEkUJEpvKLXqAYTKEY+lTg==} dependencies: tinyspy: 2.2.0 - dev: false - /@vitest/spy@1.0.0: - resolution: {integrity: sha512-k2gZwSi7nkwcYMj1RNgb45jNUDV/opAGlsVvcmYrRXu2QljMSlyAa0Yut+n3S39XoEKp0I4ggVLABj0xVInynw==} - dependencies: - tinyspy: 2.2.0 - dev: true - - /@vitest/utils@0.34.6: - resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} + /@vitest/utils@1.1.0: + resolution: {integrity: sha512-z+s510fKmYz4Y41XhNs3vcuFTFhcij2YF7F8VQfMEYAAUfqQh0Zfg7+w9xdgFGhPf3tX3TicAe+8BDITk6ampQ==} dependencies: diff-sequences: 29.6.3 loupe: 2.3.7 pretty-format: 29.7.0 - dev: false - - /@vitest/utils@1.0.0: - resolution: {integrity: sha512-r9JhgaP2bUYSnKE9w0aNblCIK8SKpDhXfJgE4TzjDNq3G40Abo5WXJBEKYAteq5p+OWedSFUg6GirNOlH7pN7Q==} - dependencies: - diff-sequences: 29.6.3 - loupe: 2.3.7 - pretty-format: 29.7.0 - dev: true /@vue/compiler-core@3.3.10: resolution: {integrity: sha512-doe0hODR1+i1menPkRzJ5MNR6G+9uiZHIknK3Zn5OcIztu6GGw7u0XUzf3AgB8h/dfsZC9eouzoLo3c3+N/cVA==} @@ -1631,15 +1567,9 @@ packages: resolution: {integrity: sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==} dev: false - /acorn-walk@8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} - engines: {node: '>=0.4.0'} - dev: false - /acorn-walk@8.3.0: resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} engines: {node: '>=0.4.0'} - dev: true /acorn@8.11.2: resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} @@ -2565,7 +2495,6 @@ packages: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 - dev: true /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} @@ -2730,7 +2659,6 @@ packages: /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - dev: true /get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} @@ -3080,7 +3008,6 @@ packages: /human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - dev: true /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} @@ -3353,7 +3280,6 @@ packages: /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} @@ -3652,7 +3578,6 @@ packages: dependencies: mlly: 1.4.2 pkg-types: 1.0.3 - dev: true /lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -3833,7 +3758,6 @@ packages: /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} - dev: true /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} @@ -4082,7 +4006,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: path-key: 4.0.0 - dev: true /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -4141,7 +4064,6 @@ packages: engines: {node: '>=12'} dependencies: mimic-fn: 4.0.0 - dev: true /open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} @@ -4177,19 +4099,11 @@ packages: engines: {node: '>=12.20'} dev: false - /p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - yocto-queue: 1.0.0 - dev: false - /p-limit@5.0.0: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} engines: {node: '>=18'} dependencies: yocto-queue: 1.0.0 - dev: true /p-map@7.0.0: resolution: {integrity: sha512-EZl03dLKv3RypkrjlevZoNwQMSy4bAblWcR18zhonktnN4fUs3asFQKSe0awn982omGxamvbejqQKQYDJYHCEg==} @@ -4251,7 +4165,6 @@ packages: /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} - dev: true /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -4782,7 +4695,6 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true /simple-git-hooks@2.9.0: resolution: {integrity: sha512-waSQ5paUQtyGC0ZxlHmcMmD9I1rRXauikBwX31bX58l5vTOhCEcBC5Bi+ZDkPXTjDnZAF8TbCqKBY+9+sVPScw==} @@ -4889,17 +4801,12 @@ packages: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} dev: false - /std-env@3.3.3: - resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==} - dev: false - /std-env@3.4.3: resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} dev: false /std-env@3.6.0: resolution: {integrity: sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg==} - dev: true /stdin-discarder@0.1.0: resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} @@ -5026,7 +4933,6 @@ packages: /strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} - dev: true /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} @@ -5115,23 +5021,12 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: false - /tinybench@2.5.0: - resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==} - dev: false - /tinybench@2.5.1: resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} - dev: true - - /tinypool@0.7.0: - resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} - engines: {node: '>=14.0.0'} - dev: false /tinypool@0.8.1: resolution: {integrity: sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==} engines: {node: '>=14.0.0'} - dev: true /tinyspy@2.2.0: resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} @@ -5551,30 +5446,8 @@ packages: vfile-message: 4.0.2 dev: true - /vite-node@0.34.6(@types/node@20.8.10)(sass@1.69.5): - resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} - engines: {node: '>=v14.18.0'} - hasBin: true - dependencies: - cac: 6.7.14 - debug: 4.3.4 - mlly: 1.4.2 - pathe: 1.1.1 - picocolors: 1.0.0 - vite: 5.0.5(@types/node@20.8.10)(sass@1.69.5) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - dev: false - - /vite-node@1.0.0(@types/node@20.10.3): - resolution: {integrity: sha512-9pGEPYsHy+7Ok7d6FkvniCmMI58IJ4KfFSK0Xq2FHWPQoBRpJKubaNBvMcXm0+uAwS6K2Rh9qJOKijdgqrjN+Q==} + /vite-node@1.1.0(@types/node@20.10.3)(sass@1.69.5): + resolution: {integrity: sha512-jV48DDUxGLEBdHCQvxL1mEh7+naVy+nhUUUaPAZLd3FJgXuxQiewHcfeZebbJ6onDqNGkP4r3MhQ342PRlG81Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: @@ -5582,7 +5455,7 @@ packages: debug: 4.3.4 pathe: 1.1.1 picocolors: 1.0.0 - vite: 5.0.5(@types/node@20.10.3) + vite: 5.0.5(@types/node@20.10.3)(sass@1.69.5) transitivePeerDependencies: - '@types/node' - less @@ -5592,9 +5465,8 @@ packages: - sugarss - supports-color - terser - dev: true - /vite@5.0.5(@types/node@20.10.3): + /vite@5.0.5(@types/node@20.10.3)(sass@1.69.5): resolution: {integrity: sha512-OekeWqR9Ls56f3zd4CaxzbbS11gqYkEiBtnWFFgYR2WV8oPJRRKq0mpskYy/XaoCL3L7VINDhqqOMNDiYdGvGg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -5626,45 +5498,9 @@ packages: esbuild: 0.19.8 postcss: 8.4.32 rollup: 4.6.1 - optionalDependencies: - fsevents: 2.3.3 - - /vite@5.0.5(@types/node@20.8.10)(sass@1.69.5): - resolution: {integrity: sha512-OekeWqR9Ls56f3zd4CaxzbbS11gqYkEiBtnWFFgYR2WV8oPJRRKq0mpskYy/XaoCL3L7VINDhqqOMNDiYdGvGg==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 20.8.10 - esbuild: 0.19.8 - postcss: 8.4.32 - rollup: 4.6.1 sass: 1.69.5 optionalDependencies: fsevents: 2.3.3 - dev: false /vitepress@1.0.0-rc.31(@types/node@20.10.3)(typescript@5.3.2): resolution: {integrity: sha512-ikH9pIjOOAbyoYAGBVfTz8TzuXp+UoWaIRMU4bw/oiTg8R65SbAaGKY84xx6TuL+f4VqUJ8lhzW82YyxSLvstA==} @@ -5691,7 +5527,7 @@ packages: mrmime: 1.0.1 shikiji: 0.7.6 shikiji-transformers: 0.7.6 - vite: 5.0.5(@types/node@20.10.3) + vite: 5.0.5(@types/node@20.10.3)(sass@1.69.5) vue: 3.3.10(typescript@5.3.2) transitivePeerDependencies: - '@algolia/client-search' @@ -5721,7 +5557,7 @@ packages: - universal-cookie dev: true - /vitest-mock-extended@1.3.1(typescript@5.3.2)(vitest@1.0.0): + /vitest-mock-extended@1.3.1(typescript@5.3.2)(vitest@1.1.0): resolution: {integrity: sha512-OpghYjh4BDuQ/Mzs3lFMQ1QRk9D8/2O9T47MLUA5eLn7K4RWIy+MfIivYOWEyxjTENjsBnzgMihDjyNalN/K0Q==} peerDependencies: typescript: 3.x || 4.x || 5.x @@ -5729,84 +5565,18 @@ packages: dependencies: ts-essentials: 9.3.2(typescript@5.3.2) typescript: 5.3.2 - vitest: 1.0.0(@types/node@20.10.3)(happy-dom@12.10.3) + vitest: 1.1.0(@types/node@20.10.3)(happy-dom@12.10.3)(sass@1.69.5) dev: true - /vitest@0.34.6(happy-dom@12.10.3)(sass@1.69.5): - resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} - engines: {node: '>=v14.18.0'} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' - happy-dom: '*' - jsdom: '*' - playwright: '*' - safaridriver: '*' - webdriverio: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - playwright: - optional: true - safaridriver: - optional: true - webdriverio: - optional: true - dependencies: - '@types/chai': 4.3.5 - '@types/chai-subset': 1.3.3 - '@types/node': 20.8.10 - '@vitest/expect': 0.34.6 - '@vitest/runner': 0.34.6 - '@vitest/snapshot': 0.34.6 - '@vitest/spy': 0.34.6 - '@vitest/utils': 0.34.6 - acorn: 8.11.2 - acorn-walk: 8.2.0 - cac: 6.7.14 - chai: 4.3.10 - debug: 4.3.4 - happy-dom: 12.10.3 - local-pkg: 0.4.3 - magic-string: 0.30.5 - pathe: 1.1.1 - picocolors: 1.0.0 - std-env: 3.3.3 - strip-literal: 1.3.0 - tinybench: 2.5.0 - tinypool: 0.7.0 - vite: 5.0.5(@types/node@20.8.10)(sass@1.69.5) - vite-node: 0.34.6(@types/node@20.8.10)(sass@1.69.5) - why-is-node-running: 2.2.2 - transitivePeerDependencies: - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - dev: false - - /vitest@1.0.0(@types/node@20.10.3)(happy-dom@12.10.3): - resolution: {integrity: sha512-jpablj5+ifiFHV3QGOxPews3uxBuu6rQUzTaQYtEd6ocBpdQBil6AvmmGRQ3Rn0WPgyzb+Ni+JekfMyng+qYng==} + /vitest@1.1.0(@types/node@20.10.3)(happy-dom@12.10.3)(sass@1.69.5): + resolution: {integrity: sha512-oDFiCrw7dd3Jf06HoMtSRARivvyjHJaTxikFxuqJjO76U436PqlVw1uLn7a8OSPrhSfMGVaRakKpA2lePdw79A==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': '*' - '@vitest/ui': '*' + '@vitest/browser': ^1.0.0 + '@vitest/ui': ^1.0.0 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -5824,11 +5594,11 @@ packages: optional: true dependencies: '@types/node': 20.10.3 - '@vitest/expect': 1.0.0 - '@vitest/runner': 1.0.0 - '@vitest/snapshot': 1.0.0 - '@vitest/spy': 1.0.0 - '@vitest/utils': 1.0.0 + '@vitest/expect': 1.1.0 + '@vitest/runner': 1.1.0 + '@vitest/snapshot': 1.1.0 + '@vitest/spy': 1.1.0 + '@vitest/utils': 1.1.0 acorn-walk: 8.3.0 cac: 6.7.14 chai: 4.3.10 @@ -5843,8 +5613,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.5.1 tinypool: 0.8.1 - vite: 5.0.5(@types/node@20.10.3) - vite-node: 1.0.0(@types/node@20.10.3) + vite: 5.0.5(@types/node@20.10.3)(sass@1.69.5) + vite-node: 1.1.0(@types/node@20.10.3)(sass@1.69.5) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -5854,7 +5624,6 @@ packages: - sugarss - supports-color - terser - dev: true /vscode-oniguruma@1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} diff --git a/src/core/builders/vite/plugins/index.ts b/src/core/builders/vite/plugins/index.ts index 4bba7677..b5a5da1e 100644 --- a/src/core/builders/vite/plugins/index.ts +++ b/src/core/builders/vite/plugins/index.ts @@ -9,7 +9,6 @@ export * from './noopBackground'; export * from './cssEntrypoints'; export * from './bundleAnalysis'; export * from './globals'; -export * from './webextensionPolyfillAlias'; -export * from './webextensionPolyfillInlineDeps'; +export * from './webextensionPolyfillMock'; export * from './excludeBrowserPolyfill'; export * from './entrypointGroupGlobals'; diff --git a/src/core/builders/vite/plugins/webextensionPolyfillAlias.ts b/src/core/builders/vite/plugins/webextensionPolyfillAlias.ts deleted file mode 100644 index dbac5671..00000000 --- a/src/core/builders/vite/plugins/webextensionPolyfillAlias.ts +++ /dev/null @@ -1,28 +0,0 @@ -import path from 'node:path'; -import type * as vite from 'vite'; -import { InternalConfig } from '~/types'; - -/** - * Creates an alias to redirect "webextension-polyfill" imports to WXT's `fakeBrowser`. - * - * This should only be used during tests. - */ -export function webextensionPolyfillAlias( - config: Omit, -): vite.PluginOption { - return { - name: 'wxt:webextension-polyfill-test-alias', - config() { - return { - resolve: { - alias: { - 'webextension-polyfill': path.resolve( - config.root, - 'node_modules/wxt/dist/virtual/mock-browser', - ), - }, - }, - }; - }, - }; -} diff --git a/src/core/builders/vite/plugins/webextensionPolyfillInlineDeps.ts b/src/core/builders/vite/plugins/webextensionPolyfillInlineDeps.ts deleted file mode 100644 index cbb1b73f..00000000 --- a/src/core/builders/vite/plugins/webextensionPolyfillInlineDeps.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type * as vite from 'vite'; - -/** - * Add all deps that import `webextension-polyfill` to `test.server.deps.inline`. - * - * TODO: Auto-detect non-wxt dependencies via `npm list`. - */ -export function webextensionPolyfillInlineDeps(): vite.PluginOption { - return { - name: 'wxt:testing-inline-deps', - config() { - const wxtModules = ['wxt/browser']; - return { - test: { - server: { - deps: { - inline: [...wxtModules], - }, - }, - }, - }; - }, - }; -} diff --git a/src/core/builders/vite/plugins/webextensionPolyfillMock.ts b/src/core/builders/vite/plugins/webextensionPolyfillMock.ts new file mode 100644 index 00000000..9c7fe51d --- /dev/null +++ b/src/core/builders/vite/plugins/webextensionPolyfillMock.ts @@ -0,0 +1,41 @@ +import path from 'node:path'; +import type * as vite from 'vite'; +import { InternalConfig } from '~/types'; + +/** + * Mock `webextension-polyfill` by inlining all dependencies that import it and adding a custom + * alias so that Vite resolves to a mocked version of the module. + * + * There are two ways to mark a module as inline: + * 1. Use partial file paths ("wxt/dist/browser.js") in the `test.server.deps.inline` option. + * 2. Use module names ("wxt" or "@webext-core/messaging") in the `ssr.noExternalize` option. + * + * This plugin uses the second approach since it's a little more intuative to understand. + * + * TODO: Detect non-wxt dependencies (like `@webext-core/*`) that import `webextension-polyfill` via + * `npm list` and inline them automatically. + */ +export function webextensionPolyfillMock( + config: Omit, +): vite.PluginOption { + return { + name: 'wxt:testing-inline-deps', + config() { + return { + resolve: { + alias: { + // Alias to use a mocked version of the polyfill + 'webextension-polyfill': path.resolve( + config.root, + 'node_modules/wxt/dist/virtual/mock-browser', + ), + }, + }, + ssr: { + // Inline all WXT modules + noExternal: ['wxt'], + }, + }; + }, + }; +} diff --git a/src/testing/wxt-vitest-plugin.ts b/src/testing/wxt-vitest-plugin.ts index 2b949965..fd7401e9 100644 --- a/src/testing/wxt-vitest-plugin.ts +++ b/src/testing/wxt-vitest-plugin.ts @@ -4,8 +4,7 @@ import { download, tsconfigPaths, globals, - webextensionPolyfillAlias, - webextensionPolyfillInlineDeps, + webextensionPolyfillMock, } from '../core/builders/vite/plugins'; import { getInternalConfig } from '~/core/utils/building'; import { InlineConfig } from '../types'; @@ -27,8 +26,7 @@ import { InlineConfig } from '../types'; */ export function WxtVitest(inlineConfig?: InlineConfig): vite.PluginOption { return getInternalConfig(inlineConfig ?? {}, 'serve').then((config) => [ - webextensionPolyfillAlias(config), - webextensionPolyfillInlineDeps(), + webextensionPolyfillMock(config), unimport(config), globals(config), download(config),