// @vitest-environment jsdom // // useUrlMirror: mirror a set of nav params onto the CURRENT history entry (replaceState) whenever they // change. Backfills the E4 deep-review's deferred coverage — the effect keys on the SERIALIZED updates // so an inline object literal (new identity every render) only fires when a param actually changes; a // null value clears a key; and every other param on the URL is preserved. import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { renderHook } from "@testing-library/react"; import { useUrlMirror } from "./useUrlMirror"; const setUrl = (search: string) => window.history.replaceState(null, "", search || "/"); let spy: ReturnType; beforeEach(() => { setUrl("/"); spy = vi.spyOn(window.history, "replaceState"); }); afterEach(() => spy.mockRestore()); describe("useUrlMirror", () => { it("writes the param onto the current entry on mount", () => { renderHook(() => useUrlMirror({ pl: "5" })); expect(window.location.search).toBe("?pl=5"); expect(spy).toHaveBeenCalledTimes(1); }); it("does not re-fire when the serialized updates are unchanged (stable dep)", () => { const { rerender } = renderHook((p: Record) => useUrlMirror(p), { initialProps: { pl: "5" } as Record, }); expect(spy).toHaveBeenCalledTimes(1); rerender({ pl: "5" }); // new object identity, same serialized value expect(spy).toHaveBeenCalledTimes(1); }); it("clears a key when its value turns null", () => { const { rerender } = renderHook((p: Record) => useUrlMirror(p), { initialProps: { pl: "5" } as Record, }); expect(window.location.search).toBe("?pl=5"); rerender({ pl: null }); expect(window.location.search).toBe(""); expect(spy).toHaveBeenCalledTimes(2); }); it("preserves other params already on the URL", () => { setUrl("?page=playlists"); renderHook(() => useUrlMirror({ pl: "5" })); expect(window.location.search).toBe("?page=playlists&pl=5"); }); it("leaves an unconsumed auth token in the fragment alone", () => { // This hook's mount effect is what ran before (and therefore before the auth card) had even // loaded, so whatever it writes to the address bar is what the card later reads. It must not eat // the #verify=/#reset= token that the emailed link carries. window.history.replaceState(null, "", "/?page=playlists#verify=tok123"); renderHook(() => useUrlMirror({ pl: "5" })); expect(window.location.hash).toBe("#verify=tok123"); expect(window.location.search).toBe("?page=playlists&pl=5"); }); });