/** * 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 }) }) }) })