Some checks reported errors
continuous-integration/drone Build encountered an error
342 lines
No EOL
13 KiB
HTML
342 lines
No EOL
13 KiB
HTML
<!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> |