# Tutorial 4: Component Composition Welcome to the **fourth tutorial** for TestDriveUi building on the first three and introduces **component composition**. You’ll learn how to make multiple Lit components **interact** while keeping the development **test-driven** and agent-friendly. --- # 🧩 Tutorial 4 — Composing Components ### “Hello Dashboard” — combining reusable components --- ## 🎯 Goal We’ll create a small **dashboard** component called `` that: 1. Renders multiple `` components. 2. Tracks how many greetings are currently visible. 3. Updates a counter when a new greeter is added. 4. Uses **TDD** for structure and behavior. In short: > “A dashboard that shows several personalized greetings and a live counter.” --- ## 🧪 1. Step 1 — Write the failing test first Create `src/components/hello-dashboard/hello-dashboard.test.js`: ```javascript import "../hello-world/hello-world.js"; import "./hello-dashboard.js"; describe("", () => { it("renders a header and a counter", () => { const el = document.createElement("hello-dashboard"); document.body.appendChild(el); const header = el.shadowRoot.querySelector("h2"); const counter = el.shadowRoot.querySelector(".counter"); expect(header.textContent).to.include("Hello Dashboard"); expect(counter.textContent).to.match(/Total greetings:/); }); it("renders at least one component", () => { const el = document.createElement("hello-dashboard"); document.body.appendChild(el); const greeters = el.shadowRoot.querySelectorAll("hello-world"); expect(greeters.length).to.be.greaterThan(0); }); it("adds a new greeter when the Add button is clicked", async () => { const el = document.createElement("hello-dashboard"); document.body.appendChild(el); const addButton = el.shadowRoot.querySelector("button"); addButton.click(); await el.updateComplete; const greeters = el.shadowRoot.querySelectorAll("hello-world"); expect(greeters.length).to.equal(2); const counter = el.shadowRoot.querySelector(".counter").textContent; expect(counter).to.include("2"); }); }); ``` Run it: ```bash npm test ``` All tests fail (expected). --- ## ⚙️ 2. Step 2 — Implement `` Create a new folder: ``` src/components/hello-dashboard/ ``` and add this file: `src/components/hello-dashboard/hello-dashboard.js` ```javascript import { LitElement, html, css } from "lit"; import "../hello-world/hello-world.js"; export class HelloDashboard extends LitElement { static styles = css` .container { font-family: system-ui, sans-serif; padding: 1rem; text-align: center; } .counter { margin: 0.5rem 0 1rem 0; font-weight: bold; color: #007acc; } button { background: #007acc; color: white; border: none; border-radius: 6px; padding: 0.4rem 1rem; cursor: pointer; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } button:hover { background: #005fa3; } `; constructor() { super(); this.greeters = ["World"]; } _addGreeter() { const newName = `User${this.greeters.length + 1}`; this.greeters = [...this.greeters, newName]; } render() { return html`

Hello Dashboard

Total greetings: ${this.greeters.length}
${this.greeters.map( (name) => html`` )}
`; } } customElements.define("hello-dashboard", HelloDashboard); ``` Run tests again: ```bash npm test ``` ✅ All should now pass. --- ## 🌐 3. Step 3 — Try it live Add this to `src/index.html`: ```html ``` Then start the dev server: ```bash npm run dev ``` Open [http://localhost:5173](http://localhost:5173) You’ll see: ``` Hello Dashboard Total greetings: 1 Hello, World! [Add Greeting] ``` Click **Add Greeting** — new greeters appear, and the counter updates automatically. --- ## 🧠 4. Step 4 — Extend your test coverage (optional) Add to `hello-dashboard.test.js`: ```javascript it("propagates custom names correctly", () => { const el = document.createElement("hello-dashboard"); el.greeters = ["Alpha", "Beta", "Gamma"]; document.body.appendChild(el); const greeters = el.shadowRoot.querySelectorAll("hello-world"); const names = Array.from(greeters).map((g) => g.getAttribute("name")); expect(names).to.deep.equal(["Alpha", "Beta", "Gamma"]); }); ``` --- ## 📚 5. Step 5 — Optional Storybook story `src/components/hello-dashboard/hello-dashboard.stories.js`: ```javascript import "./hello-dashboard.js"; export default { title: "UI/Hello Dashboard" }; export const Default = () => ``; ``` --- ## 🧩 6. Lessons learned | Concept | What you practiced | | --------------------- | ------------------------------------------- | | **Composition** | One Lit component rendering others | | **Reactive arrays** | Using property updates to trigger re-render | | **Dynamic templates** | Rendering lists with `.map()` | | **Event testing** | Simulating clicks and rechecking the DOM | | **Incremental TDD** | Adding small features one test at a time | --- ## ✅ 7. Summary You’ve now implemented: 1. A simple reactive component (`hello-world`) 2. User interaction (input updates) 3. Property-based rendering 4. Component composition (`hello-dashboard`) Your **TestDrive-UI** foundation now supports: * Isolated unit tests (`Mocha + jsdom`) * Composed component testing * Interactive browser previews (`Vite`) --- Continue to **Event communication** between components — e.g., the dashboard listening to events fired by each greeter when they change their name. xxx