| 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
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html', 'lcov'],
lines: 90,
functions: 90,
branches: 85,
statements: 90,
exclude: [
'node_modules/',
'dist/',
'coverage/',
'**/*.test.ts',
'**/*.test.tsx',
'src/test/**',
],
},
},
});
✅ Coverage by Component Type
const COVERAGE_TARGETS = {
combatSystem: { unit: 95, e2e: 85 },
vitalPointSystem: { unit: 95, e2e: 80 },
trigramSystem: { unit: 95, e2e: 80 },
animationSystem: { unit: 90, e2e: 75 },
threejsComponents: { unit: 85, e2e: 70 },
sceneComponents: { unit: 85, e2e: 70 },
reactComponents: { unit: 95, e2e: 80 },
screens: { unit: 90, e2e: 85 },
utilities: { unit: 100, e2e: 0 },
constants: { unit: 100, e2e: 0 },
audioSystem: { unit: 90, e2e: 75 },
stateManagement: { unit: 95, e2e: 80 },
} as const;
2. Unit Testing Strategy (Vitest)
ALWAYS write comprehensive unit tests:
✅ Test File Organization
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
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(() => {
combatSystem = new CombatSystem();
});
afterEach(() => {
vi.clearAllMocks();
});
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',
force: 100,
accuracy: 0.95,
});
expect(damage).toBeGreaterThan(100);
expect(damage).toBeLessThanOrEqual(200);
});
it('should apply reduced damage for missed vital points', () => {
const missedDamage = combatSystem.calculateVitalPointDamage({
point: 'BAIHUI',
force: 100,
accuracy: 0.3,
});
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);
});
});
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
import { render } from '@testing-library/react';
import { Canvas } from '@react-three/fiber';
import { describe, it, expect } from 'vitest';
import { CombatCharacter3D } from '../CombatCharacter3D';
describe('CombatCharacter3D', () => {
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"
/>
);
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>
);
expect(true).toBe(true);
});
});
3. E2E Testing Strategy (Cypress)
ALWAYS write E2E tests for user workflows:
✅ E2E Test Organization
cypress/
├── e2e/
│ ├── combat-system.cy.ts
│ ├── trigram-selection.cy.ts
│ ├── vital-points.cy.ts
│ ├── intro-screen.cy.ts
│ └── performance.cy.ts
✅ Comprehensive E2E Test
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)');
});
});
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);
frameCount = 0;
lastTime = currentTime;
}
frameCount++;
if (frameCount < 300) {
requestAnimationFrame(measureFPS);
}
};
requestAnimationFrame(measureFPS);
});
});
});
describe('Accessibility', () => {
it('should be keyboard navigable', () => {
cy.get('body').type('{tab}');
cy.focused().should('have.attr', 'data-testid');
cy.get('body').type('1');
cy.get('[data-testid="current-stance"]').should('contain', 'GEON');
cy.get('body').type('2');
cy.get('[data-testid="current-stance"]').should('contain', 'TAE');
});
it('should have proper ARIA labels', () => {
cy.get('[data-testid="stance-GEON"]')
.should('have.attr', 'aria-label')
.and('contain', '건')
.and('contain', 'Heaven');
});
it('should meet WCAG 2.1 AA contrast standards', () => {
cy.get('[data-testid="combat-screen"]').then($el => {
const bgColor = $el.css('background-color');
const textColor = $el.css('color');
expect(bgColor).to.exist;
expect(textColor).to.exist;
});
});
});
});
4. Performance Testing Requirements
ALWAYS validate 60fps performance:
✅ Performance Test Pattern
describe('Performance - Combat System', () => {
it('should complete combat calculation in <5ms', () => {
const combatSystem = new CombatSystem();
const iterations = 1000;
const start = performance.now();
for (let i = 0; i < iterations; i++) {
combatSystem.calculateDamage(100, 0.85, 'BAIHUI');
}
const duration = performance.now() - start;
const avgTime = duration / iterations;
expect(avgTime).toBeLessThan(5);
});
it('should handle 1000 vital point checks per frame', () => {
const vitalPointSystem = new VitalPointSystem();
const start = performance.now();
for (let i = 0; i < 1000; i++) {
vitalPointSystem.checkHit([
Math.random() * 2 - 1,
Math.random() * 2 - 1,
Math.random() * 2 - 1,
]);
}
const duration = performance.now() - start;
expect(duration).toBeLessThan(16.67);
});
it('should maintain 60fps during 28-bone animation', () => {
const animationSystem = new SkeletalAnimationSystem();
const frameCount = 300;
const start = performance.now();
for (let i = 0; i < frameCount; i++) {
animationSystem.updateBones(0.0166);
}
const duration = performance.now() - start;
const avgFrameTime = duration / frameCount;
expect(avgFrameTime).toBeLessThan(16.67);
});
});
5. Accessibility Testing Standards
ALWAYS ensure WCAG 2.1 Level AA compliance:
✅ Accessibility Test Pattern
describe('Accessibility - Combat UI', () => {
it('should have proper semantic HTML structure', () => {
const { container } = render(<CombatScreen width={1200} height={800} />);
expect(container.querySelector('main')).toBeInTheDocument();
expect(container.querySelector('nav')).toBeInTheDocument();
});
it('should have ARIA labels on interactive elements', () => {
const { getByRole } = render(<CombatScreen width={1200} height={800} />);
const stanceButton = getByRole('button', { name: /건.*Heaven/i });
expect(stanceButton).toHaveAttribute('aria-label');
});
it('should support keyboard navigation', () => {
const { getByTestId } = render(<CombatScreen width={1200} height={800} />);
const firstButton = getByTestId('stance-GEON');
firstButton.focus();
expect(document.activeElement).toBe(firstButton);
});
it('should meet WCAG 2.1 AA contrast ratio (4.5:1)', () => {
const textColor = KOREAN_COLORS.TEXT_PRIMARY;
const bgColor = KOREAN_COLORS.UI_BACKGROUND_DARK;
expect(textColor).toBe(0xffffff);
expect(bgColor).toBe(0x0a0a0a);
});
it('should have focus indicators', () => {
const { getByTestId } = render(<CombatScreen width={1200} height={800} />);
const button = getByTestId('stance-GEON');
button.focus();
const styles = window.getComputedStyle(button);
expect(
styles.outline !== 'none' ||
styles.border !== 'none'
).toBe(true);
});
});
6. Common Testing Anti-Patterns to REJECT
Immediately flag and reject these patterns:
❌ Missing Test Files
src/systems/grappling/GrapplingSystem.ts
❌ Insufficient Test Coverage
describe('CombatSystem', () => {
it('should work', () => {
const combat = new CombatSystem();
expect(combat).toBeTruthy();
});
});
describe('CombatSystem', () => {
it('should initialize with default values', () => { });
it('should handle valid input', () => { });
it('should reject invalid input', () => { });
it('should handle edge cases', () => { });
it('should meet performance targets', () => { });
});
❌ Missing data-testid Attributes
<button onClick={handleClick}>
공격 | Attack
</button>
<button
data-testid="attack-button"
aria-label="공격 | Attack"
onClick={handleClick}
>
공격 | Attack
</button>
❌ No Performance Tests
export function calculateDamage(force: number, accuracy: number): number {
return result;
}
describe('calculateDamage Performance', () => {
it('should complete in <5ms', () => {
const start = performance.now();
for (let i = 0; i < 1000; i++) {
calculateDamage(100, 0.85);
}
const duration = performance.now() - start;
expect(duration / 1000).toBeLessThan(5);
});
});
❌ No Accessibility Tests
export const StanceSelector: React.FC = () => {
return <div>Stance selection</div>;
};
describe('StanceSelector Accessibility', () => {
it('should have ARIA labels', () => { });
it('should be keyboard navigable', () => { });
it('should meet WCAG contrast standards', () => { });
});
7. Required Testing Patterns
Enforce these testing patterns:
✅ Complete Test Suite Structure
describe('ComponentName', () => {
beforeEach(() => { });
afterEach(() => { });
describe('Initialization', () => {
it('should initialize with default values', () => { });
it('should accept custom configuration', () => { });
});
describe('Core Functionality', () => {
it('should handle valid input correctly', () => { });
it('should reject invalid input', () => { });
it('should maintain state correctly', () => { });
});
describe('Edge Cases', () => {
it('should handle empty input', () => { });
it('should handle boundary values', () => { });
it('should handle concurrent operations', () => { });
});
describe('Performance', () => {
it('should complete within performance budget', () => { });
it('should scale with input size', () => { });
});
describe('Integration', () => {
it('should integrate with dependent systems', () => { });
});
});
✅ Mock Audio System for Testing
import { vi } from 'vitest';
global.AudioContext = vi.fn().mockImplementation(() => ({
createGain: vi.fn().mockReturnValue({
connect: vi.fn(),
gain: { value: 1 },
}),
createOscillator: vi.fn().mockReturnValue({
connect: vi.fn(),
start: vi.fn(),
stop: vi.fn(),
}),
}));
vi.mock('howler', () => ({
Howl: vi.fn().mockImplementation(() => ({
play: vi.fn(),
stop: vi.fn(),
volume: vi.fn(),
})),
}));
Enforcement Rules
Rule 1: No New Code Without Tests
IF (new feature or component added)
THEN (unit tests AND E2E tests MUST be included)
ELSE (reject the change)
Rule 2: Maintain Minimum 90% Coverage
IF (pull request submitted)
THEN (verify coverage meets 90% threshold)
ELSE (add tests to meet coverage requirement)
Rule 3: All Interactive Elements Need data-testid
IF (button, input, or interactive element added)
THEN (add data-testid attribute)
ELSE (add test ID before approval)
Rule 4: Performance Tests for Critical Code
IF (code affects combat, animation, or rendering)
THEN (include performance tests with <5ms budget)
ELSE (add performance validation)
Rule 5: Accessibility Tests for UI Components
IF (UI component added or modified)
THEN (include ARIA labels, keyboard nav, WCAG contrast tests)
ELSE (add accessibility tests)
Test Quality Checklist
Before approving any code change:
ISO 27001 Alignment
This skill enforces controls from:
- A.12.1 - Operational Procedures (Testing standards documented)
- A.14.2 - Security in Development Processes (Testing in SDLC)
- A.14.3 - Test Data (Test data management practices)
- A.18.2 - Compliance with Security Policies (Testing compliance)
NIST CSF 2.0 Alignment
- GV.PO-01: Testing policy established and communicated
- ID.RA-01: Asset vulnerabilities identified (via testing)
- PR.DS-06: Integrity checking through automated tests
- DE.AE-02: Event analysis capabilities (test failures)
- RS.AN-03: Analysis performed to understand root cause (test debugging)
CIS Controls v8.1 Alignment
- Control 4: Secure Configuration (validated through tests)
- Control 16: Application Software Security (comprehensive testing)
- Control 17: Incident Response Management (test failure handling)
Remember
Testing is not a checkbox—it is the foundation of quality. Every feature, every component, every line of code must be validated through comprehensive, meaningful tests.
When writing tests:
- COVER - Achieve >90% coverage with meaningful tests
- VALIDATE - Test happy paths, edge cases, and error conditions
- PERFORM - Ensure code meets 60fps performance targets
- ACCESS - Validate WCAG 2.1 Level AA accessibility
- INTEGRATE - Test E2E user workflows with Cypress
- AUTOMATE - Run tests in CI/CD pipeline
흑괘의 품질을 검증하라 - Validate the Quality of the Black Trigram