Add DetailsHeaderBadge with flight number and codesharing

This commit is contained in:
2026-04-17 01:34:29 +03:00
parent a6fc2f7a2e
commit ade7feb715
2 changed files with 166 additions and 0 deletions
@@ -0,0 +1,119 @@
// @vitest-environment jsdom
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { DetailsHeaderBadge } from "./DetailsHeaderBadge.js";
import type { ISimpleFlight, IFlightLeg } from "../../types.js";
vi.mock("@/i18n/provider.js", () => ({ useTranslation: () => ({ t: (k: string) => k }) }));
function makeLeg(overrides: Partial<IFlightLeg> = {}): IFlightLeg {
return {
arrival: {
scheduled: { airport: "", airportCode: "LED", city: "", cityCode: "", countryCode: "" },
latest: { airport: "", airportCode: "LED", city: "", cityCode: "", countryCode: "" },
dispatch: "",
gate: "",
terminal: "",
times: {
scheduledArrival: {
dayChange: { value: 0, title: "" },
local: "",
localTime: "",
tzOffset: 0,
utc: "",
},
},
},
dayChange: 0,
departure: {
scheduled: { airport: "", airportCode: "SVO", city: "", cityCode: "", countryCode: "" },
latest: { airport: "", airportCode: "SVO", city: "", cityCode: "", countryCode: "" },
dispatch: "",
gate: "",
terminal: "",
checkingStatus: "Scheduled",
parkingStand: "",
times: {
scheduledDeparture: {
dayChange: { value: 0, title: "" },
local: "",
localTime: "",
tzOffset: 0,
utc: "2026-04-20T10:00:00Z",
},
},
},
equipment: {},
flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
flyingTime: "1h",
index: 0,
operatingBy: {},
status: "Scheduled",
updated: "",
...overrides,
} as IFlightLeg;
}
function makeDirect(): ISimpleFlight {
return {
id: "X",
routeType: "Direct",
flyingTime: "1h",
status: "Scheduled",
flightId: { carrier: "SU", flightNumber: "0022", suffix: "", date: "20260417" },
operatingBy: { carrier: "SU", flightNumber: "0022" },
leg: makeLeg(),
} as ISimpleFlight;
}
function makeMultiLeg(): ISimpleFlight {
return {
id: "Y",
routeType: "MultiLeg",
flyingTime: "2h",
status: "Scheduled",
flightId: { carrier: "SU", flightNumber: "0022", suffix: "", date: "20260417" },
operatingBy: { carrier: "SU", flightNumber: "0022" },
legs: [
makeLeg({ operatingBy: { carrier: "KL", flightNumber: "1234" } }),
makeLeg({ operatingBy: { carrier: "AF", flightNumber: "5678" } }),
makeLeg({ operatingBy: { carrier: "SU", flightNumber: "0022" } }),
],
} as ISimpleFlight;
}
describe("DetailsHeaderBadge", () => {
it("renders primary flight number SU 0022", () => {
render(<DetailsHeaderBadge flight={makeDirect()} locale="ru" />);
expect(screen.getByText("SU 0022")).toBeTruthy();
});
it("renders operator-logo", () => {
render(<DetailsHeaderBadge flight={makeDirect()} locale="ru" />);
expect(screen.getByTestId("operator-logo")).toBeTruthy();
});
it("does NOT render codesharing for Direct", () => {
render(<DetailsHeaderBadge flight={makeDirect()} locale="ru" />);
expect(screen.queryByTestId("codesharing")).toBeNull();
});
it("renders codesharing list for MultiLeg with partner carriers", () => {
render(<DetailsHeaderBadge flight={makeMultiLeg()} locale="ru" />);
const cs = screen.getByTestId("codesharing");
expect(cs).toBeTruthy();
expect(cs.textContent).toContain("KL 1234");
expect(cs.textContent).toContain("AF 5678");
expect(cs.textContent).not.toContain("SU 0022");
});
it("renders small status button when showStatus=true", () => {
render(<DetailsHeaderBadge flight={makeDirect()} locale="ru" showStatus />);
expect(screen.getByTestId("flight-status-button")).toBeTruthy();
});
it("does not render status button by default", () => {
render(<DetailsHeaderBadge flight={makeDirect()} locale="ru" />);
expect(screen.queryByTestId("flight-status-button")).toBeNull();
});
});
@@ -0,0 +1,47 @@
import type { FC } from "react";
import type { ISimpleFlight, IFlightLeg } from "../../types.js";
import { OperatorLogo } from "./OperatorLogo.js";
import { FlightStatusButton } from "./FlightStatusButton.js";
export interface DetailsHeaderBadgeProps {
flight: ISimpleFlight;
locale: string;
large?: boolean;
round?: boolean;
showStatus?: boolean;
}
function getCodeshareLegs(flight: ISimpleFlight): IFlightLeg[] {
if (flight.routeType !== "MultiLeg") return [];
return flight.legs.filter(
(l) => l.operatingBy?.carrier && l.operatingBy.carrier !== flight.flightId.carrier,
);
}
export const DetailsHeaderBadge: FC<DetailsHeaderBadgeProps> = ({
flight,
locale,
large = true,
round = false,
showStatus = false,
}) => {
const codeshareLegs = getCodeshareLegs(flight);
const primaryNumber = `${flight.flightId.carrier} ${flight.flightId.flightNumber}`;
return (
<div className="details-header-badge">
<div className="details-header-badge__flight-number">
<div className="details-header-badge__primary">{primaryNumber}</div>
{codeshareLegs.length > 0 && (
<div className="details-header-badge__codesharing" data-testid="codesharing">
{codeshareLegs
.map((l) => `${l.operatingBy.carrier} ${l.operatingBy.flightNumber}`)
.join(", ")}
</div>
)}
</div>
<OperatorLogo flight={flight} locale={locale} large={large} round={round} />
{showStatus && <FlightStatusButton flight={flight} locale={locale} small />}
</div>
);
};