Files
syndarix/frontend/tests/components/settings/PasswordChangeForm.test.tsx
Felipe Cardoso b2f3ec8f25 Refactor ESLint configuration and update test rules for clarity and consistency
- Consolidated and modularized `eslint.config.mjs` with defined rules for source, test, E2E, and scripts.
- Improved test and E2E rules with relaxed settings for flexibility and enhanced mocking.
- Standardized variable naming and removed redundant imports in unit and E2E tests.
- Updated error handling and comments to align with modern TypeScript best practices (e.g., `@ts-expect-error`).
2025-11-10 10:57:43 +01:00

169 lines
5.8 KiB
TypeScript

/**
* Tests for PasswordChangeForm Component
*/
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { PasswordChangeForm } from '@/components/settings/PasswordChangeForm';
import * as useAuthModule from '@/lib/api/hooks/useAuth';
import { toast } from 'sonner';
jest.mock('@/lib/api/hooks/useAuth');
jest.mock('sonner', () => ({ toast: { success: jest.fn(), error: jest.fn() } }));
const mockUsePasswordChange = useAuthModule.usePasswordChange as jest.Mock;
const mockToast = toast as jest.Mocked<typeof toast>;
describe('PasswordChangeForm', () => {
let queryClient: QueryClient;
let user: ReturnType<typeof userEvent.setup>;
const mockMutateAsync = jest.fn();
beforeEach(() => {
queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
user = userEvent.setup();
jest.clearAllMocks();
mockUsePasswordChange.mockReturnValue({
mutateAsync: mockMutateAsync,
isPending: false,
isError: false,
isSuccess: false,
error: null,
});
});
const renderWithProvider = (component: React.ReactElement) =>
render(<QueryClientProvider client={queryClient}>{component}</QueryClientProvider>);
describe('Rendering', () => {
it('renders all password fields', () => {
renderWithProvider(<PasswordChangeForm />);
expect(screen.getByLabelText(/current password/i)).toBeInTheDocument();
expect(screen.getByLabelText(/^new password/i)).toBeInTheDocument();
expect(screen.getByLabelText(/confirm new password/i)).toBeInTheDocument();
});
it('renders change password button', () => {
renderWithProvider(<PasswordChangeForm />);
expect(screen.getByRole('button', { name: /change password/i })).toBeInTheDocument();
});
it('shows password strength requirements', () => {
renderWithProvider(<PasswordChangeForm />);
expect(screen.getByText(/at least 8 characters/i)).toBeInTheDocument();
});
it('uses usePasswordChange hook', () => {
renderWithProvider(<PasswordChangeForm />);
expect(mockUsePasswordChange).toHaveBeenCalled();
});
});
describe('Form State', () => {
it('disables submit when pristine', () => {
renderWithProvider(<PasswordChangeForm />);
expect(screen.getByRole('button', { name: /change password/i })).toBeDisabled();
});
it('disables inputs while submitting', () => {
mockUsePasswordChange.mockReturnValue({
mutateAsync: mockMutateAsync,
isPending: true,
isError: false,
isSuccess: false,
error: null,
});
renderWithProvider(<PasswordChangeForm />);
expect(screen.getByLabelText(/current password/i)).toBeDisabled();
expect(screen.getByLabelText(/^new password/i)).toBeDisabled();
expect(screen.getByLabelText(/confirm new password/i)).toBeDisabled();
});
it('shows loading text while submitting', () => {
mockUsePasswordChange.mockReturnValue({
mutateAsync: mockMutateAsync,
isPending: true,
isError: false,
isSuccess: false,
error: null,
});
renderWithProvider(<PasswordChangeForm />);
expect(screen.getByText(/changing password/i)).toBeInTheDocument();
});
});
describe('User Interactions', () => {
it('allows typing in current password field', async () => {
renderWithProvider(<PasswordChangeForm />);
const currentPasswordInput = screen.getByLabelText(/current password/i) as HTMLInputElement;
await user.type(currentPasswordInput, 'OldPassword123!');
expect(currentPasswordInput.value).toBe('OldPassword123!');
});
it('allows typing in new password field', async () => {
renderWithProvider(<PasswordChangeForm />);
const newPasswordInput = screen.getByLabelText(/^new password/i) as HTMLInputElement;
await user.type(newPasswordInput, 'NewPassword123!');
expect(newPasswordInput.value).toBe('NewPassword123!');
});
it('allows typing in confirm password field', async () => {
renderWithProvider(<PasswordChangeForm />);
const confirmPasswordInput = screen.getByLabelText(/confirm new password/i) as HTMLInputElement;
await user.type(confirmPasswordInput, 'NewPassword123!');
expect(confirmPasswordInput.value).toBe('NewPassword123!');
});
});
describe('Form Submission - Success', () => {
it('calls mutateAsync with correct data on successful submission', async () => {
mockMutateAsync.mockResolvedValueOnce({});
renderWithProvider(<PasswordChangeForm />);
// Simulate the hook callback being triggered (success path)
const hookCallback = mockUsePasswordChange.mock.calls[0][0];
// Trigger the callback as if mutation succeeded
hookCallback('Password changed successfully');
expect(mockToast.success).toHaveBeenCalledWith('Password changed successfully');
});
it('calls onSuccess callback after successful password change', async () => {
const onSuccess = jest.fn();
mockMutateAsync.mockResolvedValueOnce({});
renderWithProvider(<PasswordChangeForm onSuccess={onSuccess} />);
// Simulate successful password change through hook callback
const hookCallback = mockUsePasswordChange.mock.calls[0][0];
hookCallback('Password changed successfully');
expect(onSuccess).toHaveBeenCalled();
});
it('shows success toast with custom message', async () => {
renderWithProvider(<PasswordChangeForm />);
const hookCallback = mockUsePasswordChange.mock.calls[0][0];
hookCallback('Your password has been updated');
expect(mockToast.success).toHaveBeenCalledWith('Your password has been updated');
});
});
});