246 lines
8.4 KiB
TypeScript
246 lines
8.4 KiB
TypeScript
![]() |
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
|
import * as calculation from '../ts/index.js';
|
||
|
|
||
|
tap.test('Calculator class should perform basic arithmetic with decimal precision', async () => {
|
||
|
const calc = new calculation.Calculator({ precision: 10 });
|
||
|
|
||
|
// Test addition
|
||
|
const sum = calc.add(0.1, 0.2);
|
||
|
expect(calc.toString(sum)).toEqual('0.3');
|
||
|
|
||
|
// Test subtraction
|
||
|
const diff = calc.subtract(1, 0.9);
|
||
|
expect(calc.toString(diff)).toEqual('0.1');
|
||
|
|
||
|
// Test multiplication
|
||
|
const product = calc.multiply(0.1, 0.2);
|
||
|
expect(calc.toString(product)).toEqual('0.02');
|
||
|
|
||
|
// Test division
|
||
|
const quotient = calc.divide(1, 3);
|
||
|
expect(calc.toString(quotient)).toEqual('0.3333333333');
|
||
|
|
||
|
// Test power
|
||
|
const power = calc.power(2, 3);
|
||
|
expect(calc.toNumber(power)).toEqual(8);
|
||
|
|
||
|
// Test square root
|
||
|
const sqrt = calc.sqrt(9);
|
||
|
expect(calc.toNumber(sqrt)).toEqual(3);
|
||
|
|
||
|
// Test rounding
|
||
|
const rounded = calc.round(3.14159, 2);
|
||
|
expect(calc.toString(rounded)).toEqual('3.14');
|
||
|
});
|
||
|
|
||
|
tap.test('Financial class should calculate time value of money correctly', async () => {
|
||
|
const financial = new calculation.Financial();
|
||
|
|
||
|
// Test Present Value
|
||
|
const pv = financial.presentValue(1000, 0.05, 5);
|
||
|
expect(financial.round(pv, 2).toString()).toEqual('783.53');
|
||
|
|
||
|
// Test Future Value
|
||
|
const fv = financial.futureValue(1000, 0.05, 5);
|
||
|
expect(financial.round(fv, 2).toString()).toEqual('1276.28');
|
||
|
|
||
|
// Test Payment
|
||
|
const pmt = financial.payment(10000, 0.05/12, 60);
|
||
|
expect(financial.round(pmt, 2).toString()).toEqual('188.71');
|
||
|
|
||
|
// Test NPV
|
||
|
const cashFlows = [-1000, 300, 300, 300, 300, 300];
|
||
|
const npv = financial.npv(0.1, cashFlows);
|
||
|
expect(financial.round(npv, 2).toString()).toEqual('137.24');
|
||
|
|
||
|
// Test periods calculation
|
||
|
const periods = financial.periods(1000, 2000, 0.08);
|
||
|
expect(financial.round(periods, 2).toString()).toEqual('9.01');
|
||
|
|
||
|
// Test rate calculation
|
||
|
const rate = financial.rate(1000, 2000, 10);
|
||
|
expect(financial.round(rate, 4).toString()).toEqual('0.0718');
|
||
|
});
|
||
|
|
||
|
tap.test('Financial class should calculate IRR correctly', async () => {
|
||
|
const financial = new calculation.Financial();
|
||
|
|
||
|
// Test basic IRR
|
||
|
const cashFlows = [-1000, 200, 300, 400, 500];
|
||
|
const irr = financial.irr(cashFlows);
|
||
|
expect(financial.round(irr, 4).toString()).toEqual('0.1283');
|
||
|
|
||
|
// Test MIRR
|
||
|
const mirr = financial.mirr(cashFlows, 0.1, 0.12);
|
||
|
expect(financial.round(mirr, 4).toString()).toEqual('0.1256');
|
||
|
});
|
||
|
|
||
|
tap.test('Interest class should calculate different types of interest', async () => {
|
||
|
const interest = new calculation.Interest();
|
||
|
|
||
|
// Test simple interest
|
||
|
const simple = interest.simple(1000, 0.05, 2);
|
||
|
expect(interest.toString(simple)).toEqual('100');
|
||
|
|
||
|
// Test simple interest amount
|
||
|
const simpleAmount = interest.simpleAmount(1000, 0.05, 2);
|
||
|
expect(interest.toString(simpleAmount)).toEqual('1100');
|
||
|
|
||
|
// Test compound interest annually
|
||
|
const compound = interest.compound(1000, 0.05, 2, 'annually');
|
||
|
expect(interest.round(compound, 2).toString()).toEqual('102.5');
|
||
|
|
||
|
// Test compound interest monthly
|
||
|
const compoundMonthly = interest.compound(1000, 0.12, 1, 'monthly');
|
||
|
expect(interest.round(compoundMonthly, 2).toString()).toEqual('126.83');
|
||
|
|
||
|
// Test continuous compound interest
|
||
|
const continuous = interest.compound(1000, 0.05, 2, 'continuous');
|
||
|
expect(interest.round(continuous, 2).toString()).toEqual('105.17');
|
||
|
|
||
|
// Test effective annual rate
|
||
|
const ear = interest.effectiveAnnualRate(0.12, 'monthly');
|
||
|
expect(interest.round(ear, 4).toString()).toEqual('0.1268');
|
||
|
|
||
|
// Test real rate
|
||
|
const realRate = interest.realRate(0.08, 0.03);
|
||
|
expect(interest.round(realRate, 4).toString()).toEqual('0.0485');
|
||
|
|
||
|
// Test Rule of 72
|
||
|
const rule72 = interest.ruleOf72(8);
|
||
|
expect(interest.toString(rule72)).toEqual('9');
|
||
|
});
|
||
|
|
||
|
tap.test('Amortization class should generate correct loan schedules', async () => {
|
||
|
const amortization = new calculation.Amortization();
|
||
|
|
||
|
// Test basic loan schedule
|
||
|
const schedule = amortization.schedule({
|
||
|
principal: 10000,
|
||
|
annualRate: 0.05,
|
||
|
termYears: 2
|
||
|
});
|
||
|
|
||
|
expect(schedule.payments).toHaveLength(24);
|
||
|
expect(amortization.round(schedule.monthlyPayment, 2).toString()).toEqual('438.71');
|
||
|
expect(amortization.round(schedule.totalInterest, 2).toString()).toEqual('529.13');
|
||
|
|
||
|
// Test remaining balance
|
||
|
const balance = amortization.remainingBalance(10000, 0.05, 24, 12);
|
||
|
expect(amortization.round(balance, 2).toString()).toEqual('5124.71');
|
||
|
|
||
|
// Test max loan amount
|
||
|
const maxLoan = amortization.maxLoanAmount(1000, 0.05, 360);
|
||
|
expect(amortization.round(maxLoan, 2).toString()).toEqual('186281.62');
|
||
|
|
||
|
// Test LTV ratio
|
||
|
const ltv = amortization.ltv(80000, 100000);
|
||
|
expect(amortization.toString(ltv)).toEqual('0.8');
|
||
|
|
||
|
// Test DTI ratio
|
||
|
const dti = amortization.dti(1500, 5000);
|
||
|
expect(amortization.toString(dti)).toEqual('0.3');
|
||
|
});
|
||
|
|
||
|
tap.test('Currency class should handle currency operations correctly', async () => {
|
||
|
const currency = new calculation.Currency();
|
||
|
|
||
|
// Set up exchange rates
|
||
|
currency.setExchangeRate('USD', 'EUR', 0.85);
|
||
|
currency.setExchangeRate('USD', 'GBP', 0.73);
|
||
|
|
||
|
// Test currency conversion
|
||
|
const converted = currency.convert(100, 'USD', 'EUR');
|
||
|
expect(currency.toString(converted)).toEqual('85');
|
||
|
|
||
|
// Test inverse conversion
|
||
|
const inverse = currency.convert(85, 'EUR', 'USD');
|
||
|
expect(currency.round(inverse, 2).toString()).toEqual('100');
|
||
|
|
||
|
// Test currency formatting
|
||
|
const formatted = currency.format(1234.56, 'USD');
|
||
|
expect(formatted).toEqual('$1,234.56');
|
||
|
|
||
|
const formattedEur = currency.format(1234.56, 'EUR');
|
||
|
expect(formattedEur).toEqual('€1.234,56');
|
||
|
|
||
|
const formattedJpy = currency.format(1234.56, 'JPY');
|
||
|
expect(formattedJpy).toEqual('¥1,235');
|
||
|
|
||
|
// Test parsing
|
||
|
const parsed = currency.parse('$1,234.56', 'USD');
|
||
|
expect(currency.toString(parsed)).toEqual('1234.56');
|
||
|
|
||
|
// Test money operations
|
||
|
const money1 = currency.money(100, 'USD');
|
||
|
const money2 = currency.money(50, 'USD');
|
||
|
const sum = currency.addMoney(money1, money2);
|
||
|
expect(currency.toString(sum.amount)).toEqual('150');
|
||
|
|
||
|
// Test percentage calculations
|
||
|
const percentage = currency.percentage(100, 15);
|
||
|
expect(currency.toString(percentage)).toEqual('15');
|
||
|
|
||
|
// Test tax calculations
|
||
|
const withTax = currency.withTax(100, 0.08);
|
||
|
expect(currency.toString(withTax)).toEqual('108');
|
||
|
|
||
|
const extracted = currency.extractTax(108, 0.08);
|
||
|
expect(currency.round(extracted.baseAmount, 2).toString()).toEqual('100');
|
||
|
expect(currency.round(extracted.taxAmount, 2).toString()).toEqual('8');
|
||
|
});
|
||
|
|
||
|
tap.test('Edge cases and error handling', async () => {
|
||
|
const calc = new calculation.Calculator();
|
||
|
const financial = new calculation.Financial();
|
||
|
const interest = new calculation.Interest();
|
||
|
const amortization = new calculation.Amortization();
|
||
|
const currency = new calculation.Currency();
|
||
|
|
||
|
// Test division by zero
|
||
|
expect(() => calc.divide(10, 0)).toThrow();
|
||
|
|
||
|
// Test zero interest rate in periods calculation
|
||
|
expect(() => financial.periods(1000, 2000, 0)).toThrow();
|
||
|
|
||
|
// Test zero periods in rate calculation
|
||
|
expect(() => financial.rate(1000, 2000, 0)).toThrow();
|
||
|
|
||
|
// Test doubling time with zero rate
|
||
|
expect(() => interest.doubleTime(0)).toThrow();
|
||
|
|
||
|
// Test Rule of 72 with zero rate
|
||
|
expect(() => interest.ruleOf72(0)).toThrow();
|
||
|
|
||
|
// Test LTV with zero property value
|
||
|
expect(() => amortization.ltv(100000, 0)).toThrow();
|
||
|
|
||
|
// Test DTI with zero income
|
||
|
expect(() => amortization.dti(1000, 0)).toThrow();
|
||
|
|
||
|
// Test currency conversion without exchange rate
|
||
|
expect(() => currency.convert(100, 'USD', 'JPY')).toThrow();
|
||
|
|
||
|
// Test adding money with different currencies
|
||
|
const usdMoney = currency.money(100, 'USD');
|
||
|
const eurMoney = currency.money(100, 'EUR');
|
||
|
expect(() => currency.addMoney(usdMoney, eurMoney)).toThrow();
|
||
|
});
|
||
|
|
||
|
tap.test('Precision and accuracy tests', async () => {
|
||
|
const calc = new calculation.Calculator({ precision: 20 });
|
||
|
|
||
|
// Test high precision calculation
|
||
|
const result = calc.divide(1, 3);
|
||
|
expect(calc.toString(result)).toEqual('0.33333333333333333333');
|
||
|
|
||
|
// Test very small numbers
|
||
|
const small = calc.multiply('0.000000001', '0.000000001');
|
||
|
expect(calc.toString(small)).toEqual('1e-18');
|
||
|
|
||
|
// Test very large numbers
|
||
|
const large = calc.multiply('1000000000000', '1000000000000');
|
||
|
expect(calc.toString(large)).toEqual('1e+24');
|
||
|
});
|
||
|
|
||
|
tap.start();
|