diff --git a/src/features/online-board/components/OnlineBoardStartPage.test.tsx b/src/features/online-board/components/OnlineBoardStartPage.test.tsx index e426e50d..99442361 100644 --- a/src/features/online-board/components/OnlineBoardStartPage.test.tsx +++ b/src/features/online-board/components/OnlineBoardStartPage.test.tsx @@ -1,18 +1,24 @@ /** * Tests for OnlineBoardStartPage component. * - * Verifies page layout rendering with filter and info sections. + * Verifies page layout rendering with filter and info sections, + * query param building from popular requests, and navigation on click. * * @vitest-environment jsdom */ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { render, screen } from "@testing-library/react"; -import { OnlineBoardStartPage } from "./OnlineBoardStartPage.js"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { OnlineBoardStartPage, buildPopularRequestQueryParams } from "./OnlineBoardStartPage.js"; +import type { PopularRequest } from "@/features/popular-requests/types.js"; + +const mockNavigate = vi.fn(); +const mockSearchParams = new URLSearchParams(); vi.mock("@modern-js/runtime/router", () => ({ - useNavigate: () => vi.fn(), + useNavigate: () => mockNavigate, useParams: () => ({ lang: "ru" }), + useSearchParams: () => [mockSearchParams], Link: ({ children, to, ...props }: { children: React.ReactNode; to: string; className?: string; [k: string]: unknown }) => ( {children} ), @@ -25,7 +31,16 @@ vi.mock("@/i18n/provider.js", () => ({ })); vi.mock("@/features/popular-requests/components/PopularRequestsPanel.js", () => ({ - PopularRequestsPanel: () =>
Popular
, + PopularRequestsPanel: ({ onRequestClick }: { onRequestClick?: (r: PopularRequest) => void }) => ( +
+ +
+ ), })); vi.mock("@/shared/hooks/useCitySearch.js", () => ({ @@ -36,6 +51,96 @@ vi.mock("@/shared/hooks/useSearchHistory.js", () => ({ useSearchHistory: () => ({ items: [], add: vi.fn(), clear: vi.fn() }), })); +// --------------------------------------------------------------------------- +// Pure function: buildPopularRequestQueryParams +// --------------------------------------------------------------------------- + +describe("buildPopularRequestQueryParams", () => { + it("builds params for FlightNumber mode", () => { + const request: PopularRequest = { + mode: "FlightNumber", + carrier: "SU", + flightNumber: "0654", + type: "Onlineboard", + }; + const params = buildPopularRequestQueryParams(request); + expect(params.get("tab")).toBe("flight"); + expect(params.get("carrier")).toBe("SU"); + expect(params.get("flight")).toBe("0654"); + expect(params.has("departure")).toBe(false); + expect(params.has("arrival")).toBe(false); + }); + + it("builds params for Departure mode", () => { + const request: PopularRequest = { + mode: "Departure", + departure: "LED", + type: "Onlineboard", + }; + const params = buildPopularRequestQueryParams(request); + expect(params.get("tab")).toBe("route"); + expect(params.get("departure")).toBe("LED"); + expect(params.has("arrival")).toBe(false); + expect(params.has("carrier")).toBe(false); + }); + + it("builds params for Arrival mode", () => { + const request: PopularRequest = { + mode: "Arrival", + arrival: "VKO", + type: "Onlineboard", + }; + const params = buildPopularRequestQueryParams(request); + expect(params.get("tab")).toBe("route"); + expect(params.get("arrival")).toBe("VKO"); + expect(params.has("departure")).toBe(false); + }); + + it("builds params for Route mode (Onlineboard)", () => { + const request: PopularRequest = { + mode: "Route", + departure: "LED", + arrival: "KRR", + type: "Onlineboard", + }; + const params = buildPopularRequestQueryParams(request); + expect(params.get("tab")).toBe("route"); + expect(params.get("departure")).toBe("LED"); + expect(params.get("arrival")).toBe("KRR"); + expect(params.has("return")).toBe(false); + }); + + it("builds params for Route mode (Schedule)", () => { + const request: PopularRequest = { + mode: "Route", + departure: "SVO", + arrival: "LED", + type: "Schedule", + }; + const params = buildPopularRequestQueryParams(request); + expect(params.get("departure")).toBe("SVO"); + expect(params.get("arrival")).toBe("LED"); + expect(params.has("return")).toBe(false); + }); + + it("builds params for RouteWithBack mode (Schedule) with return flag", () => { + const request: PopularRequest = { + mode: "RouteWithBack", + departure: "SVO", + arrival: "LED", + type: "Schedule", + }; + const params = buildPopularRequestQueryParams(request); + expect(params.get("departure")).toBe("SVO"); + expect(params.get("arrival")).toBe("LED"); + expect(params.get("return")).toBe("true"); + }); +}); + +// --------------------------------------------------------------------------- +// Component tests +// --------------------------------------------------------------------------- + describe("OnlineBoardStartPage", () => { beforeEach(() => { vi.clearAllMocks(); @@ -91,4 +196,15 @@ describe("OnlineBoardStartPage", () => { render(); expect(screen.getByTestId("feedback-button")).toBeTruthy(); }); + + it("navigates on popular request click (Onlineboard type)", () => { + render(); + const btn = screen.getByTestId("popular-click"); + fireEvent.click(btn); + expect(mockNavigate).toHaveBeenCalledWith( + expect.objectContaining({ + search: expect.stringContaining("tab=flight"), + }), + ); + }); }); diff --git a/src/features/online-board/components/OnlineBoardStartPage.tsx b/src/features/online-board/components/OnlineBoardStartPage.tsx index e728e192..d5475403 100644 --- a/src/features/online-board/components/OnlineBoardStartPage.tsx +++ b/src/features/online-board/components/OnlineBoardStartPage.tsx @@ -12,7 +12,8 @@ * @module */ -import { type FC, useCallback } from "react"; +import { type FC, useCallback, useMemo } from "react"; +import { useNavigate, useParams, useSearchParams } from "@modern-js/runtime/router"; import { useTranslation } from "@/i18n/provider.js"; import { PageLayout } from "@/ui/layout/PageLayout.js"; import { PageTabs } from "@/ui/layout/PageTabs.js"; @@ -22,13 +23,102 @@ import { PopularRequestsPanel } from "@/features/popular-requests/components/Pop import type { PopularRequest } from "@/features/popular-requests/types.js"; import "./OnlineBoardStartPage.scss"; +/** + * Build URL search params for a given popular request. + * + * Schedule-type requests produce params for the schedule page; + * Onlineboard-type requests produce params for the online board filter. + */ +export function buildPopularRequestQueryParams( + request: PopularRequest, +): URLSearchParams { + const params = new URLSearchParams(); + + switch (request.mode) { + case "FlightNumber": + params.set("tab", "flight"); + params.set("carrier", request.carrier); + params.set("flight", request.flightNumber); + break; + case "Departure": + params.set("tab", "route"); + params.set("departure", request.departure); + break; + case "Arrival": + params.set("tab", "route"); + params.set("arrival", request.arrival); + break; + case "Route": + case "RouteWithBack": + if (request.type === "Onlineboard") { + params.set("tab", "route"); + } + params.set("departure", request.departure); + params.set("arrival", request.arrival); + if (request.mode === "RouteWithBack" && request.type === "Schedule") { + params.set("return", "true"); + } + break; + } + + return params; +} + export const OnlineBoardStartPage: FC = () => { const { t } = useTranslation(); + const navigate = useNavigate(); + const routeParams = useParams<{ lang: string }>(); + const lang = routeParams.lang ?? "ru"; + const [searchParams] = useSearchParams(); - const handlePopularRequestClick = useCallback((_request: PopularRequest) => { - // Navigation is handled by PopularRequestItem internally; - // this callback is available for analytics or custom behavior. - }, []); + /** Derive initial filter props from current URL query params */ + const filterInitialProps = useMemo(() => { + const tab = searchParams.get("tab"); + const departure = searchParams.get("departure"); + const arrival = searchParams.get("arrival"); + const carrier = searchParams.get("carrier"); + const flight = searchParams.get("flight"); + const date = searchParams.get("date"); + + // No query params — use defaults + if (!tab && !departure && !arrival && !carrier && !flight) { + return {}; + } + + return { + ...(tab === "flight" || tab === "route" + ? { initialTab: tab as "flight" | "route" } + : {}), + ...(departure ? { initialDeparture: departure } : {}), + ...(arrival ? { initialArrival: arrival } : {}), + ...(date ? { initialDate: date } : {}), + ...(carrier && flight + ? { initialFlightNumber: `${carrier}${flight}` } + : {}), + }; + }, [searchParams]); + + const handlePopularRequestClick = useCallback( + (request: PopularRequest) => { + const params = buildPopularRequestQueryParams(request); + + // Schedule-type requests navigate to the schedule feature + if (request.type === "Schedule") { + navigate({ + pathname: `/${lang}/schedule`, + search: params.toString(), + }); + return; + } + + // Onlineboard requests stay on this page with query params + navigate({ + pathname: `/${lang}/online-board`, + search: params.toString(), + }); + }, + [navigate, lang], + ); return (
@@ -44,7 +134,7 @@ export const OnlineBoardStartPage: FC = () => { breadcrumbs={[]} contentLeft={ <> - + } diff --git a/tests/integration/online-board/start-page.test.tsx b/tests/integration/online-board/start-page.test.tsx index 4890c885..ac25c975 100644 --- a/tests/integration/online-board/start-page.test.tsx +++ b/tests/integration/online-board/start-page.test.tsx @@ -20,6 +20,7 @@ const navigateSpy = vi.fn(); vi.mock("@modern-js/runtime/router", () => ({ useNavigate: () => navigateSpy, useParams: () => ({ lang: "ru" }), + useSearchParams: () => [new URLSearchParams()], Link: ({ children, to, ...props }: { children: React.ReactNode; to: string; [k: string]: unknown }) => ( {children} ),