#49 fix: marcador de producto sin imagen visible en el mapa de provenance
This commit is contained in:
224
tests/unit/components/provenance/ProvenanceMap.spec.js
Normal file
224
tests/unit/components/provenance/ProvenanceMap.spec.js
Normal file
@@ -0,0 +1,224 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { nextTick } from 'vue'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import ProvenanceMap from '@/components/provenance/ProvenanceMap.vue'
|
||||
import vuetify from '@/plugins/vuetify'
|
||||
|
||||
const leaflet = vi.hoisted(() => {
|
||||
const markers = []
|
||||
const polylines = []
|
||||
const layers = []
|
||||
function makeLayer (type) {
|
||||
const layer = {
|
||||
type,
|
||||
addTo: vi.fn(function () { return this }),
|
||||
on: vi.fn(),
|
||||
bindTooltip: vi.fn(function () { return this }),
|
||||
remove: vi.fn(),
|
||||
}
|
||||
layers.push(layer)
|
||||
return layer
|
||||
}
|
||||
const map = {
|
||||
setView: vi.fn(function () { return this }),
|
||||
fitBounds: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
invalidateSize: vi.fn(),
|
||||
on: vi.fn(),
|
||||
}
|
||||
const bounds = { extend: vi.fn(), isValid: vi.fn(() => true) }
|
||||
return {
|
||||
markers,
|
||||
polylines,
|
||||
layers,
|
||||
map,
|
||||
bounds,
|
||||
L: {
|
||||
map: vi.fn(() => map),
|
||||
tileLayer: vi.fn(() => makeLayer('tileLayer')),
|
||||
marker: vi.fn((pos, opts) => {
|
||||
const marker = makeLayer('marker')
|
||||
marker.pos = pos
|
||||
marker.opts = opts
|
||||
markers.push(marker)
|
||||
return marker
|
||||
}),
|
||||
divIcon: vi.fn(opts => opts),
|
||||
polyline: vi.fn((points, opts) => {
|
||||
const polyline = makeLayer('polyline')
|
||||
polyline.points = points
|
||||
polyline.opts = opts
|
||||
polylines.push(polyline)
|
||||
return polyline
|
||||
}),
|
||||
latLngBounds: vi.fn(() => bounds),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('leaflet', () => ({ default: leaflet.L }))
|
||||
|
||||
const settings = { latitude: 4.6, longitude: -74.08, address: 'Cra 1 #2-3' }
|
||||
|
||||
const provenance = [
|
||||
{
|
||||
product: { id: 1, name: 'Panela regional', catalogue_images: ['http://localhost/media/panela.jpg'] },
|
||||
suppliers: [
|
||||
{
|
||||
supplier: { id: 5, name: 'Asociación La Mesa' },
|
||||
organization: { id: 3, name: 'Red de Economía Solidaria' },
|
||||
municipality: { id: 7, name: 'Santa Bárbara', latitude: 6.23, longitude: -75.56 },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
product: { id: 2, name: 'Arroz blanco', catalogue_images: [] },
|
||||
suppliers: [
|
||||
{ supplier: { id: 6, name: 'Campesinos del Oriente' }, municipality: { id: 8, name: 'La Mesa', latitude: 4.86, longitude: -74.63 } },
|
||||
{ supplier: { id: 7, name: 'Finca El Paraíso' }, municipality: { id: 9, name: 'San Antonio', latitude: 6.22, longitude: -75.57 } },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function mountMap (props = {}) {
|
||||
const api = { getStoreSettings: vi.fn().mockResolvedValue(settings) }
|
||||
const pinia = createPinia()
|
||||
setActivePinia(pinia)
|
||||
const wrapper = mount(ProvenanceMap, {
|
||||
props: { provenance, ...props },
|
||||
global: {
|
||||
plugins: [pinia, vuetify],
|
||||
provide: { api },
|
||||
},
|
||||
})
|
||||
return { api, wrapper }
|
||||
}
|
||||
|
||||
function handlerOf (marker, event) {
|
||||
return marker.on.mock.calls.find(args => args[0] === event)[1]
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
leaflet.markers.length = 0
|
||||
leaflet.polylines.length = 0
|
||||
leaflet.layers.length = 0
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
describe('ProvenanceMap', () => {
|
||||
it('carga la configuración de la tienda al montarse', async () => {
|
||||
const { api } = mountMap()
|
||||
await flushPromises()
|
||||
|
||||
expect(api.getStoreSettings).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('crea el mapa con la tienda y un marcador por municipio de origen', async () => {
|
||||
mountMap()
|
||||
await flushPromises()
|
||||
|
||||
expect(leaflet.L.map).toHaveBeenCalledTimes(1)
|
||||
expect(leaflet.L.tileLayer).toHaveBeenCalledTimes(1)
|
||||
expect(leaflet.markers).toHaveLength(4)
|
||||
expect(leaflet.map.fitBounds).toHaveBeenCalled()
|
||||
expect(leaflet.markers[0].bindTooltip).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('al pasar sobre un producto dibuja una línea hasta la tienda', async () => {
|
||||
mountMap()
|
||||
await flushPromises()
|
||||
|
||||
handlerOf(leaflet.markers[1], 'mouseover')()
|
||||
|
||||
expect(leaflet.L.polyline).toHaveBeenCalledTimes(1)
|
||||
expect(leaflet.L.polyline.mock.calls[0][0]).toEqual([[6.23, -75.56], [4.6, -74.08]])
|
||||
expect(leaflet.polylines[0].addTo).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('al pasar sobre la tienda dibuja las líneas de todos los productos', async () => {
|
||||
mountMap()
|
||||
await flushPromises()
|
||||
|
||||
handlerOf(leaflet.markers[0], 'mouseover')()
|
||||
|
||||
expect(leaflet.L.polyline).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('al salir del marcador se limpian las líneas', async () => {
|
||||
mountMap()
|
||||
await flushPromises()
|
||||
|
||||
handlerOf(leaflet.markers[1], 'mouseover')()
|
||||
expect(leaflet.polylines).toHaveLength(1)
|
||||
handlerOf(leaflet.markers[1], 'mouseout')()
|
||||
|
||||
expect(leaflet.polylines[0].remove).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('al hacer clic en un producto abre el diálogo con el proveedor', async () => {
|
||||
mountMap()
|
||||
await flushPromises()
|
||||
|
||||
handlerOf(leaflet.markers[1], 'click')()
|
||||
await nextTick()
|
||||
|
||||
expect(document.body.textContent).toContain('Asociación La Mesa')
|
||||
expect(document.body.textContent).toContain('Panela regional')
|
||||
})
|
||||
|
||||
it('no dibuja la línea de la tienda cuando la tienda no tiene coordenadas', async () => {
|
||||
const api = { getStoreSettings: vi.fn().mockResolvedValue({ address: 'Cra 1' }) }
|
||||
const pinia = createPinia()
|
||||
setActivePinia(pinia)
|
||||
mount(ProvenanceMap, {
|
||||
props: { provenance },
|
||||
global: {
|
||||
plugins: [pinia, vuetify],
|
||||
provide: { api },
|
||||
},
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(leaflet.markers).toHaveLength(3)
|
||||
handlerOf(leaflet.markers[0], 'mouseover')()
|
||||
expect(leaflet.L.polyline).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('muestra un aviso cuando ningún municipio tiene coordenadas', async () => {
|
||||
const noCoords = [
|
||||
{ product: { id: 1, name: 'Panela' }, suppliers: [{ supplier: { id: 5, name: 'A' }, municipality: { id: 7, name: 'M' } }] },
|
||||
]
|
||||
const { wrapper } = mountMap({ provenance: noCoords })
|
||||
await flushPromises()
|
||||
|
||||
expect(leaflet.L.map).not.toHaveBeenCalled()
|
||||
expect(wrapper.text()).toContain('coordenadas')
|
||||
})
|
||||
|
||||
it('lee las coordenadas string del backend y da fondo visible al producto sin imagen', async () => {
|
||||
const backendProvenance = [
|
||||
{
|
||||
product: { id: 110, name: 'Panela condimentada 150 grs', catalogue_images: [] },
|
||||
suppliers: [
|
||||
{
|
||||
supplier: { id: 4, name: 'Asociación Agropecuaria La Mesa' },
|
||||
organization: null,
|
||||
municipality: { id: 2653, name: 'LA MESA', latitude: '4.6310280', longitude: '-74.4615880' },
|
||||
department: { id: 91, name: 'Cundinamarca' },
|
||||
country: { id: 3, name: 'Colombia', code: 'CO' },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
mountMap({ provenance: backendProvenance })
|
||||
await flushPromises()
|
||||
|
||||
expect(leaflet.L.map).toHaveBeenCalledTimes(1)
|
||||
expect(leaflet.markers).toHaveLength(2)
|
||||
expect(leaflet.markers[1].pos).toEqual([4.631028, -74.461588])
|
||||
const productIcon = leaflet.L.divIcon.mock.calls.find(args => args[0].html.includes('provenance-product-fallback'))
|
||||
expect(productIcon).toBeDefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ProvenanceRelationModal from '@/components/provenance/ProvenanceRelationModal.vue'
|
||||
import vuetify from '@/plugins/vuetify'
|
||||
|
||||
const relation = {
|
||||
supplier: { id: 5, name: 'Asociación La Mesa', contact_email: 'info@mesa.co' },
|
||||
organization: { id: 3, name: 'Red de Economía Solidaria' },
|
||||
municipality: { id: 7, name: 'Santa Bárbara' },
|
||||
department: { id: 2, name: 'Antioquia' },
|
||||
country: { id: 1, name: 'Colombia', code: 'CO' },
|
||||
}
|
||||
const product = {
|
||||
id: 1,
|
||||
name: 'Panela regional',
|
||||
catalogue_images: ['http://localhost/media/panela.jpg'],
|
||||
}
|
||||
|
||||
function mountModal (props = {}) {
|
||||
return mount(ProvenanceRelationModal, {
|
||||
props: { product, relation, visible: true, ...props },
|
||||
global: { plugins: [vuetify] },
|
||||
})
|
||||
}
|
||||
|
||||
function bodyText () {
|
||||
return document.body.textContent
|
||||
}
|
||||
|
||||
describe('ProvenanceRelationModal', () => {
|
||||
it('muestra el producto y todas las entidades de la relación', () => {
|
||||
mountModal()
|
||||
|
||||
expect(bodyText()).toContain('Panela regional')
|
||||
expect(bodyText()).toContain('Proveedor')
|
||||
expect(bodyText()).toContain('Asociación La Mesa')
|
||||
expect(bodyText()).toContain('Organización')
|
||||
expect(bodyText()).toContain('Red de Economía Solidaria')
|
||||
expect(bodyText()).toContain('Municipio')
|
||||
expect(bodyText()).toContain('Santa Bárbara')
|
||||
expect(bodyText()).toContain('Departamento')
|
||||
expect(bodyText()).toContain('Antioquia')
|
||||
expect(bodyText()).toContain('País')
|
||||
expect(bodyText()).toContain('Colombia')
|
||||
})
|
||||
|
||||
it('incluye los detalles de contacto del proveedor', () => {
|
||||
mountModal()
|
||||
|
||||
expect(bodyText()).toContain('info@mesa.co')
|
||||
})
|
||||
|
||||
it('muestra la imagen del producto cuando existe', () => {
|
||||
mountModal()
|
||||
|
||||
expect(document.querySelector('img').getAttribute('src')).toBe(
|
||||
'http://localhost/media/panela.jpg'
|
||||
)
|
||||
})
|
||||
|
||||
it('no muestra las entidades ausentes', () => {
|
||||
mountModal({ relation: { supplier: { id: 5, name: 'Asociación La Mesa' } } })
|
||||
|
||||
expect(bodyText()).not.toContain('Organización')
|
||||
expect(bodyText()).not.toContain('Municipio')
|
||||
expect(bodyText()).not.toContain('Departamento')
|
||||
expect(bodyText()).not.toContain('País')
|
||||
})
|
||||
|
||||
it('no muestra contenido cuando no hay relación', () => {
|
||||
mountModal({ relation: null })
|
||||
|
||||
expect(bodyText()).not.toContain('Proveedor')
|
||||
})
|
||||
|
||||
it('no muestra el diálogo cuando visible es false', () => {
|
||||
mountModal({ visible: false })
|
||||
|
||||
expect(bodyText()).not.toContain('Asociación La Mesa')
|
||||
})
|
||||
})
|
||||
@@ -30,10 +30,14 @@ const provenance = [
|
||||
function mountSection (props = {}) {
|
||||
return mount(ProvenanceSection, {
|
||||
props: { provenance, ...props },
|
||||
global: { plugins: [vuetify] },
|
||||
global: { plugins: [vuetify], stubs: { ProvenanceMap: true } },
|
||||
})
|
||||
}
|
||||
|
||||
function mapWrapper (wrapper) {
|
||||
return wrapper.findComponent({ name: 'ProvenanceMap' })
|
||||
}
|
||||
|
||||
describe('ProvenanceSection', () => {
|
||||
it('no renderiza nada cuando no hay provenance', () => {
|
||||
const wrapper = mountSection({ provenance: null })
|
||||
@@ -84,4 +88,40 @@ describe('ProvenanceSection', () => {
|
||||
|
||||
expect(wrapper.findComponent(ProvenanceGraph).props('provenance')).toStrictEqual(provenance)
|
||||
})
|
||||
|
||||
it('oculta el mapa por defecto', () => {
|
||||
const wrapper = mountSection()
|
||||
|
||||
expect(mapWrapper(wrapper).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('muestra el mapa al hacer clic en su título y lo oculta al volver a hacer clic', async () => {
|
||||
const wrapper = mountSection()
|
||||
|
||||
await wrapper.find('[data-test="map-toggle"]').trigger('click')
|
||||
expect(mapWrapper(wrapper).exists()).toBe(true)
|
||||
|
||||
await wrapper.find('[data-test="map-toggle"]').trigger('click')
|
||||
expect(mapWrapper(wrapper).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('también despliega y repliega el mapa con el botón de chevron', async () => {
|
||||
const wrapper = mountSection()
|
||||
|
||||
const button = wrapper.find('[data-test="map-toggle-button"]')
|
||||
expect(button.exists()).toBe(true)
|
||||
await button.trigger('click')
|
||||
expect(mapWrapper(wrapper).exists()).toBe(true)
|
||||
|
||||
await button.trigger('click')
|
||||
expect(mapWrapper(wrapper).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('pasa el provenance al mapa al desplegarlo', async () => {
|
||||
const wrapper = mountSection()
|
||||
|
||||
await wrapper.find('[data-test="map-toggle"]').trigger('click')
|
||||
|
||||
expect(mapWrapper(wrapper).props('provenance')).toStrictEqual(provenance)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user