- name
- testing-strategy-enforcement
- description
- Enforces comprehensive testing standards for Black Trigram including >90% coverage,
unit tests (Vitest), E2E tests (Cypress), Three.js component testing, performance
testing (60fps target), and accessibility testing. Ensures test quality and coverage.
- license
- MIT
# Testing Strategy Enforcement Skill
## Purpose
This skill ensures that all code changes in Black Trigram maintain exceptional test coverage, proper testing strategies across unit/E2E layers, and validate performance and accessibility standards throughout the codebase.
## When to Apply
**Automatically trigger this skill when:**
- Adding new features or components
- Modifying existing game logic or systems
- Implementing UI components (React or Three.js)
- Working with combat, animation, or vital point systems
- Creating or updating state management logic
- Adding performance-critical code
- Implementing accessibility features
- Refactoring existing code
- Reviewing pull requests with code changes
## Core Principles
### 1. Test Coverage Requirements
**ALWAYS maintain these coverage thresholds:**
✅ **Overall Coverage Targets**
```typescript
// Partial excerpt from vitest.config.ts - Coverage configuration
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html', 'lcov'],
lines: 90, // Minimum 90% line coverage
functions: 90, // Minimum 90% function coverage
branches: 85, // Minimum 85% branch coverage
statements: 90, // Minimum 90% statement coverage
exclude: [
'node_modules/',
'dist/',
'coverage/',
'**/*.test.ts',
'**/*.test.tsx',
'src/test/**',
],
},
},
});
```
✅ **Coverage by Component Type**
```typescript
// Target coverage levels per component category
const COVERAGE_TARGETS = {
// Core game systems - CRITICAL
combatSystem: { unit: 95, e2e: 85 },
vitalPointSystem: { unit: 95, e2e: 80 },
trigramSystem: { unit: 95, e2e: 80 },
animationSystem: { unit: 90, e2e: 75 },
// Three.js components - MODERATE (harder to test)
threejsComponents: { unit: 85, e2e: 70 },
sceneComponents: { unit: 85, e2e: 70 },
// UI components - HIGH
reactComponents: { unit: 95, e2e: 80 },
screens: { unit: 90, e2e: 85 },
// Utility functions - MAXIMUM
utilities: { unit: 100, e2e: 0 },
constants: { unit: 100, e2e: 0 },
// Audio system - HIGH
audioSystem: { unit: 90, e2e: 75 },
// State management - CRITICAL
stateManagement: { unit: 95, e2e: 80 },
} as const;
```
### 2. Unit Testing Strategy (Vitest)
**ALWAYS write comprehensive unit tests:**
✅ **Test File Organization**
```typescript
// ALWAYS place unit tests in __tests__ directory
src/
├── systems/
│ ├── combat/
│ │ ├── CombatSystem.ts
│ │ └── __tests__/
│ │ ├── CombatSystem.test.ts
│ │ ├── VitalPointSystem.test.ts
│ │ └── DamageCalculation.test.ts
│ ├── trigram/
│ │ ├── TrigramSystem.ts
│ │ └── __tests__/
│ │ └── TrigramSystem.test.ts
```
✅ **Comprehensive Test Structure**
```typescript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { CombatSystem } from '../CombatSystem';
import { TRIGRAM_STANCES } from '@/types/constants';
describe('CombatSystem', () => {
let combatSystem: CombatSystem;
beforeEach(() => {
// Setup fresh instance for each test
combatSystem = new CombatSystem();
});
afterEach(() => {
// Cleanup resources
vi.clearAllMocks();
});
// Group related tests
describe('Stance Management', () => {
it('should initialize with Geon stance by default', () => {
expect(combatSystem.currentStance).toBe('GEON');
});
it('should transition between all 8 trigram stances', () => {
Object.keys(TRIGRAM_STANCES).forEach(stance => {
combatSystem.setStance(stance);
expect(combatSystem.currentStance).toBe(stance);
});
});
it('should reject invalid stance names', () => {
expect(() => combatSystem.setStance('INVALID')).toThrow();
});
});
describe('Vital Point Targeting', () => {
it('should detect all 70 vital points', () => {
const vitalPoints = combatSystem.getVitalPoints();
expect(vitalPoints).toHaveLength(70);
});
it('should calculate correct damage for vital point hit', () => {
const damage = combatSystem.calculateVitalPointDamage({
point: 'BAIHUI', // GV20
force: 100,
accuracy: 0.95,
});
expect(damage).toBeGreaterThan(100); // Vital point multiplier
expect(damage).toBeLessThanOrEqual(200);
});
it('should apply reduced damage for missed vital points', () => {
const missedDamage = combatSystem.calculateVitalPointDamage({
point: 'BAIHUI',
force: 100,
accuracy: 0.3, // Poor accuracy
});
expect(missedDamage).toBeLessThan(100);
});
});
describe('Performance', () => {
it('should calculate combat within 5ms', () => {
const start = performance.now();
for (let i = 0; i < 1000; i++) {
combatSystem.calculateDamage(100, 0.8, 'BAIHUI');
}
const duration = performance.now() - start;
expect(duration).toBeLessThan(5); // <5ms per frame budget
});
});
describe('Edge Cases', () => {
it('should handle zero damage gracefully', () => {
const damage = combatSystem.calculateDamage(0, 1.0, 'BAIHUI');
expect(damage).toBe(0);
});
it('should handle negative force by clamping to zero', () => {
const damage = combatSystem.calculateDamage(-50, 1.0, 'BAIHUI');
expect(damage).toBe(0);
});
it('should handle accuracy outside 0-1 range', () => {
expect(() =>
combatSystem.calculateDamage(100, 1.5, 'BAIHUI')
).toThrow();
});
});
});
```
✅ **Testing Three.js Components**
```typescript
import { render } from '@testing-library/react';
import { Canvas } from '@react-three/fiber';
import { describe, it, expect } from 'vitest';
import { CombatCharacter3D } from '../CombatCharacter3D';
describe('CombatCharacter3D', () => {
// Helper to render Three.js components
const render3D = (component: React.ReactElement) => {
return render(
<Canvas data-testid="test-canvas">
<Suspense fallback={null}>
{component}
</Suspense>
</Canvas>
);
};
it('should render without crashing', () => {
const { container } = render3D(
<CombatCharacter3D
position={[0, 0, 0]}
stance="GEON"
/>
);
const canvas = container.querySelector('canvas');
expect(canvas).toBeInTheDocument();
});
it('should apply Korean theming colors', () => {
const { container } = render3D(
<CombatCharacter3D
position={[0, 0, 0]}
stance="GEON"
data-testid="combat-character"
/>
);
// Verify component renders (color testing requires more complex setup)
expect(container).toBeTruthy();
});
it('should update stance color when stance changes', () => {
const { rerender } = render3D(
<CombatCharacter3D position={[0, 0, 0]} stance="GEON" />
);
rerender(
<Canvas>
<CombatCharacter3D position={[0, 0, 0]} stance="TAE" />
</Canvas>
);
// Verify re-render succeeds
expect(true).toBe(true);
});
});
```
### 3. E2E Testing Strategy (Cypress)
**ALWAYS write E2E tests for user workflows:**
✅ **E2E Test Organization**
```typescript
// cypress/e2e/ - User workflow tests
cypress/
├── e2e/
│ ├── combat-system.cy.ts // Combat gameplay flows
│ ├── trigram-selection.cy.ts // Stance selection UI
│ ├── vital-points.cy.ts // Vital point targeting
│ ├── intro-screen.cy.ts // Navigation flows
│ └── performance.cy.ts // 60fps validation
```
✅ **Comprehensive E2E Test**
```typescript
describe('Combat System E2E', () => {
beforeEach(() => {
cy.visit('/');
cy.get('[data-testid="start-button"]').click();
cy.get('[data-testid="combat-screen"]').should('be.visible');
});
describe('Trigram Stance Selection', () => {
it('should allow selecting all 8 trigram stances', () => {
const stances = ['GEON', 'TAE', 'LI', 'JIN', 'SON', 'GAM', 'GAN', 'GON'];
stances.forEach(stance => {
cy.get(`[data-testid="stance-${stance}"]`).click();
cy.get('[data-testid="current-stance"]')
.should('contain', stance);
});
});
it('should display bilingual Korean | English text', () => {
cy.get('[data-testid="stance-GEON"]')
.should('contain', '건')
.and('contain', 'Heaven');
});
it('should show trigram symbol (☰)', () => {
cy.get('[data-testid="stance-GEON"]')
.should('contain', '☰');
});
});
describe('Vital Point Targeting', () => {
it('should highlight all 70 vital points', () => {
cy.get('[data-testid="vital-point-overlay"]').click();
cy.get('[data-testid^="vital-point-"]')
.should('have.length', 70);
});
it('should show Korean and TCM names on hover', () => {
cy.get('[data-testid="vital-point-BAIHUI"]').trigger('mouseover');
cy.get('[data-testid="vital-point-tooltip"]')
.should('contain', '백회')
.and('contain', 'Baihui (GV20)');
});
it('should calculate critical damage on precise hit', () => {
cy.get('[data-testid="vital-point-BAIHUI"]').click();
cy.get('[data-testid="damage-indicator"]')
.should('contain', 'CRITICAL')
.and('have.css', 'color', 'rgb(255, 68, 68)'); // KOREAN_COLORS.CRITICAL_HIT
});
});
describe('Combat Performance', () => {
it('should maintain 60fps during combat', () => {
cy.window().then(win => {
let frameCount = 0;
let lastTime = performance.now();
const measureFPS = () => {
const currentTime = performance.now();
const deltaTime = currentTime - lastTime;
if (deltaTime >= 1000) {
const fps = frameCount / (deltaTime / 1000);
expect(fps).to.be.greaterThan(55); // Allow 5fps tolerance
frameCount = 0;
lastTime = currentTime;
}
frameCount++;
if (frameCount < 300) { // Test for 5 seconds
requestAnimationFrame(measureFPS);
}
};
requestAnimationFrame(measureFPS);
});
});
});
describe('Accessibility', () => {
it('should be keyboard navigable', () => {
Ver en GitHub