From 7ef23c2905102caf598b6511e29c987eb2e6c706 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 3 Nov 2025 21:46:15 +0100 Subject: [PATCH] chore: Fixed line endings i tutorials and provided Introduction --- INTRODUCTION.md | 199 ++++++ tutorials/Additional Tutorial Suggestions.md | 288 ++++---- tutorials/Tutorial 1 Hello World.md | 374 +++++----- tutorials/Tutorial 2 Reactive Properties.md | 432 ++++++------ .../Tutorial 3 Bidirectional Data Binding.md | 440 ++++++------ tutorials/Tutorial 4 Component Composition.md | 514 +++++++------- .../Tutorial 5 Component Communication.md | 502 +++++++------- tutorials/Tutorial 6 Shared State.md | 644 +++++++++--------- tutorials/Tutorial 7 Persistence Patterns.md | 476 ++++++------- .../Tutorial 8 Agent Driven Tdd Automation.md | 482 ++++++------- 10 files changed, 2275 insertions(+), 2076 deletions(-) create mode 100644 INTRODUCTION.md diff --git a/INTRODUCTION.md b/INTRODUCTION.md new file mode 100644 index 0000000..0df5447 --- /dev/null +++ b/INTRODUCTION.md @@ -0,0 +1,199 @@ +# ๐Ÿงช Introduction to Test-Driven-UI Development + +Hereโ€™s an introduction to the **TestDrive-UI development philosophy**. + +It explains *how to develop UI components the TestDrive-UI way*, why each tool is part of the stack, and links directly to their official projects. + +--- + +### โ€œDesign the test first โ€” let the interface emergeโ€ + +--- + +## ๐ŸŽฏ What is TestDrive-UI? + +**TestDrive-UI** is a lightweight, browser-first development scaffold for building interactive web components using a **Test-Driven Development (TDD)** workflow. + +It provides a reproducible structure thatโ€™s simple enough for hobby projects, but robust enough for AI-assisted, agent-driven development loops. + +> **Core idea:** +> Every UI feature begins as a **behavioral test** โ€” not a design or mockup. +> The implementation grows only to satisfy that test. +> The UI emerges naturally from verified expectations. + +--- + +## ๐Ÿงฑ The Toolchain Overview + +| Tool | Purpose | Why itโ€™s used | Website | +| -------------------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| ๐Ÿงฉ **[Lit](https://lit.dev/)** | Define Web Components with declarative rendering | Fast, framework-agnostic, minimal build overhead; lets you create reusable, encapsulated components | [https://lit.dev](https://lit.dev) | +| ๐Ÿงช **[Mocha](https://mochajs.org/)** | Test runner for JavaScript | Mature, flexible, CLI-friendly โ€” ideal for writing descriptive, behavior-oriented tests (`describe` / `it`) | [https://mochajs.org](https://mochajs.org) | +| ๐Ÿ’ฌ **[Chai](https://www.chaijs.com/)** | Assertion library | Clean, readable syntax for expectations (`expect(value).to.equal(...)`) | [https://www.chaijs.com](https://www.chaijs.com) | +| ๐Ÿง  **[JSDOM](https://github.com/jsdom/jsdom)** | Simulated DOM for Node | Lets you run DOM-based UI tests without a browser; deterministic and CI-friendly | [https://github.com/jsdom/jsdom](https://github.com/jsdom/jsdom) | +| โšก **[Vite](https://vitejs.dev/)** | Development server & bundler | Instant hot-reloads and minimal config for web-component projects | [https://vitejs.dev](https://vitejs.dev) | +| ๐Ÿงฑ **[Storybook](https://storybook.js.org/)** (optional) | Visual component sandbox | Lets you document, preview, and visually test each component in isolation | [https://storybook.js.org](https://storybook.js.org) | +| ๐Ÿ”ฌ **[Playwright](https://playwright.dev/)** (optional) | End-to-end browser testing | Adds full browser automation for real UI validation | [https://playwright.dev](https://playwright.dev) | + +Together, these tools form the **TestDrive-UI loop**: + +``` +Requirement โ†’ Test (Mocha+Chai) โ†’ Implementation (Lit) โ†’ Run (Vite) โ†’ Refine โ†’ Repeat +``` + +--- + +## ๐Ÿงฉ How the Development Flow Works + +### 1. Define the Behavior + +Write a short **requirement statement** or JSON spec describing what the component *should do* โ€” not how it looks. + +Example: + +> โ€œWhen the user types in the input field, the greeting updates immediately.โ€ + +This requirement drives both test and implementation. + +--- + +### 2. Write the Test First + +Create a Mocha test file in `src/components//.test.js`: + +```javascript +import "./hello-world.js"; + +describe("", () => { + it("updates the greeting when user types", 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!"); + }); +}); +``` + +When you run `npm test`, this test will **fail** until you implement the behavior. + +--- + +### 3. Implement Just Enough Code to Pass + +Write the smallest possible component in **Lit** to satisfy the test: + +```javascript +import { LitElement, html, css } from "lit"; + +export class HelloWorld extends LitElement { + static properties = { name: { type: String } }; + constructor() { super(); this.name = "World"; } + + render() { + return html` +
Hello, ${this.name}!
+ + `; + } + _onInput(e) { this.name = e.target.value; } +} +customElements.define("hello-world", HelloWorld); +``` + +Run tests again โ€” they should now pass. + +--- + +### 4. Refine, Extend, and Visualize + +When a component works, visualize it: + +```bash +npm run dev +``` + +Vite opens the browser instantly so you can adjust styling and layout. + +Optionally, use Storybook to display multiple variants: + +```javascript +export default { title: "UI/Hello World" }; +export const Default = () => ``; +export const Named = () => ``; +``` + +--- + +### 5. Iterate + +Each new behavior (feature, bugfix, style rule, accessibility improvement) starts with a **new test**. +The system evolves incrementally, each step verifiable by automation. + +--- + +## ๐Ÿง  Why This Matters + +| Principle | Benefit | +| ----------------------------------------- | ------------------------------------------------ | +| **TDD-first** | Prevents UI drift and regression early. | +| **Declarative UI (Lit)** | Clean separation of state and template. | +| **Deterministic Testing (JSDOM + Mocha)** | Reproducible results without browser complexity. | +| **Fast Iteration (Vite)** | Keeps flow tight and interactive. | +| **Optional Visual Layer (Storybook)** | Communication and design documentation built in. | + +--- + +## ๐Ÿงฉ Advanced Extensions + +Once the base workflow feels solid, TestDrive-UI scales elegantly to more advanced cases: + +* **Shared reactive stores** โ€” implement central state management (see Tutorial 6). +* **Persistence** โ€” save data using localStorage or IndexedDB (see Tutorial 7). +* **Agent automation** โ€” use AI coding agents to generate and maintain tests automatically (see Tutorial 8). +* **End-to-end testing** โ€” run full browser simulations via Playwright. + +--- + +## ๐Ÿ”— Official Tool Links + +| Tool | Website | +| ---------- | ---------------------------------------------------------------- | +| Lit | [https://lit.dev](https://lit.dev) | +| Mocha | [https://mochajs.org](https://mochajs.org) | +| Chai | [https://www.chaijs.com](https://www.chaijs.com) | +| JSDOM | [https://github.com/jsdom/jsdom](https://github.com/jsdom/jsdom) | +| Vite | [https://vitejs.dev](https://vitejs.dev) | +| Storybook | [https://storybook.js.org](https://storybook.js.org) | +| Playwright | [https://playwright.dev](https://playwright.dev) | + +--- + +## ๐Ÿš€ Quick Start Recap + +```bash +# Clone or unzip scaffold +npm install + +# Run tests +npm test + +# Start live preview +npm run dev +``` + +* Tests define behavior. +* Components evolve from tests. +* UIs emerge naturally from verified code. + +> **TestDrive-UI** isnโ€™t a framework โ€” itโ€™s a *method*. +> Itโ€™s how you and your coding agents learn to reason about interfaces through evidence. + +--- + +Start with our tutorials to get going. + +xxx \ No newline at end of file diff --git a/tutorials/Additional Tutorial Suggestions.md b/tutorials/Additional Tutorial Suggestions.md index cfe764d..a107546 100644 --- a/tutorials/Additional Tutorial Suggestions.md +++ b/tutorials/Additional Tutorial Suggestions.md @@ -1,144 +1,144 @@ -List of **next-stage tutorial ideas**, organized by **theme** and **complexity**, to help evolve both the teaching path and the framework. - ---- - -## ๐Ÿงฉ **A. UI and Interaction Patterns** - -1. **Tutorial 9 โ€” Forms and Validation** - - * Build a `` component with multiple fields and validation logic. - * Introduce unit tests for validation rules and error display. - * Show how to test async validations (e.g., simulated API check). - -2. **Tutorial 10 โ€” Modal Dialogs and Overlays** - - * Implement a `` web component using Lit and CSS transitions. - * Demonstrate accessibility (`aria-*`) and keyboard interactions. - * TDD focus: test open/close state, keyboard handling, and backdrop clicks. - -3. **Tutorial 11 โ€” Keyboard Navigation and Accessibility** - - * Add keyboard shortcuts and focus management to existing components. - * Test tab order, focus restoration, and ARIA attributes using jsdom. - -4. **Tutorial 12 โ€” Dynamic Lists and Reordering** - - * Build a draggable `` component. - * Test DOM updates, drag/drop events, and final order assertions. - ---- - -## โš™๏ธ **B. State, Data, and Logic** - -5. **Tutorial 13 โ€” Derived and Computed State** - - * Extend the store to include computed values (e.g., counts, filters). - * Teach memoization and how to test reactive derivations. - -6. **Tutorial 14 โ€” Undo/Redo and Time Travel** - - * Add a store history stack and commands for undo/redo. - * Test state rollbacks deterministically. - -7. **Tutorial 15 โ€” Multi-Store Coordination** - - * Split state across multiple stores (e.g., `UserStore`, `SettingsStore`). - * Show how to compose subscriptions and test inter-store dependencies. - -8. **Tutorial 16 โ€” Data Fetching and Caching** - - * Use `fetch()` to load data asynchronously and cache it in the store. - * Mock HTTP requests in tests. - * Discuss retry and error handling patterns. - ---- - -## ๐Ÿ’Ž **C. Styling, Theming, and Branding** - -9. **Tutorial 17 โ€” Theming and CSS Variables** - - * Implement a theme manager (light/dark/custom colors). - * Test dynamic theme changes via store and DOM styles. - -10. **Tutorial 18 โ€” Component Libraries and Design Tokens** - - * Introduce reusable style tokens and component variants. - * Build visual regression tests using Storybook snapshots. - ---- - -## ๐Ÿง  **D. Architecture and Automation** - -11. **Tutorial 19 โ€” Reactive Controllers and Composition** - - * Use Litโ€™s `ReactiveController` pattern to encapsulate logic like stores or timers. - * TDD pattern: one controller, multiple components. - -12. **Tutorial 20 โ€” Custom Build and Test Pipelines** - - * Show how to integrate Mocha tests into CI (GitHub Actions). - * Include code coverage (nyc) and automatic agent test reporting. - -13. **Tutorial 21 โ€” Agentic Refactoring** - - * Demonstrate agents proposing architectural changes: - - * identifying code smells, - * extracting controllers, - * enforcing consistent store usage. - -14. **Tutorial 22 โ€” Agent-Driven Story Generation** - - * Agents create new Storybook stories based on test cases. - * Bridge test specs and UX documentation automatically. - ---- - -## ๐Ÿ”ฎ **E. Integration and Expansion** - -15. **Tutorial 23 โ€” REST and GraphQL API Integration** - - * Fetch and render real backend data. - * Mock responses for offline testing. - * Introduce contract tests for schema validation. - -16. **Tutorial 24 โ€” Internationalization (i18n)** - - * Add locale switching to greetings. - * Test translation loading and pluralization behavior. - -17. **Tutorial 25 โ€” Progressive Web App (PWA) Integration** - - * Cache store data offline. - * Add service worker tests for persistence. - -18. **Tutorial 26 โ€” Performance and Profiling** - - * Measure component render times. - * Write regression tests for performance thresholds. - ---- - -## ๐Ÿš€ **F. Visionary / Meta Layer** - -19. **Tutorial 27 โ€” Visual AI Testing** - - * Integrate image snapshots from Storybook for visual regression comparison. - * Show how agents can detect UI drift. - -20. **Tutorial 28 โ€” Self-Testing Components** - - * Each component ships with its own self-test harness. - * Components can verify their own integrity in isolation. - -21. **Tutorial 29 โ€” AI-Assisted UX Heuristics** - - * Agents analyze user interaction logs and propose UI simplifications. - * TDD for heuristic improvements. - -22. **Tutorial 30 โ€” Meta-Framework Evolution** - - * Turn TestDrive-UI into a reusable CLI (`npx testdrive-ui new `). - * Generate scaffolds, tests, and stories automatically. - -xxx +List of **next-stage tutorial ideas**, organized by **theme** and **complexity**, to help evolve both the teaching path and the framework. + +--- + +## ๐Ÿงฉ **A. UI and Interaction Patterns** + +1. **Tutorial 9 โ€” Forms and Validation** + + * Build a `` component with multiple fields and validation logic. + * Introduce unit tests for validation rules and error display. + * Show how to test async validations (e.g., simulated API check). + +2. **Tutorial 10 โ€” Modal Dialogs and Overlays** + + * Implement a `` web component using Lit and CSS transitions. + * Demonstrate accessibility (`aria-*`) and keyboard interactions. + * TDD focus: test open/close state, keyboard handling, and backdrop clicks. + +3. **Tutorial 11 โ€” Keyboard Navigation and Accessibility** + + * Add keyboard shortcuts and focus management to existing components. + * Test tab order, focus restoration, and ARIA attributes using jsdom. + +4. **Tutorial 12 โ€” Dynamic Lists and Reordering** + + * Build a draggable `` component. + * Test DOM updates, drag/drop events, and final order assertions. + +--- + +## โš™๏ธ **B. State, Data, and Logic** + +5. **Tutorial 13 โ€” Derived and Computed State** + + * Extend the store to include computed values (e.g., counts, filters). + * Teach memoization and how to test reactive derivations. + +6. **Tutorial 14 โ€” Undo/Redo and Time Travel** + + * Add a store history stack and commands for undo/redo. + * Test state rollbacks deterministically. + +7. **Tutorial 15 โ€” Multi-Store Coordination** + + * Split state across multiple stores (e.g., `UserStore`, `SettingsStore`). + * Show how to compose subscriptions and test inter-store dependencies. + +8. **Tutorial 16 โ€” Data Fetching and Caching** + + * Use `fetch()` to load data asynchronously and cache it in the store. + * Mock HTTP requests in tests. + * Discuss retry and error handling patterns. + +--- + +## ๐Ÿ’Ž **C. Styling, Theming, and Branding** + +9. **Tutorial 17 โ€” Theming and CSS Variables** + + * Implement a theme manager (light/dark/custom colors). + * Test dynamic theme changes via store and DOM styles. + +10. **Tutorial 18 โ€” Component Libraries and Design Tokens** + + * Introduce reusable style tokens and component variants. + * Build visual regression tests using Storybook snapshots. + +--- + +## ๐Ÿง  **D. Architecture and Automation** + +11. **Tutorial 19 โ€” Reactive Controllers and Composition** + + * Use Litโ€™s `ReactiveController` pattern to encapsulate logic like stores or timers. + * TDD pattern: one controller, multiple components. + +12. **Tutorial 20 โ€” Custom Build and Test Pipelines** + + * Show how to integrate Mocha tests into CI (GitHub Actions). + * Include code coverage (nyc) and automatic agent test reporting. + +13. **Tutorial 21 โ€” Agentic Refactoring** + + * Demonstrate agents proposing architectural changes: + + * identifying code smells, + * extracting controllers, + * enforcing consistent store usage. + +14. **Tutorial 22 โ€” Agent-Driven Story Generation** + + * Agents create new Storybook stories based on test cases. + * Bridge test specs and UX documentation automatically. + +--- + +## ๐Ÿ”ฎ **E. Integration and Expansion** + +15. **Tutorial 23 โ€” REST and GraphQL API Integration** + + * Fetch and render real backend data. + * Mock responses for offline testing. + * Introduce contract tests for schema validation. + +16. **Tutorial 24 โ€” Internationalization (i18n)** + + * Add locale switching to greetings. + * Test translation loading and pluralization behavior. + +17. **Tutorial 25 โ€” Progressive Web App (PWA) Integration** + + * Cache store data offline. + * Add service worker tests for persistence. + +18. **Tutorial 26 โ€” Performance and Profiling** + + * Measure component render times. + * Write regression tests for performance thresholds. + +--- + +## ๐Ÿš€ **F. Visionary / Meta Layer** + +19. **Tutorial 27 โ€” Visual AI Testing** + + * Integrate image snapshots from Storybook for visual regression comparison. + * Show how agents can detect UI drift. + +20. **Tutorial 28 โ€” Self-Testing Components** + + * Each component ships with its own self-test harness. + * Components can verify their own integrity in isolation. + +21. **Tutorial 29 โ€” AI-Assisted UX Heuristics** + + * Agents analyze user interaction logs and propose UI simplifications. + * TDD for heuristic improvements. + +22. **Tutorial 30 โ€” Meta-Framework Evolution** + + * Turn TestDrive-UI into a reusable CLI (`npx testdrive-ui new `). + * Generate scaffolds, tests, and stories automatically. + +xxx diff --git a/tutorials/Tutorial 1 Hello World.md b/tutorials/Tutorial 1 Hello World.md index 5fbcbfb..676a7ff 100644 --- a/tutorials/Tutorial 1 Hello World.md +++ b/tutorials/Tutorial 1 Hello World.md @@ -1,187 +1,187 @@ -# Tutorial 1: Hello World! - -Letโ€™s walk through your first **HelloWorld** component built with **TestDrive-UI**. - -This will demonstrate the full workflow: -๐Ÿ‘‰ requirement โ†’ test โ†’ implementation โ†’ run โ†’ refinement. - ---- - -## ๐Ÿงฑ 1. Project setup - -If you havenโ€™t already: - -```bash -unzip testdrive-ui.zip -cd testdrive-ui -npm install -``` - -Now youโ€™re ready to create your first component. - ---- - -## ๐Ÿงฉ 2. Create a component folder - -```bash -mkdir src/components/hello-world -``` - -Inside it, youโ€™ll have: - -``` -hello-world/ -โ”œโ”€โ”€ hello-world.js -โ”œโ”€โ”€ hello-world.test.js -โ””โ”€โ”€ hello-world.stories.js -``` - ---- - -## โœ๏ธ 3. Step 1 โ€” Write the requirement - -> โ€œWhen `` is rendered, it should display the text **Hello World!** -> and clicking the element should show an alert.โ€ - -Thatโ€™s your behavioral spec โ€” the *โ€œwhyโ€* that drives the test. - ---- - -## ๐Ÿงช 4. Step 2 โ€” Write the test first - -`src/components/hello-world/hello-world.test.js` - -```javascript -import "./hello-world.js"; - -describe("", () => { - it("renders the correct greeting", () => { - const el = document.createElement("hello-world"); - document.body.appendChild(el); - - const content = el.shadowRoot.textContent; - expect(content).to.include("Hello World!"); - }); - - it("triggers an alert on click", () => { - const el = document.createElement("hello-world"); - document.body.appendChild(el); - - let alerted = false; - window.alert = () => (alerted = true); - - const div = el.shadowRoot.querySelector("div"); - div.click(); - - expect(alerted).to.be.true; - }); -}); -``` - -Run this test now: - -```bash -npm test -``` - -Both tests will **fail** initially โ€” perfect, thatโ€™s the TDD start. - ---- - -## ๐Ÿ’ก 5. Step 3 โ€” Implement the component - -`src/components/hello-world/hello-world.js` - -```javascript -import { LitElement, html, css } from "lit"; - -export class HelloWorld extends LitElement { - static styles = css` - div { - font-family: system-ui, sans-serif; - font-size: 1.5rem; - color: #007acc; - padding: 1rem; - text-align: center; - cursor: pointer; - user-select: none; - } - div:hover { - color: #005fa3; - } - `; - - render() { - return html`
Hello World!
`; - } - - _onClick() { - alert("Hello from TestDrive-UI!"); - } -} - -customElements.define("hello-world", HelloWorld); -``` - -Run the tests again: - -```bash -npm test -``` - -โœ… Both should now pass. - ---- - -## โšก 6. Step 4 โ€” Preview it in the browser - -Add it to your `index.html`: - -```html - - -``` - -Then run: - -```bash -npm run dev -``` - -Open [http://localhost:5173](http://localhost:5173) -โ†’ Youโ€™ll see your clickable **Hello World!** component rendered live. - ---- - -## ๐Ÿงญ 7. Step 5 โ€” Visual story (optional) - -`src/components/hello-world/hello-world.stories.js` - -```javascript -import "./hello-world.js"; - -export default { - title: "UI/Hello World", -}; - -export const Default = () => ``; -``` - -Once you add Storybook to the scaffold later, this file will automatically generate a visual preview card. - ---- - -## โœ… 8. What you achieved - -* Created an **independent UI component** with Lit -* Wrote deterministic **tests with jsdom + Mocha** -* Verified behavior automatically -* Previewed visually via **Vite** - -This loop is your core **TestDrive-UI workflow**: - -``` -spec โ†’ test โ†’ implement โ†’ run โ†’ refine -``` - -xxx +# Tutorial 1: Hello World! + +Letโ€™s walk through your first **HelloWorld** component built with **TestDrive-UI**. + +This will demonstrate the full workflow: +๐Ÿ‘‰ requirement โ†’ test โ†’ implementation โ†’ run โ†’ refinement. + +--- + +## ๐Ÿงฑ 1. Project setup + +If you havenโ€™t already: + +```bash +unzip testdrive-ui.zip +cd testdrive-ui +npm install +``` + +Now youโ€™re ready to create your first component. + +--- + +## ๐Ÿงฉ 2. Create a component folder + +```bash +mkdir src/components/hello-world +``` + +Inside it, youโ€™ll have: + +``` +hello-world/ +โ”œโ”€โ”€ hello-world.js +โ”œโ”€โ”€ hello-world.test.js +โ””โ”€โ”€ hello-world.stories.js +``` + +--- + +## โœ๏ธ 3. Step 1 โ€” Write the requirement + +> โ€œWhen `` is rendered, it should display the text **Hello World!** +> and clicking the element should show an alert.โ€ + +Thatโ€™s your behavioral spec โ€” the *โ€œwhyโ€* that drives the test. + +--- + +## ๐Ÿงช 4. Step 2 โ€” Write the test first + +`src/components/hello-world/hello-world.test.js` + +```javascript +import "./hello-world.js"; + +describe("", () => { + it("renders the correct greeting", () => { + const el = document.createElement("hello-world"); + document.body.appendChild(el); + + const content = el.shadowRoot.textContent; + expect(content).to.include("Hello World!"); + }); + + it("triggers an alert on click", () => { + const el = document.createElement("hello-world"); + document.body.appendChild(el); + + let alerted = false; + window.alert = () => (alerted = true); + + const div = el.shadowRoot.querySelector("div"); + div.click(); + + expect(alerted).to.be.true; + }); +}); +``` + +Run this test now: + +```bash +npm test +``` + +Both tests will **fail** initially โ€” perfect, thatโ€™s the TDD start. + +--- + +## ๐Ÿ’ก 5. Step 3 โ€” Implement the component + +`src/components/hello-world/hello-world.js` + +```javascript +import { LitElement, html, css } from "lit"; + +export class HelloWorld extends LitElement { + static styles = css` + div { + font-family: system-ui, sans-serif; + font-size: 1.5rem; + color: #007acc; + padding: 1rem; + text-align: center; + cursor: pointer; + user-select: none; + } + div:hover { + color: #005fa3; + } + `; + + render() { + return html`
Hello World!
`; + } + + _onClick() { + alert("Hello from TestDrive-UI!"); + } +} + +customElements.define("hello-world", HelloWorld); +``` + +Run the tests again: + +```bash +npm test +``` + +โœ… Both should now pass. + +--- + +## โšก 6. Step 4 โ€” Preview it in the browser + +Add it to your `index.html`: + +```html + + +``` + +Then run: + +```bash +npm run dev +``` + +Open [http://localhost:5173](http://localhost:5173) +โ†’ Youโ€™ll see your clickable **Hello World!** component rendered live. + +--- + +## ๐Ÿงญ 7. Step 5 โ€” Visual story (optional) + +`src/components/hello-world/hello-world.stories.js` + +```javascript +import "./hello-world.js"; + +export default { + title: "UI/Hello World", +}; + +export const Default = () => ``; +``` + +Once you add Storybook to the scaffold later, this file will automatically generate a visual preview card. + +--- + +## โœ… 8. What you achieved + +* Created an **independent UI component** with Lit +* Wrote deterministic **tests with jsdom + Mocha** +* Verified behavior automatically +* Previewed visually via **Vite** + +This loop is your core **TestDrive-UI workflow**: + +``` +spec โ†’ test โ†’ implement โ†’ run โ†’ refine +``` + +xxx diff --git a/tutorials/Tutorial 2 Reactive Properties.md b/tutorials/Tutorial 2 Reactive Properties.md index e82179a..5718e98 100644 --- a/tutorials/Tutorial 2 Reactive Properties.md +++ b/tutorials/Tutorial 2 Reactive Properties.md @@ -1,217 +1,217 @@ -# Tutorial 2 Reactive Properties - -This second **TestDrive-UI** tutorial extends your previous `hello-world` component by introducing **reactive properties** (i.e., component inputs) and **dynamic rendering**, all under **TDD** control. - -Weโ€™ll end up with a `` component that greets a given name โ€” and can change dynamically when the property updates. - ---- - -## ๐Ÿงญ 1. Goal - -> The component should display โ€œHello, [name]!โ€ -> and automatically update when the `name` property changes. - -If no `name` is given, it should default to โ€œWorldโ€. - ---- - -## ๐Ÿงช 2. Step 1 โ€” Write the failing test first - -Create `src/components/hello-world/hello-world.props.test.js`: - -```javascript -import "./hello-world.js"; - -describe(" (with name property)", () => { - it("renders default greeting when no name is set", () => { - const el = document.createElement("hello-world"); - document.body.appendChild(el); - - const text = el.shadowRoot.textContent.trim(); - expect(text).to.equal("Hello, World!"); - }); - - it("renders custom greeting when name is set", async () => { - const el = document.createElement("hello-world"); - document.body.appendChild(el); - el.name = "Kale"; - - // Wait for Litโ€™s update cycle - await el.updateComplete; - - const text = el.shadowRoot.textContent.trim(); - expect(text).to.equal("Hello, Kale!"); - }); - - it("reacts to property change after initial render", async () => { - const el = document.createElement("hello-world"); - document.body.appendChild(el); - el.name = "Aria"; - await el.updateComplete; - - let text = el.shadowRoot.textContent.trim(); - expect(text).to.equal("Hello, Aria!"); - - el.name = "Nova"; - await el.updateComplete; - - text = el.shadowRoot.textContent.trim(); - expect(text).to.equal("Hello, Nova!"); - }); -}); -``` - -Run: - -```bash -npm test -``` - -All tests should fail โ€” we havenโ€™t implemented anything yet. - ---- - -## ๐Ÿงฉ 3. Step 2 โ€” Implement the feature - -Open your existing `src/components/hello-world/hello-world.js` -and replace the class with this improved version: - -```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` - div { - font-family: system-ui, sans-serif; - font-size: 1.5rem; - color: #007acc; - padding: 1rem; - text-align: center; - cursor: pointer; - user-select: none; - } - div:hover { - color: #005fa3; - } - `; - - render() { - return html`
- Hello, ${this.name}! -
`; - } - - _onClick() { - alert(`Hello, ${this.name}!`); - } -} - -customElements.define("hello-world", HelloWorld); -``` - -Run the tests again: - -```bash -npm test -``` - -โœ… All should now pass. - ---- - -## โšก 4. Step 3 โ€” Try it live - -Edit `src/index.html` to demonstrate both variants: - -```html - - -``` - -Then: - -```bash -npm run dev -``` - -In the browser youโ€™ll see: - -``` -Hello, World! -Hello, Coulomb! -``` - -and both are clickable. - ---- - -## ๐Ÿ”„ 5. Step 4 โ€” Live updates (optional exploration) - -Open the browser console and type: - -```javascript -document.querySelector("hello-world").name = "Agent"; -``` - -The first greeting should **update instantly** to: - -``` -Hello, Agent! -``` - -Thatโ€™s Litโ€™s reactive update mechanism at work. - ---- - -## ๐Ÿงญ 6. Step 5 โ€” Visual story (optional) - -`src/components/hello-world/hello-world.stories.js` - -```javascript -import "./hello-world.js"; - -export default { - title: "UI/Hello World (Reactive)" -}; - -export const Default = () => ``; -export const CustomName = () => ``; -``` - -If Storybook is later installed, these stories will become live demos. - ---- - -## ๐Ÿงฉ 7. Key Takeaways - -| Concept | Explanation | -| --------------------- | ------------------------------------------------- | -| **Reactive property** | Declared via `static properties = { ... }` in Lit | -| **Default values** | Set in the constructor | -| **Automatic updates** | Changing the property triggers re-render | -| **Testing updates** | Use `await el.updateComplete` before asserting | - ---- - -## ๐Ÿงช 8. What you learned - -* How to **declare reactive component properties** -* How to **test reactivity** with Mocha + jsdom -* How to **update and verify UI behavior** in a TDD loop - ---- - -Next, we can take it one level further: - -> Add a **text input** inside `` that updates the `name` property live when the user types. - +# Tutorial 2 Reactive Properties + +This second **TestDrive-UI** tutorial extends your previous `hello-world` component by introducing **reactive properties** (i.e., component inputs) and **dynamic rendering**, all under **TDD** control. + +Weโ€™ll end up with a `` component that greets a given name โ€” and can change dynamically when the property updates. + +--- + +## ๐Ÿงญ 1. Goal + +> The component should display โ€œHello, [name]!โ€ +> and automatically update when the `name` property changes. + +If no `name` is given, it should default to โ€œWorldโ€. + +--- + +## ๐Ÿงช 2. Step 1 โ€” Write the failing test first + +Create `src/components/hello-world/hello-world.props.test.js`: + +```javascript +import "./hello-world.js"; + +describe(" (with name property)", () => { + it("renders default greeting when no name is set", () => { + const el = document.createElement("hello-world"); + document.body.appendChild(el); + + const text = el.shadowRoot.textContent.trim(); + expect(text).to.equal("Hello, World!"); + }); + + it("renders custom greeting when name is set", async () => { + const el = document.createElement("hello-world"); + document.body.appendChild(el); + el.name = "Kale"; + + // Wait for Litโ€™s update cycle + await el.updateComplete; + + const text = el.shadowRoot.textContent.trim(); + expect(text).to.equal("Hello, Kale!"); + }); + + it("reacts to property change after initial render", async () => { + const el = document.createElement("hello-world"); + document.body.appendChild(el); + el.name = "Aria"; + await el.updateComplete; + + let text = el.shadowRoot.textContent.trim(); + expect(text).to.equal("Hello, Aria!"); + + el.name = "Nova"; + await el.updateComplete; + + text = el.shadowRoot.textContent.trim(); + expect(text).to.equal("Hello, Nova!"); + }); +}); +``` + +Run: + +```bash +npm test +``` + +All tests should fail โ€” we havenโ€™t implemented anything yet. + +--- + +## ๐Ÿงฉ 3. Step 2 โ€” Implement the feature + +Open your existing `src/components/hello-world/hello-world.js` +and replace the class with this improved version: + +```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` + div { + font-family: system-ui, sans-serif; + font-size: 1.5rem; + color: #007acc; + padding: 1rem; + text-align: center; + cursor: pointer; + user-select: none; + } + div:hover { + color: #005fa3; + } + `; + + render() { + return html`
+ Hello, ${this.name}! +
`; + } + + _onClick() { + alert(`Hello, ${this.name}!`); + } +} + +customElements.define("hello-world", HelloWorld); +``` + +Run the tests again: + +```bash +npm test +``` + +โœ… All should now pass. + +--- + +## โšก 4. Step 3 โ€” Try it live + +Edit `src/index.html` to demonstrate both variants: + +```html + + +``` + +Then: + +```bash +npm run dev +``` + +In the browser youโ€™ll see: + +``` +Hello, World! +Hello, Coulomb! +``` + +and both are clickable. + +--- + +## ๐Ÿ”„ 5. Step 4 โ€” Live updates (optional exploration) + +Open the browser console and type: + +```javascript +document.querySelector("hello-world").name = "Agent"; +``` + +The first greeting should **update instantly** to: + +``` +Hello, Agent! +``` + +Thatโ€™s Litโ€™s reactive update mechanism at work. + +--- + +## ๐Ÿงญ 6. Step 5 โ€” Visual story (optional) + +`src/components/hello-world/hello-world.stories.js` + +```javascript +import "./hello-world.js"; + +export default { + title: "UI/Hello World (Reactive)" +}; + +export const Default = () => ``; +export const CustomName = () => ``; +``` + +If Storybook is later installed, these stories will become live demos. + +--- + +## ๐Ÿงฉ 7. Key Takeaways + +| Concept | Explanation | +| --------------------- | ------------------------------------------------- | +| **Reactive property** | Declared via `static properties = { ... }` in Lit | +| **Default values** | Set in the constructor | +| **Automatic updates** | Changing the property triggers re-render | +| **Testing updates** | Use `await el.updateComplete` before asserting | + +--- + +## ๐Ÿงช 8. What you learned + +* How to **declare reactive component properties** +* How to **test reactivity** with Mocha + jsdom +* How to **update and verify UI behavior** in a TDD loop + +--- + +Next, we can take it one level further: + +> Add a **text input** inside `` that updates the `name` property live when the user types. + xxx \ No newline at end of file diff --git a/tutorials/Tutorial 3 Bidirectional Data Binding.md b/tutorials/Tutorial 3 Bidirectional Data Binding.md index a221a93..28a8548 100644 --- a/tutorials/Tutorial 3 Bidirectional Data Binding.md +++ b/tutorials/Tutorial 3 Bidirectional Data Binding.md @@ -1,221 +1,221 @@ -# 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. - +# 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 \ No newline at end of file diff --git a/tutorials/Tutorial 4 Component Composition.md b/tutorials/Tutorial 4 Component Composition.md index d1b12e2..68a66db 100644 --- a/tutorials/Tutorial 4 Component Composition.md +++ b/tutorials/Tutorial 4 Component Composition.md @@ -1,257 +1,257 @@ -# 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 +# 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 diff --git a/tutorials/Tutorial 5 Component Communication.md b/tutorials/Tutorial 5 Component Communication.md index 5247e96..2a137f1 100644 --- a/tutorials/Tutorial 5 Component Communication.md +++ b/tutorials/Tutorial 5 Component Communication.md @@ -1,252 +1,252 @@ -# Tutorial 5: Component Communication - -Welcome to the **fifth tutorial** takes you into one of the most important patterns in UI development: **communication between components.** -Up to now, your `` components have been independent. In this tutorial, youโ€™ll make them *talk* to their parent `` component via **custom events**, keeping everything under **TDD**. - ---- - -# ๐Ÿงญ Tutorial 5 โ€” Component Communication - -### โ€œTalking Componentsโ€ โ€” events between parent and child - ---- - -## ๐ŸŽฏ Goal - -Weโ€™ll make each `` component **emit an event** when its name changes. -The `` component will **listen to these events** and maintain a live **activity log**. - -The flow will look like this: - -``` -[User types in input] - โ†“ - fires event "name-changed" - โ†“ - listens, updates log - โ†“ -Log list displays latest updates -``` - ---- - -## ๐Ÿงช 1. Step 1 โ€” Write the failing test first - -Create `src/components/hello-dashboard/hello-dashboard.events.test.js`: - -```javascript -import "../hello-world/hello-world.js"; -import "./hello-dashboard.js"; - -describe(" (events)", () => { - it("shows a log section that updates when a child emits 'name-changed'", async () => { - const el = document.createElement("hello-dashboard"); - document.body.appendChild(el); - - // Initial render - let log = el.shadowRoot.querySelector(".log"); - expect(log.textContent).to.include("No activity yet"); - - // Simulate child event - const firstChild = el.shadowRoot.querySelector("hello-world"); - const event = new CustomEvent("name-changed", { - detail: { oldName: "World", newName: "Agent" }, - bubbles: true, - composed: true - }); - firstChild.dispatchEvent(event); - await el.updateComplete; - - log = el.shadowRoot.querySelector(".log"); - expect(log.textContent).to.include("World โ†’ Agent"); - }); -}); -``` - -Run: - -```bash -npm test -``` - -The test fails (expected). - ---- - -## ๐Ÿงฉ 2. Step 2 โ€” Update `` to fire events - -Open `src/components/hello-world/hello-world.js` -and update `_onInput()` to emit an event whenever the name changes: - -```javascript -_onInput(event) { - const oldName = this.name; - this.name = event.target.value; - - this.dispatchEvent( - new CustomEvent("name-changed", { - detail: { oldName, newName: this.name }, - bubbles: true, - composed: true - }) - ); -} -``` - -Thatโ€™s it โ€” now `hello-world` tells the world when its name changes. - ---- - -## โš™๏ธ 3. Step 3 โ€” Update `` to handle events - -Edit `src/components/hello-dashboard/hello-dashboard.js`: - -Add a new property and event handler: - -```javascript -constructor() { - super(); - this.greeters = ["World"]; - this.logs = []; -} - -connectedCallback() { - super.connectedCallback(); - this.addEventListener("name-changed", this._onNameChanged); -} - -_onNameChanged = (e) => { - const { oldName, newName } = e.detail; - this.logs = [`${oldName} โ†’ ${newName}`, ...this.logs]; -}; -``` - -Now render the log area under the button: - -```javascript -render() { - return html` -
-

