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}
),