Claude Code Plugins

Community-maintained marketplace

Feedback

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name nuxt-ui-tdd
description 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.

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:

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:

pnpm test:storybook:run -- {ComponentName}

Save the failure output:

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:

{
  "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:

<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:

pnpm test:storybook:run -- {ComponentName}

Expected: Tests pass (GREEN phase).

7. Update test.json for GREEN Phase

Update test state to "passed":

{
  "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:

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)

<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)

<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

// 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

// 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

# 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