diff --git a/frontend/src/components/common/CalendarWeek.test.tsx b/frontend/src/components/common/CalendarWeek.test.tsx
index 64a5657..05f81a1 100644
--- a/frontend/src/components/common/CalendarWeek.test.tsx
+++ b/frontend/src/components/common/CalendarWeek.test.tsx
@@ -266,6 +266,27 @@ describe('CalendarWeek', () => {
render()
expect(screen.queryByTestId('calendar-event')).not.toBeInTheDocument()
})
+
+ it('composes the band aria-label from parent label, type label, and the interpolated date range', () => {
+ // Regression guard: a tips bug in this repo surfaced as the literal
+ // un-interpolated "Tip: {title}" in an accessibility label and nothing
+ // caught it. Assert the real composed string here, not merely that an
+ // aria-label attribute exists.
+ const plannedDays = [
+ pd({ id: '1', date: '2026-01-05', dayType: 'sjukpenning' }),
+ pd({ id: '2', date: '2026-01-06', dayType: 'sjukpenning' }),
+ ]
+ render()
+ const band = screen.getAllByTestId(/^calendar-band-/)[0]
+ expect(band).toHaveAttribute('aria-label', 'Alice · Sjukpenning, 2026-01-05 – 2026-01-06')
+ })
+
+ it('uses the partner label in the band aria-label for the other parent\'s days', () => {
+ const plannedDays = [pd({ id: '1', date: '2026-01-05', parentPersonalDataUuid: 'other-parent' })]
+ render()
+ const band = screen.getAllByTestId(/^calendar-band-/)[0]
+ expect(band).toHaveAttribute('aria-label', 'Partner · Semester, 2026-01-05 – 2026-01-05')
+ })
})
describe('the weekend-gap bug: a wish-linked band bridges free-day gaps', () => {
diff --git a/frontend/src/hooks/useDayOverrides.test.ts b/frontend/src/hooks/useDayOverrides.test.ts
index 62f211a..b2e00d8 100644
--- a/frontend/src/hooks/useDayOverrides.test.ts
+++ b/frontend/src/hooks/useDayOverrides.test.ts
@@ -8,24 +8,18 @@ vi.mock('@/services/api')
describe('useDayOverrides', () => {
const mockOverrides = [
- {
+ makeDayOverride({
id: 'do-1',
- workspaceId: 'ws-1',
date: '2026-12-24',
isWorkingDay: true,
note: 'Jobbade på juldagen',
- createdAt: '2026-01-01T00:00:00',
- updatedAt: '2026-01-01T00:00:00',
- },
- {
+ }),
+ makeDayOverride({
id: 'do-2',
- workspaceId: 'ws-1',
date: '2026-06-20',
isWorkingDay: false,
note: 'Extra ledigt',
- createdAt: '2026-01-01T00:00:00',
- updatedAt: '2026-01-01T00:00:00',
- },
+ }),
]
beforeEach(() => {
diff --git a/frontend/src/services/api.test.ts b/frontend/src/services/api.test.ts
index f1cfc20..91923b5 100644
--- a/frontend/src/services/api.test.ts
+++ b/frontend/src/services/api.test.ts
@@ -432,6 +432,74 @@ describe('API response mappers (snake_case → camelCase)', () => {
expect(result.createdAt).toBe('2026-06-14T17:09:47')
})
})
+
+ describe('listDayOverridesByWorkspace', () => {
+ it('maps snake_case day overrides to camelCase', async () => {
+ // Regression test: toDayOverride was the only mapper in this file with
+ // no snake_case→camelCase test — this is the exact shape of bug that
+ // shipped for getTips (see the getTips test below): a missing/wrong
+ // mapping surfaces only at runtime, never in a suite that mocks the
+ // module and hand-rolls already-camelCase fixtures.
+ const rawResponse = [
+ {
+ id: 'do-1',
+ workspace_id: 'ws1',
+ date: '2026-12-24',
+ is_working_day: true,
+ note: 'Jobbade på juldagen',
+ created_at: '2026-01-01T00:00:00',
+ updated_at: '2026-01-02T00:00:00',
+ },
+ ]
+
+ fetchMock.mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: async () => rawResponse,
+ })
+
+ const result = await api.listDayOverridesByWorkspace('ws1')
+
+ expect(result).toEqual([
+ {
+ id: 'do-1',
+ workspaceId: 'ws1',
+ date: '2026-12-24',
+ isWorkingDay: true,
+ note: 'Jobbade på juldagen',
+ createdAt: '2026-01-01T00:00:00',
+ updatedAt: '2026-01-02T00:00:00',
+ },
+ ])
+ })
+
+ it('maps a null note/created_at/updated_at through unchanged', async () => {
+ const rawResponse = [
+ {
+ id: 'do-2',
+ workspace_id: 'ws1',
+ date: '2026-06-20',
+ is_working_day: false,
+ note: null,
+ created_at: null,
+ updated_at: null,
+ },
+ ]
+
+ fetchMock.mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: async () => rawResponse,
+ })
+
+ const result = await api.listDayOverridesByWorkspace('ws1')
+
+ expect(result[0]!.note).toBeNull()
+ expect(result[0]!.createdAt).toBeNull()
+ expect(result[0]!.updatedAt).toBeNull()
+ expect(result[0]!.isWorkingDay).toBe(false)
+ })
+ })
})
describe('apiFetch (shared request/response handling)', () => {
@@ -1334,6 +1402,46 @@ describe('endpoint functions (representative coverage)', () => {
])
})
+ it('applyPlan maps a populated collisions array to camelCase (§3.5)', async () => {
+ // The only other applyPlan mapping test passes collisions: [], so
+ // existing_day_type/proposed_day_type were never checked against real
+ // data — this exercises the actual field renames.
+ fetchMock.mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: async () => ({
+ accepted: false,
+ created_count: 0,
+ skipped_identical_count: 0,
+ skipped_free_count: 0,
+ replaced_count: 0,
+ collisions: [
+ {
+ date: '2026-06-15',
+ parent_personal_data_uuid: 'pd1',
+ child_id: 'child1',
+ existing_day_type: 'vacation',
+ proposed_day_type: 'sjukpenning',
+ },
+ ],
+ rejections: [],
+ created_days: [],
+ }),
+ })
+
+ const result = await api.applyPlan('ws1', [], false)
+
+ expect(result.collisions).toEqual([
+ {
+ date: '2026-06-15',
+ parentPersonalDataUuid: 'pd1',
+ childId: 'child1',
+ existingDayType: 'vacation',
+ proposedDayType: 'sjukpenning',
+ },
+ ])
+ })
+
it('syncPlan posts the workspace id and maps the nested proposal/applyResult/changes', async () => {
fetchMock.mockResolvedValue({
ok: true,