import { describe, expect, it } from "vitest"; import { alsAufzaehlung, alsDatum, alsGanzzahl, alsJaNein, alsListe, alsZahl } from "@/lib/import/werte"; import { csvZerlegen, trennzeichenErkennen } from "@/lib/import/parse"; // Eine Importdatei kommt aus fremder Hand — aus einem Altsystem, aus Excel, // von einer Steuerberatung. Die Fälle hier sind keine Randfälle, sondern das, // was tatsächlich in solchen Dateien steht. describe("Datum aus einer Zelle", () => { it("liest ISO und österreichische Schreibweise", () => { expect(alsDatum("2026-08-03")).toBe("2026-08-03"); expect(alsDatum("3.8.2026")).toBe("2026-08-03"); expect(alsDatum("03.08.2026")).toBe("2026-08-03"); expect(alsDatum("03/08/2026")).toBe("2026-08-03"); }); it("weist zweistellige Jahre ab, statt das Jahrhundert zu raten", () => { // „15.08.68" ist als Geburtsdatum 1968 und als Vertragsende 2068. Wer // hier eine Regel erfindet, legt lautlos Personen an, die noch nicht // geboren sind. expect(alsDatum("15.08.68")).toBeNull(); }); it("weist einen Tag ab, den es nicht gibt", () => { // Date rechnet den 31.02. stillschweigend in den 03.03. um — genau die // Sorte Korrektur, die niemand bemerkt. expect(alsDatum("31.02.2026")).toBeNull(); expect(alsDatum("2026-13-01")).toBeNull(); }); it("liest die Serienzahl, die Excel in unformatierten Spalten hinterlässt", () => { // 1 = 01.01.1900. Ab 60 verschiebt Excels erfundener 29.02.1900 alles um // einen Tag; 61 ist deshalb der 01.03.1900 und nicht der 02.03. expect(alsDatum("1")).toBe("1900-01-01"); expect(alsDatum("61")).toBe("1900-03-01"); // Zwei allgemein bekannte Anker, damit die Umrechnung nachprüfbar ist // und nicht nur zu sich selbst passt. expect(alsDatum("25569")).toBe("1970-01-01"); expect(alsDatum("44927")).toBe("2023-01-01"); expect(alsDatum("46237")).toBe("2026-08-03"); }); it("liefert für Unfug null statt eines Datums", () => { expect(alsDatum("")).toBeNull(); expect(alsDatum("sofort")).toBeNull(); expect(alsDatum("3.8.")).toBeNull(); }); }); describe("Zahl aus einer Zelle", () => { it("versteht Komma und Punkt als Dezimaltrenner", () => { expect(alsZahl("38,5")).toBe(38.5); expect(alsZahl("38.5")).toBe(38.5); }); it("versteht beide Tausendertrennungen", () => { expect(alsZahl("1.234,50")).toBe(1234.5); expect(alsZahl("1,234.50")).toBe(1234.5); }); it("liest ein einzelnes Komma deutsch", () => { // „1,234" sind hierzulande 1,234 — nicht 1234. Andersherum wäre ein // Monatsgehalt um Faktor 1000 daneben. expect(alsZahl("1,234")).toBe(1.234); }); it("verträgt Währungszeichen und Leerzeichen", () => { expect(alsZahl(" 3 450,00 € ")).toBe(3450); }); it("weist Text ab", () => { expect(alsZahl("Vollzeit")).toBeNull(); expect(alsZahl("38,5h")).toBeNull(); expect(alsGanzzahl("38,5")).toBeNull(); expect(alsGanzzahl("2219")).toBe(2219); }); }); describe("Ja/Nein aus einer Zelle", () => { it("versteht die üblichen Schreibweisen", () => { for (const j of ["ja", "JA", "j", "x", "X", "wahr", "true", "1"]) expect(alsJaNein(j)).toBe(true); for (const n of ["nein", "N", "falsch", "false", "0", "-"]) expect(alsJaNein(n)).toBe(false); }); it("liefert für Unbekanntes null statt „nein“", () => { // Stillschweigend „nein“ anzunehmen hiesse, aus einem Tippfehler in der // Spalte „Betriebsrat“ einen fehlenden Kündigungsschutz zu machen. expect(alsJaNein("vielleicht")).toBeNull(); expect(alsJaNein("")).toBeNull(); }); }); describe("Liste und Aufzählung", () => { it("trennt an Semikolon und Komma und wirft Leeres weg", () => { expect(alsListe("Mo; Di ,Mi;")).toEqual(["Mo", "Di", "Mi"]); expect(alsListe("")).toEqual([]); }); it("bringt Aufzählungswerte auf die Schreibweise der Datenbank", () => { expect(alsAufzaehlung("vollzeit", ["Vollzeit", "Teilzeit"])).toBe("Vollzeit"); expect(alsAufzaehlung(" TEILZEIT ", ["Vollzeit", "Teilzeit"])).toBe("Teilzeit"); expect(alsAufzaehlung("halbtags", ["Vollzeit", "Teilzeit"])).toBeNull(); }); }); describe("CSV", () => { it("erkennt das Semikolon, das deutsches Excel schreibt", () => { expect(trennzeichenErkennen("Personalnummer;Vorname;Nachname")).toBe(";"); expect(trennzeichenErkennen("Personalnummer,Vorname,Nachname")).toBe(","); }); it("lässt ein Trennzeichen innerhalb von Anführungszeichen in Ruhe", () => { // Ohne das verschiebt eine Adresse mit Semikolon alle folgenden Spalten // — und die Zeile wird plausibel falsch importiert statt abgewiesen. const [kopf, zeile] = csvZerlegen('Nr;Adresse;Ort\n1;"Hauptstr. 1; Stiege 2";Wien\n'); expect(kopf).toEqual(["Nr", "Adresse", "Ort"]); expect(zeile).toEqual(["1", "Hauptstr. 1; Stiege 2", "Wien"]); }); it("versteht verdoppelte Anführungszeichen und Umbrüche im Feld", () => { const zeilen = csvZerlegen('A;B\n1;"sagt ""hallo""\nin zwei Zeilen"\n'); expect(zeilen[1]).toEqual(["1", 'sagt "hallo"\nin zwei Zeilen']); }); it("verträgt CRLF und eine fehlende letzte Zeilenschaltung", () => { expect(csvZerlegen("A;B\r\n1;2")).toEqual([ ["A", "B"], ["1", "2"], ]); }); it("entfernt die Byte-Reihenfolge-Markierung von Excel", () => { // Ohne das heisst die erste Spalte „Personalnummer“ und wird als // fehlend gemeldet — ein Fehler, der beim Ansehen der Datei unsichtbar ist. expect(csvZerlegen("Personalnummer;Vorname")[0]).toEqual(["Personalnummer", "Vorname"]); }); });