Files
F-r-ldraledigplannerare/frontend/e2e/tests/red-days.spec.ts
T
jacobh2 dbef997da4 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
2026-09-19 19:02:02 +02:00

113 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* §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)
})
})