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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user