import * as React from 'react';
import expect from 'expect';
import { screen, render, waitFor, fireEvent } from '@testing-library/react';
import { Basic, PageInfo } from './useInfiniteGetList.stories';
import { QueryClient } from '@tanstack/react-query';
import { testDataProvider } from './testDataProvider';
import { PaginationPayload, SortPayload } from '../types';
import { useInfiniteGetList } from './useInfiniteGetList';
import { CoreAdminContext } from '../core';
describe('useInfiniteGetList', () => {
const UseInfiniteGetList = ({
resource = 'posts',
pagination = { page: 1, perPage: 10 },
sort = { field: 'id', order: 'DESC' } as const,
filter = {},
options = {},
meta = undefined,
callback = null,
}: {
resource?: string;
pagination?: PaginationPayload;
sort?: SortPayload;
filter?: any;
options?: any;
meta?: any;
callback?: any;
}) => {
const hookValue = useInfiniteGetList(
resource,
{ pagination, sort, filter, meta },
options
);
if (callback) callback(hookValue);
return
hello
;
};
it('should call dataProvider.getList() on mount', async () => {
const dataProvider = {
getList: jest.fn(() =>
Promise.resolve({
data: [{ id: 73, name: 'France', code: 'FR' }],
total: 1,
})
),
} as any;
render();
await waitFor(() => {
expect(dataProvider.getList).toBeCalledTimes(1);
expect(dataProvider.getList).toBeCalledWith('heroes', {
filter: {},
pagination: { page: 1, perPage: 20 },
sort: { field: 'id', order: 'DESC' },
signal: undefined,
});
});
});
it('should not call the dataProvider on update', async () => {
const dataProvider = {
getList: jest.fn(() =>
Promise.resolve({
data: [{ id: 73, name: 'France', code: 'FR' }],
total: 1,
})
),
} as any;
const { rerender } = render();
await waitFor(() => {
expect(dataProvider.getList).toBeCalledTimes(1);
});
rerender();
await waitFor(() => {
expect(dataProvider.getList).toBeCalledTimes(1);
});
});
it('should call the dataProvider on update when the resource changes', async () => {
const dataProvider = {
getList: jest.fn(() =>
Promise.resolve({
data: [{ id: 73, name: 'France', code: 'FR' }],
total: 1,
})
),
} as any;
const { rerender } = render(
);
await waitFor(() => {
expect(dataProvider.getList).toBeCalledTimes(1);
});
rerender();
await waitFor(() => {
expect(dataProvider.getList).toBeCalledTimes(2);
});
});
it('should accept a meta parameter', async () => {
const dataProvider = {
getList: jest.fn(() =>
Promise.resolve({
data: [{ id: 73, name: 'France', code: 'FR' }],
total: 1,
})
),
} as any;
render(
);
await waitFor(() => {
expect(dataProvider.getList).toBeCalledWith('heroes', {
filter: {},
pagination: { page: 1, perPage: 20 },
sort: { field: 'id', order: 'DESC' },
meta: { hello: 'world' },
signal: undefined,
});
});
});
it('should call success side effects on success', async () => {
const onSuccess1 = jest.fn();
const countries = [
{ id: 73, name: 'France', code: 'FR' },
{ id: 74, name: 'Italia', code: 'IT' },
];
const dataProvider = {
getList: (resource, params) => {
return Promise.resolve({
data: countries.slice(
(params.pagination.page - 1) *
params.pagination.perPage,
(params.pagination.page - 1) *
params.pagination.perPage +
params.pagination.perPage
),
total: countries.length,
});
},
};
render(
);
await waitFor(async () => {
expect(onSuccess1).toBeCalledTimes(1);
expect(screen.getByLabelText('country').innerHTML).toContain(
'France'
);
screen.getByLabelText('refetch-button').click();
await waitFor(async () => {
expect(onSuccess1).toBeCalledTimes(2);
expect(screen.queryAllByLabelText('country')).toHaveLength(2);
});
});
});
it('should not pre-populate getOne Query Cache if more than 100 results', async () => {
const callback: any = jest.fn();
const queryClient = new QueryClient();
const dataProvider = testDataProvider({
// @ts-ignore
getList: jest.fn((_resource, { pagination: { page, perPage } }) =>
Promise.resolve({
data: Array.from(Array(perPage).keys()).map(index => ({
id: index + 1 + (page - 1) * perPage,
title: `item ${index + 1 + (page - 1) * perPage}`,
})),
total: perPage * 2,
})
),
});
render(
);
await waitFor(() => {
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
pages: expect.arrayContaining([
expect.objectContaining({
data: expect.arrayContaining([
{ id: 1, title: 'item 1' },
{ id: 101, title: 'item 101' },
]),
}),
]),
}),
})
);
});
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '1' }])
).toBeUndefined();
});
it('should not pre-populate getOne Query Cache if more than 100 results across several pages', async () => {
let hookValue;
const callback: any = jest.fn(value => {
hookValue = value;
});
const queryClient = new QueryClient();
const dataProvider = testDataProvider({
// @ts-ignore
getList: jest.fn((_resource, { pagination: { page, perPage } }) =>
Promise.resolve({
data: Array.from(Array(perPage).keys()).map(index => ({
id: index + 1 + (page - 1) * perPage,
title: `item ${index + 1 + (page - 1) * perPage}`,
})),
total: perPage * 2,
})
),
});
render(
);
await waitFor(() => {
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
pages: expect.arrayContaining([
expect.objectContaining({
data: expect.arrayContaining([
{ id: 1, title: 'item 1' },
{ id: 51, title: 'item 51' },
]),
}),
]),
}),
})
);
});
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '1' }])
).toBeDefined();
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '51' }])
).toBeDefined();
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '52' }])
).not.toBeDefined();
// Fetch next page
hookValue.fetchNextPage();
await waitFor(() => {
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
pages: expect.arrayContaining([
expect.objectContaining({
data: expect.arrayContaining([
{ id: 52, title: 'item 52' },
{ id: 102, title: 'item 102' },
]),
}),
]),
}),
})
);
});
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '1' }])
).toBeDefined();
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '51' }])
).toBeDefined();
// query data for item 52 should still be undefined
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '52' }])
).not.toBeDefined();
});
it('should only populate the getOne Query Cache with the records from the last fetched page', async () => {
let hookValue;
const callback: any = jest.fn(value => {
hookValue = value;
});
const queryClient = new QueryClient();
const dataProvider = testDataProvider({
// @ts-ignore
getList: jest.fn((_resource, { pagination: { page } }) =>
Promise.resolve({
data: [
{
id: page,
title: `item ${page}`,
},
],
total: 2,
})
),
});
render(
);
await waitFor(() => {
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
pages: expect.arrayContaining([
expect.objectContaining({
data: [{ id: 1, title: 'item 1' }],
}),
]),
}),
})
);
});
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '1' }])
).toEqual({ id: 1, title: 'item 1' });
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '2' }])
).toBeUndefined();
// Manually change query data for item 1
queryClient.setQueryData(['posts', 'getOne', { id: '1' }], {
id: 1,
title: 'changed!',
});
// Fetch next page
hookValue.fetchNextPage();
await waitFor(() => {
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
pages: expect.arrayContaining([
expect.objectContaining({
data: [{ id: 2, title: 'item 2' }],
}),
]),
}),
})
);
});
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '2' }])
).toEqual({ id: 2, title: 'item 2' });
// Check that the getOne Query Cache for item 1 has not been overriden
expect(
queryClient.getQueryData(['posts', 'getOne', { id: '1' }])
).toEqual({ id: 1, title: 'changed!' });
});
it('should abort the request if the query is canceled', async () => {
const abort = jest.fn();
const dataProvider = testDataProvider({
getList: jest.fn(
(_resource, { signal }) =>
new Promise(() => {
signal.addEventListener('abort', () => {
abort(signal.reason);
});
})
) as any,
});
dataProvider.supportAbortSignal = true;
const queryClient = new QueryClient();
render(
);
await waitFor(() => {
expect(dataProvider.getList).toHaveBeenCalled();
});
queryClient.cancelQueries({
queryKey: ['posts', 'getInfiniteList'],
});
await waitFor(() => {
expect(abort).toHaveBeenCalled();
});
});
describe('fetchNextPage', () => {
it('should fetch the next page when the dataProvider uses total', async () => {
render();
const button = await screen.findByLabelText('refetch-button');
fireEvent.click(button);
await screen.findByText('Belgium -- BE');
});
it('should fetch the next page when the dataProvider uses pageInfo', async () => {
render();
const button = await screen.findByLabelText('refetch-button');
fireEvent.click(button);
await screen.findByText('Belgium -- BE');
});
});
});