Add unit testing philosophy and guidelines to CLAUDE.md

- Introduced a new section on unit testing, emphasizing the importance of unit tests, writing testable code, and structuring tests effectively.
- Updated development workflow to include best practices for creating modular, testable functions.
- Added guidelines for Ink CLI component development, including core concepts, component structure, and styling guidelines.
- Included testing strategies for Ink components and common patterns for CLI development.
This commit is contained in:
dal 2025-08-04 22:43:20 -06:00
parent 95bf3fd1a4
commit 96c44edc96
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
2 changed files with 1220 additions and 7 deletions

File diff suppressed because it is too large Load Diff

191
CLAUDE.md
View File

@ -60,17 +60,72 @@ This is a pnpm-based monorepo using Turborepo with the following structure:
- **Run quality checks while working**: Periodically run `turbo build:dry-run` and `turbo lint` to catch issues early - **Run quality checks while working**: Periodically run `turbo build:dry-run` and `turbo lint` to catch issues early
- **Your primary job is the functional implementation**: Focus on accomplishing the task; the QA engineer specializes in comprehensive testing - **Your primary job is the functional implementation**: Focus on accomplishing the task; the QA engineer specializes in comprehensive testing
## Unit Testing Philosophy
### Importance of Unit Tests
- **Unit tests are critical** - Every function should be testable in isolation
- **Small, focused tests** - Each test should verify one specific behavior
- **Test file naming**: Unit tests use `.test.ts` extension, integration tests use `.int.test.ts`
- **Default to unit tests** - Always write unit tests unless specifically asked for integration tests
- **100% testability goal** - All code should be written to be testable in small chunks
### Writing Testable Code
- **Pure functions first** - Functions should have clear inputs and outputs
- **Dependency injection** - Pass dependencies as parameters instead of importing directly
- **Avoid side effects** - Separate business logic from I/O operations
- **Small functions** - If a function is hard to test, it's probably doing too much
- **Mockable boundaries** - Design interfaces that can be easily mocked in tests
### Test Structure
```typescript
// Example of testable function design
// Bad: Hard to test
async function sendNotification(userId: string) {
const user = await db.getUser(userId); // Direct database dependency
const message = createMessage(user);
await emailService.send(message); // Direct service dependency
}
// Good: Easily testable
async function sendNotification(
userId: string,
getUser: (id: string) => Promise<User>,
sendEmail: (message: Message) => Promise<void>
) {
const user = await getUser(userId);
const message = createMessage(user);
await sendEmail(message);
}
// Unit test example (*.test.ts)
describe('sendNotification', () => {
it('should send email with correct message', async () => {
const mockUser = { id: '123', name: 'Test User' };
const mockGetUser = vi.fn().mockResolvedValue(mockUser);
const mockSendEmail = vi.fn().mockResolvedValue(undefined);
await sendNotification('123', mockGetUser, mockSendEmail);
expect(mockGetUser).toHaveBeenCalledWith('123');
expect(mockSendEmail).toHaveBeenCalledWith(
expect.objectContaining({ to: mockUser.email })
);
});
});
```
## Development Workflow ## Development Workflow
When writing code, follow this workflow to ensure code quality: When writing code, follow this workflow to ensure code quality:
### 1. Write Modular, Testable Functions ### 1. Write Modular, Testable Functions
- Create small, focused functions with single responsibilities - **Create small, focused functions** with single responsibilities that can be unit tested effectively
- Design functions to be easily testable with clear inputs/outputs - **Design for testability** - Every function should have clear inputs/outputs and be testable in isolation
- Use dependency injection for external dependencies - **Use dependency injection** - Pass dependencies as parameters to enable easy mocking in unit tests
- **IMPORTANT: Write functional, composable code - avoid classes** - **IMPORTANT: Write functional, composable code - avoid classes**
- All features should be composed of testable functions - **All features must be composed of testable functions** - If you can't write a unit test for it, refactor it
- Follow existing patterns in the codebase - **Follow existing patterns** in the codebase for consistency
- **Remember**: Unit tests use `.test.ts`, integration tests use `.int.test.ts`
### 2. Build Features by Composing Functions ### 2. Build Features by Composing Functions
- Combine modular functions to create complete features - Combine modular functions to create complete features
@ -287,10 +342,15 @@ export async function getWorkspaceSettingsHandler(
- For update operations, we should almost always perform an upsert unless otherwise specified - For update operations, we should almost always perform an upsert unless otherwise specified
## Test Running Guidelines ## Test Running Guidelines
- **Always default to unit tests** unless specifically asked for integration tests
- **Test file naming conventions**:
- Unit tests: `*.test.ts` (e.g., `user.test.ts`)
- Integration tests: `*.int.test.ts` (e.g., `user.int.test.ts`)
- When running tests, use the following Turbo commands: - When running tests, use the following Turbo commands:
- `turbo test:unit` for unit tests - `turbo test:unit` for unit tests (run these frequently during development)
- `turbo test:integration` for integration tests - `turbo test:integration` for integration tests (run sparingly, only when needed)
- `turbo test` for running all tests - `turbo test` for running all tests
- **Unit tests should be fast** - If a test takes more than a few milliseconds, it might be an integration test
## Pre-Completion Workflow ## Pre-Completion Workflow
- Run `turbo build:dry-run lint` during development to ensure type safety and code quality - Run `turbo build:dry-run lint` during development to ensure type safety and code quality
@ -298,6 +358,123 @@ export async function getWorkspaceSettingsHandler(
- Update task status and documentation in `.claude/tasks/` files - Update task status and documentation in `.claude/tasks/` files
- Coordinate with the monorepo-task-planner for any requirement changes - Coordinate with the monorepo-task-planner for any requirement changes
## Ink CLI Component Development
### Overview
Ink is a React-based framework for building command-line interfaces. We use Ink v6.1.0 (latest stable) which requires Node.js 20+ and React 19.
### Core Concepts
- **React for CLIs**: Build CLI output using React components
- **Flexbox Layout**: Uses Yoga layout engine for terminal layouts
- **Component-based**: All UI is built with components like `<Box>` and `<Text>`
### Component Structure
- **All text must be wrapped in `<Text>` components**
- **`<Box>` is the fundamental container** - works like a flex container
- **Use flexbox properties** for layout (flexDirection, justifyContent, alignItems)
- **Style through props** - color, backgroundColor, bold, italic, etc.
### Key APIs and Hooks
- `render()` - Display components in the terminal
- `useState()` - Manage component state
- `useEffect()` - Handle side effects and lifecycle
- `useInput()` - Capture and handle user keyboard input
- `useFocus()` - Manage focus between components
- `useStdout()` - Direct access to stdout stream
- `useStderr()` - Direct access to stderr stream
### Component Best Practices
```typescript
import React, { useState, useEffect } from 'react';
import { render, Box, Text } from 'ink';
// Example: Progress indicator component
const ProgressIndicator = ({ total, current }: { total: number; current: number }) => {
const percentage = Math.round((current / total) * 100);
return (
<Box flexDirection="column">
<Text color="green">Progress: {percentage}%</Text>
<Box width={50}>
<Text>{`[${'='.repeat(percentage / 2).padEnd(50, ' ')}]`}</Text>
</Box>
</Box>
);
};
// Example: Interactive menu component
const Menu = ({ items }: { items: string[] }) => {
const [selectedIndex, setSelectedIndex] = useState(0);
useInput((input, key) => {
if (key.upArrow && selectedIndex > 0) {
setSelectedIndex(selectedIndex - 1);
}
if (key.downArrow && selectedIndex < items.length - 1) {
setSelectedIndex(selectedIndex + 1);
}
});
return (
<Box flexDirection="column">
{items.map((item, index) => (
<Text key={item} color={index === selectedIndex ? 'green' : 'white'}>
{index === selectedIndex ? '> ' : ' '}{item}
</Text>
))}
</Box>
);
};
```
### Styling Guidelines
- **Text formatting**: Use props like `bold`, `italic`, `underline`, `strikethrough`
- **Colors**: Use `color` prop with color names or hex values
- **Background**: Use `backgroundColor` prop on Box components (v6.1.0+)
- **Dimensions**: Control with `width`, `height`, `minWidth`, `minHeight`
- **Spacing**: Use `margin`, `padding`, `gap` props
- **Text wrapping**: Use `wrap` prop on Text components
### TypeScript Support
```typescript
import type { FC } from 'react';
import { Box, Text } from 'ink';
interface StatusMessageProps {
status: 'success' | 'error' | 'warning';
message: string;
}
const StatusMessage: FC<StatusMessageProps> = ({ status, message }) => {
const colors = {
success: 'green',
error: 'red',
warning: 'yellow'
};
return (
<Box>
<Text color={colors[status]} bold>
{status.toUpperCase()}:
</Text>
<Text> {message}</Text>
</Box>
);
};
```
### Testing Ink Components
- Use `ink-testing-library` for testing
- Test component output and interactions
- Mock terminal dimensions for consistent tests
### Common Patterns
1. **Loading states**: Use spinners from `ink-spinner`
2. **Forms**: Use `ink-text-input` for text input
3. **Tables**: Use `ink-table` for tabular data
4. **Links**: Use `ink-link` for clickable URLs
5. **Progress**: Use `ink-progress-bar` for progress indicators
## Local Development ## Local Development
### Local Development Details ### Local Development Details