# Nuxt UI Tdd

> Guide for building Vue 3 components with NuxtUI using strict Test-Driven Development (TDD) methodology enforced by a TDD guard hook. Use this skill when implementing new UI components (atoms, molecules, organisms) for the Poche project, creating Storybook stories with interaction tests, or working within the RED-GREEN-REFACTOR cycle. Particularly useful when the user mentions "TDD", "test-first", "create a component", "build a component", "implement [ComponentName]", or when adding UI functionality.

- Skill: `majiayu000/nuxt-ui-tdd` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/nuxt-ui-tdd`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/nuxt-ui-tdd/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/nuxt-ui-tdd

---


# NuxtUI TDD Component Development

## Overview

Build Vue 3 components with NuxtUI following strict Test-Driven Development (TDD) methodology. This skill provides workflows, templates, and guardrails for creating well-tested, reusable UI components using the Atomic Design pattern.

## When to Use This Skill

Use this skill when:
- Building new Vue components (atoms, molecules, organisms)
- Creating components that wrap NuxtUI base components (UButton, UInput, UFormField, etc.)
- Working with Storybook interaction tests
- Following TDD methodology with the TDD guard hook
- User requests involve "implement [ComponentName]", "create a component", "build with TDD"

## Core Workflow

### 1. Determine Component Level

Before starting, identify the atomic design level. Consult `references/naming-conventions.md` for detailed guidance.

**Quick Reference**:
- **Atoms**: Single UI elements (Button, Input, Label, Icon)
- **Molecules**: 2-3 atoms combined (FormField, SearchBar, ArticleCard)
- **Organisms**: Complex sections (ArticleList, Navigation, ReaderView)
- **Templates**: Page layouts (DefaultLayout, AuthLayout)

**Naming Patterns**:
- Atoms: `{Element}.vue` (e.g., Button.vue)
- Molecules: `{Concept}{Type}.vue` (e.g., FormField.vue, SearchBar.vue)
- Organisms: `{Feature}{Component}.vue` (e.g., ArticleList.vue)

### 2. Create the Test File First

**Location**: Same directory as component
**Naming**: `{ComponentName}.stories.ts`

Use the template from `assets/story-template.ts` as a starting point.

**Critical TDD Rules**:
- Create ONLY ONE test/story initially
- Use the "Default" story for basic rendering
- Include ONE focused assertion per story
- Run test to confirm it fails (RED phase)

**Example First Test**:
```typescript
import type { Meta, StoryObj } from '@storybook/vue3';
import { expect, within } from '@storybook/test';
import SearchBar from './SearchBar.vue';

const meta = {
  title: 'Molecules/SearchBar',
  component: SearchBar,
  tags: ['autodocs'],
  argTypes: {
    placeholder: {
      control: 'text',
      description: 'Placeholder text',
    },
  },
} satisfies Meta<typeof SearchBar>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
  args: {
    placeholder: 'Search articles...',
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const input = await canvas.findByPlaceholderText(/search articles/i);
    await expect(input).toBeInTheDocument();
  },
};
```

### 3. Capture RED Phase Evidence

Run Storybook test-runner to verify the test fails:

```bash
pnpm test:storybook:run -- {ComponentName}
```

Save the failure output:
```bash
pnpm test:storybook:run -- SearchBar | tee /tmp/searchbar-red-phase.txt
```

**Expected**: Test fails because component doesn't exist.

### 4. Update test.json for TDD Guard

Manually update `.claude/tdd-guard/data/test.json` to record the RED phase:

```json
{
  "verificationMode": false,
  "batchMode": false,
  "testModules": [
    {
      "moduleId": "/Users/tk/Code/poche/apps/web/components/molecules/SearchBar.stories.ts",
      "tests": [
        {
          "name": "Default",
          "fullName": "Molecules/SearchBar › Default",
          "state": "failed",
          "note": "RED phase - component doesn't exist"
        }
      ]
    }
  ],
  "redPhaseEvidence": {
    "completed": true,
    "command": "pnpm test:storybook -- SearchBar",
    "exitCode": 1,
    "timestamp": "2025-11-02T13:52:00.000Z",
    "testSuitesFailed": 1,
    "failureCount": 1,
    "totalTests": 1,
    "sampleErrors": [
      "FAIL browser: chromium components/molecules/SearchBar.stories.ts",
      "Failed to fetch dynamically imported module"
    ]
  },
  "unhandledErrors": [],
  "reason": "failed"
}
```

This step is CRITICAL - the TDD guard will block implementation without RED phase evidence.

### 5. Implement Minimal Component

**Location**: `apps/web/components/{level}/{ComponentName}.vue`

Use `assets/component-template.vue` as a starting point.

**Critical Implementation Rules**:
- Write ONLY the code needed to pass the current test
- Do NOT add untested props or functionality
- Wrap appropriate NuxtUI base components
- Keep it simple and minimal

**Example Minimal Implementation**:
```vue
<script setup lang="ts">
defineProps<{
  placeholder?: string;
}>();
</script>

