import { fireEvent, render, screen, waitFor } from '@testing-library/react' import useDocumentTitle from '@/hooks/use-document-title' import { changePasswordWithToken } from '@/service/common' import { useVerifyForgotPasswordToken } from '@/service/use-common' import ChangePasswordForm from './ChangePasswordForm' const mockReplace = vi.fn() vi.mock('@/next/navigation', () => ({ useSearchParams: () => new URLSearchParams('token=url-token-t1'), useRouter: () => ({ replace: mockReplace }), })) vi.mock('@/service/use-common', () => ({ useVerifyForgotPasswordToken: vi.fn(), })) vi.mock('@/service/common', () => ({ changePasswordWithToken: vi.fn(), })) vi.mock('@/hooks/use-document-title', () => ({ __esModule: true, default: vi.fn(), })) vi.mock('@/utils/var', () => ({ basePath: '' })) type UseVerifyResult = ReturnType const mockUseVerify = vi.mocked(useVerifyForgotPasswordToken) const mockChangePassword = vi.mocked(changePasswordWithToken) const mockUseDocumentTitle = vi.mocked(useDocumentTitle) const VALID_PASSWORD = 'ValidPass123!' describe('ChangePasswordForm', () => { beforeEach(() => { vi.clearAllMocks() }) it('uses the loading title while the token is being verified', () => { mockUseVerify.mockReturnValue({ data: undefined, refetch: vi.fn(), } as unknown as UseVerifyResult) render() expect(mockUseDocumentTitle).toHaveBeenLastCalledWith('common.loading') }) describe('when token is valid', () => { const T2 = 'verified-token-t2' beforeEach(() => { mockUseVerify.mockReturnValue({ data: { result: 'success', is_valid: true, email: 'user@example.com', token: T2 }, refetch: vi.fn(), } as unknown as UseVerifyResult) }) it('renders the password form', () => { render() expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('login.changePassword') expect(mockUseDocumentTitle).toHaveBeenLastCalledWith('login.changePassword') }) it('submits with T2 (from validity response), NOT T1 (from URL)', async () => { mockChangePassword.mockResolvedValue({ result: 'success' }) render() const inputs = Array.from( document.querySelectorAll('input[type="password"]'), ) as [HTMLInputElement, HTMLInputElement] fireEvent.change(inputs[0], { target: { value: VALID_PASSWORD } }) fireEvent.change(inputs[1], { target: { value: VALID_PASSWORD } }) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.reset/ })) await waitFor(() => { expect(mockChangePassword).toHaveBeenCalledWith({ url: '/forgot-password/resets', body: { token: T2, new_password: VALID_PASSWORD, password_confirm: VALID_PASSWORD, }, }) }) }) it('uses the success title after the password is changed', async () => { mockChangePassword.mockResolvedValue({ result: 'success' }) render() const inputs = Array.from( document.querySelectorAll('input[type="password"]'), ) as [HTMLInputElement, HTMLInputElement] fireEvent.change(inputs[0], { target: { value: VALID_PASSWORD } }) fireEvent.change(inputs[1], { target: { value: VALID_PASSWORD } }) fireEvent.click(screen.getByRole('button', { name: /common\.operation\.reset/ })) expect( await screen.findByRole('heading', { level: 1, name: 'login.passwordChangedTip' }), ).toBeInTheDocument() expect(mockUseDocumentTitle).toHaveBeenLastCalledWith('login.passwordChangedTip') }) }) describe('when token is invalid', () => { beforeEach(() => { mockUseVerify.mockReturnValue({ data: { result: 'success', is_valid: false, email: '', token: '' }, refetch: vi.fn(), } as unknown as UseVerifyResult) }) it('shows invalid token state and no form', () => { render() expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('login.invalid') expect(mockUseDocumentTitle).toHaveBeenLastCalledWith('login.invalid') expect( screen.queryByRole('button', { name: /common\.operation\.reset/ }), ).not.toBeInTheDocument() }) }) })