import { describe, expect, it } from "vitest"; import { toCsv } from "@/lib/export"; import { anrede, baueLolyoZeile, LOLYO_CSV, LOLYO_EMAIL_BESTAETIGT, LOLYO_GRUPPE, LOLYO_SPALTEN, LOLYO_SPRACHE, lolyoSpalten, ohneTypografie, type LolyoQuelle, } from "@/lib/lolyo"; // Die Datei legt in LOLYO Konten an. Eine falsche Spalte fällt hier nicht auf, // sondern dort — als Konto, das niemandem gehört, oder als Zeile, die der // Import abweist. // Die Kopfzeile aus der Vorlage des Anbieters, Zeichen für Zeichen. const VORGABE = "Benutzer/Code;Email;Passwort;Titel prefix;Vorname;Nachname;Title Suffix;Position;Gruppen;Telefon;Anrede;Sprache;E-Mail bestätigt"; const UUID = "8f2b1c40-9d3e-4a77-b2c1-5e6f70a81234"; const PASSWORT = "Startpasswort1!"; function person(teil: Partial = {}): LolyoQuelle { return { id: UUID, company_email: "max.alpentest@alpenwerk-firmentest.com", title_prefix: ["Mag."], title_suffix: ["MBA"], first_name: "Max", last_name: "Alpentest", job_title: "HR-Manager", phone: "+4366412345678", gender: "m", ...teil, }; } const zeile = (teil: Partial = {}) => baueLolyoZeile(person(teil), PASSWORT); describe("Spalten", () => { it("stehen genau so und in genau der Reihenfolge wie in der Vorlage", () => { expect(LOLYO_SPALTEN.join(";")).toBe(VORGABE); }); it("sind 13", () => { expect(LOLYO_SPALTEN).toHaveLength(13); }); it("werden alle gefüllt — keine Spalte ohne Zuordnung", () => { const z = zeile(); for (const spalte of LOLYO_SPALTEN) { expect(z, `Spalte "${spalte}" fehlt in der Zeile`).toHaveProperty(spalte); } expect(Object.keys(z).sort()).toEqual([...LOLYO_SPALTEN].sort()); }); }); describe("Die Zuordnung", () => { it("nimmt als Benutzercode die Alpenwerk-UUID", () => { expect(zeile()["Benutzer/Code"]).toBe(UUID); }); it("nimmt die Firmenadresse und nie die private", () => { // Die Datei legt Konten bei einem fremden Anbieter an. Eine Privatadresse // hat dort nichts verloren; ohne Firmenadresse bleibt die Zelle leer. expect(zeile().Email).toBe("max.alpentest@alpenwerk-firmentest.com"); expect(zeile({ company_email: null }).Email).toBe(""); }); it("trägt das Passwort ein, das die Route hereinreicht", () => { expect(zeile().Passwort).toBe(PASSWORT); }); it("setzt Titel, Name und Position aus Alpenwerk", () => { const z = zeile(); expect(z["Titel prefix"]).toBe("Mag."); expect(z.Vorname).toBe("Max"); expect(z.Nachname).toBe("Alpentest"); expect(z["Title Suffix"]).toBe("MBA"); expect(z.Position).toBe("HR-Manager"); }); it("fügt mehrere Titel zusammen und lässt keine zu einer leeren Zelle werden", () => { expect(zeile({ title_prefix: ["Dr.", "Mag."] })["Titel prefix"]).toBe("Dr. Mag."); expect(zeile({ title_prefix: [], title_suffix: [] })["Titel prefix"]).toBe(""); }); it("setzt Gruppe, Sprache und Bestätigung fest", () => { const z = zeile(); expect(z.Gruppen).toBe(LOLYO_GRUPPE); expect(z.Sprache).toBe(LOLYO_SPRACHE); expect(z["E-Mail bestätigt"]).toBe(LOLYO_EMAIL_BESTAETIGT); }); it("leitet die Anrede aus dem Geschlecht ab", () => { expect(anrede("m")).toBe("Herr"); expect(anrede("w")).toBe("Frau"); // Lieber leer als geraten: eine falsche Anrede steht in jeder Nachricht, // die die App verschickt. expect(anrede("")).toBe(""); }); it("lässt eine fehlende Telefonnummer leer", () => { expect(zeile({ phone: null }).Telefon).toBe(""); }); }); describe("Typografische Zeichen", () => { it("weichen dem Bindestrich", () => { // Der Gedankenstrich kam im Zielsystem als „–" an. Ein Bindestrich sagt // dasselbe und ist in beiden Kodierungen dasselbe Byte. expect(ohneTypografie("Angestellte/r – F&E")).toBe("Angestellte/r - F&E"); expect(ohneTypografie("Lehrling — Wolkersdorf")).toBe("Lehrling - Wolkersdorf"); }); it("gelten für Anführungszeichen, Auslassungspunkte und geschützte Leerzeichen", () => { expect(ohneTypografie("„Schnitte“")).toBe('"Schnitte"'); expect(ohneTypografie("O’Brien")).toBe("O'Brien"); expect(ohneTypografie("und so weiter …")).toBe("und so weiter ..."); expect(ohneTypografie("W 17")).toBe("W 17"); }); it("lassen Umlaute in Ruhe", () => { // „Bäckerei" zu „Baeckerei" zu machen hiesse, ein Problem zu verstecken, // das dann auch Namen beträfe — und dort wäre es keine Kosmetik mehr. expect(ohneTypografie("Arbeiter/in – Bäckerei")).toBe("Arbeiter/in - Bäckerei"); expect(ohneTypografie("Müller-Weiß")).toBe("Müller-Weiß"); }); it("gelten für jede Zelle aus den Daten, nicht nur für die Position", () => { const z = zeile({ job_title: "Angestellte/r – F&E", last_name: "O’Brien", title_suffix: ["M–A"] }); expect(z.Position).toBe("Angestellte/r - F&E"); expect(z.Nachname).toBe("O'Brien"); expect(z["Title Suffix"]).toBe("M-A"); }); it("gelten nicht für das Passwort", () => { // Ein ersetztes Zeichen fiele niemandem auf — es gäbe nur ein Konto, in // das niemand hineinkommt. expect(baueLolyoZeile(person(), "Pass–wort–2026").Passwort).toBe("Pass–wort–2026"); }); }); describe("Die Datei", () => { it("ist semikolongetrennt und ohne BOM", () => { // Das BOM hinge unsichtbar am Namen der ersten Spalte, und // „Benutzer/Code" käme beim Einlesen in keiner Zuordnung mehr vor. const csv = toCsv([zeile()], lolyoSpalten(), LOLYO_CSV); expect(csv.startsWith("Benutzer/Code;")).toBe(true); expect(csv.charCodeAt(0)).not.toBe(0xfeff); }); it("beginnt mit der Kopfzeile der Vorlage", () => { const csv = toCsv([], lolyoSpalten(), LOLYO_CSV); expect(csv.split("\r\n")[0]).toBe(VORGABE); }); it("lässt die Telefonnummer unangetastet", () => { // Der Formelschutz von toCsv stellt jedem Wert mit führendem „+" ein // Hochkomma voran, damit Excel ihn nicht rechnet. Diese Datei liest eine // Maschine: dort stünde danach in **jedem** Konto `'+4366…` als Nummer, // weil jede österreichische Nummer mit „+" beginnt. const csv = toCsv([zeile()], lolyoSpalten(), LOLYO_CSV); expect(csv).toContain(";+4366412345678;"); expect(csv).not.toContain("'+43"); }); });