122 lines
4.8 KiB
TypeScript
122 lines
4.8 KiB
TypeScript
import { MemoryRouter, Route, Routes } from 'react-router-dom'
|
|
import { render, screen, waitFor } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { message } from 'antd'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { ResetPasswordPage } from './ResetPasswordPage'
|
|
|
|
const validateResetTokenMock = vi.fn()
|
|
const resetPasswordMock = vi.fn()
|
|
|
|
vi.mock('@/services/auth', () => ({
|
|
validateResetToken: (token: string) => validateResetTokenMock(token),
|
|
resetPassword: (payload: unknown) => resetPasswordMock(payload),
|
|
}))
|
|
|
|
function renderResetPasswordPage(initialEntry: string) {
|
|
return render(
|
|
<MemoryRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }} initialEntries={[initialEntry]}>
|
|
<Routes>
|
|
<Route path="/reset-password" element={<ResetPasswordPage />} />
|
|
</Routes>
|
|
</MemoryRouter>,
|
|
)
|
|
}
|
|
|
|
describe('ResetPasswordPage', () => {
|
|
beforeEach(() => {
|
|
validateResetTokenMock.mockReset()
|
|
resetPasswordMock.mockReset()
|
|
|
|
validateResetTokenMock.mockResolvedValue({
|
|
valid: true,
|
|
email: 'user@example.com',
|
|
expires_at: '2026-03-28T12:00:00Z',
|
|
})
|
|
resetPasswordMock.mockResolvedValue(undefined)
|
|
|
|
vi.spyOn(message, 'success').mockImplementation(() => undefined as never)
|
|
vi.spyOn(message, 'error').mockImplementation(() => undefined as never)
|
|
})
|
|
|
|
it('shows an invalid-link state when the reset token is missing', async () => {
|
|
renderResetPasswordPage('/reset-password')
|
|
|
|
expect(await screen.findByText('链接无效')).toBeInTheDocument()
|
|
expect(validateResetTokenMock).not.toHaveBeenCalled()
|
|
expect(screen.getByRole('button', { name: '重新申请' })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: '返回登录' })).toBeInTheDocument()
|
|
})
|
|
|
|
it('shows an invalid-link state when token validation fails', async () => {
|
|
validateResetTokenMock.mockRejectedValueOnce(new Error('token invalid'))
|
|
|
|
renderResetPasswordPage('/reset-password?token=expired-token')
|
|
|
|
await waitFor(() => expect(validateResetTokenMock).toHaveBeenCalledWith('expired-token'))
|
|
|
|
expect(await screen.findByText('链接无效')).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: '重新申请' })).toBeInTheDocument()
|
|
})
|
|
|
|
it('renders the reset form after token validation succeeds', async () => {
|
|
renderResetPasswordPage('/reset-password?token=token-123')
|
|
|
|
await waitFor(() => expect(validateResetTokenMock).toHaveBeenCalledWith('token-123'))
|
|
|
|
expect(await screen.findByRole('heading', { name: '重置密码' })).toBeInTheDocument()
|
|
expect(screen.getByPlaceholderText('新密码')).toBeInTheDocument()
|
|
expect(screen.getByPlaceholderText('确认新密码')).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: '确认重置' })).toBeInTheDocument()
|
|
expect(screen.getByText('user@example.com')).toBeInTheDocument()
|
|
})
|
|
|
|
it('submits the new password and shows the success state', async () => {
|
|
const user = userEvent.setup()
|
|
const { container } = renderResetPasswordPage('/reset-password?token=token-123')
|
|
|
|
await waitFor(() => expect(validateResetTokenMock).toHaveBeenCalledWith('token-123'))
|
|
await waitFor(() => expect(container.querySelectorAll('input[type="password"]')).toHaveLength(2))
|
|
|
|
const passwordInputs = Array.from(
|
|
container.querySelectorAll('input[type="password"]'),
|
|
) as HTMLInputElement[]
|
|
|
|
await user.type(passwordInputs[0], 'NewPass123!')
|
|
await user.type(passwordInputs[1], 'NewPass123!')
|
|
await user.click(screen.getByRole('button', { name: '确认重置' }))
|
|
|
|
await waitFor(() =>
|
|
expect(resetPasswordMock).toHaveBeenCalledWith({
|
|
token: 'token-123',
|
|
new_password: 'NewPass123!',
|
|
confirm_password: 'NewPass123!',
|
|
}),
|
|
)
|
|
|
|
expect(await screen.findByText('密码已重置')).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: '立即登录' })).toBeInTheDocument()
|
|
expect(message.success).toHaveBeenCalledWith('密码重置成功')
|
|
})
|
|
|
|
it('surfaces backend failures when resetting the password', async () => {
|
|
const user = userEvent.setup()
|
|
const { container } = renderResetPasswordPage('/reset-password?token=token-123')
|
|
resetPasswordMock.mockRejectedValueOnce(new Error('reset failed'))
|
|
|
|
await waitFor(() => expect(validateResetTokenMock).toHaveBeenCalledWith('token-123'))
|
|
await waitFor(() => expect(container.querySelectorAll('input[type="password"]')).toHaveLength(2))
|
|
|
|
const passwordInputs = Array.from(
|
|
container.querySelectorAll('input[type="password"]'),
|
|
) as HTMLInputElement[]
|
|
|
|
await user.type(passwordInputs[0], 'NewPass123!')
|
|
await user.type(passwordInputs[1], 'NewPass123!')
|
|
await user.click(screen.getByRole('button', { name: '确认重置' }))
|
|
|
|
await waitFor(() => expect(message.error).toHaveBeenCalledWith('reset failed'))
|
|
})
|
|
})
|