generated from coulomb/repo-seed
chore: Fixed line endings i tutorials and provided Introduction
This commit is contained in:
@@ -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 `<hello-world name="Kale"></hello-world>` 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("<hello-world> (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`<div @click=${this._onClick}>
|
||||
Hello, ${this.name}!
|
||||
</div>`;
|
||||
}
|
||||
|
||||
_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
|
||||
<hello-world></hello-world>
|
||||
<hello-world name="Coulomb"></hello-world>
|
||||
```
|
||||
|
||||
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 = () => `<hello-world></hello-world>`;
|
||||
export const CustomName = () => `<hello-world name="Bernd"></hello-world>`;
|
||||
```
|
||||
|
||||
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 `<hello-world>` 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 `<hello-world name="Kale"></hello-world>` 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("<hello-world> (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`<div @click=${this._onClick}>
|
||||
Hello, ${this.name}!
|
||||
</div>`;
|
||||
}
|
||||
|
||||
_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
|
||||
<hello-world></hello-world>
|
||||
<hello-world name="Coulomb"></hello-world>
|
||||
```
|
||||
|
||||
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 = () => `<hello-world></hello-world>`;
|
||||
export const CustomName = () => `<hello-world name="Bernd"></hello-world>`;
|
||||
```
|
||||
|
||||
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 `<hello-world>` that updates the `name` property live when the user types.
|
||||
|
||||
xxx
|
||||
Reference in New Issue
Block a user