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 }) => (
{children}
),
}))
// Mock all page components to verify routing
vi.mock('@/pages/Auth', () => ({
Auth: () => Auth Page
,
}))
vi.mock('@/pages/Calendar', () => ({
Calendar: () => Calendar Page
,
}))
vi.mock('@/pages/Dashboard', () => ({
Dashboard: () => Dashboard Page
,
}))
vi.mock('@/pages/Settings', () => ({
Settings: () => Settings Page
,
}))
vi.mock('@/pages/DevFetch', () => ({
DevFetch: () => DevFetch Page
,
}))
// 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(
)
}
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()
})
})
})
})