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)
+[**Read The Docs**][stl-docs] | [Edit the docs][stl-docs-repo] | [Examples](./examples)
+
[![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]
-[](#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]
[![Star on GitHub][github-star-badge]][github-star]
[![Tweet][twitter-badge]][twitter]
+
+
-## Table of Contents
-
-
-
+[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
- [The Problem](#the-problem)
- [This Solution](#this-solution)
- [Installation](#installation)
+- [Setup](#setup)
+ - [Auto-cleanup](#auto-cleanup)
- [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
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][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.
+
+```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
+
+### Auto-cleanup
+
+In Vitest (via the `svelteTesting` plugin) and Jest (via the `beforeEach` and `afterEach` globals),
+this library will automatically setup and cleanup the test environment before and after each test.
+
+To do your own cleanup, or if you're using another framework, call the `setup` and `cleanup` functions yourself:
-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).
+```js
+import { cleanup, render, setup } from '@testing-library/svelte'
+
+// before
+setup()
+
+// test
+render(/* ... */)
+
+// after
+cleanup()
+```
+
+To disable auto-cleanup in Vitest, set the `autoCleanup` option of the plugin to false:
+
+```js
+svelteTesting({ autoCleanup: false })
+```
+
+To disable auto-cleanup in Jest and other frameworks with global test hooks,
+set the `STL_SKIP_AUTO_CLEANUP` environment variable:
+
+```shell
+STL_SKIP_AUTO_CLEANUP=1 jest
+```
## 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
@@ -103,6 +198,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
@@ -111,34 +208,46 @@ 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]):
+
+
+
+{/if}
diff --git a/examples/basic/basic.test.js b/examples/basic/basic.test.js
new file mode 100644
index 0000000..6ea6099
--- /dev/null
+++ b/examples/basic/basic.test.js
@@ -0,0 +1,26 @@
+import { render, screen } from '@testing-library/svelte'
+import { userEvent } from '@testing-library/user-event'
+import { expect, test } from 'vitest'
+
+import Subject from './basic.svelte'
+
+test('no initial greeting', () => {
+ render(Subject, { name: 'World' })
+
+ const button = screen.getByRole('button', { name: 'Greet' })
+ const greeting = screen.queryByText(/hello/iu)
+
+ expect(button).toBeInTheDocument()
+ expect(greeting).not.toBeInTheDocument()
+})
+
+test('greeting appears on click', async () => {
+ const user = userEvent.setup()
+ render(Subject, { name: 'World' })
+
+ const button = screen.getByRole('button')
+ await user.click(button)
+ const greeting = screen.getByText(/hello world/iu)
+
+ expect(greeting).toBeInTheDocument()
+})
diff --git a/examples/basic/readme.md b/examples/basic/readme.md
new file mode 100644
index 0000000..6d98485
--- /dev/null
+++ b/examples/basic/readme.md
@@ -0,0 +1,68 @@
+# Basic
+
+This basic example demonstrates how to:
+
+- Pass props to your Svelte component using [render()]
+- [Query][] the structure of your component's DOM elements using screen
+- Interact with your component using [@testing-library/user-event][]
+- Make assertions using expect, using matchers from
+ [@testing-library/jest-dom][]
+
+[query]: https://testing-library.com/docs/queries/about
+[render()]: https://testing-library.com/docs/svelte-testing-library/api#render
+[@testing-library/user-event]: https://testing-library.com/docs/user-event/intro
+[@testing-library/jest-dom]: https://github.com/testing-library/jest-dom
+
+## Table of contents
+
+- [`basic.svelte`](#basicsvelte)
+- [`basic.test.js`](#basictestjs)
+
+## `basic.svelte`
+
+```svelte file=./basic.svelte
+
+
+
+
+{#if showGreeting}
+
Hello {name}
+{/if}
+```
+
+## `basic.test.js`
+
+```js file=./basic.test.js
+import { render, screen } from '@testing-library/svelte'
+import { userEvent } from '@testing-library/user-event'
+import { expect, test } from 'vitest'
+
+import Subject from './basic.svelte'
+
+test('no initial greeting', () => {
+ render(Subject, { name: 'World' })
+
+ const button = screen.getByRole('button', { name: 'Greet' })
+ const greeting = screen.queryByText(/hello/iu)
+
+ expect(button).toBeInTheDocument()
+ expect(greeting).not.toBeInTheDocument()
+})
+
+test('greeting appears on click', async () => {
+ const user = userEvent.setup()
+ render(Subject, { name: 'World' })
+
+ const button = screen.getByRole('button')
+ await user.click(button)
+ const greeting = screen.getByText(/hello world/iu)
+
+ expect(greeting).toBeInTheDocument()
+})
+```
diff --git a/examples/binds/bind.svelte b/examples/binds/bind.svelte
new file mode 100644
index 0000000..62fd858
--- /dev/null
+++ b/examples/binds/bind.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/examples/binds/bind.test.js b/examples/binds/bind.test.js
new file mode 100644
index 0000000..6d00665
--- /dev/null
+++ b/examples/binds/bind.test.js
@@ -0,0 +1,24 @@
+import { render, screen } from '@testing-library/svelte'
+import { userEvent } from '@testing-library/user-event'
+import { expect, test } from 'vitest'
+
+import Subject from './bind.svelte'
+
+test('value binding', async () => {
+ const user = userEvent.setup()
+ let value = ''
+
+ render(Subject, {
+ get value() {
+ return value
+ },
+ set value(nextValue) {
+ value = nextValue
+ },
+ })
+
+ const input = screen.getByRole('textbox')
+ await user.type(input, 'hello world')
+
+ expect(value).toBe('hello world')
+})
diff --git a/examples/binds/no-bind.svelte b/examples/binds/no-bind.svelte
new file mode 100644
index 0000000..3e4079a
--- /dev/null
+++ b/examples/binds/no-bind.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/examples/binds/readme.md b/examples/binds/readme.md
new file mode 100644
index 0000000..fb7c745
--- /dev/null
+++ b/examples/binds/readme.md
@@ -0,0 +1,82 @@
+# Binds
+
+Two-way data binding using [bindable() props][] is difficult to test directly.
+It's usually easier to structure your code so that you can test user-facing
+results, leaving the binding as an implementation detail.
+
+However, if two-way binding is an important developer-facing API of your
+component, you can use setters to test your binding.
+
+[bindable() props]: https://svelte.dev/docs/svelte/$bindable
+
+## Table of contents
+
+- [`bind.svelte`](#bindsvelte)
+- [`bind.test.js`](#bindtestjs)
+- [Consider avoiding binding](#consider-avoiding-binding)
+
+## `bind.svelte`
+
+```svelte file=./bind.svelte
+
+
+
+```
+
+## `bind.test.js`
+
+```js file=./bind.test.js
+import { render, screen } from '@testing-library/svelte'
+import { userEvent } from '@testing-library/user-event'
+import { expect, test } from 'vitest'
+
+import Subject from './bind.svelte'
+
+test('value binding', async () => {
+ const user = userEvent.setup()
+ let value = ''
+
+ render(Subject, {
+ get value() {
+ return value
+ },
+ set value(nextValue) {
+ value = nextValue
+ },
+ })
+
+ const input = screen.getByRole('textbox')
+ await user.type(input, 'hello world')
+
+ expect(value).toBe('hello world')
+})
+```
+
+## Consider avoiding binding
+
+Before embarking on writing tests for bindable props, consider avoiding
+`bindable()` entirely. Two-way data binding can make your data flows and state
+changes difficult to reason about and test effectively. Instead, you can use
+value props to pass data down and callback props to pass changes back up to the
+parent.
+
+> Well-written applications use bindings very sparingly — the vast majority of
+> data flow should be top-down --
+> [Rich Harris](https://github.com/sveltejs/svelte/issues/10768#issue-2181814844)
+
+For example, rather than using a `bindable()` prop, use a value prop to pass the
+value down and callback prop to send changes back up to the parent:
+
+```svelte file=./no-bind.svelte
+
+
+
+```
diff --git a/examples/contexts/context.svelte b/examples/contexts/context.svelte
new file mode 100644
index 0000000..c0fbb19
--- /dev/null
+++ b/examples/contexts/context.svelte
@@ -0,0 +1,14 @@
+
+
+
+ {#each messages.current as message (message.id)}
+
{message.text}
+
+ {/each}
+
diff --git a/examples/contexts/context.test.js b/examples/contexts/context.test.js
new file mode 100644
index 0000000..4a433d2
--- /dev/null
+++ b/examples/contexts/context.test.js
@@ -0,0 +1,24 @@
+import { render, screen } from '@testing-library/svelte'
+import { expect, test } from 'vitest'
+
+import Subject from './context.svelte'
+
+test('notifications with messages from context', async () => {
+ const messages = {
+ get current() {
+ return [
+ { id: 'abc', text: 'hello' },
+ { id: 'def', text: 'world' },
+ ]
+ },
+ }
+
+ render(Subject, {
+ context: new Map([['messages', messages]]),
+ props: { label: 'Notifications' },
+ })
+
+ const status = screen.getByRole('status', { name: 'Notifications' })
+
+ expect(status).toHaveTextContent('hello world')
+})
diff --git a/examples/contexts/readme.md b/examples/contexts/readme.md
new file mode 100644
index 0000000..15284a9
--- /dev/null
+++ b/examples/contexts/readme.md
@@ -0,0 +1,61 @@
+# Context
+
+If your component requires access to contexts, you can pass those contexts in
+when you render the component. When using extra [component options][] like
+`context`, be sure to place props under the `props` key.
+
+[component options]:
+ https://testing-library.com/docs/svelte-testing-library/api#component-options
+
+## Table of contents
+
+- [`context.svelte`](#contextsvelte)
+- [`context.test.js`](#contexttestjs)
+
+## `context.svelte`
+
+```svelte file=./context.svelte
+
+
+
+ {#each messages.current as message (message.id)}
+
diff --git a/examples/deprecated/deprecated-slot.test.js b/examples/deprecated/deprecated-slot.test.js
new file mode 100644
index 0000000..dfe73b8
--- /dev/null
+++ b/examples/deprecated/deprecated-slot.test.js
@@ -0,0 +1,13 @@
+import { render, screen, within } from '@testing-library/svelte'
+import { expect, test } from 'vitest'
+
+import SubjectTest from './deprecated-slot.test.svelte'
+
+test('heading with slot', () => {
+ render(SubjectTest)
+
+ const heading = screen.getByRole('heading')
+ const child = within(heading).getByTestId('child')
+
+ expect(child).toBeInTheDocument()
+})
diff --git a/examples/deprecated/deprecated-slot.test.svelte b/examples/deprecated/deprecated-slot.test.svelte
new file mode 100644
index 0000000..92b5f6b
--- /dev/null
+++ b/examples/deprecated/deprecated-slot.test.svelte
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/examples/deprecated/readme.md b/examples/deprecated/readme.md
new file mode 100644
index 0000000..ca1a8d1
--- /dev/null
+++ b/examples/deprecated/readme.md
@@ -0,0 +1,105 @@
+# Deprecated Svelte 3/4 features
+
+Several features from Svelte 3 and 4 have been deprecated in Svelte 5, but while
+you still have components using the old syntax, or if you haven't yet updated to
+Svelte 5, you can continue to use `@testing-library/svelte` to test your
+components.
+
+## Table of contents
+
+- [Events](#events)
+ - [`deprecated-event.svelte`](#deprecated-eventsvelte)
+ - [`deprecated-event.test.js`](#deprecated-eventtestjs)
+- [Slots](#slots)
+ - [`deprecated-slot.svelte`](#deprecated-slotsvelte)
+ - [`deprecated-slot.test.svelte`](#deprecated-slottestsvelte)
+ - [`deprecated-slot.test.js`](#deprecated-slottestjs)
+
+## Events
+
+The `on:event` syntax was deprecated in favor of callback props. However, if you
+have updated your Svelte runtime to version 5, you can use the `events`
+component option to continue to test events in older components.
+
+### `deprecated-event.svelte`
+
+```svelte file=./deprecated-event.svelte
+
+```
+
+### `deprecated-event.test.js`
+
+> \[!WARNING]
+>
+> If you are still using Svelte version 3 or 4, `render` will **not** have an
+> `events` option. Instead, use `component.$on` to attach an event listener.
+>
+> ```js
+> const onClick = vi.fn()
+>
+> const { component } = render(Subject)
+> component.$on('click', onClick)
+> ```
+
+```js file=./deprecated-event.test.js
+import { render, screen } from '@testing-library/svelte'
+import userEvent from '@testing-library/user-event'
+import { expect, test, vi } from 'vitest'
+
+import Subject from './deprecated-event.svelte'
+
+test('on:click event', async () => {
+ const user = userEvent.setup()
+ const onClick = vi.fn()
+
+ render(Subject, { events: { click: onClick } })
+
+ const button = screen.getByRole('button')
+ await user.click(button)
+
+ expect(onClick).toHaveBeenCalledOnce()
+})
+```
+
+## Slots
+
+The slots feature was deprecated in favor of snippets. If you have components
+that still use slots, you can create a wrapper component to test them.
+
+### `deprecated-slot.svelte`
+
+```svelte file=./deprecated-slot.svelte
+
+
+
+```
+
+### `deprecated-slot.test.svelte`
+
+```svelte file=./deprecated-slot.test.svelte
+
+
+
+
+
+```
+
+### `deprecated-slot.test.js`
+
+```js file=deprecated-slot.test.js
+import { render, screen, within } from '@testing-library/svelte'
+import { expect, test } from 'vitest'
+
+import SubjectTest from './deprecated-slot.test.svelte'
+
+test('heading with slot', () => {
+ render(SubjectTest)
+
+ const heading = screen.getByRole('heading')
+ const child = within(heading).getByTestId('child')
+
+ expect(child).toBeInTheDocument()
+})
+```
diff --git a/examples/events/event.svelte b/examples/events/event.svelte
new file mode 100644
index 0000000..3562da1
--- /dev/null
+++ b/examples/events/event.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/examples/events/event.test.js b/examples/events/event.test.js
new file mode 100644
index 0000000..c37902b
--- /dev/null
+++ b/examples/events/event.test.js
@@ -0,0 +1,17 @@
+import { render, screen } from '@testing-library/svelte'
+import { userEvent } from '@testing-library/user-event'
+import { expect, test, vi } from 'vitest'
+
+import Subject from './event.svelte'
+
+test('onclick event', async () => {
+ const user = userEvent.setup()
+ const onclick = vi.fn()
+
+ render(Subject, { onclick })
+
+ const button = screen.getByRole('button')
+ await user.click(button)
+
+ expect(onclick).toHaveBeenCalledOnce()
+})
diff --git a/examples/events/readme.md b/examples/events/readme.md
new file mode 100644
index 0000000..7af235a
--- /dev/null
+++ b/examples/events/readme.md
@@ -0,0 +1,43 @@
+# Events
+
+Events can be tested using spy functions. If you're using Vitest you can use
+[vi.fn()][] to create a spy.
+
+[vi.fn()]: https://vitest.dev/api/vi.html#vi-fn
+
+## Table of contents
+
+- [`event.svelte`](#eventsvelte)
+- [`event.test.js`](#eventtestjs)
+
+## `event.svelte`
+
+```svelte file=./event.svelte
+
+
+
+```
+
+## `event.test.js`
+
+```js file=./event.test.js
+import { render, screen } from '@testing-library/svelte'
+import { userEvent } from '@testing-library/user-event'
+import { expect, test, vi } from 'vitest'
+
+import Subject from './event.svelte'
+
+test('onclick event', async () => {
+ const user = userEvent.setup()
+ const onclick = vi.fn()
+
+ render(Subject, { onclick })
+
+ const button = screen.getByRole('button')
+ await user.click(button)
+
+ expect(onclick).toHaveBeenCalledOnce()
+})
+```
diff --git a/examples/readme.md b/examples/readme.md
new file mode 100644
index 0000000..e8c7e26
--- /dev/null
+++ b/examples/readme.md
@@ -0,0 +1,8 @@
+# `@testing-library/svelte` examples
+
+- [Basic](./basic)
+- [Events](./events)
+- [Snippets](./snippets)
+- [Contexts](./contexts)
+- [Binds](./binds)
+- [Deprecated Svelte 3 and 4 features](./deprecated)
diff --git a/examples/snippets/basic-snippet.svelte b/examples/snippets/basic-snippet.svelte
new file mode 100644
index 0000000..55e1f71
--- /dev/null
+++ b/examples/snippets/basic-snippet.svelte
@@ -0,0 +1,7 @@
+
+
+
+ {@render children?.()}
+
diff --git a/examples/snippets/basic-snippet.test.js b/examples/snippets/basic-snippet.test.js
new file mode 100644
index 0000000..11cf95c
--- /dev/null
+++ b/examples/snippets/basic-snippet.test.js
@@ -0,0 +1,13 @@
+import { render, screen, within } from '@testing-library/svelte'
+import { expect, test } from 'vitest'
+
+import SubjectTest from './basic-snippet.test.svelte'
+
+test('basic snippet', () => {
+ render(SubjectTest)
+
+ const heading = screen.getByRole('heading')
+ const child = within(heading).getByTestId('child')
+
+ expect(child).toBeInTheDocument()
+})
diff --git a/examples/snippets/basic-snippet.test.svelte b/examples/snippets/basic-snippet.test.svelte
new file mode 100644
index 0000000..e96cac7
--- /dev/null
+++ b/examples/snippets/basic-snippet.test.svelte
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/examples/snippets/complex-snippet.svelte b/examples/snippets/complex-snippet.svelte
new file mode 100644
index 0000000..81e8a70
--- /dev/null
+++ b/examples/snippets/complex-snippet.svelte
@@ -0,0 +1,9 @@
+
+
+
+ {@render message?.(greeting)}
+
diff --git a/examples/snippets/complex-snippet.test.js b/examples/snippets/complex-snippet.test.js
new file mode 100644
index 0000000..55885ef
--- /dev/null
+++ b/examples/snippets/complex-snippet.test.js
@@ -0,0 +1,18 @@
+import { render, screen } from '@testing-library/svelte'
+import { createRawSnippet } from 'svelte'
+import { expect, test } from 'vitest'
+
+import Subject from './complex-snippet.svelte'
+
+test('renders greeting in message snippet', () => {
+ render(Subject, {
+ name: 'Alice',
+ message: createRawSnippet((greeting) => ({
+ render: () => `${greeting()}`,
+ })),
+ })
+
+ const message = screen.getByTestId('message')
+
+ expect(message).toHaveTextContent('Hello, Alice!')
+})
diff --git a/examples/snippets/readme.md b/examples/snippets/readme.md
new file mode 100644
index 0000000..30a70d6
--- /dev/null
+++ b/examples/snippets/readme.md
@@ -0,0 +1,108 @@
+# Snippets
+
+Snippets are difficult to test directly. It's usually easier to structure your
+code so that you can test the user-facing results, leaving any snippets as an
+implementation detail. However, if snippets are an important developer-facing
+API of your component, there are several strategies you can use.
+
+## Table of contents
+
+- [Basic snippets example](#basic-snippets-example)
+ - [`basic-snippet.svelte`](#basic-snippetsvelte)
+ - [`basic-snippet.test.svelte`](#basic-snippettestsvelte)
+ - [`basic-snippet.test.js`](#basic-snippettestjs)
+- [Using `createRawSnippet`](#using-createrawsnippet)
+ - [`complex-snippet.svelte`](#complex-snippetsvelte)
+ - [`complex-snippet.test.js`](#complex-snippettestjs)
+
+## Basic snippets example
+
+For simple snippets, you can use a wrapper component and "dummy" children to
+test them. Setting `data-testid` attributes can be helpful when testing slots in
+this manner.
+
+### `basic-snippet.svelte`
+
+```svelte file=./basic-snippet.svelte
+
+
+
+ {@render children?.()}
+
+```
+
+### `basic-snippet.test.svelte`
+
+```svelte file=./basic-snippet.test.svelte
+
+
+
+
+
+```
+
+### `basic-snippet.test.js`
+
+```js file=./basic-snippet.test.js
+import { render, screen, within } from '@testing-library/svelte'
+import { expect, test } from 'vitest'
+
+import SubjectTest from './basic-snippet.test.svelte'
+
+test('basic snippet', () => {
+ render(SubjectTest)
+
+ const heading = screen.getByRole('heading')
+ const child = within(heading).getByTestId('child')
+
+ expect(child).toBeInTheDocument()
+})
+```
+
+## Using `createRawSnippet`
+
+For more complex snippets, e.g. where you want to check arguments, you can use
+Svelte's [createRawSnippet][] API.
+
+[createRawSnippet]: https://svelte.dev/docs/svelte/svelte#createRawSnippet
+
+### `complex-snippet.svelte`
+
+```svelte file=./complex-snippet.svelte
+
+
+