diff --git a/tutorials/Additional Tutorial Suggestions.md b/tutorials/Additional Tutorial Suggestions.md new file mode 100644 index 0000000..cfe764d --- /dev/null +++ b/tutorials/Additional Tutorial Suggestions.md @@ -0,0 +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 diff --git a/tutorials/Tutorial 1 Hello World.md b/tutorials/Tutorial 1 Hello World.md new file mode 100644 index 0000000..5fbcbfb --- /dev/null +++ b/tutorials/Tutorial 1 Hello World.md @@ -0,0 +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 diff --git a/tutorials/Tutorial 2 Reactive Properties.md b/tutorials/Tutorial 2 Reactive Properties.md new file mode 100644 index 0000000..e82179a --- /dev/null +++ b/tutorials/Tutorial 2 Reactive Properties.md @@ -0,0 +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. + +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 new file mode 100644 index 0000000..a221a93 --- /dev/null +++ b/tutorials/Tutorial 3 Bidirectional Data Binding.md @@ -0,0 +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. + +xxx \ No newline at end of file diff --git a/tutorials/Tutorial 4 Component Composition.md b/tutorials/Tutorial 4 Component Composition.md new file mode 100644 index 0000000..d1b12e2 --- /dev/null +++ b/tutorials/Tutorial 4 Component Composition.md @@ -0,0 +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 diff --git a/tutorials/Tutorial 5 Component Communication.md b/tutorials/Tutorial 5 Component Communication.md new file mode 100644 index 0000000..5247e96 --- /dev/null +++ b/tutorials/Tutorial 5 Component Communication.md @@ -0,0 +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? + +xxx \ No newline at end of file diff --git a/tutorials/Tutorial 6 Shared State.md b/tutorials/Tutorial 6 Shared State.md new file mode 100644 index 0000000..a83c9eb --- /dev/null +++ b/tutorials/Tutorial 6 Shared State.md @@ -0,0 +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 diff --git a/tutorials/Tutorial 7 Persistence Patterns.md b/tutorials/Tutorial 7 Persistence Patterns.md new file mode 100644 index 0000000..13aae79 --- /dev/null +++ b/tutorials/Tutorial 7 Persistence Patterns.md @@ -0,0 +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 diff --git a/tutorials/Tutorial 8 Agent Driven Tdd Automation.md b/tutorials/Tutorial 8 Agent Driven Tdd Automation.md new file mode 100644 index 0000000..d4c672e --- /dev/null +++ b/tutorials/Tutorial 8 Agent Driven Tdd Automation.md @@ -0,0 +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. + +xxx \ No newline at end of file