import * as React from 'react';
import { useEffect } from 'react';
import expect from 'expect';
import { render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import useLogoutIfAccessDenied from './useLogoutIfAccessDenied';
import AuthContext from './AuthContext';
import useLogout from './useLogout';
import { useNotify } from '../notification/useNotify';
import { AuthProvider } from '../types';
import { useSafeSetState } from '../util';
let loggedIn = true;
const authProvider: AuthProvider = {
login: () => {
loggedIn = true;
return Promise.resolve();
},
logout: jest.fn(() => {
loggedIn = false;
return Promise.resolve();
}),
checkAuth: () =>
loggedIn ? Promise.resolve() : Promise.reject('bad method'),
checkError: params => {
if (params instanceof Error) {
return Promise.reject(
new Error(
params.message === 'denied' ? 'logout' : params.message
)
);
}
return Promise.resolve();
},
getPermissions: () => Promise.reject('bad method'),
};
const TestComponent = ({
error,
disableNotification,
}: {
error?: any;
disableNotification?: boolean;
}) => {
const [loggedOut, setLoggedOut] = useSafeSetState(false);
const logoutIfAccessDenied = useLogoutIfAccessDenied();
useEffect(() => {
logoutIfAccessDenied(error, disableNotification).then(setLoggedOut);
}, [error, disableNotification, logoutIfAccessDenied, setLoggedOut]);
return
{loggedOut ? '' : 'logged in'}
;
};
jest.mock('./useLogout');
jest.mock('../notification/useNotify');
//@ts-expect-error
useLogout.mockImplementation(() => {
const logout = () => authProvider.logout(null);
return logout;
});
const notify = jest.fn();
//@ts-expect-error
useNotify.mockImplementation(() => notify);
const TestWrapper = ({ children }) => (
{children}
);
describe('useLogoutIfAccessDenied', () => {
afterEach(() => {
//@ts-expect-error
authProvider.logout.mockClear();
notify.mockClear();
authProvider.login('');
});
it('should not log out if passed an error that does not make the authProvider throw', async () => {
render(
} />,
{
wrapper: TestWrapper,
}
);
await waitFor(() => {
expect(authProvider.logout).toHaveBeenCalledTimes(0);
expect(notify).toHaveBeenCalledTimes(0);
expect(screen.queryByText('logged in')).not.toBeNull();
});
});
it('should logout if passed an error that makes the authProvider throw', async () => {
render(
}
/>,
{
wrapper: TestWrapper,
}
);
await waitFor(() => {
expect(authProvider.logout).toHaveBeenCalledTimes(1);
expect(notify).toHaveBeenCalledTimes(1);
expect(screen.queryByText('logged in')).toBeNull();
});
});
it('should not send multiple notifications if already logged out', async () => {
render(
>
}
/>,
{
wrapper: TestWrapper,
}
);
await waitFor(() => {
expect(authProvider.logout).toHaveBeenCalledTimes(1);
expect(notify).toHaveBeenCalledTimes(1);
expect(screen.queryByText('logged in')).toBeNull();
});
});
it('should not send multiple notifications if the errors arrive with a delay', async () => {
let index = 0;
const delayedAuthProvider = {
...authProvider,
checkError: () =>
new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('foo')), index * 100);
index++; // answers immediately first, then after 100ms the second time
}),
};
render(
>
}
/>
);
await waitFor(() => {
expect(authProvider.logout).toHaveBeenCalledTimes(2); /// two logouts, but only one notification
});
expect(notify).toHaveBeenCalledTimes(1);
expect(screen.queryByText('logged in')).toBeNull();
});
it('should logout without showing a notification if disableAuthentication is true', async () => {
render(
}
/>,
{
wrapper: TestWrapper,
}
);
await waitFor(() => {
expect(authProvider.logout).toHaveBeenCalledTimes(1);
expect(notify).toHaveBeenCalledTimes(0);
expect(screen.queryByText('logged in')).toBeNull();
});
});
it('should logout without showing a notification if authProvider returns error with message false', async () => {
render(
{
return Promise.reject({ message: false });
},
}}
>
>
}
/>
);
await waitFor(() => {
expect(authProvider.logout).toHaveBeenCalledTimes(1);
expect(notify).toHaveBeenCalledTimes(0);
expect(screen.queryByText('logged in')).toBeNull();
});
});
it('should notify if passed an error with a message that makes the authProvider throw', async () => {
render(
}
/>,
{
wrapper: TestWrapper,
}
);
await waitFor(() => {
expect(authProvider.logout).toHaveBeenCalledTimes(1);
expect(notify).toHaveBeenCalledTimes(1);
expect(notify.mock.calls[0][0]).toEqual('Test message');
expect(screen.queryByText('logged in')).toBeNull();
});
});
it('should not logout the user if logoutUser is set to false', async () => {
render(
{
return Promise.reject({
logoutUser: false,
redirectTo: '/unauthorized',
});
},
}}
>
>
}
/>
unauthorized}
/>
);
await waitFor(() => {
expect(authProvider.logout).toHaveBeenCalledTimes(0);
expect(notify).toHaveBeenCalledTimes(1);
expect(screen.queryByText('logged in')).toBeNull();
expect(screen.queryByText('unauthorized')).not.toBeNull();
});
});
});