import * as apollo from '@apollo/client';
import { ApolloError } from '@apollo/client';
import { cleanup, renderHook } from '@testing-library/react-hooks';
import { usePaginatedQuery } from '../usePaginatedQuery';
import TEST_QUERY from './testQuery.gql';

describe('usePaginatedQuery', () => {
    afterEach(cleanup);

    const evict = jest.fn();
    jest.spyOn(apollo, 'useApolloClient').mockReturnValue({
        cache: { evict },
    } as unknown as apollo.ApolloClient<object>);

    const mockQuery = (result: Partial<apollo.QueryResult>) =>
        jest
            .spyOn(apollo, 'useQuery')
            .mockReturnValue(result as apollo.QueryResult);

    test('returns loading = true if query is loading', () => {
        mockQuery({ loading: true });

        const { result } = renderHook(() => usePaginatedQuery(TEST_QUERY, {}));

        expect(result.current).toEqual({
            loading: true,
            error: undefined,
            data: undefined,
            fetchingMore: false,
            isLoading: false,
        });
    });

    test('returns error if an error occurred', () => {
        const error = 'ERROR';
        mockQuery({
            loading: true,
            error: new ApolloError({ errorMessage: error }),
        });

        const { result } = renderHook(() => usePaginatedQuery(TEST_QUERY, {}));

        expect(result.current.error?.message).toBe(error);
    });

    test('returns data if available', () => {
        const data = [{ test: 'value' }];
        mockQuery({ loading: true, data });

        const { result } = renderHook(() => usePaginatedQuery(TEST_QUERY, {}));

        expect(result.current.data).toBe(data);
        expect(result.current.loading).toBe(true);
    });

    test('returns "fetchMore" callback', () => {
        const fetchMore = jest.fn();
        mockQuery({ fetchMore });
        const { result } = renderHook(() => usePaginatedQuery(TEST_QUERY, {}));
        expect(result.current.fetchMore).toBe(fetchMore);
    });

    test('returns last available data while fetching new', () => {
        const data = [{ test: 'value' }];
        mockQuery({ loading: false, data });
        const { result, rerender } = renderHook(() =>
            usePaginatedQuery(TEST_QUERY, {})
        );

        mockQuery({ loading: true, data: undefined });
        rerender();

        expect(result.current.data).toBe(data);
        expect(result.current.loading).toBe(true);
    });

    test('evicts existing data from cache and when variables change', () => {
        const expectedArgs = {
            id: 'ROOT_QUERY',
            fieldName: 'testQuery',
        };
        mockQuery({ loading: true });
        evict.mockClear();
        const { rerender } = renderHook(
            (props: apollo.OperationVariables = {}) =>
                usePaginatedQuery(TEST_QUERY, props)
        );
        expect(evict).not.toHaveBeenCalled();

        evict.mockClear();
        rerender();
        expect(evict).not.toHaveBeenCalled();

        evict.mockClear();
        rerender({ newVar: 'withNewValue' });
        expect(evict).toHaveBeenCalledWith(expectedArgs);
    });
});
