c5481a05f0
Branches were the weakest metric and had the least headroom above the 85% floor. These are tests for decision paths that were genuinely unexercised, not padding. The largest gaps were systemic rather than per-file: the `err instanceof Error ? ... : fallback` branch was untested across eight data hooks, useVacationDays had no dedicated test file at all, and Calendar.tsx's missing-workspace/child/personalData fallbacks were never hit. Left deliberately uncovered, with reasons: `?? fallback` guards whose fallback cannot occur given how the value is constructed upstream, and guards behind buttons that are disabled whenever the guard would matter. Reaching those means calling handlers directly, which tests the test rather than the app. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXMhc9nT6sNz1SL4j8dznL
150 lines
3.8 KiB
TypeScript
150 lines
3.8 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
import { renderHook, waitFor } from '@testing-library/react'
|
|
import { usePlanApply } from './usePlanApply'
|
|
import * as api from '@/services/api'
|
|
|
|
vi.mock('@/services/api')
|
|
|
|
describe('usePlanApply', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('initializes with null response and no loading/error', () => {
|
|
const { result } = renderHook(() => usePlanApply())
|
|
expect(result.current.response).toBeNull()
|
|
expect(result.current.isLoading).toBe(false)
|
|
expect(result.current.error).toBeNull()
|
|
})
|
|
|
|
it('calls applyPlan and returns the response', async () => {
|
|
const mockResponse = {
|
|
accepted: true,
|
|
createdCount: 2,
|
|
skippedIdenticalCount: 0,
|
|
skippedFreeCount: 0,
|
|
replacedCount: 0,
|
|
collisions: [],
|
|
rejections: [],
|
|
createdDays: [],
|
|
}
|
|
|
|
vi.mocked(api.applyPlan).mockResolvedValue(mockResponse)
|
|
|
|
const { result } = renderHook(() => usePlanApply())
|
|
const days = [
|
|
{
|
|
date: '2026-06-15',
|
|
parentPersonalDataUuid: 'pd-1',
|
|
childId: 'c-1',
|
|
dayType: 'sjukpenning' as const,
|
|
isDubbeldag: false,
|
|
},
|
|
]
|
|
|
|
const applyPromise = result.current.apply('ws-1', days, false)
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false)
|
|
})
|
|
|
|
const response = await applyPromise
|
|
expect(response).toEqual(mockResponse)
|
|
expect(result.current.response).toEqual(mockResponse)
|
|
expect(api.applyPlan).toHaveBeenCalledWith('ws-1', days, false)
|
|
})
|
|
|
|
it('sets error when applyPlan throws', async () => {
|
|
vi.mocked(api.applyPlan).mockRejectedValue(new Error('Network error'))
|
|
|
|
const { result } = renderHook(() => usePlanApply())
|
|
const days = [
|
|
{
|
|
date: '2026-06-15',
|
|
parentPersonalDataUuid: 'pd-1',
|
|
childId: 'c-1',
|
|
dayType: 'sjukpenning' as const,
|
|
},
|
|
]
|
|
|
|
try {
|
|
await result.current.apply('ws-1', days, false)
|
|
} catch {
|
|
// Expected to throw
|
|
}
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false)
|
|
})
|
|
|
|
expect(result.current.error).toBe('Network error')
|
|
expect(result.current.response).toBeNull()
|
|
})
|
|
|
|
it('falls back to a generic message when a non-Error value is thrown', async () => {
|
|
vi.mocked(api.applyPlan).mockRejectedValue('boom')
|
|
|
|
const { result } = renderHook(() => usePlanApply())
|
|
const days = [
|
|
{
|
|
date: '2026-06-15',
|
|
parentPersonalDataUuid: 'pd-1',
|
|
childId: 'c-1',
|
|
dayType: 'sjukpenning' as const,
|
|
},
|
|
]
|
|
|
|
try {
|
|
await result.current.apply('ws-1', days, false)
|
|
} catch {
|
|
// Expected to throw
|
|
}
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false)
|
|
})
|
|
|
|
expect(result.current.error).toBe('Kunde inte använda plan')
|
|
expect(result.current.response).toBeNull()
|
|
})
|
|
|
|
it('handles replace parameter correctly', async () => {
|
|
const mockResponse = {
|
|
accepted: true,
|
|
createdCount: 1,
|
|
skippedIdenticalCount: 0,
|
|
skippedFreeCount: 0,
|
|
replacedCount: 1,
|
|
collisions: [],
|
|
rejections: [],
|
|
createdDays: [],
|
|
}
|
|
|
|
vi.mocked(api.applyPlan).mockResolvedValue(mockResponse)
|
|
|
|
const { result } = renderHook(() => usePlanApply())
|
|
const days = [
|
|
{
|
|
date: '2026-06-15',
|
|
parentPersonalDataUuid: 'pd-1',
|
|
childId: 'c-1',
|
|
dayType: 'sjukpenning' as const,
|
|
},
|
|
]
|
|
|
|
const applyPromise = result.current.apply('ws-1', days, true)
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false)
|
|
})
|
|
|
|
const response = await applyPromise
|
|
expect(api.applyPlan).toHaveBeenCalledWith('ws-1', days, true)
|
|
expect(response).toEqual(mockResponse)
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.response).toEqual(mockResponse)
|
|
})
|
|
})
|
|
})
|