// @vitest-environment jsdom import {afterEach, describe, expect, it, vi} from 'vitest'; import {cleanup, fireEvent, render, screen} from '@testing-library/react'; import {MemoryRouter} from 'react-router-dom'; import ResetPasswordPage from './ResetPasswordPage.jsx'; import {fetchRoutes, json} from '../test/fetchRoutes.js'; vi.mock('react-i18next', () => import('../test/i18n-mock.js')); const renderPage = (hash = '') => render( , ); function submitPasswords(password, repeat = password) { fireEvent.change(screen.getByLabelText('REGISTRATION.PASSWORD'), {target: {value: password}}); fireEvent.change(screen.getByLabelText('REGISTRATION.PASSWORD_CONFIRMATION'), {target: {value: repeat}}); fireEvent.click(screen.getByRole('button', {name: 'RESET_PASSWORD.SUBMIT'})); } function submitEmail(email = 'a@b.de') { fireEvent.change(screen.getByLabelText('RESET_PASSWORD.EMAIL'), {target: {value: email}}); fireEvent.click(screen.getByRole('button', {name: 'RESET_PASSWORD.SEND'})); } describe('ResetPasswordPage', () => { afterEach(cleanup); it('without a fragment asks for the email and shows the same text for any address', async () => { const mock = fetchRoutes({'POST /api/auth/password-reset': json(204, null)}); renderPage(); submitEmail(); expect(await screen.findByText('RESET_PASSWORD.SENT')).toBeTruthy(); expect(JSON.parse(mock.mock.calls[0][1].body)).toEqual({email: 'a@b.de'}); }); it('shows the lock text with the minutes on a 429', async () => { fetchRoutes({'POST /api/auth/password-reset': {...json(429, {}), headers: new Headers({'Retry-After': '900'})}}); renderPage(); submitEmail(); expect(await screen.findByText('RESET_PASSWORD.ERROR_TOO_MANY')).toBeTruthy(); }); it('with a fragment sends the token and the new password only on the click and then offers the login', async () => { const mock = fetchRoutes({'POST /api/auth/password-reset/confirm': json(204, null)}); renderPage('#tok-123'); expect(mock).not.toHaveBeenCalled(); submitPasswords('secret123'); expect(await screen.findByText('RESET_PASSWORD.DONE')).toBeTruthy(); expect(JSON.parse(mock.mock.calls[0][1].body)).toEqual({token: 'tok-123', password: 'secret123'}); expect(screen.getByRole('link', {name: 'RESET_PASSWORD.TO_LOGIN'}).getAttribute('href')).toBe('/login'); }); it.each([ ['a short password', 'short', 'short', 'VALIDATION.PASSWORD_TOO_SHORT'], ['a different repeat', 'secret123', 'secret124', 'VALIDATION.PASSWORD_MISMATCH'], ])('sends nothing for %s', async (_name, password, repeat, message) => { const mock = fetchRoutes({}); renderPage('#tok-123'); submitPasswords(password, repeat); expect(await screen.findByText(message)).toBeTruthy(); expect(mock).not.toHaveBeenCalled(); }); it('shows the invalid text and the email form again for a used or expired link', async () => { fetchRoutes({'POST /api/auth/password-reset/confirm': json(410, {code: 'RESET_INVALID'})}); renderPage('#old'); submitPasswords('secret123'); expect(await screen.findByText('RESET_PASSWORD.INVALID')).toBeTruthy(); expect(screen.getByRole('button', {name: 'RESET_PASSWORD.SEND'})).toBeTruthy(); }); it('shows the lock text with the minutes when the confirm is limited', async () => { fetchRoutes({ 'POST /api/auth/password-reset/confirm': {...json(429, {}), headers: new Headers({'Retry-After': '900'})}, }); renderPage('#tok'); submitPasswords('secret123'); expect(await screen.findByText('RESET_PASSWORD.ERROR_TOO_MANY')).toBeTruthy(); }); });