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