From 8c2e5dee1ba496452b766884395e02bf0fa94d19 Mon Sep 17 00:00:00 2001 From: Jacob Hagstedt Date: Mon, 14 Sep 2026 22:32:54 +0200 Subject: [PATCH] Distinguish the Dashboard and Plan age-4 figures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both screens showed an "expiring at age 4" number under near-identical labels, and they disagreed -- 143 on the Dashboard versus 100 on the Plan tab for the same parent. Both were correct: the Dashboard reflects the saved plan, the Plan tab a proposal that has not been applied and which simulates future recurring wishes. The numbers are left untouched; forcing them to agree would make one of them wrong. Instead each label now states its own basis ("om nuvarande plan följs" / "om detta förslag tillämpas"), and the Plan tab's intro says outright that the saved plan may differ, and why. Tests assert the distinguishing copy on both screens so the two cannot silently drift back to indistinguishable labels. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RXMhc9nT6sNz1SL4j8dznL --- .../components/planning/PlanResults.test.tsx | 33 +++++++++++++++++++ frontend/src/hooks/useLeaveWishes.test.ts | 1 + frontend/src/i18n/en/dashboard.ts | 4 +-- frontend/src/i18n/en/plan.ts | 4 +-- frontend/src/i18n/sv/dashboard.ts | 4 +-- frontend/src/i18n/sv/plan.ts | 4 +-- frontend/src/pages/Dashboard.test.tsx | 32 ++++++++++++++++++ 7 files changed, 74 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/planning/PlanResults.test.tsx b/frontend/src/components/planning/PlanResults.test.tsx index 1ae1b6a..ec945bf 100644 --- a/frontend/src/components/planning/PlanResults.test.tsx +++ b/frontend/src/components/planning/PlanResults.test.tsx @@ -363,6 +363,39 @@ describe('PlanResults', () => { expect(table).toHaveTextContent('10') }) + // The Dashboard's own expiring-at-4 figure reflects the currently saved + // plan; this table reflects an unsaved proposal and can legitimately show + // a different number. The title/intro must say so, so the two screens are + // never mistaken for showing the same thing (types/entities.ts: + // PlanAge4Projection vs Age4Projection). + it('labels the age-4 projection section as previewing this unsaved proposal, in Swedish', () => { + const proposal = buildProposal({ + age4Projections: [ + { parentPersonalDataUuid: 'pd1', childId: 'child1', fourthBirthday: '2030-03-01', projectedLeftAtB4: 106, savableAfterB4: 96, expiringAtB4: 10 }, + ], + }) + renderLocalized(, 'sv') + + const section = screen.getByTestId('plan-age4-projections') + expect(section).toHaveTextContent('Vid barnets 4-årsdag, om detta förslag tillämpas') + expect(section).toHaveTextContent('om just detta förslag tillämpas') + expect(section).toHaveTextContent('Din sparade plan') + }) + + it('labels the age-4 projection section as previewing this unsaved proposal, in English', () => { + const proposal = buildProposal({ + age4Projections: [ + { parentPersonalDataUuid: 'pd1', childId: 'child1', fourthBirthday: '2030-03-01', projectedLeftAtB4: 106, savableAfterB4: 96, expiringAtB4: 10 }, + ], + }) + renderLocalized(, 'en') + + const section = screen.getByTestId('plan-age4-projections') + expect(section).toHaveTextContent("At the child's 4th birthday, if this proposal is applied") + expect(section).toHaveTextContent('if this proposal is applied') + expect(section).toHaveTextContent('Your saved plan') + }) + it('shows an assumed badge for a vacation year with no entered data (§3.2)', () => { const proposal = buildProposal({ vacationBalances: [ diff --git a/frontend/src/hooks/useLeaveWishes.test.ts b/frontend/src/hooks/useLeaveWishes.test.ts index e0931c5..ec3fb93 100644 --- a/frontend/src/hooks/useLeaveWishes.test.ts +++ b/frontend/src/hooks/useLeaveWishes.test.ts @@ -11,6 +11,7 @@ const mockWish = { workspaceId: 'ws1', childId: null, name: 'Sommar', + nameMessageKey: null, windowStartMonth: 6, windowStartDay: 15, windowEndMonth: 7, diff --git a/frontend/src/i18n/en/dashboard.ts b/frontend/src/i18n/en/dashboard.ts index 62295a3..23b161d 100644 --- a/frontend/src/i18n/en/dashboard.ts +++ b/frontend/src/i18n/en/dashboard.ts @@ -8,8 +8,8 @@ export const dashboard = { 'dashboard.createChildError': 'Could not create child', 'dashboard.daysLeft': 'Days left', 'dashboard.enterManually': 'Enter details manually', - 'dashboard.expiringAtB4': 'Total expiring at age 4 (sum of all parents)', - 'dashboard.expiringAtB4ForParent': 'Expiring at age 4 – {name}', + 'dashboard.expiringAtB4': 'Expiring at age 4 if the current plan is followed (all parents)', + 'dashboard.expiringAtB4ForParent': 'Expiring at age 4 if the current plan is followed – {name}', 'dashboard.expiringAtB4ParentFallback': 'Parent {number}', 'dashboard.fetchFromFk': 'Fetch from Försäkringskassan', 'dashboard.fillAllFields': 'Fill in all fields', diff --git a/frontend/src/i18n/en/plan.ts b/frontend/src/i18n/en/plan.ts index 79f102b..9173bc3 100644 --- a/frontend/src/i18n/en/plan.ts +++ b/frontend/src/i18n/en/plan.ts @@ -9,8 +9,8 @@ export const plan = { 'plan.title': 'Planning', 'plan.updateWishError': 'Could not save wish', 'plan.wishesTitle': 'Your wishes', - 'planResults.age4ProjectionsIntro': 'Here\'s what the 4th birthday looks like if this plan is taken as proposed — life may of course work out differently.', - 'planResults.age4ProjectionsTitle': 'At the child\'s 4th birthday, if this plan is followed', + 'planResults.age4ProjectionsIntro': 'Here\'s what the 4th birthday looks like if this proposal is applied, including future recurring wishes. Your saved plan (under Overview) may show a different result.', + 'planResults.age4ProjectionsTitle': 'At the child\'s 4th birthday, if this proposal is applied', 'planResults.applyButton': 'Use plan', 'planResults.applySavedPrefix': 'The plan has been saved!', 'planResults.applyingButton': 'Saving...', diff --git a/frontend/src/i18n/sv/dashboard.ts b/frontend/src/i18n/sv/dashboard.ts index 0c1777b..bf51f53 100644 --- a/frontend/src/i18n/sv/dashboard.ts +++ b/frontend/src/i18n/sv/dashboard.ts @@ -8,8 +8,8 @@ export const dashboard = { 'dashboard.createChildError': 'Kunde inte skapa barn', 'dashboard.daysLeft': 'Kvarvarande dagar', 'dashboard.enterManually': 'Ange uppgifter manuellt', - 'dashboard.expiringAtB4': 'Totalt förfaller vid 4 års ålder (summa av alla föräldrar)', - 'dashboard.expiringAtB4ForParent': 'Förfaller vid 4 års ålder – {name}', + 'dashboard.expiringAtB4': 'Förfaller vid 4 år om nuvarande plan följs (alla föräldrar)', + 'dashboard.expiringAtB4ForParent': 'Förfaller vid 4 år om nuvarande plan följs – {name}', 'dashboard.expiringAtB4ParentFallback': 'Förälder {number}', 'dashboard.fetchFromFk': 'Hämta från Försäkringskassan', 'dashboard.fillAllFields': 'Fyll i alla fält', diff --git a/frontend/src/i18n/sv/plan.ts b/frontend/src/i18n/sv/plan.ts index 851402e..cb9ccec 100644 --- a/frontend/src/i18n/sv/plan.ts +++ b/frontend/src/i18n/sv/plan.ts @@ -9,8 +9,8 @@ export const plan = { 'plan.title': 'Planering', 'plan.updateWishError': 'Kunde inte spara önskemål', 'plan.wishesTitle': 'Dina önskemål', - 'planResults.age4ProjectionsIntro': 'Så här ser det ut vid 4-årsdagen om den här planen tas ut som föreslaget — livet kan förstås bli annorlunda.', - 'planResults.age4ProjectionsTitle': 'Vid barnets 4-årsdag, om planen genomförs', + 'planResults.age4ProjectionsIntro': 'Så här ser det ut vid 4-årsdagen om just detta förslag tillämpas, inklusive kommande års återkommande önskemål. Din sparade plan (under Översikt) kan visa ett annat resultat.', + 'planResults.age4ProjectionsTitle': 'Vid barnets 4-årsdag, om detta förslag tillämpas', 'planResults.applyButton': 'Använd plan', 'planResults.applySavedPrefix': 'Planen har sparats!', 'planResults.applyingButton': 'Sparar...', diff --git a/frontend/src/pages/Dashboard.test.tsx b/frontend/src/pages/Dashboard.test.tsx index 050cff6..11819c2 100644 --- a/frontend/src/pages/Dashboard.test.tsx +++ b/frontend/src/pages/Dashboard.test.tsx @@ -201,6 +201,38 @@ describe('Dashboard', () => { expect(screen.getByText(/Återstående till 4 års ålder/)).toBeInTheDocument() }) + // The combined expiring-at-4 figure reflects the currently saved plan, while + // the Plan tab's own figure (PlanResults.tsx) reflects an unsaved proposal — + // the label must say so, so the two screens are never confused for showing + // the same thing. See PlanAge4Projection vs Age4Projection in types/entities.ts. + it('labels the combined expiring-at-4 metric as reflecting the current saved plan', () => { + renderWithRouter() + expect(screen.getByTestId('metric-expiring-combined')).toHaveTextContent( + 'Förfaller vid 4 år om nuvarande plan följs (alla föräldrar)', + ) + }) + + it('labels the per-parent expiring-at-4 metric as reflecting the current saved plan', () => { + vi.mocked(hooks.useAnalytics).mockReturnValue({ + data: { + ...mockAnalyticsData, + parentProjections: [ + { personalDataUuid: 'pd-1', childId: 'c-1', birthDate: '2022-01-01', fourthBirthday: '2026-01-01', totalDays: 240, daysUsed: 30, plannedDaysBeforeB4: 10, projectedLeftAtB4: 200, savableAfterB4: 96, expiringAtB4: 60 }, + { personalDataUuid: 'pd-2', childId: 'c-1', birthDate: '2022-01-01', fourthBirthday: '2026-01-01', totalDays: 240, daysUsed: 20, plannedDaysBeforeB4: 5, projectedLeftAtB4: 210, savableAfterB4: 96, expiringAtB4: 44 }, + ], + combinedExpiringAtB4: 104, + }, + isLoading: false, + error: null, + refresh: vi.fn(), + } as any) + + renderWithRouter() + expect(screen.getByTestId('metric-expiring-parent-pd-1')).toHaveTextContent( + 'Förfaller vid 4 år om nuvarande plan följs – Alex', + ) + }) + it('displays error message if analytics fails', () => { vi.mocked(hooks.useAnalytics).mockReturnValue({ data: null,