Hello Dashboard

-
Total greetings: ${this.greeters.length}
- ${this.greeters.map( - (name) => html`` - )} - - -
- ${this.logs.length === 0 - ? html`

No activity yet

` - : html`
    - ${this.logs.map((l) => html`
  • ${l}
  • `)} -
`} -
-
- `; -} -``` - -Run tests again: - -```bash -npm test -``` - -โœ… The event test should now pass. - ---- - -## ๐ŸŒ 4. Step 4 โ€” Try it live - -In `src/index.html`: - -```html - -``` - -Then: - -```bash -npm run dev -``` - -Open the page โ€” -Type into a greeter input, and youโ€™ll see a live **activity log** appear below the button: - -``` -Hello Dashboard -Total greetings: 1 -Hello, World! -[ Add Greeting ] -Activity Log: -World โ†’ Agent -``` - ---- - -## ๐Ÿง  5. Step 5 โ€” Add a test for multiple events (optional) - -Extend `hello-dashboard.events.test.js`: - -```javascript -it("keeps a running list of multiple name changes", async () => { - const el = document.createElement("hello-dashboard"); - document.body.appendChild(el); - - const child = el.shadowRoot.querySelector("hello-world"); - - const events = [ - new CustomEvent("name-changed", { detail: { oldName: "World", newName: "A" }, bubbles: true, composed: true }), - new CustomEvent("name-changed", { detail: { oldName: "A", newName: "B" }, bubbles: true, composed: true }) - ]; - - child.dispatchEvent(events[0]); - child.dispatchEvent(events[1]); - await el.updateComplete; - - const items = Array.from(el.shadowRoot.querySelectorAll(".log li")).map(li => li.textContent); - expect(items).to.deep.equal(["A โ†’ B", "World โ†’ A"]); -}); -``` - -โœ… It should pass too. - ---- - -## ๐Ÿ”ฉ 6. Key Concepts - -| Concept | Explanation | -| -------------------------- | ----------------------------------------------------------------- | -| **CustomEvent** | A way for child components to send structured messages to parents | -| **Bubbling + composed** | Ensures events cross shadow DOM boundaries | -| **Reactive logs** | Updating an array property triggers rerender | -| **Hierarchical testing** | TDD across parent/child relationships | -| **Single source of truth** | Dashboard owns state; children only emit events | - ---- - -## โœ… 7. Summary - -Youโ€™ve now built: - -1. Independent reactive components (`hello-world`) -2. Composed layouts (`hello-dashboard`) -3. Two-way UI interaction (input updates) -4. **Inter-component event communication** - -Your **TestDrive-UI** framework now supports: - -* Pure front-end logic -* Reactive re-rendering -* Nested component tests -* Event-driven state management - ---- - -Next step (Tutorial 6) will cover: - -> **State synchronization** โ€” introducing a shared store or context (e.g. using Litโ€™s reactive controllers or a lightweight signal system) so that multiple components reflect shared data automatically. - -Would you like to continue with that direction next? - +# Tutorial 5: Component Communication + +Welcome to the **fifth tutorial** takes you into one of the most important patterns in UI development: **communication between components.** +Up to now, your `` components have been independent. In this tutorial, youโ€™ll make them *talk* to their parent `` component via **custom events**, keeping everything under **TDD**. + +--- + +# ๐Ÿงญ Tutorial 5 โ€” Component Communication + +### โ€œTalking Componentsโ€ โ€” events between parent and child + +--- + +## ๐ŸŽฏ Goal + +Weโ€™ll make each `` component **emit an event** when its name changes. +The `` component will **listen to these events** and maintain a live **activity log**. + +The flow will look like this: + +``` +[User types in input] + โ†“ + fires event "name-changed" + โ†“ + listens, updates log + โ†“ +Log list displays latest updates +``` + +--- + +## ๐Ÿงช 1. Step 1 โ€” Write the failing test first + +Create `src/components/hello-dashboard/hello-dashboard.events.test.js`: + +```javascript +import "../hello-world/hello-world.js"; +import "./hello-dashboard.js"; + +describe(" (events)", () => { + it("shows a log section that updates when a child emits 'name-changed'", async () => { + const el = document.createElement("hello-dashboard"); + document.body.appendChild(el); + + // Initial render + let log = el.shadowRoot.querySelector(".log"); + expect(log.textContent).to.include("No activity yet"); + + // Simulate child event + const firstChild = el.shadowRoot.querySelector("hello-world"); + const event = new CustomEvent("name-changed", { + detail: { oldName: "World", newName: "Agent" }, + bubbles: true, + composed: true + }); + firstChild.dispatchEvent(event); + await el.updateComplete; + + log = el.shadowRoot.querySelector(".log"); + expect(log.textContent).to.include("World โ†’ Agent"); + }); +}); +``` + +Run: + +```bash +npm test +``` + +The test fails (expected). + +--- + +## ๐Ÿงฉ 2. Step 2 โ€” Update `` to fire events + +Open `src/components/hello-world/hello-world.js` +and update `_onInput()` to emit an event whenever the name changes: + +```javascript +_onInput(event) { + const oldName = this.name; + this.name = event.target.value; + + this.dispatchEvent( + new CustomEvent("name-changed", { + detail: { oldName, newName: this.name }, + bubbles: true, + composed: true + }) + ); +} +``` + +Thatโ€™s it โ€” now `hello-world` tells the world when its name changes. + +--- + +## โš™๏ธ 3. Step 3 โ€” Update `` to handle events + +Edit `src/components/hello-dashboard/hello-dashboard.js`: + +Add a new property and event handler: + +```javascript +constructor() { + super(); + this.greeters = ["World"]; + this.logs = []; +} + +connectedCallback() { + super.connectedCallback(); + this.addEventListener("name-changed", this._onNameChanged); +} + +_onNameChanged = (e) => { + const { oldName, newName } = e.detail; + this.logs = [`${oldName} โ†’ ${newName}`, ...this.logs]; +}; +``` + +Now render the log area under the button: + +```javascript +render() { + return html` +
+

