374 lines
12 KiB
TypeScript
374 lines
12 KiB
TypeScript
import { renderHook, act } from '@testing-library/react';
|
|
import {
|
|
useDataProvider,
|
|
useThrottledDataProvider,
|
|
type useDataProviderParams,
|
|
} from './use-data-provider';
|
|
import { type Subscribe, type UpdateCallback } from './generic-data-provider';
|
|
import { MockedProvider } from '@apollo/client/testing';
|
|
|
|
type Data = number;
|
|
type Delta = number;
|
|
type Variables = { partyId: string; marketIds?: string[] };
|
|
|
|
const unsubscribe = jest.fn();
|
|
const reload = jest.fn();
|
|
const flush = jest.fn();
|
|
const load = jest.fn();
|
|
const update = jest.fn();
|
|
const insert = jest.fn();
|
|
const variables = { partyId: '0x123' };
|
|
|
|
const updateCallbackPayload: Parameters<UpdateCallback<Data, Delta>>['0'] = {
|
|
data: 0,
|
|
loading: false,
|
|
loaded: true,
|
|
pageInfo: null,
|
|
};
|
|
|
|
const dataProvider = jest.fn<
|
|
ReturnType<Subscribe<Data, Delta, Variables>>,
|
|
Parameters<Subscribe<Data, Delta, Variables>>
|
|
>();
|
|
|
|
dataProvider.mockReturnValue({
|
|
unsubscribe,
|
|
reload,
|
|
flush,
|
|
load,
|
|
});
|
|
|
|
jest.useFakeTimers();
|
|
|
|
describe('useDataProvider hook', () => {
|
|
const render = (
|
|
initialProps?: useDataProviderParams<Data, Delta, Variables>
|
|
) =>
|
|
renderHook<
|
|
ReturnType<typeof useDataProvider>,
|
|
useDataProviderParams<Data, Delta, Variables>
|
|
>((props) => useDataProvider(props), {
|
|
wrapper: MockedProvider,
|
|
initialProps,
|
|
});
|
|
beforeEach(() => {
|
|
update.mockClear();
|
|
insert.mockClear();
|
|
dataProvider.mockClear();
|
|
unsubscribe.mockClear();
|
|
});
|
|
it('calls update on load', async () => {
|
|
const { result } = render({ dataProvider, update, variables });
|
|
expect(result.current.data).toEqual(null);
|
|
expect(result.current.loading).toEqual(true);
|
|
expect(result.current.error).toEqual(undefined);
|
|
const callback = dataProvider.mock.calls[0][0];
|
|
await act(async () => {
|
|
callback(updateCallbackPayload);
|
|
});
|
|
expect(result.current.data).toEqual(updateCallbackPayload.data);
|
|
expect(result.current.loading).toEqual(false);
|
|
expect(update).toBeCalledTimes(2);
|
|
expect(update.mock.calls[1][0].data).toEqual(updateCallbackPayload.data);
|
|
});
|
|
|
|
it('calls update on error', async () => {
|
|
const { result } = render({ dataProvider, update, variables });
|
|
expect(result.current.data).toEqual(null);
|
|
expect(result.current.loading).toEqual(true);
|
|
expect(result.current.error).toEqual(undefined);
|
|
const callback = dataProvider.mock.calls[0][0];
|
|
await act(async () => {
|
|
callback(updateCallbackPayload);
|
|
});
|
|
expect(result.current.data).toEqual(updateCallbackPayload.data);
|
|
expect(result.current.loading).toEqual(false);
|
|
expect(update).toBeCalledTimes(2);
|
|
expect(update.mock.calls[1][0].data).toEqual(updateCallbackPayload.data);
|
|
});
|
|
|
|
it('calls update if isUpdate and skip setting state if update returns true', async () => {
|
|
const { result } = render({ dataProvider, update, variables });
|
|
let data = 0;
|
|
const delta = 0;
|
|
const callback = dataProvider.mock.calls[0][0];
|
|
await act(async () => {
|
|
callback({ ...updateCallbackPayload, data: ++data });
|
|
});
|
|
expect(update).toBeCalledTimes(2);
|
|
expect(result.current.data).toEqual(data);
|
|
await act(async () => {
|
|
callback({
|
|
...updateCallbackPayload,
|
|
data: ++data,
|
|
delta,
|
|
isUpdate: true,
|
|
});
|
|
});
|
|
expect(result.current.data).toEqual(data);
|
|
expect(update).toBeCalledTimes(3);
|
|
expect(update.mock.calls[2][0].data).toEqual(data);
|
|
expect(update.mock.calls[2][0].delta).toEqual(delta);
|
|
update.mockReturnValueOnce(true);
|
|
await act(async () => {
|
|
callback({
|
|
...updateCallbackPayload,
|
|
data: ++data,
|
|
delta,
|
|
isUpdate: true,
|
|
});
|
|
});
|
|
expect(result.current.data).toEqual(update.mock.calls[2][0].data);
|
|
expect(update).toBeCalledTimes(4);
|
|
expect(update.mock.calls[3][0].data).toEqual(data);
|
|
expect(update.mock.calls[3][0].delta).toEqual(delta);
|
|
});
|
|
|
|
it('calls insert if isInsert and skip setting state if update returns true', async () => {
|
|
const { result } = render({ dataProvider, insert, variables });
|
|
let data = 0;
|
|
const insertionData = 0;
|
|
const callback = dataProvider.mock.calls[0][0];
|
|
await act(async () => {
|
|
callback({ ...updateCallbackPayload, data: ++data });
|
|
});
|
|
await act(async () => {
|
|
callback({
|
|
...updateCallbackPayload,
|
|
data: ++data,
|
|
insertionData,
|
|
isInsert: true,
|
|
});
|
|
});
|
|
expect(result.current.data).toEqual(data);
|
|
expect(insert).toBeCalledTimes(1);
|
|
expect(insert.mock.calls[0][0].data).toEqual(data);
|
|
expect(insert.mock.calls[0][0].insertionData).toEqual(insertionData);
|
|
insert.mockReturnValueOnce(true);
|
|
await act(async () => {
|
|
callback({
|
|
...updateCallbackPayload,
|
|
data: ++data,
|
|
insertionData,
|
|
isInsert: true,
|
|
});
|
|
});
|
|
expect(result.current.data).toEqual(insert.mock.calls[0][0].data);
|
|
expect(insert).toBeCalledTimes(2);
|
|
expect(insert.mock.calls[1][0].data).toEqual(data);
|
|
expect(insert.mock.calls[1][0].insertionData).toEqual(insertionData);
|
|
});
|
|
|
|
it('calls dataProvider with updated variables if skip switched to true', async () => {
|
|
const { rerender } = render({
|
|
dataProvider,
|
|
variables: { partyId: '' },
|
|
skip: true,
|
|
});
|
|
expect(dataProvider).toBeCalledTimes(0);
|
|
rerender({
|
|
dataProvider,
|
|
variables,
|
|
});
|
|
expect(dataProvider).toBeCalledTimes(1);
|
|
expect(dataProvider.mock.calls[0][2]).toEqual(variables);
|
|
});
|
|
it('uses same data provider when rerendered with equal variables', async () => {
|
|
const { rerender } = render({
|
|
dataProvider,
|
|
variables: { ...variables, marketIds: ['a', 'b'] },
|
|
});
|
|
expect(dataProvider).toBeCalledTimes(1);
|
|
rerender({
|
|
dataProvider,
|
|
variables: { ...variables, marketIds: ['b', 'a'] },
|
|
});
|
|
expect(dataProvider).toBeCalledTimes(1);
|
|
rerender({
|
|
dataProvider,
|
|
variables: { ...variables },
|
|
});
|
|
expect(dataProvider).toBeCalledTimes(2);
|
|
});
|
|
|
|
it('calls new update and insert when replaced', async () => {
|
|
const { rerender } = render({
|
|
dataProvider,
|
|
update,
|
|
insert,
|
|
variables,
|
|
});
|
|
const data = 0;
|
|
const delta = 0;
|
|
const insertionData = 0;
|
|
const callback = dataProvider.mock.calls[0][0];
|
|
await act(async () => {
|
|
callback({ ...updateCallbackPayload, data });
|
|
});
|
|
const newUpdate = jest.fn();
|
|
const newInsert = jest.fn();
|
|
expect(update).toBeCalledTimes(2);
|
|
expect(insert).toBeCalledTimes(0);
|
|
rerender({ dataProvider, update: newUpdate, insert: newInsert, variables });
|
|
await act(async () => {
|
|
callback({
|
|
...updateCallbackPayload,
|
|
data: data,
|
|
delta,
|
|
isUpdate: true,
|
|
});
|
|
});
|
|
expect(newUpdate).toBeCalledTimes(1);
|
|
await act(async () => {
|
|
callback({
|
|
...updateCallbackPayload,
|
|
data,
|
|
insertionData,
|
|
isInsert: true,
|
|
});
|
|
});
|
|
expect(newUpdate).toBeCalledTimes(2);
|
|
expect(newInsert).toBeCalledTimes(1);
|
|
expect(update).toBeCalledTimes(2);
|
|
expect(insert).toBeCalledTimes(0);
|
|
});
|
|
|
|
it('skip updates if skipUpdates is true', async () => {
|
|
const { result, rerender } = render({
|
|
dataProvider,
|
|
update,
|
|
variables,
|
|
skipUpdates: true,
|
|
});
|
|
expect(update).toBeCalledTimes(1);
|
|
let data = 0;
|
|
const delta = 1;
|
|
const callback = dataProvider.mock.calls[0][0];
|
|
await act(async () => {
|
|
callback({ ...updateCallbackPayload, data });
|
|
});
|
|
expect(update).toBeCalledTimes(2);
|
|
expect(result.current.data).toEqual(data);
|
|
await act(async () => {
|
|
callback({
|
|
...updateCallbackPayload,
|
|
data: data + delta,
|
|
delta,
|
|
isUpdate: true,
|
|
});
|
|
});
|
|
expect(update).toBeCalledTimes(2);
|
|
expect(result.current.data).toEqual(data);
|
|
rerender({ dataProvider, variables, update });
|
|
expect(update).toBeCalledTimes(2);
|
|
await act(async () => {
|
|
callback({
|
|
...updateCallbackPayload,
|
|
data: (data = data + delta),
|
|
delta,
|
|
isUpdate: true,
|
|
});
|
|
});
|
|
expect(update).toBeCalledTimes(3);
|
|
expect(result.current.data).toEqual(data);
|
|
expect(dataProvider).toBeCalledTimes(1);
|
|
});
|
|
|
|
it('change data provider instance on variables change', async () => {
|
|
const { result, rerender } = render({ dataProvider, update, variables });
|
|
const callback = dataProvider.mock.calls[0][0];
|
|
expect(dataProvider).toBeCalledTimes(1);
|
|
await act(async () => {
|
|
callback({ ...updateCallbackPayload });
|
|
});
|
|
expect(update).toBeCalledTimes(2);
|
|
|
|
// setting same variables, with different object reference
|
|
await act(async () => {
|
|
rerender({ dataProvider, update, variables: { ...variables } });
|
|
});
|
|
expect(unsubscribe).toBeCalledTimes(0);
|
|
expect(dataProvider).toBeCalledTimes(1);
|
|
|
|
// changing variables after date was loaded
|
|
await act(async () => {
|
|
rerender({ dataProvider, update, variables: { partyId: '0x321' } });
|
|
});
|
|
expect(unsubscribe).toBeCalledTimes(1);
|
|
expect(dataProvider).toBeCalledTimes(2);
|
|
expect(result.current.data).toEqual(null);
|
|
expect(result.current.loading).toEqual(true);
|
|
expect(result.current.error).toEqual(undefined);
|
|
await act(async () => {
|
|
callback({ ...updateCallbackPayload });
|
|
});
|
|
expect(update).toBeCalledTimes(4);
|
|
|
|
// changing variables, apollo query will return error
|
|
await act(async () => {
|
|
rerender({ dataProvider, update, variables });
|
|
});
|
|
expect(unsubscribe).toBeCalledTimes(2);
|
|
expect(dataProvider).toBeCalledTimes(3);
|
|
expect(result.current.data).toEqual(null);
|
|
expect(result.current.loading).toEqual(true);
|
|
expect(result.current.error).toEqual(undefined);
|
|
await act(async () => {
|
|
callback({
|
|
data: null,
|
|
loading: false,
|
|
loaded: false,
|
|
error: new Error(),
|
|
pageInfo: null,
|
|
});
|
|
});
|
|
expect(update).toBeCalledTimes(6);
|
|
});
|
|
|
|
it('do not create data provider instance when skip is true', async () => {
|
|
const { result } = render({ dataProvider, update, variables, skip: true });
|
|
expect(dataProvider).toBeCalledTimes(0);
|
|
expect(result.current.data).toEqual(null);
|
|
expect(result.current.loading).toEqual(false);
|
|
expect(result.current.error).toEqual(undefined);
|
|
});
|
|
});
|
|
|
|
describe('useThrottledDataProvider hook', () => {
|
|
beforeEach(() => {
|
|
dataProvider.mockClear();
|
|
});
|
|
it('throttling should delay update', async () => {
|
|
const wait = 100;
|
|
const { result } = renderHook(
|
|
() =>
|
|
useThrottledDataProvider(
|
|
{
|
|
dataProvider,
|
|
variables,
|
|
},
|
|
wait
|
|
),
|
|
{ wrapper: MockedProvider }
|
|
);
|
|
expect(result.current.data).toEqual(null);
|
|
expect(result.current.loading).toEqual(true);
|
|
expect(result.current.error).toEqual(undefined);
|
|
const callback = dataProvider.mock.calls[0][0];
|
|
await act(async () => {
|
|
callback({ ...updateCallbackPayload, data: 1 }); // initial value
|
|
});
|
|
await act(async () => {
|
|
callback({ ...updateCallbackPayload, data: 2, isUpdate: true, delta: 1 }); // first update, should be excluded
|
|
callback({ ...updateCallbackPayload, data: 3, isUpdate: true, delta: 1 }); // next update, should be excluded
|
|
callback({ ...updateCallbackPayload, data: 4, isUpdate: true, delta: 1 }); // next update, should be excluded
|
|
callback({ ...updateCallbackPayload, data: 5, isUpdate: true, delta: 1 }); // last update, should be executed after timeout
|
|
});
|
|
expect(result.current.data).toEqual(1);
|
|
await act(async () => {
|
|
jest.runAllTimers();
|
|
});
|
|
expect(result.current.data).toEqual(5);
|
|
});
|
|
});
|