/** * 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; describe('PasswordChangeForm', () => { let queryClient: QueryClient; let user: ReturnType; 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({component}); describe('Rendering', () => { it('renders all password fields', () => { renderWithProvider(); 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(); expect(screen.getByRole('button', { name: /change password/i })).toBeInTheDocument(); }); it('shows password strength requirements', () => { renderWithProvider(); expect(screen.getByText(/at least 8 characters/i)).toBeInTheDocument(); }); it('uses usePasswordChange hook', () => { renderWithProvider(); expect(mockUsePasswordChange).toHaveBeenCalled(); }); }); describe('Form State', () => { it('disables submit when pristine', () => { renderWithProvider(); 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(); 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(); expect(screen.getByText(/changing password/i)).toBeInTheDocument(); }); }); describe('User Interactions', () => { it('allows typing in current password field', async () => { renderWithProvider(); 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(); 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(); 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(); // 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(); // 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(); const hookCallback = mockUsePasswordChange.mock.calls[0][0]; hookCallback('Your password has been updated'); expect(mockToast.success).toHaveBeenCalledWith('Your password has been updated'); }); }); });