Files
F-r-ldraledigplannerare/frontend/e2e/tests/drag-create-delete.spec.ts
T
jacobh2 ae6db7e16f Redesign the setup flow and consolidate the CSS into one primitives layer
Onboarding asks explicitly for one or two parents and shows one card per
parent for balances; the balance form is per parent with inline
validation; the partner can be filled from the FK deduction or added
from it. Settings and Dashboard reuse the same form.

CSS: bare buttons carry no colour any more; buttons, cards, choice
cards, fields, alerts, badges, eyebrow and modal chrome are defined once
in primitives.css. Media queries live only in responsive.css. A style
guard test blocks hex, !important and stray media queries. Bilingual
fixes: calendar selects size by content, header no longer wraps. Vitest
is capped at four workers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 12:27:29 +02:00

140 lines
6.9 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.
/**
* The two authoring gestures the calendar rework is actually about:
* dragging a range to create planned days, and deleting them again through
* the period popover. Neither was covered end-to-end before — the existing
* specs only check rendering (bands, red days, age-4 figures) of state the
* API seeded directly.
*
* A drag no longer creates a `LeaveWish`/synced period on its own — it opens
* the day popover pre-selected on every dragged date, and saving writes
* exactly those days (ad-hoc `planned_leave_day` rows, no wish). See
* `Calendar.tsx`'s pointerup handler and `handleSavePlannedDay`.
*
* 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
* leading/trailing-week gating bug, where `CalendarWeek.tsx` used to wire
* pointer handlers only for cells matching `weekDates[0]`'s month, so a
* trailing day belonging to the next month was inert and a drag could never
* reach it.
*
* Uses a brand-new, empty account (see `support/onboarding.ts`) rather than
* the shared demo workspace, so the calendar starts with nothing on it.
*
* The target month is derived from "today" (never a hardcoded year).
*/
import { test, expect } from '@playwright/test'
import { goToCalendarMonth, calendarDayCell, dragSelectDates } from '../support/calendarNav'
import { toIsoDate, addDays } from '../support/dates'
import { uniqueEmail, registerAndReachOnboarding, fillChildStep, continueSoloAndSkipBalances } from '../support/onboarding'
test.use({ storageState: { cookies: [], origins: [] } })
/**
* A month, at least two months out from today, whose last day is NOT a
* Sunday — i.e. the grid actually renders a trailing-week row bleeding into
* the following month (a month that ends on Sunday has no trailing row at
* all after the "no phantom week" fix; see `Calendar.tsx`'s week-building
* loop). Returns that month plus the last two of its days and the first day
* of the next month — the exact range the drag below crosses.
*/
function targetRange(): { month: Date; dates: [string, string, string] } {
const now = new Date()
let month = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 2, 1))
let lastDayOfMonth = new Date(Date.UTC(month.getUTCFullYear(), month.getUTCMonth() + 1, 0))
while (lastDayOfMonth.getUTCDay() === 0 /* Sunday */) {
month = new Date(Date.UTC(month.getUTCFullYear(), month.getUTCMonth() + 1, 1))
lastDayOfMonth = new Date(Date.UTC(month.getUTCFullYear(), month.getUTCMonth() + 1, 0))
}
const dates = [addDays(lastDayOfMonth, -1), lastDayOfMonth, addDays(lastDayOfMonth, 1)].map(toIsoDate) as [
string,
string,
string,
]
return { month, dates }
}
test('dragging a range across a month boundary plans every day, and deleting it removes the days for good', async ({
page,
}) => {
await registerAndReachOnboarding(page, uniqueEmail('drag-delete'), 'Robin Testsson')
await fillChildStep(page, 'Nova')
await continueSoloAndSkipBalances(page)
const { month, dates } = targetRange()
const [firstDate, , lastDate] = dates
await goToCalendarMonth(page, month)
const adhocBand = page.locator('[data-testid^="calendar-band-adhoc:"]')
await expect(adhocBand).toHaveCount(0) // a brand-new account — nothing here yet
// The drag spans the month's last two days plus the first day of the
// following month, rendered as a trailing cell in this same grid.
await dragSelectDates(page, dates)
// 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')
await page.getByTestId('save-button').click()
await expect(page.getByTestId('planned-day-modal__content')).not.toBeVisible()
// All three dragged days are one continuous ad-hoc PERIOD, but the grid
// renders a multi-week band as one DOM element per week ROW it crosses
// (see CalendarWeek.tsx's `computeBandSegments` and
// calendar-band.spec.ts) — here Nov 29 (a Sunday, the last column of its
// week) and Nov 30 (a Monday, the first column of the next) fall in
// different rows even though they're one calendar day apart. Both
// elements share the same `data-testid` (derived from the band's key +
// overall start date), which IS the "one period" assertion: a real
// second period would get its own id.
await expect(adhocBand.first()).toBeVisible()
await expect
.poll(async () => {
const testIds = await adhocBand.evaluateAll((els) => els.map((el) => el.getAttribute('data-testid')))
return new Set(testIds).size
})
.toBe(1)
// Open it through its band — an ad-hoc band (no wishId) shows the plain
// date range and can only be deleted, not edited. `.first()` because the
// band may render as more than one DOM segment (see above); either one
// opens the same underlying period.
await adhocBand.first().click()
await expect(page.getByTestId('period-popover__content')).toBeVisible()
await expect(page.getByTestId('period-adhoc-notice')).toBeVisible()
await expect(page.getByTestId('period-popover__content')).toContainText(`${firstDate} – ${lastDate}`)
await page.getByTestId('period-delete-button').click()
await expect(page.getByTestId('period-delete-confirm')).toBeVisible()
// Regression guard for the "Yes, delete" footer bug: the confirm button's
// label is long enough to wrap without `white-space: nowrap` on `.btn`
// (src/styles/primitives.css), which used to make it visibly taller than
// "Cancel" sitting next to it. Assert both the CSS guard and the rendered
// heights actually matching.
const cancelButton = page.getByTestId('period-delete-cancel-button')
const confirmButton = page.getByTestId('period-delete-confirm-button')
await expect(cancelButton).toHaveCSS('white-space', 'nowrap')
await expect(confirmButton).toHaveCSS('white-space', 'nowrap')
const [cancelBox, confirmBox] = await Promise.all([cancelButton.boundingBox(), confirmButton.boundingBox()])
expect(cancelBox).not.toBeNull()
expect(confirmBox).not.toBeNull()
expect(confirmBox?.height).toBe(cancelBox?.height)
await page.getByTestId('period-delete-confirm-button').click()
await expect(page.getByTestId('period-popover__overlay')).not.toBeVisible()
// The regression this guards ("Remove a period's days along with the
// period"): the underlying planned days must actually be gone
// server-side, not merely dropped from client state. A reload refetches
// from the API, so this is the assertion a surviving-days bug would fail.
await page.reload()
await goToCalendarMonth(page, month)
await expect(page.locator('[data-testid^="calendar-band-adhoc:"]')).toHaveCount(0)
// And the day itself is empty again — clicking it opens the "plan a new
// day" modal, not an edit of a day that supposedly no longer exists.
await calendarDayCell(page, dates[1]).click()
await expect(page.getByTestId('modal-title')).toHaveText('Planera en dag')
})