@corbat-tech/coding-standards-mcp 1.0.3 → 2.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.
- package/README.md +233 -337
- package/dist/agent.d.ts +5 -6
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +95 -217
- package/dist/agent.js.map +1 -1
- package/dist/analysis/code-analyzer.d.ts +44 -0
- package/dist/analysis/code-analyzer.d.ts.map +1 -0
- package/dist/analysis/code-analyzer.js +528 -0
- package/dist/analysis/code-analyzer.js.map +1 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +112 -0
- package/dist/errors.js.map +1 -0
- package/dist/guardrails.d.ts +35 -0
- package/dist/guardrails.d.ts.map +1 -0
- package/dist/guardrails.js +303 -0
- package/dist/guardrails.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +36 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +63 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +40 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +97 -0
- package/dist/metrics.js.map +1 -0
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +239 -108
- package/dist/profiles.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/tools/definitions.d.ts +143 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +229 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers/get-context.d.ts +12 -0
- package/dist/tools/handlers/get-context.d.ts.map +1 -0
- package/dist/tools/handlers/get-context.js +233 -0
- package/dist/tools/handlers/get-context.js.map +1 -0
- package/dist/tools/handlers/health.d.ts +11 -0
- package/dist/tools/handlers/health.d.ts.map +1 -0
- package/dist/tools/handlers/health.js +57 -0
- package/dist/tools/handlers/health.js.map +1 -0
- package/dist/tools/handlers/index.d.ts +12 -0
- package/dist/tools/handlers/index.d.ts.map +1 -0
- package/dist/tools/handlers/index.js +12 -0
- package/dist/tools/handlers/index.js.map +1 -0
- package/dist/tools/handlers/init.d.ts +12 -0
- package/dist/tools/handlers/init.d.ts.map +1 -0
- package/dist/tools/handlers/init.js +102 -0
- package/dist/tools/handlers/init.js.map +1 -0
- package/dist/tools/handlers/profiles.d.ts +11 -0
- package/dist/tools/handlers/profiles.d.ts.map +1 -0
- package/dist/tools/handlers/profiles.js +25 -0
- package/dist/tools/handlers/profiles.js.map +1 -0
- package/dist/tools/handlers/search.d.ts +12 -0
- package/dist/tools/handlers/search.d.ts.map +1 -0
- package/dist/tools/handlers/search.js +58 -0
- package/dist/tools/handlers/search.js.map +1 -0
- package/dist/tools/handlers/validate.d.ts +15 -0
- package/dist/tools/handlers/validate.d.ts.map +1 -0
- package/dist/tools/handlers/validate.js +71 -0
- package/dist/tools/handlers/validate.js.map +1 -0
- package/dist/tools/handlers/verify.d.ts +38 -0
- package/dist/tools/handlers/verify.d.ts.map +1 -0
- package/dist/tools/handlers/verify.js +172 -0
- package/dist/tools/handlers/verify.js.map +1 -0
- package/dist/tools/index.d.ts +22 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +75 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/schemas.d.ts +29 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +20 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools.js +2 -2
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +141 -71
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +92 -40
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/profiles/examples/microservice-kafka.yaml +122 -0
- package/profiles/examples/startup-fast.yaml +67 -0
- package/profiles/examples/strict-enterprise.yaml +62 -0
- package/profiles/templates/angular.yaml +614 -0
- package/profiles/templates/csharp-dotnet.yaml +529 -0
- package/profiles/templates/flutter.yaml +547 -0
- package/profiles/templates/go.yaml +1276 -0
- package/profiles/templates/java-spring-backend.yaml +326 -0
- package/profiles/templates/kotlin-spring.yaml +417 -0
- package/profiles/templates/nextjs.yaml +536 -0
- package/profiles/templates/nodejs.yaml +594 -0
- package/profiles/templates/python.yaml +546 -0
- package/profiles/templates/react.yaml +456 -0
- package/profiles/templates/rust.yaml +508 -0
- package/profiles/templates/vue.yaml +483 -0
|
@@ -329,3 +329,459 @@ api:
|
|
|
329
329
|
# ├── integration/
|
|
330
330
|
# ├── e2e/
|
|
331
331
|
# └── setup.ts
|
|
332
|
+
|
|
333
|
+
# ----------------------------------------------------------------------------
|
|
334
|
+
# CODE EXAMPLES
|
|
335
|
+
# ----------------------------------------------------------------------------
|
|
336
|
+
codeExamples:
|
|
337
|
+
functionalComponent:
|
|
338
|
+
description: "Basic functional component with TypeScript"
|
|
339
|
+
code: |
|
|
340
|
+
// components/UserCard.tsx
|
|
341
|
+
interface UserCardProps {
|
|
342
|
+
user: User;
|
|
343
|
+
onSelect?: (userId: string) => void;
|
|
344
|
+
isActive?: boolean;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export default function UserCard({ user, onSelect, isActive = false }: UserCardProps) {
|
|
348
|
+
const handleClick = () => onSelect?.(user.id);
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<article
|
|
352
|
+
className={cn('user-card', { 'user-card--active': isActive })}
|
|
353
|
+
onClick={handleClick}
|
|
354
|
+
role="button"
|
|
355
|
+
tabIndex={0}
|
|
356
|
+
aria-pressed={isActive}
|
|
357
|
+
>
|
|
358
|
+
<img src={user.avatar} alt={`${user.name}'s avatar`} />
|
|
359
|
+
<h3>{user.name}</h3>
|
|
360
|
+
<p>{user.email}</p>
|
|
361
|
+
</article>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
customHook:
|
|
366
|
+
description: "Custom hook with proper typing and cleanup"
|
|
367
|
+
code: |
|
|
368
|
+
// hooks/useDebounce.ts
|
|
369
|
+
import { useState, useEffect } from 'react';
|
|
370
|
+
|
|
371
|
+
export function useDebounce<T>(value: T, delay: number): T {
|
|
372
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
373
|
+
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
|
376
|
+
return () => clearTimeout(timer); // Cleanup
|
|
377
|
+
}, [value, delay]);
|
|
378
|
+
|
|
379
|
+
return debouncedValue;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
dataFetchingHook:
|
|
383
|
+
description: "Data fetching with TanStack Query"
|
|
384
|
+
code: |
|
|
385
|
+
// hooks/useUsers.ts
|
|
386
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
387
|
+
import { usersApi } from '@/core/api/users';
|
|
388
|
+
|
|
389
|
+
export function useUsers() {
|
|
390
|
+
return useQuery({
|
|
391
|
+
queryKey: ['users'],
|
|
392
|
+
queryFn: usersApi.getAll,
|
|
393
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function useCreateUser() {
|
|
398
|
+
const queryClient = useQueryClient();
|
|
399
|
+
|
|
400
|
+
return useMutation({
|
|
401
|
+
mutationFn: usersApi.create,
|
|
402
|
+
onSuccess: () => {
|
|
403
|
+
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
contextProvider:
|
|
409
|
+
description: "Context with TypeScript for theme/auth"
|
|
410
|
+
code: |
|
|
411
|
+
// context/AuthContext.tsx
|
|
412
|
+
interface AuthContextValue {
|
|
413
|
+
user: User | null;
|
|
414
|
+
isAuthenticated: boolean;
|
|
415
|
+
login: (credentials: Credentials) => Promise<void>;
|
|
416
|
+
logout: () => void;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
420
|
+
|
|
421
|
+
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
422
|
+
const [user, setUser] = useState<User | null>(null);
|
|
423
|
+
|
|
424
|
+
const login = async (credentials: Credentials) => {
|
|
425
|
+
const user = await authApi.login(credentials);
|
|
426
|
+
setUser(user);
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const logout = () => setUser(null);
|
|
430
|
+
|
|
431
|
+
const value: AuthContextValue = {
|
|
432
|
+
user,
|
|
433
|
+
isAuthenticated: !!user,
|
|
434
|
+
login,
|
|
435
|
+
logout,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function useAuth(): AuthContextValue {
|
|
442
|
+
const context = useContext(AuthContext);
|
|
443
|
+
if (!context) throw new Error('useAuth must be used within AuthProvider');
|
|
444
|
+
return context;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
zustandStore:
|
|
448
|
+
description: "Zustand store for global state"
|
|
449
|
+
code: |
|
|
450
|
+
// stores/cartStore.ts
|
|
451
|
+
import { create } from 'zustand';
|
|
452
|
+
import { devtools, persist } from 'zustand/middleware';
|
|
453
|
+
|
|
454
|
+
interface CartState {
|
|
455
|
+
items: CartItem[];
|
|
456
|
+
total: number;
|
|
457
|
+
addItem: (item: CartItem) => void;
|
|
458
|
+
removeItem: (itemId: string) => void;
|
|
459
|
+
clearCart: () => void;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export const useCartStore = create<CartState>()(
|
|
463
|
+
devtools(
|
|
464
|
+
persist(
|
|
465
|
+
(set, get) => ({
|
|
466
|
+
items: [],
|
|
467
|
+
total: 0,
|
|
468
|
+
|
|
469
|
+
addItem: (item) =>
|
|
470
|
+
set((state) => ({
|
|
471
|
+
items: [...state.items, item],
|
|
472
|
+
total: state.total + item.price,
|
|
473
|
+
})),
|
|
474
|
+
|
|
475
|
+
removeItem: (itemId) =>
|
|
476
|
+
set((state) => {
|
|
477
|
+
const item = state.items.find((i) => i.id === itemId);
|
|
478
|
+
return {
|
|
479
|
+
items: state.items.filter((i) => i.id !== itemId),
|
|
480
|
+
total: state.total - (item?.price ?? 0),
|
|
481
|
+
};
|
|
482
|
+
}),
|
|
483
|
+
|
|
484
|
+
clearCart: () => set({ items: [], total: 0 }),
|
|
485
|
+
}),
|
|
486
|
+
{ name: 'cart-storage' }
|
|
487
|
+
)
|
|
488
|
+
)
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
componentTest:
|
|
492
|
+
description: "Component test with React Testing Library"
|
|
493
|
+
code: |
|
|
494
|
+
// components/UserCard.test.tsx
|
|
495
|
+
import { render, screen } from '@testing-library/react';
|
|
496
|
+
import userEvent from '@testing-library/user-event';
|
|
497
|
+
import { UserCard } from './UserCard';
|
|
498
|
+
|
|
499
|
+
describe('UserCard', () => {
|
|
500
|
+
const mockUser = { id: '1', name: 'John Doe', email: 'john@example.com', avatar: '/avatar.png' };
|
|
501
|
+
|
|
502
|
+
it('renders user information', () => {
|
|
503
|
+
render(<UserCard user={mockUser} />);
|
|
504
|
+
|
|
505
|
+
expect(screen.getByRole('heading', { name: 'John Doe' })).toBeInTheDocument();
|
|
506
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
507
|
+
expect(screen.getByAltText("John Doe's avatar")).toBeInTheDocument();
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('calls onSelect when clicked', async () => {
|
|
511
|
+
const user = userEvent.setup();
|
|
512
|
+
const onSelect = vi.fn();
|
|
513
|
+
|
|
514
|
+
render(<UserCard user={mockUser} onSelect={onSelect} />);
|
|
515
|
+
await user.click(screen.getByRole('button'));
|
|
516
|
+
|
|
517
|
+
expect(onSelect).toHaveBeenCalledWith('1');
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('is keyboard accessible', async () => {
|
|
521
|
+
const user = userEvent.setup();
|
|
522
|
+
const onSelect = vi.fn();
|
|
523
|
+
|
|
524
|
+
render(<UserCard user={mockUser} onSelect={onSelect} />);
|
|
525
|
+
await user.tab();
|
|
526
|
+
await user.keyboard('{Enter}');
|
|
527
|
+
|
|
528
|
+
expect(onSelect).toHaveBeenCalled();
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
errorBoundary:
|
|
533
|
+
description: "Error boundary for graceful error handling"
|
|
534
|
+
code: |
|
|
535
|
+
// components/ErrorBoundary.tsx
|
|
536
|
+
interface Props {
|
|
537
|
+
children: React.ReactNode;
|
|
538
|
+
fallback?: React.ReactNode;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
interface State {
|
|
542
|
+
hasError: boolean;
|
|
543
|
+
error?: Error;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export class ErrorBoundary extends React.Component<Props, State> {
|
|
547
|
+
state: State = { hasError: false };
|
|
548
|
+
|
|
549
|
+
static getDerivedStateFromError(error: Error): State {
|
|
550
|
+
return { hasError: true, error };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
554
|
+
console.error('Error caught:', error, errorInfo);
|
|
555
|
+
// Log to error tracking service
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
render() {
|
|
559
|
+
if (this.state.hasError) {
|
|
560
|
+
return this.props.fallback ?? (
|
|
561
|
+
<div role="alert">
|
|
562
|
+
<h2>Something went wrong</h2>
|
|
563
|
+
<button onClick={() => this.setState({ hasError: false })}>
|
|
564
|
+
Try again
|
|
565
|
+
</button>
|
|
566
|
+
</div>
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
return this.props.children;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
# ----------------------------------------------------------------------------
|
|
574
|
+
# ANTI-PATTERNS
|
|
575
|
+
# ----------------------------------------------------------------------------
|
|
576
|
+
antiPatterns:
|
|
577
|
+
propDrilling:
|
|
578
|
+
name: "Prop Drilling"
|
|
579
|
+
description: "Passing props through many levels of components"
|
|
580
|
+
bad: |
|
|
581
|
+
// ❌ Props passed through 4 levels
|
|
582
|
+
function App() {
|
|
583
|
+
const [user, setUser] = useState<User | null>(null);
|
|
584
|
+
return <Layout user={user} setUser={setUser} />;
|
|
585
|
+
}
|
|
586
|
+
function Layout({ user, setUser }) {
|
|
587
|
+
return <Sidebar user={user} setUser={setUser} />;
|
|
588
|
+
}
|
|
589
|
+
function Sidebar({ user, setUser }) {
|
|
590
|
+
return <UserMenu user={user} setUser={setUser} />;
|
|
591
|
+
}
|
|
592
|
+
good: |
|
|
593
|
+
// ✅ Use Context or Zustand
|
|
594
|
+
function App() {
|
|
595
|
+
return (
|
|
596
|
+
<AuthProvider>
|
|
597
|
+
<Layout />
|
|
598
|
+
</AuthProvider>
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
function UserMenu() {
|
|
602
|
+
const { user, logout } = useAuth(); // Direct access
|
|
603
|
+
return <button onClick={logout}>Logout {user?.name}</button>;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
useEffectAbuse:
|
|
607
|
+
name: "useEffect for Derived State"
|
|
608
|
+
description: "Using useEffect to synchronize state that should be computed"
|
|
609
|
+
bad: |
|
|
610
|
+
// ❌ useEffect for derived state
|
|
611
|
+
function ProductList({ products }) {
|
|
612
|
+
const [total, setTotal] = useState(0);
|
|
613
|
+
|
|
614
|
+
useEffect(() => {
|
|
615
|
+
const sum = products.reduce((acc, p) => acc + p.price, 0);
|
|
616
|
+
setTotal(sum);
|
|
617
|
+
}, [products]);
|
|
618
|
+
|
|
619
|
+
return <div>Total: {total}</div>;
|
|
620
|
+
}
|
|
621
|
+
good: |
|
|
622
|
+
// ✅ Use useMemo for derived values
|
|
623
|
+
function ProductList({ products }) {
|
|
624
|
+
const total = useMemo(
|
|
625
|
+
() => products.reduce((acc, p) => acc + p.price, 0),
|
|
626
|
+
[products]
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
return <div>Total: {total}</div>;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
inlineHandlers:
|
|
633
|
+
name: "Inline Function Definitions in JSX"
|
|
634
|
+
description: "Creating new functions on every render"
|
|
635
|
+
bad: |
|
|
636
|
+
// ❌ New function created on every render
|
|
637
|
+
function ItemList({ items, onRemove }) {
|
|
638
|
+
return (
|
|
639
|
+
<ul>
|
|
640
|
+
{items.map(item => (
|
|
641
|
+
<li key={item.id}>
|
|
642
|
+
{item.name}
|
|
643
|
+
<button onClick={() => onRemove(item.id)}>Remove</button>
|
|
644
|
+
</li>
|
|
645
|
+
))}
|
|
646
|
+
</ul>
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
good: |
|
|
650
|
+
// ✅ Use useCallback or extract component
|
|
651
|
+
function ItemList({ items, onRemove }) {
|
|
652
|
+
return (
|
|
653
|
+
<ul>
|
|
654
|
+
{items.map(item => (
|
|
655
|
+
<Item key={item.id} item={item} onRemove={onRemove} />
|
|
656
|
+
))}
|
|
657
|
+
</ul>
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const Item = memo(function Item({ item, onRemove }) {
|
|
662
|
+
const handleRemove = useCallback(() => onRemove(item.id), [item.id, onRemove]);
|
|
663
|
+
return (
|
|
664
|
+
<li>
|
|
665
|
+
{item.name}
|
|
666
|
+
<button onClick={handleRemove}>Remove</button>
|
|
667
|
+
</li>
|
|
668
|
+
);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
useStateForServerData:
|
|
672
|
+
name: "useState for Server Data"
|
|
673
|
+
description: "Managing server state with useState instead of a data fetching library"
|
|
674
|
+
bad: |
|
|
675
|
+
// ❌ Manual server state management
|
|
676
|
+
function UserList() {
|
|
677
|
+
const [users, setUsers] = useState<User[]>([]);
|
|
678
|
+
const [loading, setLoading] = useState(true);
|
|
679
|
+
const [error, setError] = useState<Error | null>(null);
|
|
680
|
+
|
|
681
|
+
useEffect(() => {
|
|
682
|
+
setLoading(true);
|
|
683
|
+
fetchUsers()
|
|
684
|
+
.then(setUsers)
|
|
685
|
+
.catch(setError)
|
|
686
|
+
.finally(() => setLoading(false));
|
|
687
|
+
}, []);
|
|
688
|
+
|
|
689
|
+
// No caching, no refetch, no deduplication...
|
|
690
|
+
}
|
|
691
|
+
good: |
|
|
692
|
+
// ✅ Use TanStack Query
|
|
693
|
+
function UserList() {
|
|
694
|
+
const { data: users, isLoading, error, refetch } = useQuery({
|
|
695
|
+
queryKey: ['users'],
|
|
696
|
+
queryFn: fetchUsers,
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
// Automatic caching, refetch, deduplication, etc.
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
anyType:
|
|
703
|
+
name: "Using 'any' Type"
|
|
704
|
+
description: "Disabling TypeScript's type checking with 'any'"
|
|
705
|
+
bad: |
|
|
706
|
+
// ❌ any disables type safety
|
|
707
|
+
function processData(data: any) {
|
|
708
|
+
return data.items.map((item: any) => item.value);
|
|
709
|
+
}
|
|
710
|
+
good: |
|
|
711
|
+
// ✅ Proper typing
|
|
712
|
+
interface DataItem {
|
|
713
|
+
value: number;
|
|
714
|
+
}
|
|
715
|
+
interface Data {
|
|
716
|
+
items: DataItem[];
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function processData(data: Data): number[] {
|
|
720
|
+
return data.items.map(item => item.value);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
testingImplementation:
|
|
724
|
+
name: "Testing Implementation Details"
|
|
725
|
+
description: "Testing internal state or methods instead of behavior"
|
|
726
|
+
bad: |
|
|
727
|
+
// ❌ Testing internal state
|
|
728
|
+
it('increments count state', () => {
|
|
729
|
+
const { result } = renderHook(() => useCounter());
|
|
730
|
+
expect(result.current.count).toBe(0);
|
|
731
|
+
act(() => result.current.increment());
|
|
732
|
+
expect(result.current.count).toBe(1);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
// ❌ Using getByTestId unnecessarily
|
|
736
|
+
expect(screen.getByTestId('user-name')).toHaveTextContent('John');
|
|
737
|
+
good: |
|
|
738
|
+
// ✅ Test visible behavior
|
|
739
|
+
it('displays incremented count', async () => {
|
|
740
|
+
render(<Counter />);
|
|
741
|
+
expect(screen.getByRole('status')).toHaveTextContent('0');
|
|
742
|
+
|
|
743
|
+
await userEvent.click(screen.getByRole('button', { name: /increment/i }));
|
|
744
|
+
|
|
745
|
+
expect(screen.getByRole('status')).toHaveTextContent('1');
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// ✅ Use accessible queries
|
|
749
|
+
expect(screen.getByRole('heading', { name: 'John' })).toBeInTheDocument();
|
|
750
|
+
|
|
751
|
+
missingKeyOrBadKey:
|
|
752
|
+
name: "Missing or Non-Unique Keys"
|
|
753
|
+
description: "Not providing keys or using index as key in dynamic lists"
|
|
754
|
+
bad: |
|
|
755
|
+
// ❌ Index as key (causes bugs with reordering)
|
|
756
|
+
{items.map((item, index) => (
|
|
757
|
+
<Item key={index} item={item} />
|
|
758
|
+
))}
|
|
759
|
+
|
|
760
|
+
// ❌ No key at all
|
|
761
|
+
{items.map(item => (
|
|
762
|
+
<Item item={item} />
|
|
763
|
+
))}
|
|
764
|
+
good: |
|
|
765
|
+
// ✅ Unique identifier as key
|
|
766
|
+
{items.map(item => (
|
|
767
|
+
<Item key={item.id} item={item} />
|
|
768
|
+
))}
|
|
769
|
+
|
|
770
|
+
inlineStyles:
|
|
771
|
+
name: "Inline Styles"
|
|
772
|
+
description: "Using inline style objects instead of CSS classes"
|
|
773
|
+
bad: |
|
|
774
|
+
// ❌ Inline styles
|
|
775
|
+
<div style={{ padding: '16px', backgroundColor: '#f0f0f0', marginTop: '8px' }}>
|
|
776
|
+
Content
|
|
777
|
+
</div>
|
|
778
|
+
good: |
|
|
779
|
+
// ✅ Use Tailwind or CSS classes
|
|
780
|
+
<div className="p-4 bg-gray-100 mt-2">
|
|
781
|
+
Content
|
|
782
|
+
</div>
|
|
783
|
+
|
|
784
|
+
// ✅ Or CSS Modules
|
|
785
|
+
<div className={styles.container}>
|
|
786
|
+
Content
|
|
787
|
+
</div>
|