Files
flyer-crawler.projectium.com/src/pages/ResetPasswordPage.test.tsx
Torben Sorensen 9fd15f3a50
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 19m58s
unit test auto-provider refactor
2026-01-02 11:33:11 -08:00

189 lines
7.0 KiB
TypeScript

// src/pages/ResetPasswordPage.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { ResetPasswordPage } from './ResetPasswordPage';
import * as apiClient from '../services/apiClient';
import { logger } from '../services/logger.client';
// The apiClient and logger are now mocked globally.
const mockedApiClient = vi.mocked(apiClient);
// The logger is mocked globally.
// Helper function to render the component within a router context
const renderWithRouter = (token: string) => {
return render(
<MemoryRouter initialEntries={[`/reset-password/${token}`]}>
<Routes>
<Route path="/reset-password/:token" element={<ResetPasswordPage />} />
<Route path="/" element={<div>Home Page</div>} />
</Routes>
</MemoryRouter>,
);
};
describe('ResetPasswordPage', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.useRealTimers();
});
it('should render the form with password fields and a submit button', () => {
renderWithRouter('test-token-123');
expect(screen.getByRole('heading', { name: /set a new password/i })).toBeInTheDocument();
expect(screen.getByPlaceholderText('New Password')).toBeInTheDocument();
expect(screen.getByPlaceholderText('Confirm New Password')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /reset password/i })).toBeInTheDocument();
});
it('should call resetPassword and show success message on valid submission', async () => {
const setTimeoutSpy = vi.spyOn(global, 'setTimeout');
mockedApiClient.resetPassword.mockResolvedValue(
new Response(JSON.stringify({ message: 'Password reset was successful!' })),
);
const token = 'valid-token';
renderWithRouter(token);
fireEvent.change(screen.getByPlaceholderText('New Password'), {
target: { value: 'newSecurePassword123' },
});
fireEvent.change(screen.getByPlaceholderText('Confirm New Password'), {
target: { value: 'newSecurePassword123' },
});
fireEvent.click(screen.getByRole('button', { name: /reset password/i }));
await waitFor(() => {
expect(mockedApiClient.resetPassword).toHaveBeenCalledWith(token, 'newSecurePassword123');
expect(screen.getByText(/password reset was successful!/i)).toBeInTheDocument();
expect(screen.getByText(/return to home/i)).toBeInTheDocument();
});
expect(logger.info).toHaveBeenCalledWith('Password has been successfully reset.');
// Check that form is cleared
expect(screen.queryByPlaceholderText('New Password')).not.toBeInTheDocument();
// Verify redirect timeout was set
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 4000);
setTimeoutSpy.mockRestore();
});
it('should show an error message if passwords do not match', async () => {
renderWithRouter('test-token');
fireEvent.change(screen.getByPlaceholderText('New Password'), {
target: { value: 'passwordA' },
});
fireEvent.change(screen.getByPlaceholderText('Confirm New Password'), {
target: { value: 'passwordB' },
});
fireEvent.click(screen.getByRole('button', { name: /reset password/i }));
await waitFor(() => {
expect(screen.getByText('Passwords do not match.')).toBeInTheDocument();
});
expect(mockedApiClient.resetPassword).not.toHaveBeenCalled();
});
it('should show an error message if the API call fails', async () => {
mockedApiClient.resetPassword.mockRejectedValueOnce(new Error('Invalid or expired token.'));
renderWithRouter('invalid-token');
fireEvent.change(screen.getByPlaceholderText('New Password'), {
target: { value: 'newSecurePassword123' },
});
fireEvent.change(screen.getByPlaceholderText('Confirm New Password'), {
target: { value: 'newSecurePassword123' },
});
fireEvent.click(screen.getByRole('button', { name: /reset password/i }));
await waitFor(() => {
expect(screen.getByText('Invalid or expired token.')).toBeInTheDocument();
});
expect(logger.error).toHaveBeenCalledWith(
{ err: expect.any(Error) },
'Failed to reset password.',
);
});
it('should show a loading spinner while submitting', async () => {
let resolvePromise: (value: Response) => void;
const mockPromise = new Promise<Response>((resolve) => {
resolvePromise = resolve;
});
mockedApiClient.resetPassword.mockReturnValue(mockPromise);
renderWithRouter('test-token');
fireEvent.change(screen.getByPlaceholderText('New Password'), {
target: { value: 'newSecurePassword123' },
});
fireEvent.change(screen.getByPlaceholderText('Confirm New Password'), {
target: { value: 'newSecurePassword123' },
});
fireEvent.click(screen.getByRole('button', { name: /reset password/i }));
// Expect button to be disabled and text to be gone (replaced by spinner)
// We use the accessible name which persists via aria-label even when text content is replaced
expect(screen.getByRole('button', { name: /reset password/i })).toBeDisabled();
expect(screen.queryByText('Reset Password')).not.toBeInTheDocument();
await act(async () => {
resolvePromise!(new Response(JSON.stringify({ message: 'Password reset was successful!' })));
});
await waitFor(() => {
expect(screen.getByText(/password reset was successful!/i)).toBeInTheDocument();
});
});
it('should show an error if no token is provided', async () => {
render(
<MemoryRouter initialEntries={['/reset-password']}>
<Routes>
<Route path="/reset-password" element={<ResetPasswordPage />} />
</Routes>
</MemoryRouter>,
);
// Fill in required fields to trigger form submission
fireEvent.change(screen.getByPlaceholderText('New Password'), {
target: { value: 'password123' },
});
fireEvent.change(screen.getByPlaceholderText('Confirm New Password'), {
target: { value: 'password123' },
});
fireEvent.click(screen.getByRole('button', { name: /reset password/i }));
await waitFor(() => {
expect(
screen.getByText('No reset token provided. Please use the link from your email.'),
).toBeInTheDocument();
});
});
it('should handle unknown errors', async () => {
mockedApiClient.resetPassword.mockRejectedValue('Unknown error');
renderWithRouter('test-token');
fireEvent.change(screen.getByPlaceholderText('New Password'), {
target: { value: 'newSecurePassword123' },
});
fireEvent.change(screen.getByPlaceholderText('Confirm New Password'), {
target: { value: 'newSecurePassword123' },
});
fireEvent.click(screen.getByRole('button', { name: /reset password/i }));
await waitFor(() => {
expect(screen.getByText('An unknown error occurred.')).toBeInTheDocument();
});
expect(logger.error).toHaveBeenCalledWith(
{ err: 'Unknown error' },
'Failed to reset password.',
);
});
});