import * as React from 'react';
import { waitFor, render, screen, fireEvent } from '@testing-library/react';
import expect from 'expect';
import { QueryClient, useMutationState } from '@tanstack/react-query';
import { CoreAdminContext } from '../core';
import { testDataProvider } from './testDataProvider';
import { useDeleteMany } from './useDeleteMany';
import { MutationMode, Params, InvalidateList } from './useDeleteMany.stories';
describe('useDeleteMany', () => {
it('returns a callback that can be used with update arguments', async () => {
const dataProvider = testDataProvider({
deleteMany: jest.fn(() => Promise.resolve({ data: [1, 2] } as any)),
});
let localDeleteMany;
const Dummy = () => {
const [deleteMany] = useDeleteMany();
localDeleteMany = deleteMany;
return ;
};
render(
);
localDeleteMany('foo', { ids: [1, 2] });
await waitFor(() => {
expect(dataProvider.deleteMany).toHaveBeenCalledWith('foo', {
ids: [1, 2],
});
});
});
it('returns a callback that can be used with no arguments', async () => {
const dataProvider = testDataProvider({
deleteMany: jest.fn(() => Promise.resolve({ data: [1, 2] } as any)),
});
let localDeleteMany;
const Dummy = () => {
const [deleteMany] = useDeleteMany('foo', { ids: [1, 2] });
localDeleteMany = deleteMany;
return ;
};
render(
);
localDeleteMany();
await waitFor(() => {
expect(dataProvider.deleteMany).toHaveBeenCalledWith('foo', {
ids: [1, 2],
});
});
});
it('uses a custom mutationFn when provided in options', async () => {
const dataProvider = testDataProvider({
deleteMany: jest.fn(() => Promise.resolve({ data: [1, 2] } as any)),
});
const customMutationFn = jest.fn(async params => params.ids);
let localDeleteMany;
let mutationData;
const Dummy = () => {
const [deleteMany, { data }] = useDeleteMany(undefined, undefined, {
mutationFn: customMutationFn,
});
localDeleteMany = deleteMany;
mutationData = data;
return ;
};
render(
);
localDeleteMany('foo', { ids: [1, 2] });
await waitFor(() => {
expect(customMutationFn).toHaveBeenCalledWith({
resource: 'foo',
ids: [1, 2],
});
});
expect(dataProvider.deleteMany).not.toHaveBeenCalled();
await waitFor(() => {
expect(mutationData).toEqual([1, 2]);
});
});
it('uses the latest declaration time mutationMode', async () => {
// This story uses the pessimistic mode by default
render();
await waitFor(() => new Promise(resolve => setTimeout(resolve, 0)));
fireEvent.click(screen.getByText('Change mutation mode to optimistic'));
fireEvent.click(screen.getByText('Delete posts'));
// Should display the optimistic result right away if the change was handled
await waitFor(() => {
expect(screen.queryByText('success')).not.toBeNull();
expect(screen.queryByText('Hello')).toBeNull();
expect(screen.queryByText('World')).not.toBeNull();
expect(screen.queryByText('mutating')).not.toBeNull();
});
await waitFor(() => {
expect(screen.queryByText('success')).not.toBeNull();
expect(screen.queryByText('Hello')).toBeNull();
expect(screen.queryByText('World')).not.toBeNull();
expect(screen.queryByText('mutating')).toBeNull();
});
});
it('uses the latest declaration time params', async () => {
let posts = [
{ id: 1, title: 'Hello' },
{ id: 2, title: 'World' },
];
const dataProvider = {
getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
deleteMany: jest.fn((_, params) => {
return new Promise(resolve => {
setTimeout(() => {
posts = posts.filter(
post => !params.ids.includes(post.id)
);
resolve({ data: params.previousData });
}, 1000);
});
}),
} as any;
// This story has no meta by default
render();
await waitFor(() => new Promise(resolve => setTimeout(resolve, 0)));
fireEvent.click(screen.getByText('Change params'));
fireEvent.click(screen.getByText('Delete posts'));
// Should display the optimistic result right away if the change was handled
await waitFor(() => {
expect(screen.queryByText('success')).not.toBeNull();
expect(screen.queryByText('Hello')).toBeNull();
expect(screen.queryByText('World')).not.toBeNull();
expect(screen.queryByText('mutating')).not.toBeNull();
});
await screen.findByText('success');
await screen.findByText('World');
expect(screen.queryByText('Hello')).toBeNull();
await waitFor(() => expect(screen.queryByText('mutating')).toBeNull());
expect(dataProvider.deleteMany).toHaveBeenCalledWith('posts', {
ids: [1],
meta: 'test',
});
});
it('uses call time params over hook time params', async () => {
const dataProvider = testDataProvider({
deleteMany: jest.fn(() => Promise.resolve({ data: [1, 2] } as any)),
});
let localDeleteMany;
const Dummy = () => {
const [deleteMany] = useDeleteMany('foo', { ids: [1, 2] });
localDeleteMany = deleteMany;
return ;
};
render(
);
localDeleteMany('foo', { ids: [3, 4] });
await waitFor(() => {
expect(dataProvider.deleteMany).toHaveBeenCalledWith('foo', {
ids: [3, 4],
});
});
});
it('calls onSettled when provided in hook time options', async () => {
const dataProvider = testDataProvider({
deleteMany: jest.fn(() => Promise.resolve({ data: [1, 2] } as any)),
});
let localDeleteMany;
const onSettled = jest.fn();
const Dummy = () => {
const [deleteMany] = useDeleteMany(
'foo',
{ ids: [1, 2] },
{ onSettled }
);
localDeleteMany = deleteMany;
return ;
};
render(
);
localDeleteMany('foo', { ids: [3, 4] });
await waitFor(() => {
expect(onSettled).toHaveBeenCalled();
});
});
it('accepts a meta parameter', async () => {
const dataProvider = testDataProvider({
deleteMany: jest.fn(() => Promise.resolve({ data: [1, 2] } as any)),
});
let localDeleteMany;
const Dummy = () => {
const [deleteMany] = useDeleteMany();
localDeleteMany = deleteMany;
return ;
};
render(
);
localDeleteMany('foo', { ids: [1, 2], meta: { hello: 'world' } });
await waitFor(() => {
expect(dataProvider.deleteMany).toHaveBeenCalledWith('foo', {
ids: [1, 2],
meta: { hello: 'world' },
});
});
});
it('sets the mutationKey', async () => {
const dataProvider = testDataProvider({
deleteMany: jest.fn(() => Promise.resolve({ data: [1, 2] } as any)),
});
let localDeleteMany;
const Dummy = () => {
const [deleteMany] = useDeleteMany('foo');
localDeleteMany = deleteMany;
return ;
};
const Observe = () => {
const mutation = useMutationState({
filters: {
mutationKey: ['foo', 'deleteMany'],
},
});
return mutations: {mutation.length};
};
render(
);
localDeleteMany('foo', { ids: [1, 2] });
await waitFor(() => {
expect(dataProvider.deleteMany).toHaveBeenCalledWith('foo', {
ids: [1, 2],
});
});
await screen.findByText('mutations: 1');
});
it('accepts a meta parameter', async () => {
const dataProvider = testDataProvider({
deleteMany: jest.fn(() => Promise.resolve({ data: [1, 2] } as any)),
});
let localDeleteMany;
const Dummy = () => {
const [deleteMany] = useDeleteMany();
localDeleteMany = deleteMany;
return ;
};
render(
);
localDeleteMany('foo', { ids: [1, 2], meta: { hello: 'world' } });
await waitFor(() => {
expect(dataProvider.deleteMany).toHaveBeenCalledWith('foo', {
ids: [1, 2],
meta: { hello: 'world' },
});
});
});
describe('query cache', () => {
it('updates getList query cache when dataProvider promise resolves', async () => {
const queryClient = new QueryClient();
queryClient.setQueryData(['foo', 'getList'], {
data: [
{ id: 1, bar: 'bar' },
{ id: 2, bar: 'bar' },
{ id: 3, bar: 'bar' },
{ id: 4, bar: 'bar' },
],
total: 4,
});
const dataProvider = {
deleteMany: jest.fn(() =>
Promise.resolve({ data: [1, 2] } as any)
),
} as any;
let localDeleteMany;
const Dummy = () => {
const [deleteMany] = useDeleteMany();
localDeleteMany = deleteMany;
return ;
};
render(
);
localDeleteMany('foo', { ids: [1, 2] });
await waitFor(() => {
expect(dataProvider.deleteMany).toHaveBeenCalledWith('foo', {
ids: [1, 2],
});
});
await waitFor(() => {
expect(queryClient.getQueryData(['foo', 'getList'])).toEqual({
data: [
{ id: 3, bar: 'bar' },
{ id: 4, bar: 'bar' },
],
total: 2,
});
});
});
it('updates getList query cache with pageInfo when dataProvider promise resolves', async () => {
const queryClient = new QueryClient();
queryClient.setQueryData(['foo', 'getList'], {
data: [
{ id: 1, bar: 'bar' },
{ id: 2, bar: 'bar' },
{ id: 3, bar: 'bar' },
{ id: 4, bar: 'bar' },
],
pageInfo: {
hasPreviousPage: false,
hasNextPage: true,
},
});
const dataProvider = {
deleteMany: jest.fn(() =>
Promise.resolve({ data: [1, 2] } as any)
),
} as any;
let localDeleteMany;
const Dummy = () => {
const [deleteMany] = useDeleteMany();
localDeleteMany = deleteMany;
return ;
};
render(
);
localDeleteMany('foo', { ids: [1, 2] });
await waitFor(() => {
expect(dataProvider.deleteMany).toHaveBeenCalledWith('foo', {
ids: [1, 2],
});
});
await waitFor(() => {
expect(queryClient.getQueryData(['foo', 'getList'])).toEqual({
data: [
{ id: 3, bar: 'bar' },
{ id: 4, bar: 'bar' },
],
pageInfo: {
hasPreviousPage: false,
hasNextPage: true,
},
});
});
});
it('updates getInfiniteList query cache when dataProvider promise resolves', async () => {
const queryClient = new QueryClient();
queryClient.setQueryData(['foo', 'getInfiniteList'], {
pages: [
{
data: [
{ id: 1, bar: 'bar' },
{ id: 2, bar: 'bar' },
{ id: 3, bar: 'bar' },
{ id: 4, bar: 'bar' },
],
total: 4,
},
],
pageParams: [],
});
const dataProvider = {
deleteMany: jest.fn(() =>
Promise.resolve({ data: [1, 2] } as any)
),
} as any;
let localDeleteMany;
const Dummy = () => {
const [deleteMany] = useDeleteMany();
localDeleteMany = deleteMany;
return ;
};
render(
);
localDeleteMany('foo', { ids: [1, 2] });
await waitFor(() => {
expect(dataProvider.deleteMany).toHaveBeenCalledWith('foo', {
ids: [1, 2],
});
});
await waitFor(() => {
expect(
queryClient.getQueryData(['foo', 'getInfiniteList'])
).toEqual({
pages: [
{
data: [
{ id: 3, bar: 'bar' },
{ id: 4, bar: 'bar' },
],
total: 2,
},
],
pageParams: [],
});
});
});
it('invalidates getList query when dataProvider resolves in undoable mode', async () => {
render();
fireEvent.click(await screen.findByText('Delete'));
await screen.findByText('resources.posts.notifications.deleted');
fireEvent.click(screen.getByText('Close'));
await waitFor(() => {
expect(screen.queryByText('1: Hello')).toBeNull();
});
});
});
});