// @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
{location.pathname + location.search}
;
}
function renderLogin(state) {
render(
}/>
}/>
,
);
}
// 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 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();
});
});