Map the tips payload onto the shape the client declares

Tips rendered as blank cards with unresolved placeholder text in their
accessibility labels. getTips was the one entity fetch in the client with no
translation from the wire's snake_case, so every tip arrived without the key
that selects its title and body, and the lookup silently missed.

The mapper now matches its siblings in that file. A test drives a card through
all eleven message keys the tips engine emits, so a twelfth added later fails
loudly instead of rendering an empty card, and two older tests that stubbed the
tips response as an empty object rather than an empty list have been corrected.
This commit is contained in:
2026-09-17 19:24:26 +02:00
parent b5321c5e8e
commit fd39bd9748
3 changed files with 71 additions and 7 deletions
@@ -74,4 +74,43 @@ describe('TipCard', () => {
const { getByTestId } = render(<TipCard tip={unknownTip} />)
expect(getByTestId('tip-card').textContent).toContain('unknown_key')
})
// Mirrors every `message_key="..."` literal emitted by
// backend/app/services/tips_service.py — verified with:
// git grep -oh 'message_key="[^"]*"' backend/app/services/tips_service.py | sort -u
// This is the test that would have caught the original bug: two of these
// eleven keys had no entry in TIP_TRANSLATION_KEYS, so their cards rendered
// with an empty title and body. If a twelfth backend rule is added and this
// list isn't updated to match, that new key isn't exercised here — but any
// key in this list that TipCard doesn't recognize now fails loudly.
const ALL_BACKEND_MESSAGE_KEYS = [
'tips.use_vacation_before_expiry',
'tips.balance_between_parents',
'tips.preserve_for_age_4',
'tips.use_dubbeldagar_strategically',
'tips.lagsta_niva_coverage',
'tips.stretch_time_at_home',
'tips.sgi_minimum_after_age_1',
'tips.wrap_weekend_with_vacation',
'tips.wrap_red_days_with_vacation',
'tips.long_break_parent_swap',
'tips.reserve_for_preschool_planning_days',
]
it.each(ALL_BACKEND_MESSAGE_KEYS)(
'renders a non-empty, translated title and body for %s',
(messageKey) => {
const tip = { id: 'tip-x', severity: 'info' as const, messageKey, data: {} }
const { getByTestId } = render(<TipCard tip={tip} />)
const title = getByTestId('tip-card').querySelector('.tip-card__title')
const body = getByTestId('tip-card').querySelector('.tip-card__body')
expect(title?.textContent).toBeTruthy()
expect(body?.textContent).toBeTruthy()
// A falsy translation lookup falls back to rendering the raw key —
// that's the "blank card" bug in disguise, so guard against it too.
expect(title?.textContent).not.toBe(messageKey)
expect(body?.textContent).not.toBe(messageKey)
},
)
})