Files
AnnasRechnungsManager/tests/lib/client-validation.test.ts
T
hwinkel db953b1e28 Add comprehensive tests for client validation, revenue and expense categories, invoice generation, and schemas
- 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.
2026-05-08 16:03:05 +02:00

299 lines
9.2 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from "vitest";
import {
isDebugMode,
setDebugMode,
validateTaxId,
validateVatId,
validateIban,
validateBic,
validateWebsite,
validateCompanyForm,
getFieldError,
hasFieldError,
type ValidationError,
type CompanyFormData,
} from "@/lib/client-validation";
describe("client-validation.ts", () => {
// Mock localStorage
const localStorageMock = (() => {
let store: Record<string, string> = {};
return {
getItem: (key: string) => store[key] || null,
setItem: (key: string, value: string) => { store[key] = value; },
removeItem: (key: string) => { delete store[key]; },
clear: () => { store = {}; },
};
})();
beforeEach(() => {
// Reset localStorage mock
localStorageMock.clear();
(global as any).localStorage = localStorageMock;
(global as any).window = { localStorage: localStorageMock };
});
afterEach(() => {
(global as any).window = undefined;
});
describe("Debug Mode", () => {
it("should be disabled by default (no window)", () => {
(global as any).window = undefined;
expect(isDebugMode()).toBe(false);
});
it("should detect debug mode from localStorage", () => {
setDebugMode(true);
expect(isDebugMode()).toBe(true);
});
it("should disable debug mode", () => {
setDebugMode(true);
expect(isDebugMode()).toBe(true);
setDebugMode(false);
expect(isDebugMode()).toBe(false);
});
});
describe("validateTaxId", () => {
it("should return null for empty/null values", () => {
expect(validateTaxId("")).toBeNull();
expect(validateTaxId(null)).toBeNull();
expect(validateTaxId(undefined)).toBeNull();
});
it("should validate correct 10-digit tax ID", () => {
expect(validateTaxId("1234567890")).toBeNull();
});
it("should reject invalid tax IDs", () => {
const result = validateTaxId("123"); // Too short
expect(result).not.toBeNull();
expect(result?.field).toBe("taxId");
expect(result?.message).toContain("10 Ziffern");
});
it("should reject tax ID with letters", () => {
const result = validateTaxId("abc4567890");
expect(result).not.toBeNull();
});
});
describe("validateVatId", () => {
it("should return null for empty/null values", () => {
expect(validateVatId("")).toBeNull();
expect(validateVatId(null)).toBeNull();
});
it("should validate correct DE VAT ID", () => {
expect(validateVatId("DE123456789")).toBeNull();
});
it("should reject VAT ID without DE prefix", () => {
const result = validateVatId("1234567890");
expect(result).not.toBeNull();
expect(result?.field).toBe("vatId");
expect(result?.message).toContain("DE + 9 Ziffern");
});
it("should reject VAT ID with wrong length", () => {
const result = validateVatId("DE12345"); // Too short
expect(result).not.toBeNull();
});
});
describe("validateIban", () => {
it("should return null for empty/null values", () => {
expect(validateIban("")).toBeNull();
expect(validateIban(null)).toBeNull();
});
it("should validate correct IBAN", () => {
expect(validateIban("DE89370400440532013000")).toBeNull();
});
it("should reject invalid IBAN", () => {
const result = validateIban("INVALID");
expect(result).not.toBeNull();
expect(result?.field).toBe("bankIban");
expect(result?.message).toContain("IBAN");
});
});
describe("validateBic", () => {
it("should return null for empty/null values", () => {
expect(validateBic("")).toBeNull();
expect(validateBic(null)).toBeNull();
});
it("should validate correct BIC", () => {
expect(validateBic("DEUTDEFF")).toBeNull();
});
it("should reject invalid BIC", () => {
const result = validateBic("123"); // Too short
expect(result).not.toBeNull();
expect(result?.field).toBe("bankBic");
});
});
describe("validateWebsite", () => {
it("should return null for empty/null values", () => {
expect(validateWebsite("")).toBeNull();
expect(validateWebsite(null)).toBeNull();
});
it("should validate correct website URLs", () => {
expect(validateWebsite("https://example.com")).toBeNull();
expect(validateWebsite("http://example.com")).toBeNull();
});
it("should reject website without protocol", () => {
const result = validateWebsite("example.com");
expect(result).not.toBeNull();
expect(result?.field).toBe("website");
expect(result?.message).toContain("http:// oder https://");
});
it("should reject too long website URLs", () => {
const longUrl = "https://" + "a".repeat(250);
const result = validateWebsite(longUrl);
expect(result).not.toBeNull();
expect(result?.message).toContain("255");
});
});
describe("validateCompanyForm", () => {
const validFormData: CompanyFormData = {
name: "Test GmbH",
address: "Hauptstraße 1",
zip: "12345",
city: "Berlin",
};
it("should pass validation for valid data", () => {
const errors = validateCompanyForm(validFormData);
expect(errors).toHaveLength(0);
});
it("should require name", () => {
const data = { ...validFormData, name: "" };
const errors = validateCompanyForm(data);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].field).toBe("name");
});
it("should require address", () => {
const data = { ...validFormData, address: "" };
const errors = validateCompanyForm(data);
const addressError = errors.find(e => e.field === "address");
expect(addressError).toBeDefined();
});
it("should require zip", () => {
const data = { ...validFormData, zip: "" };
const errors = validateCompanyForm(data);
const zipError = errors.find(e => e.field === "zip");
expect(zipError).toBeDefined();
});
it("should require city", () => {
const data = { ...validFormData, city: "" };
const errors = validateCompanyForm(data);
const cityError = errors.find(e => e.field === "city");
expect(cityError).toBeDefined();
});
it("should validate zip format (digits only)", () => {
const data = { ...validFormData, zip: "abc" };
const errors = validateCompanyForm(data);
const zipError = errors.find(e => e.field === "zip");
expect(zipError).toBeDefined();
expect(zipError?.message).toContain("Zahlen");
});
it("should validate optional fields when provided", () => {
const data = {
...validFormData,
taxId: "123", // Invalid
vatId: "INVALID", // Invalid
website: "example.com", // Invalid (no protocol)
};
const errors = validateCompanyForm(data);
expect(errors.length).toBeGreaterThanOrEqual(3);
});
it("should accept valid optional fields", () => {
const data = {
...validFormData,
taxId: "1234567890",
vatId: "DE123456789",
website: "https://example.com",
};
const errors = validateCompanyForm(data);
const hasTaxIdError = errors.some(e => e.field === "taxId");
const hasVatIdError = errors.some(e => e.field === "vatId");
const hasWebsiteError = errors.some(e => e.field === "website");
expect(hasTaxIdError).toBe(false);
expect(hasVatIdError).toBe(false);
expect(hasWebsiteError).toBe(false);
});
it("should validate email format", () => {
const data = { ...validFormData, email: "invalid-email" };
const errors = validateCompanyForm(data);
const emailError = errors.find(e => e.field === "email");
expect(emailError).toBeDefined();
});
it("should accept valid email", () => {
const data = { ...validFormData, email: "test@example.com" };
const errors = validateCompanyForm(data);
const emailError = errors.find(e => e.field === "email");
expect(emailError).toBeUndefined();
});
it("should validate field length limits", () => {
const data = {
...validFormData,
name: "a".repeat(300), // Too long
phone: "a".repeat(30), // Too long
};
const errors = validateCompanyForm(data);
expect(errors.length).toBeGreaterThan(0);
});
});
describe("getFieldError", () => {
it("should return error message for field", () => {
const errors: ValidationError[] = [
{ field: "name", message: "Name required" },
{ field: "email", message: "Invalid email" },
];
expect(getFieldError("name", errors)).toBe("Name required");
expect(getFieldError("email", errors)).toBe("Invalid email");
});
it("should return null for field without error", () => {
const errors: ValidationError[] = [];
expect(getFieldError("name", errors)).toBeNull();
});
});
describe("hasFieldError", () => {
it("should return true if field has error", () => {
const errors: ValidationError[] = [
{ field: "name", message: "Name required" },
];
expect(hasFieldError("name", errors)).toBe(true);
expect(hasFieldError("email", errors)).toBe(false);
});
it("should return false for empty errors", () => {
expect(hasFieldError("name", [])).toBe(false);
});
});
});