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,