import * as React from 'react';
import expect from 'expect';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { Route, Routes } from 'react-router';
import { ShowController } from './ShowController';
import { CoreAdminContext } from '../../core';
import { AuthProvider, DataProvider } from '../../types';
import { TestMemoryRouter } from '../../routing';
import { testDataProvider } from '../../dataProvider';
import {
Authenticated,
CanAccess,
DisableAuthentication,
} from './useShowController.security.stories';
import { EncodedId } from './useShowController.stories';
describe('useShowController', () => {
const defaultProps = {
id: 12,
resource: 'posts',
debounce: 200,
};
it('should call the dataProvider.getOne() function on mount', async () => {
const getOne = jest
.fn()
.mockImplementationOnce(() =>
Promise.resolve({ data: { id: 12, title: 'hello' } })
);
const dataProvider = { getOne } as unknown as DataProvider;
render(
{({ record }) => {record && record.title}
}
);
await waitFor(() => {
expect(getOne).toHaveBeenCalled();
expect(screen.queryAllByText('hello')).toHaveLength(1);
});
});
it.each([
{ id: 'test?', url: '/posts/test%3F' },
{ id: 'test%', url: '/posts/test%25' },
])(
'should decode the id $id from the route params',
async ({ id, url }) => {
const getOne = jest
.fn()
.mockImplementationOnce(() =>
Promise.resolve({ data: { id, title: 'hello' } })
);
const dataProvider = { getOne } as unknown as DataProvider;
render();
await waitFor(() => {
expect(getOne).toHaveBeenCalledWith('posts', {
id,
signal: undefined,
});
});
await waitFor(() => {
expect(screen.queryAllByText('Title: hello')).toHaveLength(1);
});
}
);
it('should use the id provided through props if any', async () => {
const getOne = jest
.fn()
.mockImplementationOnce(() =>
Promise.resolve({ data: { id: 0, title: 'hello' } })
);
const dataProvider = { getOne } as unknown as DataProvider;
render(
{({ record }) => (
{record && record.title}
)}
}
/>
);
await waitFor(() => {
expect(getOne).toHaveBeenCalledWith('posts', {
id: 0,
signal: undefined,
});
});
await waitFor(() => {
expect(screen.queryAllByText('hello')).toHaveLength(1);
});
});
it('should accept custom client query options', async () => {
const mock = jest.spyOn(console, 'error').mockImplementation(() => {});
const getOne = jest
.fn()
.mockImplementationOnce(() => Promise.reject(new Error()));
const onError = jest.fn();
const dataProvider = { getOne } as unknown as DataProvider;
render(
{() => }
}
/>
);
await waitFor(() => {
expect(getOne).toHaveBeenCalled();
expect(onError).toHaveBeenCalled();
});
mock.mockRestore();
});
it('should accept meta in queryOptions', async () => {
const getOne = jest
.fn()
.mockImplementationOnce(() =>
Promise.resolve({ data: { id: 0, title: 'hello' } })
);
const dataProvider = { getOne } as unknown as DataProvider;
render(
{() => }
}
/>
);
await waitFor(() => {
expect(getOne).toHaveBeenCalledWith('posts', {
id: '1',
meta: { foo: 'bar' },
signal: undefined,
});
});
});
describe('security', () => {
it('should not call the dataProvider until the authentication check passes', async () => {
let resolveAuthCheck: () => void;
const authProvider: AuthProvider = {
checkAuth: jest.fn(
() =>
new Promise(resolve => {
resolveAuthCheck = resolve;
})
),
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
getPermissions: () => Promise.resolve(),
};
const dataProvider = testDataProvider({
// @ts-ignore
getOne: jest.fn(() =>
Promise.resolve({
data: { id: 1, title: 'A post', votes: 0 },
})
),
});
render(
);
await waitFor(() => {
expect(authProvider.checkAuth).toHaveBeenCalled();
});
expect(dataProvider.getOne).not.toHaveBeenCalled();
resolveAuthCheck!();
await screen.findByText('A post - 0 votes');
});
it('should redirect to the /access-denied page when users do not have access', async () => {
render();
await screen.findByText('List');
fireEvent.click(await screen.findByText('posts.show access'));
fireEvent.click(await screen.findByText('Show'));
await screen.findByText('Loading...');
await screen.findByText('Access denied');
});
it('should display the show view when users have access', async () => {
render();
await screen.findByText('List');
fireEvent.click(await screen.findByText('Show'));
await screen.findByText('Loading...');
await screen.findByText('Post #1 - 90 votes');
});
it('should call the dataProvider if disableAuthentication is true', async () => {
const authProvider: AuthProvider = {
checkAuth: jest.fn(),
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
getPermissions: () => Promise.resolve(),
};
const dataProvider = testDataProvider({
// @ts-ignore
getOne: jest.fn(() =>
Promise.resolve({
data: { id: 1, title: 'A post', votes: 0 },
})
),
});
render(
);
await screen.findByText('A post - 0 votes');
expect(dataProvider.getOne).toHaveBeenCalled();
expect(authProvider.checkAuth).not.toHaveBeenCalled();
});
it('should not call checkAuth nor canAccess when disableAuthentication is true', async () => {
const authProvider: AuthProvider = {
checkAuth: jest.fn().mockResolvedValue(true),
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
getPermissions: () => Promise.resolve(),
canAccess: jest.fn().mockResolvedValue(false),
};
render();
await screen.findByText('Post #1 - 90 votes');
expect(authProvider.checkAuth).not.toHaveBeenCalled();
expect(authProvider.canAccess).not.toHaveBeenCalled();
});
it('should not call checkAuth nor canAccess when disableAuthentication is true even if useAuthState was called before', async () => {
const authProvider: AuthProvider = {
checkAuth: jest.fn().mockResolvedValue(true),
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
getPermissions: () => Promise.resolve(),
canAccess: jest.fn().mockResolvedValue(false),
};
render();
await screen.findByText('Post #1 - 90 votes');
fireEvent.click(await screen.findByText('List'));
await screen.findByText('List view');
fireEvent.click(await screen.findByText('Show'));
await screen.findByText('Post #1 - 90 votes');
expect(authProvider.checkAuth).toHaveBeenCalledTimes(1);
expect(authProvider.canAccess).not.toHaveBeenCalled();
});
});
});