diff --git a/client/src/components/Roadtrip/RoadtripStopPopup.test.tsx b/client/src/components/Roadtrip/RoadtripStopPopup.test.tsx index 8ba54af35..f9764950e 100644 --- a/client/src/components/Roadtrip/RoadtripStopPopup.test.tsx +++ b/client/src/components/Roadtrip/RoadtripStopPopup.test.tsx @@ -373,6 +373,27 @@ describe('editing a roadtrip stop', () => { fireEvent.click(screen.getByRole('button', { name: 'Save' })) await waitFor(() => expect(onSaveNight).toHaveBeenCalledWith({ endDayId: 5, checkIn: '16:00', checkOut: '10:00' })) }) + + it('FE-ROADTRIP-STOPPOPUP-034: a new draft handed to the open popup shows the answers of that stop, not the previous one', async () => { + const onSave = vi.fn() + const onSaveNight = vi.fn() + const nights = { days: [{ id: 4, number: 1, date: null }, { id: 5, number: 2, date: null }], defaultEndDayId: 5 } + const hotelEdit = draft({ poi: poi({ category: 'hotel', name: 'Hotel Adlon' }), overnight: nights, editing: { placeId: 7, stopType: 'hotel', dwellMinutes: 30, accommodationId: 8, checkIn: '16:00', checkOut: '10:00' } }) + const fuelEdit = draft({ editing: { placeId: 9, stopType: 'charging', dwellMinutes: 20 } }) + const ui = (d: RoadtripStopDraft) => + + const { rerender } = render(ui(hotelEdit)) + expect(screen.getByRole('textbox', { name: 'Check-in' })).toHaveValue('16:00') + + rerender(ui(fuelEdit)) + expect(screen.getByRole('button', { name: /Charging/ })).toHaveAttribute('aria-pressed', 'true') + fireEvent.click(screen.getByRole('button', { name: 'Save' })) + await waitFor(() => expect(onSave).toHaveBeenCalledWith({ stopType: 'charging', dwellMinutes: 20 })) + + rerender(ui(draft({ poi: poi({ category: 'hotel', name: 'Hotel Bristol' }), arrivalTime: '15:45', overnight: nights }))) + expect(screen.getByRole('button', { name: 'Overnight' })).toHaveAttribute('aria-pressed', 'true') + expect(screen.getByRole('textbox', { name: 'Check-in' })).toHaveValue('15:45') + }) }) diff --git a/client/src/components/Roadtrip/RoadtripStopPopup.tsx b/client/src/components/Roadtrip/RoadtripStopPopup.tsx index 5da159da7..b6f9ee065 100644 --- a/client/src/components/Roadtrip/RoadtripStopPopup.tsx +++ b/client/src/components/Roadtrip/RoadtripStopPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { ParkingSquare, Hourglass, AlertTriangle, BedDouble } from 'lucide-react' import Modal from '../shared/Modal' import CustomTimePicker from '../shared/CustomTimePicker' @@ -69,6 +69,12 @@ interface RoadtripStopPopupProps { * which neither column shows. This asks the two questions that matter (what kind of stop, * how long) and says where it will land, with a way out to the full form for the rest. */ +const nightByDefault = (draft: RoadtripStopDraft | null): boolean => + draft?.editing ? !!draft.editing.accommodationId : draft?.poi.category === 'hotel' + +const checkInByDefault = (draft: RoadtripStopDraft | null): string => + draft?.editing?.checkIn || draft?.arrivalTime || '' + export default function RoadtripStopPopup({ draft, duplicateName, onClose, onSave, onSaveNight, onMoreDetails, }: RoadtripStopPopupProps): React.ReactElement | null { @@ -80,12 +86,20 @@ export default function RoadtripStopPopup({ // /trips//www.hotel.de instead of leaving the app. The helper adds the // scheme for a bare host and drops anything that is not http(s). const websiteHref = safeExternalHref(draft?.poi.website) - const { stopType, dwell, setDwell, saving, setSaving, kind, pickKind } = useStopDraftForm(draft) + const { stopType, dwell, setDwell, saving, setSaving, kind, pickKind, seed } = useStopDraftForm(draft) // A hotel is a night by default and a campsite a pause, which is what each already // means everywhere else — but both offer the other, because a campsite is somewhere // people sleep and a hotel is somewhere people stop for lunch. - const [night, setNight] = useState(draft?.editing ? !!draft.editing.accommodationId : draft?.poi.category === 'hotel') - const [checkIn, setCheckIn] = useState(draft?.editing?.checkIn || draft?.arrivalTime || '') + const [night, setNight] = useState(() => nightByDefault(draft)) + const [checkIn, setCheckIn] = useState(() => checkInByDefault(draft)) + + // The popup stays mounted while the draft is swapped for another one, so every + // answer is seeded again from the new draft, the way the phone sheet does it. + useEffect(() => { + seed(draft) + setNight(nightByDefault(draft)) + setCheckIn(checkInByDefault(draft)) + }, [draft, seed]) if (!draft) return null const overnight = draft.overnight diff --git a/client/src/components/Roadtrip/useStopDraftForm.ts b/client/src/components/Roadtrip/useStopDraftForm.ts index 6328e6e6a..4458fedc7 100644 --- a/client/src/components/Roadtrip/useStopDraftForm.ts +++ b/client/src/components/Roadtrip/useStopDraftForm.ts @@ -24,8 +24,8 @@ export function stopDraftDefaults(draft: RoadtripStopDraft | null | undefined): /** * The two questions a stop found along the drive answers before it goes onto the trip, * what kind it is and how long it takes, behind the desktop stop popup and the phone's - * draft sheet. `initialDraft` gives the first answers; the phone seeds them again for - * every draft it is handed. + * draft sheet. `initialDraft` gives the first answers; both seed them again with + * `seed` for every draft they are handed while they stay open. */ export function useStopDraftForm(initialDraft: RoadtripStopDraft | null) { const [stopType, setStopType] = useState(() => stopDraftDefaults(initialDraft).stopType);