|
| 1 | +# Binds |
| 2 | + |
| 3 | +Two-way data binding using [bindable() props][] is difficult to test directly. |
| 4 | +It's usually easier to structure your code so that you can test user-facing |
| 5 | +results, leaving the binding as an implementation detail. |
| 6 | + |
| 7 | +However, if two-way binding is an important developer-facing API of your |
| 8 | +component, you can use setters to test your binding. |
| 9 | + |
| 10 | +[bindable() props]: https://svelte.dev/docs/svelte/$bindable |
| 11 | + |
| 12 | +## Table of contents |
| 13 | + |
| 14 | +- [`bind.svelte`](#bindsvelte) |
| 15 | +- [`bind.test.js`](#bindtestjs) |
| 16 | +- [Consider avoiding binding](#consider-avoiding-binding) |
| 17 | + |
| 18 | +## `bind.svelte` |
| 19 | + |
| 20 | +```svelte file=./bind.svelte |
| 21 | +<script> |
| 22 | + let { value = $bindable('') } = $props() |
| 23 | +</script> |
| 24 | +
|
| 25 | +<input type="text" bind:value /> |
| 26 | +``` |
| 27 | + |
| 28 | +## `bind.test.js` |
| 29 | + |
| 30 | +```svelte file=./bind.test.js |
| 31 | +import { render, screen } from '@testing-library/svelte' |
| 32 | +import { userEvent } from '@testing-library/user-event' |
| 33 | +import { expect, test } from 'vitest' |
| 34 | + |
| 35 | +import Subject from './bind.svelte' |
| 36 | + |
| 37 | +test('value binding', async () => { |
| 38 | + const user = userEvent.setup() |
| 39 | + let value = '' |
| 40 | + |
| 41 | + render(Subject, { |
| 42 | + get value() { |
| 43 | + return value |
| 44 | + }, |
| 45 | + set value(nextValue) { |
| 46 | + value = nextValue |
| 47 | + }, |
| 48 | + }) |
| 49 | + |
| 50 | + const input = screen.getByRole('textbox') |
| 51 | + await user.type(input, 'hello world') |
| 52 | + |
| 53 | + expect(value).toBe('hello world') |
| 54 | +}) |
| 55 | +``` |
| 56 | + |
| 57 | +## Consider avoiding binding |
| 58 | + |
| 59 | +Before embarking on writing tests for bindable props, consider avoiding |
| 60 | +`bindable()` entirely. Two-way data binding can make your data flows and state |
| 61 | +changes difficult to reason about and test effectively. Instead, you can use |
| 62 | +value props to pass data down and callback props to pass changes back up to the |
| 63 | +parent. |
| 64 | + |
| 65 | +> Well-written applications use bindings very sparingly — the vast majority of |
| 66 | +> data flow should be top-down -- |
| 67 | +> <cite>[Rich Harris](https://github.com/sveltejs/svelte/issues/10768#issue-2181814844)</cite> |
| 68 | +
|
| 69 | +For example, rather than using a `bindable()` prop, use a value prop to pass the |
| 70 | +value down and callback prop to send changes back up to the parent: |
| 71 | + |
| 72 | +```svelte file=./no-bind.svelte |
| 73 | +<script> |
| 74 | + let { value, onInput } = $props() |
| 75 | +
|
| 76 | + const oninput = (event) => { |
| 77 | + onInput(event.target.value) |
| 78 | + } |
| 79 | +</script> |
| 80 | +
|
| 81 | +<input type="text" {value} {oninput} /> |
| 82 | +``` |
0 commit comments