From cee496c469ffaf2d51d014351a020e63232d7156 Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 9 Oct 2026 17:38:48 +0200 Subject: [PATCH] fix(places): coordinates pasted on the phone survive the next search pick On the phone place form, pasting a "lat, lng" pair into the latitude field filled both coordinates, but a later search pick whose place came without a position emptied them again whenever an earlier pick had filled them. On the desktop the pasted position stays. Cause: the phone's paste handler wrote lat and lng with setForm directly, so autoFilledRef still listed them as written by the previous pick, and mergeResult clears such fields when the new pick says nothing about them. The desktop goes through handleChange, which marks a field as typed by hand. Fix: the phone's paste handler goes through handleChange for both halves, the same as the desktop dialog. Red test: tests/unit/mobile/trip/MPlaceEditSheet.test.tsx FE-MOB-PLEDIT-047 failed on the unfixed code with "expect(element).toHaveValue(35.7101) Expected the element to have value: 35.7101 Received: (empty)". No existing assertions changed. --- .../screens/trip/sheets/MPlaceEditSheet.tsx | 5 +++- .../unit/mobile/trip/MPlaceEditSheet.test.tsx | 30 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/client/src/mobile/screens/trip/sheets/MPlaceEditSheet.tsx b/client/src/mobile/screens/trip/sheets/MPlaceEditSheet.tsx index 4a2ca9ca3..ee6710c4c 100644 --- a/client/src/mobile/screens/trip/sheets/MPlaceEditSheet.tsx +++ b/client/src/mobile/screens/trip/sheets/MPlaceEditSheet.tsx @@ -165,7 +165,10 @@ export default function MPlaceEditSheet({ planner, onOpenExpense }: MPlaceEditSh const pair = parseCoordinatePair(e.clipboardData.getData('text')) if (pair) { e.preventDefault() - setForm(prev => ({ ...prev, lat: pair[0], lng: pair[1] })) + // Through handleChange, as on the desktop: a pasted position is the user's, + // so the next search pick must not clear it. + handleChange('lat', pair[0]) + handleChange('lng', pair[1]) } } diff --git a/client/tests/unit/mobile/trip/MPlaceEditSheet.test.tsx b/client/tests/unit/mobile/trip/MPlaceEditSheet.test.tsx index b4c05e383..1a5df245c 100644 --- a/client/tests/unit/mobile/trip/MPlaceEditSheet.test.tsx +++ b/client/tests/unit/mobile/trip/MPlaceEditSheet.test.tsx @@ -11,7 +11,7 @@ import { server } from '../../../helpers/msw/server' import { resetAllStores, seedStore } from '../../../helpers/store' import { fireEvent, render, screen, waitFor } from '../../../helpers/render' -// FE-MOB-PLEDIT-001 to FE-MOB-PLEDIT-046, plus the 009b, 025b and 029b variants +// FE-MOB-PLEDIT-001 to FE-MOB-PLEDIT-047, plus the 009b, 025b and 029b variants // planner.t echoes the key, so every label/placeholder is asserted as its key. const CATEGORIES = [ @@ -189,6 +189,34 @@ describe('MPlaceEditSheet', () => { expect(screen.getByPlaceholderText('places.formLng')).toHaveValue('139.6917') }) + it('FE-MOB-PLEDIT-047: a pasted "lat, lng" pair counts as typed, so a later pick without a position keeps it', async () => { + let searches = 0 + server.use( + http.post('/api/maps/search', () => { + searches += 1 + return HttpResponse.json(searches === 1 + ? { source: 'osm', places: [{ name: 'Ueno Koen', address: 'Taito', lat: 35.7, lng: 139.7 }] } + : { source: 'osm', places: [{ name: 'Ameyoko', address: 'Ueno' }] }) + }), + ) + setup() + const search = screen.getByPlaceholderText('places.mapsSearchPlaceholder') + fireEvent.change(search, { target: { value: 'ueno koen' } }) + fireEvent.click(screen.getByRole('button', { name: 'common.search' })) + fireEvent.click(await screen.findByText('Ueno Koen')) + + const lat = screen.getByPlaceholderText('places.formLat') + fireEvent.paste(lat, { clipboardData: { getData: () => '35.7101, 139.7745' } }) + + fireEvent.change(search, { target: { value: 'ameyoko' } }) + fireEvent.click(screen.getByRole('button', { name: 'common.search' })) + fireEvent.click(await screen.findByText('Ameyoko')) + + expect(nameField()).toHaveValue('Ameyoko') + expect(lat).toHaveValue('35.7101') + expect(screen.getByPlaceholderText('places.formLng')).toHaveValue('139.7745') + }) + it('FE-MOB-PLEDIT-013: an unparseable paste is left to the browser', () => { setup() const lat = screen.getByPlaceholderText('places.formLat')