Wire DayTabs into OnlineBoardDetailsPage stickyContent
This commit is contained in:
@@ -77,6 +77,7 @@ const mockFlight: IDirectFlight = {
|
||||
let mockState = {
|
||||
flight: mockFlight as IDirectFlight | null,
|
||||
allFlights: [mockFlight] as IDirectFlight[],
|
||||
daysOfFlight: ["20250115"] as string[],
|
||||
loading: false,
|
||||
error: null as Error | null,
|
||||
};
|
||||
@@ -85,6 +86,17 @@ vi.mock("../hooks/useFlightDetails.js", () => ({
|
||||
useFlightDetails: () => mockState,
|
||||
}));
|
||||
|
||||
vi.mock("@/shared/hooks/useAppSettings.js", () => ({
|
||||
useAppSettings: () => ({
|
||||
onlineboardSearchFrom: 2,
|
||||
onlineboardSearchTo: 14,
|
||||
scheduleSearchFrom: 30,
|
||||
scheduleSearchTo: 30,
|
||||
loading: false,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../hooks/useLiveFlightDetails.js", () => ({
|
||||
useLiveFlightDetails: (_id: unknown, initialFlight: unknown) => ({
|
||||
flight: initialFlight,
|
||||
@@ -120,6 +132,7 @@ describe("OnlineBoardDetailsPage", () => {
|
||||
mockState = {
|
||||
flight: mockFlight,
|
||||
allFlights: [mockFlight],
|
||||
daysOfFlight: ["20250115"],
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
@@ -133,19 +146,19 @@ describe("OnlineBoardDetailsPage", () => {
|
||||
});
|
||||
|
||||
it("renders loading skeleton", () => {
|
||||
mockState = { flight: null, allFlights: [], loading: true, error: null };
|
||||
mockState = { flight: null, allFlights: [], daysOfFlight: [], loading: true, error: null };
|
||||
render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://www.aeroflot.ru" />);
|
||||
expect(screen.queryByTestId("flight-details")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders error state", () => {
|
||||
mockState = { flight: null, allFlights: [], loading: false, error: new Error("fail") };
|
||||
mockState = { flight: null, allFlights: [], daysOfFlight: [], loading: false, error: new Error("fail") };
|
||||
render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://www.aeroflot.ru" />);
|
||||
expect(screen.getByTestId("flight-details-error")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders not-found state", () => {
|
||||
mockState = { flight: null, allFlights: [], loading: false, error: null };
|
||||
mockState = { flight: null, allFlights: [], daysOfFlight: [], loading: false, error: null };
|
||||
render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://www.aeroflot.ru" />);
|
||||
expect(screen.getByTestId("flight-details-not-found")).toBeTruthy();
|
||||
});
|
||||
@@ -208,7 +221,7 @@ describe("OnlineBoardDetailsPage", () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
mockState = { flight: flightWithTransition, allFlights: [flightWithTransition], loading: false, error: null };
|
||||
mockState = { flight: flightWithTransition, allFlights: [flightWithTransition], daysOfFlight: ["20250115"], loading: false, error: null };
|
||||
render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://example.com" />);
|
||||
expect(screen.getByTestId("flight-details-accordion")).toBeTruthy();
|
||||
});
|
||||
@@ -218,21 +231,29 @@ describe("OnlineBoardDetailsPage", () => {
|
||||
it("renders mini-list when multiple flights are returned", () => {
|
||||
const first = mockFlight;
|
||||
const second = { ...mockFlight, id: "SU0022-20260417", flightId: { ...mockFlight.flightId, date: "20260417" } };
|
||||
mockState = { flight: first, allFlights: [first, second], loading: false, error: null };
|
||||
mockState = { flight: first, allFlights: [first, second], daysOfFlight: ["20250115", "20260417"], loading: false, error: null };
|
||||
render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://example.com" />);
|
||||
expect(screen.getByTestId("flights-mini-list")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not render mini-list when only one flight is returned", () => {
|
||||
mockState = { flight: mockFlight, allFlights: [mockFlight], loading: false, error: null };
|
||||
mockState = { flight: mockFlight, allFlights: [mockFlight], daysOfFlight: ["20250115"], loading: false, error: null };
|
||||
render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://example.com" />);
|
||||
expect(screen.queryByTestId("flights-mini-list")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders inside PageLayout (has page-layout class)", () => {
|
||||
mockState = { flight: mockFlight, allFlights: [mockFlight], loading: false, error: null };
|
||||
mockState = { flight: mockFlight, allFlights: [mockFlight], daysOfFlight: ["20250115"], loading: false, error: null };
|
||||
const { container } = render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://example.com" />);
|
||||
expect(container.querySelector(".page-layout")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("day tabs integration", () => {
|
||||
it("renders DayTabs as sticky content", () => {
|
||||
mockState = { flight: mockFlight, allFlights: [mockFlight], daysOfFlight: ["20260416"], loading: false, error: null };
|
||||
render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://example.com" />);
|
||||
expect(screen.getByTestId("day-tabs")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type { FC } from "react";
|
||||
import { useCallback, type FC } from "react";
|
||||
import { useNavigate } from "@modern-js/runtime/router";
|
||||
import { useTranslation } from "@/i18n/provider.js";
|
||||
import "./OnlineBoardDetailsPage.scss";
|
||||
import { FlightCard } from "@/ui/flights/FlightCard.js";
|
||||
@@ -16,12 +17,15 @@ import { SeoHead } from "@/ui/seo/SeoHead.js";
|
||||
import { JsonLdRenderer } from "@/shared/seo/json-ld.js";
|
||||
import { PageLayout } from "@/ui/layout/PageLayout.js";
|
||||
import { PageTabs } from "@/ui/layout/PageTabs.js";
|
||||
import { useAppSettings } from "@/shared/hooks/useAppSettings.js";
|
||||
import { useFlightDetails } from "../hooks/useFlightDetails.js";
|
||||
import { useLiveFlightDetails } from "../hooks/useLiveFlightDetails.js";
|
||||
import { buildFlightDetailsSeo } from "../seo.js";
|
||||
import { buildFlightJsonLd } from "../json-ld.js";
|
||||
import { buildOnlineBoardUrl } from "../url.js";
|
||||
import { FlightDetailsAccordion } from "./details-panels/FlightDetailsAccordion.js";
|
||||
import { FlightsMiniList } from "./FlightsMiniList/index.js";
|
||||
import { DayTabs } from "./DayTabs/index.js";
|
||||
import type { IParsedFlightId, IFlightLeg } from "../types.js";
|
||||
|
||||
export interface OnlineBoardDetailsPageProps {
|
||||
@@ -152,7 +156,7 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
|
||||
flights: `${flightId.carrier}${flightId.flightNumber}${flightId.suffix ?? ""}`,
|
||||
dates: `${flightId.date.slice(0, 4)}-${flightId.date.slice(4, 6)}-${flightId.date.slice(6, 8)}`,
|
||||
};
|
||||
const { flight: firstFlight, allFlights, loading, error } = useFlightDetails(detailsParams);
|
||||
const { flight: firstFlight, allFlights, daysOfFlight, loading, error } = useFlightDetails(detailsParams);
|
||||
|
||||
// Pick the flight matching the URL's flightId (date-based match). The API
|
||||
// response may contain multiple flights with the same flight number on
|
||||
@@ -168,6 +172,23 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
|
||||
|
||||
const displayFlight = connectionStatus === "live" && liveFlight ? liveFlight : flight;
|
||||
|
||||
const { onlineboardSearchFrom, onlineboardSearchTo } = useAppSettings();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleNavigateDate = useCallback(
|
||||
(newDate: string) => {
|
||||
const url = buildOnlineBoardUrl({
|
||||
type: "details",
|
||||
carrier: flightId.carrier,
|
||||
flightNumber: flightId.flightNumber,
|
||||
...(flightId.suffix ? { suffix: flightId.suffix } : {}),
|
||||
date: newDate,
|
||||
});
|
||||
void navigate(`/${locale}/${url}`);
|
||||
},
|
||||
[flightId.carrier, flightId.flightNumber, flightId.suffix, locale, navigate],
|
||||
);
|
||||
|
||||
const onlineboardHref = `/${locale}/onlineboard`;
|
||||
const commonLayoutProps = {
|
||||
headerLeft: <PageTabs viewType="onlineboard" />,
|
||||
@@ -228,6 +249,16 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
|
||||
lang={locale}
|
||||
/>
|
||||
}
|
||||
stickyContent={
|
||||
<DayTabs
|
||||
selectedDate={flightId.date}
|
||||
availableDates={daysOfFlight}
|
||||
daysBefore={onlineboardSearchFrom}
|
||||
daysAfter={onlineboardSearchTo}
|
||||
locale={locale}
|
||||
onNavigate={handleNavigateDate}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="flight-details" data-testid="flight-details">
|
||||
{/* Connection status */}
|
||||
|
||||
@@ -76,6 +76,17 @@ vi.mock("@/features/online-board/hooks/useLiveFlightDetails.js", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/shared/hooks/useAppSettings.js", () => ({
|
||||
useAppSettings: () => ({
|
||||
onlineboardSearchFrom: 2,
|
||||
onlineboardSearchTo: 14,
|
||||
scheduleSearchFrom: 30,
|
||||
scheduleSearchTo: 30,
|
||||
loading: false,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -178,6 +189,8 @@ describe("Details page error handling", () => {
|
||||
it("renders error state for details API failure", () => {
|
||||
mockUseFlightDetails.mockReturnValue({
|
||||
flight: null,
|
||||
allFlights: [],
|
||||
daysOfFlight: [],
|
||||
loading: false,
|
||||
error: new ApiHttpError("HTTP 500", 500),
|
||||
});
|
||||
@@ -195,6 +208,8 @@ describe("Details page error handling", () => {
|
||||
it("renders not-found when details returns null", () => {
|
||||
mockUseFlightDetails.mockReturnValue({
|
||||
flight: null,
|
||||
allFlights: [],
|
||||
daysOfFlight: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
@@ -50,6 +50,17 @@ vi.mock("@/features/online-board/hooks/useLiveFlightDetails.js", () => ({
|
||||
useLiveFlightDetails: (...args: unknown[]) => mockUseLiveFlightDetails(...args),
|
||||
}));
|
||||
|
||||
vi.mock("@/shared/hooks/useAppSettings.js", () => ({
|
||||
useAppSettings: () => ({
|
||||
onlineboardSearchFrom: 2,
|
||||
onlineboardSearchTo: 14,
|
||||
scheduleSearchFrom: 30,
|
||||
scheduleSearchTo: 30,
|
||||
loading: false,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -64,6 +75,7 @@ function setupWithFlight(flight: ISimpleFlight = DIRECT_FLIGHT) {
|
||||
mockUseFlightDetails.mockReturnValue({
|
||||
flight,
|
||||
allFlights: [flight],
|
||||
daysOfFlight: ["20250115"],
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
@@ -179,6 +191,7 @@ describe("Flight details page integration", () => {
|
||||
mockUseFlightDetails.mockReturnValue({
|
||||
flight: null,
|
||||
allFlights: [],
|
||||
daysOfFlight: [],
|
||||
loading: false,
|
||||
error: new Error("API error"),
|
||||
});
|
||||
@@ -201,6 +214,7 @@ describe("Flight details page integration", () => {
|
||||
mockUseFlightDetails.mockReturnValue({
|
||||
flight: null,
|
||||
allFlights: [],
|
||||
daysOfFlight: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user