Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
max_line_length = off
trim_trailing_whitespace = false
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
"test:all": "pnpm test:vitest:jsdom && pnpm test:vitest:happy-dom && pnpm test:jest && pnpm test:examples",
"test:all:legacy": "pnpm test:vitest:jsdom && pnpm test:vitest:happy-dom && pnpm test:jest",
"build": "pnpm build:types && pnpm build:docs",
"build:types": "tsc --build && cp packages/svelte/src/core/types.d.ts packages/svelte/dist/core",
"build:docs": "remark --output --use remark-toc --use remark-code-import --use unified-prettier README.md examples && cp -f README.md packages/svelte",
"build:types": "tsc --build",
"build:docs": "remark --output --use remark-toc --use remark-code-import --use unified-prettier README.md packages/*/README.md examples && cp -f README.md packages/svelte",
"contributors:add": "all-contributors add",
"contributors:generate": "all-contributors generate",
"install:3": "./scripts/install-dependencies 3",
Expand Down
147 changes: 147 additions & 0 deletions packages/svelte-core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# @testing-library/svelte-core

Do you want to build your own Svelte testing library? You may want to use our
rendering core, which abstracts away differences in Svelte versions to provide a
simple API to render Svelte components into the document and clean them up
afterwards

## Table of Contents

- [Example Usage](#example-usage)
- [API](#api)
- [`setup`](#setup)
- [`mount`](#mount)
- [`cleanup`](#cleanup)
- [`addCleanupTask`](#addcleanuptask)
- [`removeCleanupTask`](#removecleanuptask)
- [Utility types](#utility-types)

## Example Usage

```ts
import { beforeEach } from 'vitest'
import { cleanup, mount, setup } from '@testing-library/svelte-core'
import type {
Component,
ComponentType,
ComponentOptions,
Props,
Exports,
} from '@testing-library/svelte-core/types'

import { bindQueries, type Screen } from './bring-your-own-queries.js'

beforeEach(() => {
cleanup()
})

export interface RenderResult<C extends Component> {
screen: Screen
component: Exports<C>
target: HTMLElement
rerender: (props: Partial<Props<C>>) => Promise<void>
unmount: () => void
}

export const render = <C extends Component>(
component: ComponentType<C>,
options: ComponentOptions<C>
): RenderResult<C> => {
const { baseElement, target, mountOptions } = setup(options)
const { component, unmount, rerender } = mount(component, mountOptions)
const screen = bindQueries(baseElement)

return { screen, component, target, rerender, unmount }
}
```

## API

### `setup`

Validate options and prepare document elements for rendering.

```ts
const { baseElement, target, mountOptions } = setup(options, renderOptions)
```

| Argument | Type | Description |
| ------------------ | ---------------------------------------- | ---------------------------------------------- |
| `componentOptions` | `Props` or partial [component options][] | Options for how the component will be rendered |
| `setupOptions` | `{ baseElement?: HTMLElement }` | Optionally override `baseElement` |

| Result | Type | Description | Default |
| -------------- | --------------------- | ------------------------------------------- | ----------------------------------- |
| `baseElement` | `HTMLElement` | The base element | `document.body` |
| `target` | `HTMLElement` | The component's `target` element | `<div>` appended to `document.body` |
| `mountOptions` | [component options][] | Validated Svelte options to pass to `mount` | `{ target, props: {} }` |

[component options]: https://svelte.dev/docs/client-side-component-api

### `mount`

Mount a Svelte component into the document.

```ts
const { component, unmount, rerender } = mount(Component, options)
```

| Argument | Type | Description |
| ----------- | --------------------- | ---------------------------- |
| `Component` | [Svelte component][] | An imported Svelte component |
| `options` | [component options][] | Svelte component options |

| Result | Type | Description |
| ----------- | ------------------------------------------ | -------------------------------------------------- |
| `component` | [component instance][] | The component instance |
| `unmount` | `() => void` | Unmount the component from the document |
| `rerender` | `(props: Partial<Props>) => Promise<void>` | Update the component's props and wait for rerender |

[Svelte component]: https://svelte.dev/docs/svelte-components
[component instance]: https://svelte.dev/docs/client-side-component-api

### `cleanup`

Cleanup rendered components and added elements. Call this when your tests are
over.

```ts
cleanup()
```

### `addCleanupTask`

Add a custom cleanup task to be called with `cleanup()`

```ts
addCleanupTask(() => {
// ...reset something
})
```

### `removeCleanupTask`

Remove a cleanup task from `cleanup()`. Useful if a cleanup task can only be run
once and may be run outside of `cleanup`

```ts
const customCleanup = () => {
// ...reset something
}

addCleanupTask(customCleanup)

const manuallyCleanupEarly = () => {
customCleanup()
removeCleanupTask(customCleanup)
}
```

### Utility types

This module exports various utility types from
`@testing-library/svelte-core/types`. They adapt to whatever Svelte version is
installed, and can be used to get type signatures for imported components,
props, events, etc.

See [`./types.d.ts`](./types.d.ts) for the full list of types available.
52 changes: 52 additions & 0 deletions packages/svelte-core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "@testing-library/svelte-core",
"version": "0.0.0-semantically-released",
"description": "Core rendering and cleanup logic for Svelte testing utilities.",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./src/index.js"
},
"./types": {
"types": "./types.d.ts"
}
},
"type": "module",
"license": "MIT",
"homepage": "https://github.com/testing-library/svelte-testing-library#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/testing-library/svelte-testing-library.git",
"directory": "packages/svelte-core"
},
"bugs": {
"url": "https://github.com/testing-library/svelte-testing-library/issues"
},
"engines": {
"node": ">=16"
},
"keywords": [
"testing",
"svelte",
"ui",
"dom",
"jsdom",
"unit",
"integration",
"functional",
"end-to-end",
"e2e"
],
"files": [
"dist",
"src",
"types.d.ts"
],
"peerDependencies": {
"svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ const mountComponent = IS_MODERN_SVELTE ? mountModern : mountLegacy
/**
* Render a Svelte component into the document.
*
* @template {import('./types.js').Component} C
* @param {import('./types.js').ComponentType<C>} Component
* @param {import('./types.js').MountOptions<C>} options
* @template {import('../types.js').Component} C
* @param {import('../types.js').ComponentType<C>} Component
* @param {import('../types.js').MountOptions<C>} options
* @returns {{
* component: C
* unmount: () => void
* rerender: (props: Partial<import('./types.js').Props<C>>) => Promise<void>
* rerender: (props: Partial<import('../types.js').Props<C>>) => Promise<void>
* }}
*/
const mount = (Component, options) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ class UnknownSvelteOptionsError extends TypeError {
/**
* Validate a component's mount options.
*
* @template {import('./types.js').Component} C
* @param {import('./types.js').ComponentOptions<C>} options - props or mount options
* @returns {Partial<import('./types.js').MountOptions<C>>}
* @template {import('../types.js').Component} C
* @param {import('../types.js').ComponentOptions<C>} options - props or mount options
* @returns {Partial<import('../types.js').MountOptions<C>>}
*/
const validateOptions = (options) => {
const isProps = !Object.keys(options).some((option) =>
Expand All @@ -55,16 +55,16 @@ const validateOptions = (options) => {
/**
* Set up the document to render a component.
*
* @template {import('./types.js').Component} C
* @param {import('./types.js').ComponentOptions<C>} componentOptions - props or mount options
* @template {import('../types.js').Component} C
* @param {import('../types.js').ComponentOptions<C>} componentOptions - props or mount options
* @param {{ baseElement?: HTMLElement | undefined }} setupOptions - base element of the document to bind any queries
* @returns {{
* baseElement: HTMLElement,
* target: HTMLElement,
* mountOptions: import('./types.js).MountOptions<C>
* mountOptions: import('../types.js').MountOptions<C>
* }}
*/
const setup = (componentOptions, setupOptions) => {
const setup = (componentOptions, setupOptions = {}) => {
const mountOptions = validateOptions(componentOptions)

const baseElement =
Expand Down
15 changes: 15 additions & 0 deletions packages/svelte-core/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"module": "node16",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"composite": true,
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src", "types.d.ts"]
}
File renamed without changes.
9 changes: 7 additions & 2 deletions packages/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
],
"files": [
"src",
"types"
"dist"
],
"peerDependencies": {
"svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0",
Expand All @@ -66,6 +66,11 @@
}
},
"dependencies": {
"@testing-library/dom": "9.x.x || 10.x.x"
"@testing-library/dom": "9.x.x || 10.x.x",
"@testing-library/svelte-core": "workspace:*"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
19 changes: 9 additions & 10 deletions packages/svelte/src/pure.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import {
getQueriesForElement,
prettyDOM,
} from '@testing-library/dom'
import * as Core from '@testing-library/svelte-core'
import * as Svelte from 'svelte'

import * as Core from './core/index.js'

/**
* Customize how Svelte renders the component.
*
* @template {import('./core/types.js').Component} C
* @typedef {import('./core/types.js').ComponentOptions<C>} SvelteComponentOptions
* @template {import('@testing-library/svelte-core/types').Component} C
* @typedef {import('@testing-library/svelte-core/types').ComponentOptions<C>} SvelteComponentOptions
*/

/**
Expand All @@ -29,15 +28,15 @@ import * as Core from './core/index.js'
/**
* The rendered component and bound testing functions.
*
* @template {import('./core/types.js').Component} C
* @template {import('@testing-library/svelte-core/types').Component} C
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
*
* @typedef {{
* container: HTMLElement
* baseElement: HTMLElement
* component: import('./core/types.js').Exports<C>
* component: import('@testing-library/svelte-core/types').Exports<C>
* debug: (el?: HTMLElement | DocumentFragment) => void
* rerender: (props: Partial<import('./core/types.js').Props<C>>) => Promise<void>
* rerender: (props: Partial<import('@testing-library/svelte-core/types').Props<C>>) => Promise<void>
* unmount: () => void
* } & {
* [P in keyof Q]: import('@testing-library/dom').BoundFunction<Q[P]>
Expand All @@ -47,10 +46,10 @@ import * as Core from './core/index.js'
/**
* Render a component into the document.
*
* @template {import('./core/types.js').Component} C
* @template {import('@testing-library/svelte-core/types').Component} C
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
*
* @param {import('./core/types.js').ComponentType<C>} Component - The component to render.
* @param {import('@testing-library/svelte-core/types').ComponentType<C>} Component - The component to render.
* @param {SvelteComponentOptions<C>} options - Customize how Svelte renders the component.
* @param {RenderOptions<Q>} renderOptions - Customize how Testing Library sets up the document and binds queries.
* @returns {RenderResult<C, Q>} The rendered component and bound testing functions.
Expand Down Expand Up @@ -155,5 +154,5 @@ for (const [key, baseEvent] of Object.entries(baseFireEvent)) {
fireEvent[key] = async (...args) => act(() => baseEvent(...args))
}

export { UnknownSvelteOptionsError } from './core/index.js'
export { UnknownSvelteOptionsError } from '@testing-library/svelte-core'
export { act, cleanup, fireEvent, render, setup }
Loading