diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index 39f9d9e..0000000
--- a/.eslintignore
+++ /dev/null
@@ -1,6 +0,0 @@
-scripts/*
-.eslintignore
-.prettierignore
-.github/workflows/*
-*.md
-types
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
deleted file mode 100644
index 77f8b7c..0000000
--- a/.eslintrc.cjs
+++ /dev/null
@@ -1,45 +0,0 @@
-module.exports = {
- root: true,
- env: {
- browser: true,
- es6: true,
- },
- extends: ['standard', 'plugin:svelte/recommended', 'prettier'],
- plugins: ['svelte', 'simple-import-sort'],
- rules: {
- 'simple-import-sort/imports': 'error',
- 'simple-import-sort/exports': 'error',
- },
- overrides: [
- {
- files: ['*.svelte'],
- parser: 'svelte-eslint-parser',
- parserOptions: {
- parser: '@typescript-eslint/parser',
- },
- rules: {
- 'no-undef-init': 'off',
- 'prefer-const': 'off',
- 'svelte/no-unused-svelte-ignore': 'off',
- },
- },
- {
- files: ['*.ts'],
- parser: '@typescript-eslint/parser',
- parserOptions: {
- project: './tsconfig.json',
- },
- extends: [
- 'plugin:@typescript-eslint/strict-type-checked',
- 'plugin:@typescript-eslint/stylistic-type-checked',
- 'prettier',
- ],
- },
- ],
- parserOptions: {
- ecmaVersion: 2022,
- sourceType: 'module',
- },
- globals: { afterEach: 'readonly', $state: 'readonly', $props: 'readonly' },
- ignorePatterns: ['!/.*'],
-}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index c9eb8a7..434a8bb 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -23,15 +23,6 @@ updates:
development:
dependency-type: 'development'
- # TODO(mcous, 2024-04-30): update to ESLint v9 + flat config
- ignore:
- - dependency-name: 'eslint'
- versions: ['>=9']
- - dependency-name: 'eslint-plugin-n'
- versions: ['>=17']
- - dependency-name: 'eslint-plugin-promise'
- versions: ['>=7']
-
# Update GitHub Actions dependencies
- package-ecosystem: 'github-actions'
directory: '/'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1251be8..136111f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -17,7 +17,7 @@ jobs:
main:
# ignore all-contributors PRs
if: ${{ !contains(github.head_ref, 'all-contributors') }}
- name: Node ${{ matrix.node }}, Svelte ${{ matrix.svelte }}, ${{ matrix.check }}
+ name: Svelte ${{ matrix.svelte }}, Node ${{ matrix.node }}, ${{ matrix.check }}
runs-on: ubuntu-latest
# enable OIDC for codecov uploads
@@ -27,20 +27,23 @@ jobs:
strategy:
fail-fast: false
matrix:
- node: ['16', '18', '20']
- svelte: ['3', '4']
+ node: ['16', '18', '20', '22']
+ svelte: ['3', '4', '5']
check: ['test:vitest:jsdom', 'test:vitest:happy-dom', 'test:jest']
+ exclude:
+ # Don't run Svelte 3 on Node versions greater than 20
+ - { svelte: '3', node: '22' }
+ # Only run Svelte 5 on Node versions greater than or equal to 20
+ - { svelte: '5', node: '16' }
+ - { svelte: '5', node: '18' }
include:
- # We only need to lint once, so do it on latest Node and Svelte
- - { node: '20', svelte: '4', check: 'lint' }
- # Run type checks in latest node
- - { node: '20', svelte: '3', check: 'types:legacy' }
- - { node: '20', svelte: '4', check: 'types:legacy' }
- - { node: '20', svelte: '5', check: 'types' }
- # Only run Svelte 5 checks on latest Node
- - { node: '20', svelte: '5', check: 'test:vitest:jsdom' }
- - { node: '20', svelte: '5', check: 'test:vitest:happy-dom' }
- - { node: '20', svelte: '5', check: 'test:jest' }
+ # Only lint and test examples on latest Node and Svelte
+ - { svelte: '5', node: '22', check: 'lint' }
+ - { svelte: '5', node: '22', check: 'test:examples' }
+ # Run type checks in latest applicable Node
+ - { svelte: '3', node: '20', check: 'types:legacy' }
+ - { svelte: '4', node: '22', check: 'types:legacy' }
+ - { svelte: '5', node: '22', check: 'types' }
steps:
- name: ⬇️ Checkout repo
@@ -59,7 +62,7 @@ jobs:
- name: ⬆️ Upload coverage report
if: ${{ startsWith(matrix.check, 'test:') }}
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
with:
use_oidc: true
fail_ci_if_error: true
@@ -73,10 +76,10 @@ jobs:
- name: ⎔ Setup node
uses: actions/setup-node@v4
with:
- node-version: 20
+ node-version: 22
- name: 📥 Download deps
- run: npm install --no-package-lock
+ run: npm install
- name: 🏗️ Build types
run: npm run build
@@ -100,7 +103,7 @@ jobs:
- name: ⎔ Setup node
uses: actions/setup-node@v4
with:
- node-version: 20
+ node-version: 22
- name: 📥 Downloads types build
uses: actions/download-artifact@v4
diff --git a/.npmrc b/.npmrc
index 43c97e7..86916fa 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1 +1,2 @@
package-lock=false
+engine-strict=true
diff --git a/.prettierignore b/.prettierignore
index 5e50c20..c750ecf 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,4 +1 @@
-scripts/*
-.eslintignore
-.prettierignore
.all-contributorsrc
diff --git a/.prettierrc.yaml b/.prettierrc.yaml
deleted file mode 100644
index 0a2ace3..0000000
--- a/.prettierrc.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
-semi: false
-singleQuote: true
-trailingComma: es5
-plugins:
- - prettier-plugin-svelte
-overrides:
- - files: '*.svelte'
- options:
- parser: svelte
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4c40705..328bcda 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -28,11 +28,10 @@ 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.
+After cloning the repository, use the `setup` script to install dependencies and run all checks:
```shell
-npm install # or `pnpm install`, or `yarn install`, etc.
-npm run validate
+npm run setup
```
### Lint and format
@@ -40,13 +39,13 @@ npm run validate
Run auto-formatting to ensure any changes adhere to the code style of the repository:
```shell
-npm run format:delta
+npm run format
```
To run lint and format checks without making any changes:
```shell
-npm run lint:delta
+npm run lint
```
### Test
@@ -63,24 +62,25 @@ npm run test:watch
Use the provided script to set up your environment for different versions of Svelte:
```shell
-# install Svelte 5
+# Svelte 5
npm run install:5
+npm run all
-# install Svelte 4
+# Svelte 4
npm run install:4
+npm run all:legacy
-# install Svelte 3
+# Svelte 3
npm run install:3
+npm run all:legacy
```
-Not all checks will pass on `svelte<5`. Reference the CI workflows to see which checks are expected to pass on older versions.
-
### Docs
-Use the `toc` script to ensure the README's table of contents is up to date:
+Use the `docs` script to ensure the README's table of contents is up to date:
```shell
-npm run toc
+npm run docs
```
Use `contributors:add` to add a contributor to the README:
diff --git a/README.md b/README.md
index bf45331..6a5f1d9 100644
--- a/README.md
+++ b/README.md
@@ -12,9 +12,10 @@
Simple and complete Svelte testing utilities that encourage good testing practices.
-[**Read The Docs**][stl-docs] | [Edit the docs][stl-docs-repo]
+[**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]
@@ -29,7 +30,9 @@
[![Watch on GitHub][github-watch-badge]][github-watch]
[![Star on GitHub][github-star-badge]][github-star]
[![Tweet][twitter-badge]][twitter]
+
+
@@ -63,13 +66,11 @@
## 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)
@@ -77,8 +78,6 @@
- [❓ Questions](#-questions)
- [Contributors](#contributors)
-
-
## The Problem
You want to write maintainable tests for your [Svelte][svelte] components.
@@ -140,6 +139,39 @@ 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:
+
+```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**][stl-docs] over at the Testing Library website.
@@ -183,8 +215,11 @@ instead of filing an issue on GitHub.
Thanks goes to these people ([emoji key][emojis]):
+
+
+
@@ -212,6 +247,7 @@ 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
+
+
+