import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import { useState } from "react"; import { renderWithMantine } from "../test/renderWithMantine"; import { useValueChange } from "./useValueChange"; /** * Mirrors the real usage: `mirrored` is seeded from `source`, can be overridden * locally, and snaps back whenever `source` changes. */ function Probe({ source, onChange, }: { source: number; onChange?: (next: number) => void; }) { const [mirrored, setMirrored] = useState(source); const [renders, setRenders] = useState(0); useValueChange(source, (next) => { setMirrored(next); onChange?.(next); }); return ( <> {mirrored} {renders} ); } describe("useValueChange", () => { it("does not call onChange on the first render", () => { const onChange = vi.fn(); const { getByTestId } = renderWithMantine( , ); expect(getByTestId("mirrored").textContent).toBe("5"); expect(onChange).not.toHaveBeenCalled(); }); it("calls onChange with the new value when the watched value changes", () => { const onChange = vi.fn(); const { rerender, getByTestId } = renderWithMantine( , ); rerender(); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenCalledWith(7); expect(getByTestId("mirrored").textContent).toBe("7"); }); it("discards a local override when the watched value changes", async () => { const user = userEvent.setup(); const { rerender, getByTestId, getByText } = renderWithMantine( , ); await user.click(getByText("override")); expect(getByTestId("mirrored").textContent).toBe("-1"); rerender(); expect(getByTestId("mirrored").textContent).toBe("9"); }); it("keeps a local override across re-renders that leave the value unchanged", async () => { const onChange = vi.fn(); const user = userEvent.setup(); const { getByTestId, getByText } = renderWithMantine( , ); await user.click(getByText("override")); await user.click(getByText("rerender")); expect(getByTestId("renders").textContent).toBe("1"); expect(getByTestId("mirrored").textContent).toBe("-1"); expect(onChange).not.toHaveBeenCalled(); }); it("treats NaN as unchanged (Object.is, not ===)", () => { const onChange = vi.fn(); const { rerender } = renderWithMantine( , ); rerender(); expect(onChange).not.toHaveBeenCalled(); }); });