import { describe, expect, it } from "vitest"; import { OFFBOARDING_GRUPPEN, OFFBOARDING_PUNKTE, fortschritt, gehoertOffboarding, istErledigt, punktZu, type AufgabenStand, } from "@/lib/offboarding"; // Vom selben Blatt Papier wie die Onboarding-Liste, für den anderen Weg. Die // Mechanik selbst (was als erledigt zählt, wie der Fortschritt entsteht) ist // mit lib/onboarding.ts geteilt (lib/checklist.ts) und dort schon geprüft — // hier geht es um das, was an dieser Liste besonders ist. function stand(teil: Partial & { item_key: string }): AufgabenStand { return { erledigt: false, wert: null, kommentar: null, updated_at: null, updated_by_name: null, ...teil }; } describe("Das Verzeichnis", () => { it("hat eindeutige Schlüssel — sie stehen in den Daten", () => { const keys = OFFBOARDING_PUNKTE.map((p) => p.key); expect(new Set(keys).size).toBe(keys.length); }); it("bildet jeden Punkt aus dem Blatt ab", () => { expect(OFFBOARDING_PUNKTE).toHaveLength(11); for (const key of [ "br_verstaendigung", "kuendigungsschreiben", "bestaetigung_kdn_bei", "lgv_austritt", "gkk_abmeldung", "if6020_austritt", "endabrechnung", "offboarding_gestartet", ]) { expect(punktZu(key), key).toBeDefined(); } }); it("führt die beiden Vermerke als Text, nicht als Haken", () => { // Ein Resturlaubssaldo abgehakt wäre wertlos — die Zahl selbst ist der Zweck. expect(punktZu("resturlaub")?.art).toBe("text"); expect(punktZu("ueberleitung_salden_betrag")?.art).toBe("text"); }); it("unterscheidet die Aufgabe von ihrem Betrag", () => { // Das Blatt nennt „Überleitung Salden für Auszahlung" zweimal: einmal als // abzuhakende Aufgabe, einmal als Feld für den tatsächlichen Betrag. Zwei // verschiedene Fragen, zwei verschiedene Punkte. expect(punktZu("ueberleitung_salden")?.art).toBe("haken"); expect(punktZu("ueberleitung_salden_betrag")?.art).toBe("text"); expect(punktZu("ueberleitung_salden")?.key).not.toBe(punktZu("ueberleitung_salden_betrag")?.key); }); it("führt keinen Ja/Nein-Punkt — anders als beim Onboarding", () => { // Auf diesem Blatt gibt es keine Stelle mit zwei Kästchen nebeneinander. expect(OFFBOARDING_PUNKTE.some((p) => p.art === "janein")).toBe(false); }); it("gruppiert vollständig — kein Punkt fällt zwischen die Gruppen", () => { const ausGruppen = OFFBOARDING_GRUPPEN.flatMap((g) => g.punkte.map((p) => p.key)); expect(new Set(ausGruppen)).toEqual(new Set(OFFBOARDING_PUNKTE.map((p) => p.key))); }); }); describe("fortschritt", () => { it("ist auf einer leeren Liste vollständig offen", () => { const f = fortschritt([]); expect(f.erledigt).toBe(0); expect(f.gesamt).toBe(OFFBOARDING_PUNKTE.length); }); it("zählt Haken und Text zusammen", () => { const f = fortschritt([ stand({ item_key: "endabrechnung", erledigt: true }), stand({ item_key: "resturlaub", wert: "12 Tage" }), ]); expect(f.erledigt).toBe(2); expect(f.offen).not.toContain("Endabrechnung"); }); }); describe("istErledigt", () => { it("verlangt bei einem Haken die ausdrückliche Markierung", () => { const punkt = punktZu("gkk_abmeldung")!; expect(istErledigt(punkt, stand({ item_key: punkt.key, erledigt: true }))).toBe(true); expect(istErledigt(punkt, stand({ item_key: punkt.key }))).toBe(false); expect(istErledigt(punkt, undefined)).toBe(false); }); }); describe("gehoertOffboarding", () => { // Geprüft wird exit_date, nicht status: eine künftige Beendigung setzt // exit_date sofort, aber status erst am Stichtag selbst. Bis dahin steht // die Person weiterhin als „Aktiv" — mit status als Bedingung bliebe der // Reiter bis zum letzten Arbeitstag unsichtbar, genau dann, wenn er // gebraucht wird. it("bejaht bei einem bereits wirksamen Austritt", () => { expect(gehoertOffboarding({ exit_date: "2026-08-01", exit_reason: "Kündigung AN" })).toBe(true); }); it("bejaht schon bei einem erst künftig wirksamen Austritt", () => { expect(gehoertOffboarding({ exit_date: "2099-01-01", exit_reason: "Kündigung AN" })).toBe(true); }); it("verneint bei No Show, obwohl exit_date ebenso gesetzt ist", () => { // Nie einen Tag gearbeitet: kein IT-Zugang, keine GKK-Anmeldung, kein // Dienstzettel. Der Reiter wäre ein Etikett ohne Inhalt. expect(gehoertOffboarding({ exit_date: "2026-08-01", exit_reason: "No Show" })).toBe(false); }); it("verneint ohne Austritt — auch nach einer Wiedereinstellung, die exit_date zurücksetzt", () => { // Beides führt auf dieselbe Datenlage: wer nie ausgeschieden ist, und wer // wiedereingestellt wurde (rehire_employee setzt exit_date und // exit_reason ausdrücklich auf null), sehen für diese Regel gleich aus. expect(gehoertOffboarding({ exit_date: null, exit_reason: null })).toBe(false); }); });