Skip to content

docs(examples): add examples to repository #440

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
"name": "Michael Cousins",
"avatar_url": "https://avatars.githubusercontent.com/u/2963448?v=4",
"profile": "https://michael.cousins.io/",
"contributions": ["code"]
"contributions": ["code", "doc", "ideas", "maintenance", "test"]
}
],
"contributorsPerLine": 7,
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ jobs:
- { svelte: '5', node: '16' }
- { svelte: '5', node: '18' }
include:
# We only need to lint once, so do it on latest Node and Svelte
# 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' }
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ npm run all:legacy

### 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:
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@

<p>Simple and complete Svelte testing utilities that encourage good testing practices.</p>

[**Read The Docs**][stl-docs] | [Edit the docs][stl-docs-repo]
[**Read The Docs**][stl-docs] | [Edit the docs][stl-docs-repo] | [Examples](./examples)

<!-- prettier-ignore-start -->

[![Build Status][build-badge]][build]
[![Code Coverage][coverage-badge]][coverage]
[![version][version-badge]][package]
Expand All @@ -29,7 +30,9 @@
[![Watch on GitHub][github-watch-badge]][github-watch]
[![Star on GitHub][github-star-badge]][github-star]
[![Tweet][twitter-badge]][twitter]

<!-- prettier-ignore-end -->

</div>

<hr />
Expand Down Expand Up @@ -63,9 +66,6 @@

## Table of Contents

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [The Problem](#the-problem)
- [This Solution](#this-solution)
- [Installation](#installation)
Expand All @@ -78,8 +78,6 @@
- [❓ Questions](#-questions)
- [Contributors](#contributors)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## The Problem

You want to write maintainable tests for your [Svelte][svelte] components.
Expand Down Expand Up @@ -217,8 +215,11 @@ instead of filing an issue on GitHub.
Thanks goes to these people ([emoji key][emojis]):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->

<!-- prettier-ignore-start -->

<!-- markdownlint-disable -->

<table>
<tbody>
<tr>
Expand All @@ -240,12 +241,13 @@ Thanks goes to these people ([emoji key][emojis]):
<td align="center" valign="top" width="14.28%"><a href="https://techblog.babyl.ca/"><img src="https://avatars.githubusercontent.com/u/19954?v=4?s=100" width="100px;" alt="Yanick Champoux"/><br /><sub><b>Yanick Champoux</b></sub></a><br /><a href="https://github.com/testing-library/svelte-testing-library/commits?author=yanick" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://michael.cousins.io/"><img src="https://avatars.githubusercontent.com/u/2963448?v=4?s=100" width="100px;" alt="Michael Cousins"/><br /><sub><b>Michael Cousins</b></sub></a><br /><a href="https://github.com/testing-library/svelte-testing-library/commits?author=mcous" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://michael.cousins.io/"><img src="https://avatars.githubusercontent.com/u/2963448?v=4?s=100" width="100px;" alt="Michael Cousins"/><br /><sub><b>Michael Cousins</b></sub></a><br /><a href="https://github.com/testing-library/svelte-testing-library/commits?author=mcous" title="Code">💻</a> <a href="https://github.com/testing-library/svelte-testing-library/commits?author=mcous" title="Documentation">📖</a> <a href="#ideas-mcous" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-mcous" title="Maintenance">🚧</a> <a href="https://github.com/testing-library/svelte-testing-library/commits?author=mcous" title="Tests">⚠️</a></td>
</tr>
</tbody>
</table>

<!-- markdownlint-restore -->

<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->
Expand Down
5 changes: 4 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ export default tseslint.config(
files: ['**/*.svelte'],
rules: {
'svelte/no-unused-svelte-ignore': 'off',
'unicorn/filename-case': ['error', { case: 'pascalCase' }],
'unicorn/filename-case': [
'error',
{ cases: { kebabCase: true, pascalCase: true } },
],
'unicorn/no-useless-undefined': 'off',
},
},
Expand Down
13 changes: 13 additions & 0 deletions examples/basic/basic.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script>
let { name } = $props()

let showGreeting = $state(false)

const onclick = () => (showGreeting = true)
</script>

<button {onclick}>Greet</button>

{#if showGreeting}
<p>Hello {name}</p>
{/if}
26 changes: 26 additions & 0 deletions examples/basic/basic.test.js
Original file line number Diff line number Diff line change
@@ -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()
})
68 changes: 68 additions & 0 deletions examples/basic/readme.md
Original file line number Diff line number Diff line change
@@ -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
<script>
let { name } = $props()

let showGreeting = $state(false)

const onclick = () => (showGreeting = true)
</script>

<button {onclick}>Greet</button>

{#if showGreeting}
<p>Hello {name}</p>
{/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()
})
```
5 changes: 5 additions & 0 deletions examples/binds/bind.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
let { value = $bindable('') } = $props()
</script>

<input type="text" bind:value />
24 changes: 24 additions & 0 deletions examples/binds/bind.test.js
Original file line number Diff line number Diff line change
@@ -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')
})
9 changes: 9 additions & 0 deletions examples/binds/no-bind.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
let { value, onInput } = $props()

const oninput = (event) => {
onInput(event.target.value)
}
</script>

<input type="text" {value} {oninput} />
82 changes: 82 additions & 0 deletions examples/binds/readme.md
Original file line number Diff line number Diff line change
@@ -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
<script>
let { value = $bindable('') } = $props()
</script>

<input type="text" bind:value />
```

## `bind.test.js`

```svelte file=./bind.test.js
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```svelte file=./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 --
> <cite>[Rich Harris](https://github.com/sveltejs/svelte/issues/10768#issue-2181814844)</cite>

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
<script>
let { value, onInput } = $props()

const oninput = (event) => {
onInput(event.target.value)
}
</script>

<input type="text" {value} {oninput} />
```
14 changes: 14 additions & 0 deletions examples/contexts/context.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script>
import { getContext } from 'svelte'

let { label } = $props()

const messages = getContext('messages')
</script>

<div role="status" aria-label={label}>
{#each messages.current as message (message.id)}
<p>{message.text}</p>
<hr />
{/each}
</div>
24 changes: 24 additions & 0 deletions examples/contexts/context.test.js
Original file line number Diff line number Diff line change
@@ -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')
})
Loading