mathstuff/tests.html
2025-11-25 11:07:51 +01:00

342 lines
No EOL
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Math Homework Helper - Unit Tests</title>
<style>
body {
font-family: 'Segoe UI', 'Roboto', 'Arial', sans-serif;
background: #f5f5f5;
padding: 20px;
max-width: 1000px;
margin: 0 auto;
}
.test-container {
background: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.test-title {
font-size: 1.5em;
font-weight: bold;
margin-bottom: 20px;
color: #2563eb;
}
.test-case {
margin-bottom: 15px;
padding: 10px;
border-left: 4px solid #ddd;
background: #f9f9f9;
}
.test-case.pass {
border-left-color: #16a34a;
background: #f0fdf4;
}
.test-case.fail {
border-left-color: #dc2626;
background: #fef2f2;
}
.test-name {
font-weight: bold;
margin-bottom: 5px;
}
.test-result {
font-size: 0.9em;
color: #666;
}
.pass-text {
color: #16a34a;
font-weight: bold;
}
.fail-text {
color: #dc2626;
font-weight: bold;
}
.summary {
font-size: 1.2em;
font-weight: bold;
padding: 15px;
border-radius: 5px;
margin-top: 20px;
}
.summary.all-pass {
background: #dcfce7;
color: #16a34a;
}
.summary.some-fail {
background: #fee2e2;
color: #dc2626;
}
</style>
</head>
<body>
<h1>🧪 Math Homework Helper - Unit Tests</h1>
<div id="test-results"></div>
<script>
// Test Framework
class TestRunner {
constructor() {
this.tests = [];
this.passed = 0;
this.failed = 0;
}
test(name, fn) {
try {
fn();
this.tests.push({ name, status: 'pass', error: null });
this.passed++;
} catch (error) {
this.tests.push({ name, status: 'fail', error: error.message });
this.failed++;
}
}
assert(condition, message) {
if (!condition) {
throw new Error(message || 'Assertion failed');
}
}
assertEqual(actual, expected, message) {
if (actual !== expected) {
throw new Error(message || `Expected ${expected}, got ${actual}`);
}
}
render() {
const resultsDiv = document.getElementById('test-results');
let html = '';
// Test cases
this.tests.forEach(test => {
const className = test.status === 'pass' ? 'pass' : 'fail';
const statusText = test.status === 'pass'
? '<span class="pass-text">✓ PASS</span>'
: '<span class="fail-text">✗ FAIL</span>';
html += `
<div class="test-case ${className}">
<div class="test-name">${test.name}</div>
<div class="test-result">${statusText}${test.error ? ': ' + test.error : ''}</div>
</div>
`;
});
// Summary
const totalTests = this.passed + this.failed;
const summaryClass = this.failed === 0 ? 'all-pass' : 'some-fail';
const summaryText = this.failed === 0
? `✓ All ${totalTests} tests passed!`
: `${this.failed} of ${totalTests} tests failed`;
html += `<div class="summary ${summaryClass}">${summaryText}</div>`;
resultsDiv.innerHTML = `
<div class="test-container">
<div class="test-title">Test Results</div>
${html}
</div>
`;
}
}
// Initialize test runner
const runner = new TestRunner();
// ============================================
// UNIT TESTS
// ============================================
// Test 1: getRandomNumber function
runner.test('getRandomNumber generates number within range', () => {
// Mock function since we need to test the logic
const getRandomNumber = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
for (let i = 0; i < 100; i++) {
const num = getRandomNumber(1, 9);
runner.assert(num >= 1 && num <= 9, `Random number ${num} out of range [1, 9]`);
}
});
// Test 2: calculateIntermediateSteps for 23 × 45
runner.test('calculateIntermediateSteps calculates correct partial products', () => {
const calculateIntermediateSteps = (num1, num2) => {
const num2Str = num2.toString();
const steps = [];
for (let i = num2Str.length - 1; i >= 0; i--) {
const digit = parseInt(num2Str[i]);
const partialProduct = num1 * digit;
const shiftAmount = num2Str.length - 1 - i;
steps.push({ digit, product: partialProduct, shift: shiftAmount });
}
return steps;
};
const steps = calculateIntermediateSteps(23, 45);
runner.assertEqual(steps.length, 2, 'Should have 2 steps for 2-digit multiplicand');
runner.assertEqual(steps[0].product, 115, 'First step: 23 × 5 = 115');
runner.assertEqual(steps[0].shift, 0, 'First step shift should be 0');
runner.assertEqual(steps[1].product, 92, 'Second step: 23 × 4 = 92');
runner.assertEqual(steps[1].shift, 1, 'Second step shift should be 1');
});
// Test 3: findIncorrectDigits function
runner.test('findIncorrectDigits identifies wrong digits correctly', () => {
const findIncorrectDigits = (userAnswer, correctAnswer) => {
const userStr = userAnswer.toString();
const correctStr = correctAnswer.toString();
const incorrectPositions = [];
const maxLen = Math.max(userStr.length, correctStr.length);
const userPadded = userStr.padStart(maxLen, '0');
const correctPadded = correctStr.padStart(maxLen, '0');
for (let i = 0; i < maxLen; i++) {
if (userPadded[i] !== correctPadded[i]) {
incorrectPositions.push(i);
}
}
return incorrectPositions;
};
const incorrect = findIncorrectDigits(123, 125);
runner.assertEqual(incorrect.length, 1, 'Should find 1 incorrect digit');
runner.assertEqual(incorrect[0], 2, 'Incorrect digit at position 2');
});
// Test 4: Point calculation - correct answer
runner.test('Correct answer adds 1 point', () => {
let points = 0;
const userAnswer = 72;
const correctAnswer = 72;
if (userAnswer === correctAnswer) {
points += 1;
}
runner.assertEqual(points, 1, 'Should add 1 point for correct answer');
});
// Test 5: Point calculation - incorrect answer
runner.test('Incorrect answer subtracts 2 points (minimum 0)', () => {
let points = 5;
const userAnswer = 70;
const correctAnswer = 72;
if (userAnswer !== correctAnswer) {
points = Math.max(0, points - 2);
}
runner.assertEqual(points, 3, 'Should subtract 2 points for incorrect answer');
});
// Test 6: Point calculation - incorrect answer with 0 points
runner.test('Points cannot go below 0', () => {
let points = 1;
const userAnswer = 70;
const correctAnswer = 72;
if (userAnswer !== correctAnswer) {
points = Math.max(0, points - 2);
}
runner.assertEqual(points, 0, 'Points should not go below 0');
});
// Test 7: Victory condition
runner.test('Victory triggered at 20 points', () => {
let points = 19;
let victory = false;
points += 1;
if (points >= 20) {
victory = true;
}
runner.assert(victory, 'Victory should be triggered at 20 points');
});
// Test 8: Difficulty configuration
runner.test('Difficulty levels have correct configurations', () => {
const difficultyConfig = {
1: { num1Min: 1, num1Max: 9, num2Min: 1, num2Max: 9 },
2: { num1Min: 10, num1Max: 99, num2Min: 1, num2Max: 9 },
3: { num1Min: 10, num1Max: 99, num2Min: 10, num2Max: 99 },
4: { num1Min: 100, num1Max: 999, num2Min: 10, num2Max: 99 },
5: { num1Min: 10000, num1Max: 99999, num2Min: 100, num2Max: 999 }
};
runner.assertEqual(difficultyConfig[1].num1Max, 9, 'Level 1: num1Max should be 9');
runner.assertEqual(difficultyConfig[2].num2Max, 9, 'Level 2: num2Max should be 9');
runner.assertEqual(difficultyConfig[3].num2Min, 10, 'Level 3: num2Min should be 10');
runner.assertEqual(difficultyConfig[5].num1Max, 99999, 'Level 5: num1Max should be 99999');
});
// Test 9: Intermediate steps only for 2+ digit multiplicand
runner.test('Intermediate steps skipped for 1-digit multiplicand', () => {
const num2 = 7; // 1-digit
const shouldShowIntermediateSteps = num2 > 9;
runner.assert(!shouldShowIntermediateSteps, 'Should not show intermediate steps for 1-digit multiplicand');
});
// Test 10: Intermediate steps shown for 2+ digit multiplicand
runner.test('Intermediate steps shown for 2+ digit multiplicand', () => {
const num2 = 45; // 2-digit
const shouldShowIntermediateSteps = num2 > 9;
runner.assert(shouldShowIntermediateSteps, 'Should show intermediate steps for 2+ digit multiplicand');
});
// Test 11: Multiplication result calculation
runner.test('Multiplication result calculated correctly', () => {
const num1 = 23;
const num2 = 45;
const result = num1 * num2;
runner.assertEqual(result, 1035, '23 × 45 should equal 1035');
});
// Test 12: Intermediate step verification
runner.test('Intermediate step verification detects errors', () => {
const userStepValue = '115';
const correctStepNum = 115;
const isCorrect = parseInt(userStepValue) === correctStepNum;
runner.assert(isCorrect, 'Intermediate step should be verified correctly');
});
// Test 13: Intermediate step verification - incorrect
runner.test('Intermediate step verification detects incorrect values', () => {
const userStepValue = '116';
const correctStepNum = 115;
const isCorrect = parseInt(userStepValue) === correctStepNum;
runner.assert(!isCorrect, 'Incorrect intermediate step should be detected');
});
// Test 14: Answer validation - empty answer
runner.test('Empty answer is rejected', () => {
const answer = '';
const isValid = answer !== '';
runner.assert(!isValid, 'Empty answer should be invalid');
});
// Test 15: Answer validation - valid answer
runner.test('Valid numeric answer is accepted', () => {
const answer = '72';
const isValid = answer !== '' && !isNaN(parseInt(answer));
runner.assert(isValid, 'Valid numeric answer should be accepted');
});
// Render results
runner.render();
</script>
</body>
</html>