Audit Online-Board expanded row per TZ 4.1.13.4
Gap audit against §4.1.13.4.3 (Tables 29/30) found that the inline boarding/deboarding row in FlightCard's default expanded body was missing three attributes: - departure.gate / arrival.gate (boarding gate number) - departure.dispatch (трап/автобус transfer type) - arrival.bagBelt (baggage belt, deboarding only) Add all three as conditional fields in the transition block, guarded by the existing isArrival flag so departure shows gate+dispatch and arrival shows gate+bagBelt. Add DETAILS.DISPATCH i18n label (ru + en). Add 16 assertion tests covering time rows, transition status/times, gate, dispatch, bagBelt, and the share/details buttons. Deferred (DONE_WITH_CONCERNS): - Check-in counter number: API type has checkingStatus string but no counter number field; requires backend extension. - Aircraft tail number: field (aircraft.registration) exists in types but is only shown in the details-page AircraftPanel, not in the FlightCard expanded body; deferred to details-page parity task. - Code-share chips in expanded segment body: currently merged into the collapsed header number column via _childFlightIds; per-segment expanded display deferred to multi-leg task.
This commit is contained in:
@@ -80,7 +80,8 @@
|
|||||||
"GATE": "Gate",
|
"GATE": "Gate",
|
||||||
"BAG_BELT": "Baggage belt",
|
"BAG_BELT": "Baggage belt",
|
||||||
"CONFIGURATION": "Configuration",
|
"CONFIGURATION": "Configuration",
|
||||||
"STATUS": "Status"
|
"STATUS": "Status",
|
||||||
|
"DISPATCH": "Transfer"
|
||||||
},
|
},
|
||||||
"BOARDING-STATUSES": {
|
"BOARDING-STATUSES": {
|
||||||
"Expected": "Expected",
|
"Expected": "Expected",
|
||||||
|
|||||||
@@ -80,7 +80,8 @@
|
|||||||
"GATE": "Выход",
|
"GATE": "Выход",
|
||||||
"BAG_BELT": "Лента выдачи багажа",
|
"BAG_BELT": "Лента выдачи багажа",
|
||||||
"CONFIGURATION": "Компоновка",
|
"CONFIGURATION": "Компоновка",
|
||||||
"STATUS": "Статус"
|
"STATUS": "Статус",
|
||||||
|
"DISPATCH": "Трансфер"
|
||||||
},
|
},
|
||||||
"BOARDING-STATUSES": {
|
"BOARDING-STATUSES": {
|
||||||
"Expected": "Ожидается",
|
"Expected": "Ожидается",
|
||||||
|
|||||||
@@ -471,3 +471,185 @@ describe("4.1.13.3 — Collapsed row state", () => {
|
|||||||
expect(document.querySelector("[data-testid='flight-card-expanded']")).toBeNull();
|
expect(document.querySelector("[data-testid='flight-card-expanded']")).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// §4.1.13.4 — Expanded representation (Table 25 / §4.1.13.4.3)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function makeTransitionItem(status: "InProgress" | "Finished" | "Expected" = "InProgress") {
|
||||||
|
return {
|
||||||
|
start: {
|
||||||
|
dayChange: { value: 0, title: "" },
|
||||||
|
local: "2026-04-15T09:00:00+03:00",
|
||||||
|
localTime: "09:00",
|
||||||
|
tzOffset: 3,
|
||||||
|
utc: "2026-04-15T06:00:00Z",
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
dayChange: { value: 0, title: "" },
|
||||||
|
local: "2026-04-15T09:30:00+03:00",
|
||||||
|
localTime: "09:30",
|
||||||
|
tzOffset: 3,
|
||||||
|
utc: "2026-04-15T06:30:00Z",
|
||||||
|
},
|
||||||
|
status,
|
||||||
|
isActual: true,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a full ISimpleFlight with boarding transition + gate/dispatch override.
|
||||||
|
*/
|
||||||
|
function makeFlightWithBoarding(opts: {
|
||||||
|
gate?: string;
|
||||||
|
dispatch?: string;
|
||||||
|
bagBelt?: string;
|
||||||
|
direction?: "departure" | "arrival";
|
||||||
|
}): ISimpleFlight {
|
||||||
|
const base = makeFlight({});
|
||||||
|
const transition = makeTransitionItem();
|
||||||
|
// `makeFlight` always returns a Direct flight so `leg` is safe to access
|
||||||
|
const baseLeg = (base as ReturnType<typeof makeFlight> & { leg: ReturnType<typeof makeLeg> }).leg;
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
leg: {
|
||||||
|
...baseLeg,
|
||||||
|
transition: {
|
||||||
|
boarding: transition,
|
||||||
|
deboarding: transition,
|
||||||
|
},
|
||||||
|
departure: {
|
||||||
|
...baseLeg.departure,
|
||||||
|
gate: opts.gate,
|
||||||
|
dispatch: opts.dispatch,
|
||||||
|
},
|
||||||
|
arrival: {
|
||||||
|
...baseLeg.arrival,
|
||||||
|
gate: opts.gate,
|
||||||
|
bagBelt: opts.bagBelt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as ISimpleFlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("4.1.13.4 — Expanded row content (TZ §4.1.13.4.3)", () => {
|
||||||
|
|
||||||
|
it("4.1.13.4-R: expanded row shows scheduled departure time with label", () => {
|
||||||
|
const flight = makeFlight({ depLocal: "2026-04-15T10:00:00+03:00" });
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} />);
|
||||||
|
const expanded = screen.getByTestId("flight-card-expanded");
|
||||||
|
// Scheduled time in expanded body
|
||||||
|
const text = expanded.textContent ?? "";
|
||||||
|
expect(text).toContain("10:00");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: expanded row shows scheduled arrival time with label", () => {
|
||||||
|
const flight = makeFlight({ arrLocal: "2026-04-15T12:30:00+03:00" });
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} />);
|
||||||
|
const expanded = screen.getByTestId("flight-card-expanded");
|
||||||
|
expect(expanded.textContent).toContain("12:30");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: expanded row shows actual departure time when available with SHARED.ACTUAL caption", () => {
|
||||||
|
const flight = makeFlight({
|
||||||
|
depLocal: "2026-04-15T10:00:00+03:00",
|
||||||
|
depActualLocal: "2026-04-15T11:15:00+03:00",
|
||||||
|
});
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} />);
|
||||||
|
const expanded = screen.getByTestId("flight-card-expanded");
|
||||||
|
// Actual time and caption key
|
||||||
|
expect(expanded.textContent).toContain("11:15");
|
||||||
|
expect(expanded.textContent).toContain("SHARED.ACTUAL");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: boarding row shows status + start + end times when transition present", () => {
|
||||||
|
const flight = makeFlightWithBoarding({});
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} direction="departure" />);
|
||||||
|
const expanded = screen.getByTestId("flight-card-expanded");
|
||||||
|
// Label key
|
||||||
|
expect(expanded.textContent).toContain("DETAILS.BOARDING");
|
||||||
|
// Status key
|
||||||
|
expect(expanded.textContent).toContain("BOARDING-STATUSES.InProgress");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: deboarding row shown on arrival direction", () => {
|
||||||
|
const flight = makeFlightWithBoarding({});
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} direction="arrival" />);
|
||||||
|
const expanded = screen.getByTestId("flight-card-expanded");
|
||||||
|
expect(expanded.textContent).toContain("DETAILS.DEBOARDING");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: boarding gate rendered when departure.gate is set (§4.1.13.4.3 Table 29)", () => {
|
||||||
|
const flight = makeFlightWithBoarding({ gate: "B12", direction: "departure" });
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} direction="departure" />);
|
||||||
|
expect(screen.getByTestId("transition-gate")).toBeTruthy();
|
||||||
|
expect(screen.getByTestId("transition-gate").textContent).toContain("B12");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: gate is NOT rendered when gate is absent", () => {
|
||||||
|
const flight = makeFlightWithBoarding({});
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} direction="departure" />);
|
||||||
|
expect(document.querySelector("[data-testid='transition-gate']")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: dispatch (трап/автобус) rendered when departure.dispatch is set (§4.1.13.4.3 Table 29)", () => {
|
||||||
|
const flight = makeFlightWithBoarding({ dispatch: "Bridge" });
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} direction="departure" />);
|
||||||
|
expect(screen.getByTestId("transition-dispatch")).toBeTruthy();
|
||||||
|
// The dispatch value goes through t("DISPATCH.Bridge") which returns "DISPATCH.Bridge" in mock
|
||||||
|
expect(screen.getByTestId("transition-dispatch").textContent).toContain("Bridge");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: dispatch NOT rendered when departure.dispatch absent", () => {
|
||||||
|
const flight = makeFlightWithBoarding({});
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} direction="departure" />);
|
||||||
|
expect(document.querySelector("[data-testid='transition-dispatch']")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: baggage belt rendered for deboarding (arrival) when arrival.bagBelt is set (§4.1.13.4.3 Table 30)", () => {
|
||||||
|
const flight = makeFlightWithBoarding({ bagBelt: "3", direction: "arrival" });
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} direction="arrival" />);
|
||||||
|
expect(screen.getByTestId("transition-bag-belt")).toBeTruthy();
|
||||||
|
expect(screen.getByTestId("transition-bag-belt").textContent).toContain("3");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: baggage belt NOT rendered for departure direction", () => {
|
||||||
|
const flight = makeFlightWithBoarding({ bagBelt: "3" });
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} direction="departure" />);
|
||||||
|
expect(document.querySelector("[data-testid='transition-bag-belt']")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: arrival gate rendered in deboarding row when arrival.gate is set", () => {
|
||||||
|
const flight = makeFlightWithBoarding({ gate: "C7" });
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} direction="arrival" />);
|
||||||
|
expect(screen.getByTestId("transition-gate")).toBeTruthy();
|
||||||
|
expect(screen.getByTestId("transition-gate").textContent).toContain("C7");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: share button always present in expanded actions row", () => {
|
||||||
|
render(<FlightCard flight={makeFlight({})} expandable initialExpanded onViewDetails={() => {}} />);
|
||||||
|
expect(screen.getByTestId("flight-share-button")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: Details button present in expanded actions row", () => {
|
||||||
|
render(<FlightCard flight={makeFlight({})} expandable initialExpanded onViewDetails={() => {}} />);
|
||||||
|
expect(screen.getByTestId("flight-details-button")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: aircraft type shown under operator column when expanded (TZ Table 25 C3)", () => {
|
||||||
|
const flight = makeFlight({ aircraftTitle: "Airbus A320" });
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} />);
|
||||||
|
const number = screen.getByTestId("flight-carrier-number");
|
||||||
|
// Aircraft name rendered inside the number/header column when expanded
|
||||||
|
expect(number.textContent).toContain("Airbus A320");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4.1.13.4-R: no transition row shown when no transition data", () => {
|
||||||
|
const flight = makeFlight({});
|
||||||
|
render(<FlightCard flight={flight} expandable initialExpanded onViewDetails={() => {}} />);
|
||||||
|
const expanded = screen.getByTestId("flight-card-expanded");
|
||||||
|
// Neither boarding nor deboarding label should appear when transition is absent
|
||||||
|
expect(expanded.textContent).not.toContain("DETAILS.BOARDING");
|
||||||
|
expect(expanded.textContent).not.toContain("DETAILS.DEBOARDING");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -199,6 +199,12 @@ export const FlightCard: FC<FlightCardProps> = ({
|
|||||||
? arrivalLeg.transition?.deboarding
|
? arrivalLeg.transition?.deboarding
|
||||||
: departureLeg.transition?.boarding;
|
: departureLeg.transition?.boarding;
|
||||||
const transitionLabelKey = isArrival ? "DETAILS.DEBOARDING" : "DETAILS.BOARDING";
|
const transitionLabelKey = isArrival ? "DETAILS.DEBOARDING" : "DETAILS.BOARDING";
|
||||||
|
|
||||||
|
// TZ §4.1.13.4.3: boarding block shows gate + dispatch (трап/автобус);
|
||||||
|
// deboarding block shows gate + baggage belt from arrival station.
|
||||||
|
const boardingGate = isArrival ? arrStation.gate : depStation.gate;
|
||||||
|
const boardingDispatch = isArrival ? arrStation.dispatch : depStation.dispatch;
|
||||||
|
const bagBelt = isArrival ? arrStation.bagBelt : undefined;
|
||||||
const BOARDING_STATUS_KEY: Record<string, string> = {
|
const BOARDING_STATUS_KEY: Record<string, string> = {
|
||||||
Finished: "BOARDING-STATUSES.Finished",
|
Finished: "BOARDING-STATUSES.Finished",
|
||||||
Expected: "BOARDING-STATUSES.Expected",
|
Expected: "BOARDING-STATUSES.Expected",
|
||||||
@@ -420,6 +426,35 @@ export const FlightCard: FC<FlightCardProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* TZ §4.1.13.4.3: gate number (выход на посадку) when available */}
|
||||||
|
{boardingGate && (
|
||||||
|
<div data-testid="transition-gate">
|
||||||
|
<span className="flight-card__detail-caption">
|
||||||
|
{t("DETAILS.GATE")}
|
||||||
|
</span>
|
||||||
|
<span className="flight-card__detail-value">{boardingGate}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* TZ §4.1.13.4.3: dispatch type (трап/автобус) when available */}
|
||||||
|
{boardingDispatch && (
|
||||||
|
<div data-testid="transition-dispatch">
|
||||||
|
<span className="flight-card__detail-caption">
|
||||||
|
{t("DETAILS.DISPATCH")}
|
||||||
|
</span>
|
||||||
|
<span className="flight-card__detail-value">
|
||||||
|
{t(`DISPATCH.${boardingDispatch}`)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* TZ §4.1.13.4.3: baggage belt for deboarding (arrival) when available */}
|
||||||
|
{bagBelt && (
|
||||||
|
<div data-testid="transition-bag-belt">
|
||||||
|
<span className="flight-card__detail-caption">
|
||||||
|
{t("DETAILS.BAG_BELT")}
|
||||||
|
</span>
|
||||||
|
<span className="flight-card__detail-value">{bagBelt}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user