ae6db7e16f
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>
84 lines
3.9 KiB
TypeScript
84 lines
3.9 KiB
TypeScript
import type { Page } from '@playwright/test'
|
||
import { expect } from '@playwright/test'
|
||
|
||
// Swedish labels from src/i18n/sv/calendar.ts — the suite pins
|
||
// `locale: 'sv-SE'` in playwright.config.ts so these are stable.
|
||
const MONTH_SELECT_LABEL = 'Välj månad'
|
||
const YEAR_SELECT_LABEL = 'Välj år'
|
||
|
||
/** Navigate the Calendar page to the month containing `date` (UTC). */
|
||
export async function goToCalendarMonth(page: Page, date: Date): Promise<void> {
|
||
await page.goto('/calendar')
|
||
await expect(page.getByTestId('calendar')).toBeVisible()
|
||
|
||
await page.getByLabel(YEAR_SELECT_LABEL).selectOption(String(date.getUTCFullYear()))
|
||
await page.getByLabel(MONTH_SELECT_LABEL).selectOption(String(date.getUTCMonth()))
|
||
}
|
||
|
||
/** The exact `data-testid="calendar-day"` cell for an ISO date, wherever it
|
||
* falls in the currently-displayed grid (including a leading/trailing day
|
||
* from an adjacent month). */
|
||
export function calendarDayCell(page: Page, isoDate: string) {
|
||
return page.locator(`[data-testid="calendar-day"][aria-label^="${isoDate}"]`)
|
||
}
|
||
|
||
/**
|
||
* Drives a real drag-select across the given ISO dates (in order), the way
|
||
* `Calendar.tsx` expects it: `CalendarDay` wires plain `onPointerDown`/
|
||
* `onPointerEnter` handlers (see `CalendarWeek.tsx`), and the page only
|
||
* treats the gesture as a multi-day drag if `pointerenter` actually fired
|
||
* for every intermediate cell before `pointerup` — see the `dates.length
|
||
* === 1` branch in `Calendar.tsx`'s `pointerup` handler, which opens the
|
||
* single-day modal instead of creating a period.
|
||
*
|
||
* A single fast `mouse.move` (or Playwright's default `hover()`/`dragTo()`)
|
||
* can collapse into one native pointer event and skip the intermediate
|
||
* cells entirely, which reads as a click on the anchor date. Moving in
|
||
* several steps per cell, with a short pause between cells, is what
|
||
* reliably fires a `pointerenter` on each one.
|
||
*
|
||
* The whole dragged range is scrolled into view ONCE, before the drag
|
||
* starts — never mid-drag. A default-viewport (1280×720) run of a 6-row
|
||
* month puts the trailing week's cells below the fold (`getBoundingClientRect`
|
||
* still reports their real, off-screen `y`); moving the mouse to those
|
||
* coordinates without scrolling first sends it past the bottom of the
|
||
* actually-rendered viewport, where there is no element to receive
|
||
* `pointerenter` at all — the browser just stops delivering the event, and
|
||
* the drag's `dragCurrent` freezes on the last on-screen cell it entered
|
||
* while still climbing toward the target. Scrolling once up front (via the
|
||
* last, typically lowest, target date) keeps every waypoint's precomputed
|
||
* center valid for the whole gesture; scrolling again after `mouse.down()`
|
||
* would invalidate the coordinates of any cell already computed and risk
|
||
* disrupting the in-progress native pointer sequence.
|
||
*/
|
||
export async function dragSelectDates(page: Page, isoDates: string[]): Promise<void> {
|
||
if (isoDates.length < 2) {
|
||
throw new Error('dragSelectDates needs at least two dates to actually drag across')
|
||
}
|
||
|
||
const lastDate = isoDates[isoDates.length - 1]
|
||
if (lastDate) {
|
||
await calendarDayCell(page, lastDate).scrollIntoViewIfNeeded()
|
||
}
|
||
|
||
const centerOf = async (isoDate: string): Promise<{ x: number; y: number }> => {
|
||
const box = await calendarDayCell(page, isoDate).boundingBox()
|
||
if (!box) throw new Error(`calendar day cell not visible for ${isoDate} — is it in the current month view?`)
|
||
return { x: box.x + box.width / 2, y: box.y + box.height / 2 }
|
||
}
|
||
|
||
const [firstDate, ...restDates] = isoDates
|
||
if (!firstDate) throw new Error('dragSelectDates needs at least two dates to actually drag across')
|
||
const start = await centerOf(firstDate)
|
||
await page.mouse.move(start.x, start.y)
|
||
await page.mouse.down()
|
||
|
||
for (const isoDate of restDates) {
|
||
const point = await centerOf(isoDate)
|
||
await page.mouse.move(point.x, point.y, { steps: 10 })
|
||
await page.waitForTimeout(50)
|
||
}
|
||
|
||
await page.mouse.up()
|
||
}
|