ae6db7e16f
Onboarding asks explicitly for one or two parents and shows one card per parent for balances; the balance form is per parent with inline validation; the partner can be filled from the FK deduction or added from it. Settings and Dashboard reuse the same form. CSS: bare buttons carry no colour any more; buttons, cards, choice cards, fields, alerts, badges, eyebrow and modal chrome are defined once in primitives.css. Media queries live only in responsive.css. A style guard test blocks hex, !important and stray media queries. Bilingual fixes: calendar selects size by content, header no longer wraps. Vitest is capped at four workers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
130 lines
4.7 KiB
TypeScript
130 lines
4.7 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
import { render, screen } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { BrowserRouter, MemoryRouter } from 'react-router-dom'
|
|
import { Header } from './Header'
|
|
|
|
const renderWithRouter = (element: React.ReactElement) => {
|
|
return render(
|
|
<BrowserRouter>
|
|
{element}
|
|
</BrowserRouter>,
|
|
)
|
|
}
|
|
|
|
const renderAtPath = (element: React.ReactElement, path: string) => {
|
|
return render(
|
|
<MemoryRouter initialEntries={[path]}>
|
|
{element}
|
|
</MemoryRouter>,
|
|
)
|
|
}
|
|
|
|
describe('Header', () => {
|
|
it('renders header with default title', () => {
|
|
renderWithRouter(<Header />)
|
|
expect(screen.getByTestId('header-title')).toHaveTextContent('Föräldraledigplannerare')
|
|
})
|
|
|
|
it('renders parent name when provided', () => {
|
|
renderWithRouter(<Header parentName="Elin" />)
|
|
expect(screen.getByTestId('parent-name')).toHaveTextContent('Elin')
|
|
})
|
|
|
|
it('renders child select when children provided', () => {
|
|
const childrenList = [
|
|
{ id: '1', name: 'Emma' },
|
|
{ id: '2', name: 'Oscar' },
|
|
]
|
|
renderWithRouter(<Header childrenList={childrenList} selectedChildId="1" />)
|
|
expect(screen.getByTestId('child-select')).toBeInTheDocument()
|
|
expect(screen.getByDisplayValue('Emma')).toBeInTheDocument()
|
|
})
|
|
|
|
it('calls onChildSelect when child selection changes', async () => {
|
|
const onChildSelect = vi.fn()
|
|
const childrenList = [
|
|
{ id: '1', name: 'Emma' },
|
|
{ id: '2', name: 'Oscar' },
|
|
]
|
|
renderWithRouter(
|
|
<Header childrenList={childrenList} selectedChildId="1" onChildSelect={onChildSelect} />,
|
|
)
|
|
const select = screen.getByTestId('child-select') as HTMLSelectElement
|
|
await userEvent.selectOptions(select, '2')
|
|
expect(onChildSelect).toHaveBeenCalledWith('2')
|
|
})
|
|
|
|
it('renders navigation links', () => {
|
|
renderWithRouter(<Header />)
|
|
expect(screen.getByText('Översikt')).toBeInTheDocument()
|
|
expect(screen.getByText('Kalender')).toBeInTheDocument()
|
|
expect(screen.getByText('Inställningar')).toBeInTheDocument()
|
|
})
|
|
|
|
it('does not render the retired Plan/Tips nav entries', () => {
|
|
renderWithRouter(<Header />)
|
|
expect(screen.queryByText('Planering')).not.toBeInTheDocument()
|
|
expect(screen.queryByText('Tips')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('sets an aria-label on the child select for accessibility', () => {
|
|
const childrenList = [
|
|
{ id: '1', name: 'Emma' },
|
|
{ id: '2', name: 'Oscar' },
|
|
]
|
|
renderWithRouter(<Header childrenList={childrenList} selectedChildId="1" />)
|
|
expect(screen.getByTestId('child-select')).toHaveAttribute('aria-label', 'Välj barn')
|
|
})
|
|
|
|
it('sets an aria-label on the logout button for accessibility', () => {
|
|
renderWithRouter(<Header onLogout={vi.fn()} />)
|
|
expect(screen.getByTestId('logout-button')).toHaveAttribute('aria-label', 'Logga ut')
|
|
})
|
|
|
|
it('renders logout button when onLogout provided', () => {
|
|
const onLogout = vi.fn()
|
|
renderWithRouter(<Header onLogout={onLogout} />)
|
|
const button = screen.getByTestId('logout-button')
|
|
expect(button).toBeInTheDocument()
|
|
})
|
|
|
|
it('calls onLogout when logout button clicked', async () => {
|
|
const onLogout = vi.fn()
|
|
renderWithRouter(<Header onLogout={onLogout} />)
|
|
const button = screen.getByTestId('logout-button')
|
|
await userEvent.click(button)
|
|
expect(onLogout).toHaveBeenCalled()
|
|
})
|
|
|
|
it('does not render logout button when not provided', () => {
|
|
renderWithRouter(<Header />)
|
|
expect(screen.queryByTestId('logout-button')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('does not crash and renders the select when selectedChildId is not provided (falls back to "")', () => {
|
|
const childrenList = [
|
|
{ id: '1', name: 'Emma' },
|
|
{ id: '2', name: 'Oscar' },
|
|
]
|
|
renderWithRouter(<Header childrenList={childrenList} />)
|
|
const select = screen.getByTestId('child-select') as HTMLSelectElement
|
|
// selectedChildId ?? '' binds the select to a value ('') that matches no
|
|
// <option>, so the browser falls back to the first option — it must not
|
|
// be coerced to something else (e.g. "undefined" or a stale selection).
|
|
expect(select.value).toBe('1')
|
|
})
|
|
|
|
it('marks the calendar link active on the calendar route', () => {
|
|
renderAtPath(<Header />, '/calendar')
|
|
expect(screen.getByText('Kalender')).toHaveAttribute('aria-current', 'page')
|
|
expect(screen.getByText('Översikt')).not.toHaveAttribute('aria-current', 'page')
|
|
})
|
|
|
|
it('marks the settings link active on the settings route, and the overview link inactive', () => {
|
|
renderAtPath(<Header />, '/settings')
|
|
expect(screen.getByText('Inställningar')).toHaveAttribute('aria-current', 'page')
|
|
expect(screen.getByText('Översikt')).not.toHaveAttribute('aria-current', 'page')
|
|
})
|
|
})
|