mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(planner): let a route choice survive the map, and keep new items on their day
Two things the mobile planner forgot. The day sheet's route toggle only ever lived in component state, and entering the map — or tapping a day while in it — forced it back on. Turning the route off therefore did nothing you could see and had reset itself by the time you looked again. The choice is now remembered per trip; the map still switches it on by default, but only while the user has never said otherwise. Adding a place or a booking from inside a day did not connect it to that day. The place dropped into the unplanned pool, which on mobile is a different screen — it looks like the save was lost — and the booking opened with no date at all. Both entry points now carry the day they were opened from, including a long-press on the mobile map, where the chip rail names a day at all times. The desktop map has no such context and passes nothing, so its pool behaviour stands. While here: the import button on the bookings and transports headers only appeared when kitinerary was installed, though the server also accepts an import when the LLM parser is configured (#2007). Closes #2003 Closes #1998
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef, useState, type ComponentType, type ReactNode } from 'react'
|
||||
import { findTodayDayId } from '../../../components/Planner/today'
|
||||
import {
|
||||
ChevronLeft, FileDown, List, Map as MapIcon, MoreHorizontal, PackageCheck,
|
||||
ChevronDown, ChevronLeft, Download, FileDown, List, Map as MapIcon, MoreHorizontal, PackageCheck,
|
||||
Plane, Plus, Rows3, Ticket, TrainFront, Trash2, Upload, Wallet,
|
||||
} from 'lucide-react'
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
@@ -241,7 +241,7 @@ export default function MTripShell({
|
||||
// Entering the map: draw the current day's route and frame it — the
|
||||
// BoundsController re-fits to include the polyline once OSRM resolves.
|
||||
if (next === 'map') {
|
||||
planner.setRouteShown(true)
|
||||
planner.autoShowRoute()
|
||||
if (planner.selectedDayId != null) planner.handleSelectDay(planner.selectedDayId, false)
|
||||
focusMapOnDay(planner.selectedDayId)
|
||||
} else {
|
||||
@@ -284,7 +284,7 @@ export default function MTripShell({
|
||||
// In map mode a day tap fits to that day's places, draws its route and drops
|
||||
// the other days' pins, so the day it framed is the day it shows.
|
||||
if (view === 'map') {
|
||||
planner.setRouteShown(true)
|
||||
planner.autoShowRoute()
|
||||
focusMapOnDay(dayId)
|
||||
}
|
||||
}
|
||||
@@ -335,11 +335,14 @@ export default function MTripShell({
|
||||
// Inactive chips only — an inline background would otherwise beat
|
||||
// the active chip's bg-m-act class.
|
||||
style={active ? undefined : { background: dayTintBackground(tint, 'badge', '--day-tint-chip') }}
|
||||
className={`flex-1 whitespace-nowrap rounded-full px-3 py-[5px] text-center text-[0.75rem] font-semibold ${
|
||||
className={`flex flex-1 items-center justify-center gap-[3px] whitespace-nowrap rounded-full px-3 py-[5px] text-center text-[0.75rem] font-semibold ${
|
||||
active ? 'bg-m-act text-m-actfg shadow-[0_6px_16px_-6px_rgba(0,0,0,.4)]' : 'text-m-ink'
|
||||
}`}
|
||||
>
|
||||
{dayChipLabel(day, language, t('planner.dayN', { n: day.day_number ?? idx + 1 }))}
|
||||
{/* Marks the second tap as "opens the day", the only day-sheet
|
||||
route that also exists in map view. */}
|
||||
{active && <ChevronDown size={11} strokeWidth={2.6} aria-hidden="true" className="flex-none opacity-70" />}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
@@ -387,7 +390,7 @@ export default function MTripShell({
|
||||
/>
|
||||
{planner.bookingImportAvailable && (
|
||||
<MIconBtn ariaLabel={t('reservations.import.title')} onClick={() => planner.setShowBookingImport(true)} size={40} className="text-m-muted backdrop-blur-[24px] backdrop-saturate-[1.7]">
|
||||
<Upload size={15} strokeWidth={2} />
|
||||
<Download size={15} strokeWidth={2} />
|
||||
</MIconBtn>
|
||||
)}
|
||||
{planner.airTrailAvailable && (
|
||||
@@ -407,7 +410,7 @@ export default function MTripShell({
|
||||
/>
|
||||
{planner.bookingImportAvailable && (
|
||||
<MIconBtn ariaLabel={t('reservations.import.title')} onClick={() => planner.setShowBookingImport(true)} size={40} className="text-m-muted backdrop-blur-[24px] backdrop-saturate-[1.7]">
|
||||
<Upload size={15} strokeWidth={2} />
|
||||
<Download size={15} strokeWidth={2} />
|
||||
</MIconBtn>
|
||||
)}
|
||||
<CompactToggle active={bookingsCompact} onToggle={() => setBookingsCompact(v => !v)} label={t('mobileTrip.compactView')} />
|
||||
|
||||
@@ -50,7 +50,10 @@ export default function MMapArea({ planner, shell }: MMapAreaProps) {
|
||||
onMarkerClick={planner.handleMarkerClick}
|
||||
// Tap on empty map = deselect, same contract as desktop.
|
||||
onMapClick={planner.handleMapClick}
|
||||
onMapContextMenu={planner.handleMapContextMenu}
|
||||
// The chip rail names a day at all times on mobile, so a place dropped on
|
||||
// the map belongs to it — the desktop map has no such context and passes
|
||||
// nothing, which keeps its pool behaviour (#1998).
|
||||
onMapContextMenu={e => planner.handleMapContextMenu(e, planner.selectedDayId)}
|
||||
// No center/zoom: the map frames itself on the trip's places at mount.
|
||||
tileUrl={planner.mapTileUrl}
|
||||
fitKey={planner.fitKey}
|
||||
@@ -62,7 +65,7 @@ export default function MMapArea({ planner, shell }: MMapAreaProps) {
|
||||
// routes this through mapTransportDetail into the day sidebar instead).
|
||||
onReservationClick={(rid: number) => shell.openSheet('transport', { reservationId: rid })}
|
||||
pois={poi.pois}
|
||||
onPoiClick={planner.openAddPlaceFromPoi}
|
||||
onPoiClick={marker => planner.openAddPlaceFromPoi(marker, planner.selectedDayId)}
|
||||
onViewportChange={poi.onViewportChange}
|
||||
onMapReady={setGlMap}
|
||||
/>
|
||||
|
||||
@@ -211,14 +211,18 @@ export function useMPlanTimeline(planner: TripPlanner) {
|
||||
}
|
||||
}, [dayAssignments, tripActions, tripId, pushUndo, updateRouteForDay, toast, t])
|
||||
|
||||
const moveRow = useCallback((item: MergedItem, direction: 'up' | 'down') => {
|
||||
/**
|
||||
* Moves an item to an arbitrary slot. The buttons swap with a neighbour, a
|
||||
* drag lands anywhere (#1997) — both come through here so the chronology
|
||||
* guard and the undo entry stay identical either way.
|
||||
*/
|
||||
const moveRowTo = useCallback((item: MergedItem, toIndex: number) => {
|
||||
if (!day) return
|
||||
const idx = merged.indexOf(item)
|
||||
const target = direction === 'up' ? idx - 1 : idx + 1
|
||||
if (idx < 0 || target < 0 || target >= merged.length) return
|
||||
if (idx < 0 || toIndex < 0 || toIndex >= merged.length || toIndex === idx) return
|
||||
const newOrder = [...merged]
|
||||
newOrder[idx] = merged[target]
|
||||
newOrder[target] = item
|
||||
newOrder.splice(idx, 1)
|
||||
newOrder.splice(toIndex, 0, item)
|
||||
// Same guard as the desktop arrow buttons: a TIMED item may not jump past
|
||||
// other timed items (untimed items move freely and keep their neighbours).
|
||||
if (itemHasTime(item, day.id) && breaksChronology(newOrder, day.id, getDisplayTimeForDay)) {
|
||||
@@ -228,6 +232,11 @@ export function useMPlanTimeline(planner: TripPlanner) {
|
||||
void applyMergedOrder(day.id, newOrder)
|
||||
}, [day, merged, applyMergedOrder, toast, t])
|
||||
|
||||
const moveRow = useCallback((item: MergedItem, direction: 'up' | 'down') => {
|
||||
const idx = merged.indexOf(item)
|
||||
moveRowTo(item, direction === 'up' ? idx - 1 : idx + 1)
|
||||
}, [merged, moveRowTo])
|
||||
|
||||
// ── Place actions ──
|
||||
const removeAssignment = useCallback((assignment: Assignment) => {
|
||||
if (!day) return
|
||||
@@ -240,18 +249,22 @@ export function useMPlanTimeline(planner: TripPlanner) {
|
||||
}, [planner])
|
||||
|
||||
// ── Add bar ──
|
||||
// Both of these sit inside a day's toolbar, so the day is the context — the
|
||||
// place gets assigned to it on save and the booking opens pre-dated (#1998).
|
||||
const addPlace = useCallback(() => {
|
||||
planner.setEditingPlace(null)
|
||||
planner.setEditingAssignmentId(null)
|
||||
planner.setPrefillCoords(null)
|
||||
planner.setPlaceFormDayId(day?.id ?? null)
|
||||
planner.setShowPlaceForm(true)
|
||||
}, [planner])
|
||||
}, [planner, day])
|
||||
|
||||
const addBooking = useCallback(() => {
|
||||
planner.setEditingReservation(null)
|
||||
planner.setBookingForAssignmentId(null)
|
||||
planner.setReservationModalDayId(day?.id ?? null)
|
||||
planner.setShowReservationModal(true)
|
||||
}, [planner])
|
||||
}, [planner, day])
|
||||
|
||||
const addTransport = useCallback(() => {
|
||||
planner.setEditingTransport(null)
|
||||
@@ -360,6 +373,7 @@ export function useMPlanTimeline(planner: TripPlanner) {
|
||||
language, timeFormat: settings.time_format,
|
||||
openTransitKeys, toggleTransit,
|
||||
moveRow, removeAssignment, editAssignment, editTransport, openTransitJourney,
|
||||
moveRowTo,
|
||||
addPlace, addBooking, addTransport,
|
||||
optimize, exportGoogleMaps, exportCoMaps, renameDay, fullPlaceOf,
|
||||
routeModeOptions, setLegMode,
|
||||
|
||||
@@ -181,6 +181,7 @@ export default function MPlaceEditSheet({ planner, onOpenExpense }: MPlaceEditSh
|
||||
setEditingPlace(null)
|
||||
setEditingAssignmentId(null)
|
||||
setPrefillCoords(null)
|
||||
planner.setPlaceFormDayId(null)
|
||||
if (deleteArmed) setDeletePlaceId(null)
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,22 @@ export default function MReservationSheet({ planner, onOpenExpense }: MReservati
|
||||
})
|
||||
setPendingFiles(pf._sourceFiles ?? [])
|
||||
} else {
|
||||
setForm(EMPTY)
|
||||
// Opened from a day's toolbar: start on that day rather than on a blank
|
||||
// date the user has to look up again (#1998). A hotel spans to the next
|
||||
// day, which is what checking in on this day actually means.
|
||||
const ctxDay = planner.reservationModalDayId != null
|
||||
? days.find(d => d.id === planner.reservationModalDayId)
|
||||
: undefined
|
||||
const ctxDate = ctxDay?.date ? ctxDay.date.slice(0, 10) : ''
|
||||
const nextDay = ctxDay ? days[days.indexOf(ctxDay) + 1] : undefined
|
||||
setForm(ctxDate
|
||||
? {
|
||||
...EMPTY,
|
||||
reservation_time: ctxDate,
|
||||
hotel_start_day: ctxDay!.id,
|
||||
hotel_end_day: nextDay?.id ?? ctxDay!.id,
|
||||
}
|
||||
: EMPTY)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [showReservationModal])
|
||||
@@ -186,6 +201,7 @@ export default function MReservationSheet({ planner, onOpenExpense }: MReservati
|
||||
|
||||
const handleClose = () => {
|
||||
if (importReviewActive) { advanceImportReview(); return }
|
||||
planner.setReservationModalDayId(null)
|
||||
setShowReservationModal(false)
|
||||
setEditingReservation(null)
|
||||
setBookingForAssignmentId(null)
|
||||
|
||||
@@ -200,11 +200,16 @@ export function useTripPlanner() {
|
||||
const [editingPlace, setEditingPlace] = useState<Place | null>(null)
|
||||
const [prefillCoords, setPrefillCoords] = useState<{ lat: number; lng: number; name?: string; address?: string; website?: string; phone?: string; osm_id?: string } | null>(null)
|
||||
const [editingAssignmentId, setEditingAssignmentId] = useState<number | null>(null)
|
||||
// Day context of the open form. Set only by the day-scoped entry points (the
|
||||
// mobile day toolbar, a long-press on the mobile map); every other opener
|
||||
// clears it, so a place added from the pool still lands in the pool (#1998).
|
||||
const [placeFormDayId, setPlaceFormDayId] = useState<number | null>(null)
|
||||
const [reservationModalDayId, setReservationModalDayId] = useState<number | null>(null)
|
||||
|
||||
// The bottom-nav "+" opens the new-place form via ?create=place.
|
||||
useEffect(() => {
|
||||
if (searchParams.get('create') === 'place') {
|
||||
setEditingPlace(null); setEditingAssignmentId(null); setShowPlaceForm(true)
|
||||
setEditingPlace(null); setEditingAssignmentId(null); setPlaceFormDayId(null); setShowPlaceForm(true)
|
||||
setSearchParams(p => { p.delete('create'); return p }, { replace: true })
|
||||
}
|
||||
}, [searchParams])
|
||||
@@ -268,7 +273,30 @@ export function useTripPlanner() {
|
||||
// Manual route planning: off by default, toggled from the day-plan footer. Mode
|
||||
// is per-session and selects which travel time the connectors show — either a
|
||||
// built-in OSRM profile or a plugin route profile ('plugin:<id>/<profile>').
|
||||
const [routeShown, setRouteShown] = useState(false)
|
||||
// Per-trip route visibility. `null` = the user has never said anything, which
|
||||
// is what lets the mobile map switch it on by default; an explicit false has to
|
||||
// survive every later map entry, and it used to be clobbered on each one (#2003).
|
||||
const routeStorageKey = tripId ? `trek:day-route:${tripId}` : null
|
||||
const [routeChoice, setRouteChoice] = useState<boolean | null>(() => {
|
||||
if (typeof window === 'undefined' || !routeStorageKey) return null
|
||||
const raw = window.localStorage.getItem(routeStorageKey)
|
||||
return raw === 'true' ? true : raw === 'false' ? false : null
|
||||
})
|
||||
const routeShown = routeChoice === true
|
||||
const setRouteShown = useCallback((v: boolean | ((prev: boolean) => boolean)) => {
|
||||
setRouteChoice(prev => {
|
||||
const next = typeof v === 'function' ? v(prev === true) : v
|
||||
if (routeStorageKey && typeof window !== 'undefined') {
|
||||
window.localStorage.setItem(routeStorageKey, String(next))
|
||||
}
|
||||
return next
|
||||
})
|
||||
}, [routeStorageKey])
|
||||
// The mobile map opens with the day's route drawn — a default, not a choice, so
|
||||
// it never overwrites an explicit off and is never written to storage itself.
|
||||
const autoShowRoute = useCallback(() => {
|
||||
setRouteChoice(prev => (prev === null ? true : prev))
|
||||
}, [])
|
||||
const [routeProfile, setRouteProfile] = useState<string>('driving')
|
||||
const [fitKey, setFitKey] = useState<number>(0)
|
||||
const initialFitTripId = useRef<number | null>(null)
|
||||
@@ -288,7 +316,10 @@ export function useTripPlanner() {
|
||||
}, [trip, places])
|
||||
|
||||
useEffect(() => {
|
||||
healthApi.features().then(f => setBookingImportAvailable(f.bookingImport)).catch(() => {})
|
||||
// The server runs the import when EITHER kitinerary or the LLM parser is
|
||||
// there (booking-import.service.ts), so gating the entry point on kitinerary
|
||||
// alone hid a working feature on LLM-only instances (#2007).
|
||||
healthApi.features().then(f => setBookingImportAvailable(f.bookingImport || f.aiParsing)).catch(() => {})
|
||||
}, [])
|
||||
|
||||
const connectionsStorageKey = tripId ? `trek:visible-connections:${tripId}` : null
|
||||
@@ -488,13 +519,14 @@ export function useTripPlanner() {
|
||||
setSelectedPlaceId(null)
|
||||
}, [])
|
||||
|
||||
const handleMapContextMenu = useCallback(async (e) => {
|
||||
const handleMapContextMenu = useCallback(async (e, dayId?: number | null) => {
|
||||
if (!can('place_edit', trip)) return
|
||||
e.originalEvent?.preventDefault()
|
||||
const { lat, lng } = e.latlng
|
||||
setPrefillCoords({ lat, lng })
|
||||
setEditingPlace(null)
|
||||
setEditingAssignmentId(null)
|
||||
setPlaceFormDayId(dayId ?? null)
|
||||
setShowPlaceForm(true)
|
||||
try {
|
||||
const { mapsApi } = await import('../../api/client')
|
||||
@@ -507,7 +539,7 @@ export function useTripPlanner() {
|
||||
|
||||
// Open the Add-Place form pre-filled from an OSM "explore" POI marker — all the
|
||||
// data already comes from the POI, so no reverse-geocode is needed.
|
||||
const openAddPlaceFromPoi = useCallback((poi: { lat: number; lng: number; name: string; address: string | null; website: string | null; phone: string | null; osm_id: string }) => {
|
||||
const openAddPlaceFromPoi = useCallback((poi: { lat: number; lng: number; name: string; address: string | null; website: string | null; phone: string | null; osm_id: string }, dayId?: number | null) => {
|
||||
if (!can('place_edit', trip)) return
|
||||
setPrefillCoords({
|
||||
lat: poi.lat,
|
||||
@@ -520,6 +552,7 @@ export function useTripPlanner() {
|
||||
})
|
||||
setEditingPlace(null)
|
||||
setEditingAssignmentId(null)
|
||||
setPlaceFormDayId(dayId ?? null)
|
||||
setShowPlaceForm(true)
|
||||
}, [trip])
|
||||
|
||||
@@ -548,6 +581,18 @@ export function useTripPlanner() {
|
||||
return { id: editingPlace.id }
|
||||
} else {
|
||||
const place = await tripActions.addPlace(tripId, data)
|
||||
// Added from inside a day? Then it belongs to that day. Without this the
|
||||
// place drops into the unplanned pool and, on mobile, into a different
|
||||
// screen entirely — which reads as "it wasn't saved" (#1998).
|
||||
if (place?.id && placeFormDayId != null) {
|
||||
try {
|
||||
await tripActions.assignPlaceToDay(tripId, placeFormDayId, place.id)
|
||||
updateRouteForDay(placeFormDayId)
|
||||
} catch (err: unknown) {
|
||||
// The place itself exists; only the day link failed.
|
||||
toast.error(err instanceof Error ? err.message : t('common.unknownError'))
|
||||
}
|
||||
}
|
||||
if (pendingFiles?.length > 0 && place?.id) {
|
||||
for (const file of pendingFiles) {
|
||||
const fd = new FormData()
|
||||
@@ -567,7 +612,7 @@ export function useTripPlanner() {
|
||||
// exist a moment ago (#1298), the same way the booking modals work.
|
||||
return place?.id ? { id: place.id } : undefined
|
||||
}
|
||||
}, [editingPlace, editingAssignmentId, tripId, toast, pushUndo])
|
||||
}, [editingPlace, editingAssignmentId, placeFormDayId, tripId, toast, pushUndo, updateRouteForDay])
|
||||
|
||||
// Open the place editor from any entry point (Places pool, inspector, map).
|
||||
// Times live per day-assignment, so when no day is in context resolve the
|
||||
@@ -576,6 +621,7 @@ export function useTripPlanner() {
|
||||
const openPlaceEditor = useCallback((place: Place, preferredAssignmentId: number | null = null) => {
|
||||
setEditingPlace(place)
|
||||
setEditingAssignmentId(preferredAssignmentId ?? resolvePoolAssignmentId(assignments, place.id))
|
||||
setPlaceFormDayId(null)
|
||||
setShowPlaceForm(true)
|
||||
}, [assignments])
|
||||
|
||||
@@ -985,6 +1031,7 @@ export function useTripPlanner() {
|
||||
showDayDetail, setShowDayDetail, dayDetailCollapsed, setDayDetailCollapsed,
|
||||
showPlaceForm, setShowPlaceForm, editingPlace, setEditingPlace,
|
||||
prefillCoords, setPrefillCoords, editingAssignmentId, setEditingAssignmentId,
|
||||
placeFormDayId, setPlaceFormDayId, reservationModalDayId, setReservationModalDayId,
|
||||
showTripForm, setShowTripForm, showMembersModal, setShowMembersModal,
|
||||
showReservationModal, setShowReservationModal, editingReservation, setEditingReservation,
|
||||
showBookingImport, setShowBookingImport, bookingImportAvailable,
|
||||
@@ -994,7 +1041,7 @@ export function useTripPlanner() {
|
||||
transportModalDayId, setTransportModalDayId,
|
||||
transportModalAutomated, setTransportModalAutomated, transitPrefill, setTransitPrefill, transitJourney, setTransitJourney,
|
||||
reservationPrefill, transportPrefill, importReviewActive, startImportReview, advanceImportReview,
|
||||
routeShown, setRouteShown, routeProfile, setRouteProfile, routeVias, fitKey, setFitKey,
|
||||
routeShown, setRouteShown, autoShowRoute, routeProfile, setRouteProfile, routeVias, fitKey, setFitKey,
|
||||
mobileSidebarOpen, setMobileSidebarOpen, mobilePlanScrollTopRef, mobilePlacesScrollTopRef,
|
||||
deletePlaceId, setDeletePlaceId, deletePlaceIds, setDeletePlaceIds,
|
||||
visibleConnections, toggleConnection, allConnectionsShown, toggleAllConnections, mapTransportDetail, setMapTransportDetail,
|
||||
|
||||
@@ -117,6 +117,10 @@ export function buildPlanner(overrides: Partial<TripPlanner> = {}): TripPlanner
|
||||
setEditingPlace: vi.fn(),
|
||||
prefillCoords: null,
|
||||
setPrefillCoords: vi.fn(),
|
||||
placeFormDayId: null,
|
||||
setPlaceFormDayId: vi.fn(),
|
||||
reservationModalDayId: null,
|
||||
setReservationModalDayId: vi.fn(),
|
||||
editingAssignmentId: null,
|
||||
setEditingAssignmentId: vi.fn(),
|
||||
showTripForm: false,
|
||||
@@ -156,6 +160,7 @@ export function buildPlanner(overrides: Partial<TripPlanner> = {}): TripPlanner
|
||||
|
||||
routeShown: false,
|
||||
setRouteShown: vi.fn(),
|
||||
autoShowRoute: vi.fn(),
|
||||
routeProfile: 'driving',
|
||||
setRouteProfile: vi.fn(),
|
||||
routeVias: [],
|
||||
|
||||
@@ -182,7 +182,7 @@ describe('MTripShell', () => {
|
||||
it('FE-MOB-SHELL-014: the map toggle draws the route and refits the selected day', () => {
|
||||
const { planner } = renderShell()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.mapView' }))
|
||||
expect(planner.setRouteShown).toHaveBeenCalledWith(true)
|
||||
expect(planner.autoShowRoute).toHaveBeenCalled()
|
||||
expect(planner.handleSelectDay).toHaveBeenCalledWith(11, false)
|
||||
expect(shellApi.view).toBe('map')
|
||||
// The timeline unmounts, the map layer stays warm underneath.
|
||||
@@ -196,13 +196,15 @@ describe('MTripShell', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.listView' }))
|
||||
expect(shellApi.view).toBe('plan')
|
||||
expect(screen.getByTestId('plan-timeline')).toBeInTheDocument()
|
||||
expect(spy(planner, 'setRouteShown')).toHaveBeenCalledTimes(1)
|
||||
// Entering the map defaults the route on once; leaving it leaves the choice alone.
|
||||
expect(spy(planner, 'autoShowRoute')).toHaveBeenCalledTimes(1)
|
||||
expect(spy(planner, 'setRouteShown')).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('FE-MOB-SHELL-016: the map toggle skips the refit while no day is selected', () => {
|
||||
const { planner } = renderShell({ selectedDayId: null } as Partial<TripPlanner>)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'mobileTrip.mapView' }))
|
||||
expect(planner.setRouteShown).toHaveBeenCalledWith(true)
|
||||
expect(planner.autoShowRoute).toHaveBeenCalled()
|
||||
expect(planner.handleSelectDay).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -238,7 +240,7 @@ describe('MTripShell', () => {
|
||||
const { planner } = renderShell()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Sun 3' }))
|
||||
expect(planner.handleSelectDay).toHaveBeenCalledWith(12, true)
|
||||
expect(planner.setRouteShown).not.toHaveBeenCalled()
|
||||
expect(planner.autoShowRoute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('FE-MOB-SHELL-021: tapping the active day opens the day sheet', () => {
|
||||
@@ -254,7 +256,7 @@ describe('MTripShell', () => {
|
||||
vi.mocked(planner.setRouteShown).mockClear()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Sun 3' }))
|
||||
expect(planner.handleSelectDay).toHaveBeenLastCalledWith(12, false)
|
||||
expect(planner.setRouteShown).toHaveBeenCalledWith(true)
|
||||
expect(planner.autoShowRoute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// #1962 — the phone lost the desktop sidebar's collapse chevron, so the map showed
|
||||
|
||||
Reference in New Issue
Block a user