# 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). You’ll 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 `` 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(" (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`
Hello, ${this.name}!
`; } _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 ``` Run: ```bash npm run dev ``` In your browser: * You’ll 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. That’s the power of Lit’s **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 = () => ``; ``` 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 You’ve 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