Raise frontend branch coverage from 86.84% to 93.78%

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
This commit is contained in:
2026-09-14 18:54:56 +02:00
parent 8869ea8e55
commit c5481a05f0
23 changed files with 1063 additions and 5 deletions
+349 -2
View File
@@ -2,7 +2,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { Calendar } from './Calendar'
import { useWorkspace } from '@/context'
import { usePlannedDays, useCalendarDays, useDayOverrides } from '@/hooks'
import { usePlannedDays, useCalendarDays, useDayOverrides, useShowWeekNumbers, getLastLeaveType } from '@/hooks'
import { LanguageProvider } from '@/i18n'
vi.mock('@/context', () => ({
useWorkspace: vi.fn(),
@@ -26,7 +27,15 @@ vi.mock('@/components/common/LoadingSpinner', () => ({
}))
vi.mock('@/components/modals/PlannedDayModal', () => ({
PlannedDayModal: () => <div data-testid="modal" />,
// Exposes the props this test suite cares about as data attributes so
// tests can assert on them without rendering the real modal.
PlannedDayModal: (props: { defaultDayType?: string; existingDay?: unknown }) => (
<div
data-testid="modal"
data-default-day-type={props.defaultDayType ?? ''}
data-existing-day={props.existingDay ? 'yes' : 'no'}
/>
),
}))
describe('Calendar', () => {
@@ -500,4 +509,342 @@ describe('Calendar', () => {
await waitFor(() => expect(refetchCalendarWindow).toHaveBeenCalledTimes(1))
})
})
describe('defensive fallbacks and additional branches', () => {
it('falls back to safe defaults when workspace/child/personalData are unavailable', () => {
const noWorkspace = { isLoading: false, selectedChildId: null, workspace: null, personalDataUuid: null }
;(useWorkspace as any).mockReturnValue(noWorkspace)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
const upsert = vi.fn()
const removeOverride = vi.fn()
;(useDayOverrides as any).mockReturnValue({ data: [], isLoading: false, error: null, upsert, remove: removeOverride })
render(<Calendar />)
// selectedChildId ?? undefined
expect(usePlannedDays).toHaveBeenCalledWith(undefined)
// workspace?.id ?? '' (used for both useCalendarDays and useDayOverrides)
expect(useCalendarDays).toHaveBeenCalledWith('', expect.any(Number), expect.any(Number))
expect(useDayOverrides).toHaveBeenCalledWith('')
// Attempting an override toggle without a workspace id must be a no-op
// (the `if (!workspace?.id) return` guard fires before any mutation).
fireEvent.click(screen.getByTestId('calendar-override-toggle'))
const calendarDays = screen.getAllByTestId('calendar-day')
const currentMonthDay = calendarDays.find((el) => !el.classList.contains('calendar-day--disabled'))
fireEvent.click(currentMonthDay!)
expect(upsert).not.toHaveBeenCalled()
expect(removeOverride).not.toHaveBeenCalled()
})
it('treats a missing calendar window as having no days yet', () => {
;(useWorkspace as any).mockReturnValue(mockWorkspace)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
;(useCalendarDays as any).mockReturnValue({
data: undefined,
isLoading: true,
error: null,
refetch: vi.fn(),
})
render(<Calendar />)
// calendarWindow?.days ?? [] — must render normally with no shaded days.
expect(document.querySelector('.calendar-day--red-day')).not.toBeInTheDocument()
expect(screen.getByTestId('calendar')).toBeInTheDocument()
})
it('treats missing planned days as an empty list for lookups and bar rendering', () => {
;(useWorkspace as any).mockReturnValue(mockWorkspace)
;(usePlannedDays as any).mockReturnValue({ ...mockPlannedDays, data: undefined })
render(<Calendar />)
const calendarDays = screen.getAllByTestId('calendar-day')
const currentMonthDay = calendarDays.find((el) => !el.classList.contains('calendar-day--disabled'))
fireEvent.click(currentMonthDay!)
// getExistingDay's (plannedDays ?? []).find(...) must not throw, and
// there is (correctly) no existing day to edit.
expect(screen.getByTestId('modal')).toHaveAttribute('data-existing-day', 'no')
})
it('renders the month/year header in English when the locale is English', () => {
;(useWorkspace as any).mockReturnValue(mockWorkspace)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
Object.defineProperty(window.navigator, 'language', { value: 'en-US', configurable: true })
vi.stubGlobal('localStorage', {
getItem: () => null,
setItem: () => undefined,
removeItem: () => undefined,
})
try {
render(
<LanguageProvider>
<Calendar />
</LanguageProvider>,
)
const title = screen.getByRole('heading', { level: 2 })
const monthButton = title.querySelector('.calendar__month-button') as HTMLButtonElement
// English month names differ from the Swedish ones (e.g. "January" vs "januari").
expect(monthButton.getAttribute('aria-label')).toContain('click to advance month')
} finally {
Object.defineProperty(window.navigator, 'language', { value: 'sv-SE', configurable: true })
vi.unstubAllGlobals()
}
})
it('shows the week-number column when the preference is enabled', () => {
;(useWorkspace as any).mockReturnValue(mockWorkspace)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
;(useShowWeekNumbers as any).mockReturnValueOnce([true])
render(<Calendar />)
expect(document.querySelector('.calendar__grid--with-week-numbers')).toBeInTheDocument()
expect(document.querySelector('.calendar__week-number-header')).toBeInTheDocument()
})
it('defaults the new-day modal to "vacation" when there is no last-used leave type', () => {
;(useWorkspace as any).mockReturnValue(mockWorkspace)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
;(getLastLeaveType as any).mockReturnValueOnce(null)
render(<Calendar />)
const calendarDays = screen.getAllByTestId('calendar-day')
const currentMonthDay = calendarDays.find((el) => !el.classList.contains('calendar-day--disabled'))
fireEvent.click(currentMonthDay!)
expect(screen.getByTestId('modal')).toHaveAttribute('data-default-day-type', 'vacation')
})
it('ignores pointer-drag gestures entirely while override mode is active', () => {
;(useWorkspace as any).mockReturnValue(mockWorkspace)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
render(<Calendar />)
fireEvent.click(screen.getByTestId('calendar-override-toggle'))
const calendarDays = screen.getAllByTestId('calendar-day')
const currentMonthDays = calendarDays.filter((el) => !el.classList.contains('calendar-day--disabled'))
const startDay = currentMonthDays[0] as HTMLElement
const endDay = currentMonthDays[3] as HTMLElement
fireEvent.pointerDown(startDay)
fireEvent.pointerEnter(endDay)
// Both handlers' `if (isOverrideMode) return` guards must fire, so no
// drag-selection highlighting should appear.
const selected = Array.from(calendarDays).filter((el) => el.classList.contains('calendar-day--selected'))
expect(selected).toHaveLength(0)
})
it('treats a missing override-list fetch as empty when clearing an override', async () => {
const today = todayDateString()
const removeOverride = vi.fn()
const refetchCalendarWindow = vi.fn().mockResolvedValue(undefined)
;(useWorkspace as any).mockReturnValue(mockWorkspace)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
;(useCalendarDays as any).mockReturnValue({
data: {
startYear: 2026,
endYear: 2026,
minYear: 2026,
maxYear: 2035,
days: [
{
date: today,
name: null,
dayType: 'working_day',
isOverride: true,
overrideIsWorkingDay: false,
isFree: true,
},
],
},
isLoading: false,
error: null,
refetch: refetchCalendarWindow,
})
// data is undefined here (e.g. before the overrides fetch resolves) —
// the (dayOverrides ?? []).find(...) fallback must kick in rather than
// throwing, and since nothing is found there is nothing to remove.
;(useDayOverrides as any).mockReturnValue({
data: undefined,
isLoading: true,
error: null,
upsert: vi.fn(),
remove: removeOverride,
})
render(<Calendar />)
fireEvent.click(screen.getByTestId('calendar-override-toggle'))
const calendarDays = screen.getAllByTestId('calendar-day')
const todayCell = calendarDays.find((el) => el.getAttribute('aria-label')?.startsWith(today))
expect(todayCell).toBeDefined()
fireEvent.click(todayCell!)
await waitFor(() => expect(refetchCalendarWindow).toHaveBeenCalledTimes(1))
expect(removeOverride).not.toHaveBeenCalled()
})
it('shows the Error message directly when the override mutation rejects with an Error', async () => {
const today = todayDateString()
const upsert = vi.fn().mockRejectedValue(new Error('quota exceeded'))
;(useWorkspace as any).mockReturnValue(mockWorkspace)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
;(useCalendarDays as any).mockReturnValue({
data: {
startYear: 2026,
endYear: 2026,
minYear: 2026,
maxYear: 2035,
days: [
{
date: today,
name: null,
dayType: 'weekend',
isOverride: false,
overrideIsWorkingDay: null,
isFree: true,
},
],
},
isLoading: false,
error: null,
refetch: vi.fn(),
})
;(useDayOverrides as any).mockReturnValue({ data: [], isLoading: false, error: null, upsert, remove: vi.fn() })
render(<Calendar />)
fireEvent.click(screen.getByTestId('calendar-override-toggle'))
const calendarDays = screen.getAllByTestId('calendar-day')
const todayCell = calendarDays.find((el) => el.getAttribute('aria-label')?.startsWith(today))
fireEvent.click(todayCell!)
await waitFor(() => {
expect(screen.getByTestId('calendar-override-error')).toHaveTextContent('quota exceeded')
})
})
it('passes an empty personalDataUuid through to the planning modal when it is unavailable', () => {
const noPersonalData = { ...mockWorkspace, personalDataUuid: null }
;(useWorkspace as any).mockReturnValue(noPersonalData)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
render(<Calendar />)
// Normal (non-override) click opens the planning modal, which must not
// crash even though personalDataUuid is missing.
const calendarDays = screen.getAllByTestId('calendar-day')
const currentMonthDay = calendarDays.find((el) => !el.classList.contains('calendar-day--disabled'))
fireEvent.click(currentMonthDay!)
expect(screen.getByTestId('modal')).toBeInTheDocument()
})
it('lets the modal offer deletion when the clicked date already has a planned day', () => {
;(useWorkspace as any).mockReturnValue(mockWorkspace)
render(<Calendar />)
const calendarDays = screen.getAllByTestId('calendar-day')
const currentMonthDay = calendarDays.find((el) => !el.classList.contains('calendar-day--disabled'))
const clickedDate = currentMonthDay!.getAttribute('aria-label')!
;(usePlannedDays as any).mockReturnValue({
...mockPlannedDays,
data: [
{
id: 'pd-1',
workspaceId: 'ws-1',
childId: 'child-1',
parentPersonalDataUuid: 'person-1',
date: clickedDate,
dayType: 'vacation',
isDubbeldag: false,
createdAt: '2026-01-01',
updatedAt: '2026-01-01',
},
],
})
const { rerender } = render(<Calendar />)
rerender(<Calendar />)
const daysAfterRerender = screen.getAllByTestId('calendar-day')
const sameDay = daysAfterRerender.find((el) => el.getAttribute('aria-label') === clickedDate)
fireEvent.click(sameDay!)
expect(screen.getByTestId('modal')).toHaveAttribute('data-existing-day', 'yes')
})
it('forces existingDay to null while a multi-day range is being planned', () => {
;(useWorkspace as any).mockReturnValue(mockWorkspace)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
render(<Calendar />)
const calendarDays = screen.getAllByTestId('calendar-day')
const currentMonthDays = calendarDays.filter((el) => !el.classList.contains('calendar-day--disabled'))
const startDay = currentMonthDays[0] as HTMLElement
const endDay = currentMonthDays[3] as HTMLElement
fireEvent.pointerDown(startDay)
fireEvent.pointerEnter(endDay)
fireEvent.pointerUp(window)
// isRangeMode ? null : ... — a completed multi-day drag must force
// existingDay to null even if one of the dates happened to have a plan.
expect(screen.getByTestId('modal')).toHaveAttribute('data-existing-day', 'no')
})
it('shows a generic override error when the mutation rejects with a non-Error value', async () => {
const today = todayDateString()
const upsert = vi.fn().mockRejectedValue('network exploded')
;(useWorkspace as any).mockReturnValue(mockWorkspace)
;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
;(useCalendarDays as any).mockReturnValue({
data: {
startYear: 2026,
endYear: 2026,
minYear: 2026,
maxYear: 2035,
days: [
{
date: today,
name: null,
dayType: 'weekend',
isOverride: false,
overrideIsWorkingDay: null,
isFree: true,
},
],
},
isLoading: false,
error: null,
refetch: vi.fn(),
})
;(useDayOverrides as any).mockReturnValue({ data: [], isLoading: false, error: null, upsert, remove: vi.fn() })
render(<Calendar />)
fireEvent.click(screen.getByTestId('calendar-override-toggle'))
const calendarDays = screen.getAllByTestId('calendar-day')
const todayCell = calendarDays.find((el) => el.getAttribute('aria-label')?.startsWith(today))
fireEvent.click(todayCell!)
await waitFor(() => {
expect(screen.getByTestId('calendar-override-error')).toBeInTheDocument()
})
// The fallback message is used, not the raw non-Error rejection value.
expect(screen.getByTestId('calendar-override-error')).not.toHaveTextContent('network exploded')
})
})
})