/** Tests des Bindeglieds zwischen globalen Kürzeln und den Seiten. */ import { useRef } from "react"; import { act, render, screen, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { usePendingAction, useSearchFocus } from "@/hooks/usePageShortcuts"; import { useUiStore } from "@/store/ui"; beforeEach(() => { // Der Store lebt global – sonst tropft eine Absicht in den nächsten Test. // Bewusst vor dem Test und nicht danach: nach dem Test hängen die Komponenten // noch am Store und React meldet ein Update außerhalb von act(). useUiStore.setState({ pendingAction: null, searchFocusRequest: 0 }); }); function Seite({ run }: { run: () => void }) { const suchfeld = useRef(null); usePendingAction("new-transaction", run); useSearchFocus(suchfeld); return ; } describe("usePendingAction", () => { it("führt die Absicht aus, sobald sie hinterlegt wird", async () => { const oeffnen = vi.fn(); render(); expect(oeffnen).not.toHaveBeenCalled(); await act(async () => { useUiStore.getState().requestAction("new-transaction"); }); await waitFor(() => expect(oeffnen).toHaveBeenCalledOnce()); // Die Absicht ist danach verbraucht. expect(useUiStore.getState().pendingAction).toBeNull(); }); it("ignoriert eine Absicht, die einer anderen Seite gilt", async () => { const oeffnen = vi.fn(); render(); await act(async () => { useUiStore.getState().requestAction("new-recurrence"); }); await waitFor(() => { expect(useUiStore.getState().pendingAction).toBe("new-recurrence"); }); expect(oeffnen).not.toHaveBeenCalled(); }); it("führt dieselbe Absicht nicht zweimal aus", async () => { const oeffnen = vi.fn(); const { rerender } = render(); await act(async () => { useUiStore.getState().requestAction("new-transaction"); }); await waitFor(() => expect(oeffnen).toHaveBeenCalledOnce()); rerender(); expect(oeffnen).toHaveBeenCalledOnce(); }); }); describe("useSearchFocus", () => { it("fokussiert und markiert das Suchfeld auf Anfrage", async () => { render(); const feld = screen.getByLabelText("Suche") as HTMLInputElement; expect(feld).not.toHaveFocus(); await act(async () => { useUiStore.getState().requestSearchFocus(); }); await waitFor(() => expect(feld).toHaveFocus()); // Der vorhandene Text ist markiert, damit Tippen ihn ersetzt. expect(feld.selectionStart).toBe(0); expect(feld.selectionEnd).toBe("Vorbelegung".length); }); it("greift beim ersten Rendern noch nicht", () => { render(); expect(screen.getByLabelText("Suche")).not.toHaveFocus(); }); it("wirkt auch bei mehrfacher Anfrage", async () => { render(); const feld = screen.getByLabelText("Suche"); await act(async () => { useUiStore.getState().requestSearchFocus(); }); await waitFor(() => expect(feld).toHaveFocus()); await act(async () => feld.blur()); await act(async () => { useUiStore.getState().requestSearchFocus(); }); await waitFor(() => expect(feld).toHaveFocus()); }); });