Files
testdrive-jsui/tutorials/Tutorial 3 Bidirectional Data Binding.md

221 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tutorial 3: Bi-Directional Data Binding
This **third tutorial** builds directly on your `hello-world` component and introduces **two-way interaction** (bi-directional data binding).
Youll learn how to let the user type into an input field and see the greeting update live — while continuing to follow a **TDD-first** approach.
---
## 🧭 1. Goal
> The `<hello-world>` component should display a greeting and an input box.
> Typing into the input should update the greeting **in real time**.
> The `name` property should still be readable/writable programmatically.
---
## 🧪 2. Step 1 — Write the failing test first
Create a new test file:
`src/components/hello-world/hello-world.input.test.js`
```javascript
import "./hello-world.js";
describe("<hello-world> (interactive input)", () => {
it("renders an input element and shows default greeting", () => {
const el = document.createElement("hello-world");
document.body.appendChild(el);
const input = el.shadowRoot.querySelector("input");
const greeting = el.shadowRoot.querySelector(".greeting");
expect(input).to.exist;
expect(greeting.textContent.trim()).to.equal("Hello, World!");
});
it("updates greeting when user types into input", async () => {
const el = document.createElement("hello-world");
document.body.appendChild(el);
const input = el.shadowRoot.querySelector("input");
input.value = "Agent";
input.dispatchEvent(new Event("input"));
await el.updateComplete;
const greeting = el.shadowRoot.querySelector(".greeting");
expect(greeting.textContent.trim()).to.equal("Hello, Agent!");
});
it("reflects property changes in input value", async () => {
const el = document.createElement("hello-world");
document.body.appendChild(el);
el.name = "Nova";
await el.updateComplete;
const input = el.shadowRoot.querySelector("input");
expect(input.value).to.equal("Nova");
});
});
```
Run tests:
```bash
npm test
```
They will all **fail** — as expected.
---
## 🧩 3. Step 2 — Implement the feature
Edit `src/components/hello-world/hello-world.js` and replace the render logic:
```javascript
import { LitElement, html, css } from "lit";
export class HelloWorld extends LitElement {
static properties = {
name: { type: String }
};
constructor() {
super();
this.name = "World";
}
static styles = css`
.container {
font-family: system-ui, sans-serif;
font-size: 1.5rem;
color: #007acc;
padding: 1rem;
text-align: center;
}
input {
font-size: 1rem;
margin-top: 1rem;
padding: 0.3rem 0.6rem;
border: 1px solid #ccc;
border-radius: 6px;
width: 60%;
text-align: center;
}
`;
render() {
return html`
<div class="container">
<div class="greeting">Hello, ${this.name}!</div>
<input
type="text"
.value=${this.name}
@input=${this._onInput}
aria-label="Name input"
/>
</div>
`;
}
_onInput(event) {
this.name = event.target.value;
}
}
customElements.define("hello-world", HelloWorld);
```
---
## 🧪 4. Step 3 — Run the tests again
```bash
npm test
```
✅ All three tests should now pass.
---
## ⚡ 5. Step 4 — Try it live
Update `src/index.html` to:
```html
<hello-world></hello-world>
```
Run:
```bash
npm run dev
```
In your browser:
* Youll see “Hello, World!”
* Type “Coulomb” in the input box.
* The greeting updates instantly: **Hello, Coulomb!**
---
## 🧠 6. Step 5 — Explore two-way binding manually
Open DevTools Console and run:
```javascript
document.querySelector("hello-world").name = "Bernd";
```
The input value updates automatically, and the greeting reflects the change too.
Thats the power of Lits **reactive updates** combined with the native DOM event loop.
---
## 📚 7. Step 6 — Optional Storybook story
`src/components/hello-world/hello-world.interactive.stories.js`
```javascript
import "./hello-world.js";
export default {
title: "UI/Hello World (Interactive)"
};
export const Interactive = () => `<hello-world></hello-world>`;
```
When you add Storybook later, this story will provide a live, interactive playground.
---
## 🔍 8. Key TDD lessons learned
| Concept | Explanation |
| -------------------- | ------------------------------------------------------------ |
| **Event testing** | Simulate user input with `dispatchEvent(new Event('input'))` |
| **Reactive updates** | Use `await el.updateComplete` to wait for re-render |
| **Two-way binding** | Property changes update DOM; DOM events update property |
| **Isolation** | jsdom tests confirm behavior without running a browser |
---
## ✅ 9. Summary
Youve now completed:
1. Static rendering (`Hello World!`)
2. Reactive property rendering (`Hello, ${name}!`)
3. Bi-directional interaction (live input → UI → property → UI)
You can now **test-drive** any UI component using the same methodology.
---
Continue to the **fourth tutorial** on **component composition** — i.e., building a small dashboard that uses multiple custom components together, still under test Control.
xxx