diff --git a/client/src/components/Map/MapView.test.tsx b/client/src/components/Map/MapView.test.tsx index 9bcf0c5d7..f0fe2149e 100644 --- a/client/src/components/Map/MapView.test.tsx +++ b/client/src/components/Map/MapView.test.tsx @@ -916,7 +916,17 @@ describe('MapView live location', () => { }) describe('MapView bounds fitting', () => { - it('FE-COMP-MAPVIEW-065: a day-detail fit nudges the map down once the animation ran (#1348)', async () => { + /* + * The day fit used to compensate for the day panel twice: once through the + * padding, and again 300ms later with panBy([0, 150]) (#1348). On a route + * running north to south the fit is height-bound, so the northernmost stop + * lands exactly on the top padding line and the nudge then pushed it off the + * canvas — the map framed everything and then drifted upwards (#1982). + * + * So this case is the inverse of what it used to assert: the padding does the + * work, and nothing moves the camera afterwards. + */ + it('FE-COMP-MAPVIEW-065: a day-detail fit reserves the panel in its padding and then leaves the camera alone (#1348, #1982)', async () => { const places = [ buildMapPlace({ id: 1, lat: 48, lng: 2 }), buildMapPlace({ id: 2, lat: 48.2, lng: 2.2 }), @@ -925,7 +935,24 @@ describe('MapView bounds fitting', () => { rerender() expect(mapMock.fitBounds).toHaveBeenCalled() - await waitFor(() => expect(mapMock.panBy).toHaveBeenCalledWith([0, 150], { animate: true }), { timeout: 2000 }) + const opts = mapMock.fitBounds.mock.calls.at(-1)?.[1] as { paddingBottomRight?: [number, number] } + expect(opts?.paddingBottomRight?.[1]).toBe(280) + + // Long enough that the old 300ms nudge would have fired by now. + await new Promise(r => setTimeout(r, 600)) + expect(mapMock.panBy).not.toHaveBeenCalled() + }) + + it('FE-COMP-MAPVIEW-065b: reserves only the small margin when no day panel is open (#1982)', () => { + const places = [ + buildMapPlace({ id: 1, lat: 48, lng: 2 }), + buildMapPlace({ id: 2, lat: 48.2, lng: 2.2 }), + ] + const { rerender } = render() + rerender() + + const opts = mapMock.fitBounds.mock.calls.at(-1)?.[1] as { paddingBottomRight?: [number, number] } + expect(opts?.paddingBottomRight?.[1]).toBe(60) }) it('FE-COMP-MAPVIEW-066: a fitKey that never changed is not re-fitted', async () => { diff --git a/client/src/components/Map/MapView.tsx b/client/src/components/Map/MapView.tsx index eca314267..5c8ca3deb 100644 --- a/client/src/components/Map/MapView.tsx +++ b/client/src/components/Map/MapView.tsx @@ -269,7 +269,6 @@ function MapController({ center, zoom }: MapControllerProps) { // finished computing asynchronously — re-fit once more to include the full route // polyline, so a route that bulges past its stops stays in view (#1128). interface BoundsControllerProps { - hasDayDetail?: boolean places: Place[] routeCoords: [number, number][] fitKey: number @@ -278,7 +277,7 @@ interface BoundsControllerProps { framedOnMount?: boolean } -function BoundsController({ places, routeCoords, fitKey, paddingOpts, hasDayDetail, framedOnMount = false }: BoundsControllerProps) { +function BoundsController({ places, routeCoords, fitKey, paddingOpts, framedOnMount = false }: BoundsControllerProps) { const map = useMap() const prevFitKey = useRef(-1) const awaitingRoute = useRef(false) @@ -289,13 +288,24 @@ function BoundsController({ places, routeCoords, fitKey, paddingOpts, hasDayDeta try { const bounds = L.latLngBounds(coords) if (bounds.isValid()) { + /* + * The padding already reserves the day panel's height at the bottom + * (paddingBox), so the fit puts the day's stops above it. There used to + * be a second, manual nudge 300ms later — panBy([0, 150]) — from the + * same change that introduced the padding, and the two compensated for + * the same panel twice. + * + * On a route that runs north to south the fit is height-bound, which + * puts the northernmost stop exactly on the top padding line; the nudge + * then pushed it off the canvas. The map appeared to frame everything + * correctly and then drift upwards, which is what the report describes + * (#1982). MapViewGL never had the nudge, so this also brings the two + * renderers back into agreement. + */ map.fitBounds(bounds, { ...paddingOpts, maxZoom: 16, animate: true }) - if (hasDayDetail) { - setTimeout(() => map.panBy([0, 150], { animate: true }), 300) - } } } catch {} - }, [map, paddingOpts, hasDayDetail]) + }, [map, paddingOpts]) // New fitKey (initial trip fit or a day selection): fit to the destinations now // and arm a one-shot re-fit for when the route arrives. @@ -827,7 +837,7 @@ export const MapView = memo(function MapView({ /> - 0 ? dayPlaces : places} routeCoords={dayPlaces.length > 0 ? routeCoords : []} fitKey={fitKey} paddingOpts={paddingOpts} hasDayDetail={hasDayDetail} framedOnMount={initialView.framed} /> + 0 ? dayPlaces : places} routeCoords={dayPlaces.length > 0 ? routeCoords : []} fitKey={fitKey} paddingOpts={paddingOpts} framedOnMount={initialView.framed} />