import React, { FC } from "react"; import { act, renderHook } from "@testing-library/react-hooks"; import { render } from "@testing-library/react"; import { mocked } from "ts-jest/utils"; import useInterval from "../useInterval"; jest.useFakeTimers(); const setInterval = mocked(window.setInterval); const clearInterval = mocked(window.clearInterval); beforeEach(() => { setInterval.mockClear(); clearInterval.mockClear(); }); interface TestProps { cb: () => void; delay?: number; defaultRunning?: boolean; } const Test: FC = ({ cb, delay = 300, defaultRunning = false }) => { useInterval(cb, delay, defaultRunning); return null; }; describe("useInterval", () => { it("should default to not starting the interval when mounted", () => { const cb = jest.fn(); const { result } = renderHook(() => useInterval(cb, 300)); const [running] = result.current; expect(cb).not.toBeCalled(); expect(running).toBe(false); expect(setInterval).not.toBeCalled(); expect(clearInterval).not.toBeCalled(); }); it("should start the interval when mounted if the defaultRunning prop is true", () => { const cb = jest.fn(); const { result } = renderHook(() => useInterval(cb, 300, true)); const [running] = result.current; expect(running).toBe(true); expect(cb).not.toBeCalled(); expect(setInterval).toBeCalledWith(expect.any(Function), 300); expect(clearInterval).not.toBeCalled(); }); it("should start/stop the interval when the start/stop functions are called", () => { const cb = jest.fn(); const { result } = renderHook(() => useInterval(cb, 300)); let [running] = result.current; const [, start, stop] = result.current; expect(setInterval).not.toBeCalled(); expect(clearInterval).not.toBeCalled(); expect(running).toBe(false); act(() => { start(); }); [running] = result.current; expect(setInterval).toBeCalledTimes(1); expect(clearInterval).not.toBeCalled(); expect(running).toBe(true); act(() => { stop(); }); [running] = result.current; expect(setInterval).toBeCalledTimes(1); expect(clearInterval).toBeCalledTimes(1); expect(running).toBe(false); }); it("should restart the interval if the delay changes", () => { const cb = jest.fn(); const { rerender } = render(); expect(setInterval).toBeCalled(); expect(clearInterval).not.toBeCalled(); rerender(); expect(setInterval).toBeCalledTimes(2); expect(clearInterval).toBeCalled(); }); it("should not restart the interval if the callback changes before the interval completes", () => { const cb1 = jest.fn(); const cb2 = jest.fn(); const { rerender } = render(); expect(cb1).not.toBeCalled(); // pretend like there was a dom update before the timer finishes that re-renders the component // and the user had used an arrow function instead of a useCallback or equiv jest.runTimersToTime(200); rerender(); jest.runTimersToTime(400); expect(cb1).not.toBeCalled(); expect(cb2).toBeCalled(); }); });