Preserve online board flight suffixes
This commit is contained in:
@@ -88,6 +88,13 @@ describe("DetailsHeaderBadge", () => {
|
||||
expect(screen.getByText("SU 0022")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("TIRREDESIGN-13: renders suffix in primary flight number", () => {
|
||||
const flight = makeDirect();
|
||||
flight.flightId = { ...flight.flightId, flightNumber: "0038", suffix: "D" };
|
||||
render(<DetailsHeaderBadge flight={flight} locale="ru" />);
|
||||
expect(screen.getByText("SU 0038D")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders operator-logo", () => {
|
||||
render(<DetailsHeaderBadge flight={makeDirect()} locale="ru" />);
|
||||
expect(screen.getByTestId("operator-logo")).toBeTruthy();
|
||||
|
||||
@@ -39,7 +39,7 @@ export const DetailsHeaderBadge: FC<DetailsHeaderBadgeProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const codeshareLegs = getCodeshareLegs(flight);
|
||||
const primaryNumber = `${flight.flightId.carrier} ${flight.flightId.flightNumber}`;
|
||||
const primaryNumber = `${flight.flightId.carrier} ${flight.flightId.flightNumber}${flight.flightId.suffix ?? ""}`;
|
||||
const carrier = operatingCarrier(flight.operatingBy) ?? flight.flightId.carrier;
|
||||
|
||||
return (
|
||||
|
||||
@@ -60,6 +60,15 @@ describe("FlightsMiniListItem", () => {
|
||||
expect(screen.getByText(/SU\s*0022/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("TIRREDESIGN-13: renders suffix in flight number", () => {
|
||||
const flight = makeDirectFlight({
|
||||
id: "SU0038D-20260514",
|
||||
flightId: { carrier: "SU", flightNumber: "0038", suffix: "D", date: "20260514" },
|
||||
});
|
||||
render(<FlightsMiniListItem flight={flight} isSelected={false} lang="ru" />);
|
||||
expect(screen.getByText(/SU\s*0038D/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders departure and arrival times", () => {
|
||||
const flight = makeDirectFlight();
|
||||
render(<FlightsMiniListItem flight={flight} isSelected={false} lang="ru" />);
|
||||
|
||||
@@ -127,14 +127,14 @@ export const FlightsMiniListItem = forwardRef<HTMLAnchorElement, FlightsMiniList
|
||||
<div className="mini-list__flight-number">
|
||||
{(() => {
|
||||
const childIds = (flight as typeof flight & {
|
||||
_childFlightIds?: { carrier: string; flightNumber: string }[];
|
||||
_childFlightIds?: { carrier: string; flightNumber: string; suffix?: string }[];
|
||||
})._childFlightIds;
|
||||
if (childIds && childIds.length > 1) {
|
||||
return childIds
|
||||
.map((c) => `${c.carrier} ${c.flightNumber}`)
|
||||
.map((c) => `${c.carrier} ${c.flightNumber}${c.suffix ?? ""}`)
|
||||
.join(", ");
|
||||
}
|
||||
return `${flight.flightId.carrier} ${flight.flightId.flightNumber}`;
|
||||
return `${flight.flightId.carrier} ${flight.flightId.flightNumber}${flight.flightId.suffix ?? ""}`;
|
||||
})()}
|
||||
<span
|
||||
className={`mini-list__status-icon ${iconColor}`}
|
||||
|
||||
@@ -173,6 +173,29 @@ describe("OnlineBoardDetailsPage", () => {
|
||||
expect(screen.getAllByText("SU 100").length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("TIRREDESIGN-13: renders suffix in the details page flight number", () => {
|
||||
const suffixFlight = {
|
||||
...mockFlight,
|
||||
id: "SU0038D-20260514",
|
||||
flightId: { carrier: "SU", flightNumber: "0038", suffix: "D", date: "20260514" },
|
||||
} as IDirectFlight;
|
||||
mockState = {
|
||||
flight: suffixFlight,
|
||||
allFlights: [suffixFlight],
|
||||
daysOfFlight: ["20260514"],
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
render(
|
||||
<OnlineBoardDetailsPage
|
||||
flightId={{ carrier: "SU", flightNumber: "0038", suffix: "D", date: "20260514" }}
|
||||
locale="ru"
|
||||
canonicalOrigin="https://www.aeroflot.ru"
|
||||
/>,
|
||||
);
|
||||
expect(screen.getAllByText("SU 0038D").length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("renders loading skeleton", () => {
|
||||
mockState = { flight: null, allFlights: [], daysOfFlight: [], loading: true, error: null };
|
||||
render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://www.aeroflot.ru" />);
|
||||
@@ -440,6 +463,20 @@ describe("OnlineBoardDetailsPage", () => {
|
||||
expect(link?.getAttribute("href")).toContain("/ru/onlineboard/flight/SU1234-20260515");
|
||||
});
|
||||
|
||||
it("TIRREDESIGN-13: flight context keeps suffix in breadcrumb back URL", () => {
|
||||
mockSearchParamsInstance = new URLSearchParams("request=onlineboard-flight-SU0038D-20260514");
|
||||
const { container } = render(
|
||||
<OnlineBoardDetailsPage
|
||||
flightId={mockFlightId}
|
||||
locale="ru"
|
||||
canonicalOrigin="https://example.com"
|
||||
/>,
|
||||
);
|
||||
const link = getCrumbLink(container, "BREADCRUMBS.FLIGHT-NUMBER");
|
||||
expect(link).toBeTruthy();
|
||||
expect(link?.getAttribute("href")).toContain("/ru/onlineboard/flight/SU0038D-20260514");
|
||||
});
|
||||
|
||||
it("route context → leaf 'Маршрут: …' linking back to /route/", () => {
|
||||
mockSearchParamsInstance = new URLSearchParams("request=onlineboard-route-MOW-LED-20260515");
|
||||
const { container } = render(
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useLiveFlightDetails } from "../hooks/useLiveFlightDetails.js";
|
||||
import { useOnlineBoard } from "../hooks/useOnlineBoard.js";
|
||||
import { parseDetailsRequestParam } from "@/shared/detailsRequestParam.js";
|
||||
import { buildFlightJsonLd } from "../json-ld.js";
|
||||
import { buildOnlineBoardUrl } from "../url.js";
|
||||
import { buildOnlineBoardUrl, parseFlightUrlParams } from "../url.js";
|
||||
import { getFlightSearchDate } from "../flightSearchDate.js";
|
||||
import { useCityName, useStationDisplayName } from "@/shared/hooks/useDictionaries.js";
|
||||
import { FlightDetailsAccordion } from "./details-panels/FlightDetailsAccordion.js";
|
||||
@@ -65,6 +65,10 @@ export interface OnlineBoardDetailsPageProps {
|
||||
canonicalOrigin: string;
|
||||
}
|
||||
|
||||
function parseParentFlightRequest(flightNumber: string, date: string): IParsedFlightId | null {
|
||||
return parseFlightUrlParams(`${flightNumber}-${date}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* One side of a leg's station block — station code + airport name + city
|
||||
* + terminal, plus scheduled / expected / actual times formatted as
|
||||
@@ -449,13 +453,14 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
|
||||
const backUrl = (() => {
|
||||
switch (parentRequest.kind) {
|
||||
case "flight": {
|
||||
const m = parentRequest.flightNumber.match(/^([A-Z]{2,3})(\d+)$/);
|
||||
if (!m || !m[1] || !m[2]) return `/${locale}/onlineboard`;
|
||||
const parsed = parseParentFlightRequest(parentRequest.flightNumber, parentRequest.date);
|
||||
if (!parsed) return `/${locale}/onlineboard`;
|
||||
return `/${locale}/${buildOnlineBoardUrl({
|
||||
type: "flight",
|
||||
carrier: m[1],
|
||||
flightNumber: m[2],
|
||||
date: parentRequest.date,
|
||||
carrier: parsed.carrier,
|
||||
flightNumber: parsed.flightNumber,
|
||||
...(parsed.suffix ? { suffix: parsed.suffix } : {}),
|
||||
date: parsed.date,
|
||||
})}`;
|
||||
}
|
||||
case "departure":
|
||||
@@ -483,8 +488,10 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
|
||||
switch (parentRequest.kind) {
|
||||
case "flight": {
|
||||
// Angular renders "Рейс: SU 6188" — carrier and number space-separated
|
||||
const m = parentRequest.flightNumber.match(/^([A-Z]{2,3})(\d+)$/);
|
||||
const formatted = m?.[1] && m?.[2] ? `${m[1]} ${m[2]}` : parentRequest.flightNumber;
|
||||
const parsed = parseParentFlightRequest(parentRequest.flightNumber, parentRequest.date);
|
||||
const formatted = parsed
|
||||
? `${parsed.carrier} ${parsed.flightNumber}${parsed.suffix ?? ""}`
|
||||
: parentRequest.flightNumber;
|
||||
return t("BREADCRUMBS.FLIGHT-NUMBER", { flightNumber: formatted });
|
||||
}
|
||||
case "departure":
|
||||
@@ -594,7 +601,7 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
|
||||
}
|
||||
|
||||
const legs = getLegs(displayFlight);
|
||||
const flightNumber = `${displayFlight.flightId.carrier} ${displayFlight.flightId.flightNumber}`;
|
||||
const flightNumber = `${displayFlight.flightId.carrier} ${displayFlight.flightId.flightNumber}${displayFlight.flightId.suffix ?? ""}`;
|
||||
|
||||
const firstLeg = legs[0];
|
||||
const lastLeg = legs[legs.length - 1];
|
||||
|
||||
@@ -94,6 +94,14 @@ describe("buildFlightJsonLd", () => {
|
||||
expect(result.flightNumber).toBe("SU0100");
|
||||
});
|
||||
|
||||
it("TIRREDESIGN-13: includes suffix in flightNumber", () => {
|
||||
const flight = makeDirectFlight({ carrier: "SU", flightNumber: "0038" });
|
||||
flight.flightId.suffix = "D";
|
||||
const result = buildFlightJsonLd(flight);
|
||||
|
||||
expect(result.flightNumber).toBe("SU0038D");
|
||||
});
|
||||
|
||||
it("maps departure airport", () => {
|
||||
const flight = makeDirectFlight({ depCode: "SVO", depAirport: "Sheremetyevo" });
|
||||
const result = buildFlightJsonLd(flight);
|
||||
|
||||
@@ -48,11 +48,11 @@ export function buildFlightJsonLd(flight: ISimpleFlight): Flight {
|
||||
const firstLeg = getFirstLeg(flight);
|
||||
const lastLeg = getLastLeg(flight);
|
||||
|
||||
const { carrier, flightNumber } = flight.flightId;
|
||||
const { carrier, flightNumber, suffix } = flight.flightId;
|
||||
|
||||
const result: Flight = {
|
||||
"@type": "Flight",
|
||||
flightNumber: `${carrier}${flightNumber}`,
|
||||
flightNumber: `${carrier}${flightNumber}${suffix ?? ""}`,
|
||||
provider: {
|
||||
"@type": "Airline",
|
||||
name: "Aeroflot",
|
||||
|
||||
@@ -170,6 +170,15 @@ describe("4.1.13.3 Table 23 — Direct flight collapsed row", () => {
|
||||
expect(screen.getByTestId("flight-carrier-number").textContent).toContain("SU 0022");
|
||||
});
|
||||
|
||||
it("TIRREDESIGN-13: renders suffix in collapsed row flight number", () => {
|
||||
const flight = {
|
||||
...makeFlight({}),
|
||||
flightId: { carrier: "SU", flightNumber: "0038", suffix: "D", date: "20260514" },
|
||||
} as ISimpleFlight;
|
||||
render(<FlightCard flight={flight} expandable />);
|
||||
expect(screen.getByTestId("flight-carrier-number").textContent).toContain("SU 0038D");
|
||||
});
|
||||
|
||||
it("T23-C2: renders operator logo (full, not mini/round) for direct flight", () => {
|
||||
render(<FlightCard flight={makeFlight({})} expandable />);
|
||||
const logo = screen.getByTestId("operator-logo");
|
||||
|
||||
@@ -184,7 +184,7 @@ export const FlightCard: FC<FlightCardProps> = ({
|
||||
? childFlightIds
|
||||
.map((id) => `${id.carrier} ${id.flightNumber}${id.suffix ?? ""}`)
|
||||
.join(", ")
|
||||
: `${flight.flightId.carrier} ${flight.flightId.flightNumber}`;
|
||||
: `${flight.flightId.carrier} ${flight.flightId.flightNumber}${flight.flightId.suffix ?? ""}`;
|
||||
// TZ §4.1.22: when OperatingBy is null, resolve carrier from flight-number
|
||||
// ranges (SU1-2999→SU, SU5000-5399→DP, SU5400-5799→HZ, etc.).
|
||||
// Falls back to the flight's own carrier code for non-SU flights or when
|
||||
|
||||
Reference in New Issue
Block a user