de4aaa1d64
Ajoute la géolocalisation aux adresses Client et Fournisseur, socle de la tournée commerciale (M6 field-sales). Back : - migration : latitude/longitude NUMERIC(10,7), geo_manual BOOLEAN, geocoded_at TIMESTAMPTZ sur client_address et supplier_address (+ COMMENT ON COLUMN FR) - GeolocatableAddressInterface (Shared/Domain/Contract) implémenté par les deux entités ; bornes WGS84 validées (Range -90/90, -180/180, messages FR) - GeocoderInterface + BanGeocoder (api-adresse.data.gouv.fr), branché via AddressGeocoder dans les processors ; géocodage auto au create/update - RG-6.08 : geo_manual=true fige les coordonnées (pas de réécriture auto) - symfony/http-client passe en dépendance de production Front : - AddressGeoPin (Leaflet + OSM) : marqueur déplaçable -> PATCH lat/lng + geoManual=true, bouton Re-géocoder, badges « à géolocaliser » / « pin manuel » - intégration dans les blocs adresse Client et Fournisseur Tests : PHPUnit (géocodage create, non-réécriture RG-6.08, mapping BAN, bornes) + Vitest (drag du pin, badges, re-géocodage).
152 lines
5.2 KiB
TypeScript
152 lines
5.2 KiB
TypeScript
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 AddressGeoPin from '../AddressGeoPin.vue'
|
|
|
|
// Mock Leaflet (hoisted) : capture le handler `dragend` et pilote la position
|
|
// renvoyee par getLatLng — permet de simuler un drag du marqueur sans DOM reel.
|
|
const leafletState = vi.hoisted(() => ({
|
|
dragendHandler: null as (() => void) | null,
|
|
markerPosition: { lat: 0, lng: 0 },
|
|
}))
|
|
|
|
vi.mock('leaflet', () => {
|
|
const marker = {
|
|
addTo: vi.fn().mockReturnThis(),
|
|
on: vi.fn((event: string, handler: () => void) => {
|
|
if (event === 'dragend') {
|
|
leafletState.dragendHandler = handler
|
|
}
|
|
}),
|
|
getLatLng: vi.fn(() => leafletState.markerPosition),
|
|
setLatLng: vi.fn(),
|
|
}
|
|
const map = {
|
|
setView: vi.fn().mockReturnThis(),
|
|
panTo: vi.fn(),
|
|
remove: vi.fn(),
|
|
}
|
|
const L = {
|
|
map: vi.fn(() => map),
|
|
tileLayer: vi.fn(() => ({ addTo: vi.fn() })),
|
|
divIcon: vi.fn(() => ({})),
|
|
marker: vi.fn(() => marker),
|
|
}
|
|
return { default: L, ...L }
|
|
})
|
|
vi.mock('leaflet/dist/leaflet.css', () => ({ default: {} }))
|
|
|
|
// Mock controlable du geocodage BAN (bouton « Re-geocoder »).
|
|
const { geocodeMock } = vi.hoisted(() => ({ geocodeMock: vi.fn() }))
|
|
vi.mock('~/shared/composables/useAddressAutocomplete', () => ({
|
|
useAddressAutocomplete: () => ({ geocode: geocodeMock }),
|
|
}))
|
|
|
|
// Auto-imports Nuxt/Vue utilises sans import explicite par le composant.
|
|
vi.stubGlobal('useI18n', () => ({ t: (key: string) => key }))
|
|
vi.stubGlobal('ref', ref)
|
|
vi.stubGlobal('computed', computed)
|
|
vi.stubGlobal('watch', watch)
|
|
vi.stubGlobal('nextTick', nextTick)
|
|
vi.stubGlobal('onMounted', onMounted)
|
|
vi.stubGlobal('onBeforeUnmount', onBeforeUnmount)
|
|
|
|
interface PinProps {
|
|
latitude?: string | null
|
|
longitude?: string | null
|
|
geoManual?: boolean
|
|
geocodeQuery?: string | null
|
|
readonly?: boolean
|
|
}
|
|
|
|
function mountPin(props: PinProps = {}) {
|
|
return mount(AddressGeoPin, {
|
|
props: {
|
|
latitude: null,
|
|
longitude: null,
|
|
geoManual: false,
|
|
geocodeQuery: '1 rue du Test, 86100 Châtellerault',
|
|
...props,
|
|
},
|
|
global: {
|
|
stubs: { MalioButton: true },
|
|
},
|
|
})
|
|
}
|
|
|
|
beforeEach(() => {
|
|
leafletState.dragendHandler = null
|
|
geocodeMock.mockReset()
|
|
})
|
|
|
|
describe('AddressGeoPin — adresse sans coordonnees', () => {
|
|
it('affiche le badge « a geolocaliser » et aucune carte', () => {
|
|
const wrapper = mountPin()
|
|
|
|
expect(wrapper.find('[data-testid="geo-badge-missing"]').exists()).toBe(true)
|
|
expect(wrapper.find('[data-testid="geo-map"]').exists()).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('AddressGeoPin — drag du marqueur (RG-6.08)', () => {
|
|
it('emet les coordonnees corrigees avec geoManual=true au dragend', async () => {
|
|
const wrapper = mountPin({ latitude: '46.5802596', longitude: '0.3404333' })
|
|
await flushPromises() // import dynamique de Leaflet + montage carte
|
|
|
|
expect(leafletState.dragendHandler).not.toBeNull()
|
|
|
|
// L'utilisateur depose le pin ailleurs (lieu-dit mal geocode).
|
|
leafletState.markerPosition = { lat: 48.1234567, lng: -1.6543217 }
|
|
leafletState.dragendHandler?.()
|
|
|
|
const emitted = wrapper.emitted('update:coords')
|
|
expect(emitted).toHaveLength(1)
|
|
expect(emitted?.[0]?.[0]).toEqual({
|
|
latitude: '48.1234567',
|
|
longitude: '-1.6543217',
|
|
geoManual: true,
|
|
})
|
|
})
|
|
|
|
it('affiche le badge « pin manuel » quand geoManual est vrai', () => {
|
|
const wrapper = mountPin({ latitude: '46.58', longitude: '0.34', geoManual: true })
|
|
|
|
expect(wrapper.find('[data-testid="geo-badge-manual"]').exists()).toBe(true)
|
|
expect(wrapper.find('[data-testid="geo-badge-missing"]').exists()).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('AddressGeoPin — re-geocodage depuis l\'adresse', () => {
|
|
it('emet la position BAN avec geoManual=false (le back refera autorite au save)', async () => {
|
|
geocodeMock.mockResolvedValueOnce({ latitude: '46.5802596', longitude: '0.3404333' })
|
|
const wrapper = mountPin()
|
|
|
|
await wrapper.find('[data-testid="geo-regeocode"]').trigger('click')
|
|
await flushPromises()
|
|
|
|
expect(geocodeMock).toHaveBeenCalledWith('1 rue du Test, 86100 Châtellerault')
|
|
expect(wrapper.emitted('update:coords')?.[0]?.[0]).toEqual({
|
|
latitude: '46.5802596',
|
|
longitude: '0.3404333',
|
|
geoManual: false,
|
|
})
|
|
})
|
|
|
|
it('signale l\'echec sans emettre quand la BAN ne trouve rien', async () => {
|
|
geocodeMock.mockResolvedValueOnce(null)
|
|
const wrapper = mountPin()
|
|
|
|
await wrapper.find('[data-testid="geo-regeocode"]').trigger('click')
|
|
await flushPromises()
|
|
|
|
expect(wrapper.emitted('update:coords')).toBeUndefined()
|
|
expect(wrapper.find('[data-testid="geo-regeocode-failed"]').exists()).toBe(true)
|
|
})
|
|
|
|
it('masque le bouton en lecture seule', () => {
|
|
const wrapper = mountPin({ readonly: true })
|
|
|
|
expect(wrapper.find('[data-testid="geo-regeocode"]').exists()).toBe(false)
|
|
})
|
|
})
|