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(); }); }); });