Hello Dashboard

+
Total greetings: ${this.greeters.length}
+ ${this.greeters.map( + (name) => html`` + )} + + +
+ ${this.logs.length === 0 + ? html`

No activity yet

` + : html`
    + ${this.logs.map((l) => html`
  • ${l}
  • `)} +
`} +
+
+ `; +} +``` + +Run tests again: + +```bash +npm test +``` + +โœ… The event test should now pass. + +--- + +## ๐ŸŒ 4. Step 4 โ€” Try it live + +In `src/index.html`: + +```html + +``` + +Then: + +```bash +npm run dev +``` + +Open the page โ€” +Type into a greeter input, and youโ€™ll see a live **activity log** appear below the button: + +``` +Hello Dashboard +Total greetings: 1 +Hello, World! +[ Add Greeting ] +Activity Log: +World โ†’ Agent +``` + +--- + +## ๐Ÿง  5. Step 5 โ€” Add a test for multiple events (optional) + +Extend `hello-dashboard.events.test.js`: + +```javascript +it("keeps a running list of multiple name changes", async () => { + const el = document.createElement("hello-dashboard"); + document.body.appendChild(el); + + const child = el.shadowRoot.querySelector("hello-world"); + + const events = [ + new CustomEvent("name-changed", { detail: { oldName: "World", newName: "A" }, bubbles: true, composed: true }), + new CustomEvent("name-changed", { detail: { oldName: "A", newName: "B" }, bubbles: true, composed: true }) + ]; + + child.dispatchEvent(events[0]); + child.dispatchEvent(events[1]); + await el.updateComplete; + + const items = Array.from(el.shadowRoot.querySelectorAll(".log li")).map(li => li.textContent); + expect(items).to.deep.equal(["A โ†’ B", "World โ†’ A"]); +}); +``` + +โœ… It should pass too. + +--- + +## ๐Ÿ”ฉ 6. Key Concepts + +| Concept | Explanation | +| -------------------------- | ----------------------------------------------------------------- | +| **CustomEvent** | A way for child components to send structured messages to parents | +| **Bubbling + composed** | Ensures events cross shadow DOM boundaries | +| **Reactive logs** | Updating an array property triggers rerender | +| **Hierarchical testing** | TDD across parent/child relationships | +| **Single source of truth** | Dashboard owns state; children only emit events | + +--- + +## โœ… 7. Summary + +Youโ€™ve now built: + +1. Independent reactive components (`hello-world`) +2. Composed layouts (`hello-dashboard`) +3. Two-way UI interaction (input updates) +4. **Inter-component event communication** + +Your **TestDrive-UI** framework now supports: + +* Pure front-end logic +* Reactive re-rendering +* Nested component tests +* Event-driven state management + +--- + +Next step (Tutorial 6) will cover: + +> **State synchronization** โ€” introducing a shared store or context (e.g. using Litโ€™s reactive controllers or a lightweight signal system) so that multiple components reflect shared data automatically. + +Would you like to continue with that direction next? + xxx \ No newline at end of file diff --git a/tutorials/Tutorial 6 Shared State.md b/tutorials/Tutorial 6 Shared State.md index a83c9eb..bbc7695 100644 --- a/tutorials/Tutorial 6 Shared State.md +++ b/tutorials/Tutorial 6 Shared State.md @@ -1,322 +1,322 @@ -# Tutorial 6: Shared State - -Now that your components can communicate through events, the **sixth tutorial** introduces the next natural step in professional UI architecture: **shared state** (synchronizing multiple components via a central store). - -Youโ€™ll learn how to implement a **lightweight reactive store** with Lit, integrate it with your existing `` and `` components, and keep everything **test-driven**. - ---- - -# โšก Tutorial 6 โ€” Shared State - -### โ€œThe Store Patternโ€ โ€” Synchronizing Multiple Components - ---- - -## ๐ŸŽฏ Goal - -Weโ€™ll create a **store** that holds the global list of greeter names. -All `` components will automatically update when the store changes. -The `` will use the store to add, remove, and rename greeters โ€” without manually passing props or listening to custom events. - -**Architecture overview:** - -``` -+------------------------+ -| HelloDashboard | -| (manages store ops) | -| | -| โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” | -| | HelloWorld | <โ”€โ”€ store.subscribe() -| โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ | -| โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” | -| | HelloWorld | | -| โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ | -+------------------------+ - โ”‚ - โ–ผ - Shared Store - (holds array of greeters) -``` - ---- - -## ๐Ÿงช 1. Step 1 โ€” Write the failing test first - -Create `src/store/hello-store.test.js`: - -```javascript -import { helloStore } from "./hello-store.js"; - -describe("helloStore", () => { - it("starts with one default greeter", () => { - const state = helloStore.getState(); - expect(state.greeters).to.deep.equal(["World"]); - }); - - it("can add a new greeter", () => { - helloStore.addGreeter("Alice"); - const state = helloStore.getState(); - expect(state.greeters).to.include("Alice"); - }); - - it("notifies subscribers on change", (done) => { - const unsubscribe = helloStore.subscribe((state) => { - expect(state.greeters).to.include("Bob"); - unsubscribe(); - done(); - }); - helloStore.addGreeter("Bob"); - }); -}); -``` - -Run: - -```bash -npm test -``` - -All tests fail (expected). - ---- - -## โš™๏ธ 2. Step 2 โ€” Implement the store - -Create a new file: -`src/store/hello-store.js` - -```javascript -class HelloStore { - constructor() { - this.state = { greeters: ["World"] }; - this.listeners = new Set(); - } - - getState() { - return this.state; - } - - _notify() { - for (const cb of this.listeners) cb(this.state); - } - - subscribe(callback) { - this.listeners.add(callback); - callback(this.state); // immediate call with current state - return () => this.listeners.delete(callback); - } - - addGreeter(name) { - this.state = { - ...this.state, - greeters: [...this.state.greeters, name] - }; - this._notify(); - } - - renameGreeter(oldName, newName) { - const updated = this.state.greeters.map((n) => - n === oldName ? newName : n - ); - this.state = { ...this.state, greeters: updated }; - this._notify(); - } -} - -export const helloStore = new HelloStore(); -``` - -Re-run: - -```bash -npm test -``` - -โœ… All tests should now pass. - ---- - -## ๐Ÿงฉ 3. Step 3 โ€” Update `` to use the store - -Modify `src/components/hello-dashboard/hello-dashboard.js`: - -```javascript -import { LitElement, html, css } from "lit"; -import "../hello-world/hello-world.js"; -import { helloStore } from "../../store/hello-store.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; - } - `; - - constructor() { - super(); - this.greeters = []; - } - - connectedCallback() { - super.connectedCallback(); - this.unsubscribe = helloStore.subscribe((state) => { - this.greeters = state.greeters; - }); - } - - disconnectedCallback() { - this.unsubscribe?.(); - super.disconnectedCallback(); - } - - _addGreeter() { - const newName = `User${this.greeters.length + 1}`; - helloStore.addGreeter(newName); - } - - render() { - return html` -
-