<template>
  <UInput
    type="search"
    :placeholder="placeholder"
    leading-icon="i-lucide-search"
  />
</template>
```

### 6. Verify GREEN Phase

Run tests again to confirm they pass:

```bash
pnpm test:storybook:run -- {ComponentName}
```

**Expected**: Tests pass (GREEN phase).

### 7. Update test.json for GREEN Phase

Update test state to "passed":

```json
{
  "tests": [
    {
      "name": "Default",
      "state": "passed",
      "note": "GREEN phase - basic SearchBar with search icon"
    }
  ]
}
```

### 8. Repeat for Next Test

Add the NEXT test ONE AT A TIME and repeat steps 3-7.

**Common Second Tests**:
- Loading state
- Error state
- Disabled state
- With icon/feature
- Required field

**Example Second Test**:
```typescript
export const Loading: Story = {
  args: {
    placeholder: 'Searching...',
    loading: true,
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const input = await canvas.findByPlaceholderText(/searching/i);
    await expect(input).toBeInTheDocument();

    const container = input.parentElement;
    const spinner = container?.querySelector('[class*="i-lucide"][class*="loader-circle"]');
    await expect(spinner).toBeInTheDocument();
  },
};
```

## TDD Guard Enforcement

The TDD guard hook enforces strict discipline. Common violations:

### Multiple Test Addition
**Violation**: Adding multiple tests/stories at once
**Fix**: Add ONE test at a time

### Premature Implementation
**Violation**: Creating component without RED phase evidence
**Fix**: Update test.json with failure evidence before implementing

### Over-Implementation
**Violation**: Adding untested props or functionality
**Fix**: Only implement what's needed for the current failing test

### Missing RED Phase
**Violation**: Implementing without test failure captured
**Fix**: Run tests, save output, update test.json with exit code and errors

## NuxtUI Component Patterns

### Wrapping UInput (Atom Example)

```vue
<script setup lang="ts">
defineProps<{
  placeholder?: string;
  type?: 'text' | 'email' | 'password' | 'search';
  disabled?: boolean;
}>();
</script>

<template>
  <UInput
    :placeholder="placeholder"
    :type="type"
    :disabled="disabled"
  />
</template>
```

### Combining UFormField + UInput (Molecule Example)

```vue
<script setup lang="ts">
defineProps<{
  label?: string;
  name?: string;
  placeholder?: string;
  required?: boolean;
}>();
</script>

<template>
  <UFormField :label="label" :name="name" :required="required">
    <UInput :placeholder="placeholder" :name="name" :required="required" />
  </UFormField>
</template>
```

### Common NuxtUI Props

Support these props when tests require them:
- `size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'`
- `color?: 'primary' | 'secondary' | 'success' | 'error'`
- `variant?: 'solid' | 'outline' | 'soft' | 'ghost'`
- `loading?: boolean`
- `disabled?: boolean`
- `required?: boolean`

