Wire first-entry geolocation into Schedule start page (TZ 4.1.1-R8)
This commit is contained in:
@@ -91,10 +91,29 @@ vi.mock("@/shared/dictionaries/index.js", () => ({
|
||||
getCityCodeByAirportCode: () => undefined,
|
||||
}));
|
||||
|
||||
// Capture the latest onCity callback so individual tests can invoke it.
|
||||
let capturedOnCity: ((code: string) => void) | null = null;
|
||||
let capturedShouldApply: (() => boolean) | null = null;
|
||||
let geoMockEnabled = false;
|
||||
|
||||
vi.mock("@/shared/hooks/useGeoCityDefault.js", () => ({
|
||||
useGeoCityDefault: (opts: { shouldApply: () => boolean; onCity: (code: string) => void }) => {
|
||||
capturedOnCity = opts.onCity;
|
||||
capturedShouldApply = opts.shouldApply;
|
||||
// If the test enabled geo, fire it synchronously so we don't need async.
|
||||
if (geoMockEnabled && opts.shouldApply()) {
|
||||
opts.onCity("MOW");
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
describe("ScheduleStartPage", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
sessionStore.clear();
|
||||
geoMockEnabled = false;
|
||||
capturedOnCity = null;
|
||||
capturedShouldApply = null;
|
||||
});
|
||||
|
||||
it("renders the start page", () => {
|
||||
@@ -180,3 +199,53 @@ describe("4.1.8: Schedule hydrates from cross-section store on mount", () => {
|
||||
expect(depInput.defaultValue).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TZ §4.1.1-R8/R10: first-entry geolocation auto-fill + time default
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("4.1.1-R8/R10: Schedule first-entry geolocation + time default", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
sessionStore.clear();
|
||||
resetCrossSectionStore();
|
||||
geoMockEnabled = false;
|
||||
capturedOnCity = null;
|
||||
capturedShouldApply = null;
|
||||
});
|
||||
|
||||
it("4.1.1-R8: populates departure with geolocated city on first entry", () => {
|
||||
geoMockEnabled = true; // mock will call onCity("MOW") synchronously on shouldApply()
|
||||
render(<ScheduleStartPage />);
|
||||
const depInput = screen.getByTestId("schedule-departure-input") as HTMLInputElement;
|
||||
expect(depInput.defaultValue).toBe("MOW");
|
||||
});
|
||||
|
||||
it("4.1.1-R8: does not populate when geolocation denied (geoMockEnabled=false)", () => {
|
||||
// geoMockEnabled stays false → useGeoCityDefault mock never fires onCity
|
||||
render(<ScheduleStartPage />);
|
||||
const depInput = screen.getByTestId("schedule-departure-input") as HTMLInputElement;
|
||||
expect(depInput.defaultValue).toBe("");
|
||||
});
|
||||
|
||||
it("4.1.1-R8: does not override departure when cross-section store has schedule filter", () => {
|
||||
setScheduleFilter({
|
||||
mode: "route", departure: "LED", arrival: "KGD",
|
||||
dateFrom: "20260515", dateTo: "20260521",
|
||||
timeFrom: "0000", timeTo: "2400",
|
||||
onlyDirect: false, showReturn: false, searchExecuted: false,
|
||||
});
|
||||
geoMockEnabled = true;
|
||||
render(<ScheduleStartPage />);
|
||||
// Departure should be LED from the snapshot, NOT overridden by geo.
|
||||
const depInput = screen.getByTestId("schedule-departure-input") as HTMLInputElement;
|
||||
expect(depInput.defaultValue).toBe("LED");
|
||||
});
|
||||
|
||||
it("4.1.1-R10: time default = 00:00-24:00 on all viewports", () => {
|
||||
render(<ScheduleStartPage />);
|
||||
const timeSelector = screen.getByTestId("time-selector");
|
||||
expect(timeSelector.textContent).toContain("00:00");
|
||||
expect(timeSelector.textContent).toContain("24:00");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
getCityCodeByAirportCode,
|
||||
} from "@/shared/dictionaries/index.js";
|
||||
import type { IDictionaries } from "@/shared/dictionaries/index.js";
|
||||
import { useGeoCityDefault } from "@/shared/hooks/useGeoCityDefault.js";
|
||||
import { buildScheduleUrl } from "../url.js";
|
||||
import { scheduleWindowBounds } from "@/shared/dateWindow.js";
|
||||
import "./ScheduleStartPage.scss";
|
||||
@@ -136,6 +137,18 @@ export const ScheduleStartPage: FC = () => {
|
||||
const [departureCode, setDepartureCode] = useState<string>(prefill.departure ?? "");
|
||||
const [arrivalCode, setArrivalCode] = useState<string>(prefill.arrival ?? "");
|
||||
|
||||
// TZ §4.1.1-R8: on first session entry with geo consent, auto-fill
|
||||
// departure city with the user's nearest city when:
|
||||
// - no stored schedule filter is present (fresh session), AND
|
||||
// - the current departure is empty (not populated by prefill or user).
|
||||
useGeoCityDefault({
|
||||
dictionaries,
|
||||
shouldApply: () => !getScheduleFilter() && !departureCode,
|
||||
onCity: (cityCode) => {
|
||||
setDepartureCode((prev) => (prev ? prev : cityCode));
|
||||
},
|
||||
});
|
||||
|
||||
// Start blank to match Angular's `ДД.ММ.ГГГГ - ДД.ММ.ГГГГ` placeholder
|
||||
// (the "current week" pre-fill was a React-only convenience that
|
||||
// pulled the date input out of parity). Submit handler defaults to
|
||||
|
||||
Reference in New Issue
Block a user