component-testing
Write comprehensive unit tests for Visual Layout Builder using Vitest. Use when creating tests for lib/ functions, schema validation, canvas utilities, or any business logic. Follows AAA pattern and project testing conventions.
When & Why to Use This Skill
This Claude skill provides a specialized framework for generating comprehensive unit tests for the Visual Layout Builder using Vitest. It streamlines the development of robust test suites for library functions, schema validation, and complex canvas utilities, ensuring high code quality and reliability by strictly adhering to the Arrange-Act-Assert (AAA) pattern and project-specific testing conventions.
Use Cases
- Automated Unit Testing: Generate Vitest-based test suites for new business logic and utility functions within the lib/ directory to ensure functional correctness.
- Schema Validation: Create rigorous tests for complex layout schemas to verify data integrity, breakpoint configurations, and component naming conventions.
- Regression Testing: Quickly draft and implement regression tests following bug fixes to prevent recurring issues in the layout engine.
- Canvas Logic Verification: Test intricate coordinate calculations, grid constraints, and responsive layout behaviors across different device breakpoints.
- Performance Benchmarking: Implement performance-focused tests to ensure that layout calculations and link group processing remain efficient under heavy component loads.
- Mocking & Integration: Utilize standardized mocking patterns for stores and external modules like Konva to isolate and test specific component behaviors.
| name | component-testing |
|---|---|
| description | Write comprehensive unit tests for Visual Layout Builder using Vitest. Use when creating tests for lib/ functions, schema validation, canvas utilities, or any business logic. Follows AAA pattern and project testing conventions. |
| allowed-tools | Read, Glob, Grep |
Component Testing Skill
Visual Layout Builder 프로젝트의 Vitest 기반 유닛 테스트 작성을 위한 전문 스킬입니다. 579+ 테스트, ~14,000 라인의 테스트 코드 패턴을 따릅니다.
When to Use
lib/디렉토리의 새로운 함수 테스트 작성- 기존 테스트 파일 수정 또는 확장
- 버그 수정 후 회귀 테스트 추가
- 스키마 검증 로직 테스트
- Canvas 유틸리티 테스트
- 성능 테스트 작성
Test File Structure
lib/__tests__/
├── [module-name].test.ts # Main test file
├── fixtures/ # Shared test data
│ ├── component-fixtures.ts
│ └── test-schemas.ts
└── README.md # Test documentation
Test File Naming Convention
// Pattern: [module-name].test.ts
schema-validation.test.ts
canvas-utils.test.ts
prompt-generator.test.ts
// For specific features:
schema-utils-dynamic-breakpoints.test.ts
component-linking-concurrent.test.ts
Basic Test Template
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { functionToTest } from '../module-name'
describe('ModuleName', () => {
describe('functionToTest', () => {
it('should perform expected behavior when given valid input', () => {
// Arrange: 테스트 데이터 준비
const input = { /* test data */ }
// Act: 동작 수행
const result = functionToTest(input)
// Assert: 결과 검증
expect(result).toBe(expectedValue)
})
it('should handle edge case appropriately', () => {
// Arrange
const edgeCaseInput = { /* edge case */ }
// Act
const result = functionToTest(edgeCaseInput)
// Assert
expect(result).toMatchObject({ /* expected shape */ })
})
it('should throw error for invalid input', () => {
// Arrange
const invalidInput = null
// Act & Assert
expect(() => functionToTest(invalidInput)).toThrow('Expected error message')
})
})
})
AAA Pattern (Arrange-Act-Assert)
항상 AAA 패턴을 따르세요:
describe('Schema Validation', () => {
it('should validate component name is PascalCase', () => {
// Arrange: 초기 데이터 준비
const schema: LaydlerSchema = {
schemaVersion: '2.0',
components: [{
id: 'c1',
name: 'invalidName', // lowercase - should fail
semanticTag: 'header',
positioning: { type: 'sticky' },
layout: { type: 'flex' }
}],
breakpoints: [{ name: 'mobile', minWidth: 0, gridCols: 4, gridRows: 8 }],
layouts: { mobile: { structure: 'vertical', components: ['c1'] } }
}
// Act: 동작 수행
const result = validateSchema(schema)
// Assert: 결과 검증
expect(result.valid).toBe(false)
expect(result.errors).toContainEqual(
expect.objectContaining({
code: 'INVALID_COMPONENT_NAME',
componentId: 'c1'
})
)
})
})
Schema Testing Patterns
Valid Schema Fixture
// fixtures/test-schemas.ts
export const createValidSchema = (overrides = {}): LaydlerSchema => ({
schemaVersion: '2.0',
components: [{
id: 'c1',
name: 'Header',
semanticTag: 'header',
positioning: { type: 'sticky', position: { top: 0 } },
layout: { type: 'flex', flex: { direction: 'row', justify: 'between' } },
responsiveCanvasLayout: {
mobile: { x: 0, y: 0, width: 4, height: 1 }
}
}],
breakpoints: [
{ name: 'mobile', minWidth: 0, gridCols: 4, gridRows: 8 }
],
layouts: {
mobile: { structure: 'vertical', components: ['c1'] }
},
...overrides
})
Testing Validation Errors
describe('Schema Validation - Error Cases', () => {
it('should detect invalid breakpoint name', () => {
const schema = createValidSchema({
breakpoints: [
{ name: 'mobile@tablet', minWidth: 0, gridCols: 4, gridRows: 8 }
]
})
const result = validateSchema(schema)
expect(result.valid).toBe(false)
expect(result.errors[0].code).toBe('INVALID_BREAKPOINT_NAME')
})
it('should detect too many breakpoints', () => {
const schema = createValidSchema({
breakpoints: Array.from({ length: 11 }, (_, i) => ({
name: `bp${i}`,
minWidth: i * 100,
gridCols: 4,
gridRows: 8
}))
})
const result = validateSchema(schema)
expect(result.errors).toContainEqual(
expect.objectContaining({ code: 'TOO_MANY_BREAKPOINTS' })
)
})
})
Canvas Testing Patterns
Canvas Layout Testing
describe('Canvas Utils', () => {
describe('getCanvasLayoutForBreakpoint', () => {
it('should return layout for existing breakpoint', () => {
const component: Component = {
id: 'c1',
name: 'Header',
semanticTag: 'header',
positioning: { type: 'sticky' },
layout: { type: 'flex' },
responsiveCanvasLayout: {
mobile: { x: 0, y: 0, width: 4, height: 1 },
desktop: { x: 0, y: 0, width: 12, height: 1 }
}
}
const result = getCanvasLayoutForBreakpoint(component, 'desktop')
expect(result).toEqual({ x: 0, y: 0, width: 12, height: 1 })
})
it('should return undefined for missing breakpoint', () => {
const component: Component = {
id: 'c1',
name: 'Header',
semanticTag: 'header',
positioning: { type: 'sticky' },
layout: { type: 'flex' }
}
const result = getCanvasLayoutForBreakpoint(component, 'tablet')
expect(result).toBeUndefined()
})
})
})
Grid Constraint Testing
describe('Grid Constraints', () => {
it('should calculate minimum grid size based on components', () => {
const components: Component[] = [
createComponent({
responsiveCanvasLayout: {
mobile: { x: 0, y: 0, width: 4, height: 2 }
}
}),
createComponent({
responsiveCanvasLayout: {
mobile: { x: 0, y: 2, width: 3, height: 3 }
}
})
]
const { minCols, minRows } = calculateMinimumGridSize(components, 'mobile')
expect(minCols).toBe(4) // Max x + width
expect(minRows).toBe(5) // Max y + height
})
})
Performance Testing
describe('Performance', () => {
it('should calculate link groups in under 50ms for 100 components', () => {
// Arrange
const componentIds = Array.from({ length: 100 }, (_, i) => `c${i}`)
const links = Array.from({ length: 50 }, (_, i) => ({
source: `c${i * 2}`,
target: `c${i * 2 + 1}`
}))
// Act
const startTime = performance.now()
const result = calculateLinkGroups(componentIds, links)
const endTime = performance.now()
// Assert
expect(endTime - startTime).toBeLessThan(50)
expect(result.size).toBeGreaterThan(0)
})
})
Mocking Patterns
Mocking Store
import { vi } from 'vitest'
vi.mock('@/store/layout-store', () => ({
useLayoutStore: vi.fn(() => ({
schema: mockSchema,
addComponent: vi.fn(),
updateComponent: vi.fn()
}))
}))
Mocking External Modules
vi.mock('konva', () => ({
Stage: vi.fn(),
Layer: vi.fn(),
Rect: vi.fn()
}))
Test Organization Best Practices
Group Related Tests
describe('Prompt Generator', () => {
describe('generatePrompt', () => {
describe('with valid schema', () => {
it('should generate prompt successfully', () => { /* ... */ })
it('should include all components', () => { /* ... */ })
it('should include canvas layout info', () => { /* ... */ })
})
describe('with invalid schema', () => {
it('should return errors for invalid components', () => { /* ... */ })
it('should return warnings for suboptimal positioning', () => { /* ... */ })
})
describe('with component links', () => {
it('should include link section in prompt', () => { /* ... */ })
it('should validate links before including', () => { /* ... */ })
})
})
})
Use Test Fixtures
// At top of test file
import {
createValidSchema,
createValidComponent,
createComponentWithCanvas
} from './fixtures/test-schemas'
describe('Schema Utils', () => {
it('should normalize schema with inheritance', () => {
const schema = createValidSchema({
breakpoints: [
{ name: 'mobile', minWidth: 0, gridCols: 4, gridRows: 8 },
{ name: 'tablet', minWidth: 768, gridCols: 8, gridRows: 8 },
{ name: 'desktop', minWidth: 1024, gridCols: 12, gridRows: 8 }
]
})
const result = normalizeSchema(schema)
// Assertions...
})
})
Running Tests
# Watch mode (development)
pnpm test
# Run once (CI)
pnpm test:run
# With UI
pnpm test:ui
# With coverage
pnpm test:coverage
Test Coverage Targets
- 핵심 비즈니스 로직: 80%+ 커버리지
- 새로운 기능: 반드시 테스트 포함
- 버그 수정: 회귀 테스트 추가 필수
Reference Files
lib/__tests__/fixtures/test-schemas.ts- 공통 테스트 픽스처lib/__tests__/fixtures/component-fixtures.ts- 컴포넌트 픽스처vitest.config.ts- Vitest 설정lib/__tests__/README.md- 테스트 문서