Cover the mappers and labels nothing was watching
The day-override mapper was the only one in the client with no test feeding it the shape the server actually sends, which is how a feature once shipped rendering blank cards to everyone while the suite stayed green. The apply response's collision fields were mapped but only ever exercised with an empty list, and the leave band's accessible label, composed from translated text and two dates, was asserted nowhere. All three were correct; none was guarded. The day-override hook's fixtures now go through the typed factory rather than hand-written objects.
This commit is contained in:
@@ -266,6 +266,27 @@ describe('CalendarWeek', () => {
|
||||
render(<CalendarWeek {...defaultProps} plannedDays={[]} />)
|
||||
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(<CalendarWeek {...defaultProps} plannedDays={plannedDays} parentName="Alice" />)
|
||||
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(<CalendarWeek {...defaultProps} plannedDays={plannedDays} personalDataUuid="parent-1" />)
|
||||
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', () => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user