ai-tools/test-driven-development/SKILL.md
Enforce strict Test-Driven Development workflow: write one test, make it pass, verify, then proceed. Prevents over-implementation and ensures code matches requirements exactly. Use when implementing new features, adding settings, or building functionality incrementally.
npx skillsauth add randyhaylor/enhanceclaude test-driven-developmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill when the user explicitly requests:
NEVER write implementation code before writing a test for it.
The TDD cycle is:
# WRONG: Adding multiple tests at once
it('should load panel1Text', () => { ... })
it('should load panel2Text', () => { ... })
it('should load panel3Text', () => { ... })
# Then implementing all at once
# RIGHT: One test, verify, next test
it('should load panel1Text', () => { ... })
# Run test → passes
# THEN add next test
it('should load panel2Text', () => { ... })
# Run test → passes
# THEN add next test
// WRONG: Implementation first
const settings = {
panel1Text: "Welcome",
panel2Text: "I'm Theia",
panel3Text: "Your partner"
}
// Then write tests
// RIGHT: Test first
it('should have panel1Text', () => {
expect(settings).toHaveProperty('panel1Text')
expect(settings.panel1Text).toBe('Welcome')
})
// Test fails → add ONLY panel1Text → test passes
Every test must be run and confirmed passing before writing the next test:
# After writing each test:
bun test your-test-file.test.ts
# Verify output shows:
✓ should have panel1Text
# 1 pass, 0 fail
# ONLY THEN proceed to next test
Write the absolute minimum code to make the current test pass:
// Test: should have panel1Text with value "Welcome"
// WRONG: Adding everything at once
{
"panel1Text": "Welcome",
"panel2Text": "I'm Theia",
"panel3Text": "Your partner"
}
// RIGHT: Only what's needed for current test
{
"panel1Text": "Welcome"
}
CRITICAL RULE: Mocked processes with timers shall always add to a cumulative maxPotentialDelayTime so the assert timeout will always be sufficient because it will be based on that plus its own buffer.
When testing code that uses timers (setTimeout, setInterval, debounce):
// WRONG: Guessing how long to wait
setTimeout(async () => {
await operation1() // Unknown duration
await operation2() // Unknown duration
}, 1000)
await vi.advanceTimersByTimeAsync(2000) // Random guess
expect(mock).toHaveBeenCalled() // Might fail
// RIGHT: Track cumulative potential delay
let maxPotentialDelayTime = 0
// Timer adds 1000ms
maxPotentialDelayTime += 1000
// Mock operations add their durations
maxPotentialDelayTime += mockOperation1Duration // e.g., 50ms
maxPotentialDelayTime += mockOperation2Duration // e.g., 100ms
// Advance timers by tracked total + buffer
await vi.advanceTimersByTimeAsync(maxPotentialDelayTime + 100)
expect(mock).toHaveBeenCalled() // Will always succeed
describe('async operation', () => {
beforeEach(() => {
vi.useFakeTimers()
})
it('completes within tracked time', async () => {
// Define all mock process timers at start
const mockProcessTimers = [
1000, // DEBOUNCE_DELAY
10, // MOCK_GET_DELAY
10, // MOCK_UPLOAD_DELAY
10, // MOCK_IMPORT_DELAY
10 // MOCK_SAVE_DELAY
]
// Define buffer for assertion safety
const assertDelayBuffer = 100
// Calculate total delay needed
const assertDelay = mockProcessTimers.reduce((sum, delay) => sum + delay, 0) + assertDelayBuffer
// Trigger operation
triggerDebouncedOperation()
// Wait for calculated delay
await vi.advanceTimersByTimeAsync(assertDelay)
expect(mockOperation).toHaveBeenCalled()
})
})
Without time tracking:
With time tracking:
Never use arbitrary timeouts like 2000ms without justification. Always track cumulative delays.
CRITICAL: Avoid dynamic imports in code paths that create timers when using fake timers for testing.
Generic Problem:
When code has an async operation (dynamic import, network call, etc.) that creates a timer AFTER the async work completes, fake timer APIs like runAllTimersAsync() will check for timers BEFORE the async operation completes, find none, and return immediately. The timer is created later and never executes.
Rule: If your code uses fake timers for testing, do not use dynamic imports (await import()) in the code path before creating the timer.
When a test fails unexpectedly, you MUST follow this procedure:
featureProofTest.test.ts) to prove each dependency worksResult: First failing proof test shows exactly what's broken. All passing = real test has environment/mocking issue.
Do NOT:
User Request: "Add three panel text settings to the config file"
// baseline-test-settings.test.ts
it('should have panel1Text', () => {
expect(settings.panel1Text).toBe('Welcome')
})
Run test:
bun test baseline-test-settings.test.ts
# ✗ Test fails - property doesn't exist
// baseline-test-settings.json
{
"panel1Text": "Welcome"
}
bun test baseline-test-settings.test.ts
# ✓ 1 pass
STOP. Report success. Ask if user wants to proceed to next test.
it('should have panel2Text', () => {
expect(settings.panel2Text).toBe("I'm Theia")
})
Run test → Fails → Add only panel2Text → Test passes → Report → Repeat
FUNDAMENTAL PRINCIPLE: Tests should verify that code works correctly, not that configuration has specific values.
// CORRECT: Test that property exists and has right type
it('should have timerVisible setting', () => {
expect(settings).toHaveProperty('timerVisible')
expect(typeof settings.timerVisible).toBe('boolean')
})
// CORRECT: Test that numeric property exists
it('should have questionTimeout setting', () => {
expect(settings).toHaveProperty('questionTimeout')
expect(typeof settings.questionTimeout).toBe('number')
})
// WRONG: Testing specific config value
it('should have timerVisible setting', () => {
expect(settings.timerVisible).toBe(false) // ❌ BAD
})
// Problem: If user changes config to true, test fails
// But the loading functionality still works!
// WRONG: Testing specific text content
it('should have welcome text', () => {
expect(settings.welcomeText).toBe('Welcome') // ❌ BAD
})
// Problem: If user changes text to "Hello", test fails
// But the loading functionality still works!
Configuration data changes frequently:
Functionality rarely changes:
If changing a config value breaks tests, your tests are wrong.
Only test specific values when verifying integration between systems:
// ACCEPTABLE in integration test:
it('should calculate timeout correctly', () => {
const timeout = calculateTimeout(settings.baseTime, settings.multiplier)
expect(timeout).toBe(100) // Testing calculation, not config
})
// WRONG: Adding all settings at once
{
"panel1Text": "Welcome",
"panel2Text": "I'm Theia",
"panel3Text": "Your partner",
"fadeInTime": 0.8,
"delayTime": 2.2
}
// Then writing tests afterward
// WRONG: All tests written first
describe('settings', () => {
it('should have panel1Text', () => { ... })
it('should have panel2Text', () => { ... })
it('should have panel3Text', () => { ... })
})
// Then implementing everything
// Add test
it('should have panel1Text', () => { ... })
// Add implementation
{ "panel1Text": "Welcome" }
// WRONG: Immediately moving to next test without running current test
it('should have panel2Text', () => { ... }) // DON'T DO THIS YET
User: "Add panel1Text, panel2Text, panel3Text to the config"
Response:
TDD Step 1: Adding panel1Text
1. Write test:
it('should have panel1Text', () => {
expect(settings.panel1Text).toBe('Welcome')
})
2. Run test:
bun test → FAILS (expected)
3. Add implementation:
{ "panel1Text": "Welcome" }
4. Run test:
bun test → PASSES ✓
✓ panel1Text complete. Ready for panel2Text.
Wait for user confirmation before proceeding to panel2Text.
Prevents Over-Implementation
Ensures Requirements Match Code
Immediate Feedback
Forces Incremental Progress
TDD aligns perfectly with solid-developer rules:
Tests make requirements explicit - no room for inference:
// Test EXPLICITLY states requirement
it('should have panel1Text with value "Welcome"', () => {
expect(settings.panel1Text).toBe('Welcome')
})
// Can only implement exactly this
If test requirements are ambiguous, STOP and ask:
User: "Add panel text settings"
STOP - Ask before writing test:
"How many panel text settings? What are their names and values?"
Tests prevent adding "helpful" extras:
// Test only requires panel1Text
it('should have panel1Text', () => { ... })
// WRONG: Adding more than tested
{
"panel1Text": "Welcome",
"panel2Text": "I'm Theia" // NOT TESTED YET
}
// RIGHT: Only what test requires
{
"panel1Text": "Welcome"
}
Don't use strict TDD for:
Before proceeding to next test, verify:
Q: How small should each test be? A: Test ONE property, ONE behavior, ONE requirement at a time.
Q: What if multiple tests would pass with the same implementation? A: Still write them one at a time. Different tests document different requirements.
Q: Should I run all tests or just the current one? A: Run all tests to ensure no regressions, but focus verification on the new test.
Q: What if the user wants me to "implement everything"? A: Still use TDD - implement everything, but one test at a time, verifying each.
User: "Add three timing settings to config: fadeIn, delay, and pause"
Claude: "TDD Step 1: Adding fadeIn setting
1. Writing test for fadeIn:
it('should have fadeIn timing', () => {
expect(settings.timing).toHaveProperty('fadeIn')
expect(typeof settings.timing.fadeIn).toBe('number')
})
2. Running test:
bun test settings.test.ts
✗ FAIL - property doesn't exist
3. Adding implementation:
{
"timing": {
"fadeIn": 0.8
}
}
4. Running test:
bun test settings.test.ts
✓ PASS
fadeIn setting complete. Ready to proceed to 'delay' setting."
User: "yes proceed"
Claude: "TDD Step 2: Adding delay setting
1. Writing test for delay:
it('should have delay timing', () => {
expect(settings.timing).toHaveProperty('delay')
expect(typeof settings.timing.delay).toBe('number')
})
2. Running test:
bun test settings.test.ts
✗ FAIL - delay property doesn't exist
3. Adding implementation:
{
"timing": {
"fadeIn": 0.8,
"delay": 2.2
}
}
4. Running test:
bun test settings.test.ts
✓ PASS (2 tests passing)
delay setting complete. Ready to proceed to 'pause' setting."
[Continues for pause...]
TDD ensures:
Use TDD when the user wants:
The TDD mantra: Red → Green → Next
tools
# XState v5 Quick Reference ## How to Look Up API Details For complete function signatures, types, and interfaces, **grep `api-reference.md`** — do NOT read it in full (12k+ lines). Example: ``` Grep pattern="createActor" path="~/.claude/skills/xstate/api-reference.md" output_mode="content" -C 5 ``` Then use `Read` with `offset`/`limit` to get the full section. This is the primary way to get precise technical info when the quick reference below isn't enough. ## Design Workflow Recommended
tools
Workaround for agent teams in VS Code extension where TeamCreate teammates cannot execute tools. Uses an echo-back-and-resume pattern where agents return tool requests instead of executing them directly.
development
Format documentation, READMEs, and structured text using header hierarchy where each level stands alone. Use when creating docs, research notes, summaries, or when user requests 'scannable,' 'well-structured,' 'skimmable,' or 'readable at multiple depths' output. Applies to markdown, technical specs, and any hierarchical text formatting.
development
Core principles for writing clean, disciplined Vue 3 apps. Invoke when reviewing architecture, adding features, or evaluating component design.