/**
 * @vitest-environment jsdom
 */
import { renderHook } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import useSetTimeout from "./useSetTimeout";

vi.useFakeTimers();

describe("useSetTimeout", () => {
  it("schedules timeouts and triggers the callbacks", () => {
    const { result } = renderHook(() => useSetTimeout());
    const setTimeoutSpy = vi.spyOn(global, "setTimeout");

    // Schedule some timeouts...
    const callback1 = vi.fn();
    const callback2 = vi.fn();
    result.current(callback1, 1000);
    result.current(callback2, 500);

    // Ensure that setTimeout was actually called with the correct arguments.
    expect(setTimeoutSpy).toHaveBeenCalledTimes(2);
    expect(setTimeoutSpy).toBeCalledWith(expect.any(Function), 1000);
    expect(setTimeoutSpy).toBeCalledWith(expect.any(Function), 500);

    // Ensure callbacks are called after timers run.
    expect(callback2).not.toBeCalled();
    vi.advanceTimersByTime(500);
    expect(callback1).not.toBeCalled();
    expect(callback2).toBeCalled();
    vi.advanceTimersByTime(500);
    expect(callback1).toBeCalled();

    setTimeoutSpy.mockRestore();
  });

  it("throws if a timeout is scheduled after the component has unmounted", () => {
    const { result, unmount } = renderHook(() => useSetTimeout());

    unmount();

    expect(() => result.current(vi.fn(), 1000)).toThrowError(
      "Can't schedule a timeout on an unmounted component.",
    );
  });

  it("clears a timeout if the component unmounts", () => {
    const { result, unmount } = renderHook(() => useSetTimeout());
    const setTimeoutSpy = vi.spyOn(global, "setTimeout");
    const clearTimeoutSpy = vi.spyOn(global, "clearTimeout");
    const callback = vi.fn();

    result.current(callback, 1000);

    // Timeout should be cleared after unmounting.
    unmount();
    expect(clearTimeoutSpy).toBeCalled();

    // And the callback should no longer be called.
    vi.runOnlyPendingTimers();
    expect(callback).not.toBeCalled();

    setTimeoutSpy.mockRestore();
    clearTimeoutSpy.mockRestore();
  });

  it("clears a timeout when cancelled", () => {
    const { result } = renderHook(() => useSetTimeout());
    const setTimeoutSpy = vi.spyOn(global, "setTimeout");
    const clearTimeoutSpy = vi.spyOn(global, "clearTimeout");
    const callback = vi.fn();
    const cancel = result.current(callback, 1000);

    // Timeout should be cleared when cancelling.
    cancel();
    expect(clearTimeoutSpy).toBeCalled();

    // And the callback should no longer be called.
    vi.runOnlyPendingTimers();
    expect(callback).not.toBeCalled();

    setTimeoutSpy.mockRestore();
    clearTimeoutSpy.mockRestore();
  });
});
