import { beforeEach, describe, expect, it, vi } from 'vitest' import { mount } from '@vue/test-utils' import VisChart from '@/components/graph/VisChart.vue' const vis = vi.hoisted(() => { const networkInstance = { on: vi.fn(), once: vi.fn(), setData: vi.fn(), setOptions: vi.fn(), fit: vi.fn(), destroy: vi.fn(), getPositions: vi.fn(() => ({})), moveNode: vi.fn(), } const dataSetInstance = { get: vi.fn(), } return { networkInstance, dataSetInstance, Network: vi.fn(() => networkInstance), DataSet: vi.fn(() => dataSetInstance), } }) vi.mock('vis-network/standalone', () => ({ Network: vis.Network, DataSet: vis.DataSet, })) function handlerFor (mock, eventName) { const call = mock.mock.calls.find(args => args[0] === eventName) return call ? call[1] : null } describe('VisChart', () => { beforeEach(() => { vi.clearAllMocks() }) it('crea la red con los nodos y aristas recibidos', () => { const nodes = [{ id: 'product:1', label: 'Panela' }] const edges = [{ from: 'product:1', to: 'supplier:5', dashes: true }] mount(VisChart, { props: { nodes, edges } }) expect(vis.Network).toHaveBeenCalledTimes(1) const [container, , options] = vis.Network.mock.calls[0] expect(container).toBeInstanceOf(HTMLElement) expect(options.physics.stabilization.enabled).toBe(true) expect(vis.dataSetInstance.get).toBeDefined() const data = vis.networkInstance.setData.mock.calls[0][0] expect(data.nodes).toBe(vis.dataSetInstance) expect(data.edges).toBe(vis.dataSetInstance) }) it('mezcla las opciones base con las recibidas', () => { mount(VisChart, { props: { nodes: [], edges: [], options: { nodes: { font: { size: 16 } }, physics: { stabilization: { iterations: 100 } } }, }, }) const options = vis.Network.mock.calls[0][2] expect(options.nodes.font.size).toBe(16) expect(options.nodes.shape).toBe('dot') expect(options.physics.stabilization.iterations).toBe(100) expect(options.physics.barnesHut.springLength).toBe(140) }) it('emite select con el nodo al hacer click', () => { const wrapper = mount(VisChart, { props: { nodes: [{ id: 'supplier:5', kind: 'supplier' }], edges: [] } }) vis.dataSetInstance.get.mockReturnValue({ id: 'supplier:5', kind: 'supplier', label: 'La Mesa' }) handlerFor(vis.networkInstance.on, 'click')({ nodes: ['supplier:5'] }) expect(wrapper.emitted('select')[0][0]).toEqual({ id: 'supplier:5', kind: 'supplier', label: 'La Mesa' }) }) it('no emite select cuando el click no cae sobre un nodo', () => { const wrapper = mount(VisChart, { props: { nodes: [], edges: [] } }) handlerFor(vis.networkInstance.on, 'click')({ nodes: [] }) expect(wrapper.emitted('select')).toBeUndefined() }) it('desactiva la física y ajusta la vista cuando la estabilización termina', () => { mount(VisChart, { props: { nodes: [], edges: [] } }) const onStabilized = handlerFor(vis.networkInstance.once, 'stabilizationIterationsDone') expect(onStabilized).toBeDefined() onStabilized() expect(vis.networkInstance.setOptions).toHaveBeenCalledWith({ physics: { enabled: false } }) expect(vis.networkInstance.fit).toHaveBeenCalled() }) it('separa verticalmente los nodos de una misma columna tras estabilizar', () => { vis.networkInstance.getPositions.mockReturnValue({ 'supplier:5': { x: 0, y: 100 }, 'supplier:6': { x: 0, y: 112 }, 'municipality:7': { x: 240, y: 100 }, 'product:1': { x: -240, y: 60 }, }) mount(VisChart, { props: { nodes: [], edges: [], minVerticalSpacing: 70 } }) const onStabilized = handlerFor(vis.networkInstance.once, 'stabilizationIterationsDone') onStabilized() expect(vis.networkInstance.moveNode).toHaveBeenCalledWith('supplier:6', 0, 170) expect(vis.networkInstance.moveNode).not.toHaveBeenCalledWith('supplier:5', 0, expect.anything()) expect(vis.networkInstance.moveNode).not.toHaveBeenCalledWith('municipality:7', 240, expect.anything()) expect(vis.networkInstance.moveNode).not.toHaveBeenCalledWith('product:1', -240, expect.anything()) }) it('no separa nodos cuando no se indica espaciado vertical mínimo', () => { vis.networkInstance.getPositions.mockReturnValue({ 'supplier:5': { x: 0, y: 100 }, 'supplier:6': { x: 0, y: 112 }, }) mount(VisChart, { props: { nodes: [], edges: [] } }) const onStabilized = handlerFor(vis.networkInstance.once, 'stabilizationIterationsDone') onStabilized() expect(vis.networkInstance.moveNode).not.toHaveBeenCalled() }) it('vuelve a estabilizar el grafo cuando cambian los nodos o aristas', async () => { const wrapper = mount(VisChart, { props: { nodes: [{ id: 'product:1' }], edges: [] } }) expect(vis.Network).toHaveBeenCalledTimes(1) expect(vis.networkInstance.destroy).not.toHaveBeenCalled() await wrapper.setProps({ nodes: [{ id: 'product:2' }], edges: [{ from: 'product:2', to: 'supplier:5' }] }) expect(vis.networkInstance.destroy).toHaveBeenCalled() expect(vis.Network).toHaveBeenCalledTimes(2) expect(vis.networkInstance.setData).toHaveBeenCalledTimes(2) }) it('aplica la altura recibida al contenedor', () => { const wrapper = mount(VisChart, { props: { height: 340 } }) expect(wrapper.element.style.height).toBe('340px') }) it('destruye la red al desmontar', () => { const wrapper = mount(VisChart, { props: { nodes: [], edges: [] } }) wrapper.unmount() expect(vis.networkInstance.destroy).toHaveBeenCalled() }) })