a4fce16dd1
Registering used to land you on a bare form asking for a child, with no hint what came next. A short wizard now walks through the child, whether you are planning alone or together, and where the day balances come from: fetched from Försäkringskassan, entered by hand, or skipped — and if skipped, the figures carry a badge saying they are estimates until real ones arrive. Fetching is the first option rather than a hidden one, so the scraper is on by default and the script that used to force it on no longer needs to. Planning and Tips lose their tabs. Authoring moved to the calendar and the figures moved to the overview, so both pages were only a second way to reach what is already on screen; their routes now redirect rather than dead-end. The dead translations went with them. Dates also follow the chosen language now instead of always formatting as Swedish.
172 lines
5.6 KiB
TypeScript
172 lines
5.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { render, screen, waitFor } from '@testing-library/react'
|
|
import * as api from '@/services/api'
|
|
import { App } from './App'
|
|
|
|
// Mock API service - AuthProvider calls refreshToken on mount
|
|
vi.mock('@/services/api')
|
|
|
|
// Mock WorkspaceProvider to avoid workspace loading logic
|
|
vi.mock('@/context/WorkspaceContext', () => ({
|
|
WorkspaceProvider: ({ children }: { children: React.ReactNode }) => (
|
|
<div data-testid="workspace-provider">{children}</div>
|
|
),
|
|
}))
|
|
|
|
// Mock all page components to verify routing
|
|
vi.mock('@/pages/Auth', () => ({
|
|
Auth: () => <div data-testid="auth-page">Auth Page</div>,
|
|
}))
|
|
|
|
vi.mock('@/pages/Calendar', () => ({
|
|
Calendar: () => <div data-testid="calendar-page">Calendar Page</div>,
|
|
}))
|
|
|
|
vi.mock('@/pages/Dashboard', () => ({
|
|
Dashboard: () => <div data-testid="dashboard-page">Dashboard Page</div>,
|
|
}))
|
|
|
|
vi.mock('@/pages/Settings', () => ({
|
|
Settings: () => <div data-testid="settings-page">Settings Page</div>,
|
|
}))
|
|
|
|
vi.mock('@/pages/DevFetch', () => ({
|
|
DevFetch: () => <div data-testid="dev-fetch-page">DevFetch Page</div>,
|
|
}))
|
|
|
|
// App supplies its own BrowserRouter. Replace it with a pass-through
|
|
// so MemoryRouter can control routing in tests.
|
|
vi.mock('react-router-dom', async () => {
|
|
const actual = await vi.importActual('react-router-dom')
|
|
return {
|
|
...actual,
|
|
BrowserRouter: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
|
}
|
|
})
|
|
|
|
// Import MemoryRouter from the mocked module (gets the real one via spread)
|
|
import { MemoryRouter } from 'react-router-dom'
|
|
|
|
describe('App', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
const renderWithRoute = (route: string = '/') => {
|
|
return render(
|
|
<MemoryRouter initialEntries={[route]}>
|
|
<App />
|
|
</MemoryRouter>
|
|
)
|
|
}
|
|
|
|
describe('auth state handling', () => {
|
|
it('shows loading state when auth is initializing', () => {
|
|
// Mock refreshToken to never resolve (simulates loading state)
|
|
vi.mocked(api.refreshToken).mockImplementation(() => new Promise(() => {}))
|
|
|
|
renderWithRoute('/')
|
|
expect(screen.getByText('Laddar…')).toBeInTheDocument()
|
|
})
|
|
|
|
it('navigates to auth page when not authenticated', async () => {
|
|
// Mock refreshToken to reject (no valid session)
|
|
vi.mocked(api.refreshToken).mockRejectedValue(new Error('Unauthorized'))
|
|
|
|
renderWithRoute('/')
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('auth-page')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
it('shows dashboard when authenticated', async () => {
|
|
// Mock refreshToken to return a token (user is authenticated)
|
|
vi.mocked(api.refreshToken).mockResolvedValue({
|
|
access_token: 'test-token',
|
|
token_type: 'bearer',
|
|
expires_in: 3600,
|
|
})
|
|
vi.mocked(api.setAccessToken).mockImplementation(() => {})
|
|
|
|
renderWithRoute('/')
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('dashboard-page')).toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('protected routes', () => {
|
|
it('renders protected route when authenticated', async () => {
|
|
// Test that calendar (another protected route) also works when authenticated
|
|
vi.mocked(api.refreshToken).mockResolvedValue({
|
|
access_token: 'test-token',
|
|
token_type: 'bearer',
|
|
expires_in: 3600,
|
|
})
|
|
vi.mocked(api.setAccessToken).mockImplementation(() => {})
|
|
|
|
renderWithRoute('/calendar')
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('calendar-page')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
it('redirects to auth page when accessing protected route without authentication', async () => {
|
|
// Even on a protected route like /calendar, unauthenticated users are sent to auth
|
|
vi.mocked(api.refreshToken).mockRejectedValue(new Error('Unauthorized'))
|
|
|
|
renderWithRoute('/calendar')
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('auth-page')).toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('retired /plan and /tips routes', () => {
|
|
// The Plan and Tips tabs were removed (leave is now authored on the
|
|
// calendar, and Översikt carries the headline figures) — neither route
|
|
// is registered any more, so both now fall through to the catch-all
|
|
// and redirect to "/" instead of rendering a page or 404ing.
|
|
it('redirects /plan to the dashboard when authenticated', async () => {
|
|
vi.mocked(api.refreshToken).mockResolvedValue({
|
|
access_token: 'test-token',
|
|
token_type: 'bearer',
|
|
expires_in: 3600,
|
|
})
|
|
vi.mocked(api.setAccessToken).mockImplementation(() => {})
|
|
|
|
renderWithRoute('/plan')
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('dashboard-page')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
it('redirects /tips to the dashboard when authenticated', async () => {
|
|
vi.mocked(api.refreshToken).mockResolvedValue({
|
|
access_token: 'test-token',
|
|
token_type: 'bearer',
|
|
expires_in: 3600,
|
|
})
|
|
vi.mocked(api.setAccessToken).mockImplementation(() => {})
|
|
|
|
renderWithRoute('/tips')
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('dashboard-page')).toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('app structure', () => {
|
|
it('wraps app with AuthProvider and WorkspaceProvider', async () => {
|
|
// Verify the provider wrappers are in place by checking that auth flow works
|
|
// (both depend on these providers existing)
|
|
vi.mocked(api.refreshToken).mockRejectedValue(new Error('Unauthorized'))
|
|
|
|
renderWithRoute('/')
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('workspace-provider')).toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|
|
})
|