Align Flight-Map first-entry toggle defaults with TZ 4.1.1-R14/R21
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { render, screen, act } from "@testing-library/react";
|
||||
import { FlightsMapStartPage } from "./FlightsMapStartPage.js";
|
||||
import type * as DictionariesModuleNS from "@/shared/dictionaries/index.js";
|
||||
@@ -578,3 +578,212 @@ describe("4.1.8 / 4.1.1-R26: Flight-Map filter cross-section isolation", () => {
|
||||
expect(filterValue["international"]).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TZ §4.1.1 ¶4 / ¶9: Flight-Map first-entry toggle defaults
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("4.1.1-R14/R21: Flight-Map first-entry toggle defaults per TZ §4.1.1", () => {
|
||||
let originalGeolocation: Geolocation | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
lastMapFilterProps = null;
|
||||
dictState.dictionaries = buildDictionaries({
|
||||
regions: [],
|
||||
countries: [],
|
||||
cities: [
|
||||
{
|
||||
code: "MOW",
|
||||
title: { ru: "Москва" },
|
||||
country_code: "RU",
|
||||
has_afl_flights: true,
|
||||
location: { lat: 55.75, lon: 37.62 },
|
||||
},
|
||||
],
|
||||
airports: [
|
||||
{
|
||||
code: "SVO",
|
||||
city_code: "MOW",
|
||||
title: { ru: "Шереметьево" },
|
||||
has_afl_flights: true,
|
||||
location: { lat: 55.75, lon: 37.62 },
|
||||
},
|
||||
],
|
||||
});
|
||||
dictState.loading = false;
|
||||
dictState.error = null;
|
||||
searchState.routes = [];
|
||||
searchState.loading = false;
|
||||
searchState.error = null;
|
||||
resetCrossSectionStore();
|
||||
|
||||
// Save original geolocation
|
||||
originalGeolocation = navigator.geolocation;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore geolocation
|
||||
Object.defineProperty(navigator, "geolocation", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: originalGeolocation,
|
||||
});
|
||||
});
|
||||
|
||||
it("4.1.1-R14: with geo consent (success) — domestic ON, international ON, transfers OFF", async () => {
|
||||
// Mock geolocation success callback
|
||||
let geoCallback: PositionCallback | null = null;
|
||||
Object.defineProperty(navigator, "geolocation", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: {
|
||||
getCurrentPosition: (cb: PositionCallback) => {
|
||||
geoCallback = cb;
|
||||
// Simulate async position callback on next tick
|
||||
setTimeout(() => {
|
||||
cb({
|
||||
coords: {
|
||||
latitude: 55.75,
|
||||
longitude: 37.62,
|
||||
accuracy: 50,
|
||||
altitude: null,
|
||||
altitudeAccuracy: null,
|
||||
heading: null,
|
||||
speed: null,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
} as GeolocationPosition);
|
||||
}, 0);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { rerender } = render(<FlightsMapStartPage />);
|
||||
|
||||
// Wait for geolocation callback to fire
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
rerender(<FlightsMapStartPage />);
|
||||
|
||||
const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
|
||||
expect(filterValue["domestic"]).toBe(true); // R14: domestic ON
|
||||
expect(filterValue["international"]).toBe(true); // R14: international ON
|
||||
expect(filterValue["connections"]).toBe(false); // R14: transfers OFF
|
||||
});
|
||||
|
||||
it("4.1.1-R21: without geo consent (error/denied) — all three toggles OFF", async () => {
|
||||
// Mock geolocation failure (permission denied, etc.)
|
||||
Object.defineProperty(navigator, "geolocation", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: {
|
||||
getCurrentPosition: (
|
||||
_cb: PositionCallback,
|
||||
errorCallback: PositionErrorCallback,
|
||||
) => {
|
||||
// Simulate async error callback on next tick
|
||||
setTimeout(() => {
|
||||
errorCallback({
|
||||
code: 1, // PERMISSION_DENIED
|
||||
message: "User denied geolocation",
|
||||
PERMISSION_DENIED: 1,
|
||||
POSITION_UNAVAILABLE: 2,
|
||||
TIMEOUT: 3,
|
||||
} as GeolocationPositionError);
|
||||
}, 0);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { rerender } = render(<FlightsMapStartPage />);
|
||||
|
||||
// Wait for geolocation error callback to fire
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
rerender(<FlightsMapStartPage />);
|
||||
|
||||
const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
|
||||
expect(filterValue["domestic"]).toBe(false); // R21: domestic OFF
|
||||
expect(filterValue["international"]).toBe(false); // R21: international OFF
|
||||
expect(filterValue["connections"]).toBe(false); // R21: transfers OFF
|
||||
});
|
||||
|
||||
it("R14: toggling off domestic when geo succeeds should not re-enable international", async () => {
|
||||
// Mock geolocation success
|
||||
Object.defineProperty(navigator, "geolocation", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: {
|
||||
getCurrentPosition: (cb: PositionCallback) => {
|
||||
setTimeout(() => {
|
||||
cb({
|
||||
coords: {
|
||||
latitude: 55.75,
|
||||
longitude: 37.62,
|
||||
accuracy: 50,
|
||||
altitude: null,
|
||||
altitudeAccuracy: null,
|
||||
heading: null,
|
||||
speed: null,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
} as GeolocationPosition);
|
||||
}, 0);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { rerender } = render(<FlightsMapStartPage />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
rerender(<FlightsMapStartPage />);
|
||||
|
||||
// At this point, geo should have fired, setting both domestic and international to true
|
||||
let filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
|
||||
expect(filterValue["domestic"]).toBe(true);
|
||||
expect(filterValue["international"]).toBe(true);
|
||||
});
|
||||
|
||||
it("R21: stored map snapshot takes precedence over geo defaults", async () => {
|
||||
// Pre-set a map snapshot with specific toggle values
|
||||
const mapSnap: MapFilterSnapshot = {
|
||||
departure: "MOW",
|
||||
arrival: null,
|
||||
date: null,
|
||||
showInternal: false,
|
||||
showInternational: true,
|
||||
showTransfers: false,
|
||||
};
|
||||
setMapFilter(mapSnap);
|
||||
|
||||
// Mock geolocation success (should NOT override the snapshot)
|
||||
Object.defineProperty(navigator, "geolocation", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: {
|
||||
getCurrentPosition: (cb: PositionCallback) => {
|
||||
setTimeout(() => {
|
||||
cb({
|
||||
coords: {
|
||||
latitude: 55.75,
|
||||
longitude: 37.62,
|
||||
accuracy: 50,
|
||||
altitude: null,
|
||||
altitudeAccuracy: null,
|
||||
heading: null,
|
||||
speed: null,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
} as GeolocationPosition);
|
||||
}, 0);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { rerender } = render(<FlightsMapStartPage />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
rerender(<FlightsMapStartPage />);
|
||||
|
||||
const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
|
||||
// Snapshot values should be preserved, not overwritten by geo defaults
|
||||
expect(filterValue["domestic"]).toBe(false);
|
||||
expect(filterValue["international"]).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -121,7 +121,14 @@ export const FlightsMapStartPage: FC<FlightsMapStartPageProps> = ({
|
||||
setFilterState((prev) =>
|
||||
prev.departure || prev.arrival
|
||||
? prev
|
||||
: { ...prev, departure: cityCode },
|
||||
: {
|
||||
...prev,
|
||||
departure: cityCode,
|
||||
// TZ §4.1.1 ¶4: with geo consent, enable domestic + international
|
||||
domestic: true,
|
||||
international: true,
|
||||
// connections stays OFF per R14
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user