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>
154 lines
4.7 KiB
TypeScript
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 })
|
|
})
|
|
})
|