Hello Dashboard

-
Total greetings: ${this.greeters.length}
- - ${this.greeters.map( - (name) => html`` - )} - - -
- `; - } -} - -customElements.define("hello-dashboard", HelloDashboard); -``` - ---- - -## ๐Ÿง  4. Step 4 โ€” Update `` to use store for renaming - -Update `_onInput()` in `src/components/hello-world/hello-world.js`: - -```javascript -import { helloStore } from "../../store/hello-store.js"; - -_onInput(event) { - const oldName = this.name; - this.name = event.target.value; - helloStore.renameGreeter(oldName, this.name); -} -``` - -Now each greeter updates the shared store when its input changes โ€” all other components subscribed to the store will react automatically. - ---- - -## ๐Ÿงช 5. Step 5 โ€” Integration test (optional) - -Add `src/integration/dashboard-store.test.js`: - -```javascript -import "../components/hello-dashboard/hello-dashboard.js"; -import { helloStore } from "../store/hello-store.js"; - -describe("HelloDashboard and HelloStore integration", () => { - it("renders as many greeters as store entries", async () => { - helloStore.addGreeter("Eve"); - const el = document.createElement("hello-dashboard"); - document.body.appendChild(el); - await el.updateComplete; - - const greeters = el.shadowRoot.querySelectorAll("hello-world"); - expect(greeters.length).to.equal(helloStore.getState().greeters.length); - }); -}); -``` - -Run: - -```bash -npm test -``` - -โœ… All pass again. - ---- - -## ๐ŸŒ 6. Step 6 โ€” Try it live - -Add to your `index.html`: - -```html - -``` - -Run: - -```bash -npm run dev -``` - -* Click โ€œAdd Greetingโ€ โ†’ new greeters appear instantly. -* Type into any greeter โ†’ all components referencing that name update automatically. - -Congratulations โ€” youโ€™ve just implemented **a reactive state system** entirely in vanilla JS + Lit! - ---- - -## ๐Ÿ“˜ 7. Key Concepts - -| Concept | Description | -| -------------------------- | ----------------------------------------------------- | -| **Centralized state** | One store manages the data for all components | -| **Observer pattern** | Components subscribe to updates, re-render on change | -| **Reactive sync** | Changes propagate automatically without prop drilling | -| **TDD-first store design** | Ensures predictable, testable state behavior | -| **Agent-friendliness** | Clear separation of state, presentation, and tests | - ---- - -## โœ… 8. Summary - -At this point, your **TestDrive-UI** ecosystem includes: - -| Layer | Responsibility | -| ----------------- | ---------------------------- | -| `hello-world` | Simple reactive component | -| `hello-dashboard` | Container and coordinator | -| `hello-store` | Shared, observable state | -| Tests | Guard behavior at each level | - ---- - -## ๐Ÿ”ฎ 9. Next Tutorial (optional direction) - -You can now evolve in one of two directions: - -1. **Tutorial 7 โ€” Persistence** - โ†’ Save and restore store state via `localStorage` or IndexedDB (so greetings persist). - -2. **Tutorial 8 โ€” Agent Automation** - โ†’ Introduce agent-driven TDD loops: use LLMs to generate new tests, detect regressions, and propose refactorings automatically. - ---- - -Continue with **Tutorial 7: State Persistence** next โ€” or move directly into **agent-driven TDD automation**. - -xxx +# Tutorial 6: Shared State + +Now that your components can communicate through events, the **sixth tutorial** introduces the next natural step in professional UI architecture: **shared state** (synchronizing multiple components via a central store). + +Youโ€™ll learn how to implement a **lightweight reactive store** with Lit, integrate it with your existing `` and `` components, and keep everything **test-driven**. + +--- + +# โšก Tutorial 6 โ€” Shared State + +### โ€œThe Store Patternโ€ โ€” Synchronizing Multiple Components + +--- + +## ๐ŸŽฏ Goal + +Weโ€™ll create a **store** that holds the global list of greeter names. +All `` components will automatically update when the store changes. +The `` will use the store to add, remove, and rename greeters โ€” without manually passing props or listening to custom events. + +**Architecture overview:** + +``` ++------------------------+ +| HelloDashboard | +| (manages store ops) | +| | +| โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” | +| | HelloWorld | <โ”€โ”€ store.subscribe() +| โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ | +| โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” | +| | HelloWorld | | +| โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ | ++------------------------+ + โ”‚ + โ–ผ + Shared Store + (holds array of greeters) +``` + +--- + +## ๐Ÿงช 1. Step 1 โ€” Write the failing test first + +Create `src/store/hello-store.test.js`: + +```javascript +import { helloStore } from "./hello-store.js"; + +describe("helloStore", () => { + it("starts with one default greeter", () => { + const state = helloStore.getState(); + expect(state.greeters).to.deep.equal(["World"]); + }); + + it("can add a new greeter", () => { + helloStore.addGreeter("Alice"); + const state = helloStore.getState(); + expect(state.greeters).to.include("Alice"); + }); + + it("notifies subscribers on change", (done) => { + const unsubscribe = helloStore.subscribe((state) => { + expect(state.greeters).to.include("Bob"); + unsubscribe(); + done(); + }); + helloStore.addGreeter("Bob"); + }); +}); +``` + +Run: + +```bash +npm test +``` + +All tests fail (expected). + +--- + +## โš™๏ธ 2. Step 2 โ€” Implement the store + +Create a new file: +`src/store/hello-store.js` + +```javascript +class HelloStore { + constructor() { + this.state = { greeters: ["World"] }; + this.listeners = new Set(); + } + + getState() { + return this.state; + } + + _notify() { + for (const cb of this.listeners) cb(this.state); + } + + subscribe(callback) { + this.listeners.add(callback); + callback(this.state); // immediate call with current state + return () => this.listeners.delete(callback); + } + + addGreeter(name) { + this.state = { + ...this.state, + greeters: [...this.state.greeters, name] + }; + this._notify(); + } + + renameGreeter(oldName, newName) { + const updated = this.state.greeters.map((n) => + n === oldName ? newName : n + ); + this.state = { ...this.state, greeters: updated }; + this._notify(); + } +} + +export const helloStore = new HelloStore(); +``` + +Re-run: + +```bash +npm test +``` + +โœ… All tests should now pass. + +--- + +## ๐Ÿงฉ 3. Step 3 โ€” Update `` to use the store + +Modify `src/components/hello-dashboard/hello-dashboard.js`: + +```javascript +import { LitElement, html, css } from "lit"; +import "../hello-world/hello-world.js"; +import { helloStore } from "../../store/hello-store.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; + } + `; + + constructor() { + super(); + this.greeters = []; + } + + connectedCallback() { + super.connectedCallback(); + this.unsubscribe = helloStore.subscribe((state) => { + this.greeters = state.greeters; + }); + } + + disconnectedCallback() { + this.unsubscribe?.(); + super.disconnectedCallback(); + } + + _addGreeter() { + const newName = `User${this.greeters.length + 1}`; + helloStore.addGreeter(newName); + } + + render() { + return html` +
+

