import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue' import TierAddressMap, { type TierMapAddress } from '../TierAddressMap.vue' // Mock Leaflet (hoisted) : capture les marqueurs crees (un par adresse // geolocalisee) et leur handler `dragend`, et trace l'appel a fitBounds. const leafletState = vi.hoisted(() => ({ markers: [] as Array<{ _latlng: { lat: number, lng: number } dragend: (() => void) | null setLatLng: ReturnType }>, fitBoundsCalled: false, })) vi.mock('leaflet', () => { function makeMarker(lat: number, lng: number) { const marker = { _latlng: { lat, lng }, dragend: null as (() => void) | null, addTo: vi.fn().mockReturnThis(), bindPopup: vi.fn().mockReturnThis(), on: vi.fn((event: string, handler: () => void) => { if (event === 'dragend') marker.dragend = handler }), getLatLng: vi.fn(() => marker._latlng), setLatLng: vi.fn(), remove: vi.fn(), } return marker } const map = { fitBounds: vi.fn(() => { leafletState.fitBoundsCalled = true }), setView: vi.fn().mockReturnThis(), remove: vi.fn(), } const L = { map: vi.fn(() => map), tileLayer: vi.fn(() => ({ addTo: vi.fn() })), divIcon: vi.fn(() => ({})), latLngBounds: vi.fn((points: unknown) => points), marker: vi.fn((pos: [number, number]) => { const marker = makeMarker(pos[0], pos[1]) leafletState.markers.push(marker) return marker }), } return { default: L, ...L } }) vi.mock('leaflet/dist/leaflet.css', () => ({ default: {} })) // Mock controlable de l'API (PATCH des coordonnees au drag). const { patchMock } = vi.hoisted(() => ({ patchMock: vi.fn() })) // Auto-imports Nuxt/Vue utilises sans import explicite par le composant. vi.stubGlobal('useI18n', () => ({ t: (key: string) => key })) vi.stubGlobal('useApi', () => ({ patch: patchMock })) vi.stubGlobal('ref', ref) vi.stubGlobal('computed', computed) vi.stubGlobal('watch', watch) vi.stubGlobal('nextTick', nextTick) vi.stubGlobal('onMounted', onMounted) vi.stubGlobal('onBeforeUnmount', onBeforeUnmount) function address(over: Partial = {}): TierMapAddress { return { id: 1, latitude: '47.218', longitude: '-1.553', geoManual: false, title: '1 rue du Test, 44000 Nantes', typeLabel: 'Livraison', patchPath: '/client_addresses/1', ...over, } } beforeEach(() => { leafletState.markers = [] leafletState.fitBoundsCalled = false patchMock.mockReset() patchMock.mockResolvedValue({}) }) describe('TierAddressMap — marqueurs', () => { it('pose un marqueur par adresse geolocalisee et liste a part celles sans coordonnees', async () => { const wrapper = mount(TierAddressMap, { props: { addresses: [ address({ id: 1, patchPath: '/client_addresses/1' }), address({ id: 2, latitude: '48.85', longitude: '2.35', patchPath: '/client_addresses/2' }), address({ id: 3, latitude: null, longitude: null, patchPath: '/client_addresses/3', title: '5 rue Sans Geo' }), ], }, }) await flushPromises() // import dynamique de Leaflet + montage carte // Deux adresses geolocalisees -> deux marqueurs ; la troisieme (sans // coords) n'est pas posee sur la carte mais listee a part. expect(leafletState.markers).toHaveLength(2) expect(leafletState.fitBoundsCalled).toBe(true) const missing = wrapper.findAll('[data-testid="tier-map-missing"]') expect(missing).toHaveLength(1) expect(missing[0]?.text()).toContain('5 rue Sans Geo') expect(wrapper.find('[data-testid="tier-map"]').exists()).toBe(true) }) it('affiche un etat vide quand aucune adresse n\'est geolocalisee', async () => { const wrapper = mount(TierAddressMap, { props: { addresses: [address({ latitude: null, longitude: null })] }, }) await flushPromises() expect(leafletState.markers).toHaveLength(0) expect(wrapper.find('[data-testid="tier-map"]').exists()).toBe(false) expect(wrapper.findAll('[data-testid="tier-map-missing"]')).toHaveLength(1) }) }) describe('TierAddressMap — pin ajustable (RG-6.08)', () => { it('PATCH les coordonnees + geoManual=true au drag quand editable', async () => { const wrapper = mount(TierAddressMap, { props: { addresses: [address({ id: 7, patchPath: '/client_addresses/7' })], editable: true }, }) await flushPromises() const marker = leafletState.markers[0] expect(marker?.dragend).not.toBeNull() // L'utilisateur depose le pin ailleurs (entree de site mal geocodee). marker!._latlng = { lat: 48.1234567, lng: -1.6543217 } marker!.dragend?.() await flushPromises() expect(patchMock).toHaveBeenCalledWith( '/client_addresses/7', { latitude: '48.1234567', longitude: '-1.6543217', geoManual: true }, { toast: false }, ) expect(wrapper.emitted('updated')?.[0]?.[0]).toEqual({ id: 7, latitude: '48.1234567', longitude: '-1.6543217', }) }) it('ne rend pas les marqueurs draggables (pas de PATCH) en lecture seule', async () => { mount(TierAddressMap, { props: { addresses: [address()], editable: false }, }) await flushPromises() // Aucun handler dragend cable -> pas de drag possible. expect(leafletState.markers[0]?.dragend).toBeNull() }) })