#49 feat: grafico unificado de provenance con filtro por niveles y re-estabilizacion

This commit is contained in:
2026-08-16 03:10:57 -05:00
parent 5c91e66429
commit ad0bbb579d
14 changed files with 508 additions and 747 deletions

View File

@@ -0,0 +1,154 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { nextTick } from 'vue'
import ProvenanceDetailModal from '@/components/provenance/ProvenanceDetailModal.vue'
import ProvenanceGraph from '@/components/provenance/ProvenanceGraph.vue'
import vuetify from '@/plugins/vuetify'
vi.mock('@/components/graph/VisChart.vue', () => ({
default: {
name: 'VisChart',
template: '<div class="vis-chart-stub" />',
props: ['nodes', 'edges', 'height', 'options', 'minVerticalSpacing'],
},
}))
const provenance = [
{
product: {
id: 1,
name: 'Panela regional por Kg',
catalogue_images: ['http://localhost/media/panela.jpg'],
},
suppliers: [
{
supplier: { id: 5, name: 'Asociación Agropecuaria La Mesa' },
organization: { id: 3, name: 'Red de Economía Solidaria' },
municipality: { id: 7, name: 'La Mesa' },
department: { id: 2, name: 'Cundinamarca' },
country: { id: 1, name: 'Colombia' },
},
],
},
]
function mountGraph (props = {}) {
return mount(ProvenanceGraph, {
props: { provenance, ...props },
global: { plugins: [vuetify] },
})
}
function visChart (wrapper) {
return wrapper.findComponent({ name: 'VisChart' })
}
function checkbox (wrapper, label) {
return wrapper.findAllComponents({ name: 'VCheckbox' }).find(cb => cb.props('label') === label)
}
async function setKinds (wrapper, kinds) {
checkbox(wrapper, 'Productos').vm.$emit('update:modelValue', kinds)
await nextTick()
}
describe('ProvenanceGraph', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('por defecto grafica solo productos y proveedores', () => {
const wrapper = mountGraph()
const nodeKinds = visChart(wrapper).props('nodes').map(node => node.kind)
expect(nodeKinds).toEqual(['product', 'supplier'])
const edges = visChart(wrapper).props('edges')
expect(edges.find(edge => edge.from === 'product:1' && edge.to === 'supplier:5').dashes).toBe(false)
})
it('pide separación vertical mínima equivalente al círculo más una línea de label', () => {
const wrapper = mountGraph()
expect(visChart(wrapper).props('minVerticalSpacing')).toBe(70)
})
it('muestra un checkbox por cada nivel con productos y proveedores activos', () => {
const wrapper = mountGraph()
const labels = wrapper.findAllComponents({ name: 'VCheckbox' }).map(cb => cb.props('label'))
expect(labels).toEqual(['Productos', 'Proveedores', 'Organizaciones', 'Municipios', 'Departamentos', 'País'])
})
it('al activar municipios agrega nodos de municipio y la arista proveedor→municipio', async () => {
const wrapper = mountGraph()
await setKinds(wrapper, ['product', 'supplier', 'municipality'])
const nodeIds = visChart(wrapper).props('nodes').map(node => node.id)
expect(nodeIds).toContain('municipality:7')
const edges = visChart(wrapper).props('edges')
expect(edges.find(edge => edge.from === 'supplier:5' && edge.to === 'municipality:7').dashes).toBe(false)
})
it('con país activo crea el nodo país', async () => {
const wrapper = mountGraph()
await setKinds(wrapper, ['product', 'supplier', 'country'])
expect(visChart(wrapper).props('nodes').map(node => node.id)).toContain('country:1')
})
it('al desactivar productos no se crean nodos de producto', async () => {
const wrapper = mountGraph()
await setKinds(wrapper, ['supplier', 'municipality', 'department'])
const nodeKinds = visChart(wrapper).props('nodes').map(node => node.kind)
expect(nodeKinds).not.toContain('product')
expect(nodeKinds).not.toContain('junction')
})
it('avisa cuando no queda ningún nivel seleccionado', async () => {
const wrapper = mountGraph()
await setKinds(wrapper, [])
expect(visChart(wrapper).exists()).toBe(false)
expect(wrapper.text()).toContain('Selecciona al menos un elemento')
})
it('muestra mensaje de próximamente cuando ningún producto tiene proveedores', () => {
const wrapper = mountGraph({
provenance: [{ product: { id: 1, name: 'Panela' }, suppliers: [] }],
})
expect(visChart(wrapper).exists()).toBe(false)
expect(wrapper.text()).toContain('próximamente')
})
it('abre el modal de detalle al seleccionar un proveedor', async () => {
const wrapper = mountGraph()
await visChart(wrapper).vm.$emit('select', { id: 'supplier:5', kind: 'supplier', label: 'Asociación Agropecuaria La Mesa', entity: { kind: 'supplier' } })
expect(wrapper.findComponent(ProvenanceDetailModal).props('visible')).toBe(true)
expect(document.body.textContent).toContain('Asociación Agropecuaria La Mesa')
})
it('ignora la selección del punto de disyunción', async () => {
const provenanceWithTwoSuppliers = [
{
product: { id: 1, name: 'Panela' },
suppliers: [
{ supplier: { id: 5, name: 'A' }, municipality: { id: 7, name: 'La Mesa' } },
{ supplier: { id: 6, name: 'B' }, municipality: { id: 8, name: 'San Antonio' } },
],
},
]
const wrapper = mountGraph({ provenance: provenanceWithTwoSuppliers })
await visChart(wrapper).vm.$emit('select', { id: 'junction:1', kind: 'junction', label: '' })
expect(wrapper.findComponent(ProvenanceDetailModal).props('visible')).toBe(false)
})
})