From bbc32310e9260322e337af6d5d77563371da695b Mon Sep 17 00:00:00 2001 From: Maurice Date: Sun, 6 Sep 2026 12:59:34 +0200 Subject: [PATCH] test(journey): pin the viewless-map window the photo layer fell into The Leaflet mock had no getCenter, so the new guard bailed out of every draw and FE-COMP-JOURNEYMAP-046/047 went red. Give the mock a loaded map by default and add FE-COMP-JOURNEYMAP-050, which takes the view away for exactly the window #2254 hit: render must survive it drawing nothing, and the moveend listener must still put the thumbnail up once fitBounds lands. --- .../components/Journey/JourneyMap.test.tsx | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/client/src/components/Journey/JourneyMap.test.tsx b/client/src/components/Journey/JourneyMap.test.tsx index 5178f2909..3fecd4748 100644 --- a/client/src/components/Journey/JourneyMap.test.tsx +++ b/client/src/components/Journey/JourneyMap.test.tsx @@ -1,4 +1,4 @@ -// FE-COMP-JOURNEYMAP-001 to FE-COMP-JOURNEYMAP-049 +// FE-COMP-JOURNEYMAP-001 to FE-COMP-JOURNEYMAP-050 vi.mock('../../api/websocket', () => ({ connect: vi.fn(), @@ -27,6 +27,10 @@ vi.mock('leaflet', () => { setView: vi.fn(), flyTo: vi.fn(), getZoom: vi.fn(() => 10), + // Leaflet throws "Set map center and zoom first." out of these until the map + // has a view; the loaded map is the default here, FE-COMP-JOURNEYMAP-050 + // takes it away for the window that #2254 fell into. + getCenter: vi.fn(() => ({ lat: 0, lng: 0 })), zoomIn: vi.fn(), zoomOut: vi.fn(), // The photo layer subscribes to zoom/pan and clusters in screen space (#1614). @@ -58,6 +62,7 @@ import { render, act, fireEvent, waitFor } from '../../../tests/helpers/render'; import { resetAllStores, seedStore } from '../../../tests/helpers/store'; import { useSettingsStore } from '../../store/settingsStore'; import { buildSettings } from '../../../tests/helpers/factories'; +import type { Mock } from 'vitest'; import L from 'leaflet'; import JourneyMap from './JourneyMap'; import type { JourneyMapHandle } from './JourneyMap'; @@ -693,4 +698,47 @@ describe('JourneyMap', () => { expect(html).toHaveLength(1); expect(html[0]).not.toMatch(/>\d+ { + const noView = () => { throw new Error('Set map center and zoom first.'); }; + let hasView = false; + const photoHtml = () => + vi.mocked(L.divIcon).mock.calls.map(c => String(c[0]?.html)).filter(h => h.includes('/uploads/')); + + // The mock hands out one shared map instance, so it can be reached — and + // stripped of its view — before the component ever builds one. Typed down to + // the three spies this needs: L.map's signature promises a real Leaflet map. + type MapProbe = { getCenter: Mock; latLngToContainerPoint: Mock; on: Mock }; + L.map(document.createElement('div')); + const map = mockedMap() as MapProbe; + vi.mocked(L.map).mockClear(); + map.getCenter.mockImplementation(() => (hasView ? { lat: 0, lng: 0 } : noView())); + map.latLngToContainerPoint.mockImplementation(() => (hasView ? { x: 0, y: 0 } : noView())); + + try { + // Rendering must survive the viewless window, and draw nothing in it. + expect(() => + render( + , + ), + ).not.toThrow(); + expect(photoHtml()).toHaveLength(0); + + // The deferred fitBounds lands: the listener this effect registered redraws. + hasView = true; + const redraw = map.on.mock.calls.find(c => c[0] === 'moveend')?.[1] as () => void; + expect(redraw).toBeTypeOf('function'); + act(() => { redraw(); }); + + expect(photoHtml()).toHaveLength(1); + expect(photoHtml()[0]).toContain('/uploads/late.jpg'); + } finally { + map.getCenter.mockImplementation(() => ({ lat: 0, lng: 0 })); + map.latLngToContainerPoint.mockImplementation(() => ({ x: 0, y: 0 })); + } + }); });