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:
@@ -10,9 +10,10 @@
|
||||
* default it now creates a fixed, one-off `LeaveWish` and lets the planner
|
||||
* fund it (a `calendar-band-wish:` band, not `adhoc:`) — see
|
||||
* `Calendar.tsx`'s `handleSaveWish`. The old "write exact ad-hoc
|
||||
* `planned_leave_day` rows" behaviour still exists, behind the modal's
|
||||
* "Advanced" disclosure (`handleSavePinnedDay`) — the first test below
|
||||
* opens it explicitly to keep exercising that path.
|
||||
* `planned_leave_day` rows" behaviour still exists (`handleSavePinnedDay`):
|
||||
* a solo household reaches it by picking a concrete type in the modal's
|
||||
* type select (a couple through the "Advanced" disclosure) — the first
|
||||
* test below does so explicitly to keep exercising that path.
|
||||
*
|
||||
* The drag deliberately crosses a month boundary (the last two days of one
|
||||
* month into the first day of the next) — a regression guard for the
|
||||
@@ -57,7 +58,7 @@ function targetRange(): { month: Date; dates: [string, string, string] } {
|
||||
return { month, dates }
|
||||
}
|
||||
|
||||
test('dragging a range and pinning it through Advanced plans every day exactly, and deleting it removes the days for good', async ({
|
||||
test('dragging a range and pinning an exact type plans every day exactly, and deleting it removes the days for good', async ({
|
||||
page,
|
||||
}) => {
|
||||
await registerAndReachOnboarding(page, uniqueEmail('drag-delete'), 'Robin Testsson')
|
||||
@@ -79,10 +80,12 @@ test('dragging a range and pinning it through Advanced plans every day exactly,
|
||||
// A multi-day drag opens the day popover pre-selected on every dragged
|
||||
// date — it does not create a period/wish by itself.
|
||||
await expect(page.getByTestId('modal-title')).toHaveText('Planera 3 dagar')
|
||||
// The default save path would create a fixed wish instead (see the next
|
||||
// test) — opening "Advanced" keeps this test on the exact-pinned-days
|
||||
// path todo.md #10/#11 kept as an escape hatch.
|
||||
await page.getByTestId('advanced-toggle').click()
|
||||
// The default save path ("let the planner choose") would create a fixed
|
||||
// wish instead (see the next test) — picking a concrete type keeps this
|
||||
// test on the exact-pinned-days path todo.md #10/#11 kept as an escape
|
||||
// hatch. A solo account has no "Advanced" toggle: the select is right
|
||||
// there.
|
||||
await page.getByTestId('day-type-select').selectOption('vacation')
|
||||
await page.getByTestId('save-button').click()
|
||||
await expect(page.getByTestId('planned-day-modal__content')).not.toBeVisible()
|
||||
|
||||
@@ -112,9 +115,11 @@ test('dragging a range and pinning it through Advanced plans every day exactly,
|
||||
await expect(page.getByTestId('period-adhoc-notice')).toBeVisible()
|
||||
await expect(page.getByTestId('period-popover__content')).toContainText(`${firstDate} – ${lastDate}`)
|
||||
|
||||
// todo.md #9 — an ad-hoc band has no recurring plan, so "every year" is
|
||||
// disabled; "this day"/"this period" are the only meaningful choices, and
|
||||
// "this period" removes the whole ad-hoc range (today's only behaviour).
|
||||
// todo.md #9 — the three delete scopes sit behind "Ta bort…". An ad-hoc
|
||||
// band has no recurring plan, so "every year" is disabled; "this day"/
|
||||
// "this period" are the only meaningful choices, and "this period"
|
||||
// removes the whole ad-hoc range (today's only behaviour).
|
||||
await page.getByTestId('period-delete-menu-button').click()
|
||||
await expect(page.getByTestId('period-delete-all-years-button')).toBeDisabled()
|
||||
await page.getByTestId('period-delete-year-button').click()
|
||||
await expect(page.getByTestId('period-delete-confirm')).toBeVisible()
|
||||
@@ -183,6 +188,7 @@ test('a drag saved without opening Advanced creates a planner-funded wish, and t
|
||||
// thing, same as "every year".
|
||||
await wishBand.first().click()
|
||||
await expect(page.getByTestId('period-popover__content')).toBeVisible()
|
||||
await page.getByTestId('period-delete-menu-button').click()
|
||||
await expect(page.getByTestId('period-delete-day-button')).toBeEnabled()
|
||||
|
||||
await page.getByTestId('period-delete-year-button').click()
|
||||
@@ -224,6 +230,7 @@ test('removing a single day out of a planner-placed period keeps it removed acro
|
||||
// `clickedDate`). Either way the popover's "this day" removes exactly one.
|
||||
await wishBand.first().click()
|
||||
await expect(page.getByTestId('period-popover__content')).toBeVisible()
|
||||
await page.getByTestId('period-delete-menu-button').click()
|
||||
await page.getByTestId('period-delete-day-button').click()
|
||||
await expect(page.getByTestId('period-delete-confirm')).toBeVisible()
|
||||
await page.getByTestId('period-delete-confirm-button').click()
|
||||
|
||||
@@ -24,40 +24,52 @@ import { calendarDayCell, goToCalendarMonth } from '../support/calendarNav'
|
||||
* 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-weak`/`--c-weekend-bg` resolve to at
|
||||
* test time is what's asserted.
|
||||
* 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.backgroundColor = `var(${name})`
|
||||
probe.style.color = `var(${name})`
|
||||
document.body.appendChild(probe)
|
||||
const resolved = getComputedStyle(probe).backgroundColor
|
||||
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 redDayBg = await resolveCssVar(page, '--c-danger-weak') // .calendar-day--red-day
|
||||
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/)
|
||||
await expect(cell).toHaveCSS('background-color', redDayBg)
|
||||
// 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 redDayBg = await resolveCssVar(page, '--c-danger-weak') // .calendar-day--red-day
|
||||
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(cell).toHaveCSS('background-color', redDayBg)
|
||||
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')
|
||||
})
|
||||
|
||||
@@ -77,18 +89,24 @@ test.describe('red days, klämdagar and the combined-holiday-name trap', () => {
|
||||
// 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 redDayBg = await resolveCssVar(page, '--c-danger-weak') // .calendar-day--red-day
|
||||
const weekendBg = await resolveCssVar(page, '--c-weekend-bg') // .calendar-day--weekend
|
||||
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(sundayCell).toHaveCSS('background-color', redDayBg)
|
||||
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/)
|
||||
await expect(saturdayCell).toHaveCSS('background-color', weekendBg)
|
||||
// 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)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user