diff --git a/frontend/src/components/calendar/RedDaysList.test.tsx b/frontend/src/components/calendar/RedDaysList.test.tsx
index 36fd9fe..e59a73f 100644
--- a/frontend/src/components/calendar/RedDaysList.test.tsx
+++ b/frontend/src/components/calendar/RedDaysList.test.tsx
@@ -36,6 +36,14 @@ const baseDays: CalendarDayEntry[] = [
overrideIsWorkingDay: null,
isFree: true,
},
+ {
+ date: '2026-05-04',
+ name: null,
+ dayType: 'working_day',
+ isOverride: false,
+ overrideIsWorkingDay: null,
+ isFree: true,
+ },
]
describe('RedDaysList', () => {
@@ -80,6 +88,47 @@ describe('RedDaysList', () => {
expect(year2028).toHaveTextContent(/Inga röda dagar eller undantag/)
})
+ it('skips a plain working day that is neither a red day nor an override', () => {
+ render()
+ // 2026-05-04 is a plain working_day, non-override entry in baseDays — it
+ // must not show up as a list item under its year.
+ expect(screen.queryByTestId('red-days-item-2026-05-04')).not.toBeInTheDocument()
+ })
+
+ it('labels a working-day override distinctly from a free-day override', () => {
+ const workingOverrideDays = [
+ {
+ date: '2026-03-02',
+ name: null,
+ dayType: 'working_day' as const,
+ isOverride: true,
+ overrideIsWorkingDay: true,
+ isFree: false,
+ },
+ ]
+ render()
+ const badge = screen.getByTestId('red-days-override-badge-2026-03-02')
+ expect(badge).toHaveClass('red-days-list__override-badge--working')
+ expect(badge).toHaveTextContent('Undantag: arbetsdag')
+ })
+
+ it('shows the effective-working label for a day that is not free', () => {
+ const workingDays = [
+ {
+ date: '2026-03-02',
+ name: null,
+ dayType: 'working_day' as const,
+ isOverride: true,
+ overrideIsWorkingDay: true,
+ isFree: false,
+ },
+ ]
+ render()
+ const item = screen.getByTestId('red-days-item-2026-03-02')
+ expect(item.querySelector('.red-days-list__effective--working')).toBeInTheDocument()
+ expect(item.querySelector('.red-days-list__effective--free')).not.toBeInTheDocument()
+ })
+
it('disables the previous button at minYear', () => {
render()
expect(screen.getByTestId('red-days-prev')).toBeDisabled()
diff --git a/frontend/src/components/common/CalendarDay.test.tsx b/frontend/src/components/common/CalendarDay.test.tsx
index 3572812..b58ae1a 100644
--- a/frontend/src/components/common/CalendarDay.test.tsx
+++ b/frontend/src/components/common/CalendarDay.test.tsx
@@ -64,6 +64,18 @@ describe('CalendarDay', () => {
expect(onClick).toHaveBeenCalledTimes(1)
})
+ it('calls onActivate instead of onClick on Enter when onActivate is provided', () => {
+ const onClick = vi.fn()
+ const onActivate = vi.fn()
+ const { getByTestId } = render(
+
+ )
+ const day = getByTestId('calendar-day')
+ fireEvent.keyDown(day, { key: 'Enter' })
+ expect(onActivate).toHaveBeenCalledTimes(1)
+ expect(onClick).not.toHaveBeenCalled()
+ })
+
it('calls onClick on Space key', () => {
const onClick = vi.fn()
const { getByTestId } = render(
diff --git a/frontend/src/components/common/CalendarEvent.test.tsx b/frontend/src/components/common/CalendarEvent.test.tsx
index e2aeec8..a416722 100644
--- a/frontend/src/components/common/CalendarEvent.test.tsx
+++ b/frontend/src/components/common/CalendarEvent.test.tsx
@@ -65,6 +65,22 @@ describe('CalendarEvent', () => {
expect(stopPropagation).toHaveBeenCalled()
})
+ it('prefixes the label with the parent label when provided', () => {
+ const { getByTestId } = render(
+
+ )
+ const event = getByTestId('calendar-event')
+ expect(event.textContent).toBe('Alice · vacation')
+ })
+
+ it('shows the double-day badge when isDubbeldag is true', () => {
+ const { getByTestId } = render(
+
+ )
+ const event = getByTestId('calendar-event')
+ expect(event.textContent).toContain('2×')
+ })
+
it('provides accessibility label', () => {
const { getByTestId } = render(
{}} />
diff --git a/frontend/src/components/common/CalendarWeek.test.tsx b/frontend/src/components/common/CalendarWeek.test.tsx
index 39aa076..c870f5a 100644
--- a/frontend/src/components/common/CalendarWeek.test.tsx
+++ b/frontend/src/components/common/CalendarWeek.test.tsx
@@ -412,6 +412,39 @@ describe('CalendarWeek', () => {
expect(events.length).toBeGreaterThanOrEqual(2)
})
+ it('closes an open bar that runs through the last day of the week', () => {
+ // A run of planned days ending on the week's final date (2026-01-11)
+ // leaves the bar "open" when the loop over weekDates finishes, which
+ // exercises the post-loop flush rather than the mid-loop gap-close path.
+ const plannedDays: PlannedLeaveDay[] = [
+ {
+ id: '1',
+ workspaceId: 'ws-1',
+ childId: 'child-1',
+ parentPersonalDataUuid: 'parent-1',
+ date: '2026-01-10',
+ dayType: 'vacation',
+ isDubbeldag: false,
+ createdAt: '2026-01-01',
+ updatedAt: '2026-01-01',
+ },
+ {
+ id: '2',
+ workspaceId: 'ws-1',
+ childId: 'child-1',
+ parentPersonalDataUuid: 'parent-1',
+ date: '2026-01-11',
+ dayType: 'vacation',
+ isDubbeldag: false,
+ createdAt: '2026-01-01',
+ updatedAt: '2026-01-01',
+ },
+ ]
+ render()
+ const events = screen.getAllByTestId('calendar-event')
+ expect(events.length).toBeGreaterThan(0)
+ })
+
it('handles empty planned days', () => {
render()
expect(screen.queryByTestId('calendar-event')).not.toBeInTheDocument()
diff --git a/frontend/src/components/dashboard/DaysBreakdown.test.tsx b/frontend/src/components/dashboard/DaysBreakdown.test.tsx
index 7bcd257..0f64bfa 100644
--- a/frontend/src/components/dashboard/DaysBreakdown.test.tsx
+++ b/frontend/src/components/dashboard/DaysBreakdown.test.tsx
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest'
import { render } from '@testing-library/react'
-import { DaysBreakdown } from './DaysBreakdown'
+import { DaysBreakdown, type DaysBreakdownProps } from './DaysBreakdown'
describe('DaysBreakdown', () => {
it('renders empty state when no breakdown provided', () => {
@@ -8,6 +8,13 @@ describe('DaysBreakdown', () => {
expect(getByTestId('days-breakdown').textContent).toContain('Inga dagar registrerade än')
})
+ it('renders empty state when breakdown itself is missing (defensive null/undefined guard)', () => {
+ const { getByTestId } = render(
+
+ )
+ expect(getByTestId('days-breakdown').textContent).toContain('Inga dagar registrerade än')
+ })
+
it('renders table with breakdown data', () => {
const breakdown = [
{ type: 'vacation', count: 10 },
diff --git a/frontend/src/components/dashboard/ParentalLeaveBreakdown.test.tsx b/frontend/src/components/dashboard/ParentalLeaveBreakdown.test.tsx
index de7e470..e98d30e 100644
--- a/frontend/src/components/dashboard/ParentalLeaveBreakdown.test.tsx
+++ b/frontend/src/components/dashboard/ParentalLeaveBreakdown.test.tsx
@@ -1,7 +1,8 @@
-import { describe, it, expect } from 'vitest'
+import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import { ParentalLeaveBreakdown } from './ParentalLeaveBreakdown'
import { LEAVE_RULES } from '@/utils/constants'
+import { LanguageProvider } from '@/i18n'
import type { ParentalLeaveFetch } from '@/types'
describe('ParentalLeaveBreakdown', () => {
@@ -36,6 +37,15 @@ describe('ParentalLeaveBreakdown', () => {
expect(screen.getByText(/Ingen data hämtad ännu/)).toBeInTheDocument()
})
+ it('omits the tier rows when tierData is absent', () => {
+ const dataWithoutTiers = { ...mockData, tierData: undefined } as unknown as ParentalLeaveFetch
+ render()
+ expect(screen.queryByTestId('breakdown-sjukpenning')).not.toBeInTheDocument()
+ expect(screen.queryByTestId('breakdown-lagsta-niva')).not.toBeInTheDocument()
+ // The rows that don't depend on tierData still render.
+ expect(screen.getByTestId('breakdown-transferable')).toBeInTheDocument()
+ })
+
it('renders breakdown table with data', () => {
render()
expect(screen.getByTestId('parental-leave-breakdown')).toBeInTheDocument()
@@ -92,4 +102,26 @@ describe('ParentalLeaveBreakdown', () => {
render()
expect(screen.getByText(/2026-06-13/)).toBeInTheDocument()
})
+
+ it('formats the provenance date using the en-US locale when the language is English', () => {
+ Object.defineProperty(window.navigator, 'language', { value: 'en-US', configurable: true })
+ vi.stubGlobal('localStorage', {
+ getItem: () => null,
+ setItem: () => undefined,
+ removeItem: () => undefined,
+ })
+
+ try {
+ render(
+
+
+ ,
+ )
+ // en-US formats as M/D/YYYY, distinct from the sv-SE "2026-06-13" form.
+ expect(screen.getByText(/6\/13\/2026/)).toBeInTheDocument()
+ } finally {
+ Object.defineProperty(window.navigator, 'language', { value: 'sv-SE', configurable: true })
+ vi.unstubAllGlobals()
+ }
+ })
})
diff --git a/frontend/src/components/inputs/DayOverridesInput.test.tsx b/frontend/src/components/inputs/DayOverridesInput.test.tsx
index cb195d8..2f9d165 100644
--- a/frontend/src/components/inputs/DayOverridesInput.test.tsx
+++ b/frontend/src/components/inputs/DayOverridesInput.test.tsx
@@ -203,6 +203,25 @@ describe('DayOverridesInput', () => {
const submitButton = screen.getByTestId('add-override-button') as HTMLButtonElement
expect(submitButton.disabled).toBe(true)
})
+
+ it('does not call onAdd when the form is submitted with no date entered', async () => {
+ render(
+
+ )
+
+ // Submit the form directly (bypassing the disabled submit button) to
+ // exercise the handler's own empty-date guard.
+ fireEvent.submit(screen.getByTestId('add-override-form'))
+
+ await waitFor(() => {
+ expect(mockOnAdd).not.toHaveBeenCalled()
+ })
+ })
})
describe('removing override', () => {
@@ -222,6 +241,26 @@ describe('DayOverridesInput', () => {
expect(mockOnRemove).toHaveBeenCalledWith('do-1')
})
})
+
+ it('swallows an onRemove failure instead of throwing (handled by the parent)', async () => {
+ mockOnRemove.mockRejectedValueOnce(new Error('delete failed'))
+ render(
+
+ )
+
+ fireEvent.click(screen.getByTestId('remove-override-do-1'))
+
+ await waitFor(() => {
+ expect(mockOnRemove).toHaveBeenCalledWith('do-1')
+ })
+ // The item is still rendered — the component didn't crash on the rejection.
+ expect(screen.getByTestId('override-item-do-1')).toBeInTheDocument()
+ })
})
describe('loading state', () => {
diff --git a/frontend/src/components/layout/Header.test.tsx b/frontend/src/components/layout/Header.test.tsx
index 831257c..63d4d3d 100644
--- a/frontend/src/components/layout/Header.test.tsx
+++ b/frontend/src/components/layout/Header.test.tsx
@@ -1,7 +1,7 @@
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
-import { BrowserRouter } from 'react-router-dom'
+import { BrowserRouter, MemoryRouter } from 'react-router-dom'
import { Header } from './Header'
const renderWithRouter = (element: React.ReactElement) => {
@@ -12,6 +12,14 @@ const renderWithRouter = (element: React.ReactElement) => {
)
}
+const renderAtPath = (element: React.ReactElement, path: string) => {
+ return render(
+
+ {element}
+ ,
+ )
+}
+
describe('Header', () => {
it('renders header with default title', () => {
renderWithRouter()
@@ -75,4 +83,39 @@ describe('Header', () => {
renderWithRouter()
expect(screen.queryByTestId('logout-button')).not.toBeInTheDocument()
})
+
+ it('does not crash and renders the select when selectedChildId is not provided (falls back to "")', () => {
+ const childrenList = [
+ { id: '1', name: 'Emma' },
+ { id: '2', name: 'Oscar' },
+ ]
+ renderWithRouter()
+ const select = screen.getByTestId('child-select') as HTMLSelectElement
+ // selectedChildId ?? '' binds the select to a value ('') that matches no
+ //