Testing
MindGames uses Jest and React Testing Library for comprehensive testing. This document outlines the testing strategy and coverage.
Test Stackโ
| Tool | Purpose |
|---|---|
| Jest | Test runner and assertions |
| React Testing Library | Component testing |
| ts-jest | TypeScript support |
| jest-environment-jsdom | DOM simulation |
Running Testsโ
# Run all tests
npm test
# Run in watch mode
npm run test:watch
# Generate coverage report
npm run test:coverage
Test Structureโ
src/__tests__/
โโโ problem-generator.test.ts # Unit tests for core logic
โโโ GameContext.test.tsx # Integration tests for state
โโโ OperationMixSlider.test.tsx # Component tests
Test Categoriesโ
1. Unit Tests: Problem Generatorโ
Tests the core problem generation algorithm.
describe('generateChain', () => {
it('should create problems where each result feeds into the next', () => {
const chain = generateChain(DEFAULT_CONFIG);
let currentValue = chain!.startingNumber;
for (const problem of chain!.problems) {
expect(problem.startValue).toBe(currentValue);
currentValue = problem.result;
}
});
it('should produce clean division results (no decimals)', () => {
const config = {
...DEFAULT_CONFIG,
operationMix: { add: 0, subtract: 0, multiply: 0, divide: 100 },
};
const chain = generateChain(config);
for (const problem of chain!.problems) {
if (problem.operation === 'divide') {
expect(Number.isInteger(problem.result)).toBe(true);
}
}
});
});
Coverage:
- Chain generation
- Worksheet generation
- Operation mix selection
- Bounds validation
- Utility functions (formatNumber, sumOfDigits)
2. Integration Tests: GameContextโ
Tests the state management flow.
describe('Session Management', () => {
it('should calculate score on session end', () => {
const { result } = renderHook(() => useGame(), { wrapper });
act(() => {
result.current.generateNewWorksheet();
result.current.startSession();
});
const firstProblem = result.current.state.worksheet!.chains[0].problems[0];
act(() => {
result.current.submitAnswer(firstProblem.id, firstProblem.result);
});
act(() => {
result.current.endSession();
});
expect(result.current.state.session!.score.correct).toBe(1);
expect(result.current.state.session!.score.percentage).toBe(100);
});
});
Coverage:
- Initial state
- Worksheet generation
- Session start/end
- Answer submission
- Chain navigation
- Configuration changes
- Statistics calculation
3. Component Tests: OperationMixSliderโ
Tests the UI component behavior.
describe('Presets', () => {
it('should apply Basic preset (40/40/10/10)', () => {
render(<OperationMixSlider value={defaultMix} onChange={mockOnChange} />);
fireEvent.click(screen.getByText('Basic'));
expect(mockOnChange).toHaveBeenCalledWith({
add: 40,
subtract: 40,
multiply: 10,
divide: 10,
});
});
it('should highlight selected preset', () => {
const randomMix = { add: 25, subtract: 25, multiply: 25, divide: 25 };
render(<OperationMixSlider value={randomMix} onChange={mockOnChange} />);
const randomButton = screen.getByText('Random').closest('button');
expect(randomButton).toHaveClass('bg-primary-500');
});
});
Coverage:
- Rendering all controls
- Preset button clicks
- Increment/decrement buttons
- Slider interactions
- Minimum/maximum constraints
Test Configurationโ
jest.config.jsโ
const config = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', {
tsconfig: {
jsx: 'react-jsx',
esModuleInterop: true,
},
}],
},
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70,
},
},
};
jest.setup.tsโ
import '@testing-library/jest-dom';
// Mock canvas-confetti
jest.mock('canvas-confetti', () => jest.fn());
// Mock next/navigation
jest.mock('next/navigation', () => ({
useRouter: () => ({
push: jest.fn(),
replace: jest.fn(),
}),
}));
Current Coverageโ
| Metric | Target | Current |
|---|---|---|
| Tests Passing | 70% | 100% (63/63) |
| Branches | 70% | ~75% |
| Functions | 70% | ~80% |
| Lines | 70% | ~78% |
Testing Best Practicesโ
1. Use act() for State Updatesโ
act(() => {
result.current.generateNewWorksheet();
});
2. Isolate Hook Tests with Wrapperโ
const wrapper = ({ children }) => (
<GameProvider>{children}</GameProvider>
);
const { result } = renderHook(() => useGame(), { wrapper });
3. Test User Behavior, Not Implementationโ
// Good: Test what user sees
expect(screen.getByText('Basic')).toBeInTheDocument();
// Avoid: Testing internal state directly
expect(component.state.selectedPreset).toBe('basic');
4. Use Descriptive Test Namesโ
it('should apply Basic preset (40/40/10/10)', () => {});
it('should highlight selected preset', () => {});
it('should respect minimum percentage constraint', () => {});
Adding New Testsโ
- Create test file in
src/__tests__/ - Import testing utilities and component/module
- Group tests with
describeblocks - Write individual tests with
itblocks - Run
npm testto verify
import { render, screen, fireEvent } from '@testing-library/react';
import MyComponent from '@/components/MyComponent';
describe('MyComponent', () => {
it('should render correctly', () => {
render(<MyComponent />);
expect(screen.getByText('Expected Text')).toBeInTheDocument();
});
});