import { render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, expect, it, vi } from "vitest"; import { HireWizard } from "@/components/hire/HireWizard"; import { ToastProvider } from "@/components/ui/Toast"; import { EMPTY_HIRE_DRAFT } from "@/components/hire/types"; import type { OpenPositionResolved } from "@/lib/positions"; vi.mock("@/actions/employees", () => ({ hireEmployee: vi.fn(async () => ({ success: true })), addEmployeeDependent: vi.fn(async () => ({ success: true })), })); vi.mock("@/actions/hireDrafts", () => ({ saveHireDraft: vi.fn(async () => ({ success: true })), deleteHireDraft: vi.fn(async () => ({ success: true })), })); vi.mock("next/navigation", () => ({ useRouter: () => ({ refresh: vi.fn(), push: vi.fn() }) })); // Die Reihenfolge der Schritte und die Prüfung, die an jedem hängt, standen // bis September 2026 in zwei Listen, die stumm zusammenpassen mussten. Beim // Vertauschen von Vertrag und Angehörige wäre die Prüfung stehengeblieben, // wo sie war — „Weiter" im Vertrag hätte die Angehörigen geprüft und ein // leeres Eintrittsdatum durchgelassen, bis die Datenbank es am Ende abweist. // Deshalb steht beides hier: die Abfolge und der Riegel am richtigen Schritt. const POSITION = { id: "p1", title: "Sachbearbeiter:in", orgUnitName: "Personal", } as unknown as OpenPositionResolved; const LOCATIONS = [{ id: "l1", name: "Wien", country: "AT" }]; /** Steigt mitten im Assistenten ein — ein Entwurf ist genau dafür da. */ function zeige(step: number, payload: Partial = {}) { return render( {}} openPositions={[POSITION]} locations={LOCATIONS} resumeDraft={{ id: "d1", step, payload: { ...EMPTY_HIRE_DRAFT, ...payload } }} /> ); } const weiter = () => screen.getByRole("button", { name: "Weiter" }); describe("Abfolge der Schritte", () => { it("führt Vertrag vor Angehörige", () => { zeige(0); const trail = screen.getAllByRole("button").filter((b) => /^\d/.test(b.textContent ?? "")); expect(trail.map((b) => b.textContent?.replace(/^\d/, ""))).toEqual([ "Person", "Position", "Vertrag", "Angehörige", "Notfallkontakt", "Zusammenfassung", ]); }); it("zeigt an Schritt 3 den Vertrag", () => { zeige(2); expect(screen.getByLabelText(/Eintrittsdatum/)).toBeInTheDocument(); }); it("zeigt an Schritt 4 die Angehörigen", () => { zeige(3); expect(screen.getByRole("button", { name: /Angehörige:n hinzufügen/ })).toBeInTheDocument(); }); it("hält im Vertrag ohne Eintrittsdatum an", () => { // Der Riegel muss mit dem Schritt gewandert sein. Bleibt er bei den // Angehörigen, ist „Weiter" hier offen — und das Eintrittsdatum fehlt // bis zum Anlegen. zeige(2, { entryDate: "" }); expect(weiter()).toBeDisabled(); }); it("lässt im Vertrag mit Eintrittsdatum weiter", () => { zeige(2, { entryDate: "2026-10-01" }); expect(weiter()).toBeEnabled(); }); it("lässt die Angehörigen leer durch", async () => { // Freiwillig — der Schritt darf niemanden aufhalten. zeige(3); expect(weiter()).toBeEnabled(); }); it("hält bei einer angefangenen Angehörigen-Zeile an", async () => { // Sie scheitert sonst erst nach dem Anlegen der Person, und die steht // dann schon in der Datenbank. const user = userEvent.setup(); zeige(3); await user.click(screen.getByRole("button", { name: /Angehörige:n hinzufügen/ })); expect(weiter()).toBeDisabled(); }); it("blättert vom Vertrag zu den Angehörigen", async () => { const user = userEvent.setup(); zeige(2, { entryDate: "2026-10-01" }); await user.click(weiter()); expect(screen.getByRole("button", { name: /Angehörige:n hinzufügen/ })).toBeInTheDocument(); expect(screen.queryByLabelText(/Eintrittsdatum/)).not.toBeInTheDocument(); }); it("blättert von den Angehörigen zurück zum Vertrag", async () => { // Der Weg zurück führt dorthin, wo man herkam — sonst springt der // Assistent an eine Stelle, die niemand erwartet. const user = userEvent.setup(); zeige(3); await user.click(screen.getByRole("button", { name: "Zurück" })); expect(screen.getByLabelText(/Eintrittsdatum/)).toBeInTheDocument(); }); }); describe("Fortschrittsleiste", () => { it("markiert den Schritt, auf dem man steht", () => { zeige(2); const aktuell = screen.getAllByRole("button").find((b) => b.getAttribute("aria-current") === "step"); expect(aktuell).toHaveTextContent("Vertrag"); }); it("lässt nur zurück, nicht vor", () => { zeige(2); const trail = screen.getAllByRole("button").filter((b) => /^\d/.test(b.textContent ?? "")); const nach = (label: string) => trail.find((b) => within(b).queryByText(label) ?? b.textContent?.includes(label)); expect(nach("Position")).toBeEnabled(); expect(nach("Angehörige")).toBeDisabled(); }); });