Hello Dashboard

+
Total greetings: ${this.greeters.length}
+ + ${this.greeters.map( + (name) => html`` + )} + + +
+ `; + } +} + +customElements.define("hello-dashboard", HelloDashboard); +``` + +--- + +## ๐Ÿง  4. Step 4 โ€” Update `` to use store for renaming + +Update `_onInput()` in `src/components/hello-world/hello-world.js`: + +```javascript +import { helloStore } from "../../store/hello-store.js"; + +_onInput(event) { + const oldName = this.name; + this.name = event.target.value; + helloStore.renameGreeter(oldName, this.name); +} +``` + +Now each greeter updates the shared store when its input changes โ€” all other components subscribed to the store will react automatically. + +--- + +## ๐Ÿงช 5. Step 5 โ€” Integration test (optional) + +Add `src/integration/dashboard-store.test.js`: + +```javascript +import "../components/hello-dashboard/hello-dashboard.js"; +import { helloStore } from "../store/hello-store.js"; + +describe("HelloDashboard and HelloStore integration", () => { + it("renders as many greeters as store entries", async () => { + helloStore.addGreeter("Eve"); + const el = document.createElement("hello-dashboard"); + document.body.appendChild(el); + await el.updateComplete; + + const greeters = el.shadowRoot.querySelectorAll("hello-world"); + expect(greeters.length).to.equal(helloStore.getState().greeters.length); + }); +}); +``` + +Run: + +```bash +npm test +``` + +โœ… All pass again. + +--- + +## ๐ŸŒ 6. Step 6 โ€” Try it live + +Add to your `index.html`: + +```html + +``` + +Run: + +```bash +npm run dev +``` + +* Click โ€œAdd Greetingโ€ โ†’ new greeters appear instantly. +* Type into any greeter โ†’ all components referencing that name update automatically. + +Congratulations โ€” youโ€™ve just implemented **a reactive state system** entirely in vanilla JS + Lit! + +--- + +## ๐Ÿ“˜ 7. Key Concepts + +| Concept | Description | +| -------------------------- | ----------------------------------------------------- | +| **Centralized state** | One store manages the data for all components | +| **Observer pattern** | Components subscribe to updates, re-render on change | +| **Reactive sync** | Changes propagate automatically without prop drilling | +| **TDD-first store design** | Ensures predictable, testable state behavior | +| **Agent-friendliness** | Clear separation of state, presentation, and tests | + +--- + +## โœ… 8. Summary + +At this point, your **TestDrive-UI** ecosystem includes: + +| Layer | Responsibility | +| ----------------- | ---------------------------- | +| `hello-world` | Simple reactive component | +| `hello-dashboard` | Container and coordinator | +| `hello-store` | Shared, observable state | +| Tests | Guard behavior at each level | + +--- + +## ๐Ÿ”ฎ 9. Next Tutorial (optional direction) + +You can now evolve in one of two directions: + +1. **Tutorial 7 โ€” Persistence** + โ†’ Save and restore store state via `localStorage` or IndexedDB (so greetings persist). + +2. **Tutorial 8 โ€” Agent Automation** + โ†’ Introduce agent-driven TDD loops: use LLMs to generate new tests, detect regressions, and propose refactorings automatically. + +--- + +Continue with **Tutorial 7: State Persistence** next โ€” or move directly into **agent-driven TDD automation**. + +xxx diff --git a/tutorials/Tutorial 7 Persistence Patterns.md b/tutorials/Tutorial 7 Persistence Patterns.md index 13aae79..df7bae9 100644 --- a/tutorials/Tutorial 7 Persistence Patterns.md +++ b/tutorials/Tutorial 7 Persistence Patterns.md @@ -1,238 +1,238 @@ -# Tutorial 7: Persistence Patterns - -This tutorial teaches both **localStorage** and **IndexedDB** persistence patterns for your reactive store. - -### โ€œSaving Greetingsโ€ โ€” Making the store survive reloads - ---- - -## ๐ŸŽฏ Goal - -Extend the `helloStore` so that all greetings persist between browser sessions. -When the page reloads, previously added greeters are restored automatically. - -Weโ€™ll implement this in **two steps**: - -1. Start with simple **`localStorage`** persistence (fast, synchronous). -2. Upgrade to **`IndexedDB`** for larger data or structured offline storage. - -Both variants will keep your TestDrive-UI environment self-contained and testable. - ---- - -## ๐Ÿงช Step 1 โ€” Write the failing tests - -Create a new file: -`src/store/hello-store.persistence.test.js` - -```javascript -import { helloStore } from "./hello-store.js"; - -describe("helloStore persistence", () => { - beforeEach(() => { - // Clean simulated storage - global.localStorage = { - store: {}, - getItem(k) { return this.store[k] || null; }, - setItem(k, v) { this.store[k] = v; }, - clear() { this.store = {}; } - }; - }); - - it("saves state to localStorage after modification", () => { - helloStore.addGreeter("Persisty"); - const saved = JSON.parse(localStorage.getItem("helloStore")); - expect(saved.greeters).to.include("Persisty"); - }); - - it("restores saved state when re-initialized", () => { - localStorage.setItem( - "helloStore", - JSON.stringify({ greeters: ["Restored"] }) - ); - const { HelloStore } = await import("./hello-store.js"); - const freshStore = new HelloStore(); - const state = freshStore.getState(); - expect(state.greeters).to.deep.equal(["Restored"]); - }); -}); -``` - -Run: - -```bash -npm test -``` - -All tests will fail initially โ€” perfect. - ---- - -## โš™๏ธ Step 2 โ€” Implement `localStorage` persistence - -Open `src/store/hello-store.js` and extend it like this: - -```javascript -class HelloStore { - constructor() { - const saved = localStorage.getItem("helloStore"); - this.state = saved ? JSON.parse(saved) : { greeters: ["World"] }; - this.listeners = new Set(); - } - - getState() { - return this.state; - } - - _notify() { - // Save current state before notifying - localStorage.setItem("helloStore", JSON.stringify(this.state)); - for (const cb of this.listeners) cb(this.state); - } - - subscribe(callback) { - this.listeners.add(callback); - callback(this.state); - return () => this.listeners.delete(callback); - } - - addGreeter(name) { - this.state = { ...this.state, greeters: [...this.state.greeters, name] }; - this._notify(); - } - - renameGreeter(oldName, newName) { - const updated = this.state.greeters.map((n) => - n === oldName ? newName : n - ); - this.state = { ...this.state, greeters: updated }; - this._notify(); - } -} - -export const helloStore = new HelloStore(); -export { HelloStore }; // exported for testing -``` - -โœ… Run `npm test` again โ€” tests should now pass. - ---- - -## ๐ŸŒ Step 3 โ€” Try it live - -Start your dev server: - -```bash -npm run dev -``` - -1. Add new greeters in the dashboard. -2. Reload the browser. - โ†’ The same greeters reappear immediately! - ---- - -## ๐Ÿงฑ Step 4 โ€” Optional: Switch to IndexedDB for larger data - -`localStorage` is fine for small JSON objects. -To persist more complex data or avoid blocking the main thread, you can use **IndexedDB** (via the tiny `idb-keyval` helper or native API). - -Create `src/store/storage.js`: - -```javascript -// Minimal IndexedDB wrapper using idb-keyval style API -const DB_NAME = "testdrive-ui"; -const STORE_NAME = "hello"; - -export async function saveState(state) { - return new Promise((resolve, reject) => { - const req = indexedDB.open(DB_NAME, 1); - req.onupgradeneeded = () => req.result.createObjectStore(STORE_NAME); - req.onsuccess = () => { - const tx = req.result.transaction(STORE_NAME, "readwrite"); - tx.objectStore(STORE_NAME).put(state, "store"); - tx.oncomplete = resolve; - tx.onerror = reject; - }; - }); -} - -export async function loadState() { - return new Promise((resolve, reject) => { - const req = indexedDB.open(DB_NAME, 1); - req.onupgradeneeded = () => req.result.createObjectStore(STORE_NAME); - req.onsuccess = () => { - const tx = req.result.transaction(STORE_NAME, "readonly"); - const get = tx.objectStore(STORE_NAME).get("store"); - get.onsuccess = () => resolve(get.result); - get.onerror = reject; - }; - }); -} -``` - -Now update the storeโ€™s `_notify()` and constructor to use these async calls: - -```javascript -import { saveState, loadState } from "./storage.js"; - -class HelloStore { - constructor() { - this.listeners = new Set(); - loadState().then( - (saved) => - (this.state = saved || { greeters: ["World"] }) && - this._notify() - ); - } - - async _notify() { - await saveState(this.state); - for (const cb of this.listeners) cb(this.state); - } -} -``` - -๐Ÿ’ก Tip: because IndexedDB operations are async, wrap state changes in `async` methods and adjust your tests with `await`. - ---- - -## ๐Ÿง  Step 5 โ€” Testing IndexedDB logic - -In Node environments, you can simulate IndexedDB with `fake-indexeddb`: - -```bash -npm install --save-dev fake-indexeddb -``` - -Then in your test setup: - -```javascript -import "fake-indexeddb/auto"; -``` - -Your tests now work without a browser. - ---- - -## โœ… Outcome - -Your TestDrive-UI environment now supports: - -| Persistence Layer | Use Case | Pros | -| ----------------- | ------------------------------------- | ------------------ | -| **localStorage** | Small, synchronous, quick persistence | Simple & immediate | -| **IndexedDB** | Large, structured, async persistence | Scalable & robust | - ---- - -## ๐Ÿ” Reflection - -Persistence transforms your store into a **long-term memory layer**. -With this, TestDrive-UI crosses into **progressive-web-app territory** โ€” offline-ready, recoverable, and reliable. - ---- - -Continue to Tutorial 8 on Agent Automation. - -xxx +# Tutorial 7: Persistence Patterns + +This tutorial teaches both **localStorage** and **IndexedDB** persistence patterns for your reactive store. + +### โ€œSaving Greetingsโ€ โ€” Making the store survive reloads + +--- + +## ๐ŸŽฏ Goal + +Extend the `helloStore` so that all greetings persist between browser sessions. +When the page reloads, previously added greeters are restored automatically. + +Weโ€™ll implement this in **two steps**: + +1. Start with simple **`localStorage`** persistence (fast, synchronous). +2. Upgrade to **`IndexedDB`** for larger data or structured offline storage. + +Both variants will keep your TestDrive-UI environment self-contained and testable. + +--- + +## ๐Ÿงช Step 1 โ€” Write the failing tests + +Create a new file: +`src/store/hello-store.persistence.test.js` + +```javascript +import { helloStore } from "./hello-store.js"; + +describe("helloStore persistence", () => { + beforeEach(() => { + // Clean simulated storage + global.localStorage = { + store: {}, + getItem(k) { return this.store[k] || null; }, + setItem(k, v) { this.store[k] = v; }, + clear() { this.store = {}; } + }; + }); + + it("saves state to localStorage after modification", () => { + helloStore.addGreeter("Persisty"); + const saved = JSON.parse(localStorage.getItem("helloStore")); + expect(saved.greeters).to.include("Persisty"); + }); + + it("restores saved state when re-initialized", () => { + localStorage.setItem( + "helloStore", + JSON.stringify({ greeters: ["Restored"] }) + ); + const { HelloStore } = await import("./hello-store.js"); + const freshStore = new HelloStore(); + const state = freshStore.getState(); + expect(state.greeters).to.deep.equal(["Restored"]); + }); +}); +``` + +Run: + +```bash +npm test +``` + +All tests will fail initially โ€” perfect. + +--- + +## โš™๏ธ Step 2 โ€” Implement `localStorage` persistence + +Open `src/store/hello-store.js` and extend it like this: + +```javascript +class HelloStore { + constructor() { + const saved = localStorage.getItem("helloStore"); + this.state = saved ? JSON.parse(saved) : { greeters: ["World"] }; + this.listeners = new Set(); + } + + getState() { + return this.state; + } + + _notify() { + // Save current state before notifying + localStorage.setItem("helloStore", JSON.stringify(this.state)); + for (const cb of this.listeners) cb(this.state); + } + + subscribe(callback) { + this.listeners.add(callback); + callback(this.state); + return () => this.listeners.delete(callback); + } + + addGreeter(name) { + this.state = { ...this.state, greeters: [...this.state.greeters, name] }; + this._notify(); + } + + renameGreeter(oldName, newName) { + const updated = this.state.greeters.map((n) => + n === oldName ? newName : n + ); + this.state = { ...this.state, greeters: updated }; + this._notify(); + } +} + +export const helloStore = new HelloStore(); +export { HelloStore }; // exported for testing +``` + +โœ… Run `npm test` again โ€” tests should now pass. + +--- + +## ๐ŸŒ Step 3 โ€” Try it live + +Start your dev server: + +```bash +npm run dev +``` + +1. Add new greeters in the dashboard. +2. Reload the browser. + โ†’ The same greeters reappear immediately! + +--- + +## ๐Ÿงฑ Step 4 โ€” Optional: Switch to IndexedDB for larger data + +`localStorage` is fine for small JSON objects. +To persist more complex data or avoid blocking the main thread, you can use **IndexedDB** (via the tiny `idb-keyval` helper or native API). + +Create `src/store/storage.js`: + +```javascript +// Minimal IndexedDB wrapper using idb-keyval style API +const DB_NAME = "testdrive-ui"; +const STORE_NAME = "hello"; + +export async function saveState(state) { + return new Promise((resolve, reject) => { + const req = indexedDB.open(DB_NAME, 1); + req.onupgradeneeded = () => req.result.createObjectStore(STORE_NAME); + req.onsuccess = () => { + const tx = req.result.transaction(STORE_NAME, "readwrite"); + tx.objectStore(STORE_NAME).put(state, "store"); + tx.oncomplete = resolve; + tx.onerror = reject; + }; + }); +} + +export async function loadState() { + return new Promise((resolve, reject) => { + const req = indexedDB.open(DB_NAME, 1); + req.onupgradeneeded = () => req.result.createObjectStore(STORE_NAME); + req.onsuccess = () => { + const tx = req.result.transaction(STORE_NAME, "readonly"); + const get = tx.objectStore(STORE_NAME).get("store"); + get.onsuccess = () => resolve(get.result); + get.onerror = reject; + }; + }); +} +``` + +Now update the storeโ€™s `_notify()` and constructor to use these async calls: + +```javascript +import { saveState, loadState } from "./storage.js"; + +class HelloStore { + constructor() { + this.listeners = new Set(); + loadState().then( + (saved) => + (this.state = saved || { greeters: ["World"] }) && + this._notify() + ); + } + + async _notify() { + await saveState(this.state); + for (const cb of this.listeners) cb(this.state); + } +} +``` + +๐Ÿ’ก Tip: because IndexedDB operations are async, wrap state changes in `async` methods and adjust your tests with `await`. + +--- + +## ๐Ÿง  Step 5 โ€” Testing IndexedDB logic + +In Node environments, you can simulate IndexedDB with `fake-indexeddb`: + +```bash +npm install --save-dev fake-indexeddb +``` + +Then in your test setup: + +```javascript +import "fake-indexeddb/auto"; +``` + +Your tests now work without a browser. + +--- + +## โœ… Outcome + +Your TestDrive-UI environment now supports: + +| Persistence Layer | Use Case | Pros | +| ----------------- | ------------------------------------- | ------------------ | +| **localStorage** | Small, synchronous, quick persistence | Simple & immediate | +| **IndexedDB** | Large, structured, async persistence | Scalable & robust | + +--- + +## ๐Ÿ” Reflection + +Persistence transforms your store into a **long-term memory layer**. +With this, TestDrive-UI crosses into **progressive-web-app territory** โ€” offline-ready, recoverable, and reliable. + +--- + +Continue to Tutorial 8 on Agent Automation. + +xxx diff --git a/tutorials/Tutorial 8 Agent Driven Tdd Automation.md b/tutorials/Tutorial 8 Agent Driven Tdd Automation.md index d4c672e..ee39f19 100644 --- a/tutorials/Tutorial 8 Agent Driven Tdd Automation.md +++ b/tutorials/Tutorial 8 Agent Driven Tdd Automation.md @@ -1,242 +1,242 @@ -# ๐Ÿค– Tutorial 8 โ€” Agent-Driven TDD Automation - -This tutorial focuses on *Agent-Driven TDD Automation* โ€” turning TestDrive-UI into a living collaboration space between human developers and coding agents. - -### โ€œCoding with Companionsโ€ โ€” Using AI Agents for Continuous Improvement - ---- - -## ๐ŸŽฏ Goal - -Integrate LLM-based coding agents into your TestDrive-UI workflow so they can: - -1. **Generate new tests** from natural-language requirements. -2. **Run tests and detect regressions** automatically. -3. **Propose targeted refactorings or patches** when failures occur. - -By combining deterministic testing with creative reasoning, you build a feedback loop that never stops improving your code. - ---- - -## ๐Ÿงฉ Concept Overview - -Traditional TDD cycle: - -``` -requirement โ†’ test โ†’ fail โ†’ code โ†’ pass โ†’ refactor -``` - -Agent-Driven TDD cycle: - -``` -requirement โ†’ agent generates test โ†’ run โ†’ fail โ†’ -agent proposes fix โ†’ run โ†’ pass โ†’ review โ†’ merge -``` - -You remain the architect and reviewer โ€” the agent acts as an automated junior developer executing fast, repeatable loops. - ---- - -## ๐Ÿงช Step 1 โ€” Define a Machine-Readable Requirement - -Create a simple JSON file to store behavioral specifications. - -`specs/hello-world.name-change.json` - -```json -{ - "component": "hello-world", - "feature": "name-change", - "description": "Greeting text should update when the user types a new name.", - "expected_behavior": [ - "Typing in the input field updates the displayed greeting immediately.", - "The component property 'name' reflects the latest input value." - ] -} -``` - -Agents use these files as prompts to generate corresponding tests. - ---- - -## โš™๏ธ Step 2 โ€” Agent Generates a Test - -An agent reads the JSON spec and produces Mocha tests automatically. - -Example output from an LLM agent: - -`generated-tests/hello-world.name-change.test.js` - -```javascript -import "./hello-world.js"; - -describe(" (auto-generated name-change)", () => { - it("updates greeting text when user types", 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!"); - }); -}); -``` - -Run your full suite: - -```bash -npm test -``` - -If the test fails, the agent analyzes output and proposes a minimal fix for `hello-world.js`. - ---- - -## ๐Ÿ” Step 3 โ€” Detect Regressions - -Agents monitor test results over time by parsing Mochaโ€™s machine-readable report (`--reporter json`). - -Example agent logic (pseudocode): - -```python -def analyze_report(report): - failed = [t for t in report['tests'] if t['err']] - if not failed: - return "All green!" - for f in failed: - print(f"Regression in {f['title']}: {f['err']['message']}") - propose_fix(f) -``` - -This analysis lets agents flag new failures, auto-create issues, or propose PRs. - ---- - -## ๐Ÿง  Step 4 โ€” Agent Proposes Refactorings - -Once tests are green, agents can examine code for quality signals: - -| Heuristic | Example Action | -| --------------------- | ------------------------------------------ | -| Duplicate logic | Extract shared helper or controller | -| Excessive DOM queries | Cache references or use ReactiveController | -| Long methods | Suggest method splitting | -| Repeated strings | Propose localization constants | - -Example prompt to your agent: - -``` -โ€œReview src/components/hello-world.js and propose a refactor -that reduces duplication without breaking current tests.โ€ -``` - -The agent runs tests after each change to validate its proposal. - ---- - -## ๐Ÿ” Step 5 โ€” Automate the Loop - -Create a simple Node script to chain the whole process. - -`scripts/agent-tdd.js` - -```javascript -import { execSync } from "child_process"; -import fs from "fs"; -import OpenAI from "openai"; // or another LLM SDK - -const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); - -function runTests() { - try { - const output = execSync("npx mocha --reporter json", { encoding: "utf-8" }); - return JSON.parse(output); - } catch (e) { - return JSON.parse(e.stdout); - } -} - -async function main() { - const report = runTests(); - const failed = report.tests.filter(t => t.err.message); - if (failed.length === 0) return console.log("โœ… All tests passed"); - - const prompt = `Some tests failed:\n${JSON.stringify(failed, null, 2)}\n -Propose minimal code changes to fix them.`; - const completion = await client.responses.create({ model: "gpt-5", input: prompt }); - fs.writeFileSync("agent-proposal.txt", completion.output_text); - console.log("๐Ÿ’ก Agent proposal written to agent-proposal.txt"); -} - -main(); -``` - -This script connects your tests with an AI assistant that learns and suggests fixes continuously. - ---- - -## ๐Ÿงฐ Step 6 โ€” Integrate into CI - -Add a CI job (e.g., GitHub Actions or local cron) to run the agent loop daily or on push. - -Example workflow: - -``` -on: - push: - schedule: - - cron: '0 2 * * *' -jobs: - agent-tdd: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: npm ci - - run: node scripts/agent-tdd.js -``` - -Now your project self-tests and self-critiques even when youโ€™re offline. - ---- - -## ๐Ÿงฉ Step 7 โ€” Visualize Agent Progress - -Agents can log progress into a dashboard component (``) showing: - -* Number of tests generated. -* Pass/fail trend over time. -* Proposed vs. accepted refactors. - -Itโ€™s your window into the machineโ€™s learning curve. - ---- - -## โœ… Outcome - -You now have a self-reinforcing loop: - -1. Humans write specs. -2. Agents create tests and code. -3. The suite proves stability. -4. Agents refactor and review under guard of tests. - -This combines the discipline of TDD with the creativity and endurance of AI. - ---- - -## ๐Ÿ” Next Steps - -* Add **semantic diff filters** so agents learn from accepted patches. -* Train agents to cluster tests into *feature domains* for smarter coverage analysis. -* Integrate Storybook snapshots for visual regression detection. -* Build a CLI (`npx agent-tdd`) to run and audit your AI test loops interactively. - ---- - -Congratulations! You finished all tutorials and should be fine going Forward Building components. - -Feel free to tell us which additional tutorials we should provide. - +# ๐Ÿค– Tutorial 8 โ€” Agent-Driven TDD Automation + +This tutorial focuses on *Agent-Driven TDD Automation* โ€” turning TestDrive-UI into a living collaboration space between human developers and coding agents. + +### โ€œCoding with Companionsโ€ โ€” Using AI Agents for Continuous Improvement + +--- + +## ๐ŸŽฏ Goal + +Integrate LLM-based coding agents into your TestDrive-UI workflow so they can: + +1. **Generate new tests** from natural-language requirements. +2. **Run tests and detect regressions** automatically. +3. **Propose targeted refactorings or patches** when failures occur. + +By combining deterministic testing with creative reasoning, you build a feedback loop that never stops improving your code. + +--- + +## ๐Ÿงฉ Concept Overview + +Traditional TDD cycle: + +``` +requirement โ†’ test โ†’ fail โ†’ code โ†’ pass โ†’ refactor +``` + +Agent-Driven TDD cycle: + +``` +requirement โ†’ agent generates test โ†’ run โ†’ fail โ†’ +agent proposes fix โ†’ run โ†’ pass โ†’ review โ†’ merge +``` + +You remain the architect and reviewer โ€” the agent acts as an automated junior developer executing fast, repeatable loops. + +--- + +## ๐Ÿงช Step 1 โ€” Define a Machine-Readable Requirement + +Create a simple JSON file to store behavioral specifications. + +`specs/hello-world.name-change.json` + +```json +{ + "component": "hello-world", + "feature": "name-change", + "description": "Greeting text should update when the user types a new name.", + "expected_behavior": [ + "Typing in the input field updates the displayed greeting immediately.", + "The component property 'name' reflects the latest input value." + ] +} +``` + +Agents use these files as prompts to generate corresponding tests. + +--- + +## โš™๏ธ Step 2 โ€” Agent Generates a Test + +An agent reads the JSON spec and produces Mocha tests automatically. + +Example output from an LLM agent: + +`generated-tests/hello-world.name-change.test.js` + +```javascript +import "./hello-world.js"; + +describe(" (auto-generated name-change)", () => { + it("updates greeting text when user types", 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!"); + }); +}); +``` + +Run your full suite: + +```bash +npm test +``` + +If the test fails, the agent analyzes output and proposes a minimal fix for `hello-world.js`. + +--- + +## ๐Ÿ” Step 3 โ€” Detect Regressions + +Agents monitor test results over time by parsing Mochaโ€™s machine-readable report (`--reporter json`). + +Example agent logic (pseudocode): + +```python +def analyze_report(report): + failed = [t for t in report['tests'] if t['err']] + if not failed: + return "All green!" + for f in failed: + print(f"Regression in {f['title']}: {f['err']['message']}") + propose_fix(f) +``` + +This analysis lets agents flag new failures, auto-create issues, or propose PRs. + +--- + +## ๐Ÿง  Step 4 โ€” Agent Proposes Refactorings + +Once tests are green, agents can examine code for quality signals: + +| Heuristic | Example Action | +| --------------------- | ------------------------------------------ | +| Duplicate logic | Extract shared helper or controller | +| Excessive DOM queries | Cache references or use ReactiveController | +| Long methods | Suggest method splitting | +| Repeated strings | Propose localization constants | + +Example prompt to your agent: + +``` +โ€œReview src/components/hello-world.js and propose a refactor +that reduces duplication without breaking current tests.โ€ +``` + +The agent runs tests after each change to validate its proposal. + +--- + +## ๐Ÿ” Step 5 โ€” Automate the Loop + +Create a simple Node script to chain the whole process. + +`scripts/agent-tdd.js` + +```javascript +import { execSync } from "child_process"; +import fs from "fs"; +import OpenAI from "openai"; // or another LLM SDK + +const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); + +function runTests() { + try { + const output = execSync("npx mocha --reporter json", { encoding: "utf-8" }); + return JSON.parse(output); + } catch (e) { + return JSON.parse(e.stdout); + } +} + +async function main() { + const report = runTests(); + const failed = report.tests.filter(t => t.err.message); + if (failed.length === 0) return console.log("โœ… All tests passed"); + + const prompt = `Some tests failed:\n${JSON.stringify(failed, null, 2)}\n +Propose minimal code changes to fix them.`; + const completion = await client.responses.create({ model: "gpt-5", input: prompt }); + fs.writeFileSync("agent-proposal.txt", completion.output_text); + console.log("๐Ÿ’ก Agent proposal written to agent-proposal.txt"); +} + +main(); +``` + +This script connects your tests with an AI assistant that learns and suggests fixes continuously. + +--- + +## ๐Ÿงฐ Step 6 โ€” Integrate into CI + +Add a CI job (e.g., GitHub Actions or local cron) to run the agent loop daily or on push. + +Example workflow: + +``` +on: + push: + schedule: + - cron: '0 2 * * *' +jobs: + agent-tdd: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: npm ci + - run: node scripts/agent-tdd.js +``` + +Now your project self-tests and self-critiques even when youโ€™re offline. + +--- + +## ๐Ÿงฉ Step 7 โ€” Visualize Agent Progress + +Agents can log progress into a dashboard component (``) showing: + +* Number of tests generated. +* Pass/fail trend over time. +* Proposed vs. accepted refactors. + +Itโ€™s your window into the machineโ€™s learning curve. + +--- + +## โœ… Outcome + +You now have a self-reinforcing loop: + +1. Humans write specs. +2. Agents create tests and code. +3. The suite proves stability. +4. Agents refactor and review under guard of tests. + +This combines the discipline of TDD with the creativity and endurance of AI. + +--- + +## ๐Ÿ” Next Steps + +* Add **semantic diff filters** so agents learn from accepted patches. +* Train agents to cluster tests into *feature domains* for smarter coverage analysis. +* Integrate Storybook snapshots for visual regression detection. +* Build a CLI (`npx agent-tdd`) to run and audit your AI test loops interactively. + +--- + +Congratulations! You finished all tutorials and should be fine going Forward Building components. + +Feel free to tell us which additional tutorials we should provide. + xxx \ No newline at end of file