Fix schedule aircraft link target
This commit is contained in:
@@ -473,7 +473,10 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
|
||||
<FlightCard flight={flight} direction="schedule" />
|
||||
<IFlyWarning flightNumber={flight.flightId.flightNumber} />
|
||||
{renderBody(flight)}
|
||||
<ScheduleLegDetails flight={flight as unknown as ISimpleFlight} />
|
||||
<ScheduleLegDetails
|
||||
flight={flight as unknown as ISimpleFlight}
|
||||
locale={locale}
|
||||
/>
|
||||
|
||||
{/* Angular hides the weekly operating schedule on
|
||||
multi-leg chains; keep it on direct flights. */}
|
||||
|
||||
@@ -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(<ScheduleLegDetails flight={makeFlight([])} locale="ru-ru" />);
|
||||
|
||||
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(<ScheduleLegDetails flight={makeFlight([])} locale="en-us" />);
|
||||
|
||||
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(<ScheduleLegDetails flight={makeFlight([])} />);
|
||||
expect(screen.queryByText("FOOD.ECONOMY")).toBeNull();
|
||||
|
||||
@@ -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<ScheduleLegDetailsProps> = ({ flight }) => {
|
||||
export const ScheduleLegDetails: FC<ScheduleLegDetailsProps> = ({
|
||||
flight,
|
||||
locale,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [expanded, setExpanded] = useState<boolean>(true);
|
||||
|
||||
@@ -47,13 +47,7 @@ export const ScheduleLegDetails: FC<ScheduleLegDetailsProps> = ({ 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 (
|
||||
<div className="schedule-leg-details" data-testid="schedule-leg-details">
|
||||
@@ -117,18 +111,14 @@ export const ScheduleLegDetails: FC<ScheduleLegDetailsProps> = ({ flight }) => {
|
||||
<span className="schedule-leg-details__label">
|
||||
{t("SHARED.PLANE")}
|
||||
</span>
|
||||
{planeUrl ? (
|
||||
<a
|
||||
className="schedule-leg-details__link"
|
||||
href={planeUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{aircraft}
|
||||
</a>
|
||||
) : (
|
||||
<span className="schedule-leg-details__value">{aircraft}</span>
|
||||
)}
|
||||
<a
|
||||
className="schedule-leg-details__link"
|
||||
href={planeUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{aircraft}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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: "<!doctype html><title>Plane park</title>",
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user