/** * Tests for Student Loan Matching Calculator * Run with: node calculations.test.js */ const { calculatePayoffYears, calculateRemainingBalance, calculateRetirementValue, calculateNetWorthComparison } = require('./calculations.js'); // Test utilities let testsPassed = 0; let testsFailed = 0; function assert(condition, message) { if (condition) { console.log(`✓ ${message}`); testsPassed++; } else { console.error(`✗ ${message}`); testsFailed++; } } function assertClose(actual, expected, tolerance = 0.01, message = '') { const diff = Math.abs(actual - expected); const pass = diff <= tolerance; if (pass) { console.log(`✓ ${message} (${actual} ≈ ${expected})`); testsPassed++; } else { console.error(`✗ ${message} (${actual} != ${expected}, diff: ${diff})`); testsFailed++; } } // Test calculatePayoffYears console.log('\n=== Testing calculatePayoffYears ==='); assert( calculatePayoffYears(30000, 500) === 6, 'Should calculate ~6 years for $30k debt at $500/month (5% interest)' ); assert( calculatePayoffYears(30000, 0) === 0, 'Should return 0 for zero payment' ); assert( calculatePayoffYears(30000, 100) === 50, 'Should return 50 (max) for payment below interest' ); // Test calculateRemainingBalance console.log('\n=== Testing calculateRemainingBalance ==='); const balance5Years = calculateRemainingBalance(30000, 500, 60, 84); assertClose( calculateRemainingBalance(30000, 500, 60, 6 * 12), 4498, 100, 'After 5 years of $500 payments on $30k, should have ~$4.5k remaining (5% interest)' ); assert( calculateRemainingBalance(30000, 500, 0, 84) === 30000, 'At month 0, balance should equal original' ); assert( calculateRemainingBalance(30000, 500, 84, 84) === 0, 'At final month, balance should be 0' ); assert( calculateRemainingBalance(30000, 500, 100, 84) === 0, 'After loan term, balance should be 0' ); // Test calculateRetirementValue console.log('\n=== Testing calculateRetirementValue ==='); const value10Years = calculateRetirementValue(5000, 10, 10); assertClose( value10Years, 73918, 100, '$5k/year for 10 years at 7% should grow to ~$74k' ); const value30Years = calculateRetirementValue(5000, 10, 30); assertClose( value30Years, 286039, 100, '$5k/year for 10 years, growing for 30 total years should be ~$286k' ); // Test full calculation scenario console.log('\n=== Testing Full Scenario ==='); const scenario1 = calculateNetWorthComparison({ salary: 60000, totalDebt: 30000, monthlyPayment: 500, matchRate: 5, currentAge: 25 }); console.log('\nScenario 1 Results:'); console.log(` Payoff years: ${scenario1.payoffYears}`); console.log(` Annual match: $${scenario1.annualMatch}`); console.log(` Retirement with program: $${scenario1.totalRetirementWith.toLocaleString()}`); console.log(` Retirement without program: $${scenario1.totalRetirementWithout.toLocaleString()}`); console.log(` Difference: $${scenario1.retirementDifference.toLocaleString()}`); assert( scenario1.payoffYears === 6, 'Should take 6 years to pay off $30k at $500/month (5% interest)' ); assert( scenario1.annualMatch === 3000, 'Annual match should be $3000 (5% of $60k salary)' ); assert( scenario1.retirementDifference > 0, 'With program should result in more retirement savings' ); // Test edge case: very high payment const scenario2 = calculateNetWorthComparison({ salary: 100000, totalDebt: 20000, monthlyPayment: 2000, matchRate: 4, currentAge: 30 }); console.log('\nScenario 2 Results (High Payment):'); console.log(` Payoff years: ${scenario2.payoffYears}`); console.log(` Annual match: $${scenario2.annualMatch}`); assert( scenario2.payoffYears <= 2, 'High payment should result in quick payoff' ); assert( scenario2.annualMatch === 4000, 'Annual match should be capped at 4% of salary' ); // Test the growth rates after payoff console.log('\n=== Testing Growth Rates After Payoff ==='); const scenario3 = calculateNetWorthComparison({ salary: 70000, totalDebt: 40000, monthlyPayment: 600, matchRate: 5, currentAge: 28 }); // Find the index right after payoff const payoffIndex = Math.ceil((scenario3.payoffYears / 2)); if (payoffIndex < scenario3.withProgram.length - 1) { const withGrowth = scenario3.withProgram[payoffIndex + 1] - scenario3.withProgram[payoffIndex]; const withoutGrowth = scenario3.withoutProgram[payoffIndex + 1] - scenario3.withoutProgram[payoffIndex]; console.log(`\nGrowth rate comparison after payoff:`); console.log(` With program growth: $${withGrowth}`); console.log(` Without program growth: $${withoutGrowth}`); // After payoff, both should be contributing the same amount, so growth should be similar // The "with" program will have a larger base, but the rate of change should be close const growthRatio = withoutGrowth > 0 ? withGrowth / withoutGrowth : 0; assertClose( growthRatio, 1.5, 1.0, 'Growth rates after payoff should be relatively similar' ); } // Summary console.log('\n=== Test Summary ==='); console.log(`Tests passed: ${testsPassed}`); console.log(`Tests failed: ${testsFailed}`); if (testsFailed === 0) { console.log('\n✅ All tests passed!'); process.exit(0); } else { console.log('\n❌ Some tests failed'); process.exit(1); }