From 6cf57596bf562cc0d2c6ba6400a364966a8cb280 Mon Sep 17 00:00:00 2001 From: gnezim Date: Thu, 14 May 2026 17:22:11 +0300 Subject: [PATCH] Fix schedule aircraft link target --- .../components/ScheduleDetailsPage.tsx | 5 +- .../components/ScheduleLegDetails.test.tsx | 24 ++++++++ .../components/ScheduleLegDetails.tsx | 46 ++++++-------- tests/e2e/schedule-aircraft-link.spec.ts | 61 +++++++++++++++++++ 4 files changed, 107 insertions(+), 29 deletions(-) create mode 100644 tests/e2e/schedule-aircraft-link.spec.ts diff --git a/src/features/schedule/components/ScheduleDetailsPage.tsx b/src/features/schedule/components/ScheduleDetailsPage.tsx index 580da351..e2fb633b 100644 --- a/src/features/schedule/components/ScheduleDetailsPage.tsx +++ b/src/features/schedule/components/ScheduleDetailsPage.tsx @@ -473,7 +473,10 @@ export const ScheduleDetailsPage: FC = ({ {renderBody(flight)} - + {/* Angular hides the weekly operating schedule on multi-leg chains; keep it on direct flights. */} diff --git a/src/features/schedule/components/ScheduleLegDetails.test.tsx b/src/features/schedule/components/ScheduleLegDetails.test.tsx index 2e989d46..cf7cec5f 100644 --- a/src/features/schedule/components/ScheduleLegDetails.test.tsx +++ b/src/features/schedule/components/ScheduleLegDetails.test.tsx @@ -42,6 +42,30 @@ function makeFlight(meal: MealType[] | undefined): ISimpleFlight { } describe("ScheduleLegDetails Питание sub-icons", () => { + it("links the aircraft title to Angular's Aeroflot plane park URL", () => { + render(); + + const link = screen.getByRole("link", { name: "Sukhoi SuperJet 100" }); + + expect(link.getAttribute("href")).toBe( + "http://www.aeroflot.ru/cms/ru/flight/plane_park", + ); + expect(link.getAttribute("target")).toBe("_blank"); + expect(link.getAttribute("rel")).toBe("noopener noreferrer"); + }); + + it("uses the language prefix from locale for the plane park URL", () => { + render(); + + expect( + screen + .getByRole("link", { name: "Sukhoi SuperJet 100" }) + .getAttribute("href"), + ).toBe( + "http://www.aeroflot.ru/cms/en/flight/plane_park", + ); + }); + it("renders no meal-class sub-icons when equipment.meal is empty", () => { render(); expect(screen.queryByText("FOOD.ECONOMY")).toBeNull(); diff --git a/src/features/schedule/components/ScheduleLegDetails.tsx b/src/features/schedule/components/ScheduleLegDetails.tsx index 6d95ec16..04c5271f 100644 --- a/src/features/schedule/components/ScheduleLegDetails.tsx +++ b/src/features/schedule/components/ScheduleLegDetails.tsx @@ -3,7 +3,7 @@ * each flight leg. Mirrors Angular's `flight-details-wrapper` accordion * (schedule view): two rows when the data exists. * - * 1. Борт — aircraft type with a link to its Aeroflot info page. + * 1. Борт — aircraft type with a link to Aeroflot's aircraft park. * 2. Питание на борту — meal-class sub-icons (Эконом / Комфорт / * Бизнес), each rendered ONLY when the API's * `equipment.meal[]` array contains the matching `type`. Matches @@ -25,18 +25,18 @@ import "./ScheduleLegDetails.scss"; export interface ScheduleLegDetailsProps { flight: ISimpleFlight; + locale?: string; } -/** Slug used for the aircraft catalog deep-link on www.aeroflot.ru. */ -function aircraftSlug(title: string | undefined | null): string | null { - if (!title) return null; - return title - .toLowerCase() - .replace(/\s+/g, "-") - .replace(/[^a-z0-9-]/g, ""); +function aircraftParkHref(locale: string | undefined): string { + const language = locale?.split("-")[0] || "ru"; + return `http://www.aeroflot.ru/cms/${language}/flight/plane_park`; } -export const ScheduleLegDetails: FC = ({ flight }) => { +export const ScheduleLegDetails: FC = ({ + flight, + locale, +}) => { const { t } = useTranslation(); const [expanded, setExpanded] = useState(true); @@ -47,13 +47,7 @@ export const ScheduleLegDetails: FC = ({ flight }) => { leg.equipment?.aircraft?.actual?.title ?? leg.equipment?.aircraft?.scheduled?.title ?? ""; - const slug = aircraftSlug(aircraft); - // External Aeroflot page that describes the aircraft model. Angular - // uses a curated slug map; the simple kebab-case works for common - // models (e.g. sukhoi-superjet-100). - const planeUrl = slug - ? `https://www.aeroflot.ru/ru-ru/about/aircrafts/${slug}` - : null; + const planeUrl = aircraftParkHref(locale); return (
@@ -117,18 +111,14 @@ export const ScheduleLegDetails: FC = ({ flight }) => { {t("SHARED.PLANE")} - {planeUrl ? ( - - {aircraft} - - ) : ( - {aircraft} - )} + + {aircraft} +
)} diff --git a/tests/e2e/schedule-aircraft-link.spec.ts b/tests/e2e/schedule-aircraft-link.spec.ts new file mode 100644 index 00000000..c3916f85 --- /dev/null +++ b/tests/e2e/schedule-aircraft-link.spec.ts @@ -0,0 +1,61 @@ +import { test, expect } from "./fixtures/console-gate"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +// Schedule details uses the same Angular aircraft-link behavior as +// online-board: the model text under "Борт" opens the generic plane park page. + +const FIXTURE_DIR = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "../fixtures/api", +); +const scheduleDetails = fs.readFileSync( + path.join(FIXTURE_DIR, "schedule-details.json"), + "utf8", +); + +const URL = + "/ru-ru/schedule/VVO/SU5752-20260518/KJA/SU6837-20260519/MJZ?request=schedule-route-VVO-MJZ-20260518-20260524"; + +test("Schedule details aircraft title opens Aeroflot plane park in a new tab", async ({ + page, + context, + consoleMessages, +}) => { + await page.route("**/api/flights/v1.1/ru/schedule/details?**", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: scheduleDetails, + }); + }); + + await context.route("http://www.aeroflot.ru/cms/ru/flight/plane_park", async (route) => { + await route.fulfill({ + status: 200, + contentType: "text/html", + body: "Plane park", + }); + }); + + await page.goto(URL); + + const details = page.locator(".schedule-leg-details"); + await expect(details).toHaveCount(1, { timeout: 15000 }); + await expect(details.getByText("Борт", { exact: true })).toBeVisible(); + + const link = details.locator("a.schedule-leg-details__link"); + await expect(link).toHaveText("Sukhoi SuperJet 100"); + await expect(link).toHaveAttribute( + "href", + "http://www.aeroflot.ru/cms/ru/flight/plane_park", + ); + await expect(link).toHaveAttribute("target", "_blank"); + + const popupPromise = page.waitForEvent("popup"); + await link.click(); + const popup = await popupPromise; + await expect(popup).toHaveURL("http://www.aeroflot.ru/cms/ru/flight/plane_park"); + await popup.close(); +});