diff --git a/frontend/src/i18n/en/calendar.ts b/frontend/src/i18n/en/calendar.ts index cb91605..569453d 100644 --- a/frontend/src/i18n/en/calendar.ts +++ b/frontend/src/i18n/en/calendar.ts @@ -1,6 +1,8 @@ export const calendar = { 'calendar.advanceMonthAria': '{month} - click to advance month', 'calendar.advanceYearAria': '{year} - click to advance year', + 'calendar.selectMonthAria': 'Select month', + 'calendar.selectYearAria': 'Select year', 'calendar.deleteError': 'Failed to delete the planned day', 'calendar.legendDoubleDay': 'Double day', 'calendar.legendIncomeBased': 'Income-based rate (sjukpenningnivå)', diff --git a/frontend/src/i18n/sv/calendar.ts b/frontend/src/i18n/sv/calendar.ts index cfff1ba..42786ae 100644 --- a/frontend/src/i18n/sv/calendar.ts +++ b/frontend/src/i18n/sv/calendar.ts @@ -1,6 +1,8 @@ export const calendar = { 'calendar.advanceMonthAria': '{month} - klicka för att byta månad', 'calendar.advanceYearAria': '{year} - klicka för att byta år', + 'calendar.selectMonthAria': 'Välj månad', + 'calendar.selectYearAria': 'Välj år', 'calendar.deleteError': 'Det gick inte att ta bort den planerade dagen', 'calendar.legendDoubleDay': 'Dubbeldag', 'calendar.legendIncomeBased': 'Sjukpenningnivå', diff --git a/frontend/src/pages/Calendar.test.tsx b/frontend/src/pages/Calendar.test.tsx index b9a0a0b..7dcfe38 100644 --- a/frontend/src/pages/Calendar.test.tsx +++ b/frontend/src/pages/Calendar.test.tsx @@ -206,86 +206,104 @@ describe('Calendar', () => { expect(selectedDaysInRange.length).toBeGreaterThan(0) }) - it('advances one month when clicking the month button', () => { + it('jumps forward to any month via month dropdown', () => { ;(useWorkspace as any).mockReturnValue(mockWorkspace) ;(usePlannedDays as any).mockReturnValue(mockPlannedDays) render() const title = screen.getByRole('heading', { level: 2 }) - const monthButton = title.querySelector('.calendar__month-button') as HTMLButtonElement + const monthSelect = title.querySelector('.calendar__month-select') as HTMLSelectElement - expect(monthButton).toBeInTheDocument() + expect(monthSelect).toBeInTheDocument() - // Get initial month/year from title - const initialText = title.textContent + // Change to a different month (e.g., month 5 = June, 0-indexed) + fireEvent.change(monthSelect, { target: { value: '5' } }) - // Click the month button to advance one month - fireEvent.click(monthButton) - - // The month should have changed (we can't predict the exact month without knowing current date, - // but we can verify the text changed) - const updatedText = title.textContent - expect(updatedText).not.toBe(initialText) + // Verify the select value changed + expect(monthSelect.value).toBe('5') }) - it('advances one year when clicking the year button', () => { + it('jumps backward to any month via month dropdown', () => { ;(useWorkspace as any).mockReturnValue(mockWorkspace) ;(usePlannedDays as any).mockReturnValue(mockPlannedDays) render() const title = screen.getByRole('heading', { level: 2 }) - const yearButton = title.querySelector('.calendar__year-button') as HTMLButtonElement + const monthSelect = title.querySelector('.calendar__month-select') as HTMLSelectElement - expect(yearButton).toBeInTheDocument() + expect(monthSelect).toBeInTheDocument() - // Get initial year - const initialYear = parseInt(yearButton.textContent || '0') + // Jump backward to January (month 0) + fireEvent.change(monthSelect, { target: { value: '0' } }) - // Click the year button to advance one year - fireEvent.click(yearButton) - - // The year should have increased by 1 - const updatedYearButton = screen.getByRole('heading', { level: 2 }).querySelector('.calendar__year-button') as HTMLButtonElement - const updatedYear = parseInt(updatedYearButton.textContent || '0') - expect(updatedYear).toBe(initialYear + 1) + expect(monthSelect.value).toBe('0') }) - it('handles December to January rollover when clicking month button in December', () => { + it('jumps forward to any year via year dropdown', () => { ;(useWorkspace as any).mockReturnValue(mockWorkspace) ;(usePlannedDays as any).mockReturnValue(mockPlannedDays) - // Mock a December render - we need to test the logic, not the actual calendar state render() const title = screen.getByRole('heading', { level: 2 }) - const monthButton = title.querySelector('.calendar__month-button') as HTMLButtonElement + const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement - // Click month button 12 times (a full year cycle) - for (let i = 0; i < 12; i++) { - fireEvent.click(monthButton) - } + expect(yearSelect).toBeInTheDocument() - // The year might have incremented depending on when we started, but the calendar should still work - const finalTitle = screen.getByRole('heading', { level: 2 }) - expect(finalTitle).toBeInTheDocument() + // Jump to year 2035 + fireEvent.change(yearSelect, { target: { value: '2035' } }) + + expect(yearSelect.value).toBe('2035') }) - it('month and year buttons have proper accessibility labels', () => { + it('jumps backward to any year via year dropdown', () => { ;(useWorkspace as any).mockReturnValue(mockWorkspace) ;(usePlannedDays as any).mockReturnValue(mockPlannedDays) render() const title = screen.getByRole('heading', { level: 2 }) - const monthButton = title.querySelector('.calendar__month-button') as HTMLButtonElement - const yearButton = title.querySelector('.calendar__year-button') as HTMLButtonElement + const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement - expect(monthButton).toHaveAttribute('aria-label') - expect(yearButton).toHaveAttribute('aria-label') - expect(monthButton.getAttribute('aria-label')).toContain('klicka för att byta månad') - expect(yearButton.getAttribute('aria-label')).toContain('klicka för att byta år') + expect(yearSelect).toBeInTheDocument() + + // Jump backward to year 2026 (the minimum supported year) + fireEvent.change(yearSelect, { target: { value: '2026' } }) + + expect(yearSelect.value).toBe('2026') + }) + + it('year dropdown includes all supported years from 2026 to 2055', () => { + ;(useWorkspace as any).mockReturnValue(mockWorkspace) + ;(usePlannedDays as any).mockReturnValue(mockPlannedDays) + + render() + + const title = screen.getByRole('heading', { level: 2 }) + const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement + + const options = Array.from(yearSelect.options).map((opt) => parseInt(opt.value)) + + // Verify the range is correct + expect(Math.min(...options)).toBe(2026) + expect(Math.max(...options)).toBe(2055) + expect(options).toHaveLength(30) // 2026 to 2055 inclusive is 30 years + }) + + it('month and year selects have proper accessibility labels', () => { + ;(useWorkspace as any).mockReturnValue(mockWorkspace) + ;(usePlannedDays as any).mockReturnValue(mockPlannedDays) + + render() + + const title = screen.getByRole('heading', { level: 2 }) + const monthSelect = title.querySelector('.calendar__month-select') as HTMLSelectElement + const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement + + expect(monthSelect).toHaveAttribute('aria-label', 'Välj månad') + expect(yearSelect).toHaveAttribute('aria-label', 'Välj år') }) // §3.4 shading + §3.6 click-to-override @@ -588,9 +606,9 @@ describe('Calendar', () => { , ) const title = screen.getByRole('heading', { level: 2 }) - const monthButton = title.querySelector('.calendar__month-button') as HTMLButtonElement - // English month names differ from the Swedish ones (e.g. "January" vs "januari"). - expect(monthButton.getAttribute('aria-label')).toContain('click to advance month') + const monthSelect = title.querySelector('.calendar__month-select') as HTMLSelectElement + // Month select has a simple aria label + expect(monthSelect.getAttribute('aria-label')).toBe('Select month') } finally { Object.defineProperty(window.navigator, 'language', { value: 'sv-SE', configurable: true }) vi.unstubAllGlobals() diff --git a/frontend/src/pages/Calendar.tsx b/frontend/src/pages/Calendar.tsx index 9111073..235dc60 100644 --- a/frontend/src/pages/Calendar.tsx +++ b/frontend/src/pages/Calendar.tsx @@ -137,12 +137,14 @@ export function Calendar(): ReactNode { setCurrentMonth(new Date(year, month + 1, 1)) } - const handleMonthClick = () => { - setCurrentMonth(new Date(year, month + 1, 1)) + const handleMonthChange = (e: React.ChangeEvent) => { + const newMonth = Number(e.target.value) + setCurrentMonth(new Date(year, newMonth, 1)) } - const handleYearClick = () => { - setCurrentMonth(new Date(year + 1, month, 1)) + const handleYearChange = (e: React.ChangeEvent) => { + const newYear = Number(e.target.value) + setCurrentMonth(new Date(newYear, month, 1)) } // §3.6 — an explicit mode toggle (rather than a modifier-click or context @@ -299,26 +301,34 @@ export function Calendar(): ReactNode { ←

- -