MVP frontend+backend
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { Dashboard } from './Dashboard'
|
||||
import * as hooks from '@/hooks'
|
||||
import { useWorkspace, useAuth } from '@/context'
|
||||
|
||||
vi.mock('@/hooks')
|
||||
vi.mock('@/context')
|
||||
vi.mock('@/services/api')
|
||||
|
||||
const renderWithRouter = (component: React.ReactElement) => {
|
||||
return render(<BrowserRouter>{component}</BrowserRouter>)
|
||||
}
|
||||
|
||||
describe('Dashboard', () => {
|
||||
const mockWorkspaceContext = {
|
||||
workspace: { id: 'ws-1', name: 'Test Workspace', planningGoal: 'balanced' as const, createdAt: '2026-01-01' },
|
||||
isLoading: false,
|
||||
error: null,
|
||||
workspaceId: 'ws-1',
|
||||
setWorkspaceId: vi.fn(),
|
||||
refetch: vi.fn(),
|
||||
children: [{ id: 'c-1', name: 'Child 1', birth_date: '2022-01-01' }],
|
||||
selectedChildId: 'c-1',
|
||||
selectChild: vi.fn(),
|
||||
personalDataUuid: 'pd-1',
|
||||
parentName: 'Test Parent',
|
||||
refreshContext: vi.fn(),
|
||||
}
|
||||
|
||||
const mockAnalyticsData = {
|
||||
workspaceId: 'ws-1',
|
||||
childId: 'c-1',
|
||||
parentProjections: [],
|
||||
combinedDaysUsed: 30,
|
||||
combinedDaysLeft: 270,
|
||||
combinedExpiringAtB4: 100,
|
||||
}
|
||||
|
||||
const mockParentalLeaveData = {
|
||||
id: 'pf-1',
|
||||
personalDataUuid: 'pd-1',
|
||||
workspaceId: 'ws-1',
|
||||
childId: 'c-1',
|
||||
source: 'scraped' as const,
|
||||
fetchedAt: '2026-06-13T10:00:00Z',
|
||||
totalDays: 300,
|
||||
daysUsed: 30,
|
||||
daysLeft: 270,
|
||||
reservedDaysTotal: 90,
|
||||
reservedDaysLeft: 80,
|
||||
transferableDaysLeft: 50,
|
||||
dubbeldagarLeft: 60,
|
||||
tierData: {
|
||||
sjukpenningTotal: 100,
|
||||
sjukpenningUsed: 10,
|
||||
sjukpenningLeft: 90,
|
||||
lagstaNivaTotal: 200,
|
||||
lagstaNivaUsed: 20,
|
||||
lagstaNivaLeft: 180,
|
||||
},
|
||||
createdAt: '2026-06-13T10:00:00Z',
|
||||
updatedAt: '2026-06-13T10:00:00Z',
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(useAuth).mockReturnValue({
|
||||
accessToken: 'token',
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
login: vi.fn(),
|
||||
register: vi.fn(),
|
||||
logout: vi.fn(),
|
||||
} as any)
|
||||
vi.mocked(useWorkspace).mockReturnValue(mockWorkspaceContext as any)
|
||||
vi.mocked(hooks.useAnalytics).mockReturnValue({
|
||||
data: mockAnalyticsData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
} as any)
|
||||
vi.mocked(hooks.useParentalLeave).mockReturnValue({
|
||||
data: mockParentalLeaveData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
} as any)
|
||||
vi.mocked(hooks.usePlannedDays).mockReturnValue({
|
||||
data: [
|
||||
{ id: '1', workspaceId: 'ws-1', childId: 'c-1', parentPersonalDataUuid: 'pd-1', date: '2026-06-15', dayType: 'vacation', isDubbeldag: false, createdAt: '', updatedAt: '' },
|
||||
{ id: '2', workspaceId: 'ws-1', childId: 'c-1', parentPersonalDataUuid: 'pd-1', date: '2026-06-16', dayType: 'sjukpenning', isDubbeldag: false, createdAt: '', updatedAt: '' },
|
||||
],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
add: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
} as any)
|
||||
vi.mocked(hooks.useBreakPeriods).mockReturnValue({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
add: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
} as any)
|
||||
vi.mocked(hooks.useScraperFetch).mockReturnValue({
|
||||
sessionId: null,
|
||||
pollData: null,
|
||||
resolvedIds: null,
|
||||
error: null,
|
||||
isUnauthorized: false,
|
||||
start: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
isRunning: false,
|
||||
isTerminal: false,
|
||||
} as any)
|
||||
})
|
||||
|
||||
it('renders loading spinner initially', () => {
|
||||
vi.mocked(useWorkspace).mockReturnValue({
|
||||
...mockWorkspaceContext,
|
||||
isLoading: true,
|
||||
} as any)
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByText(/Loading dashboard/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows add child form when no children', () => {
|
||||
vi.mocked(useWorkspace).mockReturnValue({
|
||||
...mockWorkspaceContext,
|
||||
children: [],
|
||||
selectedChildId: null,
|
||||
} as any)
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByText(/Du har inga barn registrerade ännu/)).toBeInTheDocument()
|
||||
expect(screen.getByLabelText(/Barnets namn/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows overview metrics when children exist', () => {
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByTestId('overview-metrics')).toBeInTheDocument()
|
||||
expect(screen.getByText(/Days Used/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows parental leave breakdown when data exists', () => {
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByTestId('parental-leave-breakdown')).toBeInTheDocument()
|
||||
expect(screen.getByText(/Reserverade dagar/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders FK fetch button', () => {
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByRole('button', { name: /Hämta från Försäkringskassan/ })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('disables FK fetch button when no child selected', () => {
|
||||
vi.mocked(useWorkspace).mockReturnValue({
|
||||
...mockWorkspaceContext,
|
||||
selectedChildId: null,
|
||||
} as any)
|
||||
renderWithRouter(<Dashboard />)
|
||||
const button = screen.getByRole('button', { name: /Hämta från Försäkringskassan/ })
|
||||
expect(button).toBeDisabled()
|
||||
})
|
||||
|
||||
it('enables FK fetch button when child selected', () => {
|
||||
renderWithRouter(<Dashboard />)
|
||||
const button = screen.getByRole('button', { name: /Hämta från Försäkringskassan/ })
|
||||
expect(button).not.toBeDisabled()
|
||||
})
|
||||
|
||||
it('shows planned days breakdown', () => {
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByTestId('days-breakdown')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows progress chart with real data', () => {
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByTestId('progress-chart')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows Swedish section headings', () => {
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByTestId('metrics-title')).toHaveTextContent(/Översikt/)
|
||||
expect(screen.getAllByText(/Planerade dagar/)[0]).toBeInTheDocument()
|
||||
expect(screen.getByText(/Återstående till 4 års ålder/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows break periods section when periods exist', () => {
|
||||
vi.mocked(hooks.useBreakPeriods).mockReturnValue({
|
||||
data: [
|
||||
{
|
||||
id: 'bp-1',
|
||||
workspaceId: 'ws-1',
|
||||
name: 'Summer Break',
|
||||
startDate: '2026-06-20',
|
||||
endDate: '2026-08-31',
|
||||
targetWeeks: 8,
|
||||
createdAt: '2026-06-13T10:00:00Z',
|
||||
updatedAt: '2026-06-13T10:00:00Z',
|
||||
},
|
||||
],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
add: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
} as any)
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByText(/Break Periods/)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Summer Break/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays error message if analytics fails', () => {
|
||||
vi.mocked(hooks.useAnalytics).mockReturnValue({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
error: 'Failed to load analytics',
|
||||
refresh: vi.fn(),
|
||||
} as any)
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByText(/Failed to load analytics/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows empty state for parental leave when data is null', () => {
|
||||
vi.mocked(hooks.useParentalLeave).mockReturnValue({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refresh: vi.fn(),
|
||||
} as any)
|
||||
renderWithRouter(<Dashboard />)
|
||||
expect(screen.getByText(/Ingen data hämtad ännu/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('opens QR code modal on FK fetch button click', async () => {
|
||||
const user = userEvent.setup()
|
||||
const mockStart = vi.fn()
|
||||
vi.mocked(hooks.useScraperFetch).mockReturnValue({
|
||||
sessionId: null,
|
||||
pollData: null,
|
||||
resolvedIds: null,
|
||||
error: null,
|
||||
isUnauthorized: false,
|
||||
start: mockStart,
|
||||
cancel: vi.fn(),
|
||||
isRunning: false,
|
||||
isTerminal: false,
|
||||
} as any)
|
||||
|
||||
renderWithRouter(<Dashboard />)
|
||||
const button = screen.getByRole('button', { name: /Hämta från Försäkringskassan/ })
|
||||
await user.click(button)
|
||||
|
||||
expect(mockStart).toHaveBeenCalledWith('c-1')
|
||||
})
|
||||
|
||||
it('shows FK button is not disabled by default with child selected', () => {
|
||||
renderWithRouter(<Dashboard />)
|
||||
const button = screen.getByRole('button', { name: /Hämta från Försäkringskassan/ })
|
||||
expect(button).toBeInTheDocument()
|
||||
expect(button).not.toBeDisabled()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user