UNPKG

saldo

Version:

Portuguese salary calculator library

201 lines (200 loc) 8.91 kB
import { describe, it, expect, vi } from "vitest"; import { simulateDependentWorker } from "../dependent-worker/simulator"; import { Twelfths } from "../dependent-worker/schemas"; import { LunchAllowance } from "../dependent-worker/lunch-allowance"; import * as ConfigSchemas from "../config/schemas"; const defaultLunchAllowance = new LunchAllowance(10.2, "cupon", 22); // Mock the calculation functions to control their output for tests vi.mock("@/dependent-worker/calculations", () => ({ getPartnerExtraDeduction: vi.fn((married, numberOfHolders, partnerDisabled) => { if (married && numberOfHolders === 1 && partnerDisabled) return 50; // Mocked value return 0; }), getTwelfthsIncome: vi.fn((income, twelfths) => { return (income * Number(twelfths)) / 12; // Using Number(twelfths) as per stub }), getDisabledDependentExtraDeduction: vi.fn((taxRetentionTable, numDisabled) => { const baseDeduction = taxRetentionTable?.dependent_disabled_addition_deduction || 20; // Mocked base return baseDeduction * numDisabled; }), })); // Minimal mock for TaxRetentionTable and TaxBracket to avoid actual file loading in unit tests vi.mock("@/tables/tax-retention", () => { class MockTaxBracket { data; constructor(data) { this.data = data; } calculate_tax(taxable_income, twelfths_income, number_of_dependents = 0, extra_deduction = 0) { const basicTax = (taxable_income + twelfths_income) * 0.1 - extra_deduction - number_of_dependents * 10; return Math.max(0, basicTax); } toJSON() { return { signal: "max", limit: this.data.limit || 1000, max_marginal_rate: this.data.max_marginal_rate || 0.2, deduction: 100, var1_deduction: 0, var2_deduction: 0, dependent_aditional_deduction: 10, effective_mensal_rate: 0.1 }; } } class MockTaxRetentionTable { situationCode; dependentDisabledDeductionVal; dependent_disabled_addition_deduction; constructor(situationCode, dependentDisabledDeductionVal) { this.situationCode = situationCode; this.dependentDisabledDeductionVal = dependentDisabledDeductionVal; this.dependent_disabled_addition_deduction = dependentDisabledDeductionVal; } static load(period, location, situation_code, year) { return new MockTaxRetentionTable(situation_code, 30); } find_bracket(taxableIncome) { return new MockTaxBracket({ limit: taxableIncome + 1000, max_marginal_rate: 0.2, }); } toJSON() { return { situation: this.situationCode, description: "Mock tax table", brackets: [], dependent_disabled_addition_deduction: this.dependent_disabled_addition_deduction }; } } return { TaxRetentionTable: MockTaxRetentionTable }; }); describe("simulateDependentWorker", () => { const baseIncome = 1000; it("should calculate for a basic scenario with defaults", () => { const result = simulateDependentWorker({ income: baseIncome }); expect(result).toBeDefined(); expect(result.taxableIncome).toBe(baseIncome + defaultLunchAllowance.taxableMonthlyValue); expect(result.socialSecurityTax).toEqual(0.11); expect(result.lunchAllowance).toEqual(new LunchAllowance(10.2, "cupon", 22)); }); it("should calculate for a married individual, 1 holder, 2 dependents", () => { const incomeVal = 2000; const result = simulateDependentWorker({ income: incomeVal, married: true, numberOfHolders: 1, numberOfDependents: 2, numberOfDependentsDisabled: 0, }); expect(result).toBeDefined(); expect(result.taxableIncome).toBe(incomeVal + defaultLunchAllowance.taxableMonthlyValue); }); it("should calculate with worker disability and custom lunch allowance", () => { const incomeVal = 1500; const result = simulateDependentWorker({ income: incomeVal, disabled: true, lunchAllowanceDailyValue: 8, lunchAllowanceMode: "salary", lunchAllowanceDaysCount: 22, }); expect(result).toBeDefined(); expect(result.lunchAllowance.dailyValue).toBe(8); expect(result.taxableIncome).toBe(incomeVal + 44); }); it("should calculate for 'azores' location and no twelfths", () => { const incomeVal = 2500; const result = simulateDependentWorker({ income: incomeVal, location: "azores", twelfths: Twelfths.NONE, }); expect(result).toBeDefined(); const expectedTwelfthsIncome = (incomeVal * Twelfths.NONE) / 12; const expectedTaxable = incomeVal + defaultLunchAllowance.taxableMonthlyValue; const expectedRetentionIncome = expectedTaxable + expectedTwelfthsIncome; expect(result.grossIncome).toBe(expectedRetentionIncome + defaultLunchAllowance.taxFreeMonthlyValue); }); it("should throw error for invalid numberOfHolders when married", () => { expect(() => { simulateDependentWorker({ income: 1000, married: true, numberOfHolders: null, // Invalid for married }); }).toThrow("'numberOfHolders' is required for married workers"); }); it("should throw error if situation cannot be determined", () => { // Spy on SituationUtils.getSituation and make it return undefined for this test const getSituationSpy = vi.spyOn(ConfigSchemas.SituationUtils, "getSituation"); getSituationSpy.mockReturnValueOnce(undefined); expect(() => { simulateDependentWorker({ income: 1000, married: true, numberOfHolders: 1, disabled: false, // The specific value of numberOfDependents doesn't strictly matter here due to the mock, // but using a common value like 1 or 0 is fine. numberOfDependents: 1, }); }).toThrow(/Could not determine situation for the given parameters/); // Restore the original implementation after the test getSituationSpy.mockRestore(); }); it("should calculate yearly gross and net salaries correctly based on (mocked) monthly values", () => { const income = 1200; const twelfths = Twelfths.ONE_MONTH; // example const result = simulateDependentWorker({ income, twelfths }); const expectedYearlyGrossSalary = 19268.4; const expectedYearlyNetSalary = 16413.6; expect(result.yearlyGrossSalary).toBeCloseTo(expectedYearlyGrossSalary); expect(result.yearlyNetSalary).toBeCloseTo(expectedYearlyNetSalary); }); describe("period parameter", () => { it("should use default period when not specified", () => { const result = simulateDependentWorker({ income: 1000 }); expect(result).toBeDefined(); expect(result.taxableIncome).toBe(1000 + defaultLunchAllowance.taxableMonthlyValue); }); it("should work with first period of 2025", () => { const result = simulateDependentWorker({ income: 1000, period: "2025-01-01_2025-07-31" }); expect(result).toBeDefined(); expect(result.taxableIncome).toBe(1000 + defaultLunchAllowance.taxableMonthlyValue); }); it("should work with second period of 2025", () => { const result = simulateDependentWorker({ income: 1000, period: "2025-08-01_2025-09-30" }); expect(result).toBeDefined(); expect(result.taxableIncome).toBe(1000 + defaultLunchAllowance.taxableMonthlyValue); }); it("should work with third period of 2025", () => { const result = simulateDependentWorker({ income: 1000, period: "2025-10-01_2025-12-31" }); expect(result).toBeDefined(); expect(result.taxableIncome).toBe(1000 + defaultLunchAllowance.taxableMonthlyValue); }); it("should throw error for invalid period", () => { expect(() => { simulateDependentWorker({ income: 1000, period: "2025-01-01_2025-06-30" // Invalid period }); }).toThrow("'period' must be one of"); }); }); });