diff --git a/src/features/online-board/components/BoardDetailsHeader/FlightActions.test.tsx b/src/features/online-board/components/BoardDetailsHeader/FlightActions.test.tsx new file mode 100644 index 00000000..e0fce849 --- /dev/null +++ b/src/features/online-board/components/BoardDetailsHeader/FlightActions.test.tsx @@ -0,0 +1,111 @@ +// @vitest-environment jsdom +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { FlightActions } from "./FlightActions.js"; +import type { ISimpleFlight } from "../../types.js"; + +vi.mock("@/i18n/provider.js", () => ({ useTranslation: () => ({ t: (k: string) => k }) })); +vi.mock("@/shared/hooks/useAppSettings.js", () => ({ + useAppSettings: () => ({ + onlineboardSearchFrom: 2, + onlineboardSearchTo: 14, + scheduleSearchFrom: 30, + scheduleSearchTo: 30, + flightStatusAvailableFromHours: 24, + buyTicketMinHours: 2, + buyTicketMaxHours: 72, + loading: false, + error: null, + }), +})); +vi.mock("./visibility/buyTicketVisibility.js", () => ({ canBuyTicket: vi.fn(() => true) })); +vi.mock("./visibility/registrationVisibility.js", () => ({ canRegister: vi.fn(() => true) })); +vi.mock("./visibility/flightStatusVisibility.js", () => ({ canViewFlightStatus: vi.fn(() => true) })); + +function makeFlight(): ISimpleFlight { + return { + id: "X", + routeType: "Direct", + flyingTime: "1h", + status: "Scheduled", + flightId: { carrier: "SU", flightNumber: "0022", suffix: "", date: "20260417" }, + operatingBy: { carrier: "SU", flightNumber: "0022" }, + leg: { + 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: "", + }, + } as ISimpleFlight; +} + +describe("FlightActions", () => { + it("renders share, buy, register by default (status hidden)", () => { + render(); + expect(screen.getByTestId("share-button")).toBeTruthy(); + expect(screen.getByTestId("buy-ticket-button")).toBeTruthy(); + expect(screen.getByTestId("registration-button")).toBeTruthy(); + expect(screen.queryByTestId("flight-status-button")).toBeNull(); + }); + + it("shows status button when showStatus=true", () => { + render(); + expect(screen.getByTestId("flight-status-button")).toBeTruthy(); + }); + + it("hides share button when showShare=false", () => { + render(); + expect(screen.queryByTestId("share-button")).toBeNull(); + }); + + it("hides buy ticket when canBuyTicket returns false", async () => { + const mod = await import("./visibility/buyTicketVisibility.js"); + vi.mocked(mod.canBuyTicket).mockReturnValue(false); + render(); + expect(screen.queryByTestId("buy-ticket-button")).toBeNull(); + vi.mocked(mod.canBuyTicket).mockReturnValue(true); + }); + + it("has data-testid=flight-actions", () => { + render(); + expect(screen.getByTestId("flight-actions")).toBeTruthy(); + }); +}); diff --git a/src/features/online-board/components/BoardDetailsHeader/FlightActions.tsx b/src/features/online-board/components/BoardDetailsHeader/FlightActions.tsx new file mode 100644 index 00000000..08cb7c63 --- /dev/null +++ b/src/features/online-board/components/BoardDetailsHeader/FlightActions.tsx @@ -0,0 +1,53 @@ +import type { FC } from "react"; +import { useAppSettings } from "@/shared/hooks/useAppSettings.js"; +import type { ISimpleFlight } from "../../types.js"; +import { AIRLINES, AIRLINES_WITH_STATUS } from "./airlines.js"; +import { canBuyTicket } from "./visibility/buyTicketVisibility.js"; +import { canRegister } from "./visibility/registrationVisibility.js"; +import { canViewFlightStatus } from "./visibility/flightStatusVisibility.js"; +import { BuyTicketButton } from "./BuyTicketButton.js"; +import { RegistrationButton } from "./RegistrationButton.js"; +import { FlightStatusButton } from "./FlightStatusButton.js"; +import { ShareButton } from "./ShareButton.js"; +import { PrintButton } from "./PrintButton.js"; + +export interface FlightActionsProps { + flight: ISimpleFlight; + locale: string; + showStatus?: boolean; + showPrint?: boolean; + showShare?: boolean; + showRegister?: boolean; + showBuy?: boolean; +} + +export const FlightActions: FC = ({ + flight, + locale, + showStatus = false, + showPrint = false, + showShare = true, + showRegister = true, + showBuy = true, +}) => { + const { flightStatusAvailableFromHours, buyTicketMinHours, buyTicketMaxHours } = useAppSettings(); + const now = new Date(); + + const canBuy = showBuy && canBuyTicket(flight, now, buyTicketMinHours, buyTicketMaxHours); + const canReg = showRegister && canRegister(flight, AIRLINES); + const canStatus = + showStatus && + canViewFlightStatus(flight, now, flightStatusAvailableFromHours, AIRLINES_WITH_STATUS); + + const shareUrl = typeof window !== "undefined" ? window.location.href : ""; + + return ( +
+ {showPrint && } + {showShare && } + {canBuy && } + {canReg && } + {canStatus && } +
+ ); +};