saldo
Version:
Portuguese salary calculator library
201 lines (200 loc) • 8.91 kB
JavaScript
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");
});
});
});