diff --git a/tests/e2e/schedule-calendar-operating-days.spec.ts b/tests/e2e/schedule-calendar-operating-days.spec.ts index b3b5ecc4..daeca8c7 100644 --- a/tests/e2e/schedule-calendar-operating-days.spec.ts +++ b/tests/e2e/schedule-calendar-operating-days.spec.ts @@ -1,4 +1,5 @@ import { test, expect } from "./fixtures/console-gate"; +import type { Page } 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 schedule @@ -13,50 +14,202 @@ function ymd(date: Date): string { ].join(""); } +function isoYmd(date: Date): string { + return [ + date.getFullYear(), + String(date.getMonth() + 1).padStart(2, "0"), + String(date.getDate()).padStart(2, "0"), + ].join("-"); +} + function addDays(date: Date, days: number): Date { const next = new Date(date); next.setDate(next.getDate() + days); return next; } +async function visibleCalendarCellClasses(page: Page) { + return page + .locator(".p-datepicker td:not(.p-datepicker-other-month) span") + .evaluateAll((nodes) => + nodes.map((node) => ({ + text: node.textContent?.trim(), + className: node.className, + })), + ); +} + test("Schedule calendar greys out non-operating days for the route", async ({ page }) => { const minDate = addDays(new Date(), -1); minDate.setHours(0, 0, 0, 0); const dateTo = addDays(minDate, 6); + const daysRequests: string[] = []; await page.route("**/api/flights/v1/*/days/**/schedule/", async (route) => { + daysRequests.push(route.request().url()); await route.fulfill({ contentType: "application/json", body: JSON.stringify({ days: `0${"1".repeat(381)}` }), }); }); - await page.goto(`/ru-ru/schedule/route/LED-KUF-${ymd(minDate)}-${ymd(dateTo)}-C0`); + await page.goto( + `/ru-ru/schedule/route/LED-KUF-${ymd(minDate)}-${ymd(dateTo)}-C0`, + ); // 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(); - const visibleDaySelector = ".p-datepicker td:not(.p-datepicker-other-month) span"; const minDateDay = String(minDate.getDate()); const nextDateDay = String(addDays(minDate, 1).getDate()); await expect.poll(async () => { - const cells = await page.locator(visibleDaySelector).evaluateAll((nodes) => - nodes.map((node) => ({ - text: node.textContent?.trim(), - className: node.className, - })), - ); + const cells = await visibleCalendarCellClasses(page); return cells.find((cell) => cell.text === minDateDay)?.className ?? ""; }, { timeout: 10000 }).toContain("p-disabled"); - const cells = await page.locator(visibleDaySelector).evaluateAll((nodes) => - nodes.map((node) => ({ - text: node.textContent?.trim(), - className: node.className, - })), + expect(daysRequests[0]).toContain( + `/days/${isoYmd(minDate)}/382/route/LED-KUF/schedule/`, ); - expect(cells.find((cell) => cell.text === nextDateDay)?.className ?? "").not.toContain("p-disabled"); + + const cells = await visibleCalendarCellClasses(page); + expect( + cells.find((cell) => cell.text === nextDateDay)?.className ?? "", + ).not.toContain("p-disabled"); +}); + +test("Schedule calendar updates an already-open picker when operating days load", async ({ page }) => { + const minDate = addDays(new Date(), -1); + minDate.setHours(0, 0, 0, 0); + const dateTo = addDays(minDate, 6); + + let releaseDays: (() => void) | undefined; + await page.route("**/api/flights/v1/*/days/**/schedule/", async (route) => { + await new Promise((resolve) => { + releaseDays = resolve; + }); + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ days: `0${"1".repeat(381)}` }), + }); + }); + + await page.goto( + `/ru-ru/schedule/route/LED-KUF-${ymd(minDate)}-${ymd(dateTo)}-C0`, + ); + + await page.locator("button.p-datepicker-trigger").first().click(); + const panel = page.locator(".p-datepicker-panel, .p-datepicker").first(); + await expect(panel).toBeVisible(); + + const visibleDaySelector = + ".p-datepicker td:not(.p-datepicker-other-month) span"; + const minDateDay = String(minDate.getDate()); + + const classBeforeLoad = await page + .locator(visibleDaySelector) + .evaluateAll( + (nodes, day) => + nodes + .map((node) => ({ + text: node.textContent?.trim(), + className: node.className, + })) + .find((cell) => cell.text === day)?.className ?? "", + minDateDay, + ); + expect(classBeforeLoad).not.toContain("p-disabled"); + + releaseDays?.(); + + await expect.poll(async () => { + const cells = await visibleCalendarCellClasses(page); + return cells.find((cell) => cell.text === minDateDay)?.className ?? ""; + }, { timeout: 10000 }).toContain("p-disabled"); +}); + +test("Schedule calendar uses the connections days endpoint when transfers are allowed", async ({ page }) => { + const minDate = addDays(new Date(), -1); + minDate.setHours(0, 0, 0, 0); + const dateTo = addDays(minDate, 6); + const daysRequests: string[] = []; + + await page.route("**/api/flights/v1/*/days/**/schedule/", async (route) => { + daysRequests.push(route.request().url()); + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ days: `0${"1".repeat(381)}` }), + }); + }); + + await page.goto( + `/ru-ru/schedule/route/LED-KUF-${ymd(minDate)}-${ymd(dateTo)}`, + ); + + await page.locator("button.p-datepicker-trigger").first().click(); + await expect( + page.locator(".p-datepicker-panel, .p-datepicker").first(), + ).toBeVisible(); + + await expect + .poll(() => daysRequests[0] ?? "", { timeout: 10000 }) + .toContain("/connections/LED-KUF-1/schedule/"); + await expect.poll(async () => { + const cells = await visibleCalendarCellClasses(page); + return ( + cells.find((cell) => cell.text === String(minDate.getDate()))?.className ?? + "" + ); + }, { timeout: 10000 }).toContain("p-disabled"); +}); + +test("Schedule return calendar uses swapped route operating days", async ({ page }) => { + const minDate = addDays(new Date(), -1); + minDate.setHours(0, 0, 0, 0); + const dateTo = addDays(minDate, 6); + const returnFrom = addDays(dateTo, 1); + const returnTo = addDays(returnFrom, 6); + const daysRequests: string[] = []; + const returnMask = `${"1".repeat(7)}0${"1".repeat(374)}`; + + await page.route("**/api/flights/v1/*/days/**/schedule/", async (route) => { + const url = route.request().url(); + daysRequests.push(url); + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ + days: url.includes("/connections/KUF-LED-1/") + ? returnMask + : "1".repeat(382), + }), + }); + }); + + await page.goto( + `/ru-ru/schedule/route/LED-KUF-${ymd(minDate)}-${ymd(dateTo)}/KUF-LED-${ymd(returnFrom)}-${ymd(returnTo)}`, + ); + + const triggers = page.locator("button.p-datepicker-trigger"); + await expect(triggers).toHaveCount(2); + await triggers.nth(1).click(); + await expect( + page.locator(".p-datepicker-panel, .p-datepicker").first(), + ).toBeVisible(); + + await expect + .poll( + () => daysRequests.some((url) => url.includes("/connections/KUF-LED-1/")), + { timeout: 10000 }, + ) + .toBe(true); + + await expect.poll(async () => { + const cells = await visibleCalendarCellClasses(page); + return ( + cells.find((cell) => cell.text === String(returnFrom.getDate())) + ?.className ?? "" + ); + }, { timeout: 10000 }).toContain("p-disabled"); });