diff --git a/src/features/schedule/components/ScheduleStartPage.test.tsx b/src/features/schedule/components/ScheduleStartPage.test.tsx
new file mode 100644
index 00000000..25ee58e6
--- /dev/null
+++ b/src/features/schedule/components/ScheduleStartPage.test.tsx
@@ -0,0 +1,155 @@
+/**
+ * Tests for ScheduleStartPage component.
+ *
+ * Verifies query param building from popular requests and
+ * form state initialization from URL search params.
+ *
+ * @vitest-environment jsdom
+ */
+
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen, fireEvent } from "@testing-library/react";
+import { ScheduleStartPage, buildSchedulePopularRequestQueryParams } from "./ScheduleStartPage.js";
+import type { PopularRequest } from "@/features/popular-requests/types.js";
+
+const mockNavigate = vi.fn();
+let mockSearchParams = new URLSearchParams();
+
+vi.mock("@modern-js/runtime/router", () => ({
+ useNavigate: () => mockNavigate,
+ useParams: () => ({ lang: "ru" }),
+ useSearchParams: () => [mockSearchParams],
+ Link: ({ children, to, ...props }: { children: React.ReactNode; to: string; className?: string; [k: string]: unknown }) => (
+ {children}
+ ),
+}));
+
+vi.mock("@/i18n/provider.js", () => ({
+ useTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}));
+
+vi.mock("@/features/popular-requests/components/PopularRequestsPanel.js", () => ({
+ PopularRequestsPanel: ({ onRequestClick }: { onRequestClick?: (r: PopularRequest) => void }) => (
+
+
+
+
+ ),
+}));
+
+vi.mock("@/features/online-board/components/OnlineBoardStartPage.js", () => ({
+ buildPopularRequestQueryParams: (request: PopularRequest) => {
+ const params = new URLSearchParams();
+ if (request.mode === "Departure") {
+ params.set("tab", "route");
+ params.set("departure", (request as { departure: string }).departure);
+ }
+ return params;
+ },
+}));
+
+vi.mock("@/shared/hooks/useCitySearch.js", () => ({
+ useCitySearch: () => ({ suggestions: [], search: vi.fn() }),
+}));
+
+vi.mock("@/ui/layout/SearchHistory.js", () => ({
+ SearchHistory: () => ,
+}));
+
+// ---------------------------------------------------------------------------
+// Pure function: buildSchedulePopularRequestQueryParams
+// ---------------------------------------------------------------------------
+
+describe("buildSchedulePopularRequestQueryParams", () => {
+ it("builds params for Route mode with departure and arrival", () => {
+ const request: PopularRequest = {
+ mode: "Route",
+ departure: "SVO",
+ arrival: "LED",
+ type: "Schedule",
+ };
+ const params = buildSchedulePopularRequestQueryParams(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 with return flag", () => {
+ const request: PopularRequest = {
+ mode: "RouteWithBack",
+ departure: "SVO",
+ arrival: "LED",
+ type: "Schedule",
+ };
+ const params = buildSchedulePopularRequestQueryParams(request);
+ expect(params.get("departure")).toBe("SVO");
+ expect(params.get("arrival")).toBe("LED");
+ expect(params.get("return")).toBe("true");
+ });
+
+ it("returns empty params for non-route modes", () => {
+ const request: PopularRequest = {
+ mode: "Arrival",
+ arrival: "VKO",
+ type: "Onlineboard",
+ };
+ const params = buildSchedulePopularRequestQueryParams(request);
+ expect(params.toString()).toBe("");
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Component tests
+// ---------------------------------------------------------------------------
+
+describe("ScheduleStartPage", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mockSearchParams = new URLSearchParams();
+ });
+
+ it("renders the start page", () => {
+ render();
+ expect(screen.getByTestId("schedule-start")).toBeTruthy();
+ });
+
+ it("navigates to schedule page on Schedule-type popular request click", () => {
+ render();
+ fireEvent.click(screen.getByTestId("popular-click-route"));
+ expect(mockNavigate).toHaveBeenCalledWith(
+ expect.objectContaining({
+ pathname: "/ru/schedule",
+ search: expect.stringContaining("departure=SVO"),
+ }),
+ );
+ });
+
+ it("navigates to onlineboard on Onlineboard-type popular request click", () => {
+ render();
+ fireEvent.click(screen.getByTestId("popular-click-onlineboard"));
+ expect(mockNavigate).toHaveBeenCalledWith(
+ expect.objectContaining({
+ pathname: "/ru/online-board",
+ }),
+ );
+ });
+
+ it("initializes form from search params", () => {
+ mockSearchParams = new URLSearchParams("departure=SVO&arrival=LED&return=true");
+ render();
+ const roundTripCheckbox = screen.getByTestId("round-trip-toggle");
+ expect((roundTripCheckbox as HTMLInputElement).checked).toBe(true);
+ });
+});
diff --git a/src/features/schedule/components/ScheduleStartPage.tsx b/src/features/schedule/components/ScheduleStartPage.tsx
index 2bf0d259..a7ed6b30 100644
--- a/src/features/schedule/components/ScheduleStartPage.tsx
+++ b/src/features/schedule/components/ScheduleStartPage.tsx
@@ -8,7 +8,7 @@
*/
import { type FC, useState, useCallback, type FormEvent } from "react";
-import { useNavigate, useParams } from "@modern-js/runtime/router";
+import { useNavigate, useParams, useSearchParams } from "@modern-js/runtime/router";
import { Calendar } from "primereact/calendar";
import { Slider, type SliderChangeEvent } from "primereact/slider";
import { AutoComplete, type AutoCompleteCompleteEvent } from "primereact/autocomplete";
@@ -19,6 +19,7 @@ import { PageTabs } from "@/ui/layout/PageTabs.js";
import { SearchHistory } from "@/ui/layout/SearchHistory.js";
import { PopularRequestsPanel } from "@/features/popular-requests/components/PopularRequestsPanel.js";
import type { PopularRequest } from "@/features/popular-requests/types.js";
+import { buildPopularRequestQueryParams } from "@/features/online-board/components/OnlineBoardStartPage.js";
import { buildScheduleUrl } from "../url.js";
import "./ScheduleStartPage.scss";
@@ -41,21 +42,42 @@ function addDays(base: Date, days: number): Date {
return result;
}
+/**
+ * Build URL search params for a schedule-type popular request.
+ *
+ * Only Route and RouteWithBack modes produce params; other modes
+ * return empty params (they belong to the online board).
+ */
+export function buildSchedulePopularRequestQueryParams(
+ request: PopularRequest,
+): URLSearchParams {
+ const params = new URLSearchParams();
+ if (request.mode === "Route" || request.mode === "RouteWithBack") {
+ params.set("departure", request.departure);
+ params.set("arrival", request.arrival);
+ if (request.mode === "RouteWithBack") {
+ params.set("return", "true");
+ }
+ }
+ return params;
+}
+
export const ScheduleStartPage: FC = () => {
const navigate = useNavigate();
const { t } = useTranslation();
const routeParams = useParams<{ lang: string }>();
const lang = routeParams.lang ?? "ru";
+ const [searchParams] = useSearchParams();
const today = new Date();
- const [departureAirport, setDepartureAirport] = useState("");
- const [arrivalAirport, setArrivalAirport] = useState("");
+ const [departureAirport, setDepartureAirport] = useState(searchParams.get("departure") ?? "");
+ const [arrivalAirport, setArrivalAirport] = useState(searchParams.get("arrival") ?? "");
const [dateFrom, setDateFrom] = useState(today);
const [dateTo, setDateTo] = useState(addDays(today, 7));
const [timeRange, setTimeRange] = useState<[number, number]>([0, 1440]);
const [directOnly, setDirectOnly] = useState(false);
- const [isRoundTrip, setIsRoundTrip] = useState(false);
+ const [isRoundTrip, setIsRoundTrip] = useState(searchParams.get("return") === "true");
const [returnDateFrom, setReturnDateFrom] = useState(addDays(today, 7));
const [returnDateTo, setReturnDateTo] = useState(addDays(today, 14));
const [returnTimeRange, setReturnTimeRange] = useState<[number, number]>([0, 1440]);
@@ -124,9 +146,26 @@ export const ScheduleStartPage: FC = () => {
[departureAirport, arrivalAirport, dateFrom, dateTo, timeRange, directOnly, isRoundTrip, returnDateFrom, returnDateTo, returnTimeRange, navigate, lang],
);
- const handlePopularRequestClick = useCallback((_request: PopularRequest) => {
- // Navigation handled by PopularRequestItem internally
- }, []);
+ const handlePopularRequestClick = useCallback(
+ (request: PopularRequest) => {
+ if (request.type === "Onlineboard") {
+ const params = buildPopularRequestQueryParams(request);
+ navigate({
+ pathname: `/${lang}/online-board`,
+ search: params.toString(),
+ });
+ return;
+ }
+
+ // Schedule-type: build params and navigate to schedule with pre-fill
+ const params = buildSchedulePopularRequestQueryParams(request);
+ navigate({
+ pathname: `/${lang}/schedule`,
+ search: params.toString(),
+ });
+ },
+ [navigate, lang],
+ );
const scheduleFilter = (