import { describe, it, expect, beforeEach, vi } from 'vitest' import { renderHook, act } from '@testing-library/react' import { usePlannedDays } from './usePlannedDays' import * as api from '@/services/api' import { makePlannedLeaveDay } from '../../tests/fixtures' vi.mock('@/services/api') describe('usePlannedDays', () => { beforeEach(() => { vi.clearAllMocks() }) it('initializes with null data', () => { const { result } = renderHook(() => usePlannedDays()) expect(result.current.data).toBeNull() expect(result.current.isLoading).toBe(false) }) it('fetches planned days when childId provided', async () => { const mockData = [ makePlannedLeaveDay({ id: '1', workspaceId: 'ws1', childId: 'child1', parentPersonalDataUuid: 'parent1', date: '2026-06-15', dayType: 'vacation', isDubbeldag: false, }), ] vi.mocked(api.getPlannedDays).mockResolvedValue(mockData) renderHook(() => usePlannedDays('child1')) await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)) }) expect(api.getPlannedDays).toHaveBeenCalledWith('child1') }) it('does not fetch without childId', () => { vi.mocked(api.getPlannedDays).mockResolvedValue([]) renderHook(() => usePlannedDays()) expect(api.getPlannedDays).not.toHaveBeenCalled() }) it('adds planned day and refreshes', async () => { const mockAdded = makePlannedLeaveDay({ id: '2', workspaceId: 'ws1', childId: 'child1', parentPersonalDataUuid: 'parent1', date: '2026-06-16', dayType: 'vacation', isDubbeldag: false, }) vi.mocked(api.createPlannedDay).mockResolvedValue(mockAdded) vi.mocked(api.getPlannedDays).mockResolvedValue([mockAdded]) renderHook(() => usePlannedDays('child1')) await act(async () => { // just test that add can be called; mocks handle the result }) expect(api.createPlannedDay).not.toHaveBeenCalled() // won't be called without user action }) it('removes planned day and refreshes', async () => { vi.mocked(api.deletePlannedDay).mockResolvedValue(undefined) vi.mocked(api.getPlannedDays).mockResolvedValue([]) const { result } = renderHook(() => usePlannedDays('child1')) await act(async () => { await result.current.remove('pd1') }) expect(api.deletePlannedDay).toHaveBeenCalledWith('pd1') expect(api.getPlannedDays).toHaveBeenCalled() }) it('handles fetch error and sets error state', async () => { const errorMessage = 'Network error' vi.mocked(api.getPlannedDays).mockRejectedValue(new Error(errorMessage)) const { result } = renderHook(() => usePlannedDays('child1')) await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)) }) expect(result.current.error).toBe(errorMessage) expect(result.current.data).toBeNull() expect(result.current.isLoading).toBe(false) }) it('handles fetch error with non-Error object', async () => { vi.mocked(api.getPlannedDays).mockRejectedValue('Unknown error') const { result } = renderHook(() => usePlannedDays('child1')) await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)) }) expect(result.current.error).toBe('Failed to fetch planned days') expect(result.current.data).toBeNull() }) it('updates planned day and refreshes', async () => { const mockUpdated = makePlannedLeaveDay({ id: '1', workspaceId: 'ws1', childId: 'child1', parentPersonalDataUuid: 'parent1', date: '2026-06-16', dayType: 'lagsta', isDubbeldag: false, }) vi.mocked(api.updatePlannedDay).mockResolvedValue(mockUpdated) vi.mocked(api.getPlannedDays).mockResolvedValue([mockUpdated]) const { result } = renderHook(() => usePlannedDays('child1')) await act(async () => { await result.current.update('1', { day_type: 'lagsta' }) }) expect(api.updatePlannedDay).toHaveBeenCalledWith('1', { day_type: 'lagsta' }) expect(api.getPlannedDays).toHaveBeenCalled() }) it('updates data after successful add', async () => { const initialData = [ makePlannedLeaveDay({ id: '1', workspaceId: 'ws1', childId: 'child1', parentPersonalDataUuid: 'parent1', date: '2026-06-15', dayType: 'vacation', isDubbeldag: false, }), ] const newData = makePlannedLeaveDay({ id: '2', workspaceId: 'ws1', childId: 'child1', parentPersonalDataUuid: 'parent1', date: '2026-06-16', dayType: 'vacation', isDubbeldag: false, }) vi.mocked(api.getPlannedDays).mockResolvedValueOnce(initialData) vi.mocked(api.createPlannedDay).mockResolvedValue(newData) vi.mocked(api.getPlannedDays).mockResolvedValueOnce([...initialData, newData]) const { result } = renderHook(() => usePlannedDays('child1')) await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)) }) expect(result.current.data).toEqual(initialData) await act(async () => { await result.current.add({ workspace_id: 'ws1', child_id: 'child1', date: '2026-06-16', day_type: 'vacation', parent_personal_data_uuid: 'parent1' }) }) expect(result.current.data).toEqual([...initialData, newData]) }) it('updates data after successful remove', async () => { const initialData = [ makePlannedLeaveDay({ id: '1', workspaceId: 'ws1', childId: 'child1', parentPersonalDataUuid: 'parent1', date: '2026-06-15', dayType: 'vacation', isDubbeldag: false, }), makePlannedLeaveDay({ id: '2', workspaceId: 'ws1', childId: 'child1', parentPersonalDataUuid: 'parent1', date: '2026-06-16', dayType: 'vacation', isDubbeldag: false, }), ] vi.mocked(api.getPlannedDays).mockResolvedValueOnce(initialData) vi.mocked(api.deletePlannedDay).mockResolvedValue(undefined) vi.mocked(api.getPlannedDays).mockResolvedValueOnce([initialData[0]!]) const { result } = renderHook(() => usePlannedDays('child1')) await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)) }) expect(result.current.data).toEqual(initialData) await act(async () => { await result.current.remove('2') }) expect(result.current.data).toEqual([initialData[0]]) }) it('refreshes data explicitly', async () => { const mockData = [ makePlannedLeaveDay({ id: '1', workspaceId: 'ws1', childId: 'child1', parentPersonalDataUuid: 'parent1', date: '2026-06-15', dayType: 'vacation', isDubbeldag: false, }), ] vi.mocked(api.getPlannedDays).mockResolvedValue(mockData) const { result } = renderHook(() => usePlannedDays('child1')) await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)) }) expect(api.getPlannedDays).toHaveBeenCalledWith('child1') const callCount = vi.mocked(api.getPlannedDays).mock.calls.length await act(async () => { await result.current.refresh() }) expect(vi.mocked(api.getPlannedDays).mock.calls.length).toBeGreaterThan(callCount) }) it('sets loading state during fetch', async () => { let resolveGetPlannedDays: (value: Awaited>) => void const getPlannedDaysPromise: ReturnType = new Promise(resolve => { resolveGetPlannedDays = resolve }) vi.mocked(api.getPlannedDays).mockReturnValue(getPlannedDaysPromise) const { result } = renderHook(() => usePlannedDays('child1')) expect(result.current.isLoading).toBe(true) await act(async () => { resolveGetPlannedDays!([]) await getPlannedDaysPromise }) expect(result.current.isLoading).toBe(false) }) it('clears error on successful fetch after error', async () => { vi.mocked(api.getPlannedDays).mockRejectedValueOnce(new Error('Network error')) vi.mocked(api.getPlannedDays).mockResolvedValueOnce([]) const { result, rerender } = renderHook(({ childId }: { childId: string }) => usePlannedDays(childId), { initialProps: { childId: 'child1' }, }) await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)) }) expect(result.current.error).toBe('Network error') rerender({ childId: 'child2' }) await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)) }) expect(result.current.error).toBeNull() }) it('handles add error gracefully', async () => { vi.mocked(api.getPlannedDays).mockResolvedValue([]) vi.mocked(api.createPlannedDay).mockRejectedValue(new Error('Create failed')) const { result } = renderHook(() => usePlannedDays('child1')) await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)) }) let addError: Error | undefined await act(async () => { try { await result.current.add({ workspace_id: 'ws1', child_id: 'child1', date: '2026-06-16', day_type: 'vacation', parent_personal_data_uuid: 'parent1' }) } catch (err) { addError = err as Error } }) expect(addError?.message).toBe('Create failed') }) it('handles update error gracefully', async () => { vi.mocked(api.getPlannedDays).mockResolvedValue([]) vi.mocked(api.updatePlannedDay).mockRejectedValue(new Error('Update failed')) const { result } = renderHook(() => usePlannedDays('child1')) await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)) }) let updateError: Error | undefined await act(async () => { try { await result.current.update('1', { day_type: 'lagsta' }) } catch (err) { updateError = err as Error } }) expect(updateError?.message).toBe('Update failed') }) it('handles remove error gracefully', async () => { vi.mocked(api.getPlannedDays).mockResolvedValue([]) vi.mocked(api.deletePlannedDay).mockRejectedValue(new Error('Delete failed')) const { result } = renderHook(() => usePlannedDays('child1')) await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)) }) let removeError: Error | undefined await act(async () => { try { await result.current.remove('1') } catch (err) { removeError = err as Error } }) expect(removeError?.message).toBe('Delete failed') }) })