TDD Exercise: Fix the Counter Component
Scenario Description
You've inherited a counter Svelte component. The tests are written but the implementation has bugs. You need to fix these issues using TDD methodology.
Requirements Specification
- Counter starts at 0
- Clicking "+" button increments count by 1
- Clicking "-" button decrements count by 1
- Clicking "Reset" button sets count to 0
- Counter has boundaries: minimum -10, maximum 10
Initial Code
Counter Component
<!-- Counter.svelte -->
<script>
let count = 0;
function increment() {
count++;
}
function decrement() {
count--;
}
function reset() {
count = 0;
}
</script>
<div class="counter">
<span class="count">{count}</span>
<button on:click={decrement}>-</button>
<button on:click={increment}>+</button>
<button on:click={reset}>Reset</button>
</div>Test File
// Counter.test.js
import { render, fireEvent } from '@testing-library/svelte';
import Counter from './Counter.svelte';
describe('Counter', () => {
test('initial value should be 0', () => {
const { getByText } = render(Counter);
expect(getByText('0')).toBeInTheDocument();
});
test('clicking + button should increment count', async () => {
const { getByText } = render(Counter);
await fireEvent.click(getByText('+'));
expect(getByText('1')).toBeInTheDocument();
});
test('clicking - button should decrement count', async () => {
const { getByText } = render(Counter);
await fireEvent.click(getByText('-'));
expect(getByText('-1')).toBeInTheDocument();
});
test('count should not go below -10', async () => {
const { getByText } = render(Counter);
// Click 12 times
for (let i = 0; i < 12; i++) {
await fireEvent.click(getByText('-'));
}
expect(getByText('-10')).toBeInTheDocument();
});
test('count should not exceed 10', async () => {
const { getByText } = render(Counter);
for (let i = 0; i < 12; i++) {
await fireEvent.click(getByText('+'));
}
expect(getByText('10')).toBeInTheDocument();
});
test('reset button should set count to zero', async () => {
const { getByText } = render(Counter);
await fireEvent.click(getByText('+'));
await fireEvent.click(getByText('+'));
await fireEvent.click(getByText('Reset'));
expect(getByText('0')).toBeInTheDocument();
});
});Your Task
- Run tests, observe which tests fail
- Analyze the problem, understand why they fail
- Fix using TDD:
- Confirm failing tests (RED)
- Write minimal code to pass (GREEN)
- Refactor code (REFACTOR)
- Ensure all tests pass
TDD Process Guide
Step 1: RED (Observe Failure)
npm testObserve output:
- Which tests fail?
- What are the failure reasons?
Expected result: Boundary tests fail (count exceeds -10 and 10 limits)
Step 2: GREEN (Make It Pass)
Follow TDD principles:
- Only modify the minimal code needed to pass tests
- Don't over-engineer
- Don't add features not required by tests
Step 3: REFACTOR (Clean Up)
After tests pass:
- Is the code clear?
- Is there duplication?
- Does it follow project conventions?
Hints
Click to expand hints
Hint 1: Boundary Checking
Tests require upper and lower limits:
- Minimum: -10
- Maximum: 10
Current code has no boundary checks.
Hint 2: increment Function
function increment() {
if (count < 10) { // Add upper limit check
count++;
}
}Hint 3: decrement Function
function decrement() {
if (count > -10) { // Add lower limit check
count--;
}
}Hint 4: Don't Modify Tests
Core TDD principle: Tests are specifications. If tests are correct, fix the code not the tests.
Reference Solution
Click to expand reference solution
<!-- Counter.svelte -->
<script>
let count = 0;
const MIN = -10;
const MAX = 10;
function increment() {
if (count < MAX) {
count++;
}
}
function decrement() {
if (count > MIN) {
count--;
}
}
function reset() {
count = 0;
}
</script>
<div class="counter">
<span class="count">{count}</span>
<button on:click={decrement}>-</button>
<button on:click={increment}>+</button>
<button on:click={reset}>Reset</button>
</div>
<style>
.counter {
display: flex;
align-items: center;
gap: 0.5rem;
}
.count {
font-size: 1.5rem;
min-width: 2rem;
text-align: center;
}
button {
padding: 0.25rem 0.5rem;
}
</style>Key Changes
- Add constants:
MINandMAXdefine boundaries - Boundary checks:
incrementanddecrementcheck boundaries - Keep it simple: Only add features required by tests, no over-engineering
Key Learning Points
1. RED-GREEN-REFACTOR Cycle
┌───────┐ ┌───────┐
│ RED │────►│ GREEN │
│(fail) │ │(pass) │
└───────┘ └───┬───┘
▲ │
│ ┌────▼────┐
│ │REFACTOR │
└────────│ │
└─────────┘2. Tests as Specifications
Tests aren't just verification tools—they're behavior specifications. By reading tests, you should understand complete component behavior.
3. Minimal Change Principle
TDD emphasizes writing only the minimum code to pass tests. This avoids over-engineering and unnecessary complexity.
4. Don't Modify Passing Tests
If tests are reasonable but code doesn't pass, fix the code not the tests.
Common Mistakes
| Mistake | Correct Approach |
|---|---|
| Write code before tests | Write tests first, observe failure, then write code |
| Modify tests to make them pass | Modify code to satisfy tests |
| Add features not required by tests | Only implement behavior required by tests |
| Skip RED phase | Must see tests fail before writing code |
Advanced Exercises
After completing the basic exercise, try:
- Add step size: Allow users to set increment/decrement step value
- Display status: Show warning when boundary is reached
- Keyboard support: Support arrow key controls
Related Skills
- test-driven-development - Core TDD skill
- verification-before-completion - Verification before completion
- systematic-debugging - Systematic debugging