Redesign the setup flow and consolidate the CSS into one primitives layer
Onboarding asks explicitly for one or two parents and shows one card per parent for balances; the balance form is per parent with inline validation; the partner can be filled from the FK deduction or added from it. Settings and Dashboard reuse the same form. CSS: bare buttons carry no colour any more; buttons, cards, choice cards, fields, alerts, badges, eyebrow and modal chrome are defined once in primitives.css. Media queries live only in responsive.css. A style guard test blocks hex, !important and stray media queries. Bilingual fixes: calendar selects size by content, header no longer wraps. Vitest is capped at four workers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -99,6 +99,7 @@ describe('API response mappers (snake_case → camelCase)', () => {
|
||||
})
|
||||
|
||||
const result = await api.getParentalLeave('pd1', 'child1')
|
||||
if (!result) throw new Error('expected a mapped ParentalLeaveFetch, got null')
|
||||
|
||||
expect(result.personalDataUuid).toBe('pd1')
|
||||
expect(result.workspaceId).toBe('ws1')
|
||||
@@ -114,12 +115,24 @@ describe('API response mappers (snake_case → camelCase)', () => {
|
||||
expect(result.createdAt).toBe('2026-06-14T17:09:47')
|
||||
expect(result.updatedAt).toBe('2026-06-14T17:09:47')
|
||||
|
||||
expect(result.tierData!.sjukpenningTotal).toBe(390)
|
||||
expect(result.tierData!.sjukpenningUsed).toBe(50)
|
||||
expect(result.tierData!.sjukpenningLeft).toBe(340)
|
||||
expect(result.tierData!.lagstaNivaTotal).toBe(90)
|
||||
expect(result.tierData!.lagstaNivaUsed).toBe(10)
|
||||
expect(result.tierData!.lagstaNivaLeft).toBe(80)
|
||||
expect(result.tierData.sjukpenningTotal).toBe(390)
|
||||
expect(result.tierData.sjukpenningUsed).toBe(50)
|
||||
expect(result.tierData.sjukpenningLeft).toBe(340)
|
||||
expect(result.tierData.lagstaNivaTotal).toBe(90)
|
||||
expect(result.tierData.lagstaNivaUsed).toBe(10)
|
||||
expect(result.tierData.lagstaNivaLeft).toBe(80)
|
||||
})
|
||||
|
||||
it('returns null when nothing has been saved yet (the backend returns 200 with a null body)', async () => {
|
||||
fetchMock.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => null,
|
||||
})
|
||||
|
||||
const result = await api.getParentalLeave('pd1', 'child1')
|
||||
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -476,7 +489,6 @@ describe('API response mappers (snake_case → camelCase)', () => {
|
||||
const rawResponse = {
|
||||
id: 'ws1',
|
||||
name: 'My Workspace',
|
||||
planning_mode: 'couple' as const,
|
||||
created_at: '2026-06-14T17:09:47',
|
||||
}
|
||||
|
||||
@@ -490,7 +502,6 @@ describe('API response mappers (snake_case → camelCase)', () => {
|
||||
|
||||
expect(result.id).toBe('ws1')
|
||||
expect(result.name).toBe('My Workspace')
|
||||
expect(result.planningMode).toBe('couple')
|
||||
expect(result.createdAt).toBe('2026-06-14T17:09:47')
|
||||
})
|
||||
})
|
||||
@@ -849,7 +860,6 @@ describe('endpoint functions (representative coverage)', () => {
|
||||
json: async () => ({
|
||||
id: 'ws1',
|
||||
name: 'Home',
|
||||
planning_mode: 'solo',
|
||||
created_at: '2026-01-01',
|
||||
}),
|
||||
})
|
||||
@@ -867,22 +877,19 @@ describe('endpoint functions (representative coverage)', () => {
|
||||
json: async () => ({
|
||||
id: 'ws1',
|
||||
name: 'New name',
|
||||
planning_mode: 'couple',
|
||||
created_at: '2026-01-01',
|
||||
}),
|
||||
})
|
||||
|
||||
const result = await api.updateWorkspace('ws1', {
|
||||
name: 'New name',
|
||||
planningMode: 'couple',
|
||||
})
|
||||
|
||||
expect(lastRequest()).toEqual({ url: '/api/workspace/ws1', method: 'PUT' })
|
||||
expect(lastRequestBody()).toEqual({
|
||||
name: 'New name',
|
||||
planning_mode: 'couple',
|
||||
})
|
||||
expect(result.planningMode).toBe('couple')
|
||||
expect(result.name).toBe('New name')
|
||||
})
|
||||
|
||||
it('addPartner posts the display name', async () => {
|
||||
@@ -929,54 +936,87 @@ describe('endpoint functions (representative coverage)', () => {
|
||||
expect(lastRequestBody()).toMatchObject({ saved_days_in: 0 })
|
||||
})
|
||||
|
||||
it('saveManualFetch applies its documented defaults for omitted optional fields', async () => {
|
||||
function makeManualFetchResponse() {
|
||||
return {
|
||||
id: 'pf1',
|
||||
personal_data_uuid: 'pd1',
|
||||
workspace_id: 'ws1',
|
||||
child_id: 'child1',
|
||||
source: 'manual',
|
||||
fetched_at: '2026-01-01',
|
||||
total_days: 480,
|
||||
days_used: 0,
|
||||
days_left: 480,
|
||||
reserved_days_total: 90,
|
||||
reserved_days_left: 90,
|
||||
transferable_days_left: 100,
|
||||
dubbeldagar_left: 60,
|
||||
tier_data: {
|
||||
sjukpenning_total: 390,
|
||||
sjukpenning_used: 0,
|
||||
sjukpenning_left: 390,
|
||||
lagsta_niva_total: 90,
|
||||
lagsta_niva_used: 0,
|
||||
lagsta_niva_left: 90,
|
||||
},
|
||||
created_at: '2026-01-01',
|
||||
updated_at: '2026-01-01',
|
||||
}
|
||||
}
|
||||
|
||||
it('saveManualFetch omits optional fields entirely when not provided', async () => {
|
||||
fetchMock.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
id: 'pf1',
|
||||
personal_data_uuid: 'pd1',
|
||||
workspace_id: 'ws1',
|
||||
child_id: 'child1',
|
||||
source: 'manual',
|
||||
fetched_at: '2026-01-01',
|
||||
total_days: 480,
|
||||
days_used: 0,
|
||||
days_left: 480,
|
||||
reserved_days_total: 90,
|
||||
reserved_days_left: 90,
|
||||
transferable_days_left: 100,
|
||||
dubbeldagar_left: 60,
|
||||
tier_data: {
|
||||
sjukpenning_total: 390,
|
||||
sjukpenning_used: 0,
|
||||
sjukpenning_left: 390,
|
||||
lagsta_niva_total: 90,
|
||||
lagsta_niva_used: 0,
|
||||
lagsta_niva_left: 90,
|
||||
},
|
||||
created_at: '2026-01-01',
|
||||
updated_at: '2026-01-01',
|
||||
}),
|
||||
json: async () => makeManualFetchResponse(),
|
||||
})
|
||||
|
||||
await api.saveManualFetch({
|
||||
personalDataUuid: 'pd1',
|
||||
workspaceId: 'ws1',
|
||||
childId: 'child1',
|
||||
totalDays: 480,
|
||||
daysUsed: 0,
|
||||
reservedDaysLeft: 90,
|
||||
transferableDaysLeft: 100,
|
||||
sjukpenningUsed: 0,
|
||||
lagstaNivaUsed: 0,
|
||||
sjukpenningLeft: 195,
|
||||
lagstaNivaLeft: 45,
|
||||
dubbeldagarLeft: 60,
|
||||
})
|
||||
|
||||
const body = lastRequestBody()
|
||||
expect(body).toMatchObject({
|
||||
personal_data_uuid: 'pd1',
|
||||
workspace_id: 'ws1',
|
||||
child_id: 'child1',
|
||||
sjukpenning_left: 195,
|
||||
lagsta_niva_left: 45,
|
||||
dubbeldagar_left: 60,
|
||||
})
|
||||
expect(body).not.toHaveProperty('reserved_days_left')
|
||||
expect(body).not.toHaveProperty('total_days')
|
||||
expect(body).not.toHaveProperty('source')
|
||||
})
|
||||
|
||||
it('saveManualFetch sends optional fields, including a null reservedDaysLeft, when provided', async () => {
|
||||
fetchMock.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => makeManualFetchResponse(),
|
||||
})
|
||||
|
||||
await api.saveManualFetch({
|
||||
personalDataUuid: 'pd1',
|
||||
workspaceId: 'ws1',
|
||||
childId: 'child1',
|
||||
sjukpenningLeft: 195,
|
||||
lagstaNivaLeft: 45,
|
||||
dubbeldagarLeft: 60,
|
||||
reservedDaysLeft: null,
|
||||
totalDays: 240,
|
||||
source: 'deduced',
|
||||
})
|
||||
|
||||
expect(lastRequestBody()).toMatchObject({
|
||||
reserved_days_total: 90,
|
||||
dubbeldagar_left: 60,
|
||||
sjukpenning_total: 390,
|
||||
lagsta_niva_total: 90,
|
||||
reserved_days_left: null,
|
||||
total_days: 240,
|
||||
source: 'deduced',
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user