From ee1c966c8d4ff7e8d0f0a80a19a086ae2b81983c Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Sat, 8 Jun 2024 23:26:34 -0400 Subject: [PATCH 1/5] ci(release): use conventionalcommits preset for release (#380) Closes #354, closes #356 --- .github/workflows/release.yml | 24 +++++++----------------- CONTRIBUTING.md | 15 ++++++++++++++- package.json | 5 +++-- release.config.js | 4 ++++ scripts/preview-release | 21 +++++++++++++++++++++ 5 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 release.config.js create mode 100755 scripts/preview-release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27af7a8..d63311e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,8 +69,8 @@ jobs: needs: main runs-on: ubuntu-latest if: ${{ github.repository == 'testing-library/svelte-testing-library' && - contains('refs/heads/main,refs/heads/beta,refs/heads/next,refs/heads/alpha', - github.ref) && github.event_name == 'push' }} + contains('refs/heads/main,refs/heads/next', github.ref) && + github.event_name == 'push' }} steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 @@ -78,24 +78,14 @@ jobs: - name: ⎔ Setup node uses: actions/setup-node@v4 with: - node-version: 16 - - - name: 📥 Download deps - run: npm install --no-package-lock + node-version: 20 - name: 🚀 Release - uses: cycjimmy/semantic-release-action@v2 + uses: cycjimmy/semantic-release-action@v4 with: - semantic_version: 17 - branches: | - [ - '+([0-9])?(.{+([0-9]),x}).x', - 'main', - 'next', - 'next-major', - {name: 'beta', prerelease: true}, - {name: 'alpha', prerelease: true} - ] + semantic_version: 24 + extra_plugins: | + conventional-changelog-conventionalcommits@8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5f35da..92856ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,10 +9,23 @@ ## Release -The module is released automatically from the `main` branch using [semantic-release-action][]. Version bumps and change logs are generated from the commit messages. +The module is released automatically from the `main` and `next` branches using [semantic-release-action][]. Version bumps and change logs are generated from the commit messages. [semantic-release-action]: https://github.com/cycjimmy/semantic-release-action +### Preview release + +If you would like to preview the release from a given branch, and... + +- You have push access to the repository +- The branch exists in GitHub + +...you can preview the next release version and changelog using: + +```shell +npm run preview-release +``` + ## Development setup After cloning the repository, install the project's dependencies and run the `validate` script to run all checks and tests to verify your setup. diff --git a/package.json b/package.json index a82fa78..c42f670 100644 --- a/package.json +++ b/package.json @@ -69,9 +69,10 @@ "test:vitest:happy-dom": "vitest run --coverage --environment happy-dom", "test:jest": "npx --node-options=\"--experimental-vm-modules --no-warnings\" jest --coverage", "types": "svelte-check", - "validate": "npm-run-all test:vitest:* types", + "validate": "npm-run-all test:vitest:* test:jest types", "contributors:add": "all-contributors add", - "contributors:generate": "all-contributors generate" + "contributors:generate": "all-contributors generate", + "preview-release": "./scripts/preview-release" }, "peerDependencies": { "svelte": "^3 || ^4 || ^5", diff --git a/release.config.js b/release.config.js new file mode 100644 index 0000000..7aeece8 --- /dev/null +++ b/release.config.js @@ -0,0 +1,4 @@ +export default { + preset: 'conventionalcommits', + branches: ['main', { name: 'next', prerelease: true }], +} diff --git a/scripts/preview-release b/scripts/preview-release new file mode 100755 index 0000000..82ebdee --- /dev/null +++ b/scripts/preview-release @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Preview the next release from a branch +# +# Prerequisites: +# - You must have push access to repository at the `origin` URL +# - The branch you are on must exist on `origin` + +set -euxo pipefail + +branch="$(git rev-parse --abbrev-ref HEAD)" +repository_url="$(git remote get-url origin)" + +npx \ + --package semantic-release@24 \ + --package conventional-changelog-conventionalcommits@8 \ + -- \ + semantic-release \ + --plugins="@semantic-release/commit-analyzer,@semantic-release/release-notes-generator" \ + --dry-run \ + --branches="$branch" \ + --repository-url="$repository_url" From 9dce164c7f1e0f1b012f7e4604352ac3bfd9eb98 Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Thu, 20 Jun 2024 11:17:14 -0400 Subject: [PATCH 2/5] feat(svelte5): incorporate Svelte 5 support into main entry point (#375) --- .eslintrc.cjs | 2 + README.md | 20 +-- jest.config.js | 8 +- package.json | 4 +- src/__tests__/auto-cleanup.test.js | 9 +- src/__tests__/fixtures/Comp.svelte | 1 + src/__tests__/fixtures/CompRunes.svelte | 13 ++ src/__tests__/fixtures/Mounter.svelte | 2 +- src/__tests__/render.test.js | 14 +- src/__tests__/rerender.test.js | 26 +-- src/__tests__/utils.js | 17 ++ src/core/index.js | 27 ++++ src/core/legacy.js | 46 ++++++ src/core/modern.svelte.js | 50 ++++++ src/core/validate-options.js | 39 +++++ src/index.js | 8 +- src/pure.js | 200 +++++++++--------------- src/svelte5-index.js | 23 --- src/svelte5.js | 30 ---- vite.config.js | 16 -- 20 files changed, 311 insertions(+), 244 deletions(-) create mode 100644 src/__tests__/fixtures/CompRunes.svelte create mode 100644 src/core/index.js create mode 100644 src/core/legacy.js create mode 100644 src/core/modern.svelte.js create mode 100644 src/core/validate-options.js delete mode 100644 src/svelte5-index.js delete mode 100644 src/svelte5.js diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 778d507..326785a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -25,6 +25,7 @@ module.exports = { }, rules: { 'no-undef-init': 'off', + 'prefer-const': 'off', }, }, { @@ -49,5 +50,6 @@ module.exports = { ecmaVersion: 2022, sourceType: 'module', }, + globals: { $state: 'readonly', $props: 'readonly' }, ignorePatterns: ['!/.*'], } diff --git a/README.md b/README.md index 51ca77a..12a1984 100644 --- a/README.md +++ b/README.md @@ -71,11 +71,11 @@ primary guiding principle is: This module is distributed via [npm][npm] which is bundled with [node][node] and should be installed as one of your project's `devDependencies`: -``` +```shell npm install --save-dev @testing-library/svelte ``` -This library has `peerDependencies` listings for `svelte >= 3`. +This library supports `svelte` versions `3`, `4`, and `5`. 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). @@ -102,22 +102,6 @@ See the [setup docs][] for more detailed setup instructions, including for other [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 add an alias to your `vite.config.js`: - -```js -export default defineConfig({ - plugins: [svelte(), svelteTesting()], - test: { - alias: { - '@testing-library/svelte': '@testing-library/svelte/svelte5', - }, - }, -}) -``` - ## Docs See the [**docs**](https://testing-library.com/docs/svelte-testing-library/intro) over at the Testing Library website. diff --git a/jest.config.js b/jest.config.js index d6b1fde..8e78075 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,11 +1,12 @@ import { VERSION as SVELTE_VERSION } from 'svelte/compiler' -const IS_SVELTE_5 = SVELTE_VERSION >= '5' +const SVELTE_TRANSFORM_PATTERN = + SVELTE_VERSION >= '5' ? '^.+\\.svelte(?:\\.js)?$' : '^.+\\.svelte$' export default { testMatch: ['/src/__tests__/**/*.test.js'], transform: { - '^.+\\.svelte$': 'svelte-jester', + [SVELTE_TRANSFORM_PATTERN]: 'svelte-jester', }, moduleFileExtensions: ['js', 'svelte'], extensionsToTreatAsEsm: ['.svelte'], @@ -14,9 +15,6 @@ export default { injectGlobals: false, moduleNameMapper: { '^vitest$': '/src/__tests__/_jest-vitest-alias.js', - '^@testing-library/svelte$': IS_SVELTE_5 - ? '/src/svelte5-index.js' - : '/src/index.js', }, resetMocks: true, restoreMocks: true, diff --git a/package.json b/package.json index c42f670..a79dc52 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "./svelte5": { "types": "./types/index.d.ts", - "default": "./src/svelte5-index.js" + "default": "./src/index.js" }, "./vitest": { "default": "./src/vitest.js" @@ -120,7 +120,7 @@ "prettier-plugin-svelte": "3.2.3", "svelte": "^3 || ^4 || ^5", "svelte-check": "^3.6.3", - "svelte-jester": "^3.0.0", + "svelte-jester": "^5.0.0", "typescript": "^5.3.3", "vite": "^5.1.1", "vitest": "^1.5.2" diff --git a/src/__tests__/auto-cleanup.test.js b/src/__tests__/auto-cleanup.test.js index b06d120..803001e 100644 --- a/src/__tests__/auto-cleanup.test.js +++ b/src/__tests__/auto-cleanup.test.js @@ -1,10 +1,5 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' -import { IS_SVELTE_5 } from './utils.js' - -const importSvelteTestingLibrary = async () => - IS_SVELTE_5 ? import('../svelte5-index.js') : import('../index.js') - const globalAfterEach = vi.fn() describe('auto-cleanup', () => { @@ -19,7 +14,7 @@ describe('auto-cleanup', () => { }) test('calls afterEach with cleanup if globally defined', async () => { - const { render } = await importSvelteTestingLibrary() + const { render } = await import('../index.js') expect(globalAfterEach).toHaveBeenCalledTimes(1) expect(globalAfterEach).toHaveBeenLastCalledWith(expect.any(Function)) @@ -35,7 +30,7 @@ describe('auto-cleanup', () => { test('does not call afterEach if process STL_SKIP_AUTO_CLEANUP is set', async () => { process.env.STL_SKIP_AUTO_CLEANUP = 'true' - await importSvelteTestingLibrary() + await import('../index.js') expect(globalAfterEach).toHaveBeenCalledTimes(0) }) diff --git a/src/__tests__/fixtures/Comp.svelte b/src/__tests__/fixtures/Comp.svelte index ba23d88..86d8acd 100644 --- a/src/__tests__/fixtures/Comp.svelte +++ b/src/__tests__/fixtures/Comp.svelte @@ -1,3 +1,4 @@ + + +

Hello {name}!

+ + diff --git a/src/__tests__/fixtures/Mounter.svelte b/src/__tests__/fixtures/Mounter.svelte index 51ebcd8..27205dd 100644 --- a/src/__tests__/fixtures/Mounter.svelte +++ b/src/__tests__/fixtures/Mounter.svelte @@ -16,4 +16,4 @@ }) - diff --git a/src/__tests__/render.test.js b/src/__tests__/render.test.js index ea445d5..f396751 100644 --- a/src/__tests__/render.test.js +++ b/src/__tests__/render.test.js @@ -1,11 +1,15 @@ import { render } from '@testing-library/svelte' -import { describe, expect, test } from 'vitest' +import { beforeAll, describe, expect, test } from 'vitest' -import Comp from './fixtures/Comp.svelte' -import { IS_SVELTE_5 } from './utils.js' +import { COMPONENT_FIXTURES } from './utils.js' -describe('render', () => { +describe.each(COMPONENT_FIXTURES)('render ($mode)', ({ component }) => { const props = { name: 'World' } + let Comp + + beforeAll(async () => { + Comp = await import(component) + }) test('renders component into the document', () => { const { getByText } = render(Comp, { props }) @@ -65,7 +69,7 @@ describe('render', () => { expect(baseElement.firstChild).toBe(container) }) - test.skipIf(IS_SVELTE_5)('should accept anchor option in Svelte v4', () => { + test('should accept anchor option', () => { const baseElement = document.body const target = document.createElement('section') const anchor = document.createElement('div') diff --git a/src/__tests__/rerender.test.js b/src/__tests__/rerender.test.js index 21a782c..52acd0f 100644 --- a/src/__tests__/rerender.test.js +++ b/src/__tests__/rerender.test.js @@ -1,10 +1,15 @@ import { act, render, screen } from '@testing-library/svelte' -import { VERSION as SVELTE_VERSION } from 'svelte/compiler' -import { describe, expect, test, vi } from 'vitest' +import { beforeAll, describe, expect, test, vi } from 'vitest' -import Comp from './fixtures/Comp.svelte' +import { COMPONENT_FIXTURES, IS_SVELTE_5, MODE_RUNES } from './utils.js' + +describe.each(COMPONENT_FIXTURES)('rerender ($mode)', ({ mode, component }) => { + let Comp + + beforeAll(async () => { + Comp = await import(component) + }) -describe('rerender', () => { test('updates props', async () => { const { rerender } = render(Comp, { name: 'World' }) const element = screen.getByText('Hello World!') @@ -29,13 +34,12 @@ describe('rerender', () => { ) }) - test('change props with accessors', async () => { - const { component, getByText } = render( - Comp, - SVELTE_VERSION < '5' - ? { accessors: true, props: { name: 'World' } } - : { name: 'World' } - ) + test.skipIf(mode === MODE_RUNES)('change props with accessors', async () => { + const componentOptions = IS_SVELTE_5 + ? { name: 'World' } + : { accessors: true, props: { name: 'World' } } + + const { component, getByText } = render(Comp, componentOptions) const element = getByText('Hello World!') expect(element).toBeInTheDocument() diff --git a/src/__tests__/utils.js b/src/__tests__/utils.js index 69be184..68be33c 100644 --- a/src/__tests__/utils.js +++ b/src/__tests__/utils.js @@ -5,3 +5,20 @@ export const IS_JSDOM = window.navigator.userAgent.includes('jsdom') export const IS_HAPPYDOM = !IS_JSDOM // right now it's happy or js export const IS_SVELTE_5 = SVELTE_VERSION >= '5' + +export const MODE_LEGACY = 'legacy' + +export const MODE_RUNES = 'runes' + +export const COMPONENT_FIXTURES = [ + { + mode: MODE_LEGACY, + component: './fixtures/Comp.svelte', + isEnabled: true, + }, + { + mode: MODE_RUNES, + component: './fixtures/CompRunes.svelte', + isEnabled: IS_SVELTE_5, + }, +].filter(({ isEnabled }) => isEnabled) diff --git a/src/core/index.js b/src/core/index.js new file mode 100644 index 0000000..f4a40aa --- /dev/null +++ b/src/core/index.js @@ -0,0 +1,27 @@ +/** + * Rendering core for svelte-testing-library. + * + * Defines how components are added to and removed from the DOM. + * Will switch to legacy, class-based mounting logic + * if it looks like we're in a Svelte <= 4 environment. + */ +import * as LegacyCore from './legacy.js' +import * as ModernCore from './modern.svelte.js' +import { + createValidateOptions, + UnknownSvelteOptionsError, +} from './validate-options.js' + +const { mount, unmount, updateProps, allowedOptions } = + ModernCore.IS_MODERN_SVELTE ? ModernCore : LegacyCore + +/** Validate component options. */ +const validateOptions = createValidateOptions(allowedOptions) + +export { + mount, + UnknownSvelteOptionsError, + unmount, + updateProps, + validateOptions, +} diff --git a/src/core/legacy.js b/src/core/legacy.js new file mode 100644 index 0000000..c9e6d1c --- /dev/null +++ b/src/core/legacy.js @@ -0,0 +1,46 @@ +/** + * Legacy rendering core for svelte-testing-library. + * + * Supports Svelte <= 4. + */ + +/** Allowed options for the component constructor. */ +const allowedOptions = [ + 'target', + 'accessors', + 'anchor', + 'props', + 'hydrate', + 'intro', + 'context', +] + +/** + * Mount the component into the DOM. + * + * The `onDestroy` callback is included for strict backwards compatibility + * with previous versions of this library. It's mostly unnecessary logic. + */ +const mount = (Component, options, onDestroy) => { + const component = new Component(options) + + if (typeof onDestroy === 'function') { + component.$$.on_destroy.push(() => { + onDestroy(component) + }) + } + + return component +} + +/** Remove the component from the DOM. */ +const unmount = (component) => { + component.$destroy() +} + +/** Update the component's props. */ +const updateProps = (component, nextProps) => { + component.$set(nextProps) +} + +export { allowedOptions, mount, unmount, updateProps } diff --git a/src/core/modern.svelte.js b/src/core/modern.svelte.js new file mode 100644 index 0000000..3da78b4 --- /dev/null +++ b/src/core/modern.svelte.js @@ -0,0 +1,50 @@ +/** + * Modern rendering core for svelte-testing-library. + * + * Supports Svelte >= 5. + */ +import * as Svelte from 'svelte' + +/** Props signals for each rendered component. */ +const propsByComponent = new Map() + +/** Whether we're using Svelte >= 5. */ +const IS_MODERN_SVELTE = typeof Svelte.mount === 'function' + +/** Allowed options to the `mount` call. */ +const allowedOptions = [ + 'target', + 'anchor', + 'props', + 'events', + 'context', + 'intro', +] + +/** Mount the component into the DOM. */ +const mount = (Component, options) => { + const props = $state(options.props ?? {}) + const component = Svelte.mount(Component, { ...options, props }) + + propsByComponent.set(component, props) + + return component +} + +/** Remove the component from the DOM. */ +const unmount = (component) => { + propsByComponent.delete(component) + Svelte.unmount(component) +} + +/** + * Update the component's props. + * + * Relies on the `$state` signal added in `mount`. + */ +const updateProps = (component, nextProps) => { + const prevProps = propsByComponent.get(component) + Object.assign(prevProps, nextProps) +} + +export { allowedOptions, IS_MODERN_SVELTE, mount, unmount, updateProps } diff --git a/src/core/validate-options.js b/src/core/validate-options.js new file mode 100644 index 0000000..c0d794b --- /dev/null +++ b/src/core/validate-options.js @@ -0,0 +1,39 @@ +class UnknownSvelteOptionsError extends TypeError { + constructor(unknownOptions, allowedOptions) { + super(`Unknown options. + + Unknown: [ ${unknownOptions.join(', ')} ] + Allowed: [ ${allowedOptions.join(', ')} ] + + To pass both Svelte options and props to a component, + or to use props that share a name with a Svelte option, + you must place all your props under the \`props\` key: + + render(Component, { props: { /** props here **/ } }) +`) + this.name = 'UnknownSvelteOptionsError' + } +} + +const createValidateOptions = (allowedOptions) => (options) => { + const isProps = !Object.keys(options).some((option) => + allowedOptions.includes(option) + ) + + if (isProps) { + return { props: options } + } + + // Check if any props and Svelte options were accidentally mixed. + const unknownOptions = Object.keys(options).filter( + (option) => !allowedOptions.includes(option) + ) + + if (unknownOptions.length > 0) { + throw new UnknownSvelteOptionsError(unknownOptions, allowedOptions) + } + + return options +} + +export { createValidateOptions, UnknownSvelteOptionsError } diff --git a/src/index.js b/src/index.js index 2e3d772..3d8f18f 100644 --- a/src/index.js +++ b/src/index.js @@ -17,4 +17,10 @@ export * from '@testing-library/dom' // export svelte-specific functions and custom `fireEvent` // `fireEvent` must be a named export to take priority over wildcard export above -export { act, cleanup, fireEvent, render } from './pure.js' +export { + act, + cleanup, + fireEvent, + render, + UnknownSvelteOptionsError, +} from './pure.js' diff --git a/src/pure.js b/src/pure.js index 364c225..71dff1e 100644 --- a/src/pure.js +++ b/src/pure.js @@ -3,155 +3,105 @@ import { getQueriesForElement, prettyDOM, } from '@testing-library/dom' -import * as Svelte from 'svelte' -import { VERSION as SVELTE_VERSION } from 'svelte/compiler' - -const IS_SVELTE_5 = /^5\./.test(SVELTE_VERSION) - -export class SvelteTestingLibrary { - svelteComponentOptions = [ - 'target', - 'accessors', - 'anchor', - 'props', - 'hydrate', - 'intro', - 'context', - ] - - targetCache = new Set() - componentCache = new Set() - - checkProps(options) { - const isProps = !Object.keys(options).some((option) => - this.svelteComponentOptions.includes(option) - ) - - // Check if any props and Svelte options were accidentally mixed. - if (!isProps) { - const unrecognizedOptions = Object.keys(options).filter( - (option) => !this.svelteComponentOptions.includes(option) - ) - - if (unrecognizedOptions.length > 0) { - throw Error(` - Unknown options were found [${unrecognizedOptions}]. This might happen if you've mixed - passing in props with Svelte options into the render function. Valid Svelte options - are [${this.svelteComponentOptions}]. You can either change the prop names, or pass in your - props for that component via the \`props\` option.\n\n - Eg: const { /** Results **/ } = render(MyComponent, { props: { /** props here **/ } })\n\n - `) - } - - return options - } - - return { props: options } - } - - render(Component, componentOptions = {}, renderOptions = {}) { - componentOptions = this.checkProps(componentOptions) - - const baseElement = - renderOptions.baseElement ?? componentOptions.target ?? document.body - - const target = - componentOptions.target ?? - baseElement.appendChild(document.createElement('div')) - - this.targetCache.add(target) - - const ComponentConstructor = Component.default || Component - - const component = this.renderComponent(ComponentConstructor, { - ...componentOptions, - target, - }) - - return { - baseElement, - component, - container: target, - debug: (el = baseElement) => console.log(prettyDOM(el)), - rerender: async (props) => { - if (props.props) { - console.warn( - 'rerender({ props: {...} }) deprecated, use rerender({...}) instead' - ) - props = props.props - } - component.$set(props) - await Svelte.tick() - }, - unmount: () => { - this.cleanupComponent(component) - }, - ...getQueriesForElement(baseElement, renderOptions.queries), - } - } - - renderComponent(ComponentConstructor, componentOptions) { - if (IS_SVELTE_5) { - throw new Error('for Svelte 5, use `@testing-library/svelte/svelte5`') - } - - const component = new ComponentConstructor(componentOptions) +import { tick } from 'svelte' - this.componentCache.add(component) - - // TODO(mcous, 2024-02-11): remove this behavior in the next major version - component.$$.on_destroy.push(() => { - this.componentCache.delete(component) - }) +import { + mount, + UnknownSvelteOptionsError, + unmount, + updateProps, + validateOptions, +} from './core/index.js' + +const targetCache = new Set() +const componentCache = new Set() + +const render = (Component, options = {}, renderOptions = {}) => { + options = validateOptions(options) + + const baseElement = + renderOptions.baseElement ?? options.target ?? document.body + + const queries = getQueriesForElement(baseElement, renderOptions.queries) + + const target = + options.target ?? baseElement.appendChild(document.createElement('div')) + + targetCache.add(target) + + const component = mount( + Component.default ?? Component, + { ...options, target }, + cleanupComponent + ) + + componentCache.add(component) + + return { + baseElement, + component, + container: target, + debug: (el = baseElement) => { + console.log(prettyDOM(el)) + }, + rerender: async (props) => { + if (props.props) { + console.warn( + 'rerender({ props: {...} }) deprecated, use rerender({...}) instead' + ) + props = props.props + } - return component + updateProps(component, props) + await tick() + }, + unmount: () => { + cleanupComponent(component) + }, + ...queries, } +} - cleanupComponent(component) { - const inCache = this.componentCache.delete(component) +const cleanupComponent = (component) => { + const inCache = componentCache.delete(component) - if (inCache) { - component.$destroy() - } + if (inCache) { + unmount(component) } +} - cleanupTarget(target) { - const inCache = this.targetCache.delete(target) - - if (inCache && target.parentNode === document.body) { - document.body.removeChild(target) - } - } +const cleanupTarget = (target) => { + const inCache = targetCache.delete(target) - cleanup() { - this.componentCache.forEach(this.cleanupComponent.bind(this)) - this.targetCache.forEach(this.cleanupTarget.bind(this)) + if (inCache && target.parentNode === document.body) { + document.body.removeChild(target) } } -const instance = new SvelteTestingLibrary() - -export const render = instance.render.bind(instance) - -export const cleanup = instance.cleanup.bind(instance) +const cleanup = () => { + componentCache.forEach(cleanupComponent) + targetCache.forEach(cleanupTarget) +} -export const act = async (fn) => { +const act = async (fn) => { if (fn) { await fn() } - return Svelte.tick() + return tick() } -export const fireEvent = async (...args) => { +const fireEvent = async (...args) => { const event = dtlFireEvent(...args) - await Svelte.tick() + await tick() return event } Object.keys(dtlFireEvent).forEach((key) => { fireEvent[key] = async (...args) => { const event = dtlFireEvent[key](...args) - await Svelte.tick() + await tick() return event } }) + +export { act, cleanup, fireEvent, render, UnknownSvelteOptionsError } diff --git a/src/svelte5-index.js b/src/svelte5-index.js deleted file mode 100644 index ab49641..0000000 --- a/src/svelte5-index.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable import/export */ -import { act } from './pure.js' -import { cleanup } from './svelte5.js' - -// If we're running in a test runner that supports afterEach -// then we'll automatically run cleanup afterEach test -// this ensures that tests run in isolation from each other -// if you don't like this then either import the `pure` module -// or set the STL_SKIP_AUTO_CLEANUP env variable to 'true'. -if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) { - afterEach(async () => { - await act() - cleanup() - }) -} - -// export all base queries, screen, etc. -export * from '@testing-library/dom' - -// export svelte-specific functions and custom `fireEvent` -// `fireEvent` must be a named export to take priority over wildcard export above -export { act, fireEvent } from './pure.js' -export { cleanup, render } from './svelte5.js' diff --git a/src/svelte5.js b/src/svelte5.js deleted file mode 100644 index a8dd494..0000000 --- a/src/svelte5.js +++ /dev/null @@ -1,30 +0,0 @@ -import { createClassComponent } from 'svelte/legacy' - -import { SvelteTestingLibrary } from './pure.js' - -class Svelte5TestingLibrary extends SvelteTestingLibrary { - svelteComponentOptions = [ - 'target', - 'props', - 'events', - 'context', - 'intro', - 'recover', - ] - - renderComponent(ComponentConstructor, componentOptions) { - const component = createClassComponent({ - ...componentOptions, - component: ComponentConstructor, - }) - - this.componentCache.add(component) - - return component - } -} - -const instance = new Svelte5TestingLibrary() - -export const render = instance.render.bind(instance) -export const cleanup = instance.cleanup.bind(instance) diff --git a/vite.config.js b/vite.config.js index b18c504..b165d41 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,28 +1,12 @@ -import path from 'node:path' - 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 = [ - { - find: '@testing-library/svelte', - replacement: path.resolve( - __dirname, - IS_SVELTE_5 ? 'src/svelte5-index.js' : 'src/index.js' - ), - }, -] - // https://vitejs.dev/config/ export default defineConfig({ plugins: [svelte(), svelteTesting()], test: { - alias, environment: 'jsdom', setupFiles: ['./src/__tests__/_vitest-setup.js'], mockReset: true, From be82df14e76913b87967d5b7eb83eb97ca9c04ab Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Mon, 24 Jun 2024 15:09:25 -0400 Subject: [PATCH 3/5] docs(README): move links to sections that use them, remove unused links (#383) --- README.md | 119 +++++++++++++++++++++++++++++------------------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 12a1984..bf45331 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,18 @@

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/testing-library/testing-library-docs) +[**Read The Docs**][stl-docs] | [Edit the docs][stl-docs-repo] [![Build Status][build-badge]][build] [![Code Coverage][coverage-badge]][coverage] -[![version][version-badge]][package] [![downloads][downloads-badge]][npmtrends] +[![version][version-badge]][package] +[![downloads][downloads-badge]][downloads] [![MIT License][license-badge]][license] -[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-) -[![PRs Welcome][prs-badge]][prs] [![Code of Conduct][coc-badge]][coc] +[![All Contributors][contributors-badge]][contributors] +[![PRs Welcome][prs-badge]][prs] +[![Code of Conduct][coc-badge]][coc] [![Discord][discord-badge]][discord] [![Watch on GitHub][github-watch-badge]][github-watch] @@ -33,6 +34,33 @@
+[stl-docs]: https://testing-library.com/docs/svelte-testing-library/intro +[stl-docs-repo]: https://github.com/testing-library/testing-library-docs +[build-badge]: https://img.shields.io/github/actions/workflow/status/testing-library/svelte-testing-library/release.yml?style=flat-square +[build]: https://github.com/testing-library/svelte-testing-library/actions +[coverage-badge]: https://img.shields.io/codecov/c/github/testing-library/svelte-testing-library.svg?style=flat-square +[coverage]: https://codecov.io/github/testing-library/svelte-testing-library +[version-badge]: https://img.shields.io/npm/v/@testing-library/svelte.svg?style=flat-square +[package]: https://www.npmjs.com/package/@testing-library/svelte +[downloads-badge]: https://img.shields.io/npm/dm/@testing-library/svelte.svg?style=flat-square +[downloads]: http://www.npmtrends.com/@testing-library/svelte +[license-badge]: https://img.shields.io/github/license/testing-library/svelte-testing-library?color=b&style=flat-square +[license]: https://github.com/testing-library/svelte-testing-library/blob/main/LICENSE +[contributors-badge]: https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square +[contributors]: #contributors +[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square +[prs]: http://makeapullrequest.com +[coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square +[coc]: https://github.com/testing-library/svelte-testing-library/blob/main/CODE_OF_CONDUCT.md +[discord-badge]: https://img.shields.io/discord/723559267868737556.svg?color=7389D8&labelColor=6A7EC2&logo=discord&logoColor=ffffff&style=flat-square +[discord]: https://discord.gg/testing-library +[github-watch-badge]: https://img.shields.io/github/watchers/testing-library/svelte-testing-library.svg?style=social +[github-watch]: https://github.com/testing-library/svelte-testing-library/watchers +[github-star-badge]: https://img.shields.io/github/stars/testing-library/svelte-testing-library.svg?style=social +[github-star]: https://github.com/testing-library/svelte-testing-library/stargazers +[twitter]: https://twitter.com/intent/tweet?text=Check%20out%20svelte-testing-library%20by%20%40@TestingLib%20https%3A%2F%2Fgithub.com%2Ftesting-library%2Fsvelte-testing-library%20%F0%9F%91%8D +[twitter-badge]: https://img.shields.io/twitter/url/https/github.com/testing-library/svelte-testing-library.svg?style=social + ## Table of Contents @@ -41,31 +69,34 @@ - [The Problem](#the-problem) - [This Solution](#this-solution) - [Installation](#installation) +- [Setup](#setup) - [Docs](#docs) - [Issues](#issues) - [🐛 Bugs](#-bugs) - [💡 Feature Requests](#-feature-requests) - [❓ Questions](#-questions) - [Contributors](#contributors) -- [LICENSE](#license) ## The Problem -You want to write tests for your Svelte components so that they avoid including implementation -details, and are maintainable in the long run. +You want to write maintainable tests for your [Svelte][svelte] components. + +[svelte]: https://svelte.dev/ ## This Solution -The `svelte-testing-library` is a very lightweight solution for testing Svelte -components. It provides light utility functions on top of `svelte` and -`dom-testing-library`, in a way that encourages better testing practices. Its -primary guiding principle is: +`@testing-library/svelte` is a lightweight library for testing Svelte +components. It provides functions on top of `svelte` and +`@testing-library/dom` so you can mount Svelte components and query their +rendered output in the DOM. Its primary guiding principle is: > [The more your tests resemble the way your software is used, the more > confidence they can give you.][guiding-principle] +[guiding-principle]: https://twitter.com/kentcdodds/status/977018512689455106 + ## Installation This module is distributed via [npm][npm] which is bundled with [node][node] and @@ -77,12 +108,18 @@ npm install --save-dev @testing-library/svelte This library supports `svelte` versions `3`, `4`, and `5`. -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). +You may also be interested in installing `@testing-library/jest-dom` so you can +use [the custom jest matchers][jest-dom]. + +[npm]: https://www.npmjs.com/ +[node]: https://nodejs.org +[jest-dom]: 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. +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 @@ -97,26 +134,31 @@ We recommend using `@testing-library/svelte` with [Vitest][] as your test runner }); ``` -See the [setup docs][] for more detailed setup instructions, including for other test runners like Jest. +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 ## Docs -See the [**docs**](https://testing-library.com/docs/svelte-testing-library/intro) over at the Testing Library website. +See the [**docs**][stl-docs] over at the Testing Library website. ## Issues _Looking to contribute? Look for the [Good First Issue][good-first-issue] label._ +[good-first-issue]: https://github.com/testing-library/svelte-testing-library/issues?utf8=✓&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3A"good+first+issue"+ + ### 🐛 Bugs Please file an issue for bugs, missing documentation, or unexpected behavior. [**See Bugs**][bugs] +[bugs]: https://github.com/testing-library/svelte-testing-library/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Acreated-desc + ### 💡 Feature Requests Please file an issue to suggest new features. Vote on feature requests by adding @@ -124,6 +166,8 @@ a 👍. This helps maintainers prioritize what to work on. [**See Feature Requests**][requests] +[requests]: https://github.com/testing-library/svelte-testing-library/issues?q=is%3Aissue+sort%3Areactions-%2B1-desc+label%3Aenhancement+is%3Aopen + ### ❓ Questions For questions related to using the library, please visit a support community @@ -132,6 +176,8 @@ instead of filing an issue on GitHub. - [Discord][discord] - [Stack Overflow][stackoverflow] +[stackoverflow]: https://stackoverflow.com/questions/tagged/svelte-testing-library + ## Contributors Thanks goes to these people ([emoji key][emojis]): @@ -173,44 +219,5 @@ Thanks goes to these people ([emoji key][emojis]): This project follows the [all-contributors][all-contributors] specification. Contributions of any kind welcome! -## LICENSE - -[MIT](LICENSE) - - - -[npm]: https://www.npmjs.com/ -[node]: https://nodejs.org -[build-badge]: https://img.shields.io/github/actions/workflow/status/testing-library/svelte-testing-library/release.yml?style=flat-square -[build]: https://github.com/testing-library/svelte-testing-library/actions -[coverage-badge]: https://img.shields.io/codecov/c/github/testing-library/svelte-testing-library.svg?style=flat-square -[coverage]: https://codecov.io/github/testing-library/svelte-testing-library -[version-badge]: https://img.shields.io/npm/v/@testing-library/svelte.svg?style=flat-square -[package]: https://www.npmjs.com/package/@testing-library/svelte -[downloads-badge]: https://img.shields.io/npm/dm/@testing-library/svelte.svg?style=flat-square -[npmtrends]: http://www.npmtrends.com/@testing-library/svelte -[discord-badge]: https://img.shields.io/discord/723559267868737556.svg?color=7389D8&labelColor=6A7EC2&logo=discord&logoColor=ffffff&style=flat-square -[discord]: https://discord.gg/testing-library -[license-badge]: https://img.shields.io/github/license/testing-library/svelte-testing-library?color=b&style=flat-square -[license]: https://github.com/testing-library/svelte-testing-library/blob/main/LICENSE -[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square -[prs]: http://makeapullrequest.com -[donate-badge]: https://img.shields.io/badge/$-support-green.svg?style=flat-square -[coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square -[coc]: https://github.com/testing-library/svelte-testing-library/blob/main/CODE_OF_CONDUCT.md -[github-watch-badge]: https://img.shields.io/github/watchers/testing-library/svelte-testing-library.svg?style=social -[github-watch]: https://github.com/testing-library/svelte-testing-library/watchers -[github-star-badge]: https://img.shields.io/github/stars/testing-library/svelte-testing-library.svg?style=social -[github-star]: https://github.com/testing-library/svelte-testing-library/stargazers -[twitter]: https://twitter.com/intent/tweet?text=Check%20out%20svelte-testing-library%20by%20%40@TestingLib%20https%3A%2F%2Fgithub.com%2Ftesting-library%2Fsvelte-testing-library%20%F0%9F%91%8D -[twitter-badge]: https://img.shields.io/twitter/url/https/github.com/testing-library/svelte-testing-library.svg?style=social [emojis]: https://github.com/all-contributors/all-contributors#emoji-key [all-contributors]: https://github.com/all-contributors/all-contributors -[set-immediate]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate -[guiding-principle]: https://twitter.com/kentcdodds/status/977018512689455106 -[bugs]: https://github.com/testing-library/svelte-testing-library/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Acreated-desc -[requests]: https://github.com/testing-library/svelte-testing-library/issues?q=is%3Aissue+sort%3Areactions-%2B1-desc+label%3Aenhancement+is%3Aopen -[good-first-issue]: https://github.com/testing-library/svelte-testing-library/issues?utf8=✓&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3A"good+first+issue"+ -[stackoverflow]: https://stackoverflow.com/questions/tagged/svelte-testing-library - - From a8f21f8402b736bac65a1361d075c0399542ff06 Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Tue, 25 Jun 2024 21:41:54 -0400 Subject: [PATCH 4/5] fix(types): build types from JS source (#376) --- .eslintignore | 1 + .eslintrc.cjs | 1 + .github/workflows/release.yml | 34 ++++++++- .gitignore | 3 + package.json | 7 +- {types => src/__tests__}/types.test-d.ts | 36 +++++++++- src/index.js | 12 ++-- src/pure.js | 91 ++++++++++++++++++++---- tsconfig.build.json | 12 ++++ tsconfig.json | 3 +- types/index.d.ts | 82 --------------------- types/vite.d.ts | 12 ---- 12 files changed, 172 insertions(+), 122 deletions(-) rename {types => src/__tests__}/types.test-d.ts (67%) create mode 100644 tsconfig.build.json delete mode 100644 types/index.d.ts delete mode 100644 types/vite.d.ts diff --git a/.eslintignore b/.eslintignore index 111e490..39f9d9e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,4 @@ scripts/* .prettierignore .github/workflows/* *.md +types diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 326785a..f403dca 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -26,6 +26,7 @@ module.exports = { rules: { 'no-undef-init': 'off', 'prefer-const': 'off', + 'svelte/no-unused-svelte-ignore': 'off', }, }, { diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d63311e..ef92dc0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,14 +59,38 @@ jobs: run: npm run test:${{ matrix.test-runner }} - name: ▶️ Run type-checks - if: ${{ matrix.node == '20' && matrix.svelte == '4' && matrix.test-runner == 'vitest:jsdom' }} + # NOTE: `SvelteComponent` is not generic in Svelte v3, so type-checking will not pass + if: ${{ matrix.node == '20' && matrix.svelte != '3' && matrix.test-runner == 'vitest:jsdom' }} run: npm run types - name: ⬆️ Upload coverage report uses: codecov/codecov-action@v3 + build: + runs-on: ubuntu-latest + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: ⎔ Setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: 📥 Download deps + run: npm install --no-package-lock + + - name: 🏗️ Build types + run: npm run build + + - name: ⬆️ Upload types build + uses: actions/upload-artifact@v4 + with: + name: types + path: types + release: - needs: main + needs: [main, build] runs-on: ubuntu-latest if: ${{ github.repository == 'testing-library/svelte-testing-library' && contains('refs/heads/main,refs/heads/next', github.ref) && @@ -80,6 +104,12 @@ jobs: with: node-version: 20 + - name: 📥 Downloads types build + uses: actions/download-artifact@v4 + with: + name: types + path: types + - name: 🚀 Release uses: cycjimmy/semantic-release-action@v4 with: diff --git a/.gitignore b/.gitignore index c09be87..151e826 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ dist yarn-error.log package-lock.json yarn.lock + +# generated typing output +types diff --git a/package.json b/package.json index a79dc52..ebd9512 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "default": "./src/index.js" }, "./vitest": { + "types": "./types/vitest.d.ts", "default": "./src/vitest.js" }, "./vite": { @@ -26,7 +27,7 @@ "homepage": "https://github.com/testing-library/svelte-testing-library#readme", "repository": { "type": "git", - "url": "https://github.com/testing-library/svelte-testing-library" + "url": "git+https://github.com/testing-library/svelte-testing-library.git" }, "bugs": { "url": "https://github.com/testing-library/svelte-testing-library/issues" @@ -49,7 +50,6 @@ "files": [ "src", "types", - "!*.test-d.ts", "!__tests__" ], "scripts": { @@ -69,7 +69,8 @@ "test:vitest:happy-dom": "vitest run --coverage --environment happy-dom", "test:jest": "npx --node-options=\"--experimental-vm-modules --no-warnings\" jest --coverage", "types": "svelte-check", - "validate": "npm-run-all test:vitest:* test:jest types", + "validate": "npm-run-all test:vitest:* test:jest types build", + "build": "tsc -p tsconfig.build.json", "contributors:add": "all-contributors add", "contributors:generate": "all-contributors generate", "preview-release": "./scripts/preview-release" diff --git a/types/types.test-d.ts b/src/__tests__/types.test-d.ts similarity index 67% rename from types/types.test-d.ts rename to src/__tests__/types.test-d.ts index 4a42bb1..9927a05 100644 --- a/types/types.test-d.ts +++ b/src/__tests__/types.test-d.ts @@ -2,8 +2,8 @@ import { expectTypeOf } from 'expect-type' import type { ComponentProps, SvelteComponent } from 'svelte' import { describe, test } from 'vitest' -import Simple from '../src/__tests__/fixtures/Simple.svelte' -import * as subject from './index.js' +import * as subject from '../index.js' +import Simple from './fixtures/Simple.svelte' describe('types', () => { test('render is a function that accepts a Svelte component', () => { @@ -62,4 +62,36 @@ describe('types', () => { expectTypeOf(result.getByVibes).parameters.toMatchTypeOf<[vibes: string]>() }) + + test('act is an async function', () => { + expectTypeOf(subject.act).toMatchTypeOf<() => Promise>() + }) + + test('act accepts a sync function', () => { + expectTypeOf(subject.act).toMatchTypeOf<(fn: () => void) => Promise>() + }) + + test('act accepts an async function', () => { + expectTypeOf(subject.act).toMatchTypeOf< + (fn: () => Promise) => Promise + >() + }) + + test('fireEvent is an async function', () => { + expectTypeOf(subject.fireEvent).toMatchTypeOf< + ( + element: Element | Node | Document | Window, + event: Event + ) => Promise + >() + }) + + test('fireEvent[eventName] is an async function', () => { + expectTypeOf(subject.fireEvent.click).toMatchTypeOf< + ( + element: Element | Node | Document | Window, + options?: {} + ) => Promise + >() + }) }) diff --git a/src/index.js b/src/index.js index 3d8f18f..2704824 100644 --- a/src/index.js +++ b/src/index.js @@ -16,11 +16,7 @@ if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) { export * from '@testing-library/dom' // export svelte-specific functions and custom `fireEvent` -// `fireEvent` must be a named export to take priority over wildcard export above -export { - act, - cleanup, - fireEvent, - render, - UnknownSvelteOptionsError, -} from './pure.js' +export { UnknownSvelteOptionsError } from './core/index.js' +export * from './pure.js' +// `fireEvent` must be named to take priority over wildcard from @testing-library/dom +export { fireEvent } from './pure.js' diff --git a/src/pure.js b/src/pure.js index 71dff1e..edb94b3 100644 --- a/src/pure.js +++ b/src/pure.js @@ -1,21 +1,61 @@ import { - fireEvent as dtlFireEvent, + fireEvent as baseFireEvent, getQueriesForElement, prettyDOM, } from '@testing-library/dom' import { tick } from 'svelte' -import { - mount, - UnknownSvelteOptionsError, - unmount, - updateProps, - validateOptions, -} from './core/index.js' +import { mount, unmount, updateProps, validateOptions } from './core/index.js' const targetCache = new Set() const componentCache = new Set() +/** + * Customize how Svelte renders the component. + * + * @template {import('svelte').SvelteComponent} C + * @typedef {import('svelte').ComponentProps | Partial>>} SvelteComponentOptions + */ + +/** + * Customize how Testing Library sets up the document and binds queries. + * + * @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries] + * @typedef {{ + * baseElement?: HTMLElement + * queries?: Q + * }} RenderOptions + */ + +/** + * The rendered component and bound testing functions. + * + * @template {import('svelte').SvelteComponent} C + * @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries] + * + * @typedef {{ + * container: HTMLElement + * baseElement: HTMLElement + * component: C + * debug: (el?: HTMLElement | DocumentFragment) => void + * rerender: (props: Partial>) => Promise + * unmount: () => void + * } & { + * [P in keyof Q]: import('@testing-library/dom').BoundFunction + * }} RenderResult + */ + +/** + * Render a component into the document. + * + * @template {import('svelte').SvelteComponent} C + * @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries] + * + * @param {import('svelte').ComponentType} Component - The component to render. + * @param {SvelteComponentOptions} options - Customize how Svelte renders the component. + * @param {RenderOptions} renderOptions - Customize how Testing Library sets up the document and binds queries. + * @returns {RenderResult} The rendered component and bound testing functions. + */ const render = (Component, options = {}, renderOptions = {}) => { options = validateOptions(options) @@ -62,6 +102,7 @@ const render = (Component, options = {}, renderOptions = {}) => { } } +/** Remove a component from the component cache. */ const cleanupComponent = (component) => { const inCache = componentCache.delete(component) @@ -70,6 +111,7 @@ const cleanupComponent = (component) => { } } +/** Remove a target element from the target cache. */ const cleanupTarget = (target) => { const inCache = targetCache.delete(target) @@ -78,11 +120,18 @@ const cleanupTarget = (target) => { } } +/** Unmount all components and remove elements added to ``. */ const cleanup = () => { componentCache.forEach(cleanupComponent) targetCache.forEach(cleanupTarget) } +/** + * Call a function and wait for Svelte to flush pending changes. + * + * @param {() => unknown} [fn] - A function, which may be `async`, to call before flushing updates. + * @returns {Promise} + */ const act = async (fn) => { if (fn) { await fn() @@ -90,18 +139,36 @@ const act = async (fn) => { return tick() } +/** + * @typedef {(...args: Parameters) => Promise>} FireFunction + */ + +/** + * @typedef {{ + * [K in import('@testing-library/dom').EventType]: (...args: Parameters) => Promise> + * }} FireObject + */ + +/** + * Fire an event on an element. + * + * Consider using `@testing-library/user-event` instead, if possible. + * @see https://testing-library.com/docs/user-event/intro/ + * + * @type {FireFunction & FireObject} + */ const fireEvent = async (...args) => { - const event = dtlFireEvent(...args) + const event = baseFireEvent(...args) await tick() return event } -Object.keys(dtlFireEvent).forEach((key) => { +Object.keys(baseFireEvent).forEach((key) => { fireEvent[key] = async (...args) => { - const event = dtlFireEvent[key](...args) + const event = baseFireEvent[key](...args) await tick() return event } }) -export { act, cleanup, fireEvent, render, UnknownSvelteOptionsError } +export { act, cleanup, fireEvent, render } diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..0baa218 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": ["./tsconfig.json"], + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "noEmit": false, + "rootDir": "src", + "outDir": "types" + }, + "exclude": ["src/**/__tests__/**"] +} diff --git a/tsconfig.json b/tsconfig.json index 2b353f3..f79cace 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,11 @@ { "compilerOptions": { "module": "node16", + "allowJs": true, "noEmit": true, "skipLibCheck": true, "strict": true, "types": ["svelte", "vite/client", "vitest", "vitest/globals"] }, - "include": ["src", "types"] + "include": ["src"] } diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index a206467..0000000 --- a/types/index.d.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Type definitions for Svelte Testing Library -// Project: https://github.com/testing-library/svelte-testing-library -// Definitions by: Rahim Alwer - -import { - BoundFunction, - EventType, - Queries, - queries, -} from '@testing-library/dom' -import { - ComponentConstructorOptions, - ComponentProps, - SvelteComponent, -} from 'svelte' - -export * from '@testing-library/dom' - -type SvelteComponentOptions = - | ComponentProps - | Partial>> - -type Constructor = new (...args: any[]) => T - -/** - * Render a Component into the Document. - */ -export type RenderResult< - C extends SvelteComponent, - Q extends Queries = typeof queries, -> = { - container: HTMLElement - baseElement: HTMLElement - component: C - debug: (el?: HTMLElement | DocumentFragment) => void - rerender: (props: Partial>) => Promise - unmount: () => void -} & { [P in keyof Q]: BoundFunction } - -export interface RenderOptions { - baseElement?: HTMLElement - queries?: Q -} - -export function render< - C extends SvelteComponent, - Q extends Queries = typeof queries, ->( - component: Constructor, - componentOptions?: SvelteComponentOptions, - renderOptions?: RenderOptions -): RenderResult - -/** - * Unmounts trees that were mounted with render. - */ -export function cleanup(): void - -/** - * Fires DOM events on an element provided by @testing-library/dom. Since Svelte needs to flush - * pending state changes via `tick`, these methods have been override and now return a promise. - */ -export type FireFunction = ( - element: Document | Element | Window, - event: Event -) => Promise - -export type FireObject = { - [K in EventType]: ( - element: Document | Element | Window, - options?: {} - ) => Promise -} - -export const fireEvent: FireFunction & FireObject - -/** - * Calls a function and notifies Svelte to flush any pending state changes. - * - * If the function returns a Promise, that Promise will be resolved first. - */ -export function act(fn?: () => unknown): Promise diff --git a/types/vite.d.ts b/types/vite.d.ts deleted file mode 100644 index 470e487..0000000 --- a/types/vite.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -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 From 2fb7423e5b89b982da0297cbea990cb0ad683700 Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Wed, 26 Jun 2024 11:23:04 -0400 Subject: [PATCH 5/5] fix(deps): allow svelte 5.0.0-next as peer dep (#384) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ebd9512..47d6103 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "preview-release": "./scripts/preview-release" }, "peerDependencies": { - "svelte": "^3 || ^4 || ^5", + "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", "vite": "*", "vitest": "*" }, @@ -119,7 +119,7 @@ "npm-run-all": "^4.1.5", "prettier": "3.2.5", "prettier-plugin-svelte": "3.2.3", - "svelte": "^3 || ^4 || ^5", + "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", "svelte-check": "^3.6.3", "svelte-jester": "^5.0.0", "typescript": "^5.3.3",