Schedule details mini-list: filter to the open flight only

Mirrors Angular's CurrentScheduleService.getScheduleType +
compareFlightsByPId: when the [-1, +1] route search returns the
open flight (matched by carrier+number signature, including each
leg of a connecting itinerary), keep only those instances; when
no match exists, fall back to a 1-item list with just the open
flight (Angular's 'default-schedule' branch). Old behaviour
returned the full route search and dumped every unrelated MOW-MMK
option into the rail.

Add e2e regression that loads the SU 6188 + SU 6341 itinerary and
asserts the rail shows only SU 6188 — not SU 6190 / SU 6699 (the
other Sunday MOW-MMK options that used to appear).
This commit is contained in:
2026-04-23 15:54:19 +03:00
parent 6dcbb332be
commit 37ae7dcd46
2 changed files with 84 additions and 5 deletions
@@ -150,19 +150,48 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
);
const miniListFlights: ISimpleFlight[] = useMemo(() => {
if (!miniListSearchParams) return flights as unknown as ISimpleFlight[];
if (siblingSearch.loading || siblingSearch.error) {
return flights as unknown as ISimpleFlight[];
}
return extractSimpleFlights(
// Mirrors Angular's `CurrentScheduleService.getScheduleType`:
// • If the route-search result contains the open flight (matched
// by the same carrier+number sequence), show those matching
// instances across the [-1, +1] day window — the SAME flight on
// adjacent days.
// • Otherwise fall back to a 1-item list with just the open
// flight ('default-schedule' branch).
// Old behaviour returned the entire route search which dumped every
// unrelated MOW-MMK option into the left rail.
const fallback = flights as unknown as ISimpleFlight[];
if (!miniListSearchParams) return fallback;
if (siblingSearch.loading || siblingSearch.error) return fallback;
// Build the open flight's signature: ordered "CARRIER+NUMBER"
// segments. For connecting itineraries the URL carries every leg
// already (each as a separate flightId).
const openSignature = flightIds
.map((f) => `${f.carrier}${f.flightNumber}`)
.join("+");
if (!openSignature) return fallback;
const allSiblings = extractSimpleFlights(
siblingSearch.flights as Array<{ routeType: string }>,
);
const matches = allSiblings.filter((sib) => {
const childIds = (sib as ISimpleFlight & {
_childFlightIds?: typeof sib.flightId[];
})._childFlightIds;
const sig = childIds && childIds.length > 0
? childIds.map((c) => `${c.carrier}${c.flightNumber}`).join("+")
: `${sib.flightId.carrier}${sib.flightId.flightNumber}`;
return sig === openSignature;
});
return matches.length > 0 ? matches : fallback;
}, [
miniListSearchParams,
siblingSearch.flights,
siblingSearch.loading,
siblingSearch.error,
flights,
flightIds,
]);
// Angular's schedule details page adds a third crumb only when the
@@ -0,0 +1,50 @@
import { test, expect } from "@playwright/test";
// On the schedule details page, the left mini-list must show only the
// CURRENTLY-OPEN flight's instance on each day in the [-1, +1] window —
// matching Angular's `CurrentScheduleService.getScheduleType` /
// `compareFlightsByPId` filtering. The old behaviour dumped the entire
// MOW→MMK route search into the rail (every flight number, every
// option), making the rail useless when the user came from the
// search-results list.
//
// Reference URL: connecting itinerary SU 6188 + SU 6341 (Moscow → St
// Petersburg → Murmansk) on 2026-04-26. Each visible day in the
// mini-list must list ≤ 1 entry — the same SU 6188 itinerary.
const URL =
"/ru-ru/schedule/VKO/SU6188-20260426/LED/SU6341-20260427/MMK?request=schedule-route-MOW-MMK-20260427-20260503";
test("mini-list shows only the open flight per day, not the full route search", async ({
page,
}) => {
await page.goto(URL);
// Wait for the mini-list to render.
const miniList = page.locator(".schedule-mini-list");
await expect(miniList).toBeVisible({ timeout: 15000 });
// Wait until the day-headers appear (they exist for both the today
// and ±1 days). Three day headers are expected total.
const dayHeaders = miniList.locator("[data-testid^='mini-list-day-header-']");
await expect(dayHeaders).toHaveCount(3);
// The expanded body must contain at most one flight entry — the
// open SU 6188 itinerary. The old behaviour rendered 4+ entries
// (every MOW-MMK option for that day).
const openBody = miniList
.locator("[data-testid^='mini-list-day-']:not([data-testid*='header'])")
.first();
await expect(openBody).toBeVisible({ timeout: 10000 });
// Mini-list items use SU 6188 in their visible label.
const items = openBody.locator(".flights-mini-list-item, [class*='mini-list'] [class*='flight']");
// Loose assertion — there should be at MOST one entry per day, and
// the visible text must include 'SU 6188' (NOT a different
// route-mate flight number).
const text = (await openBody.innerText()).replace(/\s+/g, " ");
expect(text).toContain("SU 6188");
// Sanity: should NOT contain other Sunday MOW-MMK flight numbers
// that the old listing pulled in (SU 6190 / SU 6699 are typical).
expect(text).not.toMatch(/SU\s*6190/);
expect(text).not.toMatch(/SU\s*6699/);
});