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,