agentic-team-templates 0.3.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.
- package/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# Frontend Testing
|
|
2
|
+
|
|
3
|
+
Guidelines for testing frontend applications effectively.
|
|
4
|
+
|
|
5
|
+
## Testing Philosophy
|
|
6
|
+
|
|
7
|
+
### Test User Behavior, Not Implementation
|
|
8
|
+
|
|
9
|
+
Focus on what users see and do, not internal component details.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
// Good: Test what user sees
|
|
13
|
+
test('shows error when form is invalid', async () => {
|
|
14
|
+
render(<LoginForm />);
|
|
15
|
+
|
|
16
|
+
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
|
|
17
|
+
|
|
18
|
+
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Bad: Test implementation details
|
|
22
|
+
test('sets hasError state to true', () => {
|
|
23
|
+
const { result } = renderHook(() => useLoginForm());
|
|
24
|
+
|
|
25
|
+
act(() => result.current.validate());
|
|
26
|
+
|
|
27
|
+
expect(result.current.hasError).toBe(true); // Testing internal state
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### The Testing Trophy
|
|
32
|
+
|
|
33
|
+
Prioritize tests by confidence and cost:
|
|
34
|
+
1. **Static Analysis** (TypeScript, ESLint) - Cheap, fast
|
|
35
|
+
2. **Unit Tests** - Pure functions, utilities
|
|
36
|
+
3. **Integration Tests** - Components working together
|
|
37
|
+
4. **E2E Tests** - Critical user flows
|
|
38
|
+
|
|
39
|
+
## Unit Tests
|
|
40
|
+
|
|
41
|
+
For pure functions and utilities.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// utils/formatCurrency.test.ts
|
|
45
|
+
import { formatCurrency } from './formatCurrency';
|
|
46
|
+
|
|
47
|
+
describe('formatCurrency', () => {
|
|
48
|
+
it('formats USD correctly', () => {
|
|
49
|
+
expect(formatCurrency(1234.56, 'USD')).toBe('$1,234.56');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('handles zero', () => {
|
|
53
|
+
expect(formatCurrency(0, 'USD')).toBe('$0.00');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('handles negative values', () => {
|
|
57
|
+
expect(formatCurrency(-50, 'USD')).toBe('-$50.00');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Component Tests
|
|
63
|
+
|
|
64
|
+
Test components as users interact with them.
|
|
65
|
+
|
|
66
|
+
### Basic Component Test
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { render, screen } from '@testing-library/react';
|
|
70
|
+
import userEvent from '@testing-library/user-event';
|
|
71
|
+
import { Counter } from './Counter';
|
|
72
|
+
|
|
73
|
+
describe('Counter', () => {
|
|
74
|
+
it('increments when plus button is clicked', async () => {
|
|
75
|
+
const user = userEvent.setup();
|
|
76
|
+
render(<Counter initialValue={0} />);
|
|
77
|
+
|
|
78
|
+
await user.click(screen.getByRole('button', { name: /increment/i }));
|
|
79
|
+
|
|
80
|
+
expect(screen.getByText('1')).toBeInTheDocument();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('calls onChange with new value', async () => {
|
|
84
|
+
const user = userEvent.setup();
|
|
85
|
+
const handleChange = vi.fn();
|
|
86
|
+
render(<Counter initialValue={0} onChange={handleChange} />);
|
|
87
|
+
|
|
88
|
+
await user.click(screen.getByRole('button', { name: /increment/i }));
|
|
89
|
+
|
|
90
|
+
expect(handleChange).toHaveBeenCalledWith(1);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Testing Forms
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
describe('LoginForm', () => {
|
|
99
|
+
it('submits form with email and password', async () => {
|
|
100
|
+
const user = userEvent.setup();
|
|
101
|
+
const handleSubmit = vi.fn();
|
|
102
|
+
render(<LoginForm onSubmit={handleSubmit} />);
|
|
103
|
+
|
|
104
|
+
await user.type(screen.getByLabelText(/email/i), 'user@example.com');
|
|
105
|
+
await user.type(screen.getByLabelText(/password/i), 'password123');
|
|
106
|
+
await user.click(screen.getByRole('button', { name: /sign in/i }));
|
|
107
|
+
|
|
108
|
+
expect(handleSubmit).toHaveBeenCalledWith({
|
|
109
|
+
email: 'user@example.com',
|
|
110
|
+
password: 'password123',
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('shows validation errors', async () => {
|
|
115
|
+
const user = userEvent.setup();
|
|
116
|
+
render(<LoginForm onSubmit={vi.fn()} />);
|
|
117
|
+
|
|
118
|
+
await user.click(screen.getByRole('button', { name: /sign in/i }));
|
|
119
|
+
|
|
120
|
+
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
|
|
121
|
+
expect(screen.getByText(/password is required/i)).toBeInTheDocument();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Testing Async Operations
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
describe('UserProfile', () => {
|
|
130
|
+
it('shows loading state then user data', async () => {
|
|
131
|
+
render(<UserProfile userId="123" />);
|
|
132
|
+
|
|
133
|
+
// Loading state
|
|
134
|
+
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
|
135
|
+
|
|
136
|
+
// Wait for data
|
|
137
|
+
expect(await screen.findByText('John Doe')).toBeInTheDocument();
|
|
138
|
+
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('shows error state on failure', async () => {
|
|
142
|
+
server.use(
|
|
143
|
+
rest.get('/api/users/:id', (req, res, ctx) => {
|
|
144
|
+
return res(ctx.status(500));
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
render(<UserProfile userId="123" />);
|
|
149
|
+
|
|
150
|
+
expect(await screen.findByText(/failed to load/i)).toBeInTheDocument();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Accessibility Tests
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
159
|
+
|
|
160
|
+
expect.extend(toHaveNoViolations);
|
|
161
|
+
|
|
162
|
+
describe('Button', () => {
|
|
163
|
+
it('has no accessibility violations', async () => {
|
|
164
|
+
const { container } = render(<Button>Click me</Button>);
|
|
165
|
+
const results = await axe(container);
|
|
166
|
+
expect(results).toHaveNoViolations();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## E2E Tests
|
|
172
|
+
|
|
173
|
+
For critical user journeys.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// e2e/checkout.spec.ts
|
|
177
|
+
import { test, expect } from '@playwright/test';
|
|
178
|
+
|
|
179
|
+
test.describe('Checkout Flow', () => {
|
|
180
|
+
test('user can complete purchase', async ({ page }) => {
|
|
181
|
+
// Add item to cart
|
|
182
|
+
await page.goto('/products');
|
|
183
|
+
await page.click('[data-testid="product-1"] >> text=Add to Cart');
|
|
184
|
+
|
|
185
|
+
// Go to cart
|
|
186
|
+
await page.click('text=Cart (1)');
|
|
187
|
+
expect(page.url()).toContain('/cart');
|
|
188
|
+
|
|
189
|
+
// Proceed to checkout
|
|
190
|
+
await page.click('text=Checkout');
|
|
191
|
+
|
|
192
|
+
// Fill shipping info
|
|
193
|
+
await page.fill('[name="email"]', 'test@example.com');
|
|
194
|
+
await page.fill('[name="address"]', '123 Main St');
|
|
195
|
+
|
|
196
|
+
// Complete purchase
|
|
197
|
+
await page.click('text=Place Order');
|
|
198
|
+
|
|
199
|
+
// Verify success
|
|
200
|
+
await expect(page.locator('text=Order Confirmed')).toBeVisible();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Mocking
|
|
206
|
+
|
|
207
|
+
### Mock API Calls
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { rest } from 'msw';
|
|
211
|
+
import { setupServer } from 'msw/node';
|
|
212
|
+
|
|
213
|
+
const server = setupServer(
|
|
214
|
+
rest.get('/api/users', (req, res, ctx) => {
|
|
215
|
+
return res(ctx.json([
|
|
216
|
+
{ id: 1, name: 'John' },
|
|
217
|
+
{ id: 2, name: 'Jane' },
|
|
218
|
+
]));
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
beforeAll(() => server.listen());
|
|
223
|
+
afterEach(() => server.resetHandlers());
|
|
224
|
+
afterAll(() => server.close());
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Mock Modules
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
vi.mock('./analytics', () => ({
|
|
231
|
+
trackEvent: vi.fn(),
|
|
232
|
+
}));
|
|
233
|
+
|
|
234
|
+
import { trackEvent } from './analytics';
|
|
235
|
+
|
|
236
|
+
test('tracks button click', async () => {
|
|
237
|
+
const user = userEvent.setup();
|
|
238
|
+
render(<TrackedButton />);
|
|
239
|
+
|
|
240
|
+
await user.click(screen.getByRole('button'));
|
|
241
|
+
|
|
242
|
+
expect(trackEvent).toHaveBeenCalledWith('button_click');
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Best Practices
|
|
247
|
+
|
|
248
|
+
### Query Priority
|
|
249
|
+
|
|
250
|
+
Use queries in this order (most accessible first):
|
|
251
|
+
1. `getByRole` - Accessible to everyone
|
|
252
|
+
2. `getByLabelText` - Good for form fields
|
|
253
|
+
3. `getByPlaceholderText` - Less ideal but okay
|
|
254
|
+
4. `getByText` - For non-interactive content
|
|
255
|
+
5. `getByTestId` - Last resort
|
|
256
|
+
|
|
257
|
+
### Avoid Testing Implementation Details
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
// Bad: Testing internal state
|
|
261
|
+
expect(component.state.isOpen).toBe(true);
|
|
262
|
+
|
|
263
|
+
// Bad: Testing class names
|
|
264
|
+
expect(container.querySelector('.modal--open')).toBeInTheDocument();
|
|
265
|
+
|
|
266
|
+
// Good: Testing what user sees
|
|
267
|
+
expect(screen.getByRole('dialog')).toBeVisible();
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### One Assertion Per Concept
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
// Good: Focused tests
|
|
274
|
+
it('shows user name', () => {
|
|
275
|
+
render(<UserCard user={mockUser} />);
|
|
276
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('shows user email', () => {
|
|
280
|
+
render(<UserCard user={mockUser} />);
|
|
281
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Acceptable: Related assertions
|
|
285
|
+
it('shows user info', () => {
|
|
286
|
+
render(<UserCard user={mockUser} />);
|
|
287
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
288
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Test Organization
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
src/
|
|
296
|
+
├── components/
|
|
297
|
+
│ └── Button/
|
|
298
|
+
│ ├── Button.tsx
|
|
299
|
+
│ └── Button.test.tsx # Co-located unit tests
|
|
300
|
+
├── features/
|
|
301
|
+
│ └── auth/
|
|
302
|
+
│ └── __tests__/ # Feature integration tests
|
|
303
|
+
│ └── login.test.tsx
|
|
304
|
+
└── test/
|
|
305
|
+
├── setup.ts # Test configuration
|
|
306
|
+
└── utils.tsx # Test utilities
|
|
307
|
+
|
|
308
|
+
e2e/
|
|
309
|
+
├── auth.spec.ts # E2E tests
|
|
310
|
+
└── checkout.spec.ts
|
|
311
|
+
```
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# Web Frontend Development Guide
|
|
2
|
+
|
|
3
|
+
Comprehensive guidelines for building modern web frontend applications.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This guide applies to:
|
|
10
|
+
- Single-page applications (SPAs)
|
|
11
|
+
- Server-rendered web applications
|
|
12
|
+
- Static sites with dynamic components
|
|
13
|
+
- Progressive web applications (PWAs)
|
|
14
|
+
|
|
15
|
+
### Key Principles
|
|
16
|
+
|
|
17
|
+
1. **User Experience First** - Fast, responsive, accessible
|
|
18
|
+
2. **Component-Based Architecture** - Small, focused, reusable
|
|
19
|
+
3. **Type Safety** - TypeScript for all new code
|
|
20
|
+
4. **Progressive Enhancement** - Core functionality without JS
|
|
21
|
+
|
|
22
|
+
### Project Structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
src/
|
|
26
|
+
├── components/ # Reusable UI components
|
|
27
|
+
│ ├── ui/ # Primitive components (Button, Input, Card)
|
|
28
|
+
│ └── features/ # Feature-specific components
|
|
29
|
+
├── pages/ # Page/route components
|
|
30
|
+
├── hooks/ # Custom hooks
|
|
31
|
+
├── lib/ # Business logic, utilities
|
|
32
|
+
├── types/ # TypeScript type definitions
|
|
33
|
+
├── styles/ # Global styles, theme
|
|
34
|
+
└── assets/ # Static assets
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Component Patterns
|
|
40
|
+
|
|
41
|
+
### Composition Over Inheritance
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// Good: Composition
|
|
45
|
+
const UserCard = ({ user }: { user: User }) => (
|
|
46
|
+
<Card>
|
|
47
|
+
<Avatar src={user.avatar} />
|
|
48
|
+
<CardContent>
|
|
49
|
+
<UserName name={user.name} />
|
|
50
|
+
</CardContent>
|
|
51
|
+
</Card>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Bad: Inheritance
|
|
55
|
+
class UserCard extends BaseCard { ... }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Pure Components
|
|
59
|
+
|
|
60
|
+
Same props = same output. No side effects in render.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// Good: Pure
|
|
64
|
+
const Greeting = ({ name }: { name: string }) => (
|
|
65
|
+
<h1>Hello, {name}!</h1>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Bad: Side effects
|
|
69
|
+
const Greeting = ({ name }) => {
|
|
70
|
+
document.title = name; // Side effect!
|
|
71
|
+
return <h1>Hello, {name}!</h1>;
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Props Interface
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
interface ButtonProps {
|
|
79
|
+
variant: 'primary' | 'secondary' | 'danger';
|
|
80
|
+
size?: 'sm' | 'md' | 'lg';
|
|
81
|
+
disabled?: boolean;
|
|
82
|
+
onClick: () => void;
|
|
83
|
+
children: React.ReactNode;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const Button = ({ variant, size = 'md', ...props }: ButtonProps) => ( ... );
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Conditional Rendering
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
// Early return for guards
|
|
93
|
+
const UserProfile = ({ user }: { user: User | null }) => {
|
|
94
|
+
if (!user) return <NotLoggedIn />;
|
|
95
|
+
return <Profile user={user} />;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Ternary for simple conditions
|
|
99
|
+
const Status = ({ isActive }: { isActive: boolean }) => (
|
|
100
|
+
<span>{isActive ? 'Active' : 'Inactive'}</span>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// && for optional rendering
|
|
104
|
+
{message && <Alert>{message}</Alert>}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## State Management
|
|
110
|
+
|
|
111
|
+
### Principles
|
|
112
|
+
|
|
113
|
+
1. **Lift State Only When Necessary** - Keep state close to where it's used
|
|
114
|
+
2. **Derive Don't Duplicate** - Calculate from existing state
|
|
115
|
+
3. **Immutable Updates** - Never mutate state directly
|
|
116
|
+
|
|
117
|
+
### Local State
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// Simple values
|
|
121
|
+
const [count, setCount] = useState(0);
|
|
122
|
+
|
|
123
|
+
// Complex state
|
|
124
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Shared State
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
// Context for global UI/auth/theme
|
|
131
|
+
const ThemeContext = createContext<Theme>('light');
|
|
132
|
+
|
|
133
|
+
const ThemeProvider = ({ children }) => {
|
|
134
|
+
const [theme, setTheme] = useState<Theme>('light');
|
|
135
|
+
return (
|
|
136
|
+
<ThemeContext.Provider value={{ theme, setTheme }}>
|
|
137
|
+
{children}
|
|
138
|
+
</ThemeContext.Provider>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Immutable Updates
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
// Good
|
|
147
|
+
setItems(items => [...items, newItem]);
|
|
148
|
+
setUser(user => ({ ...user, name: newName }));
|
|
149
|
+
|
|
150
|
+
// Bad: Mutation
|
|
151
|
+
items.push(newItem);
|
|
152
|
+
setItems(items);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Anti-Patterns
|
|
156
|
+
|
|
157
|
+
- **Prop Drilling**: Use context or composition
|
|
158
|
+
- **Over-Centralization**: Keep local state local
|
|
159
|
+
- **Stale Closures**: Use refs or functional updates
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Styling
|
|
164
|
+
|
|
165
|
+
### Principles
|
|
166
|
+
|
|
167
|
+
- Consistency via design system
|
|
168
|
+
- Maintainability via component-scoped styles
|
|
169
|
+
- Mobile-first responsive design
|
|
170
|
+
- Performance-conscious CSS
|
|
171
|
+
|
|
172
|
+
### Responsive Design
|
|
173
|
+
|
|
174
|
+
```css
|
|
175
|
+
/* Base: Mobile */
|
|
176
|
+
.container { padding: 1rem; }
|
|
177
|
+
|
|
178
|
+
/* Tablet */
|
|
179
|
+
@media (min-width: 768px) {
|
|
180
|
+
.container { padding: 2rem; }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* Desktop */
|
|
184
|
+
@media (min-width: 1024px) {
|
|
185
|
+
.container { padding: 3rem; max-width: 1200px; }
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### CSS Custom Properties
|
|
190
|
+
|
|
191
|
+
```css
|
|
192
|
+
:root {
|
|
193
|
+
--color-primary: #3b82f6;
|
|
194
|
+
--color-text: #1e293b;
|
|
195
|
+
--spacing-md: 1rem;
|
|
196
|
+
--radius-md: 0.5rem;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
[data-theme="dark"] {
|
|
200
|
+
--color-text: #f1f5f9;
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Anti-Patterns
|
|
205
|
+
|
|
206
|
+
- Avoid `!important`
|
|
207
|
+
- Avoid inline styles for theming
|
|
208
|
+
- Avoid magic numbers
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Accessibility (a11y)
|
|
213
|
+
|
|
214
|
+
### Semantic HTML
|
|
215
|
+
|
|
216
|
+
```html
|
|
217
|
+
<!-- Good -->
|
|
218
|
+
<nav><ul><li><a href="/home">Home</a></li></ul></nav>
|
|
219
|
+
<main><article><h1>Title</h1></article></main>
|
|
220
|
+
<button onClick={fn}>Submit</button>
|
|
221
|
+
|
|
222
|
+
<!-- Bad -->
|
|
223
|
+
<div class="nav"><div onclick="...">Home</div></div>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Keyboard Navigation
|
|
227
|
+
|
|
228
|
+
- All interactive elements must be keyboard accessible
|
|
229
|
+
- Visible focus indicators
|
|
230
|
+
- Skip links for main content
|
|
231
|
+
- Focus trapping for modals
|
|
232
|
+
|
|
233
|
+
### ARIA
|
|
234
|
+
|
|
235
|
+
Use only when native HTML is insufficient:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
<button aria-expanded={isOpen} aria-controls="menu">Menu</button>
|
|
239
|
+
<input aria-invalid={hasError} aria-describedby="error-msg" />
|
|
240
|
+
<div aria-live="polite">{statusMessage}</div>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Forms
|
|
244
|
+
|
|
245
|
+
Every input needs a label:
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
<label htmlFor="email">Email</label>
|
|
249
|
+
<input id="email" type="email" />
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Color and Contrast
|
|
253
|
+
|
|
254
|
+
- Normal text: 4.5:1 contrast ratio
|
|
255
|
+
- Large text: 3:1 contrast ratio
|
|
256
|
+
- Don't rely on color alone
|
|
257
|
+
|
|
258
|
+
### Testing Checklist
|
|
259
|
+
|
|
260
|
+
- [ ] Navigate with keyboard only
|
|
261
|
+
- [ ] Test with screen reader
|
|
262
|
+
- [ ] Check color contrast
|
|
263
|
+
- [ ] Verify focus indicators
|
|
264
|
+
- [ ] Test at 200% zoom
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Testing
|
|
269
|
+
|
|
270
|
+
### Philosophy
|
|
271
|
+
|
|
272
|
+
Test user behavior, not implementation details.
|
|
273
|
+
|
|
274
|
+
### Unit Tests (Pure Functions)
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
describe('formatCurrency', () => {
|
|
278
|
+
it('formats USD correctly', () => {
|
|
279
|
+
expect(formatCurrency(1234.56, 'USD')).toBe('$1,234.56');
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Component Tests
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
import { render, screen } from '@testing-library/react';
|
|
288
|
+
import userEvent from '@testing-library/user-event';
|
|
289
|
+
|
|
290
|
+
test('shows error when form is invalid', async () => {
|
|
291
|
+
render(<LoginForm />);
|
|
292
|
+
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
|
|
293
|
+
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Accessibility Tests
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
301
|
+
|
|
302
|
+
test('has no a11y violations', async () => {
|
|
303
|
+
const { container } = render(<Button>Click</Button>);
|
|
304
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Query Priority
|
|
309
|
+
|
|
310
|
+
1. `getByRole` - Most accessible
|
|
311
|
+
2. `getByLabelText` - For form fields
|
|
312
|
+
3. `getByText` - For content
|
|
313
|
+
4. `getByTestId` - Last resort
|
|
314
|
+
|
|
315
|
+
### E2E Tests (Critical Paths)
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
test('user can complete purchase', async ({ page }) => {
|
|
319
|
+
await page.goto('/products');
|
|
320
|
+
await page.click('text=Add to Cart');
|
|
321
|
+
await page.click('text=Checkout');
|
|
322
|
+
await expect(page.locator('text=Order Confirmed')).toBeVisible();
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Performance
|
|
329
|
+
|
|
330
|
+
### Core Web Vitals
|
|
331
|
+
|
|
332
|
+
- **LCP** (Largest Contentful Paint): < 2.5s
|
|
333
|
+
- **FID/INP** (Interactivity): < 100ms / < 200ms
|
|
334
|
+
- **CLS** (Layout Shift): < 0.1
|
|
335
|
+
|
|
336
|
+
### Code Splitting
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
const Dashboard = lazy(() => import('./pages/Dashboard'));
|
|
340
|
+
|
|
341
|
+
<Suspense fallback={<Loading />}>
|
|
342
|
+
<Dashboard />
|
|
343
|
+
</Suspense>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Minimize Bundle Size
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
// Bad: Import entire library
|
|
350
|
+
import _ from 'lodash';
|
|
351
|
+
|
|
352
|
+
// Good: Import specific function
|
|
353
|
+
import debounce from 'lodash/debounce';
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Prevent Re-renders
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
// Memoize expensive components
|
|
360
|
+
const ExpensiveList = memo(({ items }) => ...);
|
|
361
|
+
|
|
362
|
+
// Memoize calculations
|
|
363
|
+
const sorted = useMemo(() => items.sort(...), [items]);
|
|
364
|
+
|
|
365
|
+
// Memoize callbacks
|
|
366
|
+
const handleClick = useCallback(() => ..., []);
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Virtualize Long Lists
|
|
370
|
+
|
|
371
|
+
Use virtualization libraries for lists with 100+ items.
|
|
372
|
+
|
|
373
|
+
### Image Optimization
|
|
374
|
+
|
|
375
|
+
- Use modern formats (WebP, AVIF)
|
|
376
|
+
- Lazy load images
|
|
377
|
+
- Always specify dimensions
|
|
378
|
+
|
|
379
|
+
### Performance Budgets
|
|
380
|
+
|
|
381
|
+
- JavaScript: < 200KB gzipped
|
|
382
|
+
- CSS: < 50KB gzipped
|
|
383
|
+
- LCP: < 2.5s
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Definition of Done
|
|
388
|
+
|
|
389
|
+
A frontend feature is complete when:
|
|
390
|
+
|
|
391
|
+
- [ ] Component renders correctly
|
|
392
|
+
- [ ] Responsive across breakpoints
|
|
393
|
+
- [ ] Keyboard navigable
|
|
394
|
+
- [ ] Screen reader accessible
|
|
395
|
+
- [ ] Loading and error states handled
|
|
396
|
+
- [ ] Tests written and passing
|
|
397
|
+
- [ ] No TypeScript errors
|
|
398
|
+
- [ ] Meets performance budgets
|
|
399
|
+
- [ ] Code reviewed and approved
|