From ca9978f00363522e397e6fabc7555ee64fbe7d94 Mon Sep 17 00:00:00 2001 From: gnezim Date: Thu, 21 May 2026 19:22:23 +0300 Subject: [PATCH] Ignore malformed direct map routes --- .../components/FlightsMapStartPage.test.tsx | 22 ++++++++++ src/features/flights-map/filterRoutes.test.ts | 43 +++++++++++++++++++ src/features/flights-map/filterRoutes.ts | 17 +++++++- 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/features/flights-map/components/FlightsMapStartPage.test.tsx b/src/features/flights-map/components/FlightsMapStartPage.test.tsx index 012be519..c9f13c41 100644 --- a/src/features/flights-map/components/FlightsMapStartPage.test.tsx +++ b/src/features/flights-map/components/FlightsMapStartPage.test.tsx @@ -476,6 +476,28 @@ describe("FlightsMapStartPage — C.4 integration", () => { expect(popups[1]!.content).toContain("https://www.aeroflot.ru/sb/app/ru-ru"); }); + it("does not draw malformed multi-hop direct routes when transfers are off", () => { + searchState.routes = [ + { route: ["SVO", "LED"], isDirect: true }, + { route: ["SVO", "AER", "LED"], isDirect: true }, + ]; + setMapFilter({ + departure: "MOW", + arrival: "LED", + date: null, + showInternal: false, + showInternational: false, + showTransfers: false, + }); + + render(); + + const polylines = lastMapCanvasProps!["polylines"] as Array<{ cityIds: string[] }>; + expect(polylines).toEqual([ + expect.objectContaining({ cityIds: ["MOW", "LED"] }), + ]); + }); + it("does not render popups in spider mode (departure only)", () => { searchState.routes = [{ route: ["MOW", "LED"], isDirect: true }]; render(); diff --git a/src/features/flights-map/filterRoutes.test.ts b/src/features/flights-map/filterRoutes.test.ts index d763462d..5c04144b 100644 --- a/src/features/flights-map/filterRoutes.test.ts +++ b/src/features/flights-map/filterRoutes.test.ts @@ -102,6 +102,49 @@ describe("filterRoutes — connections", () => { }); }); +describe("filterRoutes — route mode without transfer-only toggle", () => { + it("drops malformed direct routes that contain intermediate cities", () => { + const routes: IFlightRoute[] = [ + { route: ["SVO", "LED"], isDirect: true }, + { route: ["SVO", "AER", "LED"], isDirect: true }, + { route: ["SVO", "AER", "LED"], isDirect: false }, + ]; + + const out = filterRoutes( + routes, + filter({ departure: "MOW", arrival: "LED", connections: false }), + d, + ); + + expect(out).toEqual([{ route: ["SVO", "LED"], isDirect: true }]); + }); + + it("keeps connecting routes in route mode when transfer-only toggle is active", () => { + const routes: IFlightRoute[] = [ + { route: ["SVO", "LED"], isDirect: true }, + { route: ["SVO", "AER", "LED"], isDirect: false }, + ]; + + const out = filterRoutes( + routes, + filter({ departure: "MOW", arrival: "LED", connections: true }), + d, + ); + + expect(out).toEqual([{ route: ["SVO", "AER", "LED"], isDirect: false }]); + }); + + it("does not apply endpoint-direct filtering in spider mode", () => { + const routes: IFlightRoute[] = [ + { route: ["SVO", "AER", "LED"], isDirect: true }, + ]; + + const out = filterRoutes(routes, filter({ departure: "MOW" }), d); + + expect(out).toEqual(routes); + }); +}); + describe("filterRoutes — airport-code normalization", () => { it("treats airport codes (SVO, JFK) as their city codes (MOW, NYC)", () => { const routes: IFlightRoute[] = [ diff --git a/src/features/flights-map/filterRoutes.ts b/src/features/flights-map/filterRoutes.ts index f9363972..98a7ec25 100644 --- a/src/features/flights-map/filterRoutes.ts +++ b/src/features/flights-map/filterRoutes.ts @@ -17,7 +17,10 @@ import type { IFlightRoute, IFlightsMapFilterState } from "./types.js"; export function filterRoutes( routes: IFlightRoute[], - filter: Pick, + filter: Pick< + IFlightsMapFilterState, + "departure" | "arrival" | "domestic" | "international" | "connections" + >, dictionaries: IDictionaries, ): IFlightRoute[] { const { domestic, international, connections } = filter; @@ -32,6 +35,17 @@ export function filterRoutes( r.route.some((code) => dictionaries.otherCityCodes.has(toCityCode(code))); const hasConnections = (r: IFlightRoute): boolean => !r.isDirect; + const isEndpointDirectRoute = (r: IFlightRoute): boolean => { + if (!filter.departure || !filter.arrival) return true; + if (!r.isDirect) return false; + + const normalized = r.route.map(toCityCode); + return ( + normalized.length === 2 && + normalized[0] === toCityCode(filter.departure) && + normalized[1] === toCityCode(filter.arrival) + ); + }; const predicates: Array<(r: IFlightRoute) => boolean> = []; @@ -39,6 +53,7 @@ export function filterRoutes( else if (international && !domestic) predicates.push(isInternational); if (connections) predicates.push(hasConnections); + else if (filter.arrival) predicates.push(isEndpointDirectRoute); if (predicates.length === 0) return routes; return routes.filter((r) => predicates.every((p) => p(r)));