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();
}
});
});