From ed541de853d8668f5fa6fbf037f3474ca435dc5e Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Wed, 24 Apr 2024 09:42:55 -0400 Subject: [PATCH] feat(vitest): add Vite plugin for Svelte browser import and autocleanup (#362) Closes #359 --- README.md | 37 +++++++++++++---- package.json | 22 ++++++++-- src/__tests__/_vitest-setup.js | 1 - src/vite.js | 75 ++++++++++++++++++++++++++++++++++ types/vite.d.ts | 12 ++++++ vite.config.js | 15 +++---- 6 files changed, 141 insertions(+), 21 deletions(-) create mode 100644 src/vite.js create mode 100644 types/vite.d.ts diff --git a/README.md b/README.md index 0a39429..51ca77a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@

Simple and complete Svelte testing utilities that encourage good testing practices.

[**Read The Docs**](https://testing-library.com/docs/svelte-testing-library/intro) | -[Edit the docs](https://github.com/alexkrolick/testing-library-docs) +[Edit the docs](https://github.com/testing-library/testing-library-docs) [![Build Status][build-badge]][build] @@ -80,19 +80,42 @@ This library has `peerDependencies` listings for `svelte >= 3`. You may also be interested in installing `@testing-library/jest-dom` so you can use [the custom jest matchers](https://github.com/testing-library/jest-dom). +## Setup + +We recommend using `@testing-library/svelte` with [Vitest][] as your test runner. To get started, add the `svelteTesting` plugin to your Vite or Vitest config. + +```diff + // vite.config.js + import { svelte } from '@sveltejs/vite-plugin-svelte' ++ import { svelteTesting } from '@testing-library/svelte/vite' + + export default defineConfig({ + plugins: [ + svelte(), ++ svelteTesting(), + ] + }); +``` + +See the [setup docs][] for more detailed setup instructions, including for other test runners like Jest. + +[vitest]: https://vitest.dev/ +[setup docs]: https://testing-library.com/docs/svelte-testing-library/setup + ### Svelte 5 support If you are riding the bleeding edge of Svelte 5, you'll need to either -import from `@testing-library/svelte/svelte5` instead of `@testing-library/svelte`, or have your `vite.config.js` contains the following alias: +import from `@testing-library/svelte/svelte5` instead of `@testing-library/svelte`, or add an alias to your `vite.config.js`: -``` -export default defineConfig(({ }) => ({ +```js +export default defineConfig({ + plugins: [svelte(), svelteTesting()], test: { alias: { - '@testing-library/svelte': '@testing-library/svelte/svelte5' - } + '@testing-library/svelte': '@testing-library/svelte/svelte5', + }, }, -})) +}) ``` ## Docs diff --git a/package.json b/package.json index 1948cfc..307e6ba 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,10 @@ }, "./vitest": { "default": "./src/vitest.js" + }, + "./vite": { + "types": "./types/vite.d.ts", + "default": "./src/vite.js" } }, "type": "module", @@ -43,8 +47,10 @@ "e2e" ], "files": [ - "src/", - "types/index.d.ts" + "src", + "types", + "!*.test-d.ts", + "!__tests__" ], "scripts": { "toc": "doctoc README.md", @@ -68,7 +74,17 @@ "contributors:generate": "all-contributors generate" }, "peerDependencies": { - "svelte": "^3 || ^4 || ^5" + "svelte": "^3 || ^4 || ^5", + "vite": "*", + "vitest": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + }, + "vitest": { + "optional": true + } }, "dependencies": { "@testing-library/dom": "^9.3.1" diff --git a/src/__tests__/_vitest-setup.js b/src/__tests__/_vitest-setup.js index 13ed819..a9d0dd3 100644 --- a/src/__tests__/_vitest-setup.js +++ b/src/__tests__/_vitest-setup.js @@ -1,2 +1 @@ import '@testing-library/jest-dom/vitest' -import '../vitest' diff --git a/src/vite.js b/src/vite.js new file mode 100644 index 0000000..0062b89 --- /dev/null +++ b/src/vite.js @@ -0,0 +1,75 @@ +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +/** + * Vite plugin to configure @testing-library/svelte. + * + * Ensures Svelte is imported correctly in tests + * and that the DOM is cleaned up after each test. + * + * @param {{resolveBrowser?: boolean, autoCleanup?: boolean}} options + * @returns {import('vite').Plugin} + */ +export const svelteTesting = ({ + resolveBrowser = true, + autoCleanup = true, +} = {}) => ({ + name: 'vite-plugin-svelte-testing-library', + config: (config) => { + if (!process.env.VITEST) { + return + } + + if (resolveBrowser) { + addBrowserCondition(config) + } + + if (autoCleanup) { + addAutoCleanup(config) + } + }, +}) + +/** + * Add `browser` to `resolve.conditions` before `node`. + * + * This ensures that Svelte's browser code is used in tests, + * rather than its SSR code. + * + * @param {import('vitest/config').UserConfig} config + */ +const addBrowserCondition = (config) => { + const resolve = config.resolve ?? {} + const conditions = resolve.conditions ?? [] + const nodeConditionIndex = conditions.indexOf('node') + const browserConditionIndex = conditions.indexOf('browser') + + if ( + nodeConditionIndex >= 0 && + (nodeConditionIndex < browserConditionIndex || browserConditionIndex < 0) + ) { + conditions.splice(nodeConditionIndex, 0, 'browser') + } + + resolve.conditions = conditions + config.resolve = resolve +} + +/** + * Add auto-cleanup file to Vitest's setup files. + * + * @param {import('vitest/config').UserConfig} config + */ +const addAutoCleanup = (config) => { + const test = config.test ?? {} + let setupFiles = test.setupFiles ?? [] + + if (typeof setupFiles === 'string') { + setupFiles = [setupFiles] + } + + setupFiles.push(join(dirname(fileURLToPath(import.meta.url)), './vitest.js')) + + test.setupFiles = setupFiles + config.test = test +} diff --git a/types/vite.d.ts b/types/vite.d.ts new file mode 100644 index 0000000..470e487 --- /dev/null +++ b/types/vite.d.ts @@ -0,0 +1,12 @@ +import type { Plugin } from 'vite' + +/** + * Vite plugin to configure @testing-library/svelte. + * + * Ensures Svelte is imported correctly in tests + * and that the DOM is cleaned up after each test. + */ +export function svelteTesting(options?: { + resolveBrowser?: boolean + autoCleanup?: boolean +}): Plugin diff --git a/vite.config.js b/vite.config.js index d9085ef..293d426 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,6 +4,8 @@ import { svelte } from '@sveltejs/vite-plugin-svelte' import { VERSION as SVELTE_VERSION } from 'svelte/compiler' import { defineConfig } from 'vite' +import { svelteTesting } from './src/vite.js' + const IS_SVELTE_5 = SVELTE_VERSION >= '5' const alias = [ @@ -17,15 +19,8 @@ const alias = [ ] // https://vitejs.dev/config/ -export default defineConfig(({ mode }) => ({ - plugins: [svelte({ hot: false })], - resolve: { - // Ensure `browser` exports are used in tests - // Vitest prefers modules' `node` export by default - // Svelte's `node` export is its SSR bundle, which does not have onMount - // https://github.com/testing-library/svelte-testing-library/issues/222#issuecomment-1909993331 - conditions: mode === 'test' ? ['browser'] : [], - }, +export default defineConfig({ + plugins: [svelte({ hot: false }), svelteTesting()], test: { alias, environment: 'jsdom', @@ -37,4 +32,4 @@ export default defineConfig(({ mode }) => ({ include: ['src'], }, }, -})) +})