dbef997da4
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
113 lines
6.0 KiB
TypeScript
113 lines
6.0 KiB
TypeScript
/**
|
||
* §3.4 red-day rendering, and the "combined holiday name" trap.
|
||
*
|
||
* The backend (`app/utils/holidays.py::get_swedish_holidays`) returns a
|
||
* COMBINED name like "Påskdagen; Söndag" whenever a fixed public holiday
|
||
* falls on a Sunday — `get_red_days` keeps that combined string (only a
|
||
* *bare* "Söndag" is dropped). A careless frontend filter that splits on
|
||
* ';' and throws away anything containing a weekday name would silently
|
||
* delete Påskdagen/Pingstdagen's own name along with it. This test asserts
|
||
* both holidays render as NAMED red days, by their real name, in the
|
||
* calendar cell's visible text (not hover-only).
|
||
*
|
||
* All dates are computed from "today" (see support/dates.ts) — never a
|
||
* hardcoded year — because Easter/Pentecost's date, and therefore which
|
||
* year's calendar page to look at, changes every run.
|
||
*/
|
||
import { test, expect, type Page } from '@playwright/test'
|
||
import { addDays, nextEasterOnOrAfter, whitSunday, toIsoDate } from '../support/dates'
|
||
import { calendarDayCell, goToCalendarMonth } from '../support/calendarNav'
|
||
|
||
/**
|
||
* Resolves a CSS custom property (declared on :root in styles/global.css) to
|
||
* the computed `rgb(...)`/`rgba(...)` string the browser would report for an
|
||
* element actually painted with it — i.e. exactly what `toHaveCSS` compares
|
||
* against. Reading the token instead of a hardcoded literal means a future
|
||
* restyle that moves the palette can't silently desync this spec from
|
||
* calendar.css: whatever `--c-danger`/`--c-text-faint` resolve to at test
|
||
* time is what's asserted.
|
||
*/
|
||
async function resolveCssVar(page: Page, varName: string): Promise<string> {
|
||
return page.evaluate((name) => {
|
||
const probe = document.createElement('div')
|
||
probe.style.color = `var(${name})`
|
||
document.body.appendChild(probe)
|
||
const resolved = getComputedStyle(probe).color
|
||
probe.remove()
|
||
return resolved
|
||
}, varName)
|
||
}
|
||
|
||
/** The day's numeral — the element that carries the red/Saturday colour in
|
||
* the Almanacka design (cells themselves have no fill). */
|
||
function dayNumeral(cell: ReturnType<typeof calendarDayCell>) {
|
||
return cell.locator('.calendar-day__number')
|
||
}
|
||
|
||
test.describe('red days, klämdagar and the combined-holiday-name trap', () => {
|
||
test('Easter Sunday (Påskdagen) renders as a named red day', async ({ page }) => {
|
||
const easter = nextEasterOnOrAfter(new Date())
|
||
await goToCalendarMonth(page, easter)
|
||
const redInk = await resolveCssVar(page, '--c-danger') // .calendar-day--red-day .calendar-day__number
|
||
const redFill = await resolveCssVar(page, '--c-danger-weak')
|
||
|
||
const cell = calendarDayCell(page, toIsoDate(easter))
|
||
await expect(cell).toHaveClass(/calendar-day--red-day/)
|
||
// The numeral carries the colour; the cell itself has no red fill.
|
||
await expect(dayNumeral(cell)).toHaveCSS('color', redInk)
|
||
await expect(cell).not.toHaveCSS('background-color', redFill)
|
||
await expect(cell.getByTestId('calendar-day-type-badge')).toHaveText('R')
|
||
await expect(cell.getByTestId('calendar-day-holiday-name')).toHaveText('Påskdagen')
|
||
})
|
||
|
||
test('Whit Sunday (Pingstdagen) renders as a named red day', async ({ page }) => {
|
||
const whitsun = whitSunday(nextEasterOnOrAfter(new Date()))
|
||
await goToCalendarMonth(page, whitsun)
|
||
const redInk = await resolveCssVar(page, '--c-danger') // .calendar-day--red-day .calendar-day__number
|
||
const redFill = await resolveCssVar(page, '--c-danger-weak')
|
||
|
||
const cell = calendarDayCell(page, toIsoDate(whitsun))
|
||
await expect(cell).toHaveClass(/calendar-day--red-day/)
|
||
await expect(dayNumeral(cell)).toHaveCSS('color', redInk)
|
||
await expect(cell).not.toHaveCSS('background-color', redFill)
|
||
await expect(cell.getByTestId('calendar-day-holiday-name')).toHaveText('Pingstdagen')
|
||
})
|
||
|
||
test('a plain Sunday is red without a holiday name; a plain Saturday is neutral grey', async ({ page }) => {
|
||
// Two and three weeks before Easter: far enough from Easter's own
|
||
// de-facto cluster (Långfredagen/Påskafton/Påskdagen/Annandag påsk,
|
||
// Easter-2..Easter+1) that neither date is itself a public or de-facto
|
||
// holiday, just an ordinary weekend.
|
||
const easter = nextEasterOnOrAfter(new Date())
|
||
const plainSunday = addDays(easter, -14)
|
||
const plainSaturday = addDays(plainSunday, -1)
|
||
|
||
// Navigate by the Sunday's own month, not Easter's: whatever month
|
||
// `plainSunday` falls in, `plainSaturday` (the day right before it) is
|
||
// guaranteed to appear in that same month's grid too — either as an
|
||
// ordinary day in the month, or as a leading fill-in day completing
|
||
// that first Mon–Sun week row (see Calendar.tsx's `startOffset`) — so
|
||
// this never depends on the two dates sharing a calendar month.
|
||
await goToCalendarMonth(page, plainSunday)
|
||
const redInk = await resolveCssVar(page, '--c-danger') // .calendar-day--red-day .calendar-day__number
|
||
const redFill = await resolveCssVar(page, '--c-danger-weak')
|
||
const saturdayInk = await resolveCssVar(page, '--c-text-faint') // .calendar-day--weekend .calendar-day__number
|
||
|
||
const sundayCell = calendarDayCell(page, toIsoDate(plainSunday))
|
||
await expect(sundayCell).toHaveClass(/calendar-day--red-day/)
|
||
await expect(sundayCell).not.toHaveClass(/calendar-day--weekend/)
|
||
await expect(dayNumeral(sundayCell)).toHaveCSS('color', redInk)
|
||
await expect(sundayCell).not.toHaveCSS('background-color', redFill)
|
||
await expect(sundayCell.getByTestId('calendar-day-type-badge')).toHaveText('R')
|
||
await expect(sundayCell.getByTestId('calendar-day-holiday-name')).toHaveCount(0)
|
||
|
||
const saturdayCell = calendarDayCell(page, toIsoDate(plainSaturday))
|
||
await expect(saturdayCell).toHaveClass(/calendar-day--weekend/)
|
||
await expect(saturdayCell).not.toHaveClass(/calendar-day--red-day/)
|
||
// A plain Saturday is a faint numeral, not a red one, and no fill.
|
||
await expect(dayNumeral(saturdayCell)).toHaveCSS('color', saturdayInk)
|
||
await expect(dayNumeral(saturdayCell)).not.toHaveCSS('color', redInk)
|
||
await expect(saturdayCell).not.toHaveCSS('background-color', redFill)
|
||
})
|
||
})
|