Fix ScheduleDetailsPage happy-path breadcrumb + add missing breadcrumb tests

The final (success) return branch was passing a hardcoded 1-item breadcrumb
array instead of the computed `breadcrumbs` variable, so the leaf crumb built
from ?request= was silently dropped for loaded flights. Loading/error/empty
branches were already correct. Adds 3 unit tests to lock the wiring.
This commit is contained in:
2026-04-21 17:50:57 +03:00
parent 5728861c5c
commit 266a6f910c
2 changed files with 167 additions and 1 deletions
@@ -0,0 +1,166 @@
/**
* Tests for ScheduleDetailsPage breadcrumb wiring.
*
* Verifies TZ §4.1.4 Table 7 rows 11-13: the leaf breadcrumb is built
* from the `?request=` query param when area === "schedule", and falls
* back to a single-item trail when no request param is present (share-link
* scenario).
*
* @vitest-environment jsdom
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import { ScheduleDetailsPage } from "./ScheduleDetailsPage.js";
import type { IScheduleFlightId } from "../types.js";
// ---------------------------------------------------------------------------
// Controlled useSearchParams — overridden per test
// ---------------------------------------------------------------------------
let mockSearchParamsGet: (key: string) => string | null = () => null;
vi.mock("@modern-js/runtime/router", () => ({
useSearchParams: () => [{ get: (k: string) => mockSearchParamsGet(k) }],
Link: ({
children,
to,
...props
}: {
children: React.ReactNode;
to: string;
[k: string]: unknown;
}) => <a href={to} {...props}>{children}</a>,
}));
vi.mock("@/i18n/provider.js", () => ({
useTranslation: () => ({
t: (key: string, vars?: Record<string, unknown>) => {
if (vars) {
return Object.entries(vars).reduce(
(s: string, [k, v]) => s.replace(`{${k}}`, String(v)),
key,
);
}
return key;
},
}),
}));
vi.mock("@/i18n/resolver.js", () => ({
localeToLanguage: () => "ru",
normalizeLocaleParam: (l: string) => l,
DEFAULT_LANGUAGE: "ru",
}));
vi.mock("../hooks/useScheduleDetails.js", () => ({
useScheduleDetails: () => ({ flights: [], loading: true, error: null }),
}));
vi.mock("../seo.js", () => ({
buildScheduleDetailsSeo: () => ({}),
}));
vi.mock("@/ui/layout/PageLayout.js", () => ({
PageLayout: ({
children,
breadcrumbs,
}: {
children?: React.ReactNode;
breadcrumbs?: { label: string; url?: string }[];
}) => (
<div>
<nav data-testid="breadcrumbs">
{breadcrumbs?.map((b, i) => (
<span key={i} data-testid={`crumb-${i}`} data-url={b.url}>
{b.label}
</span>
))}
</nav>
{children}
</div>
),
}));
vi.mock("@/ui/layout/PageTabs.js", () => ({
PageTabs: () => <div />,
}));
vi.mock("@/ui/flights/FlightListSkeleton.js", () => ({
FlightListSkeleton: () => <div data-testid="skeleton" />,
}));
vi.mock("@/ui/seo/SeoHead.js", () => ({
SeoHead: () => null,
}));
// ---------------------------------------------------------------------------
const flightId: IScheduleFlightId = {
carrier: "SU",
flightNumber: "1234",
date: "20260515",
};
describe("ScheduleDetailsPage breadcrumbs", () => {
beforeEach(() => {
mockSearchParamsGet = () => null;
});
it("shows 1-item trail when no ?request= param (share-link)", () => {
render(
<ScheduleDetailsPage
flights={[flightId]}
locale="ru-ru"
canonicalOrigin="https://example.com"
/>,
);
expect(screen.queryByTestId("crumb-0")).toBeTruthy();
expect(screen.queryByTestId("crumb-1")).toBeNull();
});
it("shows 2-item trail with route leaf when ?request= carries schedule area (one-way)", () => {
mockSearchParamsGet = (k) =>
k === "request"
? "schedule-route-NBC-KHV-20220307-20220313"
: null;
render(
<ScheduleDetailsPage
flights={[flightId]}
locale="ru-ru"
canonicalOrigin="https://example.com"
/>,
);
const crumb0 = screen.getByTestId("crumb-0");
const crumb1 = screen.getByTestId("crumb-1");
expect(crumb0.textContent).toContain("SCHEDULE.TITLE");
// Leaf label key is BREADCRUMBS.SCHEDULE-ROUTE (the mock t() returns the
// key since it has no literal {…} placeholders in the key string itself)
expect(crumb1.textContent).toContain("BREADCRUMBS.SCHEDULE-ROUTE");
// Back URL must rebuild a schedule search URL with the IATA codes
const backUrl = crumb1.getAttribute("data-url") ?? "";
expect(backUrl).toContain("/schedule/");
expect(backUrl).toContain("NBC");
expect(backUrl).toContain("KHV");
});
it("ignores ?request= that carries onlineboard area", () => {
mockSearchParamsGet = (k) =>
k === "request"
? "onlineboard-route-MOW-LED-20260515"
: null;
render(
<ScheduleDetailsPage
flights={[flightId]}
locale="ru-ru"
canonicalOrigin="https://example.com"
/>,
);
// Should stay at 1-item trail — no leaf
expect(screen.getByTestId("crumb-0")).toBeTruthy();
expect(screen.queryByTestId("crumb-1")).toBeNull();
});
});
@@ -268,7 +268,7 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
</div>
}
title={<h1 className="text--white page-title">{title}</h1>}
breadcrumbs={[{ label: t("SCHEDULE.TITLE"), url: scheduleHref }]}
breadcrumbs={breadcrumbs}
>
<SeoHead {...seoProps} />
{/* Angular renders `flight-details-full-route` once at the top for