按照 TDD 规范完成三个核心功能的接口对接: ## 新增功能 ### 1. 删除作品功能 (app/generationRecord.tsx) - 新增 use-template-generation-actions.ts hook - 支持单个删除和批量删除作品 - 删除确认对话框 - 删除成功后自动刷新列表 - 完整的错误处理和加载状态 ### 2. 作品搜索功能 (app/searchWorksResults.tsx) - 新增 use-works-search.ts hook - 替换模拟数据为真实 SDK 接口 - 支持关键词搜索和分类筛选 - 支持分页加载 - 完整的加载、错误、空结果状态处理 ### 3. 修改密码功能 (app/changePassword.tsx) - 新增 use-change-password.ts hook - 使用 Better Auth 的 changePassword API - 客户端表单验证(密码长度、确认密码匹配等) - 成功后自动返回并提示 ## 技术实现 - 严格遵循 TDD 规范(先写测试,后写实现) - 新增 3 个 hooks 和对应的单元测试 - 更新中英文翻译文件 - 更新 jest.setup.js 添加必要的 mock ## 文档 - 新增 api_integration_report.md - API 对接分析报告 - 新增 api_integration_development_plan.md - 开发计划和完成汇总 Co-Authored-By: Claude <noreply@anthropic.com>
157 lines
4.3 KiB
TypeScript
157 lines
4.3 KiB
TypeScript
import { renderHook, act, waitFor } from '@testing-library/react-native'
|
|
import { useChangePassword } from '@/hooks/use-change-password'
|
|
import { authClient } from '@/lib/auth'
|
|
|
|
// Mock authClient
|
|
jest.mock('@/lib/auth', () => ({
|
|
authClient: {
|
|
changePassword: jest.fn(),
|
|
},
|
|
}))
|
|
|
|
describe('useChangePassword', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks()
|
|
})
|
|
|
|
it('should change password successfully', async () => {
|
|
const mockChangePassword = authClient.changePassword as jest.MockedFunction<typeof authClient.changePassword>
|
|
mockChangePassword.mockResolvedValue({ error: null })
|
|
|
|
const { result } = renderHook(() => useChangePassword())
|
|
|
|
await act(async () => {
|
|
await result.current.changePassword({
|
|
oldPassword: 'oldPass123',
|
|
newPassword: 'newPass123',
|
|
})
|
|
})
|
|
|
|
expect(mockChangePassword).toHaveBeenCalledWith({
|
|
oldPassword: 'oldPass123',
|
|
newPassword: 'newPass123',
|
|
revokeOtherSessions: true,
|
|
})
|
|
expect(result.current.error).toBeNull()
|
|
})
|
|
|
|
it('should validate old password is not empty', async () => {
|
|
const { result } = renderHook(() => useChangePassword())
|
|
|
|
await act(async () => {
|
|
await result.current.changePassword({
|
|
oldPassword: '',
|
|
newPassword: 'newPass123',
|
|
})
|
|
})
|
|
|
|
expect(result.current.error).toEqual({
|
|
message: '旧密码不能为空',
|
|
})
|
|
expect(authClient.changePassword).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should validate new password and confirm password match', async () => {
|
|
const { result } = renderHook(() => useChangePassword())
|
|
|
|
await act(async () => {
|
|
await result.current.changePassword({
|
|
oldPassword: 'oldPass123',
|
|
newPassword: 'newPass123',
|
|
confirmPassword: 'differentPass123',
|
|
})
|
|
})
|
|
|
|
expect(result.current.error).toEqual({
|
|
message: '新密码和确认密码不一致',
|
|
})
|
|
expect(authClient.changePassword).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should validate new password length (minimum 6 characters)', async () => {
|
|
const { result } = renderHook(() => useChangePassword())
|
|
|
|
await act(async () => {
|
|
await result.current.changePassword({
|
|
oldPassword: 'oldPass123',
|
|
newPassword: '12345',
|
|
})
|
|
})
|
|
|
|
expect(result.current.error).toEqual({
|
|
message: '新密码长度至少为6位',
|
|
})
|
|
expect(authClient.changePassword).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should validate new password is different from old password', async () => {
|
|
const { result } = renderHook(() => useChangePassword())
|
|
|
|
await act(async () => {
|
|
await result.current.changePassword({
|
|
oldPassword: 'samePass123',
|
|
newPassword: 'samePass123',
|
|
})
|
|
})
|
|
|
|
expect(result.current.error).toEqual({
|
|
message: '新密码不能与当前密码相同',
|
|
})
|
|
expect(authClient.changePassword).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should handle API errors', async () => {
|
|
const mockError = {
|
|
status: 400,
|
|
statusText: 'Bad Request',
|
|
message: '旧密码错误',
|
|
}
|
|
|
|
const mockChangePassword = authClient.changePassword as jest.MockedFunction<typeof authClient.changePassword>
|
|
mockChangePassword.mockRejectedValue(mockError)
|
|
|
|
const { result } = renderHook(() => useChangePassword())
|
|
|
|
await act(async () => {
|
|
await result.current.changePassword({
|
|
oldPassword: 'wrongPass',
|
|
newPassword: 'newPass123',
|
|
})
|
|
})
|
|
|
|
expect(result.current.error).toEqual(mockError)
|
|
})
|
|
|
|
it('should set loading state during password change', async () => {
|
|
let resolveChangePassword: (value: any) => void
|
|
const changePasswordPromise = new Promise((resolve) => {
|
|
resolveChangePassword = resolve
|
|
})
|
|
|
|
const mockChangePassword = authClient.changePassword as jest.MockedFunction<typeof authClient.changePassword>
|
|
mockChangePassword.mockReturnValue(changePasswordPromise)
|
|
|
|
const { result } = renderHook(() => useChangePassword())
|
|
|
|
act(() => {
|
|
result.current.changePassword({
|
|
oldPassword: 'oldPass123',
|
|
newPassword: 'newPass123',
|
|
})
|
|
})
|
|
|
|
expect(result.current.loading).toBe(true)
|
|
|
|
await act(async () => {
|
|
resolveChangePassword!({ error: null })
|
|
await changePasswordPromise
|
|
})
|
|
|
|
expect(result.current.loading).toBe(false)
|
|
})
|
|
})
|