@wipal/agent-team 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.claude/rules/common/general-rules.md +141 -0
  2. package/.claude/rules/lessons/lessons.md +91 -0
  3. package/.claude/rules/role-rules/dev-fe-rules.md +146 -0
  4. package/.claude/rules/role-rules/sa-rules.md +226 -0
  5. package/.claude/skills/SKILL-INDEX.md +299 -0
  6. package/.claude/skills/community/security-validator/SKILL.md +392 -0
  7. package/.claude/skills/core/agent-creation/SKILL.md +338 -0
  8. package/.claude/skills/core/code-review/SKILL.md +154 -0
  9. package/.claude/skills/core/git-automation/SKILL.md +93 -0
  10. package/.claude/skills/core/retrospect-work/SKILL.md +172 -0
  11. package/.claude/skills/domain/architecture/adr-writing/SKILL.md +254 -0
  12. package/.claude/skills/domain/architecture/adr-writing/references/adr-best-practices.md +257 -0
  13. package/.claude/skills/domain/architecture/adr-writing/references/adr-examples.md +246 -0
  14. package/.claude/skills/domain/architecture/adr-writing/references/adr-template.md +160 -0
  15. package/.claude/skills/domain/architecture/architecture-patterns/SKILL.md +316 -0
  16. package/.claude/skills/domain/architecture/architecture-patterns/references/event-driven.md +393 -0
  17. package/.claude/skills/domain/architecture/architecture-patterns/references/microservices.md +315 -0
  18. package/.claude/skills/domain/architecture/architecture-patterns/references/monolith.md +321 -0
  19. package/.claude/skills/domain/architecture/architecture-patterns/references/serverless.md +457 -0
  20. package/.claude/skills/domain/architecture/performance-engineering/SKILL.md +227 -0
  21. package/.claude/skills/domain/architecture/performance-engineering/references/benchmarking.md +336 -0
  22. package/.claude/skills/domain/architecture/performance-engineering/references/caching-strategies.md +284 -0
  23. package/.claude/skills/domain/architecture/performance-engineering/references/optimization.md +298 -0
  24. package/.claude/skills/domain/architecture/security-architecture/SKILL.md +206 -0
  25. package/.claude/skills/domain/architecture/security-architecture/references/auth-patterns.md +209 -0
  26. package/.claude/skills/domain/architecture/security-architecture/references/compliance.md +246 -0
  27. package/.claude/skills/domain/architecture/security-architecture/references/threat-modeling.md +219 -0
  28. package/.claude/skills/domain/architecture/system-design/SKILL.md +227 -0
  29. package/.claude/skills/domain/architecture/system-design/references/distributed-systems.md +231 -0
  30. package/.claude/skills/domain/architecture/system-design/references/resilience.md +344 -0
  31. package/.claude/skills/domain/architecture/system-design/references/scalability.md +303 -0
  32. package/.claude/skills/domain/architecture/tech-selection/SKILL.md +192 -0
  33. package/.claude/skills/domain/architecture/tech-selection/references/build-vs-buy.md +258 -0
  34. package/.claude/skills/domain/architecture/tech-selection/references/evaluation-framework.md +203 -0
  35. package/.claude/skills/domain/architecture/tech-selection/references/tech-radar.md +257 -0
  36. package/.claude/skills/domain/backend/api-design/SKILL.md +121 -0
  37. package/.claude/skills/domain/backend/database-design/SKILL.md +156 -0
  38. package/.claude/skills/domain/backend/performance-be/SKILL.md +210 -0
  39. package/.claude/skills/domain/backend/security/SKILL.md +138 -0
  40. package/.claude/skills/domain/backend/testing-be/SKILL.md +203 -0
  41. package/.claude/skills/domain/devops/ci-cd/SKILL.md +188 -0
  42. package/.claude/skills/domain/devops/containerization/SKILL.md +177 -0
  43. package/.claude/skills/domain/devops/deployment/SKILL.md +198 -0
  44. package/.claude/skills/domain/devops/infrastructure-as-code/SKILL.md +178 -0
  45. package/.claude/skills/domain/devops/monitoring/SKILL.md +163 -0
  46. package/.claude/skills/domain/frontend/accessibility/SKILL.md +179 -0
  47. package/.claude/skills/domain/frontend/frontend-design/SKILL.md +138 -0
  48. package/.claude/skills/domain/frontend/performance-fe/SKILL.md +195 -0
  49. package/.claude/skills/domain/frontend/state-management/SKILL.md +190 -0
  50. package/.claude/skills/domain/frontend/testing-fe/SKILL.md +193 -0
  51. package/.claude/skills/domain/product/requirements-gathering/SKILL.md +136 -0
  52. package/.claude/skills/domain/product/roadmap-planning/SKILL.md +169 -0
  53. package/.claude/skills/domain/product/sprint-planning/SKILL.md +151 -0
  54. package/.claude/skills/domain/product/stakeholder-communication/SKILL.md +162 -0
  55. package/.claude/skills/domain/product/user-stories/SKILL.md +141 -0
  56. package/.claude/skills/domain/quality/bug-reporting/SKILL.md +150 -0
  57. package/.claude/skills/domain/quality/regression-testing/SKILL.md +178 -0
  58. package/.claude/skills/domain/quality/test-automation/SKILL.md +185 -0
  59. package/.claude/skills/domain/quality/test-planning/SKILL.md +177 -0
  60. package/.claude/skills/leadership/code-review-advanced/SKILL.md +167 -0
  61. package/.claude/skills/leadership/mentoring/SKILL.md +151 -0
  62. package/.claude/skills/leadership/technical-debt/SKILL.md +166 -0
  63. package/.claude/skills/leadership/technical-decision/SKILL.md +160 -0
  64. package/.claude/skills/security-reports/.gitkeep +0 -0
  65. package/.claude/skills/skills-registry.yaml +441 -0
  66. package/README.md +232 -0
  67. package/bin/agent-team.js +107 -0
  68. package/package.json +51 -0
  69. package/src/commands/add.js +227 -0
  70. package/src/commands/init.js +136 -0
  71. package/src/commands/list.js +66 -0
  72. package/src/commands/remove.js +71 -0
  73. package/src/commands/switch.js +53 -0
  74. package/src/index.js +11 -0
  75. package/src/interactive/prompts.js +153 -0
  76. package/src/server/api/agents.js +150 -0
  77. package/src/server/api/roles.js +97 -0
  78. package/src/server/api/skills.js +79 -0
  79. package/src/server/index.js +78 -0
  80. package/src/ui/agents.html +174 -0
  81. package/src/ui/css/styles.css +470 -0
  82. package/src/ui/index.html +107 -0
  83. package/src/ui/roles.html +371 -0
  84. package/src/ui/skills.html +332 -0
  85. package/src/utils/file-utils.js +193 -0
  86. package/src/utils/skill-resolver.js +594 -0
  87. package/src/utils/skill-scanner.js +154 -0
  88. package/templates/CLAUDE.md.tmpl +42 -0
  89. package/templates/knowledge.md.tmpl +31 -0
