import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { HireWizardProvider, useHireWizard } from "@/components/hire/HireWizardContext"; import { ToastProvider } from "@/components/ui/Toast"; import type { Entwurf } from "@/lib/entwuerfe"; const entwurfSperren = vi.fn<(id: string) => Promise<{ success: boolean; error?: string }>>(async () => ({ success: true, })); const entwurfFreigeben = vi.fn<(id: string) => Promise<{ success: boolean }>>(async () => ({ success: true })); const refresh = vi.fn(); vi.mock("@/actions/hireDrafts", () => ({ entwurfSperren: (id: string) => entwurfSperren(id), entwurfFreigeben: (id: string) => entwurfFreigeben(id), saveHireDraft: vi.fn(), deleteHireDraft: vi.fn(), })); vi.mock("next/navigation", () => ({ useRouter: () => ({ refresh, push: vi.fn() }) })); // Der Assistent selbst spielt hier keine Rolle — geprüft wird, was um ihn // herum passiert: die Sperre holen, bevor er aufgeht, und sie zurückgeben, // wenn er zugeht. vi.mock("@/components/hire/HireWizard", () => ({ HireWizard: ({ open, onClose, resumeDraft }: { open: boolean; onClose: () => void; resumeDraft: Entwurf | null }) => open ? (

Assistent offen{resumeDraft ? `: ${resumeDraft.id}` : ""}

) : null, })); const ENTWURF: Entwurf = { id: "d1", step: 2, payload: { firstName: "Manuel", lastName: "Aigner" }, updated_at: "2026-09-09T08:00:00.000Z", vonMir: false, autor: "Anna Berger", gesperrtVon: null, }; function Ausloeser() { const { openWizard } = useHireWizard(); return ( <> ); } function zeige() { return render( ); } beforeEach(() => { entwurfSperren.mockClear(); entwurfSperren.mockResolvedValue({ success: true }); entwurfFreigeben.mockClear(); refresh.mockClear(); }); describe("Sperre um den Assistenten", () => { it("holt die Sperre, bevor der Assistent aufgeht", async () => { const user = userEvent.setup(); zeige(); await user.click(screen.getByRole("button", { name: "Fortsetzen" })); expect(entwurfSperren).toHaveBeenCalledWith("d1"); expect(await screen.findByText("Assistent offen: d1")).toBeInTheDocument(); }); it("macht gar nicht erst auf, wenn die Sperre belegt ist", async () => { // Aufzumachen und beim Speichern zu scheitern wäre der teure Weg: die // Person hätte den ganzen Assistenten umsonst durchlaufen. entwurfSperren.mockResolvedValue({ success: false, error: "Wird gerade bearbeitet." }); const user = userEvent.setup(); zeige(); await user.click(screen.getByRole("button", { name: "Fortsetzen" })); expect(screen.queryByText(/Assistent offen/)).not.toBeInTheDocument(); expect(await screen.findByText("Wird gerade bearbeitet.")).toBeInTheDocument(); }); it("holt die Liste nach, wenn die Sperre belegt war", async () => { // Danach steht an der Zeile, wer sie hält — statt dass nur eine Meldung // aufblitzt und die Karte weiter „Fortsetzen" anbietet. entwurfSperren.mockResolvedValue({ success: false, error: "Wird gerade bearbeitet." }); const user = userEvent.setup(); zeige(); await user.click(screen.getByRole("button", { name: "Fortsetzen" })); expect(refresh).toHaveBeenCalled(); }); it("gibt die Sperre zurück, wenn der Assistent zugeht", async () => { const user = userEvent.setup(); zeige(); await user.click(screen.getByRole("button", { name: "Fortsetzen" })); await user.click(await screen.findByRole("button", { name: "Zumachen" })); expect(entwurfFreigeben).toHaveBeenCalledWith("d1"); }); it("gibt nichts frei, was nie gesperrt war", async () => { // Eine neue Einstellung hat noch keinen Entwurf — es gäbe keine Zeile, // auf die sich die Freigabe beziehen könnte. const user = userEvent.setup(); zeige(); await user.click(screen.getByRole("button", { name: "Neu" })); await user.click(await screen.findByRole("button", { name: "Zumachen" })); expect(entwurfSperren).not.toHaveBeenCalled(); expect(entwurfFreigeben).not.toHaveBeenCalled(); }); it("sperrt beim Anlegen ohne Entwurf nichts", async () => { const user = userEvent.setup(); zeige(); await user.click(screen.getByRole("button", { name: "Neu" })); expect(entwurfSperren).not.toHaveBeenCalled(); expect(await screen.findByText("Assistent offen")).toBeInTheDocument(); }); it("gibt die Sperre nur einmal zurück", async () => { // Ein zweites Freigeben träfe womöglich schon die Sperre der nächsten // Person, die den Entwurf inzwischen geöffnet hat. const user = userEvent.setup(); const { unmount } = zeige(); await user.click(screen.getByRole("button", { name: "Fortsetzen" })); await user.click(await screen.findByRole("button", { name: "Zumachen" })); unmount(); expect(entwurfFreigeben).toHaveBeenCalledTimes(1); }); it("gibt die Sperre auch beim Verlassen der Seite zurück", async () => { // Zumachen ist der Normalfall, Wegnavigieren nicht der seltene. const user = userEvent.setup(); const { unmount } = zeige(); await user.click(screen.getByRole("button", { name: "Fortsetzen" })); unmount(); expect(entwurfFreigeben).toHaveBeenCalledWith("d1"); }); it("frischt die Sperre auf, solange der Assistent offen ist", async () => { // Ohne das liefe die Frist einem langen Ausfüllen davon, und das // Speichern am Ende fände seine eigene Sperre abgelaufen. vi.useFakeTimers({ shouldAdvanceTime: true }); try { const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); zeige(); await user.click(screen.getByRole("button", { name: "Fortsetzen" })); expect(entwurfSperren).toHaveBeenCalledTimes(1); await vi.advanceTimersByTimeAsync(11 * 60 * 1000); expect(entwurfSperren.mock.calls.length).toBeGreaterThan(1); expect(entwurfSperren).toHaveBeenLastCalledWith("d1"); } finally { vi.useRealTimers(); } }); it("hört mit dem Auffrischen auf, sobald zugemacht ist", async () => { // Sonst hielte ein zugeklappter Assistent den Entwurf endlos belegt — // genau das, was die Frist verhindern soll. vi.useFakeTimers({ shouldAdvanceTime: true }); try { const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); zeige(); await user.click(screen.getByRole("button", { name: "Fortsetzen" })); await user.click(await screen.findByRole("button", { name: "Zumachen" })); const nachher = entwurfSperren.mock.calls.length; await vi.advanceTimersByTimeAsync(30 * 60 * 1000); expect(entwurfSperren.mock.calls.length).toBe(nachher); } finally { vi.useRealTimers(); } }); });