From ba02e177766ad6a8c5880d42d93e7d5559527efc Mon Sep 17 00:00:00 2001 From: Jonas Menzel Date: Wed, 9 Sep 2026 21:58:20 +0200 Subject: [PATCH] =?UTF-8?q?test:=20K=C3=BCrzel-Verdrahtung=20zwischen=20La?= =?UTF-8?q?yout=20und=20Seiten=20absichern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sechs Tests für usePendingAction und useSearchFocus. Der Store wird vor dem Test zurückgesetzt statt danach – nach dem Test hängen die Komponenten noch am Store und React meldet ein Update außerhalb von act(). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH --- frontend/src/hooks/usePageShortcuts.test.tsx | 108 +++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 frontend/src/hooks/usePageShortcuts.test.tsx diff --git a/frontend/src/hooks/usePageShortcuts.test.tsx b/frontend/src/hooks/usePageShortcuts.test.tsx new file mode 100644 index 0000000..9832a49 --- /dev/null +++ b/frontend/src/hooks/usePageShortcuts.test.tsx @@ -0,0 +1,108 @@ +/** 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()); + }); +});