Fix flights map transfer toggle fallback
This commit is contained in:
@@ -10,6 +10,7 @@ import { transformDictionaries } from "@/shared/dictionaries/index.js";
|
||||
import type { IDictionaries, IRawDictionaries } from "@/shared/dictionaries/index.js";
|
||||
import type {
|
||||
FlightsMapCalendarParams,
|
||||
IFlightsMapFilterState,
|
||||
FlightsMapSearchParams,
|
||||
} from "../types.js";
|
||||
import {
|
||||
@@ -383,6 +384,7 @@ describe("TZ §4.1.4 Table 7 breadcrumbs — Flight-Map pages (rows 1-3)", () =>
|
||||
describe("FlightsMapStartPage — C.4 integration", () => {
|
||||
beforeEach(() => {
|
||||
lastMapCanvasProps = null;
|
||||
lastMapFilterProps = null;
|
||||
searchCalls.length = 0;
|
||||
dictState.dictionaries = buildDictionaries({
|
||||
regions: [],
|
||||
@@ -513,6 +515,33 @@ describe("FlightsMapStartPage — C.4 integration", () => {
|
||||
const last = [...searchCalls].reverse().find((p) => p?.departure && p?.arrival);
|
||||
expect(last?.connections).toBe(1);
|
||||
});
|
||||
|
||||
it("keeps transfer-only toggle off after the user disables auto-fallback", () => {
|
||||
searchState.routes = [];
|
||||
render(<FlightsMapStartPage />);
|
||||
act(() => {
|
||||
(lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
|
||||
});
|
||||
act(() => {
|
||||
(lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
|
||||
});
|
||||
|
||||
const autoFallbackValue = lastMapFilterProps!["value"] as IFlightsMapFilterState;
|
||||
expect(autoFallbackValue.connections).toBe(true);
|
||||
|
||||
act(() => {
|
||||
(lastMapFilterProps!["onChange"] as (state: IFlightsMapFilterState) => void)({
|
||||
...autoFallbackValue,
|
||||
connections: false,
|
||||
});
|
||||
});
|
||||
|
||||
const userValue = lastMapFilterProps!["value"] as IFlightsMapFilterState;
|
||||
expect(userValue.connections).toBe(false);
|
||||
|
||||
const last = [...searchCalls].reverse().find((p) => p?.departure && p?.arrival);
|
||||
expect(last?.connections).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -161,6 +161,8 @@ export const FlightsMapStartPage: FC<FlightsMapStartPageProps> = ({
|
||||
const [effectiveConnections, setEffectiveConnections] = useState<0 | 1>(
|
||||
filterState.connections ? 1 : 0,
|
||||
);
|
||||
const [connectionsFallbackSuppressed, setConnectionsFallbackSuppressed] =
|
||||
useState(false);
|
||||
|
||||
const persistFilterState = useCallback((newState: IFlightsMapFilterState) => {
|
||||
setFilterState(newState);
|
||||
@@ -225,6 +227,7 @@ export const FlightsMapStartPage: FC<FlightsMapStartPageProps> = ({
|
||||
useEffect(() => {
|
||||
if (loading || error) return;
|
||||
if (effectiveConnections !== 0) return;
|
||||
if (connectionsFallbackSuppressed) return;
|
||||
if (!filterState.departure || !filterState.arrival) return;
|
||||
if (routes.length > 0) return;
|
||||
setEffectiveConnections(1);
|
||||
@@ -232,6 +235,7 @@ export const FlightsMapStartPage: FC<FlightsMapStartPageProps> = ({
|
||||
loading,
|
||||
error,
|
||||
effectiveConnections,
|
||||
connectionsFallbackSuppressed,
|
||||
filterState.departure,
|
||||
filterState.arrival,
|
||||
routes,
|
||||
@@ -239,14 +243,43 @@ export const FlightsMapStartPage: FC<FlightsMapStartPageProps> = ({
|
||||
|
||||
// Reflect fallback in the UI toggle once.
|
||||
useEffect(() => {
|
||||
if (filterState.arrival && effectiveConnections === 1 && !filterState.connections) {
|
||||
if (
|
||||
filterState.arrival &&
|
||||
effectiveConnections === 1 &&
|
||||
!filterState.connections &&
|
||||
!connectionsFallbackSuppressed
|
||||
) {
|
||||
setFilterState((prev) => ({ ...prev, connections: true }));
|
||||
}
|
||||
}, [effectiveConnections, filterState.arrival, filterState.connections]);
|
||||
}, [
|
||||
effectiveConnections,
|
||||
filterState.arrival,
|
||||
filterState.connections,
|
||||
connectionsFallbackSuppressed,
|
||||
]);
|
||||
|
||||
const handleFilterChange = useCallback((newState: IFlightsMapFilterState) => {
|
||||
const sameRoute =
|
||||
newState.departure === filterState.departure &&
|
||||
newState.arrival === filterState.arrival;
|
||||
const turnedConnectionsOff =
|
||||
sameRoute && filterState.connections && !newState.connections;
|
||||
|
||||
setConnectionsFallbackSuppressed(
|
||||
turnedConnectionsOff
|
||||
? true
|
||||
: sameRoute && !newState.connections
|
||||
? connectionsFallbackSuppressed
|
||||
: false,
|
||||
);
|
||||
persistFilterState(newState);
|
||||
}, [persistFilterState]);
|
||||
}, [
|
||||
connectionsFallbackSuppressed,
|
||||
filterState.arrival,
|
||||
filterState.connections,
|
||||
filterState.departure,
|
||||
persistFilterState,
|
||||
]);
|
||||
|
||||
const handleMarkerClick = useCallback(
|
||||
(markerId: string) => {
|
||||
|
||||
@@ -1,11 +1,73 @@
|
||||
import { test, expect } from "./fixtures/console-gate";
|
||||
import {
|
||||
routeAppSettingsFixture,
|
||||
routeDictionaryFixtures,
|
||||
} from "./helpers/api-fixtures";
|
||||
|
||||
async function routeFlightsMapTransferOnlyFixtures(
|
||||
page: import("@playwright/test").Page,
|
||||
): Promise<void> {
|
||||
await routeAppSettingsFixture(page);
|
||||
await routeDictionaryFixtures(page);
|
||||
|
||||
await page.route("**/api/flights/1/*/destinations?**", async (route) => {
|
||||
const url = new URL(route.request().url());
|
||||
const departure = url.searchParams.get("departure");
|
||||
const arrival = url.searchParams.get("arrival");
|
||||
const connections = url.searchParams.get("connections");
|
||||
|
||||
const hasTransferRoute =
|
||||
departure === "LED" && arrival === "MLE" && connections === "1";
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
routes: hasTransferRoute
|
||||
? [{ route: ["LED", "SVO", "MLE"], isDirect: false }]
|
||||
: [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route("**/api/flights/v1/*/days/**/flights-map/", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ days: "1".repeat(200) }),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.route("**/map/api/tile/**", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "image/gif",
|
||||
body: Buffer.from("R0lGODlhAQABAAAAACw=", "base64"),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function selectCity(
|
||||
page: import("@playwright/test").Page,
|
||||
inputTestId: string,
|
||||
query: string,
|
||||
optionTestId: string,
|
||||
): Promise<void> {
|
||||
const input = page.getByTestId(inputTestId).locator("input");
|
||||
await input.click();
|
||||
await input.fill(query);
|
||||
await expect(page.getByTestId(optionTestId)).toBeVisible({ timeout: 10000 });
|
||||
await page.getByTestId(optionTestId).click();
|
||||
}
|
||||
|
||||
test.describe("Flights Map", () => {
|
||||
test("/ru/flights-map renders or shows feature-flag disabled message", async ({
|
||||
page,
|
||||
consoleMessages,
|
||||
}) => {
|
||||
await page.goto("/ru/flights-map");
|
||||
await page.goto("/ru-ru/flights-map");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
// Either the map page renders or the feature-flag-disabled fallback shows
|
||||
@@ -14,4 +76,31 @@ test.describe("Flights Map", () => {
|
||||
|
||||
await expect(mapStart.or(mapDisabled)).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test("transfer-only checkbox can be switched off after auto-fallback", async ({
|
||||
page,
|
||||
consoleMessages,
|
||||
}) => {
|
||||
await routeFlightsMapTransferOnlyFixtures(page);
|
||||
|
||||
await page.goto("/ru-ru/flights-map");
|
||||
await expect(page.getByTestId("flights-map-start")).toBeVisible();
|
||||
|
||||
await selectCity(page, "fm-departure-input", "Санкт", "city-suggestion-LED");
|
||||
await selectCity(page, "fm-arrival-input", "Мале", "city-suggestion-MLE");
|
||||
|
||||
const transferToggle = page.getByTestId("fm-connections-toggle");
|
||||
const transferLabel = page.getByText("Показать только рейсы с пересадкой", {
|
||||
exact: true,
|
||||
});
|
||||
await expect(transferToggle).toBeEnabled();
|
||||
await expect(transferToggle).toBeChecked({ timeout: 10000 });
|
||||
|
||||
await transferLabel.click();
|
||||
await expect(transferToggle).not.toBeChecked();
|
||||
await page.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => {});
|
||||
await expect(transferToggle).not.toBeChecked();
|
||||
|
||||
expect(consoleMessages).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
+12
-2
@@ -1,7 +1,14 @@
|
||||
import { test, expect } from "./fixtures/console-gate";
|
||||
import {
|
||||
routeDictionaryFixtures,
|
||||
routeOnlineboardRouteFixtures,
|
||||
} from "./helpers/api-fixtures";
|
||||
|
||||
test.describe("Smoke tests", () => {
|
||||
test("root / redirects to /ru/onlineboard", async ({ page, consoleMessages }) => {
|
||||
await routeDictionaryFixtures(page);
|
||||
await routeOnlineboardRouteFixtures(page);
|
||||
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
@@ -9,8 +16,8 @@ test.describe("Smoke tests", () => {
|
||||
// Depending on SSR/CSR mode, this may be a server 302 or a client-side
|
||||
// navigation. Wait up to 15s for either outcome.
|
||||
try {
|
||||
await page.waitForURL("**/ru/onlineboard", { timeout: 15000 });
|
||||
expect(page.url()).toContain("/ru/onlineboard");
|
||||
await page.waitForURL(/\/ru(?:-ru)?\/onlineboard$/, { timeout: 15000 });
|
||||
expect(page.url()).toMatch(/\/ru(?:-ru)?\/onlineboard$/);
|
||||
} catch {
|
||||
// If the redirect doesn't fire (e.g. loader not invoked in dev SSR mode),
|
||||
// verify the page at least rendered the online board content.
|
||||
@@ -54,6 +61,9 @@ test.describe("Smoke tests", () => {
|
||||
page,
|
||||
consoleMessages,
|
||||
}) => {
|
||||
await routeDictionaryFixtures(page);
|
||||
await routeOnlineboardRouteFixtures(page);
|
||||
|
||||
await page.goto("/ru-en/onlineboard?_preferredLanguage=en&_preferredLocale=ruh");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user