main
This commit is contained in:
479
.agent/commands/nextjs-api-tester.md
Normal file
479
.agent/commands/nextjs-api-tester.md
Normal file
@@ -0,0 +1,479 @@
|
||||
---
|
||||
allowed-tools: Read, Write, Edit, Bash
|
||||
argument-hint: [route-path] [--method=GET] [--data='{}'] [--headers='{}']
|
||||
description: Test and validate Next.js API routes with comprehensive test scenarios
|
||||
---
|
||||
|
||||
## Next.js API Route Tester
|
||||
|
||||
**API Route**: $ARGUMENTS
|
||||
|
||||
## Current Project Analysis
|
||||
|
||||
### API Routes Detection
|
||||
- App Router API: @app/api/
|
||||
- Pages Router API: @pages/api/
|
||||
- API configuration: @next.config.js
|
||||
- Environment variables: @.env.local
|
||||
|
||||
### Project Context
|
||||
- Next.js version: !`grep '"next"' package.json | head -1`
|
||||
- TypeScript config: @tsconfig.json (if exists)
|
||||
- Testing framework: @jest.config.js or @vitest.config.js (if exists)
|
||||
|
||||
## API Route Analysis
|
||||
|
||||
### Route Discovery
|
||||
Based on the provided route path, analyze:
|
||||
- **Route File**: Locate the actual route file
|
||||
- **HTTP Methods**: Supported methods (GET, POST, PUT, DELETE, PATCH)
|
||||
- **Route Parameters**: Dynamic segments and query parameters
|
||||
- **Middleware**: Applied middleware functions
|
||||
- **Authentication**: Required authentication/authorization
|
||||
|
||||
### Route Implementation Review
|
||||
- Route handler implementation: @app/api/[route-path]/route.ts or @pages/api/[route-path].ts
|
||||
- Type definitions: @types/ or inline types
|
||||
- Validation schemas: @lib/validations/ or inline validation
|
||||
- Database models: @lib/models/ or @models/
|
||||
|
||||
## Test Generation Strategy
|
||||
|
||||
### 1. Basic Functionality Tests
|
||||
```javascript
|
||||
// Basic API route test template
|
||||
describe('API Route: /api/[route-path]', () => {
|
||||
describe('GET requests', () => {
|
||||
test('should return 200 for valid request', async () => {
|
||||
const response = await fetch('/api/[route-path]');
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
test('should return valid JSON response', async () => {
|
||||
const response = await fetch('/api/[route-path]');
|
||||
const data = await response.json();
|
||||
expect(data).toBeDefined();
|
||||
expect(typeof data).toBe('object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST requests', () => {
|
||||
test('should create resource with valid data', async () => {
|
||||
const testData = { name: 'Test', email: 'test@example.com' };
|
||||
const response = await fetch('/api/[route-path]', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(testData)
|
||||
});
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
const result = await response.json();
|
||||
expect(result.name).toBe(testData.name);
|
||||
});
|
||||
|
||||
test('should reject invalid data', async () => {
|
||||
const invalidData = { invalid: 'field' };
|
||||
const response = await fetch('/api/[route-path]', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(invalidData)
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Authentication Tests
|
||||
```javascript
|
||||
describe('Authentication', () => {
|
||||
test('should require authentication for protected routes', async () => {
|
||||
const response = await fetch('/api/protected-route');
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
|
||||
test('should allow authenticated requests', async () => {
|
||||
const token = 'valid-jwt-token';
|
||||
const response = await fetch('/api/protected-route', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
expect(response.status).not.toBe(401);
|
||||
});
|
||||
|
||||
test('should validate JWT token format', async () => {
|
||||
const invalidToken = 'invalid-token';
|
||||
const response = await fetch('/api/protected-route', {
|
||||
headers: { 'Authorization': `Bearer ${invalidToken}` }
|
||||
});
|
||||
expect(response.status).toBe(403);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Input Validation Tests
|
||||
```javascript
|
||||
describe('Input Validation', () => {
|
||||
const validationTests = [
|
||||
{ field: 'email', invalid: 'not-an-email', valid: 'test@example.com' },
|
||||
{ field: 'phone', invalid: '123', valid: '+1234567890' },
|
||||
{ field: 'age', invalid: -1, valid: 25 },
|
||||
{ field: 'name', invalid: '', valid: 'John Doe' }
|
||||
];
|
||||
|
||||
validationTests.forEach(({ field, invalid, valid }) => {
|
||||
test(`should validate ${field} field`, async () => {
|
||||
const invalidData = { [field]: invalid };
|
||||
const validData = { [field]: valid };
|
||||
|
||||
// Test invalid data
|
||||
const invalidResponse = await fetch('/api/[route-path]', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(invalidData)
|
||||
});
|
||||
expect(invalidResponse.status).toBe(400);
|
||||
|
||||
// Test valid data
|
||||
const validResponse = await fetch('/api/[route-path]', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(validData)
|
||||
});
|
||||
expect(validResponse.status).not.toBe(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Error Handling Tests
|
||||
```javascript
|
||||
describe('Error Handling', () => {
|
||||
test('should handle malformed JSON', async () => {
|
||||
const response = await fetch('/api/[route-path]', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: 'invalid-json'
|
||||
});
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
|
||||
test('should handle missing Content-Type header', async () => {
|
||||
const response = await fetch('/api/[route-path]', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ test: 'data' })
|
||||
});
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
|
||||
test('should handle request timeout', async () => {
|
||||
// Mock slow endpoint
|
||||
jest.setTimeout(5000);
|
||||
const response = await fetch('/api/slow-endpoint');
|
||||
// Test appropriate timeout handling
|
||||
}, 5000);
|
||||
|
||||
test('should handle database connection errors', async () => {
|
||||
// Mock database failure
|
||||
const mockDbError = jest.spyOn(db, 'connect').mockRejectedValue(new Error('DB Error'));
|
||||
|
||||
const response = await fetch('/api/[route-path]');
|
||||
expect(response.status).toBe(500);
|
||||
|
||||
mockDbError.mockRestore();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Performance Tests
|
||||
```javascript
|
||||
describe('Performance', () => {
|
||||
test('should respond within acceptable time', async () => {
|
||||
const startTime = Date.now();
|
||||
const response = await fetch('/api/[route-path]');
|
||||
const endTime = Date.now();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(endTime - startTime).toBeLessThan(1000); // 1 second
|
||||
});
|
||||
|
||||
test('should handle concurrent requests', async () => {
|
||||
const promises = Array.from({ length: 10 }, () =>
|
||||
fetch('/api/[route-path]')
|
||||
);
|
||||
|
||||
const responses = await Promise.all(promises);
|
||||
responses.forEach(response => {
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
test('should implement rate limiting', async () => {
|
||||
const requests = Array.from({ length: 100 }, () =>
|
||||
fetch('/api/[route-path]')
|
||||
);
|
||||
|
||||
const responses = await Promise.all(requests);
|
||||
const rateLimitedResponses = responses.filter(r => r.status === 429);
|
||||
expect(rateLimitedResponses.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Manual Testing Commands
|
||||
|
||||
### cURL Commands Generation
|
||||
```bash
|
||||
# GET request
|
||||
curl -X GET "http://localhost:3000/api/[route-path]" \
|
||||
-H "Accept: application/json"
|
||||
|
||||
# POST request with data
|
||||
curl -X POST "http://localhost:3000/api/[route-path]" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
-d '{"key": "value"}'
|
||||
|
||||
# Authenticated request
|
||||
curl -X GET "http://localhost:3000/api/protected-route" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
|
||||
-H "Accept: application/json"
|
||||
|
||||
# Upload file
|
||||
curl -X POST "http://localhost:3000/api/upload" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
|
||||
-F "file=@path/to/file.jpg"
|
||||
```
|
||||
|
||||
### HTTPie Commands
|
||||
```bash
|
||||
# GET request
|
||||
http GET localhost:3000/api/[route-path]
|
||||
|
||||
# POST request with JSON
|
||||
http POST localhost:3000/api/[route-path] key=value
|
||||
|
||||
# Authenticated request
|
||||
http GET localhost:3000/api/protected-route Authorization:"Bearer TOKEN"
|
||||
|
||||
# Custom headers
|
||||
http GET localhost:3000/api/[route-path] X-Custom-Header:value
|
||||
```
|
||||
|
||||
## Interactive Testing Tools
|
||||
|
||||
### Postman Collection Generation
|
||||
```json
|
||||
{
|
||||
"info": {
|
||||
"name": "Next.js API Tests",
|
||||
"description": "Generated API tests for [route-path]"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "GET [route-path]",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/[route-path]",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["api", "[route-path]"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "POST [route-path]",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"key\": \"value\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/[route-path]",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["api", "[route-path]"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Thunder Client Collection
|
||||
```json
|
||||
{
|
||||
"client": "Thunder Client",
|
||||
"collectionName": "Next.js API Tests",
|
||||
"dateExported": "2024-01-01",
|
||||
"version": "1.1",
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"name": "Test API Route",
|
||||
"url": "localhost:3000/api/[route-path]",
|
||||
"method": "GET",
|
||||
"headers": [
|
||||
{
|
||||
"name": "Accept",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Test Data Management
|
||||
|
||||
### Test Fixtures
|
||||
```typescript
|
||||
// test/fixtures/apiTestData.ts
|
||||
export const validUserData = {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
age: 30,
|
||||
role: 'user'
|
||||
};
|
||||
|
||||
export const invalidUserData = {
|
||||
name: '',
|
||||
email: 'invalid-email',
|
||||
age: -1,
|
||||
role: 'invalid-role'
|
||||
};
|
||||
|
||||
export const testHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'API-Test-Suite/1.0'
|
||||
};
|
||||
```
|
||||
|
||||
### Mock Data Generation
|
||||
```typescript
|
||||
// test/utils/mockData.ts
|
||||
export function generateMockUser() {
|
||||
return {
|
||||
id: Math.random().toString(36).substr(2, 9),
|
||||
name: `User ${Math.floor(Math.random() * 1000)}`,
|
||||
email: `user${Date.now()}@example.com`,
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
export function generateBulkTestData(count: number) {
|
||||
return Array.from({ length: count }, generateMockUser);
|
||||
}
|
||||
```
|
||||
|
||||
## Test Environment Setup
|
||||
|
||||
### Jest Configuration
|
||||
```javascript
|
||||
// jest.config.js for API testing
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
|
||||
testMatch: ['**/__tests__/**/*.test.js', '**/?(*.)+(spec|test).js'],
|
||||
collectCoverageFrom: [
|
||||
'pages/api/**/*.{js,ts}',
|
||||
'app/api/**/*.{js,ts}',
|
||||
'!**/*.d.ts',
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 70,
|
||||
functions: 70,
|
||||
lines: 70,
|
||||
statements: 70
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Test Setup
|
||||
```javascript
|
||||
// test/setup.js
|
||||
import { createMocks } from 'node-mocks-http';
|
||||
import { testDb } from './testDatabase';
|
||||
|
||||
// Global test setup
|
||||
beforeAll(async () => {
|
||||
// Setup test database
|
||||
await testDb.connect();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// Cleanup test database
|
||||
await testDb.disconnect();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// Reset database state
|
||||
await testDb.reset();
|
||||
});
|
||||
|
||||
// Helper function for API testing
|
||||
global.createAPITest = (handler) => {
|
||||
return (method, url, options = {}) => {
|
||||
const { req, res } = createMocks({
|
||||
method,
|
||||
url,
|
||||
...options
|
||||
});
|
||||
return handler(req, res);
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Automated Testing Integration
|
||||
|
||||
### GitHub Actions Workflow
|
||||
```yaml
|
||||
name: API Tests
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test-api:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
- run: npm ci
|
||||
- run: npm run test:api
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
```
|
||||
|
||||
### Continuous Testing
|
||||
```bash
|
||||
# Watch mode for development
|
||||
npm run test:api -- --watch
|
||||
|
||||
# Coverage reporting
|
||||
npm run test:api -- --coverage
|
||||
|
||||
# Specific route testing
|
||||
npm run test:api -- --testNamePattern="api/users"
|
||||
```
|
||||
|
||||
## Test Results Analysis
|
||||
|
||||
Generate comprehensive test report including:
|
||||
1. **Test Coverage**: Line, branch, function coverage percentages
|
||||
2. **Performance Metrics**: Response times, throughput
|
||||
3. **Security Analysis**: Authentication, authorization, input validation
|
||||
4. **Error Handling**: Exception scenarios and error responses
|
||||
5. **Compatibility**: Cross-environment testing results
|
||||
|
||||
Provide actionable recommendations for improving API reliability, performance, and security.
|
||||
488
.agent/commands/nextjs-component-generator.md
Normal file
488
.agent/commands/nextjs-component-generator.md
Normal file
@@ -0,0 +1,488 @@
|
||||
---
|
||||
allowed-tools: Read, Write, Edit
|
||||
argument-hint: [component-name] [--client] [--server] [--page] [--layout]
|
||||
description: Generate optimized React components for Next.js with TypeScript and best practices
|
||||
---
|
||||
|
||||
## Next.js Component Generator
|
||||
|
||||
**Component Name**: $ARGUMENTS
|
||||
|
||||
## Project Context Analysis
|
||||
|
||||
### Framework Detection
|
||||
- Next.js config: @next.config.js
|
||||
- TypeScript config: @tsconfig.json (if exists)
|
||||
- Tailwind config: @tailwind.config.js (if exists)
|
||||
- Package.json: @package.json
|
||||
|
||||
### Existing Component Patterns
|
||||
- Components directory: @components/
|
||||
- App directory: @app/ (if App Router)
|
||||
- Pages directory: @pages/ (if Pages Router)
|
||||
- Styles directory: @styles/
|
||||
|
||||
## Component Generation Requirements
|
||||
|
||||
### 1. Component Type Detection
|
||||
Based on arguments and context, determine component type:
|
||||
- **Client Component**: Interactive UI with state/events (`--client` or default for interactive components)
|
||||
- **Server Component**: Static rendering, data fetching (`--server` or default for Next.js 13+)
|
||||
- **Page Component**: Route-level component (`--page`)
|
||||
- **Layout Component**: Shared layout wrapper (`--layout`)
|
||||
|
||||
### 2. File Structure Creation
|
||||
Generate comprehensive component structure:
|
||||
```
|
||||
components/[ComponentName]/
|
||||
├── index.ts # Barrel export
|
||||
├── [ComponentName].tsx # Main component
|
||||
├── [ComponentName].module.css # Component styles
|
||||
├── [ComponentName].test.tsx # Unit tests
|
||||
├── [ComponentName].stories.tsx # Storybook story (if detected)
|
||||
└── types.ts # TypeScript types
|
||||
```
|
||||
|
||||
### 3. Component Templates
|
||||
|
||||
#### Server Component Template
|
||||
```typescript
|
||||
import { FC } from 'react';
|
||||
import styles from './ComponentName.module.css';
|
||||
|
||||
interface ComponentNameProps {
|
||||
/**
|
||||
* Component description
|
||||
*/
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* Additional CSS classes
|
||||
*/
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ComponentName - Server Component
|
||||
*
|
||||
* @description Brief description of component purpose
|
||||
* @example
|
||||
* <ComponentName>Content</ComponentName>
|
||||
*/
|
||||
export const ComponentName: FC<ComponentNameProps> = ({
|
||||
children,
|
||||
className = '',
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<div className={`${styles.container} ${className}`} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComponentName;
|
||||
```
|
||||
|
||||
#### Client Component Template
|
||||
```typescript
|
||||
'use client';
|
||||
|
||||
import { FC, useState, useEffect } from 'react';
|
||||
import styles from './ComponentName.module.css';
|
||||
|
||||
interface ComponentNameProps {
|
||||
/**
|
||||
* Component description
|
||||
*/
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* Click event handler
|
||||
*/
|
||||
onClick?: () => void;
|
||||
/**
|
||||
* Additional CSS classes
|
||||
*/
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ComponentName - Client Component
|
||||
*
|
||||
* @description Interactive component with client-side functionality
|
||||
* @example
|
||||
* <ComponentName onClick={() => console.log('clicked')}>
|
||||
* Content
|
||||
* </ComponentName>
|
||||
*/
|
||||
export const ComponentName: FC<ComponentNameProps> = ({
|
||||
children,
|
||||
onClick,
|
||||
className = '',
|
||||
...props
|
||||
}) => {
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
|
||||
const handleClick = () => {
|
||||
setIsActive(!isActive);
|
||||
onClick?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`${styles.button} ${isActive ? styles.active : ''} ${className}`}
|
||||
onClick={handleClick}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComponentName;
|
||||
```
|
||||
|
||||
#### Page Component Template
|
||||
```typescript
|
||||
import { Metadata } from 'next';
|
||||
import ComponentName from '@/components/ComponentName';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Page Title',
|
||||
description: 'Page description',
|
||||
};
|
||||
|
||||
interface PageProps {
|
||||
params: { id: string };
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
}
|
||||
|
||||
export default function Page({ params, searchParams }: PageProps) {
|
||||
return (
|
||||
<main>
|
||||
<h1>Page Title</h1>
|
||||
<ComponentName />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Layout Component Template
|
||||
```typescript
|
||||
import { FC } from 'react';
|
||||
import styles from './Layout.module.css';
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Page title
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout - Shared layout component
|
||||
*
|
||||
* @description Provides consistent layout structure across pages
|
||||
*/
|
||||
export const Layout: FC<LayoutProps> = ({
|
||||
children,
|
||||
title,
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.layout}>
|
||||
<header className={styles.header}>
|
||||
{title && <h1 className={styles.title}>{title}</h1>}
|
||||
</header>
|
||||
|
||||
<main className={styles.main}>
|
||||
{children}
|
||||
</main>
|
||||
|
||||
<footer className={styles.footer}>
|
||||
<p>© 2024 Your App</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
```
|
||||
|
||||
### 4. CSS Module Templates
|
||||
|
||||
#### Basic Component Styles
|
||||
```css
|
||||
/* ComponentName.module.css */
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid transparent;
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
|
||||
.button:focus {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.button.active {
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Layout Styles
|
||||
```css
|
||||
/* Layout.module.css */
|
||||
.layout {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 1rem 2rem;
|
||||
background-color: #f8fafc;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 1rem 2rem;
|
||||
background-color: #f1f5f9;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
text-align: center;
|
||||
color: #64748b;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. TypeScript Types
|
||||
```typescript
|
||||
// types.ts
|
||||
export interface BaseComponentProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
export interface ButtonProps extends BaseComponentProps {
|
||||
variant?: 'primary' | 'secondary' | 'outline';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface LayoutProps extends BaseComponentProps {
|
||||
title?: string;
|
||||
sidebar?: React.ReactNode;
|
||||
breadcrumbs?: BreadcrumbItem[];
|
||||
}
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
label: string;
|
||||
href?: string;
|
||||
current?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Unit Tests
|
||||
```typescript
|
||||
// ComponentName.test.tsx
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import ComponentName from './ComponentName';
|
||||
|
||||
describe('ComponentName', () => {
|
||||
it('renders children correctly', () => {
|
||||
render(<ComponentName>Test Content</ComponentName>);
|
||||
expect(screen.getByText('Test Content')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('applies custom className', () => {
|
||||
render(<ComponentName className="custom-class">Test</ComponentName>);
|
||||
const element = screen.getByText('Test');
|
||||
expect(element).toHaveClass('custom-class');
|
||||
});
|
||||
|
||||
it('handles click events', () => {
|
||||
const handleClick = jest.fn();
|
||||
render(<ComponentName onClick={handleClick}>Click me</ComponentName>);
|
||||
|
||||
const button = screen.getByText('Click me');
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('toggles active state on click', () => {
|
||||
render(<ComponentName>Toggle</ComponentName>);
|
||||
const button = screen.getByText('Toggle');
|
||||
|
||||
expect(button).not.toHaveClass('active');
|
||||
|
||||
fireEvent.click(button);
|
||||
expect(button).toHaveClass('active');
|
||||
|
||||
fireEvent.click(button);
|
||||
expect(button).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('is accessible', () => {
|
||||
render(<ComponentName>Accessible Button</ComponentName>);
|
||||
const button = screen.getByRole('button');
|
||||
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toHaveAccessibleName('Accessible Button');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 7. Storybook Stories (if detected)
|
||||
```typescript
|
||||
// ComponentName.stories.tsx
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import ComponentName from './ComponentName';
|
||||
|
||||
const meta: Meta<typeof ComponentName> = {
|
||||
title: 'Components/ComponentName',
|
||||
component: ComponentName,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'A reusable component built for Next.js applications.',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
onClick: { action: 'clicked' },
|
||||
className: { control: 'text' },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: 'Default Component',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCustomClass: Story = {
|
||||
args: {
|
||||
children: 'Custom Styled',
|
||||
className: 'custom-style',
|
||||
},
|
||||
};
|
||||
|
||||
export const Interactive: Story = {
|
||||
args: {
|
||||
children: 'Click me',
|
||||
onClick: () => alert('Component clicked!'),
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 8. Barrel Export
|
||||
```typescript
|
||||
// index.ts
|
||||
export { default } from './ComponentName';
|
||||
export type { ComponentNameProps } from './ComponentName';
|
||||
```
|
||||
|
||||
## Framework-Specific Optimizations
|
||||
|
||||
### Tailwind CSS Integration (if detected)
|
||||
Replace CSS modules with Tailwind classes:
|
||||
```typescript
|
||||
export const ComponentName: FC<ComponentNameProps> = ({
|
||||
children,
|
||||
className = '',
|
||||
}) => {
|
||||
return (
|
||||
<div className={`flex flex-col p-4 rounded-lg border border-slate-200 bg-white ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Next.js App Router Optimizations
|
||||
- **Server Components**: Default for non-interactive components
|
||||
- **Client Components**: Explicit 'use client' directive
|
||||
- **Metadata**: Include metadata for page components
|
||||
- **Loading States**: Implement loading.tsx for async components
|
||||
|
||||
### Accessibility Features
|
||||
- **ARIA Labels**: Proper labeling for screen readers
|
||||
- **Keyboard Navigation**: Tab order and keyboard shortcuts
|
||||
- **Focus Management**: Visible focus indicators
|
||||
- **Semantic HTML**: Proper semantic elements
|
||||
|
||||
## Component Generation Process
|
||||
|
||||
1. **Analysis**: Analyze existing project structure and patterns
|
||||
2. **Template Selection**: Choose appropriate template based on component type
|
||||
3. **Customization**: Adapt template to project conventions
|
||||
4. **File Creation**: Generate all component files
|
||||
5. **Integration**: Update index files and exports
|
||||
6. **Validation**: Verify component compiles and tests pass
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
- [ ] Component follows project naming conventions
|
||||
- [ ] TypeScript types are properly defined
|
||||
- [ ] CSS follows established patterns (modules or Tailwind)
|
||||
- [ ] Unit tests cover key functionality
|
||||
- [ ] Component is accessible (ARIA, keyboard navigation)
|
||||
- [ ] Documentation includes usage examples
|
||||
- [ ] Storybook story created (if Storybook detected)
|
||||
- [ ] Component compiles without errors
|
||||
- [ ] Tests pass successfully
|
||||
|
||||
Provide the complete component implementation with all specified files and features.
|
||||
Reference in New Issue
Block a user