From 29fc586930ae53ef1803bc91b59e1c1011db2d64 Mon Sep 17 00:00:00 2001 From: Maurice Date: Wed, 9 Sep 2026 18:07:06 +0200 Subject: [PATCH] test(maps): pin the coordinates a suggestion carries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The half of the change that saves the pick from a text search for its own label was covered by nothing. MAPS-AUTO-010 reads `source` and stops there; the client test writes the coordinates into its own mock and then asserts them. Delete `lat: p.lat, lng: p.lng` from the service and both stay green, while the fallback quietly goes back to searching for "Tokio Hauptbahnhof, 東京駅丸の内駅舎" and taking whatever comes back first. So the server test now asserts the coordinates on the wire for both indexes, and the mobile sheet — which carries a copy of the same fallback and had no test for it at all — gets one for the fallback and one for the source mark. --- .../unit/mobile/trip/PlPlaceSearch.test.tsx | 41 +++++++++++++++++++ .../tests/unit/nest/maps.autocomplete.test.ts | 12 ++++++ 2 files changed, 53 insertions(+) diff --git a/client/tests/unit/mobile/trip/PlPlaceSearch.test.tsx b/client/tests/unit/mobile/trip/PlPlaceSearch.test.tsx index bf755e99d..29823408f 100644 --- a/client/tests/unit/mobile/trip/PlPlaceSearch.test.tsx +++ b/client/tests/unit/mobile/trip/PlPlaceSearch.test.tsx @@ -132,6 +132,47 @@ describe('PlPlaceSearch', () => { expect(onPick).toHaveBeenLastCalledWith(expect.objectContaining({ name: 'Louvre Museum', lat: '48.8606' })) }) + it('FE-MOB-PLSRCH-005b: an OpenStreetMap row keeps its own coordinates instead of searching for its label', async () => { + // The layer's second line is the name written on the building, not an + // address, so joining the two asks a question nobody typed — and whatever + // came back first was taken as the place the user had already picked. + server.use( + recordAutocomplete([{ + placeId: 'node:9712313', + mainText: 'Tokio Hauptbahnhof', + secondaryText: '東京駅丸の内駅舎', + source: 'openstreetmap', + lat: 35.6811816, + lng: 139.76598265, + }]), + http.get('/api/maps/details/:placeId', () => HttpResponse.json({ place: null, disabled: true })), + recordSearch(), + ) + const { input, onPick } = setup() + fireEvent.change(input, { target: { value: 'Tok' } }) + fireEvent.click(await screen.findByText('Tokio Hauptbahnhof')) + + await waitFor(() => expect(onPick).toHaveBeenCalledTimes(2)) + expect(searchBodies).toHaveLength(0) + expect(onPick).toHaveBeenLastCalledWith(expect.objectContaining({ + name: 'Tokio Hauptbahnhof', lat: '35.6811816', lng: '139.76598265', + })) + }) + + it('FE-MOB-PLSRCH-005c: a suggestion says which index answered', async () => { + // Both indexes answer the keystroke path at once, so the name the response + // carries for the whole list is true of the call and wrong for half its rows. + server.use(recordAutocomplete([ + { ...SUGGESTION, source: 'trek-places' }, + { placeId: 'node:1', mainText: 'Louvre Palace', secondaryText: 'Palais du Louvre', source: 'openstreetmap' }, + ])) + const { input } = setup() + fireEvent.change(input, { target: { value: 'Lou' } }) + + expect(await screen.findByText('TREK')).toBeInTheDocument() + expect(await screen.findByText('OpenStreetMap')).toBeInTheDocument() + }) + it('FE-MOB-PLSRCH-006: also falls back when the details hop answers without coordinates', async () => { server.use( recordAutocomplete(), diff --git a/server/tests/unit/nest/maps.autocomplete.test.ts b/server/tests/unit/nest/maps.autocomplete.test.ts index b0705216d..a51cc808d 100644 --- a/server/tests/unit/nest/maps.autocomplete.test.ts +++ b/server/tests/unit/nest/maps.autocomplete.test.ts @@ -209,6 +209,18 @@ describe('MapsService.autocompletePlaces', () => { expect(suggestions.map(s => s.source)).toEqual(['trek-places', 'openstreetmap']); }); + it('MAPS-AUTO-011: a suggestion carries the coordinates the index already gave, so the pick needs no second hop', async () => { + mockSearch.mockResolvedValue([hit(), osmHit()]); + + const { suggestions } = await make().autocompletePlaces(1, INPUT); + + // Without these the client, when the details lookup cannot answer, searches + // for the label instead — and for a layer row that label is a name plus its + // local spelling, which is not a query anybody typed. + expect(suggestions[0]).toMatchObject({ lat: 54.0879, lng: 12.1408 }); + expect(suggestions[1]).toMatchObject({ lat: 35.6811816, lng: 139.76598265 }); + }); + it('MAPS-AUTO-007: the keystroke never leaves for the index while the admin has it off', async () => { mockSearch.mockResolvedValue([hit()]);