db953b1e28
- Implement tests for client validation functions including tax ID, VAT ID, IBAN, BIC, website, and company form validation. - Create tests for revenue and expense categories ensuring all expected categories and labels are present. - Add tests for invoice number generation with various scenarios including prefix handling and sequence padding. - Introduce tests for default categories and their integration, ensuring no overlaps and consistent naming conventions. - Implement Zod schema validation tests for currency, tax rates, IBAN, tax ID, VAT ID, invoices, companies, and customers. - Add utility tests for tax calculations, including item amounts and invoice totals, ensuring correct handling of tax rates and formatting. - Set up Vitest configuration and global test setup for consistent testing environment.
159 lines
4.5 KiB
TypeScript
159 lines
4.5 KiB
TypeScript
import {
|
|
TAX_RATES,
|
|
calcItemAmounts,
|
|
calcItemAmountsKleinunternehmer,
|
|
calcInvoiceTotals,
|
|
formatCurrency,
|
|
formatDate,
|
|
} from "@/lib/tax";
|
|
|
|
describe("tax.ts - German Tax Calculations", () => {
|
|
describe("TAX_RATES", () => {
|
|
it("should have correct tax rate values", () => {
|
|
expect(TAX_RATES).toEqual([
|
|
{ label: "19% MwSt. (Regelsteuersatz)", value: 19 },
|
|
{ label: "7% MwSt. (ermäßigt)", value: 7 },
|
|
{ label: "0% (steuerfrei / §13b UStG)", value: 0 },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("calcItemAmounts", () => {
|
|
it("should calculate correct amounts for 19% tax", () => {
|
|
const result = calcItemAmounts(2, 100, 19);
|
|
expect(result).toEqual({
|
|
netAmount: 200,
|
|
taxAmount: 38,
|
|
grossAmount: 238,
|
|
});
|
|
});
|
|
|
|
it("should calculate correct amounts for 7% tax", () => {
|
|
const result = calcItemAmounts(1, 50, 7);
|
|
expect(result).toEqual({
|
|
netAmount: 50,
|
|
taxAmount: 3.5,
|
|
grossAmount: 53.5,
|
|
});
|
|
});
|
|
|
|
it("should handle tax-free items (0%)", () => {
|
|
const result = calcItemAmounts(3, 33.33, 0);
|
|
expect(result).toEqual({
|
|
netAmount: 99.99,
|
|
taxAmount: 0,
|
|
grossAmount: 99.99,
|
|
});
|
|
});
|
|
|
|
it("should handle decimal quantities", () => {
|
|
const result = calcItemAmounts(1.5, 100, 19);
|
|
expect(result).toEqual({
|
|
netAmount: 150,
|
|
taxAmount: 28.5,
|
|
grossAmount: 178.5,
|
|
});
|
|
});
|
|
|
|
it("should round to 2 decimal places", () => {
|
|
const result = calcItemAmounts(1, 33.333, 19);
|
|
// 33.333 * 19% = 6.333..., rounded to 6.33
|
|
expect(result.netAmount).toBe(33.33);
|
|
expect(result.taxAmount).toBe(6.33);
|
|
expect(result.grossAmount).toBe(39.66);
|
|
});
|
|
});
|
|
|
|
describe("calcItemAmountsKleinunternehmer", () => {
|
|
it("should set tax to 0 for Kleinunternehmer", () => {
|
|
const result = calcItemAmountsKleinunternehmer(2, 100);
|
|
expect(result).toEqual({
|
|
netAmount: 200,
|
|
taxAmount: 0,
|
|
grossAmount: 200,
|
|
});
|
|
});
|
|
|
|
it("should treat gross as net for Kleinunternehmer", () => {
|
|
const result = calcItemAmountsKleinunternehmer(1, 50);
|
|
expect(result.netAmount).toBe(50);
|
|
expect(result.grossAmount).toBe(50);
|
|
});
|
|
});
|
|
|
|
describe("calcInvoiceTotals", () => {
|
|
it("should sum up multiple invoice items", () => {
|
|
const items = [
|
|
{ netAmount: 100, taxAmount: 19, grossAmount: 119 },
|
|
{ netAmount: 50, taxAmount: 3.5, grossAmount: 53.5 },
|
|
];
|
|
const result = calcInvoiceTotals(items);
|
|
expect(result).toEqual({
|
|
netTotal: 150,
|
|
taxTotal: 22.5,
|
|
grossTotal: 172.5,
|
|
});
|
|
});
|
|
|
|
it("should handle empty items array", () => {
|
|
const result = calcInvoiceTotals([]);
|
|
expect(result).toEqual({
|
|
netTotal: 0,
|
|
taxTotal: 0,
|
|
grossTotal: 0,
|
|
});
|
|
});
|
|
|
|
it("should round totals to 2 decimal places", () => {
|
|
const items = [
|
|
{ netAmount: 33.333, taxAmount: 6.333, grossAmount: 39.666 },
|
|
{ netAmount: 66.666, taxAmount: 12.666, grossAmount: 79.332 },
|
|
];
|
|
const result = calcInvoiceTotals(items);
|
|
// calcInvoiceTotals uses Math.round which rounds 99.999 to 100
|
|
expect(result.netTotal).toBe(100);
|
|
expect(result.taxTotal).toBe(19);
|
|
expect(result.grossTotal).toBe(119);
|
|
});
|
|
|
|
it("should handle Kleinunternehmer invoice (no tax)", () => {
|
|
const items = [
|
|
{ netAmount: 100, taxAmount: 0, grossAmount: 100 },
|
|
{ netAmount: 200, taxAmount: 0, grossAmount: 200 },
|
|
];
|
|
const result = calcInvoiceTotals(items);
|
|
expect(result).toEqual({
|
|
netTotal: 300,
|
|
taxTotal: 0,
|
|
grossTotal: 300,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("formatCurrency", () => {
|
|
it("should format number to EUR currency", () => {
|
|
// Note: Intl.NumberFormat uses non-breaking space (\u00A0) before €
|
|
expect(formatCurrency(1234.56)).toBe("1.234,56\u00A0€");
|
|
});
|
|
|
|
it("should handle string input", () => {
|
|
expect(formatCurrency("999.99")).toBe("999,99\u00A0€");
|
|
});
|
|
|
|
it("should format zero correctly", () => {
|
|
expect(formatCurrency(0)).toBe("0,00\u00A0€");
|
|
});
|
|
});
|
|
|
|
describe("formatDate", () => {
|
|
it("should format Date object to German format", () => {
|
|
const date = new Date("2026-05-08");
|
|
expect(formatDate(date)).toBe("8.5.2026");
|
|
});
|
|
|
|
it("should format date string to German format", () => {
|
|
expect(formatDate("2026-12-31")).toBe("31.12.2026");
|
|
});
|
|
});
|
|
});
|