diff --git a/frontend/src/components/common/CalendarDay.test.tsx b/frontend/src/components/common/CalendarDay.test.tsx
index a536892..85574d6 100644
--- a/frontend/src/components/common/CalendarDay.test.tsx
+++ b/frontend/src/components/common/CalendarDay.test.tsx
@@ -93,4 +93,20 @@ describe('CalendarDay', () => {
const events = queryByTestId('calendar-day-events')
expect(events).not.toBeInTheDocument()
})
+
+ it('applies today class when isToday is true', () => {
+ const { getByTestId } = render(
+
+ )
+ const day = getByTestId('calendar-day')
+ expect(day).toHaveClass('calendar-day--today')
+ })
+
+ it('does not apply today class when isToday is false', () => {
+ const { getByTestId } = render(
+
+ )
+ const day = getByTestId('calendar-day')
+ expect(day).not.toHaveClass('calendar-day--today')
+ })
})
diff --git a/frontend/src/pages/Calendar.test.tsx b/frontend/src/pages/Calendar.test.tsx
new file mode 100644
index 0000000..c068cd2
--- /dev/null
+++ b/frontend/src/pages/Calendar.test.tsx
@@ -0,0 +1,146 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { render, screen, fireEvent } from '@testing-library/react'
+import { Calendar } from './Calendar'
+import { useWorkspace } from '@/context'
+import { usePlannedDays } from '@/hooks'
+
+vi.mock('@/context', () => ({
+ useWorkspace: vi.fn(),
+}))
+
+vi.mock('@/hooks', () => ({
+ usePlannedDays: vi.fn(),
+}))
+
+vi.mock('@/components/layout/Layout', () => ({
+ Layout: ({ children }: { children: React.ReactNode }) =>
{children}
,
+}))
+
+vi.mock('@/components/common/LoadingSpinner', () => ({
+ LoadingSpinner: () => Loading...
,
+}))
+
+vi.mock('@/components/modals/PlannedDayModal', () => ({
+ PlannedDayModal: () => ,
+}))
+
+describe('Calendar', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ const mockWorkspace = {
+ isLoading: false,
+ selectedChildId: 'child-1',
+ workspace: { id: 'ws-1' },
+ personalDataUuid: 'person-1',
+ }
+
+ const mockPlannedDays = {
+ data: [],
+ isLoading: false,
+ add: vi.fn(),
+ update: vi.fn(),
+ remove: vi.fn(),
+ }
+
+ it('renders loading state while data is loading', () => {
+ ;(useWorkspace as any).mockReturnValue({ ...mockWorkspace, isLoading: true })
+ ;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
+
+ render()
+ expect(screen.getByText('Loading...')).toBeInTheDocument()
+ })
+
+ it('renders 7 weekday headers in Monday-first Swedish order', () => {
+ ;(useWorkspace as any).mockReturnValue(mockWorkspace)
+ ;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
+
+ render()
+ const weekdays = screen.getAllByText(/^(Mån|Tis|Ons|Tor|Fre|Lör|Sön)$/)
+ expect(weekdays).toHaveLength(7)
+
+ const weekdayOrder = ['Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör', 'Sön']
+ weekdays.forEach((el, idx) => {
+ expect(el.textContent).toBe(weekdayOrder[idx])
+ })
+ })
+
+ it('renders day cells for all weeks in a month', () => {
+ ;(useWorkspace as any).mockReturnValue(mockWorkspace)
+ ;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
+
+ render()
+
+ const calendarDays = screen.getAllByTestId('calendar-day')
+ expect(calendarDays.length).toBeGreaterThan(28)
+ expect(calendarDays.length % 7).toBe(0)
+ })
+
+ it('marks Sunday-starting month (March 2026) correctly with 7-column grid', () => {
+ ;(useWorkspace as any).mockReturnValue(mockWorkspace)
+ ;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
+
+ const { rerender } = render()
+ expect(screen.getByTestId('calendar')).toBeInTheDocument()
+ rerender()
+
+ const calendarDays = screen.getAllByTestId('calendar-day')
+ expect(calendarDays.length % 7).toBe(0)
+ })
+
+ it('opens modal when clicking a current-month day', () => {
+ ;(useWorkspace as any).mockReturnValue(mockWorkspace)
+ ;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
+
+ render()
+
+ const calendarDays = screen.getAllByTestId('calendar-day')
+ const currentMonthDay = calendarDays.find((el) => !el.classList.contains('calendar-day--disabled'))
+
+ if (currentMonthDay) {
+ fireEvent.click(currentMonthDay)
+ expect(screen.getByTestId('modal')).toBeInTheDocument()
+ }
+ })
+
+ it('does not open modal when clicking disabled (non-current-month) day', () => {
+ ;(useWorkspace as any).mockReturnValue(mockWorkspace)
+ ;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
+
+ render()
+
+ const calendarDays = screen.getAllByTestId('calendar-day')
+ const disabledDay = calendarDays.find((el) => el.classList.contains('calendar-day--disabled'))
+
+ if (disabledDay) {
+ fireEvent.click(disabledDay)
+ expect(disabledDay).toHaveClass('calendar-day--disabled')
+ }
+ })
+
+ it('displays current month title', () => {
+ ;(useWorkspace as any).mockReturnValue(mockWorkspace)
+ ;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
+
+ render()
+
+ const title = screen.getByRole('heading', { level: 2 })
+ expect(title.textContent).toMatch(/\d{4}/)
+ })
+
+ it('handles next/prev month navigation', () => {
+ ;(useWorkspace as any).mockReturnValue(mockWorkspace)
+ ;(usePlannedDays as any).mockReturnValue(mockPlannedDays)
+
+ render()
+
+ const nextButton = screen.getByLabelText('Next month')
+ fireEvent.click(nextButton)
+
+ const prevButton = screen.getByLabelText('Previous month')
+ fireEvent.click(prevButton)
+
+ expect(screen.getByTestId('calendar')).toBeInTheDocument()
+ })
+})