Files
expo-popcore-app/tests/hooks/use-template-generation-actions.test.ts
imeepos e1340fa101 feat: 实现 API 接口对接功能 (删除作品、作品搜索、修改密码)
按照 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>
2026-01-23 19:15:24 +08:00

360 lines
11 KiB
TypeScript

/**
* use-template-generation-actions.test.ts
*
* TDD 测试文件 - 测试模板生成记录的删除功能
*
* 测试覆盖:
* 1. 单个删除成功场景
* 2. 单个删除失败场景
* 3. 批量删除成功场景(可选)
* 4. 批量删除失败场景(可选)
*/
import { renderHook, act, waitFor } from '@testing-library/react-native'
import { root } from '@repo/core'
import { TemplateGenerationController } from '@repo/sdk'
import { useDeleteGeneration, useBatchDeleteGenerations } from '@/hooks/use-template-generation-actions'
// Mock SDK
jest.mock('@repo/core', () => ({
root: {
get: jest.fn(),
},
}))
describe('use-template-generation-actions', () => {
// 在每个测试前重置所有 mocks
beforeEach(() => {
jest.clearAllMocks()
})
describe('useDeleteGeneration', () => {
it('应该成功删除单个生成记录', async () => {
// Arrange: 准备测试数据
const mockDelete = jest.fn().mockResolvedValue({ message: '删除成功' })
const mockController = {
delete: mockDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act: 渲染 hook 并执行删除
const { result } = renderHook(() => useDeleteGeneration())
await act(async () => {
const response = await result.current.deleteGeneration('test-id-123')
// Assert: 验证返回值
expect(response).toEqual({
data: { message: '删除成功' },
error: null,
})
})
// Assert: 验证控制器被正确调用
expect(root.get).toHaveBeenCalledWith(TemplateGenerationController)
expect(mockDelete).toHaveBeenCalledWith({ id: 'test-id-123' })
expect(mockDelete).toHaveBeenCalledTimes(1)
})
it('删除失败时应该返回错误信息', async () => {
// Arrange: 准备测试数据和模拟错误
const mockError = {
message: '删除失败:记录不存在',
code: 'NOT_FOUND',
}
const mockDelete = jest.fn().mockRejectedValue(mockError)
const mockController = {
delete: mockDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act: 渲染 hook 并执行删除
const { result } = renderHook(() => useDeleteGeneration())
await act(async () => {
const response = await result.current.deleteGeneration('non-existent-id')
// Assert: 验证返回的错误信息
expect(response).toEqual({
data: null,
error: mockError,
})
})
// Assert: 验证控制器被调用
expect(mockDelete).toHaveBeenCalledWith({ id: 'non-existent-id' })
})
it('删除过程中应该设置正确的加载状态', async () => {
// Arrange: 准备异步测试
const mockDelete = jest.fn().mockImplementation(
() =>
new Promise((resolve) => {
setTimeout(() => resolve({ message: '删除成功' }), 100)
}),
)
const mockController = {
delete: mockDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act: 渲染 hook
const { result } = renderHook(() => useDeleteGeneration())
// Assert: 初始状态应该是未加载
expect(result.current.loading).toBe(false)
// Act: 开始删除
const deletePromise = act(async () => {
await result.current.deleteGeneration('test-id')
})
// Assert: 删除过程中应该是加载状态
await waitFor(() => {
expect(result.current.loading).toBe(true)
})
await deletePromise
// Assert: 删除完成后应该重置加载状态
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
})
it('删除失败后应该更新错误状态', async () => {
// Arrange
const mockError = {
message: '网络错误',
code: 'NETWORK_ERROR',
}
const mockDelete = jest.fn().mockRejectedValue(mockError)
const mockController = {
delete: mockDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act
const { result } = renderHook(() => useDeleteGeneration())
await act(async () => {
await result.current.deleteGeneration('test-id')
})
// Assert: 验证错误状态
expect(result.current.error).toEqual(mockError)
})
it('连续调用多次删除应该正确处理', async () => {
// Arrange
const mockDelete = jest.fn()
.mockResolvedValueOnce({ message: '第一次删除成功' })
.mockResolvedValueOnce({ message: '第二次删除成功' })
const mockController = {
delete: mockDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act
const { result } = renderHook(() => useDeleteGeneration())
await act(async () => {
const response1 = await result.current.deleteGeneration('id-1')
const response2 = await result.current.deleteGeneration('id-2')
// Assert
expect(response1.data?.message).toBe('第一次删除成功')
expect(response2.data?.message).toBe('第二次删除成功')
})
// Assert: 验证调用次数
expect(mockDelete).toHaveBeenCalledTimes(2)
expect(mockDelete).toHaveBeenNthCalledWith(1, { id: 'id-1' })
expect(mockDelete).toHaveBeenNthCalledWith(2, { id: 'id-2' })
})
})
describe('useBatchDeleteGenerations', () => {
it('应该成功批量删除生成记录', async () => {
// Arrange
const mockBatchDelete = jest.fn().mockResolvedValue({
message: '批量删除成功',
deletedCount: 3,
})
const mockController = {
batchDelete: mockBatchDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act
const { result } = renderHook(() => useBatchDeleteGenerations())
await act(async () => {
const response = await result.current.batchDeleteGenerations([
'id-1',
'id-2',
'id-3',
])
// Assert
expect(response).toEqual({
data: {
message: '批量删除成功',
deletedCount: 3,
},
error: null,
})
})
// Assert: 验证控制器被正确调用
expect(root.get).toHaveBeenCalledWith(TemplateGenerationController)
expect(mockBatchDelete).toHaveBeenCalledWith({
ids: ['id-1', 'id-2', 'id-3'],
})
})
it('批量删除失败时应该返回错误信息', async () => {
// Arrange
const mockError = {
message: '批量删除失败:部分记录不存在',
code: 'PARTIAL_FAILURE',
}
const mockBatchDelete = jest.fn().mockRejectedValue(mockError)
const mockController = {
batchDelete: mockBatchDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act
const { result } = renderHook(() => useBatchDeleteGenerations())
await act(async () => {
const response = await result.current.batchDeleteGenerations([
'id-1',
'id-2',
])
// Assert
expect(response).toEqual({
data: null,
error: mockError,
})
})
expect(mockBatchDelete).toHaveBeenCalledWith({
ids: ['id-1', 'id-2'],
})
})
it('批量删除时应该设置正确的加载状态', async () => {
// Arrange
const mockBatchDelete = jest.fn().mockImplementation(
() =>
new Promise((resolve) => {
setTimeout(() => resolve({ message: '批量删除成功' }), 100)
}),
)
const mockController = {
batchDelete: mockBatchDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act
const { result } = renderHook(() => useBatchDeleteGenerations())
expect(result.current.loading).toBe(false)
// Act
const deletePromise = act(async () => {
await result.current.batchDeleteGenerations(['id-1', 'id-2'])
})
// Assert: 删除过程中
await waitFor(() => {
expect(result.current.loading).toBe(true)
})
await deletePromise
// Assert: 删除完成后
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
})
it('传入空数组时应该正常处理', async () => {
// Arrange
const mockBatchDelete = jest.fn().mockResolvedValue({
message: '批量删除成功',
deletedCount: 0,
})
const mockController = {
batchDelete: mockBatchDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act
const { result } = renderHook(() => useBatchDeleteGenerations())
await act(async () => {
const response = await result.current.batchDeleteGenerations([])
// Assert
expect(response.data?.deletedCount).toBe(0)
})
expect(mockBatchDelete).toHaveBeenCalledWith({ ids: [] })
})
})
describe('边界情况', () => {
it('应该处理 ID 为空字符串的情况', async () => {
// Arrange
const mockDelete = jest.fn().mockResolvedValue({ message: '删除成功' })
const mockController = {
delete: mockDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act
const { result } = renderHook(() => useDeleteGeneration())
await act(async () => {
await result.current.deleteGeneration('')
})
// Assert: 即使是空字符串,也应该调用 API
expect(mockDelete).toHaveBeenCalledWith({ id: '' })
})
it('应该处理超长 ID 的情况', async () => {
// Arrange
const longId = 'a'.repeat(1000)
const mockDelete = jest.fn().mockResolvedValue({ message: '删除成功' })
const mockController = {
delete: mockDelete,
} as unknown as TemplateGenerationController
;(root.get as jest.Mock).mockReturnValue(mockController)
// Act
const { result } = renderHook(() => useDeleteGeneration())
await act(async () => {
await result.current.deleteGeneration(longId)
})
// Assert
expect(mockDelete).toHaveBeenCalledWith({ id: longId })
})
})
})