import * as React from 'react';
import expect from 'expect';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { testDataProvider } from '../../dataProvider';
import {
AccessControl,
DefaultTitle,
FetchError,
Loading,
NoAuthProvider,
Offline,
RedirectOnError,
WithAuthProviderNoAccessControl,
WithRenderProps,
} from './EditBase.stories';
import { onlineManager } from '@tanstack/react-query';
describe('EditBase', () => {
beforeEach(() => {
onlineManager.setOnline(true);
});
it('should give access to the save function', async () => {
const dataProvider = testDataProvider({
getOne: () =>
// @ts-ignore
Promise.resolve({ data: { id: 12, test: 'previous' } }),
update: jest.fn((_, { id, data, previousData }) =>
Promise.resolve({ data: { id, ...previousData, ...data } })
),
});
render(
);
await waitFor(() => {
screen.getByText('previous');
});
fireEvent.click(screen.getByText('save'));
await waitFor(() => {
expect(dataProvider.update).toHaveBeenCalledWith('posts', {
id: 12,
data: { test: 'test' },
previousData: { id: 12, test: 'previous' },
});
});
});
it('should allow to override the onSuccess function', async () => {
const dataProvider = testDataProvider({
getOne: () =>
// @ts-ignore
Promise.resolve({ data: { id: 12, test: 'previous' } }),
update: jest.fn((_, { id, data, previousData }) =>
Promise.resolve({ data: { id, ...previousData, ...data } })
),
});
const onSuccess = jest.fn();
render(
);
await waitFor(() => {
screen.getByText('previous');
});
fireEvent.click(screen.getByText('save'));
await waitFor(() => {
expect(onSuccess).toHaveBeenCalledWith(
{
id: 12,
test: 'test',
},
{
id: 12,
data: { test: 'test' },
previousData: { id: 12, test: 'previous' },
resource: 'posts',
meta: undefined,
},
{ snapshot: expect.any(Array) },
expect.anything()
);
});
});
it('should allow to override the onSuccess function at call time', async () => {
const dataProvider = testDataProvider({
getOne: () =>
// @ts-ignore
Promise.resolve({ data: { id: 12, test: 'previous' } }),
update: jest.fn((_, { id, data, previousData }) =>
Promise.resolve({ data: { id, ...previousData, ...data } })
),
});
const onSuccess = jest.fn();
const onSuccessOverride = jest.fn();
render(
);
await waitFor(() => {
screen.getByText('previous');
});
fireEvent.click(screen.getByText('save'));
await waitFor(() => {
expect(onSuccessOverride).toHaveBeenCalledWith(
{
id: 12,
test: 'test',
},
{
id: 12,
data: { test: 'test' },
previousData: { id: 12, test: 'previous' },
resource: 'posts',
meta: undefined,
},
{ snapshot: expect.any(Array) },
expect.anything()
);
});
expect(onSuccess).not.toHaveBeenCalled();
});
it('should allow to override the onError function', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
const dataProvider = testDataProvider({
getOne: () =>
// @ts-ignore
Promise.resolve({ data: { id: 12, test: 'previous' } }),
update: jest.fn(() => Promise.reject({ message: 'test' })),
});
const onError = jest.fn();
render(
);
await screen.findByText('previous');
fireEvent.click(screen.getByText('save'));
await waitFor(() => {
expect(onError).toHaveBeenCalledWith(
{ message: 'test' },
{
id: 12,
data: { test: 'test' },
previousData: { id: 12, test: 'previous' },
resource: 'posts',
meta: undefined,
},
{ snapshot: expect.any(Array) },
expect.anything()
);
});
});
it('should allow to override the onError function at call time', async () => {
const dataProvider = testDataProvider({
getOne: () =>
// @ts-ignore
Promise.resolve({ data: { id: 12, test: 'previous' } }),
update: jest.fn(() => Promise.reject({ message: 'test' })),
});
const onError = jest.fn();
const onErrorOverride = jest.fn();
render(
);
await screen.findByText('previous');
fireEvent.click(screen.getByText('save'));
await waitFor(() => {
expect(onErrorOverride).toHaveBeenCalledWith(
{ message: 'test' },
{
id: 12,
data: { test: 'test' },
previousData: { id: 12, test: 'previous' },
resource: 'posts',
meta: undefined,
},
{ snapshot: expect.any(Array) },
expect.anything()
);
});
expect(onError).not.toHaveBeenCalled();
});
it('should allow to override the transform function', async () => {
const dataProvider = testDataProvider({
getOne: () =>
// @ts-ignore
Promise.resolve({ data: { id: 12, test: 'previous' } }),
update: jest.fn((_, { id, data, previousData }) =>
Promise.resolve({ data: { id, ...previousData, ...data } })
),
});
const transform = jest.fn((data, _options) => ({
...data,
test: 'test transformed',
}));
render(
);
await screen.findByText('previous');
fireEvent.click(screen.getByText('save'));
await waitFor(() => {
expect(transform).toHaveBeenCalledWith(
{ test: 'test' },
{ previousData: { id: 12, test: 'previous' } }
);
});
await waitFor(() => {
expect(dataProvider.update).toHaveBeenCalledWith('posts', {
id: 12,
data: { test: 'test transformed' },
previousData: { id: 12, test: 'previous' },
});
});
});
it('should allow to override the transform function at call time', async () => {
const dataProvider = testDataProvider({
getOne: () =>
// @ts-ignore
Promise.resolve({ data: { id: 12, test: 'previous' } }),
update: jest.fn((_, { id, data, previousData }) =>
Promise.resolve({ data: { id, ...previousData, ...data } })
),
});
const transform = jest.fn();
const transformOverride = jest.fn((data, _options) => ({
...data,
test: 'test transformed',
}));
render(
);
await screen.findByText('previous');
fireEvent.click(screen.getByText('save'));
await waitFor(() => {
expect(transformOverride).toHaveBeenCalledWith(
{ test: 'test' },
{ previousData: { id: 12, test: 'previous' } }
);
});
await waitFor(() => {
expect(dataProvider.update).toHaveBeenCalledWith('posts', {
id: 12,
data: { test: 'test transformed' },
previousData: { id: 12, test: 'previous' },
});
});
expect(transform).not.toHaveBeenCalled();
});
it('should load data immediately if authProvider is not provided', async () => {
const dataProvider = testDataProvider({
// @ts-ignore
getOne: jest.fn(() =>
Promise.resolve({ data: { id: 12, test: 'Hello' } })
),
});
render();
expect(dataProvider.getOne).toHaveBeenCalled();
await screen.findByText('Hello');
});
it('should not wait for the authentication resolution before loading data when disableAuthentication is true', async () => {
const authProvider = {
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
checkAuth: jest.fn(),
};
const dataProvider = testDataProvider({
// @ts-ignore
getOne: jest.fn(() =>
Promise.resolve({ data: { id: 12, test: 'Hello' } })
),
});
render(
);
await screen.findByText('Hello');
expect(authProvider.checkAuth).not.toHaveBeenCalled();
});
it('should wait for the authentication resolution before loading data', async () => {
let resolveAuth: () => void;
const authProvider = {
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
checkAuth: () =>
new Promise(resolve => {
resolveAuth = resolve;
}),
};
const dataProvider = testDataProvider({
// @ts-ignore
getOne: jest.fn(() =>
Promise.resolve({ data: { id: 12, test: 'Hello' } })
),
});
render(
);
expect(dataProvider.getOne).not.toHaveBeenCalled();
await screen.findByText('Authentication loading...');
resolveAuth!();
await screen.findByText('Hello');
});
it('should wait for both the authentication and authorization resolution before loading data', async () => {
let resolveAuth: () => void;
let resolveCanAccess: (value: boolean) => void;
const authProvider = {
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
checkAuth: () =>
new Promise(resolve => {
resolveAuth = resolve;
}),
canAccess: jest.fn(
() =>
new Promise(resolve => {
resolveCanAccess = resolve;
})
),
};
const dataProvider = testDataProvider({
// @ts-ignore
getOne: jest.fn(() =>
Promise.resolve({ data: { id: 12, test: 'Hello' } })
),
});
render(
);
expect(dataProvider.getOne).not.toHaveBeenCalled();
await screen.findByText('Authentication loading...');
resolveAuth!();
expect(dataProvider.getOne).not.toHaveBeenCalled();
await screen.findByText('Authentication loading...');
await waitFor(() => {
expect(authProvider.canAccess).toHaveBeenCalled();
});
resolveCanAccess!(true);
await screen.findByText('Hello');
});
it('should provide a default title', async () => {
render();
await screen.findByText('Post Hello (en)');
fireEvent.click(screen.getByText('FR'));
await screen.findByText('Article Hello (fr)');
});
it('should allow resource specific default title', async () => {
render();
await screen.findByText('Update article Hello (en)');
fireEvent.click(screen.getByText('FR'));
await screen.findByText("Modifier l'article Hello (fr)");
});
it('should allow renderProp', async () => {
const dataProvider = testDataProvider({
getOne: () =>
// @ts-ignore
Promise.resolve({ data: { id: 12, test: 'Hello' } }),
update: jest.fn((_, { id, data, previousData }) =>
Promise.resolve({ data: { id, ...previousData, ...data } })
),
});
render(
);
await screen.findByText('12');
await screen.findByText('Hello');
fireEvent.click(screen.getByText('save'));
await waitFor(() => {
expect(dataProvider.update).toHaveBeenCalledWith('posts', {
id: 12,
data: { test: 'test' },
previousData: { id: 12, test: 'Hello' },
});
});
});
it('should render the offline prop node when offline', async () => {
const { rerender } = render();
await screen.findByText('You are offline, cannot load data');
rerender();
await screen.findByText('Hello');
expect(
screen.queryByText('You are offline, cannot load data')
).toBeNull();
rerender();
// Ensure the data is still displayed when going offline after it was loaded
await screen.findByText('You are offline, the data may be outdated');
await screen.findByText('Hello');
});
it('should render loading component while loading', async () => {
render();
expect(screen.queryByText('Loading data...')).not.toBeNull();
expect(screen.queryByText('Hello')).toBeNull();
fireEvent.click(screen.getByText('Resolve loading'));
await waitFor(() => {
expect(screen.queryByText('Loading data...')).toBeNull();
});
await screen.findByText('Hello');
});
it('should render error component on error', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
render();
expect(screen.queryByText('Something went wrong.')).toBeNull();
expect(screen.queryByText('Hello')).toBeNull();
fireEvent.click(screen.getByText('Reject loading'));
await waitFor(() => {
expect(screen.queryByText('Something went wrong.')).not.toBeNull();
});
expect(screen.queryByText('Hello')).toBeNull();
jest.spyOn(console, 'error').mockRestore();
});
it('should redirect when no error component is provided', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
render();
expect(screen.queryByText('Hello')).toBeNull();
fireEvent.click(screen.getByText('Reject loading'));
await waitFor(() => {
expect(screen.queryByText('List view')).not.toBeNull();
});
expect(screen.queryByText('Hello')).toBeNull();
jest.spyOn(console, 'error').mockRestore();
});
});