UNPKG

saldo

Version:

Portuguese salary calculator library

196 lines (195 loc) 10.3 kB
import { describe, it, expect } from "vitest"; import { calculateGrossIncome, calculateSsPay, calculateSpecificDeductions, calculateMaxExpenses, calculateExpensesNeeded, calculateTaxableIncome, calculateYouthIrsDiscount, findTaxRank, calculateIrsPay, calculateNetIncome, } from "../independent-worker/calculations"; import { TAX_RANKS } from "../data/tax-ranks-data"; import { IAS_PER_YEAR } from "../data/ias-data"; import { YOUTH_IRS } from "../data/youth-irs-data"; import { FrequencyChoices } from "../independent-worker/schemas"; describe("Independent Worker Calculations", () => { describe("calculateGrossIncome", () => { it("should calculate yearly income correctly", () => { const result = calculateGrossIncome(30000, FrequencyChoices.Year, 0); expect(result.year).toBe(30000); expect(result.month).toBe(2500); expect(result.day).toBeCloseTo(120.97, 2); // 30000 / 248 }); it("should calculate monthly income correctly", () => { const result = calculateGrossIncome(2500, FrequencyChoices.Month, 0); expect(result.year).toBe(30000); expect(result.month).toBe(2500); expect(result.day).toBeCloseTo(120.97, 2); }); it("should calculate daily income correctly", () => { const result = calculateGrossIncome(120.97, FrequencyChoices.Day, 0); expect(result.year).toBeCloseTo(30000, -1); // Allow more precision for floating point expect(result.month).toBeCloseTo(2500, 0); expect(result.day).toBeCloseTo(120.97, 2); }); it("should handle days off correctly", () => { const result = calculateGrossIncome(30000, FrequencyChoices.Year, 5); expect(result.year).toBe(30000); expect(result.month).toBe(2500); expect(result.day).toBeCloseTo(123.46, 2); // 30000 / (248 - 5) }); }); describe("calculateSsPay", () => { const grossIncome = { year: 30000, month: 2500, day: 120.97 }; const maxSsIncome = 12 * 522.50; // 2025 IAS it("should calculate SS pay correctly", () => { const result = calculateSsPay(grossIncome, 0.214, 0, maxSsIncome, false, 0); expect(result.year).toBeGreaterThan(0); expect(result.month).toBeGreaterThanOrEqual(20); expect(result.day).toBeGreaterThan(0); }); it("should return zero for SS first year", () => { const result = calculateSsPay(grossIncome, 0.214, 0, maxSsIncome, true, 0); expect(result.year).toBe(0); expect(result.month).toBe(0); expect(result.day).toBe(0); }); it("should handle SS discount", () => { const result = calculateSsPay(grossIncome, 0.214, 0.1, maxSsIncome, false, 0); expect(result.year).toBeGreaterThan(0); }); }); describe("calculateSpecificDeductions", () => { it("should calculate specific deductions correctly", () => { const ssPay = { year: 5000, month: 416.67, day: 20.16 }; const grossIncome = { year: 30000, month: 2500, day: 120.97 }; const result = calculateSpecificDeductions(ssPay, grossIncome); expect(result).toBe(Math.max(4104, Math.min(5000, 3000))); }); it("should use minimum deduction of 4104", () => { const ssPay = { year: 1000, month: 83.33, day: 4.03 }; const grossIncome = { year: 30000, month: 2500, day: 120.97 }; const result = calculateSpecificDeductions(ssPay, grossIncome); expect(result).toBe(4104); }); }); describe("calculateMaxExpenses", () => { it("should calculate max expenses correctly", () => { const grossIncome = { year: 30000, month: 2500, day: 120.97 }; const result = calculateMaxExpenses(grossIncome, 15); expect(result).toBe(4500); // 15% of 30000 }); }); describe("calculateExpensesNeeded", () => { it("should calculate expenses needed correctly", () => { const result = calculateExpensesNeeded(4500, 4104); expect(result).toBe(396); // 4500 - 4104 }); it("should return zero when max expenses is less than specific deductions", () => { const result = calculateExpensesNeeded(3000, 4104); expect(result).toBe(0); }); }); describe("calculateTaxableIncome", () => { const grossIncome = { year: 30000, month: 2500, day: 120.97 }; it("should calculate taxable income for regular scenario", () => { const result = calculateTaxableIncome(grossIncome, 0, false, false, 396, 396); expect(result).toBeCloseTo(22500, 0); // 30000 * 0.75 }); it("should calculate taxable income for first year", () => { const result = calculateTaxableIncome(grossIncome, 0, true, false, 396, 396); expect(result).toBeCloseTo(11250, 0); // 30000 * 0.375 }); it("should calculate taxable income for second year", () => { const result = calculateTaxableIncome(grossIncome, 0, false, true, 396, 396); expect(result).toBeCloseTo(16875, 0); // 30000 * 0.5625 }); it("should handle missing expenses", () => { const result = calculateTaxableIncome(grossIncome, 0, false, false, 396, 200); expect(result).toBeCloseTo(22696, 0); // (30000 * 0.75) + (396 - 200) = 22500 + 196 = 22696 }); }); describe("calculateYouthIrsDiscount", () => { const grossIncome = { year: 30000, month: 2500, day: 120.97 }; const currentIas = 522.50; it("should return zero when benefits are disabled", () => { const result = calculateYouthIrsDiscount(false, grossIncome, 2025, 1, currentIas); expect(result).toBe(0); }); it("should calculate youth IRS discount correctly", () => { const result = calculateYouthIrsDiscount(true, grossIncome, 2025, 1, currentIas); expect(result).toBeGreaterThan(0); expect(result).toBeLessThanOrEqual(grossIncome.year); }); }); describe("findTaxRank", () => { it("should find correct tax rank for 2025", () => { const result = findTaxRank(10000, 2025); expect(result).toBeDefined(); expect(result.id).toBeGreaterThan(0); expect(result.min).toBeLessThanOrEqual(10000); expect(result.max === null || result.max >= 10000).toBe(true); }); it("should find correct tax rank for 2024", () => { const result = findTaxRank(15000, 2024); expect(result).toBeDefined(); expect(result.id).toBeGreaterThan(0); }); it("should find correct tax rank for 2023", () => { const result = findTaxRank(20000, 2023); expect(result).toBeDefined(); expect(result.id).toBeGreaterThan(0); }); }); describe("calculateIrsPay", () => { const taxRank = { id: 1, min: 0, max: 10000, normalTax: 0.13, averageTax: 0.13 }; const mockTaxRanks = [taxRank]; it("should calculate IRS pay for regular scenario", () => { const result = calculateIrsPay(10000, taxRank, false, 0.2, 0, mockTaxRanks); expect(result.year).toBeCloseTo(1300, 0); // 10000 * 0.13 expect(result.month).toBeCloseTo(108.33, 2); expect(result.day).toBeCloseTo(5.24, 2); }); it("should calculate IRS pay for RNH scenario", () => { const result = calculateIrsPay(10000, taxRank, true, 0.2, 0, mockTaxRanks); expect(result.year).toBeCloseTo(2000, 0); // 10000 * 0.2 expect(result.month).toBeCloseTo(166.67, 2); expect(result.day).toBeCloseTo(8.06, 2); }); it("should calculate IRS pay correctly for the highest bracket (averageTax is null)", () => { // Simulate the highest bracket scenario const bracket8 = { id: 8, min: 44987, max: 83696, normalTax: 0.45, averageTax: 0.35408 }; const bracket9 = { id: 9, min: 83696, max: null, normalTax: 0.48, averageTax: null }; const taxRanks = [bracket8, bracket9]; // For taxableIncome of 120000: // Tax should be: 83696 * 0.35408 + (120000 - 83696) * 0.48 // = 29637.47 + 17425.92 = 47063.39 const result = calculateIrsPay(120000, bracket9, false, 0.2, 0, taxRanks); // Allow small rounding differences (within 5€) expect(result.year).toBeCloseTo(47061, -1); expect(result.year).toBeGreaterThan(40000); // Should never be 0! }); }); describe("calculateNetIncome", () => { const grossIncome = { year: 30000, month: 2500, day: 120.97 }; const irsPay = { year: 3000, month: 250, day: 12.10 }; const ssPay = { year: 2000, month: 166.67, day: 8.06 }; it("should calculate net income correctly", () => { const result = calculateNetIncome(grossIncome, irsPay, ssPay); expect(result.year).toBe(25000); // 30000 - 3000 - 2000 expect(result.month).toBeCloseTo(2083.33, 2); // 2500 - 250 - 166.67 expect(result.day).toBeCloseTo(100.81, 2); // 25000 / 248 }); }); describe("Constants", () => { it("should have correct IAS values", () => { expect(IAS_PER_YEAR[2023]).toBe(480.43); expect(IAS_PER_YEAR[2024]).toBe(509.26); expect(IAS_PER_YEAR[2025]).toBe(522.50); }); it("should have correct tax ranks structure", () => { expect(TAX_RANKS[2025]).toBeDefined(); expect(TAX_RANKS[2025].length).toBeGreaterThan(0); expect(TAX_RANKS[2024]).toBeDefined(); expect(TAX_RANKS[2023]).toBeDefined(); }); it("should have correct youth IRS structure", () => { expect(YOUTH_IRS[2025]).toBeDefined(); expect(YOUTH_IRS[2025][1]).toBeDefined(); expect(YOUTH_IRS[2025][1].maxDiscountPercentage).toBeGreaterThan(0); expect(YOUTH_IRS[2025][1].maxDiscountIasMultiplier).toBeGreaterThan(0); }); }); });