425e29ea21
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>
75 lines
2.7 KiB
TypeScript
75 lines
2.7 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
import { render, fireEvent } from '@testing-library/react'
|
|
import { PlanningGoalSelector } from './PlanningGoalSelector'
|
|
|
|
describe('PlanningGoalSelector', () => {
|
|
it('renders three planning goal options', () => {
|
|
const { getByTestId } = render(
|
|
<PlanningGoalSelector onChange={() => {}} />
|
|
)
|
|
expect(getByTestId('goal-maximize-contiguous')).toBeInTheDocument()
|
|
expect(getByTestId('goal-minimize-stress')).toBeInTheDocument()
|
|
expect(getByTestId('goal-balance')).toBeInTheDocument()
|
|
})
|
|
|
|
it('renders as radio buttons', () => {
|
|
const { getByTestId } = render(
|
|
<PlanningGoalSelector onChange={() => {}} />
|
|
)
|
|
const radio1 = getByTestId('goal-maximize-contiguous') as HTMLInputElement
|
|
const radio2 = getByTestId('goal-minimize-stress') as HTMLInputElement
|
|
expect(radio1.type).toBe('radio')
|
|
expect(radio2.type).toBe('radio')
|
|
})
|
|
|
|
it('checks selected value', () => {
|
|
const { getByTestId } = render(
|
|
<PlanningGoalSelector value="minimize_stress" onChange={() => {}} />
|
|
)
|
|
const radio = getByTestId('goal-minimize-stress') as HTMLInputElement
|
|
expect(radio.checked).toBe(true)
|
|
})
|
|
|
|
it('calls onChange when selecting option', () => {
|
|
const onChange = vi.fn()
|
|
const { getByTestId } = render(
|
|
<PlanningGoalSelector onChange={onChange} />
|
|
)
|
|
const radio = getByTestId('goal-balance')
|
|
fireEvent.click(radio)
|
|
expect(onChange).toHaveBeenCalledWith('balance')
|
|
})
|
|
|
|
it('updates selection when value changes', () => {
|
|
const { getByTestId, rerender } = render(
|
|
<PlanningGoalSelector value="maximize_contiguous" onChange={() => {}} />
|
|
)
|
|
let radio = getByTestId('goal-maximize-contiguous') as HTMLInputElement
|
|
expect(radio.checked).toBe(true)
|
|
|
|
rerender(<PlanningGoalSelector value="balance" onChange={() => {}} />)
|
|
radio = getByTestId('goal-balance') as HTMLInputElement
|
|
expect(radio.checked).toBe(true)
|
|
})
|
|
|
|
it('only one option can be selected at a time', () => {
|
|
const { getByTestId } = render(
|
|
<PlanningGoalSelector value="maximize_contiguous" onChange={() => {}} />
|
|
)
|
|
const radio1 = getByTestId('goal-maximize-contiguous') as HTMLInputElement
|
|
const radio2 = getByTestId('goal-minimize-stress') as HTMLInputElement
|
|
expect(radio1.checked).toBe(true)
|
|
expect(radio2.checked).toBe(false)
|
|
})
|
|
|
|
it('has no selection when value is undefined', () => {
|
|
const { getByTestId } = render(
|
|
<PlanningGoalSelector onChange={() => {}} />
|
|
)
|
|
const radio1 = getByTestId('goal-maximize-contiguous') as HTMLInputElement
|
|
const radio2 = getByTestId('goal-minimize-stress') as HTMLInputElement
|
|
expect(radio1.checked).toBe(false)
|
|
expect(radio2.checked).toBe(false)
|
|
})
|
|
})
|