Distinguish the Dashboard and Plan age-4 figures

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RXMhc9nT6sNz1SL4j8dznL
This commit is contained in:
2026-09-14 22:32:54 +02:00
parent dc4c0a5edd
commit 8c2e5dee1b
7 changed files with 74 additions and 8 deletions
@@ -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(<PlanResults proposal={proposal} members={mockMembers} />, '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(<PlanResults proposal={proposal} members={mockMembers} />, '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: [
@@ -11,6 +11,7 @@ const mockWish = {
workspaceId: 'ws1',
childId: null,
name: 'Sommar',
nameMessageKey: null,
windowStartMonth: 6,
windowStartDay: 15,
windowEndMonth: 7,
+2 -2
View File
@@ -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',
+2 -2
View File
@@ -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...',
+2 -2
View File
@@ -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',
+2 -2
View File
@@ -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...',
+32
View File
@@ -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(<Dashboard />)
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(<Dashboard />)
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,