Files
F-r-ldraledigplannerare/frontend/src/pages/Auth.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

183 lines
6.4 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { Auth } from './Auth'
import { useAuth } from '@/context/AuthContext'
import { useWorkspace } from '@/context/WorkspaceContext'
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)
describe('Auth', () => {
const mockLogin = vi.fn()
const mockRegister = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
mockUseAuth.mockReturnValue({
accessToken: null,
isAuthenticated: false,
isLoading: false,
login: mockLogin,
register: mockRegister,
logout: vi.fn(),
})
mockUseWorkspace.mockReturnValue({
workspace: null,
isLoading: false,
error: null,
workspaceId: null,
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 the login form by default, with the login tab active', () => {
render(<Auth />)
expect(screen.getByRole('heading', { name: 'Logga in' })).toBeInTheDocument()
expect(screen.getByPlaceholderText('E-post')).toBeInTheDocument()
expect(screen.getByPlaceholderText('Lösenord')).toBeInTheDocument()
expect(screen.queryByPlaceholderText('Namn')).not.toBeInTheDocument()
// Both tabs' text is unique except "Logga in", which the login-mode
// submit button also renders — the tabs carry their own testids so
// these queries don't collide with it.
expect(screen.getByTestId('auth-tab-login')).toHaveAttribute('aria-pressed', 'true')
expect(screen.getByTestId('auth-tab-register')).toHaveAttribute('aria-pressed', 'false')
})
it('switches to the registration form via the tab', () => {
render(<Auth />)
fireEvent.click(screen.getByTestId('auth-tab-register'))
expect(screen.getByRole('heading', { name: 'Skapa konto' })).toBeInTheDocument()
expect(screen.getByPlaceholderText('Namn')).toBeInTheDocument()
expect(screen.getByTestId('auth-tab-register')).toHaveAttribute('aria-pressed', 'true')
expect(screen.getByTestId('auth-tab-login')).toHaveAttribute('aria-pressed', 'false')
})
it('switches back to the login form via the tab', () => {
render(<Auth />)
fireEvent.click(screen.getByTestId('auth-tab-register'))
fireEvent.click(screen.getByTestId('auth-tab-login'))
expect(screen.getByRole('heading', { name: 'Logga in' })).toBeInTheDocument()
expect(screen.queryByPlaceholderText('Namn')).not.toBeInTheDocument()
})
it('clears the error banner when switching between the Log in and Create account tabs', async () => {
mockLogin.mockRejectedValue(new Error('Fel användarnamn'))
render(<Auth />)
fireEvent.click(screen.getByTestId('auth-submit'))
expect(await screen.findByRole('alert')).toHaveTextContent('Fel användarnamn')
fireEvent.click(screen.getByTestId('auth-tab-register'))
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})
it('logs in with the entered credentials and navigates home', async () => {
mockLogin.mockResolvedValue(undefined)
render(<Auth />)
fireEvent.change(screen.getByPlaceholderText('E-post'), { target: { value: 'a@b.com' } })
fireEvent.change(screen.getByPlaceholderText('Lösenord'), { target: { value: 'secret' } })
fireEvent.click(screen.getByTestId('auth-submit'))
await waitFor(() => expect(mockLogin).toHaveBeenCalledWith({ email: 'a@b.com', password: 'secret' }))
expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true })
})
it('registers with the entered details and navigates home', async () => {
mockRegister.mockResolvedValue(undefined)
render(<Auth />)
fireEvent.click(screen.getByTestId('auth-tab-register'))
fireEvent.change(screen.getByPlaceholderText('Namn'), { target: { value: 'Alex' } })
fireEvent.change(screen.getByPlaceholderText('E-post'), { target: { value: 'a@b.com' } })
fireEvent.change(screen.getByPlaceholderText('Lösenord'), { target: { value: 'secret' } })
fireEvent.click(screen.getByTestId('auth-submit'))
await waitFor(() =>
expect(mockRegister).toHaveBeenCalledWith({ email: 'a@b.com', password: 'secret', displayName: 'Alex' }),
)
expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true })
})
it('shows the error message from a failed login', async () => {
mockLogin.mockRejectedValue(new Error('Fel användarnamn'))
render(<Auth />)
fireEvent.click(screen.getByTestId('auth-submit'))
expect(await screen.findByRole('alert')).toHaveTextContent('Fel användarnamn')
})
it('shows a generic error message when login rejects with a non-Error value', async () => {
mockLogin.mockRejectedValue('nope')
render(<Auth />)
fireEvent.click(screen.getByTestId('auth-submit'))
expect(await screen.findByRole('alert')).toHaveTextContent('Något gick fel')
})
it('redirects to the dashboard once authenticated and the workspace has loaded', () => {
mockUseAuth.mockReturnValue({
accessToken: 'token',
isAuthenticated: true,
isLoading: false,
login: mockLogin,
register: mockRegister,
logout: vi.fn(),
})
render(<Auth />)
expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true })
})
it('does not redirect while the workspace is still loading', () => {
mockUseAuth.mockReturnValue({
accessToken: 'token',
isAuthenticated: true,
isLoading: false,
login: mockLogin,
register: mockRegister,
logout: vi.fn(),
})
mockUseWorkspace.mockReturnValue({
workspace: null,
isLoading: true,
error: null,
workspaceId: null,
setWorkspaceId: vi.fn(),
refetch: vi.fn(),
children: [],
selectedChildId: null,
selectChild: vi.fn(),
personalDataUuid: null,
parentName: null,
refreshContext: vi.fn(),
planVersion: 0,
bumpPlanVersion: vi.fn(),
})
render(<Auth />)
expect(mockNavigate).not.toHaveBeenCalled()
})
})