metacoding 1.5.0 → 2.0.1
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/CHANGELOG.md +59 -0
- package/README.md +108 -514
- package/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +18 -19
- package/lib/cli.js.map +1 -1
- package/lib/commands/init.d.ts +8 -14
- package/lib/commands/init.d.ts.map +1 -1
- package/lib/commands/init.js +105 -387
- package/lib/commands/init.js.map +1 -1
- package/lib/commands/update.d.ts +9 -9
- package/lib/commands/update.d.ts.map +1 -1
- package/lib/commands/update.js +141 -320
- package/lib/commands/update.js.map +1 -1
- package/lib/services/backup.d.ts +1 -1
- package/lib/services/backup.d.ts.map +1 -1
- package/lib/services/backup.js +10 -6
- package/lib/services/backup.js.map +1 -1
- package/lib/services/filesystem.d.ts.map +1 -1
- package/lib/services/filesystem.js +11 -5
- package/lib/services/filesystem.js.map +1 -1
- package/lib/services/gitignore-manager.js +5 -5
- package/lib/services/gitignore-manager.js.map +1 -1
- package/lib/services/project-detector.d.ts +9 -8
- package/lib/services/project-detector.d.ts.map +1 -1
- package/lib/services/project-detector.js +79 -197
- package/lib/services/project-detector.js.map +1 -1
- package/lib/services/skill-manager.d.ts +23 -0
- package/lib/services/skill-manager.d.ts.map +1 -0
- package/lib/services/skill-manager.js +212 -0
- package/lib/services/skill-manager.js.map +1 -0
- package/lib/types/index.d.ts +5 -15
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +9 -17
- package/skills/metacoding-workflow/SKILL.md +52 -0
- package/skills/metacoding-workflow/agents/openai.yaml +4 -0
- package/skills/metacoding-workflow/assets/templates/changelog-entry.md +6 -0
- package/skills/metacoding-workflow/assets/templates/project-context.md +18 -0
- package/skills/metacoding-workflow/assets/templates/repeated-task-checklist.md +8 -0
- package/skills/metacoding-workflow/assets/templates/task-entry.md +9 -0
- package/skills/metacoding-workflow/assets/templates/test-plan.md +8 -0
- package/skills/metacoding-workflow/references/javascript.md +7 -0
- package/skills/metacoding-workflow/references/node.md +7 -0
- package/skills/metacoding-workflow/references/platform-adaptation.md +37 -0
- package/skills/metacoding-workflow/references/python.md +7 -0
- package/skills/metacoding-workflow/references/react.md +7 -0
- package/skills/metacoding-workflow/references/repository-organization.md +84 -0
- package/skills/metacoding-workflow/references/typescript.md +7 -0
- package/skills/metacoding-workflow/references/workflow-rules.md +54 -0
- package/skills/vendor-templates/claude-agent.md.template +41 -0
- package/lib/services/assistant-adapter.d.ts +0 -18
- package/lib/services/assistant-adapter.d.ts.map +0 -1
- package/lib/services/assistant-adapter.js +0 -246
- package/lib/services/assistant-adapter.js.map +0 -1
- package/lib/services/cursor.d.ts +0 -47
- package/lib/services/cursor.d.ts.map +0 -1
- package/lib/services/cursor.js +0 -314
- package/lib/services/cursor.js.map +0 -1
- package/lib/services/template-manager.d.ts +0 -23
- package/lib/services/template-manager.d.ts.map +0 -1
- package/lib/services/template-manager.js +0 -374
- package/lib/services/template-manager.js.map +0 -1
- package/lib/services/vscode.d.ts +0 -10
- package/lib/services/vscode.d.ts.map +0 -1
- package/lib/services/vscode.js +0 -108
- package/lib/services/vscode.js.map +0 -1
- package/templates/assistants/AGENTS.md +0 -203
- package/templates/assistants/CLAUDE.md +0 -156
- package/templates/assistants/GEMINI.md +0 -193
- package/templates/general/code-review.instructions.md +0 -265
- package/templates/general/copilot-instructions.md +0 -427
- package/templates/general/docs-update.instructions.md +0 -275
- package/templates/general/release.instructions.md +0 -242
- package/templates/general/template.json +0 -9
- package/templates/general/test-runner.instructions.md +0 -188
- package/templates/javascript/javascript.coding.instructions.md +0 -500
- package/templates/javascript/javascript.docs.instructions.md +0 -563
- package/templates/javascript/javascript.testing.instructions.md +0 -686
- package/templates/javascript/template.json +0 -36
- package/templates/node/nodejs.coding.instructions.md +0 -249
- package/templates/node/nodejs.docs.instructions.md +0 -261
- package/templates/node/nodejs.testing.instructions.md +0 -373
- package/templates/node/template.json +0 -23
- package/templates/python/python.coding.instructions.md +0 -338
- package/templates/python/python.docs.instructions.md +0 -1178
- package/templates/python/python.testing.instructions.md +0 -1073
- package/templates/python/template.json +0 -75
- package/templates/react/react.coding.instructions.md +0 -694
- package/templates/react/react.docs.instructions.md +0 -451
- package/templates/react/react.testing.instructions.md +0 -192
- package/templates/react/template.json +0 -14
- package/templates/react/test-runner.instructions.md +0 -135
- package/templates/typescript/template.json +0 -16
- package/templates/typescript/typescript.coding.instructions.md +0 -368
- package/templates/typescript/typescript.docs.instructions.md +0 -760
- package/templates/typescript/typescript.testing.instructions.md +0 -739
|
@@ -1,694 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: 'React/Frontend-specific coding standards and best practices'
|
|
3
|
-
applyTo: '**/*.{tsx,jsx,ts,js}'
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# React/Frontend Coding Standards and Best Practices
|
|
7
|
-
|
|
8
|
-
## Language and Framework Preferences
|
|
9
|
-
|
|
10
|
-
- **Primary Language:** TypeScript with React 18+ for all React projects
|
|
11
|
-
- **Build Tool:** Vite for development and build tooling
|
|
12
|
-
- **Code Style:** Prettier with ESLint for consistent formatting and linting
|
|
13
|
-
- **State Management:** Context API for simple state, Zustand/Redux Toolkit for complex state
|
|
14
|
-
- **Target Compatibility:** Modern browsers (ES2020+), Node.js 18+
|
|
15
|
-
|
|
16
|
-
## Code Quality Guidelines
|
|
17
|
-
|
|
18
|
-
- **Component Design:** Small, focused components with single responsibilities
|
|
19
|
-
- **Functions:** Prefer functional components with hooks over class components
|
|
20
|
-
- **Performance:** Use React.memo, useMemo, and useCallback judiciously
|
|
21
|
-
- **Error Handling:** Implement error boundaries and proper error handling
|
|
22
|
-
- **Accessibility:** Follow WCAG guidelines and use semantic HTML
|
|
23
|
-
- **Type Safety:** Strict TypeScript configuration with comprehensive type coverage
|
|
24
|
-
|
|
25
|
-
## Naming Conventions
|
|
26
|
-
|
|
27
|
-
- **Files:** PascalCase for components (e.g., `UserProfile.tsx`), camelCase for utilities (e.g., `apiHelpers.ts`)
|
|
28
|
-
- **Components:** PascalCase (e.g., `UserCard`, `NavigationMenu`)
|
|
29
|
-
- **Functions/Hooks:** camelCase (e.g., `useUserData`, `handleSubmit`)
|
|
30
|
-
- **Variables:** camelCase (e.g., `userData`, `isLoading`)
|
|
31
|
-
- **Constants:** SCREAMING_SNAKE_CASE (e.g., `API_ENDPOINTS`, `DEFAULT_TIMEOUT`)
|
|
32
|
-
- **Interfaces/Types:** PascalCase with descriptive names (e.g., `UserData`, `ApiResponse`)
|
|
33
|
-
- **CSS Classes:** kebab-case (e.g., `user-card`, `navigation-menu`)
|
|
34
|
-
|
|
35
|
-
## Code Organization
|
|
36
|
-
|
|
37
|
-
- **Feature-Based Structure:** Organize by features rather than file types
|
|
38
|
-
- **Component Co-location:** Keep related files (component, styles, tests) together
|
|
39
|
-
- **Barrel Exports:** Use index.ts files for clean imports
|
|
40
|
-
- **Separation of Concerns:** Separate business logic from presentation logic
|
|
41
|
-
|
|
42
|
-
### Recommended Project Structure
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
src/
|
|
46
|
-
components/ # Reusable UI components
|
|
47
|
-
ui/ # Basic UI primitives (Button, Input, Modal)
|
|
48
|
-
layout/ # Layout components (Header, Sidebar, Footer)
|
|
49
|
-
features/ # Feature-specific components and logic
|
|
50
|
-
auth/ # Authentication feature
|
|
51
|
-
dashboard/ # Dashboard feature
|
|
52
|
-
hooks/ # Custom React hooks
|
|
53
|
-
services/ # API calls and external services
|
|
54
|
-
utils/ # Utility functions
|
|
55
|
-
types/ # TypeScript type definitions
|
|
56
|
-
stores/ # State management (Context, Zustand, etc.)
|
|
57
|
-
assets/ # Static assets (images, icons, fonts)
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## React-Specific Best Practices
|
|
61
|
-
|
|
62
|
-
### Component Design Patterns
|
|
63
|
-
|
|
64
|
-
```tsx
|
|
65
|
-
// Good: Functional component with TypeScript
|
|
66
|
-
interface UserCardProps {
|
|
67
|
-
user: User;
|
|
68
|
-
onEdit?: (user: User) => void;
|
|
69
|
-
className?: string;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export const UserCard: React.FC<UserCardProps> = ({
|
|
73
|
-
user,
|
|
74
|
-
onEdit,
|
|
75
|
-
className,
|
|
76
|
-
}) => {
|
|
77
|
-
const handleEditClick = useCallback(() => {
|
|
78
|
-
onEdit?.(user);
|
|
79
|
-
}, [user, onEdit]);
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<div className={`user-card ${className || ''}`}>
|
|
83
|
-
<h3>{user.name}</h3>
|
|
84
|
-
<p>{user.email}</p>
|
|
85
|
-
{onEdit && <button onClick={handleEditClick}>Edit</button>}
|
|
86
|
-
</div>
|
|
87
|
-
);
|
|
88
|
-
};
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Custom Hooks
|
|
92
|
-
|
|
93
|
-
```tsx
|
|
94
|
-
// Good: Custom hook for data fetching
|
|
95
|
-
interface UseUserDataResult {
|
|
96
|
-
user: User | null;
|
|
97
|
-
loading: boolean;
|
|
98
|
-
error: string | null;
|
|
99
|
-
refetch: () => void;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export const useUserData = (userId: string): UseUserDataResult => {
|
|
103
|
-
const [user, setUser] = useState<User | null>(null);
|
|
104
|
-
const [loading, setLoading] = useState(true);
|
|
105
|
-
const [error, setError] = useState<string | null>(null);
|
|
106
|
-
|
|
107
|
-
const fetchUser = useCallback(async () => {
|
|
108
|
-
try {
|
|
109
|
-
setLoading(true);
|
|
110
|
-
setError(null);
|
|
111
|
-
const userData = await userService.getUser(userId);
|
|
112
|
-
setUser(userData);
|
|
113
|
-
} catch (err) {
|
|
114
|
-
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
115
|
-
} finally {
|
|
116
|
-
setLoading(false);
|
|
117
|
-
}
|
|
118
|
-
}, [userId]);
|
|
119
|
-
|
|
120
|
-
useEffect(() => {
|
|
121
|
-
fetchUser();
|
|
122
|
-
}, [fetchUser]);
|
|
123
|
-
|
|
124
|
-
return { user, loading, error, refetch: fetchUser };
|
|
125
|
-
};
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### State Management Patterns
|
|
129
|
-
|
|
130
|
-
```tsx
|
|
131
|
-
// Good: Context for global state
|
|
132
|
-
interface AppContextType {
|
|
133
|
-
user: User | null;
|
|
134
|
-
theme: 'light' | 'dark';
|
|
135
|
-
setUser: (user: User | null) => void;
|
|
136
|
-
setTheme: (theme: 'light' | 'dark') => void;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const AppContext = createContext<AppContextType | undefined>(undefined);
|
|
140
|
-
|
|
141
|
-
export const useAppContext = (): AppContextType => {
|
|
142
|
-
const context = useContext(AppContext);
|
|
143
|
-
if (!context) {
|
|
144
|
-
throw new Error('useAppContext must be used within AppProvider');
|
|
145
|
-
}
|
|
146
|
-
return context;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
150
|
-
children,
|
|
151
|
-
}) => {
|
|
152
|
-
const [user, setUser] = useState<User | null>(null);
|
|
153
|
-
const [theme, setTheme] = useState<'light' | 'dark'>('light');
|
|
154
|
-
|
|
155
|
-
const value = useMemo(
|
|
156
|
-
() => ({
|
|
157
|
-
user,
|
|
158
|
-
theme,
|
|
159
|
-
setUser,
|
|
160
|
-
setTheme,
|
|
161
|
-
}),
|
|
162
|
-
[user, theme]
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
|
166
|
-
};
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## Performance Optimization
|
|
170
|
-
|
|
171
|
-
### Component Optimization
|
|
172
|
-
|
|
173
|
-
```tsx
|
|
174
|
-
// Good: Memoized component to prevent unnecessary re-renders
|
|
175
|
-
interface UserListProps {
|
|
176
|
-
users: User[];
|
|
177
|
-
onUserSelect: (user: User) => void;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export const UserList = React.memo<UserListProps>(({ users, onUserSelect }) => {
|
|
181
|
-
return (
|
|
182
|
-
<div className="user-list">
|
|
183
|
-
{users.map((user) => (
|
|
184
|
-
<UserCard key={user.id} user={user} onEdit={onUserSelect} />
|
|
185
|
-
))}
|
|
186
|
-
</div>
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
UserList.displayName = 'UserList';
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### Optimized Callbacks and Values
|
|
194
|
-
|
|
195
|
-
```tsx
|
|
196
|
-
// Good: Memoized callbacks and computed values
|
|
197
|
-
export const Dashboard: React.FC = () => {
|
|
198
|
-
const [filter, setFilter] = useState('');
|
|
199
|
-
const [users, setUsers] = useState<User[]>([]);
|
|
200
|
-
|
|
201
|
-
// Memoize expensive computations
|
|
202
|
-
const filteredUsers = useMemo(() => {
|
|
203
|
-
return users.filter((user) =>
|
|
204
|
-
user.name.toLowerCase().includes(filter.toLowerCase())
|
|
205
|
-
);
|
|
206
|
-
}, [users, filter]);
|
|
207
|
-
|
|
208
|
-
// Memoize callbacks to prevent child re-renders
|
|
209
|
-
const handleUserSelect = useCallback((user: User) => {
|
|
210
|
-
console.log('Selected user:', user);
|
|
211
|
-
}, []);
|
|
212
|
-
|
|
213
|
-
const handleFilterChange = useCallback(
|
|
214
|
-
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
215
|
-
setFilter(event.target.value);
|
|
216
|
-
},
|
|
217
|
-
[]
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
return (
|
|
221
|
-
<div>
|
|
222
|
-
<input
|
|
223
|
-
value={filter}
|
|
224
|
-
onChange={handleFilterChange}
|
|
225
|
-
placeholder="Filter users..."
|
|
226
|
-
/>
|
|
227
|
-
<UserList users={filteredUsers} onUserSelect={handleUserSelect} />
|
|
228
|
-
</div>
|
|
229
|
-
);
|
|
230
|
-
};
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
## Error Handling and Validation
|
|
234
|
-
|
|
235
|
-
### Error Boundaries
|
|
236
|
-
|
|
237
|
-
```tsx
|
|
238
|
-
interface ErrorBoundaryState {
|
|
239
|
-
hasError: boolean;
|
|
240
|
-
error?: Error;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
class ErrorBoundary extends React.Component<
|
|
244
|
-
React.PropsWithChildren<{}>,
|
|
245
|
-
ErrorBoundaryState
|
|
246
|
-
> {
|
|
247
|
-
constructor(props: React.PropsWithChildren<{}>) {
|
|
248
|
-
super(props);
|
|
249
|
-
this.state = { hasError: false };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
253
|
-
return { hasError: true, error };
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
257
|
-
console.error('Error caught by boundary:', error, errorInfo);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
render() {
|
|
261
|
-
if (this.state.hasError) {
|
|
262
|
-
return (
|
|
263
|
-
<div className="error-boundary">
|
|
264
|
-
<h2>Something went wrong</h2>
|
|
265
|
-
<p>{this.state.error?.message}</p>
|
|
266
|
-
</div>
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return this.props.children;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Form Validation
|
|
276
|
-
|
|
277
|
-
```tsx
|
|
278
|
-
// Good: Form with validation using react-hook-form
|
|
279
|
-
import { useForm } from 'react-hook-form';
|
|
280
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
281
|
-
import { z } from 'zod';
|
|
282
|
-
|
|
283
|
-
const userSchema = z.object({
|
|
284
|
-
name: z.string().min(2, 'Name must be at least 2 characters'),
|
|
285
|
-
email: z.string().email('Invalid email address'),
|
|
286
|
-
age: z.number().min(18, 'Must be at least 18 years old'),
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
type UserFormData = z.infer<typeof userSchema>;
|
|
290
|
-
|
|
291
|
-
export const UserForm: React.FC = () => {
|
|
292
|
-
const {
|
|
293
|
-
register,
|
|
294
|
-
handleSubmit,
|
|
295
|
-
formState: { errors, isSubmitting },
|
|
296
|
-
} = useForm<UserFormData>({
|
|
297
|
-
resolver: zodResolver(userSchema),
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const onSubmit = async (data: UserFormData) => {
|
|
301
|
-
try {
|
|
302
|
-
await userService.createUser(data);
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error('Failed to create user:', error);
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
return (
|
|
309
|
-
<form onSubmit={handleSubmit(onSubmit)}>
|
|
310
|
-
<div>
|
|
311
|
-
<input {...register('name')} placeholder="Name" />
|
|
312
|
-
{errors.name && <p className="error">{errors.name.message}</p>}
|
|
313
|
-
</div>
|
|
314
|
-
|
|
315
|
-
<div>
|
|
316
|
-
<input {...register('email')} placeholder="Email" />
|
|
317
|
-
{errors.email && <p className="error">{errors.email.message}</p>}
|
|
318
|
-
</div>
|
|
319
|
-
|
|
320
|
-
<div>
|
|
321
|
-
<input
|
|
322
|
-
{...register('age', { valueAsNumber: true })}
|
|
323
|
-
type="number"
|
|
324
|
-
placeholder="Age"
|
|
325
|
-
/>
|
|
326
|
-
{errors.age && <p className="error">{errors.age.message}</p>}
|
|
327
|
-
</div>
|
|
328
|
-
|
|
329
|
-
<button type="submit" disabled={isSubmitting}>
|
|
330
|
-
{isSubmitting ? 'Creating...' : 'Create User'}
|
|
331
|
-
</button>
|
|
332
|
-
</form>
|
|
333
|
-
);
|
|
334
|
-
};
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
## Testing Standards
|
|
338
|
-
|
|
339
|
-
### Component Testing
|
|
340
|
-
|
|
341
|
-
```tsx
|
|
342
|
-
// Good: Component testing with React Testing Library
|
|
343
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
344
|
-
import { UserCard } from './UserCard';
|
|
345
|
-
|
|
346
|
-
const mockUser: User = {
|
|
347
|
-
id: '1',
|
|
348
|
-
name: 'John Doe',
|
|
349
|
-
email: 'john@example.com',
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
describe('UserCard', () => {
|
|
353
|
-
it('renders user information correctly', () => {
|
|
354
|
-
render(<UserCard user={mockUser} />);
|
|
355
|
-
|
|
356
|
-
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
357
|
-
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it('calls onEdit when edit button is clicked', () => {
|
|
361
|
-
const mockOnEdit = jest.fn();
|
|
362
|
-
render(<UserCard user={mockUser} onEdit={mockOnEdit} />);
|
|
363
|
-
|
|
364
|
-
const editButton = screen.getByText('Edit');
|
|
365
|
-
fireEvent.click(editButton);
|
|
366
|
-
|
|
367
|
-
expect(mockOnEdit).toHaveBeenCalledWith(mockUser);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it('does not render edit button when onEdit is not provided', () => {
|
|
371
|
-
render(<UserCard user={mockUser} />);
|
|
372
|
-
|
|
373
|
-
expect(screen.queryByText('Edit')).not.toBeInTheDocument();
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
### Hook Testing
|
|
379
|
-
|
|
380
|
-
```tsx
|
|
381
|
-
// Good: Custom hook testing
|
|
382
|
-
import { renderHook, waitFor } from '@testing-library/react';
|
|
383
|
-
import { useUserData } from './useUserData';
|
|
384
|
-
|
|
385
|
-
// Mock the service
|
|
386
|
-
jest.mock('../services/userService');
|
|
387
|
-
|
|
388
|
-
describe('useUserData', () => {
|
|
389
|
-
beforeEach(() => {
|
|
390
|
-
jest.clearAllMocks();
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
it('fetches user data successfully', async () => {
|
|
394
|
-
const mockUser = { id: '1', name: 'John Doe' };
|
|
395
|
-
(userService.getUser as jest.Mock).mockResolvedValue(mockUser);
|
|
396
|
-
|
|
397
|
-
const { result } = renderHook(() => useUserData('1'));
|
|
398
|
-
|
|
399
|
-
expect(result.current.loading).toBe(true);
|
|
400
|
-
expect(result.current.user).toBe(null);
|
|
401
|
-
|
|
402
|
-
await waitFor(() => {
|
|
403
|
-
expect(result.current.loading).toBe(false);
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
expect(result.current.user).toEqual(mockUser);
|
|
407
|
-
expect(result.current.error).toBe(null);
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
it('handles error states correctly', async () => {
|
|
411
|
-
const errorMessage = 'User not found';
|
|
412
|
-
(userService.getUser as jest.Mock).mockRejectedValue(
|
|
413
|
-
new Error(errorMessage)
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
const { result } = renderHook(() => useUserData('1'));
|
|
417
|
-
|
|
418
|
-
await waitFor(() => {
|
|
419
|
-
expect(result.current.loading).toBe(false);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
expect(result.current.user).toBe(null);
|
|
423
|
-
expect(result.current.error).toBe(errorMessage);
|
|
424
|
-
});
|
|
425
|
-
});
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
## Accessibility Standards
|
|
429
|
-
|
|
430
|
-
### Semantic HTML and ARIA
|
|
431
|
-
|
|
432
|
-
```tsx
|
|
433
|
-
// Good: Accessible form component
|
|
434
|
-
export const AccessibleForm: React.FC = () => {
|
|
435
|
-
const [fieldError, setFieldError] = useState<string>('');
|
|
436
|
-
|
|
437
|
-
return (
|
|
438
|
-
<form role="form" aria-label="User registration form">
|
|
439
|
-
<fieldset>
|
|
440
|
-
<legend>Personal Information</legend>
|
|
441
|
-
|
|
442
|
-
<div className="form-group">
|
|
443
|
-
<label htmlFor="username">Username:</label>
|
|
444
|
-
<input
|
|
445
|
-
id="username"
|
|
446
|
-
type="text"
|
|
447
|
-
aria-required="true"
|
|
448
|
-
aria-describedby={fieldError ? 'username-error' : undefined}
|
|
449
|
-
aria-invalid={fieldError ? 'true' : 'false'}
|
|
450
|
-
/>
|
|
451
|
-
{fieldError && (
|
|
452
|
-
<div
|
|
453
|
-
id="username-error"
|
|
454
|
-
role="alert"
|
|
455
|
-
aria-live="polite"
|
|
456
|
-
className="error-message"
|
|
457
|
-
>
|
|
458
|
-
{fieldError}
|
|
459
|
-
</div>
|
|
460
|
-
)}
|
|
461
|
-
</div>
|
|
462
|
-
</fieldset>
|
|
463
|
-
|
|
464
|
-
<button type="submit" aria-describedby="submit-help">
|
|
465
|
-
Register
|
|
466
|
-
</button>
|
|
467
|
-
<div id="submit-help" className="help-text">
|
|
468
|
-
Click to create your account
|
|
469
|
-
</div>
|
|
470
|
-
</form>
|
|
471
|
-
);
|
|
472
|
-
};
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
### Keyboard Navigation
|
|
476
|
-
|
|
477
|
-
```tsx
|
|
478
|
-
// Good: Keyboard accessible dropdown
|
|
479
|
-
export const Dropdown: React.FC<DropdownProps> = ({ options, onSelect }) => {
|
|
480
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
481
|
-
const [activeIndex, setActiveIndex] = useState(-1);
|
|
482
|
-
|
|
483
|
-
const handleKeyDown = (event: React.KeyboardEvent) => {
|
|
484
|
-
switch (event.key) {
|
|
485
|
-
case 'Enter':
|
|
486
|
-
case ' ':
|
|
487
|
-
event.preventDefault();
|
|
488
|
-
if (activeIndex >= 0) {
|
|
489
|
-
onSelect(options[activeIndex]);
|
|
490
|
-
}
|
|
491
|
-
setIsOpen(!isOpen);
|
|
492
|
-
break;
|
|
493
|
-
case 'ArrowDown':
|
|
494
|
-
event.preventDefault();
|
|
495
|
-
setActiveIndex((prev) => (prev < options.length - 1 ? prev + 1 : 0));
|
|
496
|
-
break;
|
|
497
|
-
case 'ArrowUp':
|
|
498
|
-
event.preventDefault();
|
|
499
|
-
setActiveIndex((prev) => (prev > 0 ? prev - 1 : options.length - 1));
|
|
500
|
-
break;
|
|
501
|
-
case 'Escape':
|
|
502
|
-
setIsOpen(false);
|
|
503
|
-
setActiveIndex(-1);
|
|
504
|
-
break;
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
return (
|
|
509
|
-
<div className="dropdown" onKeyDown={handleKeyDown}>
|
|
510
|
-
<button
|
|
511
|
-
aria-haspopup="listbox"
|
|
512
|
-
aria-expanded={isOpen}
|
|
513
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
514
|
-
>
|
|
515
|
-
Select option
|
|
516
|
-
</button>
|
|
517
|
-
{isOpen && (
|
|
518
|
-
<ul role="listbox" className="dropdown-menu">
|
|
519
|
-
{options.map((option, index) => (
|
|
520
|
-
<li
|
|
521
|
-
key={option.id}
|
|
522
|
-
role="option"
|
|
523
|
-
aria-selected={index === activeIndex}
|
|
524
|
-
className={index === activeIndex ? 'active' : ''}
|
|
525
|
-
onClick={() => onSelect(option)}
|
|
526
|
-
>
|
|
527
|
-
{option.label}
|
|
528
|
-
</li>
|
|
529
|
-
))}
|
|
530
|
-
</ul>
|
|
531
|
-
)}
|
|
532
|
-
</div>
|
|
533
|
-
);
|
|
534
|
-
};
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
## Styling and CSS Standards
|
|
538
|
-
|
|
539
|
-
### CSS-in-JS with Styled Components
|
|
540
|
-
|
|
541
|
-
```tsx
|
|
542
|
-
import styled from 'styled-components';
|
|
543
|
-
|
|
544
|
-
// Good: Styled component with theme support
|
|
545
|
-
const Button = styled.button<{ variant?: 'primary' | 'secondary' }>`
|
|
546
|
-
padding: ${({ theme }) => theme.spacing.md};
|
|
547
|
-
border: none;
|
|
548
|
-
border-radius: ${({ theme }) => theme.borderRadius.sm};
|
|
549
|
-
font-weight: 500;
|
|
550
|
-
cursor: pointer;
|
|
551
|
-
transition: all 0.2s ease;
|
|
552
|
-
|
|
553
|
-
${({ variant = 'primary', theme }) => {
|
|
554
|
-
switch (variant) {
|
|
555
|
-
case 'primary':
|
|
556
|
-
return `
|
|
557
|
-
background-color: ${theme.colors.primary};
|
|
558
|
-
color: ${theme.colors.white};
|
|
559
|
-
|
|
560
|
-
&:hover {
|
|
561
|
-
background-color: ${theme.colors.primaryDark};
|
|
562
|
-
}
|
|
563
|
-
`;
|
|
564
|
-
case 'secondary':
|
|
565
|
-
return `
|
|
566
|
-
background-color: ${theme.colors.secondary};
|
|
567
|
-
color: ${theme.colors.text};
|
|
568
|
-
|
|
569
|
-
&:hover {
|
|
570
|
-
background-color: ${theme.colors.secondaryDark};
|
|
571
|
-
}
|
|
572
|
-
`;
|
|
573
|
-
}
|
|
574
|
-
}}
|
|
575
|
-
|
|
576
|
-
&:disabled {
|
|
577
|
-
opacity: 0.6;
|
|
578
|
-
cursor: not-allowed;
|
|
579
|
-
}
|
|
580
|
-
`;
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
### CSS Modules
|
|
584
|
-
|
|
585
|
-
```tsx
|
|
586
|
-
// UserCard.module.css
|
|
587
|
-
.userCard {
|
|
588
|
-
border: 1px solid #e0e0e0;
|
|
589
|
-
border-radius: 8px;
|
|
590
|
-
padding: 16px;
|
|
591
|
-
margin-bottom: 16px;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
.userCard:hover {
|
|
595
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
.userInfo {
|
|
599
|
-
margin-bottom: 12px;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
.userName {
|
|
603
|
-
font-size: 1.2em;
|
|
604
|
-
font-weight: bold;
|
|
605
|
-
margin-bottom: 4px;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// UserCard.tsx
|
|
609
|
-
import styles from './UserCard.module.css';
|
|
610
|
-
|
|
611
|
-
export const UserCard: React.FC<UserCardProps> = ({ user }) => {
|
|
612
|
-
return (
|
|
613
|
-
<div className={styles.userCard}>
|
|
614
|
-
<div className={styles.userInfo}>
|
|
615
|
-
<h3 className={styles.userName}>{user.name}</h3>
|
|
616
|
-
<p>{user.email}</p>
|
|
617
|
-
</div>
|
|
618
|
-
</div>
|
|
619
|
-
);
|
|
620
|
-
};
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
## Common Anti-Patterns to Avoid
|
|
624
|
-
|
|
625
|
-
- **Inline Styles for Complex Styling:** Use CSS-in-JS or CSS modules instead
|
|
626
|
-
- **Prop Drilling:** Use Context or state management for deeply nested props
|
|
627
|
-
- **Mutating Props:** Always treat props as read-only
|
|
628
|
-
- **Missing Keys in Lists:** Always provide unique keys for list items
|
|
629
|
-
- **Not Cleaning Up Effects:** Remove event listeners and cancel async operations
|
|
630
|
-
- **Using Index as Key:** Use stable, unique identifiers for keys
|
|
631
|
-
- **Overusing useEffect:** Consider if the effect is really necessary
|
|
632
|
-
- **Not Memoizing Expensive Calculations:** Use useMemo for expensive computations
|
|
633
|
-
- **Creating Objects/Functions in Render:** This causes unnecessary re-renders
|
|
634
|
-
- **Ignoring Accessibility:** Always consider screen readers and keyboard navigation
|
|
635
|
-
|
|
636
|
-
## Security Considerations
|
|
637
|
-
|
|
638
|
-
### XSS Prevention
|
|
639
|
-
|
|
640
|
-
```tsx
|
|
641
|
-
// Good: Safe rendering of user content
|
|
642
|
-
import DOMPurify from 'dompurify';
|
|
643
|
-
|
|
644
|
-
interface SafeHtmlProps {
|
|
645
|
-
content: string;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
export const SafeHtml: React.FC<SafeHtmlProps> = ({ content }) => {
|
|
649
|
-
const sanitizedContent = useMemo(() => {
|
|
650
|
-
return DOMPurify.sanitize(content);
|
|
651
|
-
}, [content]);
|
|
652
|
-
|
|
653
|
-
return <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />;
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
// Good: Escape user input in URL parameters
|
|
657
|
-
const SearchResults: React.FC = () => {
|
|
658
|
-
const query = useSearchParams().get('q') || '';
|
|
659
|
-
const encodedQuery = encodeURIComponent(query);
|
|
660
|
-
|
|
661
|
-
return (
|
|
662
|
-
<div>
|
|
663
|
-
<h2>Results for: {query}</h2>
|
|
664
|
-
<a href={`/search/advanced?query=${encodedQuery}`}>Advanced search</a>
|
|
665
|
-
</div>
|
|
666
|
-
);
|
|
667
|
-
};
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
### Environment Variables
|
|
671
|
-
|
|
672
|
-
```tsx
|
|
673
|
-
// Good: Environment configuration
|
|
674
|
-
interface Config {
|
|
675
|
-
apiUrl: string;
|
|
676
|
-
environment: 'development' | 'staging' | 'production';
|
|
677
|
-
enableAnalytics: boolean;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
const config: Config = {
|
|
681
|
-
apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:3000',
|
|
682
|
-
environment:
|
|
683
|
-
(import.meta.env.VITE_ENVIRONMENT as Config['environment']) ||
|
|
684
|
-
'development',
|
|
685
|
-
enableAnalytics: import.meta.env.VITE_ENABLE_ANALYTICS === 'true',
|
|
686
|
-
};
|
|
687
|
-
|
|
688
|
-
// Validate required environment variables
|
|
689
|
-
if (!config.apiUrl) {
|
|
690
|
-
throw new Error('VITE_API_URL environment variable is required');
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
export default config;
|
|
694
|
-
```
|