import { describe, it, expect, vi } from 'vitest'
import { render, fireEvent } from '@testing-library/react'
import { TipCard } from './TipCard'
import { sv } from '@/i18n/sv'
describe('TipCard', () => {
const mockTip = {
id: 'tip1',
severity: 'info' as const,
messageKey: 'tips.wrap_red_days_with_vacation',
data: { parent_name: 'Alex', cluster_start: '2026-12-24', cluster_end: '2026-12-26' },
}
it('renders tip card with title', () => {
const { getByTestId } = render()
expect(getByTestId('tip-card')).toBeInTheDocument()
expect(getByTestId('tip-card').textContent).toContain(sv['tip.wrapRedDaysWithVacation.title'])
})
it('applies severity class', () => {
const { getByTestId } = render()
expect(getByTestId('tip-card')).toHaveClass('tip-card--info')
})
it('renders warning severity class', () => {
const warningTip = { ...mockTip, severity: 'warning' as const }
const { getByTestId } = render()
expect(getByTestId('tip-card')).toHaveClass('tip-card--warning')
})
it('renders action severity class', () => {
const actionTip = { ...mockTip, severity: 'action' as const }
const { getByTestId } = render()
expect(getByTestId('tip-card')).toHaveClass('tip-card--action')
})
it('renders dismiss button when onDismiss provided', () => {
const { getByTestId } = render(
{}} />
)
expect(getByTestId('dismiss-button')).toBeInTheDocument()
})
it('does not render dismiss button when onDismiss not provided', () => {
const { queryByTestId } = render()
expect(queryByTestId('dismiss-button')).not.toBeInTheDocument()
})
it('calls onDismiss with tip id when dismissed', () => {
const onDismiss = vi.fn()
const { getByTestId } = render(
)
fireEvent.click(getByTestId('dismiss-button'))
expect(onDismiss).toHaveBeenCalledWith('tip1')
})
it('interpolates the tip data into the translated body text', () => {
const { getByTestId } = render()
expect(getByTestId('tip-card').textContent).toContain('Alex')
expect(getByTestId('tip-card').textContent).toContain('2026-12-24')
})
it('provides accessibility label', () => {
const { getByTestId } = render()
expect(getByTestId('tip-card')).toHaveAttribute(
'aria-label',
`Tips: ${sv['tip.wrapRedDaysWithVacation.title']}`,
)
})
it('maps unknown messageKey to itself', () => {
const unknownTip = { ...mockTip, messageKey: 'unknown_key' }
const { getByTestId } = render()
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.age4_pooling_loss_exact',
'tips.age4_pooling_loss_hedged',
'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()
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)
},
)
})