@@ -0,0 +1,138 @@
1
+ ---
2
+ name: frontend-design
3
+ description: |
4
+ Create distinctive, production-grade frontend interfaces. Use when building
5
+ UI components, pages, websites, dashboards, React/Vue components, HTML/CSS
6
+ layouts, or when styling/beautifying any web UI. Use when user mentions
7
+ "design", "UI", "component", "page", "style", "dashboard", "landing page".
8
+ Make designs UNFORGETTABLE - avoid generic AI aesthetics.
9
+ version: 1.0.0
10
+ category: frontend
11
+ tags:
12
+ - ui
13
+ - ux
14
+ - design
15
+ - components
16
+ depends_on:
17
+ - code-review
18
+ recommends:
19
+ - accessibility
20
+ - state-management
21
+ used_by:
22
+ - accessibility
23
+ ---
24
+
25
+ # Skill: Frontend Design
26
+
27
+ ## Core Principle
28
+ **Design with intention.** Bold minimalism and refined maximalism both work - mediocrity never does. Every choice should be deliberate.
29
+
30
+ ## Hard Rules
31
+
32
+ 1. **NEVER use Inter, Roboto, Arial, system fonts** - Choose distinctive, characterful fonts
33
+ 2. **NEVER use purple gradients on white** - Most AI-generated aesthetic
34
+ 3. **NEVER use Space Grotesk repeatedly** - Vary font choices
35
+ 4. **ALWAYS commit to an aesthetic** - Timid design is bad design
36
+ 5. **ALWAYS consider motion** - Animations create delight
37
+ 6. **ALWAYS test responsive** - Mobile is not optional
38
+
39
+ ## Design Decision Framework
40
+
41
+ ### Before Coding
42
+ 1. **Purpose**: What problem does this solve? Who uses it?
43
+ 2. **Tone**: Pick extreme - brutalist, maximalist, organic, luxury, playful, editorial...
44
+ 3. **Differentiation**: What makes this UNFORGETTABLE?
45
+
46
+ ### Typography
47
+ - Choose fonts with character
48
+ - Pair distinctive display + refined body
49
+ - Avoid generic defaults
50
+
51
+ ### Color
52
+ - Commit to cohesive palette
53
+ - Dominant colors with sharp accents
54
+ - Avoid evenly-distributed "safe" palettes
55
+
56
+ ### Motion
57
+ - One well-orchestrated page load > scattered micro-interactions
58
+ - Use `animation-delay` for staggered reveals
59
+ - Hover states should surprise
60
+
61
+ ### Layout
62
+ - Unexpected layouts, asymmetry, grid-breaking
63
+ - Generous negative space OR controlled density
64
+ - Not predictable 3-column layouts
65
+
66
+ ## Common Mistakes
67
+
68
+ | ❌ Mistake | ✅ Fix |
69
+ |------------|--------|
70
+ | Inter/Roboto font | Characterful fonts like Instrument Serif |
71
+ | Purple gradient on white | Bold single color or unexpected palette |
72
+ | Predictable 3-column | Asymmetry, overlap, diagonal flow |
73
+ | No animation | CSS transitions, staggered reveals |
74
+ | Safe, even spacing | Generous space OR controlled density |
75
+ | Same design every time | Vary between light/dark, different aesthetics |
76
+
77
+ ## Quick Patterns
78
+
79
+ ```css
80
+ /* Distinctive typography */
81
+ font-family: 'Instrument Serif', serif;
82
+ font-family: 'DM Sans', sans-serif;
83
+
84
+ /* Commit to color */
85
+ --primary: #1a1a1a;
86
+ --accent: #ff3366;
87
+
88
+ /* Motion */
89
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
90
+
91
+ /* Break the grid */
92
+ transform: translateY(-20px);
93
+ mix-blend-mode: difference;
94
+ ```
95
+
96
+ ## Component Patterns
97
+
98
+ ### Atomic Structure
99
+ ```text
100
+ components/
101
+ ├── atoms/ # Button, Input, Label
102
+ ├── molecules/ # SearchBar, FormField
103
+ ├── organisms/ # Header, Sidebar
104
+ ├── templates/ # PageLayouts
105
+ └── pages/ # Complete pages
106
+ ```
107
+
108
+ ### Compound Components
109
+ ```tsx
110
+ <Card>
111
+ <Card.Header>Title</Card.Header>
112
+ <Card.Body>Content</Card.Body>
113
+ <Card.Footer>Action</Card.Footer>
114
+ </Card>
115
+ ```
116
+
117
+ ## Accessibility (Non-negotiable)
118
+
119
+ ```tsx
120
+ // Icon button needs label
121
+ <button aria-label="Close">
122
+ <XIcon />
123
+ </button>
124
+
125
+ // Form field with hint
126
+ <label htmlFor="email">Email</label>
127
+ <input id="email" aria-describedby="email-hint" />
128
+ <span id="email-hint">We'll never share</span>
129
+ ```
130
+
131
+ ## Output Checklist
132
+
133
+ - [ ] Typography: Distinctive, not generic
134
+ - [ ] Color: Cohesive, committed
135
+ - [ ] Layout: Intentional, not default
136
+ - [ ] Motion: Purposeful
137
+ - [ ] Responsive: Works at 375px
138
+ - [ ] Accessible: ARIA labels, focus states
@@ -0,0 +1,195 @@
1
+ ---
2
+ name: performance-fe
3
+ description: |
4
+ React/frontend performance optimization patterns. Use when: optimizing render
5
+ performance, reducing bundle size, improving Core Web Vitals, or when user
6
+ mentions "performance", "slow", "optimize", "bundle size", "Lighthouse", "LCP".
7
+ Fast apps = happy users.
8
+ version: 1.0.0
9
+ category: frontend
10
+ tags:
11
+ - performance
12
+ - optimization
13
+ - core-web-vitals
14
+ - bundle-size
15
+ depends_on:
16
+ - frontend-design
17
+ recommends: []
18
+ used_by: []
19
+ ---
20
+
21
+ # Skill: Performance FE
22
+
23
+ ## Core Principle
24
+ **Measure before optimizing.** Premature optimization is waste. Fix real bottlenecks.
25
+
26
+ ## Core Web Vitals
27
+
28
+ | Metric | Target | What |
29
+ |--------|--------|------|
30
+ | **LCP** | < 2.5s | Largest Contentful Paint |
31
+ | **FID** | < 100ms | First Input Delay |
32
+ | **CLS** | < 0.1 | Cumulative Layout Shift |
33
+
34
+ ## Hard Rules
35
+
36
+ 1. **NEVER optimize without measuring** - Profile first
37
+ 2. **NEVER use useEffect for derived state** - Compute in render
38
+ 3. **ALWAYS lazy load below fold** - Code splitting
39
+ 4. **ALWAYS memoize expensive computes** - useMemo, useCallback
40
+ 5. **ALWAYS optimize images** - WebP, lazy load, srcset
41
+
42
+ ## React Performance Patterns
43
+
44
+ ### Memoization
45
+
46
+ ```tsx
47
+ import { memo, useMemo, useCallback } from 'react';
48
+
49
+ // Memo component to prevent re-renders
50
+ const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
51
+ return <div>{/* expensive render */}</div>;
52
+ });
53
+
54
+ // Memo expensive computation
55
+ function Dashboard({ items }) {
56
+ const sortedItems = useMemo(
57
+ () => items.sort((a, b) => b.value - a.value),
58
+ [items]
59
+ );
60
+ }
61
+
62
+ // Memo callback passed to children
63
+ function Parent() {
64
+ const handleClick = useCallback((id) => {
65
+ console.log(id);
66
+ }, []);
67
+
68
+ return <Child onClick={handleClick} />;
69
+ }
70
+ ```
71
+
72
+ ### List Virtualization
73
+
74
+ ```tsx
75
+ import { FixedSizeList } from 'react-window';
76
+
77
+ function VirtualList({ items }) {
78
+ return (
79
+ <FixedSizeList
80
+ height={600}
81
+ itemCount={items.length}
82
+ itemSize={50}
83
+ >
84
+ {({ index, style }) => (
85
+ <div style={style}>{items[index].name}</div>
86
+ )}
87
+ </FixedSizeList>
88
+ );
89
+ }
90
+ ```
91
+
92
+ ## Code Splitting
93
+
94
+ ```tsx
95
+ import { lazy, Suspense } from 'react';
96
+
97
+ // Lazy load heavy components
98
+ const HeavyChart = lazy(() => import('./HeavyChart'));
99
+
100
+ function Dashboard() {
101
+ return (
102
+ <Suspense fallback={<Loading />}>
103
+ <HeavyChart />
104
+ </Suspense>
105
+ );
106
+ }
107
+
108
+ // Route-based splitting
109
+ const routes = {
110
+ '/settings': lazy(() => import('./Settings')),
111
+ '/analytics': lazy(() => import('./Analytics')),
112
+ };
113
+ ```
114
+
115
+ ## Common Mistakes
116
+
117
+ | ❌ Mistake | ✅ Fix |
118
+ |------------|--------|
119
+ | Inline objects in render | useMemo, move outside |
120
+ | Inline functions in render | useCallback |
121
+ | useEffect for derived state | Compute in render |
122
+ | Large initial bundle | Code splitting |
123
+ | Unoptimized images | WebP, lazy load, srcset |
124
+ | No virtualization | react-window |
125
+
126
+ ## Image Optimization
127
+
128
+ ```tsx
129
+ // Responsive images
130
+ <picture>
131
+ <source srcSet="/image.avif" type="image/avif" />
132
+ <source srcSet="/image.webp" type="image/webp" />
133
+ <img src="/image.jpg" loading="lazy" alt="Description" />
134
+ </picture>
135
+
136
+ // Next.js Image
137
+ import Image from 'next/image';
138
+
139
+ <Image
140
+ src="/hero.jpg"
141
+ alt="Hero"
142
+ width={1200}
143
+ height={600}
144
+ priority // For above fold
145
+ />
146
+ ```
147
+
148
+ ## Bundle Analysis
149
+
150
+ ```bash
151
+ # Analyze bundle size
152
+ npx vite-bundle-visualizer
153
+
154
+ # Check what's in your bundle
155
+ npx source-map-explorer dist/assets/*.js
156
+ ```
157
+
158
+ ## Performance Checklist
159
+
160
+ ### JavaScript
161
+ - [ ] Code splitting implemented
162
+ - [ ] Tree shaking working
163
+ - [ ] No unnecessary dependencies
164
+ - [ ] Lazy loading for routes
165
+
166
+ ### React
167
+ - [ ] No useEffect for derived state
168
+ - [ ] Lists virtualized
169
+ - [ ] Expensive computes memoized
170
+ - [ ] Props stable (useCallback)
171
+
172
+ ### Assets
173
+ - [ ] Images optimized (WebP/AVIF)
174
+ - [ ] Lazy loading for below fold
175
+ - [ ] Fonts preloaded
176
+ - [ ] Critical CSS inlined
177
+
178
+ ### Metrics
179
+ - [ ] LCP < 2.5s
180
+ - [ ] FID < 100ms
181
+ - [ ] CLS < 0.1
182
+
183
+ ## Profiling
184
+
185
+ ```bash
186
+ # Lighthouse
187
+ npx lighthouse https://yoursite.com --view
188
+
189
+ # React DevTools Profiler
190
+ # 1. Open DevTools
191
+ # 2. Go to Profiler tab
192
+ # 3. Click record
193
+ # 4. Interact with app
194
+ # 5. Analyze flame graph
195
+ ```
@@ -0,0 +1,190 @@
1
+ ---
2
+ name: state-management
3
+ description: |
4
+ Frontend state management patterns with Zustand, TanStack Query, React Context.
5
+ Use when: managing application state, server state, URL state, or when user
6
+ mentions "state", "Zustand", "React Query", "TanStack Query", "context", "store".
7
+ Choose the right tool for each type of state.
8
+ version: 1.0.0
9
+ category: frontend
10
+ tags:
11
+ - state
12
+ - zustand
13
+ - tanstack-query
14
+ - react-query
15
+ depends_on:
16
+ - frontend-design
17
+ recommends: []
18
+ used_by: []
19
+ ---
20
+
21
+ # Skill: State Management
22
+
23
+ ## Core Principle
24
+ **Different state types need different solutions.** Don't use a hammer for everything.
25
+
26
+ ## State Types
27
+
28
+ | Type | Tool | Examples |
29
+ |------|------|----------|
30
+ | **Server State** | TanStack Query | API data, cached responses |
31
+ | **Client State** | Zustand | UI state, preferences |
32
+ | **URL State** | URL params | Filters, pagination |
33
+ | **Form State** | React Hook Form | Form inputs, validation |
34
+ | **Local State** | useState | Component-only state |
35
+
36
+ ## Server State (TanStack Query)
37
+
38
+ ```tsx
39
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
40
+
41
+ // Fetching data
42
+ function useUser(id: string) {
43
+ return useQuery({
44
+ queryKey: ['user', id],
45
+ queryFn: () => fetchUser(id),
46
+ staleTime: 5 * 60 * 1000, // 5 minutes
47
+ });
48
+ }
49
+
50
+ // Mutations
51
+ function useUpdateUser() {
52
+ const queryClient = useQueryClient();
53
+
54
+ return useMutation({
55
+ mutationFn: updateUser,
56
+ onSuccess: (data, variables) => {
57
+ // Invalidate and refetch
58
+ queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
59
+ },
60
+ });
61
+ }
62
+
63
+ // Usage
64
+ function UserProfile({ userId }: { userId: string }) {
65
+ const { data, isLoading, error } = useUser(userId);
66
+ const update = useUpdateUser();
67
+
68
+ if (isLoading) return <Loading />;
69
+ if (error) return <Error />;
70
+
71
+ return (
72
+ <div>
73
+ <h1>{data.name}</h1>
74
+ <button onClick={() => update.mutate({ id: userId, name: 'New' })}>
75
+ Update
76
+ </button>
77
+ </div>
78
+ );
79
+ }
80
+ ```
81
+
82
+ ## Client State (Zustand)
83
+
84
+ ```tsx
85
+ import { create } from 'zustand';
86
+ import { persist, devtools } from 'zustand/middleware';
87
+
88
+ // Store definition
89
+ interface AppState {
90
+ theme: 'light' | 'dark';
91
+ sidebarOpen: boolean;
92
+ toggleTheme: () => void;
93
+ toggleSidebar: () => void;
94
+ }
95
+
96
+ const useStore = create<AppState>()(
97
+ devtools(
98
+ persist(
99
+ (set) => ({
100
+ theme: 'light',
101
+ sidebarOpen: false,
102
+ toggleTheme: () => set((s) => ({ theme: s.theme === 'light' ? 'dark' : 'light' })),
103
+ toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
104
+ }),
105
+ { name: 'app-store' }
106
+ )
107
+ )
108
+ );
109
+
110
+ // Usage
111
+ function ThemeToggle() {
112
+ const { theme, toggleTheme } = useStore();
113
+ return <button onClick={toggleTheme}>{theme}</button>;
114
+ }
115
+
116
+ // Selectors for performance
117
+ const theme = useStore((s) => s.theme); // Only re-renders when theme changes
118
+ ```
119
+
120
+ ## URL State
121
+
122
+ ```tsx
123
+ import { useSearchParams } from 'react-router-dom';
124
+
125
+ function ProductList() {
126
+ const [searchParams, setSearchParams] = useSearchParams();
127
+
128
+ // Read from URL
129
+ const page = Number(searchParams.get('page')) || 1;
130
+ const category = searchParams.get('category') || 'all';
131
+
132
+ // Update URL
133
+ const setPage = (p: number) => {
134
+ setSearchParams({ page: String(p), category });
135
+ };
136
+
137
+ return (
138
+ <div>
139
+ <Pagination current={page} onChange={setPage} />
140
+ </div>
141
+ );
142
+ }
143
+ ```
144
+
145
+ ## Common Mistakes
146
+
147
+ | ❌ Mistake | ✅ Fix |
148
+ |------------|--------|
149
+ | Storing API data in Zustand | Use TanStack Query |
150
+ | useState for global state | Use Zustand |
151
+ | useEffect for derived state | Compute during render |
152
+ | Prop drilling | Context or Zustand |
153
+ | Not persisting preferences | Add persist middleware |
154
+
155
+ ## Derived State
156
+
157
+ ```tsx
158
+ // ❌ Bad: useEffect for derived state
159
+ const [total, setTotal] = useState(0);
160
+ useEffect(() => {
161
+ setTotal(items.reduce((sum, item) => sum + item.price, 0));
162
+ }, [items]);
163
+
164
+ // ✅ Good: Compute during render
165
+ const total = items.reduce((sum, item) => sum + item.price, 0);
166
+ ```
167
+
168
+ ## When to Use What
169
+
170
+ ```
171
+ API data → TanStack Query
172
+
173
+ Is it cached? Server state
174
+
175
+ Is it shared? Zustand
176
+
177
+ Is it form? React Hook Form
178
+
179
+ Is it URL? Search params
180
+
181
+ Local only? useState
182
+ ```
183
+
184
+ ## Quick Checklist
185
+
186
+ - [ ] Server state uses TanStack Query
187
+ - [ ] Client state uses Zustand
188
+ - [ ] No useEffect for derived state
189
+ - [ ] Form state uses React Hook Form
190
+ - [ ] URL state for shareable state
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: testing-fe
3
+ description: |
4
+ Frontend testing with Vitest, Testing Library, Playwright. Use when: writing
5
+ unit tests, integration tests, component tests, E2E tests, or when user
6
+ mentions "test", "vitest", "jest", "testing library", "playwright", "cypress".
7
+ Test behavior, not implementation.
8
+ version: 1.0.0
9
+ category: frontend
10
+ tags:
11
+ - testing
12
+ - vitest
13
+ - testing-library
14
+ - playwright
15
+ depends_on:
16
+ - code-review
17
+ recommends: []
18
+ used_by: []
19
+ ---
20
+
21
+ # Skill: Testing FE
22
+
23
+ ## Core Principle
24
+ **Test behavior, not implementation.** Users don't care about your code structure—they care if it works.
25
+
26
+ ## Testing Trophy
27
+
28
+ ```
29
+
30
+ /E2E\ Few, slow, expensive
31
+ /─────\
32
+ / API \ Some, medium speed
33
+ /─────────\
34
+ / Integration \ Many, fast
35
+ /───────────────\
36
+ / Unit Tests \ Many, very fast
37
+ /───────────────────\
38
+ ```
39
+
40
+ ## Hard Rules
41
+
42
+ 1. **NEVER test implementation** - Test user behavior
43
+ 2. **NEVER use container.querySelector** - Use getBy* queries
44
+ 3. **ALWAYS use userEvent** - Not fireEvent
45
+ 4. **ALWAYS wait for async** - findBy*, waitFor
46
+ 5. **ALWAYS clean up** - No state leakage
47
+
48
+ ## Component Testing (Vitest + Testing Library)
49
+
50
+ ```tsx
51
+ // Button.test.tsx
52
+ import { render, screen } from '@testing-library/react';
53
+ import userEvent from '@testing-library/user-event';
54
+ import { describe, it, expect, vi } from 'vitest';
55
+ import { Button } from './Button';
56
+
57
+ describe('Button', () => {
58
+ it('renders with text', () => {
59
+ render(<Button>Click me</Button>);
60
+ expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
61
+ });
62
+
63
+ it('calls onClick when clicked', async () => {
64
+ const user = userEvent.setup();
65
+ const onClick = vi.fn();
66
+
67
+ render(<Button onClick={onClick}>Click me</Button>);
68
+
69
+ await user.click(screen.getByRole('button'));
70
+
71
+ expect(onClick).toHaveBeenCalledOnce();
72
+ });
73
+
74
+ it('shows loading state', () => {
75
+ render(<Button loading>Submit</Button>);
76
+
77
+ expect(screen.getByRole('button')).toBeDisabled();
78
+ expect(screen.getByText(/loading/i)).toBeInTheDocument();
79
+ });
80
+ });
81
+ ```
82
+
83
+ ## Async Testing
84
+
85
+ ```tsx
86
+ import { render, screen, waitFor } from '@testing-library/react';
87
+
88
+ it('shows user after loading', async () => {
89
+ render(<UserProfile userId="1" />);
90
+
91
+ // Wait for element to appear
92
+ expect(await screen.findByText('John Doe')).toBeInTheDocument();
93
+
94
+ // Or use waitFor for complex conditions
95
+ await waitFor(() => {
96
+ expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
97
+ });
98
+ });
99
+ ```
100
+
101
+ ## Mocking
102
+
103
+ ```tsx
104
+ import { vi } from 'vitest';
105
+
106
+ // Mock module
107
+ vi.mock('../api', () => ({
108
+ fetchUser: vi.fn().mockResolvedValue({ id: '1', name: 'John' }),
109
+ }));
110
+
111
+ // Mock hooks
112
+ const mockNavigate = vi.fn();
113
+ vi.mock('react-router-dom', () => ({
114
+ useNavigate: () => mockNavigate,
115
+ }));
116
+
117
+ // Spy on console
118
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
119
+ ```
120
+
121
+ ## Common Mistakes
122
+
123
+ | ❌ Mistake | ✅ Fix |
124
+ |------------|--------|
125
+ | Testing props/state | Test rendered output |
126
+ | Using fireEvent | Use userEvent |
127
+ | Not waiting for async | findBy*, waitFor |
128
+ | Testing implementation | Test user behavior |
129
+ | Brittle selectors | Use accessible queries |
130
+
131
+ ## Query Priority
132
+
133
+ ```tsx
134
+ // 1. Accessible queries (best)
135
+ screen.getByRole('button', { name: /submit/i });
136
+ screen.getByLabelText(/email/i);
137
+ screen.getByPlaceholderText(/search/i);
138
+ screen.getByText(/welcome/i);
139
+
140
+ // 2. Semantic queries
141
+ screen.getByAltText(/profile/i);
142
+ screen.getByTitle(/close/i);
143
+
144
+ // 3. Test ID (last resort)
145
+ screen.getByTestId('submit-button');
146
+ ```
147
+
148
+ ## E2E Testing (Playwright)
149
+
150
+ ```typescript
151
+ // example.spec.ts
152
+ import { test, expect } from '@playwright/test';
153
+
154
+ test('user can login', async ({ page }) => {
155
+ await page.goto('/login');
156
+
157
+ await page.fill('[name="email"]', 'test@example.com');
158
+ await page.fill('[name="password"]', 'password123');
159
+ await page.click('button[type="submit"]');
160
+
161
+ await expect(page).toHaveURL('/dashboard');
162
+ await expect(page.locator('h1')).toContainText('Welcome');
163
+ });
164
+ ```
165
+
166
+ ## Vitest Config
167
+
168
+ ```typescript
169
+ // vitest.config.ts
170
+ import { defineConfig } from 'vitest/config';
171
+ import react from '@vitejs/plugin-react';
172
+
173
+ export default defineConfig({
174
+ plugins: [react()],
175
+ test: {
176
+ environment: 'jsdom',
177
+ setupFiles: ['./src/test/setup.ts'],
178
+ coverage: {
179
+ reporter: ['text', 'html'],
180
+ exclude: ['node_modules/', 'src/test/'],
181
+ },
182
+ },
183
+ });
184
+ ```
185
+
186
+ ## Quick Checklist
187
+
188
+ - [ ] Tests user behavior, not implementation
189
+ - [ ] Uses accessible queries (getByRole)
190
+ - [ ] Uses userEvent, not fireEvent
191
+ - [ ] Waits for async with findBy/waitFor
192
+ - [ ] Mocks external dependencies
193
+ - [ ] Coverage reports generated