Initial commit: Add MathHomeworkHelper project with web and Android components
Some checks reported errors
continuous-integration/drone Build encountered an error

This commit is contained in:
Juan José Gutiérrez de Quevedo Pérez 2025-11-25 11:07:51 +01:00
commit f9558008e1
37 changed files with 5318 additions and 0 deletions

342
tests.html Normal file
View file

@ -0,0 +1,342 @@
<!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>