import { renderHook } from '@testing-library/react-hooks';
import { describe, expect, it, vi } from 'vitest';
import { useValueOrOnDemand } from './useValueOrOnDemand';

describe('useValueOrDemand hook', () => {
  it.each(['test', 5])('loads synchronous values', async (input) => {
    const { result } = renderHook(() => useValueOrOnDemand(input));

    const [value, isLoading] = result.current;
    expect(value).toEqual(String(input));
    expect(isLoading).toBeFalsy();
  });

  it('returns empty string if no value given', async () => {
    const { result } = renderHook(() => useValueOrOnDemand());

    const [value, isLoading] = result.current;
    expect(value).toBe('');
    expect(isLoading).toBeFalsy();
  });

  it.each(['test', 5])('loads async values', async (input) => {
    const promise = () => Promise.resolve(input);
    const { result, waitForNextUpdate } = renderHook(() =>
      useValueOrOnDemand(promise),
    );

    await waitForNextUpdate();

    const [value, isLoading] = result.current;
    expect(isLoading).toBeFalsy();
    expect(value).toEqual(String(input));
  });

  it('uses empty string as default if promise is rejected', async () => {
    const promise = () => Promise.reject('error');
    const { result, waitForNextUpdate } = renderHook(() =>
      useValueOrOnDemand(promise),
    );

    await waitForNextUpdate();

    const [value, isLoading] = result.current;
    expect(value).toBe('');
    expect(isLoading).toBeFalsy();
  });

  it('uses given fallback if promise is rejected', async () => {
    const spy = vi.fn(() => 'default');
    const promise = () => Promise.reject('error');
    const { result, waitForNextUpdate } = renderHook(() =>
      useValueOrOnDemand(promise, spy),
    );

    await waitForNextUpdate();

    const [value, isLoading] = result.current;
    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith('error');
    expect(value).toBe('default');
    expect(isLoading).toBeFalsy();
  });
});
