import { act, renderHook } from "@testing-library/react"; import { createElement } from "react"; import { renderToString } from "react-dom/server"; import { useReducedMotion } from "./useReducedMotion"; type Listener = () => void; /** matchMedia que responde só à consulta de movimento reduzido e deixa o teste mudar a preferência depois. */ function mockPreference(initial: boolean) { const listeners = new Set(); const query = { matches: initial, media: "(prefers-reduced-motion: reduce)", addEventListener: jest.fn((_: string, listener: Listener) => listeners.add(listener), ), removeEventListener: jest.fn((_: string, listener: Listener) => listeners.delete(listener), ), }; window.matchMedia = jest.fn().mockReturnValue(query); return { query, change(value: boolean) { query.matches = value; for (const listener of listeners) listener(); }, }; } const original = window.matchMedia; afterEach(() => { window.matchMedia = original; }); describe("useReducedMotion", () => { it("is false when the system does not ask for reduced motion", () => { mockPreference(false); const { result } = renderHook(() => useReducedMotion()); expect(result.current).toBe(false); }); it("is true when the system asks for reduced motion", () => { mockPreference(true); const { result } = renderHook(() => useReducedMotion()); expect(result.current).toBe(true); expect(window.matchMedia).toHaveBeenCalledWith( "(prefers-reduced-motion: reduce)", ); }); it("follows the preference when it changes while the page is open", () => { const preference = mockPreference(false); const { result } = renderHook(() => useReducedMotion()); expect(result.current).toBe(false); act(() => preference.change(true)); expect(result.current).toBe(true); act(() => preference.change(false)); expect(result.current).toBe(false); }); it("stops listening when the component unmounts", () => { const preference = mockPreference(false); const { unmount } = renderHook(() => useReducedMotion()); unmount(); expect(preference.query.removeEventListener).toHaveBeenCalledTimes(1); }); it("is false on the server and in browsers without matchMedia, so the HTML always hydrates the same", () => { mockPreference(true); function Probe() { return createElement("i", { "data-reduced": String(useReducedMotion()) }); } expect(renderToString(createElement(Probe))).toContain( 'data-reduced="false"', ); // biome-ignore lint/suspicious/noExplicitAny: simula um navegador sem a API (window as any).matchMedia = undefined; const { result } = renderHook(() => useReducedMotion()); expect(result.current).toBe(false); }); });