diff --git a/src/features/schedule/components/ScheduleFilter.tsx b/src/features/schedule/components/ScheduleFilter.tsx index a658a87f..b5ee7de9 100644 --- a/src/features/schedule/components/ScheduleFilter.tsx +++ b/src/features/schedule/components/ScheduleFilter.tsx @@ -79,9 +79,22 @@ function todayIso(): string { return `${y}-${m}-${day}`; } +/** + * Format a Date as `yyyy-MM-dd` — the shape returned by the schedule + * `/calendar` API (`bitmaskToDates` in api.ts). Used only for the + * disabled-dates set lookup; the URL builder uses the dashless + * `dateToYyyymmdd` form above. + */ +function dateToIsoYmd(value: Date): string { + const y = value.getFullYear().toString(); + const m = (value.getMonth() + 1).toString().padStart(2, "0"); + const d = value.getDate().toString().padStart(2, "0"); + return `${y}-${m}-${d}`; +} + /** Inverse of the API's enabled-days list: every date inside - * [minDate, maxDate] that isn't in `availableYmd` gets disabled so - * PrimeReact's Calendar greys them out (TIRREDESIGN-12). */ + * [minDate, maxDate] that isn't in `availableYmd` (yyyy-MM-dd) gets + * disabled so PrimeReact's Calendar greys them out (TIRREDESIGN-12). */ function computeDisabledDates( availableYmd: string[], minDate: Date, @@ -92,7 +105,7 @@ function computeDisabledDates( const cursor = new Date(minDate); cursor.setHours(0, 0, 0, 0); while (cursor.getTime() <= maxDate.getTime()) { - const ymd = dateToYyyymmdd(cursor); + const ymd = dateToIsoYmd(cursor); if (!available.has(ymd)) { out.push(new Date(cursor)); } diff --git a/tests/e2e/schedule-calendar-operating-days.spec.ts b/tests/e2e/schedule-calendar-operating-days.spec.ts new file mode 100644 index 00000000..e606e67f --- /dev/null +++ b/tests/e2e/schedule-calendar-operating-days.spec.ts @@ -0,0 +1,47 @@ +import { test, expect } from "@playwright/test"; + +// TIRREDESIGN-12 — when both schedule cities are filled, the date-picker +// must grey out the days the route does NOT operate. The fix in +// ScheduleFilter.tsx aligned the date-format used for the +// available-days set lookup (yyyy-MM-dd, matching the schedule +// `/days` API output) — previously the lookup compared `yyyymmdd` +// against `yyyy-MM-dd`, so every day was treated as unavailable +// and the entire calendar greyed out. + +test("Schedule calendar greys out non-operating days for the route", async ({ + page, +}) => { + await page.goto("/ru-ru/schedule/route/MOW-MMK-20260427-20260503"); + await expect(page.locator(".day-grouped-flight-list").first()).toBeVisible({ + timeout: 15000, + }); + + // Open the picker. + await page.locator("button.p-datepicker-trigger").first().click(); + const panel = page.locator(".p-datepicker-panel, .p-datepicker").first(); + await expect(panel).toBeVisible(); + + // Wait for the operating-days API to come back (the disabled-set + // is derived from it; before that, every day is enabled). + await expect + .poll(async () => + page + .locator(".p-datepicker td span.p-disabled") + .count(), + { timeout: 10000 }) + .toBeGreaterThan(0); + + // The MOW→MMK route operates roughly daily today onward, so the + // visible month must contain BOTH enabled and disabled cells. + // The check is intentionally loose because the live operating + // schedule shifts by route — but a fully-enabled or fully-disabled + // calendar would prove the format-mismatch regression returned. + const enabled = await page + .locator(".p-datepicker td span:not(.p-disabled)") + .count(); + const disabled = await page + .locator(".p-datepicker td span.p-disabled") + .count(); + expect(enabled).toBeGreaterThan(0); + expect(disabled).toBeGreaterThan(0); +});