Public Access
106 lines
3.6 KiB
React
106 lines
3.6 KiB
React
// @vitest-environment jsdom
|
|
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
import {cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react';
|
|
import {MemoryRouter, Route, Routes, useLocation} from 'react-router-dom';
|
|
import LoginPage from './LoginPage.jsx';
|
|
import {fetchRoutes, json} from '../test/fetchRoutes.js';
|
|
|
|
vi.mock('react-i18next', () => import('../test/i18n-mock.js'));
|
|
|
|
function CurrentPath() {
|
|
const location = useLocation();
|
|
return <div data-testid="path">{location.pathname + location.search}</div>;
|
|
}
|
|
|
|
function renderLogin(state) {
|
|
render(
|
|
<MemoryRouter initialEntries={[{pathname: '/login', state}]}>
|
|
<Routes>
|
|
<Route path="/login" element={<LoginPage/>}/>
|
|
<Route path="*" element={<CurrentPath/>}/>
|
|
</Routes>
|
|
</MemoryRouter>,
|
|
);
|
|
}
|
|
|
|
// The page reads Retry-After from a refusal.
|
|
const refused = (status, code) => ({...json(status, {code}), headers: new Headers()});
|
|
|
|
function submit() {
|
|
fireEvent.change(screen.getByLabelText('LOGIN.EMAIL'), {target: {value: 'a@b.de'}});
|
|
fireEvent.change(screen.getByLabelText('LOGIN.PASSWORD'), {target: {value: 'secret'}});
|
|
fireEvent.click(screen.getByRole('button', {name: 'LOGIN.SUBMIT'}));
|
|
}
|
|
|
|
describe('LoginPage', () => {
|
|
beforeEach(() => {
|
|
fetchRoutes({'POST /api/auth/login': json(200, {message: 'Login successful'})});
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
localStorage.clear();
|
|
});
|
|
|
|
it('says that the session expired', () => {
|
|
renderLogin({sessionExpired: true, from: '/library'});
|
|
|
|
expect(screen.getByText('LOGIN.SESSION_EXPIRED')).toBeTruthy();
|
|
});
|
|
|
|
it('shows no message on a normal visit', () => {
|
|
renderLogin(undefined);
|
|
|
|
expect(screen.queryByText('LOGIN.SESSION_EXPIRED')).toBeNull();
|
|
});
|
|
|
|
it('goes back to the page of the expired session after login', async () => {
|
|
renderLogin({sessionExpired: true, from: '/library?status=PLAYING'});
|
|
|
|
submit();
|
|
|
|
await waitFor(() => expect(screen.getByTestId('path').textContent).toBe('/library?status=PLAYING'));
|
|
expect(localStorage.getItem('tbp_session')).not.toBeNull();
|
|
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/auth/login'), expect.objectContaining({
|
|
credentials: 'include',
|
|
headers: expect.objectContaining({'X-TBP-Client': 'web'}),
|
|
}));
|
|
});
|
|
|
|
it('ignores a return path outside the app', async () => {
|
|
renderLogin({sessionExpired: true, from: '//evil.example'});
|
|
|
|
submit();
|
|
|
|
await waitFor(() => expect(screen.getByTestId('path').textContent).toBe('/dashboard'));
|
|
});
|
|
|
|
it('hides the expired message once a login attempt fails', async () => {
|
|
fetchRoutes({'POST /api/auth/login': refused(401, 'INVALID_CREDENTIALS')});
|
|
renderLogin({sessionExpired: true, from: '/library'});
|
|
|
|
submit();
|
|
|
|
await waitFor(() => expect(screen.getByText('LOGIN.ERROR_INVALID_CREDENTIALS')).toBeTruthy());
|
|
expect(screen.queryByText('LOGIN.SESSION_EXPIRED')).toBeNull();
|
|
});
|
|
|
|
it('says that an account waits for the admin', async () => {
|
|
fetchRoutes({'POST /api/auth/login': refused(403, 'ACCOUNT_PENDING')});
|
|
renderLogin();
|
|
|
|
submit();
|
|
|
|
expect(await screen.findByText('LOGIN.ERROR_ACCOUNT_PENDING')).toBeTruthy();
|
|
});
|
|
|
|
it('says that a blocked account is blocked', async () => {
|
|
fetchRoutes({'POST /api/auth/login': refused(403, 'ACCOUNT_BLOCKED')});
|
|
renderLogin();
|
|
|
|
submit();
|
|
|
|
expect(await screen.findByText('LOGIN.ERROR_ACCOUNT_BLOCKED')).toBeTruthy();
|
|
});
|
|
});
|