Files
F-r-ldraledigplannerare/frontend/src/components/layout/Layout.test.tsx
T
jacobh2 ae6db7e16f Redesign the setup flow and consolidate the CSS into one primitives layer
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>
2026-09-19 12:27:29 +02:00

154 lines
4.7 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { MemoryRouter } from 'react-router-dom'
import { Layout } from './Layout'
import { useAuth } from '@/context/AuthContext'
import { useWorkspace } from '@/context/WorkspaceContext'
// Layout imports useAuth/useWorkspace directly from their modules (not the
// @/context barrel), so those exact modules must be mocked here.
vi.mock('@/context/AuthContext')
vi.mock('@/context/WorkspaceContext')
const mockNavigate = vi.fn()
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom')
return {
...actual,
useNavigate: () => mockNavigate,
}
})
const mockUseAuth = vi.mocked(useAuth)
const mockUseWorkspace = vi.mocked(useWorkspace)
function renderLayout(children = <div>page content</div>) {
return render(
<MemoryRouter>
<Layout>{children}</Layout>
</MemoryRouter>,
)
}
describe('Layout', () => {
beforeEach(() => {
vi.clearAllMocks()
mockUseAuth.mockReturnValue({
accessToken: 'token',
isAuthenticated: true,
isLoading: false,
login: vi.fn(),
register: vi.fn(),
logout: vi.fn().mockResolvedValue(undefined),
})
mockUseWorkspace.mockReturnValue({
workspace: null,
isLoading: false,
error: null,
workspaceId: 'ws1',
setWorkspaceId: vi.fn(),
refetch: vi.fn(),
children: [],
selectedChildId: null,
selectChild: vi.fn(),
personalDataUuid: null,
parentName: null,
refreshContext: vi.fn(),
planVersion: 0,
bumpPlanVersion: vi.fn(),
})
})
it('renders children inside the main content area', () => {
renderLayout(<div data-testid="page-content">hello</div>)
expect(screen.getByTestId('layout-main')).toContainElement(screen.getByTestId('page-content'))
})
it('renders Header and Footer', () => {
renderLayout()
expect(screen.getByTestId('header')).toBeInTheDocument()
expect(screen.getByTestId('footer')).toBeInTheDocument()
})
it('passes parent name and children list from workspace context to Header', () => {
mockUseWorkspace.mockReturnValue({
workspace: null,
isLoading: false,
error: null,
workspaceId: 'ws1',
setWorkspaceId: vi.fn(),
refetch: vi.fn(),
children: [
{ id: 'c1', name: 'Emma', birth_date: '2024-01-01' },
{ id: 'c2', name: 'Oscar', birth_date: '2025-01-01' },
],
selectedChildId: 'c1',
selectChild: vi.fn(),
personalDataUuid: 'pd1',
parentName: 'Elin',
refreshContext: vi.fn(),
planVersion: 0,
bumpPlanVersion: vi.fn(),
})
renderLayout()
expect(screen.getByTestId('parent-name')).toHaveTextContent('Elin')
expect(screen.getByTestId('child-select')).toBeInTheDocument()
expect(screen.getByDisplayValue('Emma')).toBeInTheDocument()
})
it('selects the correct child when selectedChildId matches', () => {
const selectChild = vi.fn()
mockUseWorkspace.mockReturnValue({
workspace: null,
isLoading: false,
error: null,
workspaceId: 'ws1',
setWorkspaceId: vi.fn(),
refetch: vi.fn(),
children: [{ id: 'c1', name: 'Emma', birth_date: '2024-01-01' }],
selectedChildId: 'c1',
selectChild,
personalDataUuid: 'pd1',
parentName: null,
refreshContext: vi.fn(),
planVersion: 0,
bumpPlanVersion: vi.fn(),
})
renderLayout()
expect(screen.getByDisplayValue('Emma')).toBeInTheDocument()
})
it('logs out and navigates to /auth when the logout button is clicked', async () => {
const logout = vi.fn().mockResolvedValue(undefined)
mockUseAuth.mockReturnValue({
accessToken: 'token',
isAuthenticated: true,
isLoading: false,
login: vi.fn(),
register: vi.fn(),
logout,
})
renderLayout()
await userEvent.click(screen.getByTestId('logout-button'))
expect(logout).toHaveBeenCalled()
expect(mockNavigate).toHaveBeenCalledWith('/auth', { replace: true })
})
it('still navigates to /auth when logout rejects (S3 — best-effort server call)', async () => {
const logout = vi.fn().mockRejectedValue(new Error('network down'))
mockUseAuth.mockReturnValue({
accessToken: 'token',
isAuthenticated: true,
isLoading: false,
login: vi.fn(),
register: vi.fn(),
logout,
})
renderLayout()
await userEvent.click(screen.getByTestId('logout-button'))
expect(logout).toHaveBeenCalled()
expect(mockNavigate).toHaveBeenCalledWith('/auth', { replace: true })
})
})