From 1984e151c17f4e24e3f848fd6d26bdcdebdfa8e4 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 29 Aug 2026 15:41:11 +0200 Subject: [PATCH] fix(map): put the CARTO key back on the template the settings preview draws The map settings fields hold what the user is editing rather than what useTileUrl already resolved, so the preview received a bare template with no key on it. resolveTileUrl then read it as a keyless CARTO url and did what it is meant to do with one: fall back to the app default. The preview drew OpenFreeMap while the fields said CARTO, whether or not a key was saved. Same reason the tab crashed for anyone on a CARTO basemap even after entering a key, which is the loop that made this hard to escape: the key is entered on the page the fallback had taken down. FE-COMP-MAP-033b pins the template the preview is handed, with and without a key. --- .../Settings/MapSettingsTab.test.tsx | 28 +++++++++++++++++-- .../components/Settings/MapSettingsTab.tsx | 8 +++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/client/src/components/Settings/MapSettingsTab.test.tsx b/client/src/components/Settings/MapSettingsTab.test.tsx index f1c5c8558..e366cb18f 100644 --- a/client/src/components/Settings/MapSettingsTab.test.tsx +++ b/client/src/components/Settings/MapSettingsTab.test.tsx @@ -8,10 +8,11 @@ import { buildUser, buildSettings } from '../../../tests/helpers/factories'; import { ToastContainer } from '../shared/Toast'; import MapSettingsTab from './MapSettingsTab'; -// Mock MapView to avoid Leaflet DOM issues in jsdom +// Mock MapView to avoid Leaflet DOM issues in jsdom. tileUrl is surfaced because +// the preview has to draw the basemap being configured, key included. vi.mock('../Map/MapView', () => ({ - MapView: ({ onMapClick }: { onMapClick?: (info: { latlng: { lat: number; lng: number } }) => void }) => ( -
onMapClick?.({ latlng: { lat: 51.5, lng: -0.1 } })} /> + MapView: ({ onMapClick, tileUrl }: { onMapClick?: (info: { latlng: { lat: number; lng: number } }) => void; tileUrl?: string }) => ( +
onMapClick?.({ latlng: { lat: 51.5, lng: -0.1 } })} /> ), })); @@ -394,6 +395,27 @@ describe('MapSettingsTab – CARTO key', () => { expect(updateSettings).toHaveBeenCalledWith(expect.objectContaining({ carto_api_key: 'demo-key' })); }); + it('FE-COMP-MAP-033b: the preview draws the CARTO basemap being configured, not the app default', async () => { + // The fields hold what the user is editing, not what useTileUrl resolved, so + // the key has to be put back on before the preview reads the template. + // Without it the preview resolves a keyless CARTO url, silently falls back to + // the default vector style, and shows a basemap nobody chose. + const user = userEvent.setup(); + seedStore(useSettingsStore, { + settings: buildSettings({ map_tile_url: CARTO_DARK_TILES, carto_api_key: 'demo-key' }), + }); + render(); + + await waitFor(() => { + expect(screen.getByTestId('map-view').getAttribute('data-tile-url')).toContain('key=demo-key'); + }); + + await user.clear(cartoInput()); + await waitFor(() => { + expect(screen.getByTestId('map-view').getAttribute('data-tile-url')).not.toContain('key='); + }); + }); + it('FE-COMP-MAP-034: a CARTO template without a key explains the watermark until one is typed', async () => { const user = userEvent.setup(); seedStore(useSettingsStore, { diff --git a/client/src/components/Settings/MapSettingsTab.tsx b/client/src/components/Settings/MapSettingsTab.tsx index 4476508fa..aaa7f74ee 100644 --- a/client/src/components/Settings/MapSettingsTab.tsx +++ b/client/src/components/Settings/MapSettingsTab.tsx @@ -11,6 +11,7 @@ import ErrorBoundary from '../shared/ErrorBoundary' import { GlMapPreviewMapbox, GlMapPreviewMaplibre } from '../Map/glLazy' import Section from './Section' import ToggleSwitch from './ToggleSwitch' +import { withTileApiKey } from '../../utils/tileUrl' import type { Place } from '../../types' import { MAPBOX_DEFAULT_STYLE, @@ -473,7 +474,12 @@ export default function MapSettingsTab(): React.ReactElement { onMarkerClick: null, onMapClick: null, onMapContextMenu: null, - tileUrl: mapTileUrl, + // With the key on it, or the preview resolves the template as a + // keyless CARTO one and quietly shows the app default instead of + // the basemap being configured. The fields hold what the user is + // editing rather than what useTileUrl already resolved, so the key + // has to be put back on here. + tileUrl: withTileApiKey(mapTileUrl, cartoKey), fitKey: null, dayOrderMap: [], leftWidth: 0,