diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 1998726e..823ec872 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -42,6 +42,7 @@ jobs: pnpm build:all:firefox-mv3 pnpm tsc --noEmit pnpm wxt zip + pnpm vitest run working-directory: demo - name: Tests diff --git a/demo/package.json b/demo/package.json index 60b56289..ba79665b 100644 --- a/demo/package.json +++ b/demo/package.json @@ -11,14 +11,16 @@ "build:all:chrome-mv2": "wxt build --mv2", "build:all:firefox-mv3": "wxt build -b firefox --mv3", "build:all:firefox-mv2": "wxt build -b firefox", + "test": "pnpm -w build && vitest", "zip": "pnpm -w build && wxt zip", "compile": "pnpm -w build && tsc --noEmit", "postinstall": "pnpm -w build && wxt prepare" }, "dependencies": { - "webextension-polyfill": "^0.10.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "vitest": "^0.34.6", + "webextension-polyfill": "^0.10.0" }, "devDependencies": { "@types/react": "^18.2.14", diff --git a/demo/src/entrypoints/__tests__/background.test.ts b/demo/src/entrypoints/__tests__/background.test.ts new file mode 100644 index 00000000..455122e7 --- /dev/null +++ b/demo/src/entrypoints/__tests__/background.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it, vi } from 'vitest'; +import background from '../background'; + +browser.i18n.getMessage = () => 'fake-message'; + +const logMock = vi.fn(); +console.log = logMock; + +describe('Background Entrypoint', () => { + it("should log the extenion's runtime ID", () => { + const id = 'some-id'; + fakeBrowser.runtime.id = id; + + background.main(); + + expect(logMock).toBeCalledWith(id); + }); +}); diff --git a/demo/vitest.config.ts b/demo/vitest.config.ts new file mode 100644 index 00000000..5f310c1a --- /dev/null +++ b/demo/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; +import { WxtVitest } from 'wxt/testing'; + +export default defineConfig({ + test: { + mockReset: true, + restoreMocks: true, + }, + plugins: [WxtVitest()], +}); diff --git a/docs/guide/auto-imports.md b/docs/guide/auto-imports.md index 256b4b9f..095e4324 100644 --- a/docs/guide/auto-imports.md +++ b/docs/guide/auto-imports.md @@ -15,6 +15,7 @@ Some WXT APIs can be used without importing them: - [`defineBackground`](/api/wxt/client/functions/defineBackground) from `wxt/client` - [`createContentScriptUi`](/api/wxt/client/functions/createContentScriptUi) from `wxt/client` - [`defineUnlistedScript`](/api/wxt/sandbox/functions/defineUnlistedScript) from `wxt/sandbox` +- [`fakeBrowser`](/api/wxt/testing/variables/fakeBrowser) from `wxt/testing` And more. All `wxt/*` APIs can be used without imports. diff --git a/docs/guide/testing.md b/docs/guide/testing.md index 470854ea..4038d1ba 100644 --- a/docs/guide/testing.md +++ b/docs/guide/testing.md @@ -1,92 +1,153 @@ # Testing -WXT provides a couple of utils for unit testing your extension. +WXT provides several utils for writing tests. -[[toc]] +## Unit tests -## Fake Browser +If you're using auto-imports (enabled by default), [Vitest](https://vitest.dev/) is the only testing framework that supports them. -The `wxt/fake-browser` package includes an in-memory implementation of the `browser` variable you can use for testing. WXT simply re-exports the `fakeBrowser` variable from [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/guide/fake-browser/). +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: -Here's an example test using Vitest: +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"`) -```ts -import { describe, it, expect, vi } from 'vitest'; -import { browser } from 'wxt/browser'; -import { fakeBrowser } from 'wxt'; +### Vitest Setup -// Function we're testing -function onHelloMessage(cb: () => void) { - browser.runtime.onMessage.addEventListener((message) => { - if (message.type === 'hello') return 'world'; - }); -} +Install vitest and add the `WxtVitest` plugin to your `vitest.config.ts` file. -// Mock the real `browser` object with a fake one -vi.mock('wxt/browser', () => import('wxt/fake-browser')); - -describe('onHelloMessage', () => { - it("should call the callback when the message type is 'hello'", () => { - const cb = vi.fn(); - const expected = 'world'; - - onHelloMessage(cb); - const actual = await fakeBrowser.runtime.sendMessage({ type: 'hello' }); - - expect(cb).toBeCalledTimes(1); - expect(actual).toBe(expected); - }); - - it("should ignore the message when the message type is not 'hello'", () => { - const cb = vi.fn(); - - onHelloMessage(cb); - await fakeBrowser.runtime.sendMessage({ type: 'not-hello' }).catch(); - - expect(cb).not.toBeCalled(); - }); -}); +```sh +pnpm i -D vitest ``` -See [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/guide/fake-browser/) for setup, implemented APIs, and example tests. - -## Handling Auto-imports - -By default, WXT uses auto-imports. For tests, this can cause issues if your test environment is not setup to handle them correctly. - -:::warning 🚧 Testing utils are not implemented yet! -Eventually, WXT will provide utilities for setting up these auto-imports. For now, you'll need to set them up manually. -::: - -Not all testing frameworks can handle auto-imports. If your framework or setup is not listed below, it may be easiest to disable auto-imports. - -To setup auto-imports manually, use [`unplugin-auto-import`](https://www.npmjs.com/package/unplugin-auto-import). It uses the same tool, `unimport`, as WXT and will result in compatiple auto-imports. `unplugin-auto-import` supports lots of different tools (vite, webpack, esbuild, rollup, etc). You can try and integrate it into your build process. - -### Vitest (Recommended) - -Vitest is easy, simply add `uplugin-auto-import` to your project. - ```ts -// vitest.config.ts -import autoImports from 'unplugin-auto-import/vite'; +// /vitest.config.ts +import { defineConfig } from 'vitest/config'; +import { WxtVitest } from 'wxt/testing'; export default defineConfig({ - plugins: [ - autoImports({ - imports: [{ name: 'defineConfig', from: 'wxt' }], - presets: [{ package: 'wxt/client' }, { package: 'wxt/browser' }], - dirs: ['components', 'composables', 'hooks', 'utils'], - }), - ], + plugins: [WxtVitest()], }); ``` -### Jest +And that's it. You're ready to start writing tests. -Don't use jest and auto-imports. You could try and configure jest to be transpiled by one of `unplugin-auto-import`'s supported built tools, but I don't know of a way to configure this. See [unplugin/unplugin-auto-import#33](https://github.com/unplugin/unplugin-auto-import/issues/33) if you want to try and set it up. +### Writing Tests -I would recommend disabling auto-imports or migrating to Vitest if you want to use auto-imports. +Here's a very basic test, written with a few different testing libraries, with a few different approaches for mocking the `browser` global. -### Mocha +:::code-group -TODO: Is this possible? Maybe with `esbuild-mocha`? I would recommend moving to Vitest. +```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. +::: diff --git a/docs/typedoc.json b/docs/typedoc.json index 03ff7f1f..59340f4e 100644 --- a/docs/typedoc.json +++ b/docs/typedoc.json @@ -3,7 +3,8 @@ "../src", "../src/client", "../src/client/browser.ts", - "../src/client/sandbox" + "../src/client/sandbox", + "../src/testing" ], "plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"], "out": "./api", diff --git a/e2e/tests/auto-imports.test.ts b/e2e/tests/auto-imports.test.ts index 953f9709..f173002e 100644 --- a/e2e/tests/auto-imports.test.ts +++ b/e2e/tests/auto-imports.test.ts @@ -23,6 +23,7 @@ describe('Auto Imports', () => { const defineConfig: typeof import('wxt')['defineConfig'] const defineContentScript: typeof import('wxt/client')['defineContentScript'] const defineUnlistedScript: typeof import('wxt/sandbox')['defineUnlistedScript'] + const fakeBrowser: typeof import('wxt/testing')['fakeBrowser'] } " `); diff --git a/package.json b/package.json index 16c3b119..f8cd839e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,11 @@ "./browser": { "import": "./dist/browser.js", "types": "./dist/browser.d.ts" + }, + "./testing": { + "require": "./dist/testing.cjs", + "import": "./dist/testing.js", + "types": "./dist/testing.d.ts" } }, "scripts": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7598234..c5ff6710 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,7 +85,7 @@ importers: version: 3.3.0 vite: specifier: ^4.4.11 - version: 4.4.11(@types/node@20.8.6) + version: 4.4.11(@types/node@20.8.6)(sass@1.64.0) web-ext-run: specifier: ^0.1.0 version: 0.1.0 @@ -174,6 +174,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + vitest: + specifier: ^0.34.6 + version: 0.34.6(jsdom@22.1.0)(sass@1.64.0) webextension-polyfill: specifier: ^0.10.0 version: 0.10.0 @@ -875,7 +878,6 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@sinclair/typebox': 0.27.8 - dev: true /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} @@ -965,7 +967,6 @@ packages: /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - dev: true /@sindresorhus/is@5.4.1: resolution: {integrity: sha512-axlrvsHlHlFmKKMEg4VyvMzFr93JWJj4eIfXY1STVuO2fsImCa7ncaiG5gC8HKOX590AW5RtRsC41/B+OfrSqw==} @@ -982,17 +983,14 @@ packages: /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} - dev: true /@types/chai-subset@1.3.3: resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} dependencies: '@types/chai': 4.3.5 - dev: true /@types/chai@4.3.5: resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==} - dev: true /@types/estree@1.0.1: resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} @@ -1106,7 +1104,6 @@ packages: '@vitest/spy': 0.34.6 '@vitest/utils': 0.34.6 chai: 4.3.10 - dev: true /@vitest/runner@0.34.6: resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} @@ -1114,21 +1111,18 @@ packages: '@vitest/utils': 0.34.6 p-limit: 4.0.0 pathe: 1.1.1 - dev: true /@vitest/snapshot@0.34.6: resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} dependencies: - magic-string: 0.30.2 + magic-string: 0.30.3 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==} dependencies: tinyspy: 2.2.0 - dev: true /@vitest/utils@0.34.6: resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} @@ -1136,7 +1130,6 @@ packages: diff-sequences: 29.6.3 loupe: 2.3.7 pretty-format: 29.7.0 - dev: true /@vue/compiler-core@3.3.4: resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==} @@ -1322,12 +1315,10 @@ packages: /abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} - dev: true /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} - dev: true /acorn@8.10.0: resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} @@ -1408,7 +1399,6 @@ packages: /ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - dev: true /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} @@ -1448,7 +1438,6 @@ packages: /assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - dev: true /async-mutex@0.4.0: resolution: {integrity: sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==} @@ -1462,7 +1451,6 @@ packages: /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true /at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} @@ -1626,7 +1614,6 @@ packages: loupe: 2.3.7 pathval: 1.1.1 type-detect: 4.0.8 - dev: true /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -1644,7 +1631,6 @@ packages: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} dependencies: get-func-name: 2.0.2 - dev: true /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} @@ -1743,7 +1729,6 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: true /commander@11.0.0: resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} @@ -1862,7 +1847,6 @@ packages: engines: {node: '>=14'} dependencies: rrweb-cssom: 0.6.0 - dev: true /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} @@ -1875,7 +1859,6 @@ packages: abab: 2.0.6 whatwg-mimetype: 3.0.0 whatwg-url: 12.0.1 - dev: true /debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -1905,7 +1888,6 @@ packages: /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - dev: true /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} @@ -1919,7 +1901,6 @@ packages: engines: {node: '>=6'} dependencies: type-detect: 4.0.8 - dev: true /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} @@ -1951,7 +1932,6 @@ packages: /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - dev: true /destr@2.0.0: resolution: {integrity: sha512-FJ9RDpf3GicEBvzI3jxc2XhHzbqD8p4ANw/1kPsFBfTvP1b7Gn/Lg1vO7R9J4IVgoMbyUmFrFGZafJ1hPZpvlg==} @@ -1960,7 +1940,6 @@ packages: /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} @@ -1986,7 +1965,6 @@ packages: engines: {node: '>=12'} dependencies: webidl-conversions: 7.0.0 - dev: true /domhandler@5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} @@ -2295,7 +2273,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: true /fs-extra@11.1.1: resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} @@ -2370,7 +2347,6 @@ packages: /get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - dev: true /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} @@ -2571,7 +2547,6 @@ packages: engines: {node: '>=12'} dependencies: whatwg-encoding: 2.0.0 - dev: true /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -2603,7 +2578,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: true /http2-wrapper@2.2.0: resolution: {integrity: sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==} @@ -2637,7 +2611,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 - dev: true /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2658,7 +2631,6 @@ packages: /immutable@4.3.1: resolution: {integrity: sha512-lj9cnmB/kVS0QHsJnYKD1uo3o39nrbKxszjnqS9Fr6NB7bZzW45U6WSGBPKXDL/CvDKqDNPA4r3DoDQ8GTxo2A==} - dev: true /import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} @@ -2838,7 +2810,6 @@ packages: /is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - dev: true /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} @@ -3022,7 +2993,6 @@ packages: - bufferutil - supports-color - utf-8-validate - dev: true /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -3212,7 +3182,6 @@ packages: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} dependencies: get-func-name: 2.0.2 - dev: true /lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} @@ -3290,14 +3259,12 @@ packages: /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: true /mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - dev: true /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} @@ -3530,7 +3497,6 @@ packages: /nwsapi@2.2.7: resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} - dev: true /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -3616,7 +3582,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: yocto-queue: 1.0.0 - dev: true /package-json@8.1.1: resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} @@ -3655,7 +3620,6 @@ packages: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} dependencies: entities: 4.5.0 - dev: true /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} @@ -3697,7 +3661,6 @@ packages: /pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - dev: true /perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -3780,7 +3743,6 @@ packages: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.2.0 - dev: true /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -3807,12 +3769,10 @@ packages: /psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - dev: true /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} - dev: true /pupa@3.1.0: resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} @@ -3823,7 +3783,6 @@ packages: /querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - dev: true /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3863,7 +3822,6 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: true /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} @@ -3942,7 +3900,6 @@ packages: /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: true /resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} @@ -4036,7 +3993,6 @@ packages: /rrweb-cssom@0.6.0: resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} - dev: true /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -4063,7 +4019,6 @@ packages: /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - dev: true /sass@1.64.0: resolution: {integrity: sha512-m7YtAGmQta9uANIUJwXesAJMSncqH+3INc8kdVXs6eV6GUC8Qu2IYKQSN8PRLgiQfpca697G94klm2leYMxSHw==} @@ -4073,7 +4028,6 @@ packages: chokidar: 3.5.3 immutable: 4.3.1 source-map-js: 1.0.2 - dev: true /sax@1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} @@ -4084,7 +4038,6 @@ packages: engines: {node: '>=v12.22.7'} dependencies: xmlchars: 2.2.0 - dev: true /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} @@ -4174,7 +4127,6 @@ packages: /siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - dev: true /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4266,11 +4218,9 @@ packages: /stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - dev: true /std-env@3.3.3: resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==} - dev: true /stdin-discarder@0.1.0: resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} @@ -4432,7 +4382,6 @@ packages: /symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - dev: true /tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} @@ -4476,17 +4425,14 @@ packages: /tinybench@2.5.0: resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==} - dev: true /tinypool@0.7.0: resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} engines: {node: '>=14.0.0'} - dev: true /tinyspy@2.2.0: resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} engines: {node: '>=14.0.0'} - dev: true /tmp@0.2.1: resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} @@ -4514,7 +4460,6 @@ packages: punycode: 2.3.0 universalify: 0.2.0 url-parse: 1.5.10 - dev: true /tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -4527,7 +4472,6 @@ packages: engines: {node: '>=14'} dependencies: punycode: 2.3.0 - dev: true /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} @@ -4600,7 +4544,6 @@ packages: /type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} - dev: true /type-fest@1.4.0: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} @@ -4721,7 +4664,6 @@ packages: /universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} - dev: true /universalify@1.0.0: resolution: {integrity: sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==} @@ -4767,7 +4709,6 @@ packages: dependencies: querystringify: 2.2.0 requires-port: 1.0.0 - dev: true /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4794,7 +4735,7 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /vite-node@0.34.6(@types/node@20.8.6): + /vite-node@0.34.6(@types/node@20.8.6)(sass@1.64.0): resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} hasBin: true @@ -4804,7 +4745,7 @@ packages: mlly: 1.4.2 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.4.11(@types/node@20.8.6) + vite: 4.4.11(@types/node@20.8.6)(sass@1.64.0) transitivePeerDependencies: - '@types/node' - less @@ -4814,9 +4755,8 @@ packages: - sugarss - supports-color - terser - dev: true - /vite@4.4.11(@types/node@20.8.6): + /vite@4.4.11(@types/node@20.8.6)(sass@1.64.0): resolution: {integrity: sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -4848,6 +4788,7 @@ packages: esbuild: 0.18.20 postcss: 8.4.27 rollup: 3.28.0 + sass: 1.64.0 optionalDependencies: fsevents: 2.3.3 @@ -4864,7 +4805,7 @@ packages: mark.js: 8.11.1 minisearch: 6.1.0 shiki: 0.14.3 - vite: 4.4.11(@types/node@20.8.6) + vite: 4.4.11(@types/node@20.8.6)(sass@1.64.0) vue: 3.3.4 transitivePeerDependencies: - '@algolia/client-search' @@ -4957,8 +4898,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.5.0 tinypool: 0.7.0 - vite: 4.4.11(@types/node@20.8.6) - vite-node: 0.34.6(@types/node@20.8.6) + vite: 4.4.11(@types/node@20.8.6)(sass@1.64.0) + vite-node: 0.34.6(@types/node@20.8.6)(sass@1.64.0) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -4970,6 +4911,72 @@ packages: - terser dev: true + /vitest@0.34.6(jsdom@22.1.0)(sass@1.64.0): + 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.6 + '@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.10.0 + acorn-walk: 8.2.0 + cac: 6.7.14 + chai: 4.3.10 + debug: 4.3.4 + jsdom: 22.1.0 + local-pkg: 0.4.3 + magic-string: 0.30.3 + 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: 4.4.11(@types/node@20.8.6)(sass@1.64.0) + vite-node: 0.34.6(@types/node@20.8.6)(sass@1.64.0) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: false + /vscode-oniguruma@1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} dev: true @@ -5008,7 +5015,6 @@ packages: engines: {node: '>=14'} dependencies: xml-name-validator: 4.0.0 - dev: true /watchpack@2.4.0: resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} @@ -5062,7 +5068,6 @@ packages: /webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} - dev: true /webpack-sources@3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} @@ -5078,12 +5083,10 @@ packages: engines: {node: '>=12'} dependencies: iconv-lite: 0.6.3 - dev: true /whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} engines: {node: '>=12'} - dev: true /whatwg-url@12.0.1: resolution: {integrity: sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==} @@ -5091,7 +5094,6 @@ packages: dependencies: tr46: 4.1.1 webidl-conversions: 7.0.0 - dev: true /whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -5156,7 +5158,6 @@ packages: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - dev: true /widest-line@4.0.1: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} @@ -5218,7 +5219,6 @@ packages: /xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} - dev: true /xml2js@0.5.0: resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} @@ -5235,7 +5235,6 @@ packages: /xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - dev: true /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} @@ -5271,7 +5270,6 @@ packages: /yocto-queue@1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - dev: true /zip-dir@2.0.0: resolution: {integrity: sha512-uhlsJZWz26FLYXOD6WVuq+fIcZ3aBPGo/cFdiLlv3KNwpa52IF3ISV8fLhQLiqVu5No3VhlqlgthN6gehil1Dg==} diff --git a/scripts/build.ts b/scripts/build.ts index cf5368a7..42b33159 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -55,6 +55,13 @@ await Promise.all([ dts: true, silent: true, }), + tsup.build({ + entry: { testing: 'src/testing/index.ts' }, + format: ['esm', 'cjs'], + sourcemap: 'inline', + dts: true, + silent: true, + }), ...virtualEntrypoints.map((entryName) => tsup.build({ entry: { diff --git a/src/client/virtual-modules/fake-browser.ts b/src/client/virtual-modules/fake-browser.ts index dc651e99..d7fc549c 100644 --- a/src/client/virtual-modules/fake-browser.ts +++ b/src/client/virtual-modules/fake-browser.ts @@ -1,3 +1,3 @@ -import { fakeBrowser as browser } from '@webext-core/fake-browser'; +import { fakeBrowser as browser } from '../../testing/fake-browser'; export default browser; diff --git a/src/core/build/__tests__/findEntrypoints.test.ts b/src/core/build/__tests__/findEntrypoints.test.ts index 6724fe7b..1bbf40c3 100644 --- a/src/core/build/__tests__/findEntrypoints.test.ts +++ b/src/core/build/__tests__/findEntrypoints.test.ts @@ -11,7 +11,7 @@ import { findEntrypoints } from '../findEntrypoints'; import fs from 'fs-extra'; import { importEntrypointFile } from '../../utils/importEntrypointFile'; import glob from 'fast-glob'; -import { fakeInternalConfig } from '../../../testing/fake-objects'; +import { fakeInternalConfig } from '../../../testing-utils/fake-objects'; import { unnormalizePath } from '../../utils/paths'; vi.mock('../../utils/importEntrypointFile'); diff --git a/src/core/runners/__tests__/index.test.ts b/src/core/runners/__tests__/index.test.ts index e07911f4..7f26b002 100644 --- a/src/core/runners/__tests__/index.test.ts +++ b/src/core/runners/__tests__/index.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; import { createExtensionRunner } from '..'; -import { fakeInternalConfig } from '../../../testing/fake-objects'; +import { fakeInternalConfig } from '../../../testing-utils/fake-objects'; import { mock } from 'vitest-mock-extended'; import { createSafariRunner } from '../safari'; import { ExtensionRunner } from '../extension-runner'; diff --git a/src/core/utils/__tests__/content-scripts.test.ts b/src/core/utils/__tests__/content-scripts.test.ts index 125d0ff1..23120e00 100644 --- a/src/core/utils/__tests__/content-scripts.test.ts +++ b/src/core/utils/__tests__/content-scripts.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { hashContentScriptOptions } from '../content-scripts'; -import { fakeInternalConfig } from '../../../testing/fake-objects'; +import { fakeInternalConfig } from '../../../testing-utils/fake-objects'; describe('Content Script Utils', () => { describe('hashContentScriptOptions', () => { diff --git a/src/core/utils/__tests__/detectDevChanges.test.ts b/src/core/utils/__tests__/detectDevChanges.test.ts index 6dd06967..354e130c 100644 --- a/src/core/utils/__tests__/detectDevChanges.test.ts +++ b/src/core/utils/__tests__/detectDevChanges.test.ts @@ -10,7 +10,7 @@ import { fakePopupEntrypoint, fakeRollupOutputAsset, fakeRollupOutputChunk, -} from '../../../testing/fake-objects'; +} from '../../../testing-utils/fake-objects'; import { BuildOutput, BuildStepOutput } from '../../types'; describe('Detect Dev Changes', () => { diff --git a/src/core/utils/__tests__/importEntrypointFile.test.ts b/src/core/utils/__tests__/importEntrypointFile.test.ts index d61a2bc9..da0d0dd2 100644 --- a/src/core/utils/__tests__/importEntrypointFile.test.ts +++ b/src/core/utils/__tests__/importEntrypointFile.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { importEntrypointFile } from '../importEntrypointFile'; -import { fakeInternalConfig } from '../../../testing/fake-objects'; +import { fakeInternalConfig } from '../../../testing-utils/fake-objects'; import { resolve } from 'node:path'; const entrypointPath = (filename: string) => diff --git a/src/core/utils/auto-imports.ts b/src/core/utils/auto-imports.ts index d4c7e647..c8afcd1c 100644 --- a/src/core/utils/auto-imports.ts +++ b/src/core/utils/auto-imports.ts @@ -9,7 +9,10 @@ export function getUnimportOptions( const defaultOptions: Partial = { debugLog: config.logger.debug, - imports: [{ name: 'defineConfig', from: 'wxt' }], + imports: [ + { name: 'defineConfig', from: 'wxt' }, + { name: 'fakeBrowser', from: 'wxt/testing' }, + ], presets: [ { package: 'wxt/client' }, { package: 'wxt/browser' }, diff --git a/src/core/utils/getInternalConfig.ts b/src/core/utils/getInternalConfig.ts index e1add79e..95c5ca26 100644 --- a/src/core/utils/getInternalConfig.ts +++ b/src/core/utils/getInternalConfig.ts @@ -14,7 +14,6 @@ import * as vite from 'vite'; import { createFsCache } from './createFsCache'; import consola, { LogLevels } from 'consola'; import * as plugins from '../vite-plugins'; -import { getGlobals } from './globals'; /** * Given an inline config, discover the config file if necessary, merge the results, resolve any @@ -251,10 +250,7 @@ async function resolveInternalViteConfig( if (finalConfig.analysis.enabled) { internalVite.plugins.push(plugins.bundleAnalysis()); } + internalVite.plugins.push(plugins.globals(finalConfig)); - internalVite.define ??= {}; - for (const global of getGlobals(finalConfig)) { - internalVite.define[global.name] = JSON.stringify(global.value); - } return internalVite; } diff --git a/src/core/utils/globals.ts b/src/core/utils/globals.ts index 3ad0b497..bf219dd4 100644 --- a/src/core/utils/globals.ts +++ b/src/core/utils/globals.ts @@ -5,42 +5,42 @@ export function getGlobals( ): Array<{ name: string; value: any; type: string }> { return [ { - name: '__MANIFEST_VERSION__', + name: surroundInUnderscore('MANIFEST_VERSION'), value: config.manifestVersion, type: `2 | 3`, }, { - name: '__BROWSER__', + name: surroundInUnderscore('BROWSER'), value: config.browser, type: `string`, }, { - name: '__IS_CHROME__', + name: surroundInUnderscore('IS_CHROME'), value: config.browser === 'chrome', type: `boolean`, }, { - name: '__IS_FIREFOX__', + name: surroundInUnderscore('IS_FIREFOX'), value: config.browser === 'firefox', type: `boolean`, }, { - name: '__IS_SAFARI__', + name: surroundInUnderscore('IS_SAFARI'), value: config.browser === 'safari', type: `boolean`, }, { - name: '__IS_EDGE__', + name: surroundInUnderscore('IS_EDGE'), value: config.browser === 'edge', type: `boolean`, }, { - name: '__IS_OPERA__', + name: surroundInUnderscore('IS_OPERA'), value: config.browser === 'opera', type: `boolean`, }, { - name: '__COMMAND__', + name: surroundInUnderscore('COMMAND'), value: config.command, type: `"build" | "serve"`, }, @@ -53,9 +53,17 @@ export function getEntrypointGlobals( ) { return [ { - name: '__ENTRYPOINT__', + name: surroundInUnderscore('ENTRYPOINT'), value: entrypointName, type: `string`, }, ]; } + +/** + * Don't hardcode the complete name so that the string litterals in this file aren't replaced during + * tests (which causes syntax errors), only during builds. + */ +function surroundInUnderscore(name: string): string { + return `__${name}__`; +} diff --git a/src/core/vite-plugins/globals.ts b/src/core/vite-plugins/globals.ts new file mode 100644 index 00000000..3e32a5e4 --- /dev/null +++ b/src/core/vite-plugins/globals.ts @@ -0,0 +1,18 @@ +import * as vite from 'vite'; +import { InternalConfig } from '../types'; +import { getGlobals } from '../utils/globals'; + +export function globals(config: InternalConfig): vite.PluginOption { + return { + name: 'wxt:globals', + config() { + const define: vite.InlineConfig['define'] = {}; + for (const global of getGlobals(config)) { + define[global.name] = JSON.stringify(global.value); + } + return { + define, + }; + }, + }; +} diff --git a/src/core/vite-plugins/index.ts b/src/core/vite-plugins/index.ts index 8f2a4b67..059afc00 100644 --- a/src/core/vite-plugins/index.ts +++ b/src/core/vite-plugins/index.ts @@ -8,3 +8,5 @@ export * from './tsconfigPaths'; export * from './noopBackground'; export * from './cssEntrypoints'; export * from './bundleAnalysis'; +export * from './globals'; +export * from './webextensionPolyfillAlias'; diff --git a/src/core/vite-plugins/webextensionPolyfillAlias.ts b/src/core/vite-plugins/webextensionPolyfillAlias.ts new file mode 100644 index 00000000..fb5772c2 --- /dev/null +++ b/src/core/vite-plugins/webextensionPolyfillAlias.ts @@ -0,0 +1,28 @@ +import path from 'node:path'; +import * 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: InternalConfig, +): vite.PluginOption { + return { + name: 'wxt:webextension-polyfill-test-alias', + config() { + return { + resolve: { + alias: { + 'webextension-polyfill': path.resolve( + config.root, + 'node_modules/wxt/dist/virtual-modules/fake-browser', + ), + }, + }, + }; + }, + }; +} diff --git a/src/testing/fake-objects.ts b/src/testing-utils/fake-objects.ts similarity index 100% rename from src/testing/fake-objects.ts rename to src/testing-utils/fake-objects.ts diff --git a/src/testing/fake-browser.ts b/src/testing/fake-browser.ts new file mode 100644 index 00000000..7ba6daf9 --- /dev/null +++ b/src/testing/fake-browser.ts @@ -0,0 +1 @@ +export { fakeBrowser, type FakeBrowser } from '@webext-core/fake-browser'; diff --git a/src/testing/index.ts b/src/testing/index.ts new file mode 100644 index 00000000..ccb6f1f5 --- /dev/null +++ b/src/testing/index.ts @@ -0,0 +1,5 @@ +/** + * @module wxt/testing + */ +export * from './fake-browser'; +export * from './wxt-vitest-plugin'; diff --git a/src/testing/wxt-vitest-plugin.ts b/src/testing/wxt-vitest-plugin.ts new file mode 100644 index 00000000..87e21672 --- /dev/null +++ b/src/testing/wxt-vitest-plugin.ts @@ -0,0 +1,35 @@ +import * as vite from 'vite'; +import { + unimport, + download, + tsconfigPaths, + globals, + webextensionPolyfillAlias, +} from '../core/vite-plugins'; +import { getInternalConfig } from '../core/utils/getInternalConfig'; +import { InlineConfig } from '../core/types'; + +/** + * Vite plugin that configures Vitest with everything required to test a WXT extension, based on the `/wxt.config.ts` + * + * ```ts + * // vitest.config.ts + * import { defineConfig } from 'vitest/config'; + * import { AutoImport } from 'wxt/testing'; + * + * export default defineConfig({ + * plugins: [AutoImport()], + * }); + * ``` + * + * @param inlineConfig Customize WXT's config for testing. Any config specified here overrides the config from your `wxt.config.ts` file. + */ +export function WxtVitest(inlineConfig?: InlineConfig): vite.PluginOption { + return getInternalConfig(inlineConfig ?? {}, 'serve').then((config) => [ + webextensionPolyfillAlias(config), + unimport(config), + globals(config), + download(config), + tsconfigPaths(config), + ]); +}