Redraw the calendar as an almanac: gridless cells, typed bands
Day cells lose their borders and fills; red days, klämdagar and Saturdays are told apart by the numeral's ink with a 12px R/K mark, today is a filled circle, overrides and exclusions mark the numeral. Bands carry their funding type by fill, ink and hatching and their owner by a left cap. The preset rail becomes a chip row, the legend a help popover, the period popover's three delete buttons fold behind one "Ta bort…", and solo users pick a type directly in the day modal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBRLomdLK9jF7wU7a8AA6H
This commit is contained in:
@@ -336,9 +336,8 @@ describe('Calendar', () => {
|
||||
// week" logic would add a 6th row of 7 disabled June cells that serves
|
||||
// no purpose.
|
||||
render(<Calendar />)
|
||||
const title = screen.getByRole('heading', { level: 2 })
|
||||
const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement
|
||||
const monthSelect = title.querySelector('.calendar__month-select') as HTMLSelectElement
|
||||
const yearSelect = screen.getByLabelText('Välj år') as HTMLSelectElement
|
||||
const monthSelect = screen.getByLabelText('Välj månad') as HTMLSelectElement
|
||||
fireEvent.change(yearSelect, { target: { value: '2026' } })
|
||||
fireEvent.change(monthSelect, { target: { value: '4' } })
|
||||
|
||||
@@ -374,10 +373,20 @@ describe('Calendar', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('displays current month title', () => {
|
||||
it('displays the month and year as real title text, capitalised', () => {
|
||||
render(<Calendar />)
|
||||
const title = screen.getByRole('heading', { level: 2 })
|
||||
expect(title.textContent).toMatch(/\d{4}/)
|
||||
expect(title.textContent).toMatch(
|
||||
/^(Januari|Februari|Mars|April|Maj|Juni|Juli|Augusti|September|Oktober|November|December) \d{4}$/,
|
||||
)
|
||||
// The title holds no controls: the month/year selects live beside it.
|
||||
expect(title.querySelector('select')).toBeNull()
|
||||
})
|
||||
|
||||
it('renders the help popover button in place of the old legend', () => {
|
||||
render(<Calendar />)
|
||||
expect(screen.getByTestId('calendar-help-button')).toBeInTheDocument()
|
||||
expect(document.querySelector('.calendar__legend')).toBeNull()
|
||||
})
|
||||
|
||||
it('handles next/prev month navigation', () => {
|
||||
@@ -405,9 +414,8 @@ describe('Calendar', () => {
|
||||
|
||||
it('jumps forward/backward via month and year dropdowns', () => {
|
||||
render(<Calendar />)
|
||||
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
|
||||
const monthSelect = screen.getByLabelText('Välj månad') as HTMLSelectElement
|
||||
const yearSelect = screen.getByLabelText('Välj år') as HTMLSelectElement
|
||||
|
||||
fireEvent.change(monthSelect, { target: { value: '5' } })
|
||||
expect(monthSelect.value).toBe('5')
|
||||
@@ -418,9 +426,8 @@ describe('Calendar', () => {
|
||||
|
||||
it('Today button jumps back to the current month/year after navigating away', () => {
|
||||
render(<Calendar />)
|
||||
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
|
||||
const monthSelect = screen.getByLabelText('Välj månad') as HTMLSelectElement
|
||||
const yearSelect = screen.getByLabelText('Välj år') as HTMLSelectElement
|
||||
|
||||
fireEvent.change(monthSelect, { target: { value: '5' } })
|
||||
fireEvent.change(yearSelect, { target: { value: '2035' } })
|
||||
@@ -438,8 +445,7 @@ describe('Calendar', () => {
|
||||
makeCalendarDaysResult({ data: makeCalendarWindow({ minYear: 2026, maxYear: 2055 }) }),
|
||||
)
|
||||
render(<Calendar />)
|
||||
const title = screen.getByRole('heading', { level: 2 })
|
||||
const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement
|
||||
const yearSelect = screen.getByLabelText('Välj år') as HTMLSelectElement
|
||||
const options = Array.from(yearSelect.options).map((opt) => parseInt(opt.value))
|
||||
expect(Math.min(...options)).toBe(2026)
|
||||
expect(Math.max(...options)).toBe(2055)
|
||||
@@ -448,8 +454,7 @@ describe('Calendar', () => {
|
||||
it('always includes the current year even before the calendar window has loaded', () => {
|
||||
vi.mocked(useCalendarDays).mockReturnValue(makeCalendarDaysResult({ data: null, isLoading: true }))
|
||||
render(<Calendar />)
|
||||
const title = screen.getByRole('heading', { level: 2 })
|
||||
const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement
|
||||
const yearSelect = screen.getByLabelText('Välj år') as HTMLSelectElement
|
||||
const options = Array.from(yearSelect.options).map((opt) => parseInt(opt.value))
|
||||
expect(options).toContain(new Date().getFullYear())
|
||||
})
|
||||
@@ -462,10 +467,8 @@ describe('Calendar', () => {
|
||||
])
|
||||
|
||||
render(<Calendar />)
|
||||
|
||||
const title = screen.getByRole('heading', { level: 2 })
|
||||
const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement
|
||||
const monthSelect = title.querySelector('.calendar__month-select') as HTMLSelectElement
|
||||
const yearSelect = screen.getByLabelText('Välj år') as HTMLSelectElement
|
||||
const monthSelect = screen.getByLabelText('Välj månad') as HTMLSelectElement
|
||||
expect(yearSelect.value).toBe('2030')
|
||||
expect(monthSelect.value).toBe('6') // July is month index 6
|
||||
|
||||
@@ -485,9 +488,8 @@ describe('Calendar', () => {
|
||||
render(<Calendar />)
|
||||
|
||||
const today = new Date()
|
||||
const title = screen.getByRole('heading', { level: 2 })
|
||||
const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement
|
||||
const monthSelect = title.querySelector('.calendar__month-select') as HTMLSelectElement
|
||||
const yearSelect = screen.getByLabelText('Välj år') as HTMLSelectElement
|
||||
const monthSelect = screen.getByLabelText('Välj månad') as HTMLSelectElement
|
||||
expect(yearSelect.value).toBe(String(today.getFullYear()))
|
||||
expect(monthSelect.value).toBe(String(today.getMonth()))
|
||||
expect(screen.queryByTestId('modal')).not.toBeInTheDocument()
|
||||
@@ -499,9 +501,8 @@ describe('Calendar', () => {
|
||||
render(<Calendar />)
|
||||
|
||||
const today = new Date()
|
||||
const title = screen.getByRole('heading', { level: 2 })
|
||||
const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement
|
||||
const monthSelect = title.querySelector('.calendar__month-select') as HTMLSelectElement
|
||||
const yearSelect = screen.getByLabelText('Välj år') as HTMLSelectElement
|
||||
const monthSelect = screen.getByLabelText('Välj månad') as HTMLSelectElement
|
||||
expect(yearSelect.value).toBe(String(today.getFullYear()))
|
||||
expect(monthSelect.value).toBe(String(today.getMonth()))
|
||||
})
|
||||
@@ -686,9 +687,8 @@ describe('Calendar', () => {
|
||||
|
||||
it('a leading week\'s in-month days are draggable (regression: the week used to be gated by weekDates[0]\'s month, not the displayed month)', () => {
|
||||
render(<Calendar />)
|
||||
const title = screen.getByRole('heading', { level: 2 })
|
||||
const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement
|
||||
const monthSelect = title.querySelector('.calendar__month-select') as HTMLSelectElement
|
||||
const yearSelect = screen.getByLabelText('Välj år') as HTMLSelectElement
|
||||
const monthSelect = screen.getByLabelText('Välj månad') as HTMLSelectElement
|
||||
// March 2026 starts on a Sunday, so its first calendar row is a
|
||||
// leading week made up of six February days plus March 1st.
|
||||
fireEvent.change(yearSelect, { target: { value: '2026' } })
|
||||
@@ -1037,8 +1037,7 @@ describe('Calendar', () => {
|
||||
fireEvent.click(screen.getByTestId('preset-click'))
|
||||
|
||||
await waitFor(() => {
|
||||
const title = screen.getByRole('heading', { level: 2 })
|
||||
const yearSelect = title.querySelector('.calendar__year-select') as HTMLSelectElement
|
||||
const yearSelect = screen.getByLabelText('Välj år') as HTMLSelectElement
|
||||
expect(yearSelect.value).toBe('2030')
|
||||
})
|
||||
})
|
||||
@@ -1448,9 +1447,11 @@ describe('Calendar', () => {
|
||||
<Calendar />
|
||||
</LanguageProvider>,
|
||||
)
|
||||
const title = screen.getByRole('heading', { level: 2 })
|
||||
const monthSelect = title.querySelector('.calendar__month-select') as HTMLSelectElement
|
||||
expect(monthSelect.getAttribute('aria-label')).toBe('Select month')
|
||||
expect(screen.getByLabelText('Select month')).toBeInTheDocument()
|
||||
// The title is real text now, in the English month name.
|
||||
expect(screen.getByRole('heading', { level: 2 }).textContent).toMatch(
|
||||
/^(January|February|March|April|May|June|July|August|September|October|November|December) \d{4}$/,
|
||||
)
|
||||
} finally {
|
||||
Object.defineProperty(window.navigator, 'language', { value: 'sv-SE', configurable: true })
|
||||
vi.unstubAllGlobals()
|
||||
@@ -1624,12 +1625,12 @@ describe('Calendar', () => {
|
||||
render(<Calendar />)
|
||||
await waitFor(() => expect(sync).toHaveBeenCalledTimes(1))
|
||||
|
||||
const yearBefore = (screen.getByRole('heading', { level: 2 }).querySelector('.calendar__year-select') as HTMLSelectElement).value
|
||||
const yearBefore = (screen.getByLabelText('Välj år') as HTMLSelectElement).value
|
||||
|
||||
fireEvent.click(screen.getByTestId('preset-click'))
|
||||
|
||||
await waitFor(() => expect(screen.getByTestId('calendar-toast')).toHaveTextContent('sync failed'))
|
||||
const yearAfter = (screen.getByRole('heading', { level: 2 }).querySelector('.calendar__year-select') as HTMLSelectElement).value
|
||||
const yearAfter = (screen.getByLabelText('Välj år') as HTMLSelectElement).value
|
||||
expect(yearAfter).toBe(yearBefore)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user