import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { renderHook, act, waitFor } from '@testing-library/react' import { useTask } from './useTask' import { Task, TaskStatus, UpdateTaskRequest } from '~/types/task' import { apiClient } from '~/services/api' // Mock the API client vi.mock('~/services/api', () => ({ apiClient: { getTask: vi.fn(), updateTask: vi.fn(), deleteTask: vi.fn(), }, })) const mockApiClient = apiClient as { getTask: ReturnType updateTask: ReturnType deleteTask: ReturnType } // Sample task data const mockTask: Task = { id: '550e8400-e29b-41d4-a716-446655440000', title: 'Test Task', description: 'Test Description', status: TaskStatus.Todo, created_at: '2023-01-01T00:00:00Z', updated_at: '2023-01-01T00:00:00Z', completed_at: null, } describe('useTask', () => { beforeEach(() => { vi.clearAllMocks() }) afterEach(() => { vi.restoreAllMocks() }) it('should initialize with default state', () => { const { result } = renderHook(() => useTask()) expect(result.current.task).toBeNull() expect(result.current.loading).toBe(false) expect(result.current.error).toBeNull() }) describe('getTask', () => { it('should fetch task successfully', async () => { mockApiClient.getTask.mockResolvedValueOnce(mockTask) const { result } = renderHook(() => useTask()) act(() => { result.current.getTask(mockTask.id) }) expect(result.current.loading).toBe(true) await waitFor(() => { expect(result.current.loading).toBe(false) }) expect(result.current.task).toEqual(mockTask) expect(result.current.error).toBeNull() expect(mockApiClient.getTask).toHaveBeenCalledWith(mockTask.id) }) it('should handle fetch errors', async () => { const errorMessage = 'Task not found' mockApiClient.getTask.mockRejectedValueOnce({ message: errorMessage }) const { result } = renderHook(() => useTask()) act(() => { result.current.getTask(mockTask.id) }) await waitFor(() => { expect(result.current.loading).toBe(false) }) expect(result.current.task).toBeNull() expect(result.current.error).toBe(errorMessage) }) }) describe('updateTask', () => { it('should update task with optimistic update', async () => { const updateData: UpdateTaskRequest = { title: 'Updated Task', status: TaskStatus.Done, } const updatedTask = { ...mockTask, ...updateData } mockApiClient.getTask.mockResolvedValueOnce(mockTask) mockApiClient.updateTask.mockResolvedValueOnce(updatedTask) const { result } = renderHook(() => useTask()) // Set initial task await act(async () => { await result.current.getTask(mockTask.id) }) let updateResult: Task | null = null await act(async () => { updateResult = await result.current.updateTask(mockTask.id, updateData) }) expect(updateResult).toEqual(updatedTask) expect(mockApiClient.updateTask).toHaveBeenCalledWith( mockTask.id, updateData ) }) it('should handle update errors and revert optimistic update', async () => { const updateData: UpdateTaskRequest = { status: TaskStatus.Done } const errorMessage = 'Update failed' // Setup initial task mockApiClient.getTask.mockResolvedValueOnce(mockTask) const { result } = renderHook(() => useTask()) // Set initial task state await act(async () => { await result.current.getTask(mockTask.id) }) expect(result.current.task).toEqual(mockTask) // Mock update failure and revert call mockApiClient.updateTask.mockRejectedValueOnce({ message: errorMessage }) mockApiClient.getTask.mockResolvedValueOnce(mockTask) let updateResult: Task | null = null await act(async () => { updateResult = await result.current.updateTask(mockTask.id, updateData) }) expect(updateResult).toBeNull() expect(result.current.error).toBe(errorMessage) expect(result.current.loading).toBe(false) }) }) describe('deleteTask', () => { it('should delete task successfully', async () => { mockApiClient.deleteTask.mockResolvedValueOnce(undefined) const { result } = renderHook(() => useTask()) // Set initial task await act(async () => { mockApiClient.getTask.mockResolvedValueOnce(mockTask) await result.current.getTask(mockTask.id) }) await act(async () => { await result.current.deleteTask(mockTask.id) }) expect(result.current.task).toBeNull() expect(result.current.error).toBeNull() expect(mockApiClient.deleteTask).toHaveBeenCalledWith(mockTask.id) }) it('should handle delete errors', async () => { const errorMessage = 'Delete failed' mockApiClient.deleteTask.mockRejectedValueOnce({ message: errorMessage }) const { result } = renderHook(() => useTask()) await act(async () => { await result.current.deleteTask(mockTask.id) }) expect(result.current.error).toBe(errorMessage) expect(result.current.loading).toBe(false) }) }) describe('clearError', () => { it('should clear error state', async () => { mockApiClient.getTask.mockRejectedValueOnce({ message: 'Test error' }) const { result } = renderHook(() => useTask()) // Trigger an error await act(async () => { await result.current.getTask(mockTask.id) }) expect(result.current.error).toBeTruthy() // Clear the error act(() => { result.current.clearError() }) expect(result.current.error).toBeNull() }) }) })