Files
flights_web/tests/e2e/schedule-calendar-operating-days.spec.ts

216 lines
6.8 KiB
TypeScript

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
// `/days` bitmask is anchored to the requested calendar minimum date:
// bit 0 maps to that exact date, not "base date minus one".
function ymd(date: Date): string {
return [
date.getFullYear(),
String(date.getMonth() + 1).padStart(2, "0"),
String(date.getDate()).padStart(2, "0"),
].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`,
);
// 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 minDateDay = String(minDate.getDate());
const nextDateDay = String(addDays(minDate, 1).getDate());
await expect.poll(async () => {
const cells = await visibleCalendarCellClasses(page);
return cells.find((cell) => cell.text === minDateDay)?.className ?? "";
}, { timeout: 10000 }).toContain("p-disabled");
expect(daysRequests[0]).toContain(
`/days/${isoYmd(minDate)}/382/route/LED-KUF/schedule/`,
);
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<void>((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");
});