4.7 KiB
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
nameproperty 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:
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:
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:
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:
npm test
✅ All should now pass.
⚡ 4. Step 3 — Try it live
Edit src/index.html to demonstrate both variants:
<hello-world></hello-world>
<hello-world name="Coulomb"></hello-world>
Then:
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:
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
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 thenameproperty live when the user types.
xxx