Test FlightsMapStartPage filterRoutes + popups + auto-fallback wiring
This commit is contained in:
@@ -3,8 +3,18 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { render, screen, act } from "@testing-library/react";
|
||||
import { FlightsMapStartPage } from "./FlightsMapStartPage.js";
|
||||
import { transformDictionaries } from "@/shared/dictionaries/index.js";
|
||||
import type { IDictionaries, IRawDictionaries } from "@/shared/dictionaries/index.js";
|
||||
import type { FlightsMapSearchParams } from "../types.js";
|
||||
|
||||
function buildDictionaries(raw?: IRawDictionaries): IDictionaries {
|
||||
return transformDictionaries(
|
||||
raw ?? { regions: [], countries: [], cities: [], airports: [] },
|
||||
"ru",
|
||||
);
|
||||
}
|
||||
|
||||
vi.mock("@modern-js/runtime/router", () => ({
|
||||
useParams: () => ({ lang: "ru" }),
|
||||
@@ -48,11 +58,12 @@ const searchState: {
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
const searchCalls: Array<FlightsMapSearchParams | null> = [];
|
||||
vi.mock("../hooks/useFlightsMapSearch.js", () => ({
|
||||
useFlightsMapSearch: () => ({
|
||||
...searchState,
|
||||
refresh: vi.fn(),
|
||||
}),
|
||||
useFlightsMapSearch: (params: FlightsMapSearchParams | null) => {
|
||||
searchCalls.push(params);
|
||||
return { ...searchState, refresh: vi.fn() };
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../hooks/useFlightsMapCalendar.js", () => ({
|
||||
@@ -60,16 +71,7 @@ vi.mock("../hooks/useFlightsMapCalendar.js", () => ({
|
||||
}));
|
||||
|
||||
const dictState: {
|
||||
dictionaries:
|
||||
| null
|
||||
| {
|
||||
cities: Array<{
|
||||
code: string;
|
||||
name: string;
|
||||
country_code: string;
|
||||
location: { lat: number; lon: number };
|
||||
}>;
|
||||
};
|
||||
dictionaries: IDictionaries | null;
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
} = {
|
||||
@@ -77,10 +79,15 @@ const dictState: {
|
||||
loading: true,
|
||||
error: null,
|
||||
};
|
||||
vi.mock("@/shared/dictionaries/index.js", () => ({
|
||||
useDictionaries: () => dictState,
|
||||
getCityCodeByAirportCode: () => undefined,
|
||||
}));
|
||||
vi.mock("@/shared/dictionaries/index.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("@/shared/dictionaries/index.js")>(
|
||||
"@/shared/dictionaries/index.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
useDictionaries: () => dictState,
|
||||
};
|
||||
});
|
||||
|
||||
describe("FlightsMapStartPage — dictionaries integration", () => {
|
||||
beforeEach(() => {
|
||||
@@ -104,7 +111,7 @@ describe("FlightsMapStartPage — dictionaries integration", () => {
|
||||
|
||||
it("does not show the loader once dictionaries resolve", () => {
|
||||
dictState.loading = false;
|
||||
dictState.dictionaries = { cities: [] };
|
||||
dictState.dictionaries = buildDictionaries();
|
||||
render(<FlightsMapStartPage />);
|
||||
expect(screen.queryByTestId("map-loader")).toBeNull();
|
||||
});
|
||||
@@ -119,22 +126,42 @@ describe("FlightsMapStartPage — markers from dictionaries", () => {
|
||||
});
|
||||
|
||||
it("maps cities to IMapMarker[] with zoomLevel and countryType", () => {
|
||||
dictState.dictionaries = {
|
||||
dictState.dictionaries = buildDictionaries({
|
||||
regions: [],
|
||||
countries: [],
|
||||
cities: [
|
||||
{
|
||||
code: "MOW",
|
||||
name: "Москва",
|
||||
title: { ru: "Москва" },
|
||||
country_code: "RU",
|
||||
has_afl_flights: true,
|
||||
location: { lat: 55, lon: 37 },
|
||||
},
|
||||
{
|
||||
code: "PAR",
|
||||
name: "Париж",
|
||||
title: { ru: "Париж" },
|
||||
country_code: "FR",
|
||||
has_afl_flights: true,
|
||||
location: { lat: 48, lon: 2 },
|
||||
},
|
||||
],
|
||||
};
|
||||
airports: [
|
||||
{
|
||||
code: "MOW",
|
||||
city_code: "MOW",
|
||||
title: { ru: "Москва" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: 55, lon: 37 },
|
||||
},
|
||||
{
|
||||
code: "PAR",
|
||||
city_code: "PAR",
|
||||
title: { ru: "Париж" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: 48, lon: 2 },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
render(<FlightsMapStartPage />);
|
||||
|
||||
@@ -152,12 +179,42 @@ describe("FlightsMapStartPage — markers from dictionaries", () => {
|
||||
});
|
||||
|
||||
it("drops cities whose location is missing or invalid", () => {
|
||||
dictState.dictionaries = {
|
||||
dictState.dictionaries = buildDictionaries({
|
||||
regions: [],
|
||||
countries: [],
|
||||
cities: [
|
||||
{ code: "MOW", name: "Москва", country_code: "RU", location: { lat: 55, lon: 37 } },
|
||||
{ code: "BAD", name: "Bad", country_code: "RU", location: { lat: Number.NaN, lon: 0 } },
|
||||
{
|
||||
code: "MOW",
|
||||
title: { ru: "Москва" },
|
||||
country_code: "RU",
|
||||
has_afl_flights: true,
|
||||
location: { lat: 55, lon: 37 },
|
||||
},
|
||||
{
|
||||
code: "BAD",
|
||||
title: { ru: "Плохой" },
|
||||
country_code: "RU",
|
||||
has_afl_flights: true,
|
||||
location: { lat: Number.NaN, lon: 0 },
|
||||
},
|
||||
],
|
||||
};
|
||||
airports: [
|
||||
{
|
||||
code: "MOW",
|
||||
city_code: "MOW",
|
||||
title: { ru: "Москва" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: 55, lon: 37 },
|
||||
},
|
||||
{
|
||||
code: "BAD",
|
||||
city_code: "BAD",
|
||||
title: { ru: "Плохой" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: Number.NaN, lon: 0 },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
render(<FlightsMapStartPage />);
|
||||
|
||||
@@ -166,7 +223,7 @@ describe("FlightsMapStartPage — markers from dictionaries", () => {
|
||||
});
|
||||
|
||||
it("passes domestic/international toggles through to MapCanvas", () => {
|
||||
dictState.dictionaries = { cities: [] };
|
||||
dictState.dictionaries = buildDictionaries();
|
||||
|
||||
render(<FlightsMapStartPage />);
|
||||
|
||||
@@ -178,13 +235,56 @@ describe("FlightsMapStartPage — markers from dictionaries", () => {
|
||||
describe("FlightsMapStartPage — polylines from search results (C.3)", () => {
|
||||
beforeEach(() => {
|
||||
lastMapCanvasProps = null;
|
||||
dictState.dictionaries = {
|
||||
dictState.dictionaries = buildDictionaries({
|
||||
regions: [],
|
||||
countries: [],
|
||||
cities: [
|
||||
{ code: "A", name: "A", country_code: "RU", location: { lat: 55, lon: 37 } },
|
||||
{ code: "B", name: "B", country_code: "RU", location: { lat: 60, lon: 40 } },
|
||||
{ code: "X", name: "X", country_code: "RU", location: { lat: 58, lon: 38 } },
|
||||
{
|
||||
code: "A",
|
||||
title: { ru: "Город A" },
|
||||
country_code: "RU",
|
||||
has_afl_flights: true,
|
||||
location: { lat: 55, lon: 37 },
|
||||
},
|
||||
{
|
||||
code: "B",
|
||||
title: { ru: "Город B" },
|
||||
country_code: "RU",
|
||||
has_afl_flights: true,
|
||||
location: { lat: 60, lon: 40 },
|
||||
},
|
||||
{
|
||||
code: "X",
|
||||
title: { ru: "Город X" },
|
||||
country_code: "RU",
|
||||
has_afl_flights: true,
|
||||
location: { lat: 58, lon: 38 },
|
||||
},
|
||||
],
|
||||
};
|
||||
airports: [
|
||||
{
|
||||
code: "A",
|
||||
city_code: "A",
|
||||
title: { ru: "Аэропорт A" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: 55, lon: 37 },
|
||||
},
|
||||
{
|
||||
code: "B",
|
||||
city_code: "B",
|
||||
title: { ru: "Аэропорт B" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: 60, lon: 40 },
|
||||
},
|
||||
{
|
||||
code: "X",
|
||||
city_code: "X",
|
||||
title: { ru: "Аэропорт X" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: 58, lon: 38 },
|
||||
},
|
||||
],
|
||||
});
|
||||
dictState.loading = false;
|
||||
dictState.error = null;
|
||||
searchState.routes = [];
|
||||
@@ -230,3 +330,138 @@ describe("FlightsMapStartPage — polylines from search results (C.3)", () => {
|
||||
expect(polylines[1]!.style).toBe("connecting");
|
||||
});
|
||||
});
|
||||
|
||||
describe("FlightsMapStartPage — C.4 integration", () => {
|
||||
beforeEach(() => {
|
||||
lastMapCanvasProps = null;
|
||||
searchCalls.length = 0;
|
||||
dictState.dictionaries = buildDictionaries({
|
||||
regions: [],
|
||||
countries: [
|
||||
{ code: "RU", title: { ru: "Россия" }, world_region_id: 500374 },
|
||||
{ code: "US", title: { ru: "США" }, world_region_id: 1 },
|
||||
],
|
||||
cities: [
|
||||
{
|
||||
code: "MOW",
|
||||
title: { ru: "Москва" },
|
||||
country_code: "RU",
|
||||
has_afl_flights: true,
|
||||
location: { lat: 55, lon: 37 },
|
||||
},
|
||||
{
|
||||
code: "LED",
|
||||
title: { ru: "Санкт-Петербург" },
|
||||
country_code: "RU",
|
||||
has_afl_flights: true,
|
||||
location: { lat: 60, lon: 30 },
|
||||
},
|
||||
{
|
||||
code: "NYC",
|
||||
title: { ru: "Нью-Йорк" },
|
||||
country_code: "US",
|
||||
has_afl_flights: true,
|
||||
location: { lat: 40, lon: -74 },
|
||||
},
|
||||
],
|
||||
airports: [
|
||||
{
|
||||
code: "SVO",
|
||||
city_code: "MOW",
|
||||
title: { ru: "Шереметьево" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: 55, lon: 37 },
|
||||
},
|
||||
{
|
||||
code: "LED",
|
||||
city_code: "LED",
|
||||
title: { ru: "Пулково" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: 60, lon: 30 },
|
||||
},
|
||||
{
|
||||
code: "JFK",
|
||||
city_code: "NYC",
|
||||
title: { ru: "Джон Кеннеди" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: 40, lon: -74 },
|
||||
},
|
||||
],
|
||||
});
|
||||
dictState.loading = false;
|
||||
dictState.error = null;
|
||||
searchState.routes = [];
|
||||
searchState.loading = false;
|
||||
searchState.error = null;
|
||||
});
|
||||
|
||||
it("renders all routes (no filter toggles) in initial filter state", () => {
|
||||
searchState.routes = [
|
||||
{ route: ["MOW", "LED"], isDirect: true },
|
||||
{ route: ["MOW", "NYC"], isDirect: true },
|
||||
];
|
||||
render(<FlightsMapStartPage />);
|
||||
const polylines = lastMapCanvasProps!["polylines"] as Array<{ cityIds: string[] }>;
|
||||
expect(polylines).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("renders a departure + arrival popup in route mode with a buy-ticket URL", () => {
|
||||
searchState.routes = [{ route: ["MOW", "LED"], isDirect: true }];
|
||||
|
||||
render(<FlightsMapStartPage />);
|
||||
act(() => {
|
||||
(lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
|
||||
});
|
||||
act(() => {
|
||||
(lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
|
||||
});
|
||||
|
||||
const popups = lastMapCanvasProps!["popups"] as Array<{ content: string; lat: number; lng: number }>;
|
||||
expect(popups).toHaveLength(2);
|
||||
expect(popups[0]!.content).toContain("Москва");
|
||||
expect(popups[1]!.content).toContain("Санкт-Петербург");
|
||||
expect(popups[1]!.content).toContain("routes=MOW.");
|
||||
expect(popups[1]!.content).toContain(".LED");
|
||||
expect(popups[1]!.content).toContain("https://www.aeroflot.ru/sb/app/ru-ru");
|
||||
});
|
||||
|
||||
it("does not render popups in spider mode (departure only)", () => {
|
||||
searchState.routes = [{ route: ["MOW", "LED"], isDirect: true }];
|
||||
render(<FlightsMapStartPage />);
|
||||
const onClick = lastMapCanvasProps!["onMarkerClick"] as (id: string) => void;
|
||||
act(() => {
|
||||
onClick("MOW");
|
||||
});
|
||||
|
||||
const popups = lastMapCanvasProps!["popups"] as unknown[];
|
||||
expect(popups).toEqual([]);
|
||||
});
|
||||
|
||||
it("auto-fallback: re-issues the search with connections=1 when direct routes come back empty", () => {
|
||||
searchState.routes = [];
|
||||
render(<FlightsMapStartPage />);
|
||||
act(() => {
|
||||
(lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
|
||||
});
|
||||
act(() => {
|
||||
(lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
|
||||
});
|
||||
|
||||
const withOne = searchCalls.filter((p) => p?.connections === 1);
|
||||
expect(withOne.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("mirror: last search call uses connections=1 after auto-fallback", () => {
|
||||
searchState.routes = [];
|
||||
render(<FlightsMapStartPage />);
|
||||
act(() => {
|
||||
(lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
|
||||
});
|
||||
act(() => {
|
||||
(lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
|
||||
});
|
||||
|
||||
const last = [...searchCalls].reverse().find((p) => p?.departure && p?.arrival);
|
||||
expect(last?.connections).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user