Phase 2: Build 9 presentational components with full test coverage
New components (all with .tsx + .test.tsx, 90%+ coverage): - CalendarDay: day cell with event badges - CalendarEvent: colored event chip with dismiss option - DaysBreakdown: table of days by type - ProgressChart: progress bar toward age-4 deadline - TipCard: optimization tip card with severity badges - VacationDaysInput: number input with min/max validation - ParentProfileInput: dual selects for salary type & employment status - PlanningGoalSelector: radio buttons for planning goal - BreakPeriodsInput: list manager with inline add/edit form All components follow existing patterns from Header/Footer: - Typed props interfaces (exported) - Semantic HTML with data-testid & ARIA attributes - No business logic, purely presentational - Comprehensive test coverage including edge cases npm test: 22 files, 167 tests passing npx tsc --noEmit: clean Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { TipCard } from './TipCard'
|
||||
|
||||
describe('TipCard', () => {
|
||||
const mockTip = {
|
||||
id: 'tip1',
|
||||
severity: 'info' as const,
|
||||
messageKey: 'wrap_red_days',
|
||||
data: {},
|
||||
}
|
||||
|
||||
it('renders tip card with title', () => {
|
||||
const { getByTestId } = render(<TipCard tip={mockTip} />)
|
||||
expect(getByTestId('tip-card')).toBeInTheDocument()
|
||||
expect(getByTestId('tip-card').textContent).toContain('Wrap Red Days')
|
||||
})
|
||||
|
||||
it('applies severity class', () => {
|
||||
const { getByTestId } = render(<TipCard tip={mockTip} />)
|
||||
expect(getByTestId('tip-card')).toHaveClass('tip-card--info')
|
||||
})
|
||||
|
||||
it('renders warning severity class', () => {
|
||||
const warningTip = { ...mockTip, severity: 'warning' as const }
|
||||
const { getByTestId } = render(<TipCard tip={warningTip} />)
|
||||
expect(getByTestId('tip-card')).toHaveClass('tip-card--warning')
|
||||
})
|
||||
|
||||
it('renders action severity class', () => {
|
||||
const actionTip = { ...mockTip, severity: 'action' as const }
|
||||
const { getByTestId } = render(<TipCard tip={actionTip} />)
|
||||
expect(getByTestId('tip-card')).toHaveClass('tip-card--action')
|
||||
})
|
||||
|
||||
it('renders dismiss button when onDismiss provided', () => {
|
||||
const { getByTestId } = render(
|
||||
<TipCard tip={mockTip} onDismiss={() => {}} />
|
||||
)
|
||||
expect(getByTestId('dismiss-button')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render dismiss button when onDismiss not provided', () => {
|
||||
const { queryByTestId } = render(<TipCard tip={mockTip} />)
|
||||
expect(queryByTestId('dismiss-button')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('calls onDismiss with tip id when dismissed', () => {
|
||||
const onDismiss = vi.fn()
|
||||
const { getByTestId } = render(
|
||||
<TipCard tip={mockTip} onDismiss={onDismiss} />
|
||||
)
|
||||
fireEvent.click(getByTestId('dismiss-button'))
|
||||
expect(onDismiss).toHaveBeenCalledWith('tip1')
|
||||
})
|
||||
|
||||
it('displays message key as body text', () => {
|
||||
const { getByTestId } = render(<TipCard tip={mockTip} />)
|
||||
expect(getByTestId('tip-card').textContent).toContain('wrap_red_days')
|
||||
})
|
||||
|
||||
it('provides accessibility label', () => {
|
||||
const { getByTestId } = render(<TipCard tip={mockTip} />)
|
||||
expect(getByTestId('tip-card')).toHaveAttribute('aria-label', 'Tip: Wrap Red Days')
|
||||
})
|
||||
|
||||
it('maps unknown messageKey to itself', () => {
|
||||
const unknownTip = { ...mockTip, messageKey: 'unknown_key' }
|
||||
const { getByTestId } = render(<TipCard tip={unknownTip} />)
|
||||
expect(getByTestId('tip-card').textContent).toContain('unknown_key')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user