Audit direct flight details per TZ §4.1.15.4/.10/.11
Gaps found and closed: - Departure gate (§4.1.15.4): rendered in boarding accordion row body, sourced from leg.departure.gate (Angular parity: flight-details-boarding L21) - Departure transfer type / dispatch (§4.1.15.4): rendered in boarding row body from leg.departure.dispatch (Angular parity: flight-details-boarding L17) - Aircraft tail number (§4.1.15.4): rendered in AircraftPanel from aircraft.registration field; AIRPLANE.TAIL-NUMBER added to all 9 locales - BoardingPanel accepts optional departure prop for gate/dispatch display; legacy hidden panel keeps existing testids without duplication - §4.1.15.10 meals + §4.1.15.11 services already implemented (assertion tests already cover icon rendering, links, fallback icon) Tests added: 4 in BoardingPanel, 2 in AircraftPanel, 3 in FlightDetailsAccordion
This commit is contained in:
@@ -67,6 +67,25 @@ describe("AircraftPanel", () => {
|
||||
expect(screen.getByTestId("aircraft-panel")).toBeTruthy();
|
||||
});
|
||||
|
||||
// §4.1.15.4: tail number (registration mark) in aircraft panel
|
||||
it("4.1.15.4-TailNumber: renders tail number when aircraft.registration is present", () => {
|
||||
const eq: IEquipmentFull = {
|
||||
aircraft: { actual: { title: "Airbus A320", registration: "VP-BQS" } },
|
||||
};
|
||||
render(<AircraftPanel equipment={eq} />);
|
||||
expect(screen.getByText("VP-BQS")).toBeTruthy();
|
||||
// label key is AIRPLANE.TAIL-NUMBER (t mock returns key)
|
||||
expect(screen.getByText("AIRPLANE.TAIL-NUMBER")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("4.1.15.4-TailNumber-Absent: omits tail-number row when registration is not present", () => {
|
||||
const eq: IEquipmentFull = {
|
||||
aircraft: { actual: { title: "Airbus A320" } },
|
||||
};
|
||||
render(<AircraftPanel equipment={eq} />);
|
||||
expect(screen.queryByText("AIRPLANE.TAIL-NUMBER")).toBeNull();
|
||||
});
|
||||
|
||||
// §4.1.15.9: previous-flight chip must be a link (opens new tab) when the
|
||||
// flight's scheduled departure date is more recent than today − 2 days.
|
||||
describe("previous-flight link (§4.1.15.9)", () => {
|
||||
|
||||
@@ -112,6 +112,11 @@ export const AircraftPanel: FC<AircraftPanelProps> = ({
|
||||
className?: string;
|
||||
}> = [];
|
||||
if (aircraft?.name) props.push({ label: t("AIRPLANE.NAME"), value: aircraft.name });
|
||||
// §4.1.15.4: tail number (registration mark, e.g. "VP-BQS") from the
|
||||
// aircraft.actual.registration field. Shown when present.
|
||||
if (aircraft?.registration) {
|
||||
props.push({ label: t("AIRPLANE.TAIL-NUMBER"), value: aircraft.registration });
|
||||
}
|
||||
if (total > 0) props.push({ label: t("AIRPLANE.SEATS-TOTAL"), value: total });
|
||||
if (economy > 0) props.push({ label: t("AIRPLANE.SEATS-ECONOMY"), value: economy });
|
||||
if (comfort > 0) props.push({ label: t("AIRPLANE.SEATS-COMFORT"), value: comfort });
|
||||
|
||||
@@ -43,4 +43,31 @@ describe("BoardingPanel", () => {
|
||||
render(<BoardingPanel item={baseItem} />);
|
||||
expect(screen.getByTestId("boarding-panel")).toBeTruthy();
|
||||
});
|
||||
|
||||
// §4.1.15.4: departure gate + transfer type in boarding panel (Angular parity)
|
||||
it("4.1.15.4-Boarding-Gate: renders gate row when departure.gate is present", () => {
|
||||
render(<BoardingPanel item={baseItem} departure={{ dispatch: undefined, gate: "D5" }} />);
|
||||
expect(screen.getByTestId("boarding-gate")).toBeTruthy();
|
||||
expect(screen.getByText("D5")).toBeTruthy();
|
||||
expect(screen.getByText("SHARED.NUMBER-EXIT")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("4.1.15.4-Boarding-Dispatch: renders dispatch type row when departure.dispatch is present", () => {
|
||||
render(<BoardingPanel item={baseItem} departure={{ dispatch: "Bus", gate: undefined }} />);
|
||||
expect(screen.getByTestId("boarding-dispatch")).toBeTruthy();
|
||||
// t mock returns key — dispatch key is "DISPATCH.Bus"
|
||||
expect(screen.getByText("DISPATCH.Bus")).toBeTruthy();
|
||||
expect(screen.getByText("SHARED.LANDING-TRANSFER")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("4.1.15.4-Boarding-NoGate: omits gate row when departure.gate is absent", () => {
|
||||
render(<BoardingPanel item={baseItem} departure={{ dispatch: undefined, gate: undefined }} />);
|
||||
expect(screen.queryByTestId("boarding-gate")).toBeNull();
|
||||
});
|
||||
|
||||
it("4.1.15.4-Boarding-NoDeparture: omits gate/dispatch rows when departure prop is not passed", () => {
|
||||
render(<BoardingPanel item={baseItem} />);
|
||||
expect(screen.queryByTestId("boarding-gate")).toBeNull();
|
||||
expect(screen.queryByTestId("boarding-dispatch")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
formatUtcOffset,
|
||||
formatDayMonthYear,
|
||||
} from "@/shared/utils/datetime/index.js";
|
||||
import type { IFlightTransitionItem, FlightTransitionStatus } from "../../types.js";
|
||||
import type { IFlightTransitionItem, IFlightLegDepartureStation, FlightTransitionStatus } from "../../types.js";
|
||||
import "./panels.scss";
|
||||
|
||||
function formatTransitionTimestamp(iso: string): string {
|
||||
@@ -25,9 +25,11 @@ const STATUS_KEYS: Record<FlightTransitionStatus, string> = {
|
||||
|
||||
export interface BoardingPanelProps {
|
||||
item: IFlightTransitionItem;
|
||||
/** Departure station — used to show transfer type + gate (§4.1.15.4). */
|
||||
departure?: Pick<IFlightLegDepartureStation, "dispatch" | "gate">;
|
||||
}
|
||||
|
||||
export const BoardingPanel: FC<BoardingPanelProps> = ({ item }) => {
|
||||
export const BoardingPanel: FC<BoardingPanelProps> = ({ item, departure }) => {
|
||||
const { t } = useTranslation();
|
||||
const hasEnd = Boolean(item.end?.local);
|
||||
|
||||
@@ -47,6 +49,20 @@ export const BoardingPanel: FC<BoardingPanelProps> = ({ item }) => {
|
||||
<span className="details-panel__value">{formatTransitionTimestamp(item.end.local)}</span>
|
||||
</div>
|
||||
)}
|
||||
{/* §4.1.15.4: dispatch type (Bus / Bridge) and departure gate from boarding panel
|
||||
Angular parity: flight-details-boarding.component.html lines 17–24 */}
|
||||
{departure?.dispatch && (
|
||||
<div className="details-panel__row" data-testid="boarding-dispatch">
|
||||
<span className="details-panel__label">{t("SHARED.LANDING-TRANSFER")}</span>
|
||||
<span className="details-panel__value">{t(`DISPATCH.${departure.dispatch}`)}</span>
|
||||
</div>
|
||||
)}
|
||||
{departure?.gate && (
|
||||
<div className="details-panel__row" data-testid="boarding-gate">
|
||||
<span className="details-panel__label">{t("SHARED.NUMBER-EXIT")}</span>
|
||||
<span className="details-panel__value">{departure.gate}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,6 +40,29 @@ function makeLeg(overrides: Partial<IFlightLeg> = {}): IFlightLeg {
|
||||
return { ...base, ...overrides };
|
||||
}
|
||||
|
||||
function makeLegWithBoarding(gate?: string, dispatch?: string): IFlightLeg {
|
||||
return makeLeg({
|
||||
departure: {
|
||||
scheduled: { airport: "JFK", airportCode: "JFK", city: "New York", cityCode: "NYC", countryCode: "US" },
|
||||
dispatch,
|
||||
gate,
|
||||
terminal: "",
|
||||
checkingStatus: "Scheduled",
|
||||
parkingStand: "",
|
||||
times: { scheduledDeparture: { dayChange: { value: 0, title: "" }, local: "", localTime: "", tzOffset: 0, utc: "" } },
|
||||
},
|
||||
transition: {
|
||||
boarding: {
|
||||
start: { dayChange: { value: 0, title: "" }, local: "11:00", localTime: "11:00", tzOffset: 0, utc: "" },
|
||||
end: { dayChange: { value: 0, title: "" }, local: "11:30", localTime: "11:30", tzOffset: 0, utc: "" },
|
||||
status: "InProgress",
|
||||
isActual: true,
|
||||
},
|
||||
},
|
||||
status: "InFlight",
|
||||
});
|
||||
}
|
||||
|
||||
describe("FlightDetailsAccordion", () => {
|
||||
it("returns null when no panels should be visible", () => {
|
||||
const leg = makeLeg();
|
||||
@@ -106,4 +129,28 @@ describe("FlightDetailsAccordion", () => {
|
||||
render(<FlightDetailsAccordion leg={leg} viewType="Onlineboard" />);
|
||||
expect(screen.getByText("DETAILS.ON_BOARD_SERVICES")).toBeTruthy();
|
||||
});
|
||||
|
||||
// §4.1.15.4: departure gate + transfer type in boarding panel row
|
||||
describe("boarding row gate + dispatch (§4.1.15.4)", () => {
|
||||
it("4.1.15.4-AccordionBoarding-Gate: shows gate in boarding body when departure.gate is set", () => {
|
||||
const leg = makeLegWithBoarding("D5", undefined);
|
||||
render(<FlightDetailsAccordion leg={leg} viewType="Onlineboard" />);
|
||||
expect(screen.getByTestId("boarding-gate")).toBeTruthy();
|
||||
expect(screen.getByText("D5")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("4.1.15.4-AccordionBoarding-Dispatch: shows dispatch in boarding body when departure.dispatch is set", () => {
|
||||
const leg = makeLegWithBoarding(undefined, "Bus");
|
||||
render(<FlightDetailsAccordion leg={leg} viewType="Onlineboard" />);
|
||||
expect(screen.getByTestId("boarding-dispatch")).toBeTruthy();
|
||||
expect(screen.getByText("DISPATCH.Bus")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("4.1.15.4-AccordionBoarding-NoFields: omits gate/dispatch rows when departure has neither", () => {
|
||||
const leg = makeLegWithBoarding(undefined, undefined);
|
||||
render(<FlightDetailsAccordion leg={leg} viewType="Onlineboard" />);
|
||||
expect(screen.queryByTestId("boarding-gate")).toBeNull();
|
||||
expect(screen.queryByTestId("boarding-dispatch")).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -161,7 +161,25 @@ export const FlightDetailsAccordion: FC<FlightDetailsAccordionProps> = ({ leg, v
|
||||
icon: ICON_BOARDING,
|
||||
title: t("DETAILS.BOARDING"),
|
||||
statusStatus: boarding.status,
|
||||
body: <TransitionTimes item={boarding} testId="boarding-times" />,
|
||||
body: (
|
||||
<>
|
||||
<TransitionTimes item={boarding} testId="boarding-times" />
|
||||
{/* §4.1.15.4: dispatch type (Bus / Bridge) and departure gate.
|
||||
Angular parity: flight-details-boarding.component.html lines 17–24 */}
|
||||
{leg.departure.dispatch && (
|
||||
<div className="details-panel__row" data-testid="boarding-dispatch">
|
||||
<span className="details-panel__label">{t("SHARED.LANDING-TRANSFER")}</span>
|
||||
<span className="details-panel__value">{t(`DISPATCH.${leg.departure.dispatch}`)}</span>
|
||||
</div>
|
||||
)}
|
||||
{leg.departure.gate && (
|
||||
<div className="details-panel__row" data-testid="boarding-gate">
|
||||
<span className="details-panel__label">{t("SHARED.NUMBER-EXIT")}</span>
|
||||
<span className="details-panel__value">{leg.departure.gate}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
legacyTestId: "boarding-panel",
|
||||
isTransition: true,
|
||||
});
|
||||
@@ -229,6 +247,9 @@ export const FlightDetailsAccordion: FC<FlightDetailsAccordionProps> = ({ leg, v
|
||||
if (row.id === "boarding" && leg.transition?.boarding) {
|
||||
legacyPanels.push(
|
||||
<div key="legacy-boarding" className="visually-hidden">
|
||||
{/* departure prop intentionally omitted — dispatch/gate already
|
||||
rendered in the visible accordion row body above to avoid
|
||||
duplicate data-testid="boarding-gate/dispatch" in the DOM. */}
|
||||
<BoardingPanel item={leg.transition.boarding} />
|
||||
</div>,
|
||||
);
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"SEATS-BUSINESS": "",
|
||||
"SEATS-COMFORT": "",
|
||||
"SEATS-ECONOMY": "",
|
||||
"SEATS-TOTAL": ""
|
||||
"SEATS-TOTAL": "",
|
||||
"TAIL-NUMBER": "Kennzeichen"
|
||||
},
|
||||
"BOARD": {
|
||||
"ARRIVAL": "Ankunft",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"SEATS-BUSINESS": "Business",
|
||||
"SEATS-COMFORT": "Comfort",
|
||||
"SEATS-ECONOMY": "Economy",
|
||||
"SEATS-TOTAL": "Number of seats"
|
||||
"SEATS-TOTAL": "Number of seats",
|
||||
"TAIL-NUMBER": "Tail number"
|
||||
},
|
||||
"BOARD": {
|
||||
"ARRIVAL": "Arrival",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"SEATS-BUSINESS": "",
|
||||
"SEATS-COMFORT": "",
|
||||
"SEATS-ECONOMY": "",
|
||||
"SEATS-TOTAL": ""
|
||||
"SEATS-TOTAL": "",
|
||||
"TAIL-NUMBER": "Matrícula"
|
||||
},
|
||||
"BOARD": {
|
||||
"ARRIVAL": "Llegada",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"SEATS-BUSINESS": "",
|
||||
"SEATS-COMFORT": "",
|
||||
"SEATS-ECONOMY": "",
|
||||
"SEATS-TOTAL": ""
|
||||
"SEATS-TOTAL": "",
|
||||
"TAIL-NUMBER": "Immatriculation"
|
||||
},
|
||||
"BOARD": {
|
||||
"ARRIVAL": "Arrivée",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"SEATS-BUSINESS": "",
|
||||
"SEATS-COMFORT": "",
|
||||
"SEATS-ECONOMY": "",
|
||||
"SEATS-TOTAL": ""
|
||||
"SEATS-TOTAL": "",
|
||||
"TAIL-NUMBER": "Numero di coda"
|
||||
},
|
||||
"BOARD": {
|
||||
"ARRIVAL": "Arrivo",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"SEATS-BUSINESS": "",
|
||||
"SEATS-COMFORT": "",
|
||||
"SEATS-ECONOMY": "",
|
||||
"SEATS-TOTAL": ""
|
||||
"SEATS-TOTAL": "",
|
||||
"TAIL-NUMBER": "機体番号"
|
||||
},
|
||||
"BOARD": {
|
||||
"ARRIVAL": "到着",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"SEATS-BUSINESS": "",
|
||||
"SEATS-COMFORT": "",
|
||||
"SEATS-ECONOMY": "",
|
||||
"SEATS-TOTAL": ""
|
||||
"SEATS-TOTAL": "",
|
||||
"TAIL-NUMBER": "꼬리번호"
|
||||
},
|
||||
"BOARD": {
|
||||
"ARRIVAL": "도착",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"SEATS-BUSINESS": "Бизнес",
|
||||
"SEATS-COMFORT": "Комфорт",
|
||||
"SEATS-ECONOMY": "Эконом",
|
||||
"SEATS-TOTAL": "Количество мест"
|
||||
"SEATS-TOTAL": "Количество мест",
|
||||
"TAIL-NUMBER": "Бортовой номер"
|
||||
},
|
||||
"BOARD": {
|
||||
"ARRIVAL": "Прилет",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"SEATS-BUSINESS": "",
|
||||
"SEATS-COMFORT": "",
|
||||
"SEATS-ECONOMY": "",
|
||||
"SEATS-TOTAL": ""
|
||||
"SEATS-TOTAL": "",
|
||||
"TAIL-NUMBER": "机尾号"
|
||||
},
|
||||
"BOARD": {
|
||||
"ARRIVAL": "到达",
|
||||
|
||||
Reference in New Issue
Block a user