## Storybook Testing Patterns

### Finding Elements

```typescript
// By placeholder
const input = await canvas.findByPlaceholderText(/search/i);

// By label
const label = await canvas.findByLabelText(/email/i);

// By text content
const error = await canvas.findByText(/error message/i);

// By role
const button = await canvas.findByRole('button', { name: /submit/i });
```

### Common Assertions

```typescript
// Element exists
await expect(input).toBeInTheDocument();

// Has attribute
await expect(input).toHaveAttribute('required');
await expect(input).toHaveAttribute('type', 'search');

// Icon/spinner presence (use attribute selectors)
const icon = container?.querySelector('[class*="i-lucide"][class*="search"]');
await expect(icon).toBeInTheDocument();
```

## Test Coverage Guidelines

**Minimum tests per component**:
- **Atoms**: 2-3 tests (Default, Disabled, Loading/Error)
- **Molecules**: 3-5 tests (Default, With[Feature], State variations)
- **Organisms**: 5-10 tests (Multiple features, interactions, edge cases)

**What to test**:
1. Basic rendering with default props
2. Props affecting appearance/behavior
3. State variations (loading, disabled, error, empty)
4. Interactions (if applicable)
5. Validation and error messages
6. Accessibility (ARIA attributes, keyboard navigation)

## Common Pitfalls

### Pitfall 1: Storybook Hot Reload Issues
**Problem**: Tests fail with "failed to fetch" after creating component
**Solution**: Kill and restart Storybook server, wait for full rebuild

### Pitfall 2: Invalid querySelector Syntax
**Problem**: `querySelector('.iconify.i-lucide\\:icon')` fails
**Solution**: Use attribute selectors: `[class*="i-lucide"][class*="icon"]`

### Pitfall 3: Vue Prop Forwarding
**Problem**: Props work without explicit declaration
**Solution**: Leverage Vue's forwarding when appropriate, add explicit props when tests require them

### Pitfall 4: TDD Guard Blocks Refactoring
**Problem**: Guard blocks even reasonable code improvements
**Solution**: Only refactor tested code; add new tests before new features

## Running Tests

```bash
# Run all Storybook tests
pnpm test:storybook:run

# Run specific component tests
pnpm test:storybook:run -- ComponentName

# Run in watch mode (development)
pnpm test:storybook

# Run with coverage
pnpm test:storybook:run --coverage
```

## Success Criteria

A component is complete when:
- [ ] All planned tests pass (100% GREEN)
- [ ] Component follows naming conventions
- [ ] Props are properly typed with JSDoc comments
- [ ] Storybook autodocs enabled
- [ ] Atomic design level is correct
- [ ] Minimum test coverage achieved

## Resources

### References
- **references/naming-conventions.md** - Complete component naming and organizational guide following Atomic Design principles
- **references/tdd-workflow.md** - Detailed TDD workflow documentation with troubleshooting, patterns, and test.json management

### Assets
- **assets/story-template.ts** - Storybook story file template with TDD comments and common patterns
- **assets/component-template.vue** - Vue component template with NuxtUI patterns and prop examples

## Quick Start Checklist

For each new component:

1. [ ] Determine atomic level (atom/molecule/organism)
2. [ ] Create story file with ONE Default test
3. [ ] Run test to confirm failure (RED)
4. [ ] Save failure output to /tmp
5. [ ] Update test.json with RED phase evidence
6. [ ] Implement minimal component code
7. [ ] Run test to confirm success (GREEN)
8. [ ] Update test.json with GREEN phase
9. [ ] Repeat for next test

## Integration with Other Skills

This skill works well with:
- **nuxt-ui** - For NuxtUI component documentation and patterns
- **tailwindcss** - For styling and utility classes
- **turborepo** - For monorepo build and test execution

