import { act, renderHook } from '@testing-library/react-hooks';

import { updateCache } from './updateCache';
import { useAsyncResource } from './useAsyncResource';
import { resourceCache } from './cache';
import { suspendFor } from './test.helpers';

describe('updateCache', () => {
  const apiFn = (id: number) =>
    id < 10
      ? Promise.resolve({ id, name: 'test name' })
      : Promise.reject({ message: 'error' });

  afterEach(() => {
    resourceCache(apiFn).clear();
  });

  it('should transition a data reader without throwing in between', async () => {
    // get the data reader from the custom hook, with params
    const { result } = renderHook(() => useAsyncResource(apiFn, 1));
    const [dataReader, updateDataReader] = result.current;

    // wait for it to fulfill
    await suspendFor(dataReader);

    // should be able to get raw data from the data reader
    expect(dataReader()).toStrictEqual({ id: 1, name: 'test name' });

    // update the data reader with a transition
    const updater = updateCache(apiFn, 2)
      .then(() => {
        act(() => updateDataReader(2));
      });

    // while update is in progress, data reader should remain the same
    const [prevDataReader] = result.current;
    expect(prevDataReader).toStrictEqual(dataReader);

    // wait for the update to happen
    await updater;

    // the new data reader should be available immediately
    const [newDataReader] = result.current;
    expect(newDataReader).not.toStrictEqual(prevDataReader);
    expect(newDataReader()).toStrictEqual({ id: 2, name: 'test name'  });
  });

  it('should throw a failure if update was not successful', async () => {
    // get the data reader from the custom hook, with params
    const { result } = renderHook(() => useAsyncResource(apiFn, 1));
    const [dataReader, updateDataReader] = result.current;

    // wait for it to fulfill
    await suspendFor(dataReader);

    // should be able to get raw data from the data reader
    expect(dataReader()).toStrictEqual({ id: 1, name: 'test name' });

    // update the data reader with a transition, but expect a failure to happen
    await updateCache(apiFn, 10)
      .then(() => {
        act(() => updateDataReader(10));
      });

    // verify that the new data reader fails with an error
    const [newDataReader] = result.current;
    expect(newDataReader).toThrowError(Error('error'));
  });
});
