mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(tours): preserve viewport when starting a new tour
This commit is contained in:
@@ -463,6 +463,23 @@ describe('MapView', () => {
|
||||
)
|
||||
expect(mapMock.fitBounds).not.toHaveBeenCalled()
|
||||
|
||||
const mapContainer = screen.getByTestId('map-container')
|
||||
const retainedCenter = { lat: 35.2, lng: 135.8 }
|
||||
mapMock.getCenter.mockReturnValue(retainedCenter)
|
||||
mapMock.getZoom.mockReturnValue(12)
|
||||
mapMock.setView([retainedCenter.lat, retainedCenter.lng], 12)
|
||||
mapMock.fitBounds.mockClear()
|
||||
mapMock.setView.mockClear()
|
||||
mapMock.panTo.mockClear()
|
||||
|
||||
rerender(<MapView places={[]} followSelection={false} focusKey={2} focusPoints={[]} />)
|
||||
expect(screen.getByTestId('map-container')).toBe(mapContainer)
|
||||
expect(mapMock.fitBounds).not.toHaveBeenCalled()
|
||||
expect(mapMock.setView).not.toHaveBeenCalled()
|
||||
expect(mapMock.panTo).not.toHaveBeenCalled()
|
||||
expect(mapMock.getCenter()).toEqual(retainedCenter)
|
||||
expect(mapMock.getZoom()).toBe(12)
|
||||
|
||||
rerender(
|
||||
<MapView places={[]} followSelection={false} focusKey={2} focusPoints={[[48, 11], [48.5, 11.8]]} />,
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@ const glMap = vi.hoisted(() => ({
|
||||
flyTo: vi.fn(),
|
||||
jumpTo: vi.fn(),
|
||||
getZoom: vi.fn().mockReturnValue(10),
|
||||
getCenter: vi.fn(() => ({ lng: 0, lat: 0 })),
|
||||
addControl: vi.fn(),
|
||||
removeControl: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
@@ -938,19 +939,6 @@ describe('MapViewGL', () => {
|
||||
route={null}
|
||||
followSelection={false}
|
||||
focusKey={1}
|
||||
focusPoints={[]}
|
||||
glProvider="maplibre-gl"
|
||||
/>,
|
||||
)
|
||||
await act(async () => {})
|
||||
expect(glMap.fitBounds).not.toHaveBeenCalled()
|
||||
|
||||
rerender(
|
||||
<MapViewGL
|
||||
places={[]}
|
||||
route={[[[48, 11], [48.2, 11.3]]]}
|
||||
followSelection={false}
|
||||
focusKey={1}
|
||||
focusPoints={[[48, 11], [48.2, 11.3]]}
|
||||
glProvider="maplibre-gl"
|
||||
/>,
|
||||
@@ -958,26 +946,35 @@ describe('MapViewGL', () => {
|
||||
await act(async () => {})
|
||||
expect(glMap.fitBounds).toHaveBeenCalledOnce()
|
||||
glMap.fitBounds.mockClear()
|
||||
|
||||
rerender(
|
||||
<MapViewGL
|
||||
places={[]}
|
||||
route={[[[48, 11], [48.2, 11.3], [48.4, 11.6]]]}
|
||||
followSelection={false}
|
||||
focusKey={1}
|
||||
focusPoints={[[48, 11], [48.2, 11.3], [48.4, 11.6]]}
|
||||
glProvider="maplibre-gl"
|
||||
/>,
|
||||
)
|
||||
await act(async () => {})
|
||||
expect(glMap.fitBounds).not.toHaveBeenCalled()
|
||||
const mapConstructions = (maplibregl.Map as any).mock.calls.length
|
||||
const retainedCenter = { lng: 135.8, lat: 35.2 }
|
||||
glMap.getCenter.mockReturnValue(retainedCenter)
|
||||
glMap.getZoom.mockReturnValue(12)
|
||||
|
||||
rerender(
|
||||
<MapViewGL
|
||||
places={[]}
|
||||
followSelection={false}
|
||||
focusKey={2}
|
||||
focusPoints={[[48, 11], [48.5, 11.8]]}
|
||||
focusPoints={[]}
|
||||
glProvider="maplibre-gl"
|
||||
/>,
|
||||
)
|
||||
await act(async () => {})
|
||||
expect(glMap.fitBounds).not.toHaveBeenCalled()
|
||||
expect(glMap.flyTo).not.toHaveBeenCalled()
|
||||
expect(glMap.jumpTo).not.toHaveBeenCalled()
|
||||
expect(glMap.easeTo).not.toHaveBeenCalled()
|
||||
expect((maplibregl.Map as any).mock.calls).toHaveLength(mapConstructions)
|
||||
expect(glMap.getCenter()).toEqual(retainedCenter)
|
||||
expect(glMap.getZoom()).toBe(12)
|
||||
|
||||
rerender(
|
||||
<MapViewGL
|
||||
places={[]}
|
||||
followSelection={false}
|
||||
focusKey={2}
|
||||
focusPoints={[[35.01, 135.76]]}
|
||||
glProvider="maplibre-gl"
|
||||
/>,
|
||||
)
|
||||
|
||||
@@ -26,7 +26,11 @@ const plannerActions = vi.hoisted(() => ({
|
||||
viewGpxTour: vi.fn(),
|
||||
addWaypoint: vi.fn(),
|
||||
openTour: vi.fn(),
|
||||
startNewTour: vi.fn(),
|
||||
forgetDeletedTour: vi.fn((_placeId: number) => undefined),
|
||||
saveOutcome: null as unknown,
|
||||
mapFocusKey: 0,
|
||||
newDraftGeneration: 0,
|
||||
mode: { type: 'neutral' } as unknown,
|
||||
isSaving: false,
|
||||
waypoints: [] as unknown,
|
||||
@@ -106,17 +110,18 @@ vi.mock('../components/Tours/planner/useTourPlanner', () => ({
|
||||
waypoints: plannerActions.waypoints,
|
||||
routeProfileFocus: plannerActions.routeProfileFocus,
|
||||
name: '',
|
||||
startNewTour: vi.fn(),
|
||||
startNewTour: plannerActions.startNewTour,
|
||||
newTourConfirmationOpen: false,
|
||||
cancelNewTour: vi.fn(),
|
||||
elevationProfileExpanded: true,
|
||||
toggleElevationProfile: vi.fn(),
|
||||
newDraftGeneration: 0,
|
||||
newDraftGeneration: plannerActions.newDraftGeneration,
|
||||
mapFocusKey: plannerActions.mapFocusKey,
|
||||
mode: plannerActions.mode,
|
||||
isSaving: plannerActions.isSaving,
|
||||
selectedWaypointId: null,
|
||||
editingPlaceId: null,
|
||||
saveOutcome: null,
|
||||
saveOutcome: plannerActions.saveOutcome,
|
||||
hasUnsavedChanges: false,
|
||||
setSelectedWaypointId: vi.fn(),
|
||||
mapBaseLayer: 'map',
|
||||
@@ -430,6 +435,16 @@ function renderPage(overrides: HookState = {}) {
|
||||
return render(<TripPlannerPage />)
|
||||
}
|
||||
|
||||
function mockStartNewTourTransition() {
|
||||
plannerActions.startNewTour.mockImplementation(() => {
|
||||
plannerActions.mode = { type: 'new-draft' }
|
||||
plannerActions.waypoints = []
|
||||
plannerActions.saveOutcome = null
|
||||
plannerActions.mapFocusKey += 1
|
||||
plannerActions.newDraftGeneration += 1
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
resetAllStores()
|
||||
@@ -438,7 +453,11 @@ beforeEach(() => {
|
||||
plannerActions.viewGpxTour.mockReset()
|
||||
plannerActions.addWaypoint.mockReset()
|
||||
plannerActions.openTour.mockReset()
|
||||
plannerActions.startNewTour.mockReset()
|
||||
plannerActions.forgetDeletedTour.mockReset()
|
||||
plannerActions.saveOutcome = null
|
||||
plannerActions.mapFocusKey = 0
|
||||
plannerActions.newDraftGeneration = 0
|
||||
plannerActions.mode = { type: 'neutral' }
|
||||
plannerActions.isSaving = false
|
||||
plannerActions.waypoints = []
|
||||
@@ -542,7 +561,83 @@ describe('TripPlannerPage — shell', () => {
|
||||
expect(plannerActions.openTour).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('FE-PAGE-TPW-063: uses the last normal Plan viewport before falling back to trip Places', () => {
|
||||
it('preserves the visible TOUR-PLANNER camera when the Plan a Tour button starts an empty draft', async () => {
|
||||
const place = buildPlace({ id: 81, lat: 35.01, lng: 135.76 })
|
||||
mockStartNewTourTransition()
|
||||
const view = renderPage({
|
||||
activeTab: 'tour-planner',
|
||||
enabledAddons: { packing: true, budget: true, documents: true, collab: true, tours: true },
|
||||
toursEnabled: true,
|
||||
places: [place],
|
||||
})
|
||||
const mapElement = screen.getByTestId('map-view')
|
||||
|
||||
expect(props('map').focusPoints).toEqual([[place.lat, place.lng]])
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Plan new tour' }))
|
||||
expect(plannerActions.startNewTour).toHaveBeenCalledOnce()
|
||||
view.rerender(<TripPlannerPage />)
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'New tour' })).toBeInTheDocument()
|
||||
expect(props('map').focusPoints).toEqual([])
|
||||
expect(props('map').route).toBeNull()
|
||||
expect(props('map').plannerWaypoints).toEqual([])
|
||||
expect(props('map').focusKey).toBe(1)
|
||||
expect(screen.getByTestId('map-view')).toBe(mapElement)
|
||||
expect(props('map').onMapClick).toBeTypeOf('function')
|
||||
act(() => props('map').onMapClick({ latlng: { lat: 35.02, lng: 135.77 } }))
|
||||
expect(plannerActions.addWaypoint).toHaveBeenCalledWith(35.02, 135.77)
|
||||
})
|
||||
|
||||
it('preserves the visible TOUR-PLANNER camera when Plan another starts a new Tour', async () => {
|
||||
const savedTour = {
|
||||
place_id: 82,
|
||||
name: 'Saved Kyoto tour',
|
||||
tour_type: 'hike',
|
||||
distance: 6,
|
||||
elevation_gain: 300,
|
||||
elevation_loss: 290,
|
||||
duration: 120,
|
||||
difficulty: null,
|
||||
wanderer_ref: null,
|
||||
match_confidence: 1,
|
||||
tour_group_id: null,
|
||||
max_hiking_difficulty: 2,
|
||||
planned: false,
|
||||
caution: false,
|
||||
has_waypoints: true,
|
||||
}
|
||||
mockStartNewTourTransition()
|
||||
plannerActions.mode = { type: 'edit-saved', placeId: savedTour.place_id }
|
||||
plannerActions.waypoints = [
|
||||
{ id: 'start', lat: 35.01, lng: 135.76, role: 'start' },
|
||||
{ id: 'end', lat: 35.02, lng: 135.77, role: 'end' },
|
||||
]
|
||||
plannerActions.mapFocusKey = 8
|
||||
plannerActions.saveOutcome = savedTour
|
||||
const view = renderPage({
|
||||
activeTab: 'tour-planner',
|
||||
enabledAddons: { packing: true, budget: true, documents: true, collab: true, tours: true },
|
||||
toursEnabled: true,
|
||||
tours: [savedTour],
|
||||
places: [buildPlace({ id: savedTour.place_id, lat: 35.01, lng: 135.76 })],
|
||||
})
|
||||
const mapElement = screen.getByTestId('map-view')
|
||||
|
||||
expect(props('map').focusPoints).toEqual([[35.01, 135.76], [35.02, 135.77]])
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Plan another' }))
|
||||
expect(plannerActions.startNewTour).toHaveBeenCalledOnce()
|
||||
view.rerender(<TripPlannerPage />)
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'New tour' })).toBeInTheDocument()
|
||||
expect(props('map').focusPoints).toEqual([])
|
||||
expect(props('map').route).toBeNull()
|
||||
expect(props('map').plannerWaypoints).toEqual([])
|
||||
expect(props('map').focusKey).toBe(9)
|
||||
expect(screen.getByTestId('map-view')).toBe(mapElement)
|
||||
expect(props('map').onMapClick).toBeTypeOf('function')
|
||||
})
|
||||
|
||||
it('FE-PAGE-TPW-063: uses the last TRIP-PLAN viewport for neutral TOUR-PLANNER fallback', () => {
|
||||
const first = buildPlace({ id: 81, lat: 47.1, lng: 11.2 })
|
||||
const second = buildPlace({ id: 82, lat: 47.3, lng: 11.6 })
|
||||
const view = renderPage({ activeTab: 'tour-planner', enabledAddons: { packing: true, budget: true, documents: true, collab: true, tours: true }, toursEnabled: true, places: [first, second] })
|
||||
@@ -566,6 +661,8 @@ describe('TripPlannerPage — shell', () => {
|
||||
hookState = { ...hookState, activeTab: 'plan' }
|
||||
view.rerender(<TripPlannerPage />)
|
||||
act(() => props('map').onViewportChange({ south: 46.9, west: 10.8, north: 47.5, east: 12.1 }))
|
||||
plannerActions.mode = { type: 'neutral' }
|
||||
plannerActions.waypoints = []
|
||||
hookState = { ...hookState, activeTab: 'tour-planner' }
|
||||
view.rerender(<TripPlannerPage />)
|
||||
|
||||
|
||||
@@ -60,7 +60,10 @@ export function useTripPlannerPage() {
|
||||
|
||||
const focusPoints = useMemo<[number, number][]>(() => {
|
||||
if (tourPlanner.mode.type === 'view-gpx') return tourPlanner.readOnlyGpxAnalysis?.routeCoordinates ?? []
|
||||
if ((tourPlanner.mode.type === 'new-draft' || tourPlanner.mode.type === 'edit-saved') && tourPlanner.waypoints.length) {
|
||||
if (tourPlanner.mode.type === 'new-draft') {
|
||||
return tourPlanner.waypoints.map(point => [point.lat, point.lng])
|
||||
}
|
||||
if (tourPlanner.mode.type === 'edit-saved' && tourPlanner.waypoints.length) {
|
||||
return tourPlanner.waypoints.map(point => [point.lat, point.lng])
|
||||
}
|
||||
return plannerFallbackFocusPoints
|
||||
|
||||
Reference in New Issue
Block a user