Testing Guide
Testing strategies and practices for IfAI.
Overview
IfAI uses multiple testing approaches:
- Unit Tests: Test functions and components in isolation
- Integration Tests: Test module interactions
- E2E Tests: Test full user workflows
- Performance Tests: Ensure app responsiveness
Frontend Testing
Unit Testing with Vitest
Setup
typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'jsdom',
globals: true
}
})Component Tests
typescript
// Component.test.tsx
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { Button } from './Button'
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>)
expect(screen.getByText('Click me')).toBeInTheDocument()
})
it('calls onClick when clicked', async () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click me</Button>)
await screen.getByText('Click me').click()
expect(handleClick).toHaveBeenCalledTimes(1)
})
})Store Tests
typescript
// stores/useChatStore.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { useChatStore } from './useChatStore'
describe('useChatStore', () => {
beforeEach(() => {
useChatStore.getState().reset()
})
it('sends message', async () => {
const { sendMessage } = useChatStore.getState()
await sendMessage('Hello')
expect(useChatStore.getState().messages).toHaveLength(1)
})
})E2E Testing with Playwright
Setup
bash
npm install -D @playwright/testtypescript
// playwright.config.ts
import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
use: {
baseURL: 'http://localhost:1420'
}
})E2E Test Example
typescript
// tests/e2e/ai-chat.spec.ts
import { test, expect } from '@playwright/test'
test.describe('AI Chat', () => {
test('sends and receives message', async ({ page }) => {
await page.goto('/')
// Open AI chat
await page.keyboard.press('Cmd+K')
// Type message
await page.fill('[data-testid="chat-input"]', 'Hello')
await page.click('[data-testid="send-button"]')
// Wait for response
await expect(page.locator('[data-testid="response"]')).toBeVisible()
})
test('displays code block with syntax highlighting', async ({ page }) => {
await page.goto('/')
await page.keyboard.press('Cmd+K')
await page.fill('[data-testid="chat-input"]', 'Create a function')
await page.click('[data-testid="send-button"]')
// Check for code block
await expect(page.locator('pre code')).toBeVisible()
await expect(page.locator('pre code')).toHaveClass(/language-typescript/)
})
})Backend Testing
Rust Unit Tests
rust
// src/commands/atomic_commands_tests.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_file() {
let result = read_file("test.txt");
assert!(result.is_ok());
}
#[test]
fn test_write_file() {
let result = write_file("test.txt", "content");
assert!(result.is_ok());
}
}Integration Tests
rust
#[cfg(test)]
mod integration_tests {
use super::*;
#[tokio::test]
async fn test_ai_chat_flow() {
let messages = vec![
Message { role: "user".into(), content: "Test".into() }
];
let response = ai_chat(messages).await;
assert!(response.is_ok());
}
}Test Organization
tests/
├── unit/ # Unit tests
│ ├── components/ # Component tests
│ ├── stores/ # Store tests
│ └── services/ # Service tests
├── e2e/ # E2E tests
│ ├── ai-chat.spec.ts # AI chat tests
│ ├── composer.spec.ts # Composer tests
│ └── navigation.spec.ts # Navigation tests
└── fixtures/ # Test data
├── sample-code/ # Sample projects
└── mock-responses/ # Mock AI responsesCommon Test Scenarios
AI Chat Tests
typescript
describe('AI Chat', () => {
test('handles empty message')
test('displays loading state')
test('shows error on failure')
test('supports slash commands')
test('maintains conversation history')
})Composer Tests
typescript
describe('Composer', () => {
test('shows file list with changes')
test('displays diff preview')
test('accepts selected changes')
test('rejects selected changes')
test('applies all changes at once')
})Code Navigation Tests
typescript
describe('Code Navigation', () => {
test('goes to definition')
test('finds references')
test('searches symbols')
test('handles undefined symbols')
test('handles multiple definitions')
})Performance Testing
Load Testing
typescript
// tests/performance/load-test.spec.ts
import { test } from '@playwright/test'
test('handles large file without lag', async ({ page }) => {
// Open large file
await page.goto('/?file=/test/large-file.ts')
// Measure performance
const metrics = await page.evaluate(() => {
const start = performance.now()
// Perform operation
document.querySelector('.monaco-editor').click()
return performance.now() - start
})
expect(metrics).toBeLessThan(100) // Should respond in <100ms
})Memory Testing
bash
# Check memory usage
npm run test:memory
# Profile memory
npm run test:profileContinuous Integration
GitHub Actions
yaml
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm install
- name: Run linter
run: npm run lint
- name: Run unit tests
run: npm run test:unit
- name: Run E2E tests
run: npm run test:e2eBest Practices
1. Test User Behavior, Not Implementation
typescript
// Good: Tests what user does
test('submits form when enter pressed', async () => {
await page.fill('[name="email"]', 'test@example.com')
await page.keyboard.press('Enter')
await expect(page).toHaveURL('/success')
})
// Avoid: Tests implementation details
test('calls onSubmit function', () => {
// Too tied to implementation
})2. Use Page Objects
typescript
// tests/pages/ChatPage.ts
export class ChatPage {
constructor(page: Page) {
this.page = page
}
async open() {
await this.page.goto('/')
await this.page.keyboard.press('Cmd+K')
}
async sendMessage(text: string) {
await this.page.fill('[data-testid="chat-input"]', text)
await this.page.click('[data-testid="send-button"]')
}
}
// Test using page object
test('sends message', async ({ page }) => {
const chatPage = new ChatPage(page)
await chatPage.open()
await chatPage.sendMessage('Hello')
})3. Mock External Dependencies
typescript
// Mock AI API
vi.mock('@/services/ai/aiClient', () => ({
chat: vi.fn().mockResolvedValue('Mock response')
}))
// Test with mock
test('uses mocked AI', async () => {
const { sendMessage } = useChatStore.getState()
await sendMessage('Test')
expect(chat).toHaveBeenCalledWith('Test')
})Debugging Tests
UI Mode
bash
# Run tests in UI mode
npm run test:uiDebug Mode
typescript
// Add debugger
test('debugging example', async ({ page }) => {
debugger // Pause here in browser
await page.goto('/')
})Coverage
bash
# Generate coverage report
npm run test:coverage
# Open coverage report
open coverage/index.htmlNext Steps
- Frontend Guide - Component testing
- Backend Guide - Rust testing
- API Reference - Testing APIs