import { describe, expect, it } from 'vitest' import { nextTick } from 'vue' import { mount } from '@vue/test-utils' import WorkflowCanvas from '@/views/system/workflow/designer/WorkflowCanvas.vue' // jsdom 不实现 SVG 测量,LogicFlow 的节点文本(autoWrap)会用到 if (typeof SVGElement !== 'undefined' && !SVGElement.prototype.getBBox) { SVGElement.prototype.getBBox = () => ({ x: 0, y: 0, width: 0, height: 0 }) } /** 一个坐标明显重叠的节点 */ function stackedNode(nodeKey, nodeType, name) { return { nodeKey, nodeType, name, x: 300, y: 300, config: {}, actorRules: [] } } /** 三个节点全叠在同一个坐标上,布局后应该被拉开成纵向流程 */ function messyDraft() { return { schemaVersion: 1, definitionId: 'expense_approval', name: '费用报销审批', nodes: [ stackedNode('start', 'start', '开始'), stackedNode('user_task_1', 'user_task', '部门负责人审批'), stackedNode('end', 'end', '结束'), ], edges: [ { edgeKey: 'edge_1', from: 'start', to: 'user_task_1', name: '', condition: null }, { edgeKey: 'edge_2', from: 'user_task_1', to: 'end', name: '', condition: null }, ], } } describe('画布一键整理(@logicflow/layout 的 dagre 插件)', () => { it('把重叠节点按纵向流程拉开,并把新坐标回报给上层', async () => { const wrapper = mount(WorkflowCanvas, { props: { draft: messyDraft() }, attachTo: document.body, }) await nextTick() const changed = wrapper.vm.autoLayout() expect(changed).toBe(true) // 布局结果必须回报给上层,否则草稿里的 x/y 还是旧的 const emitted = wrapper.emitted('graph-change') expect(emitted).toBeTruthy() const graph = emitted[emitted.length - 1][0] const byId = Object.fromEntries(graph.nodes.map((item) => [item.id, item])) // 纵向(rankdir: TB):开始在上、结束在下,且三个节点不再重叠 expect(byId.start.y).toBeLessThan(byId.user_task_1.y) expect(byId.user_task_1.y).toBeLessThan(byId.end.y) expect(new Set(graph.nodes.map((item) => `${item.x},${item.y}`)).size).toBe(3) wrapper.unmount() }) it('布局只改坐标:节点与连线的编码、拓扑原样保留', async () => { const wrapper = mount(WorkflowCanvas, { props: { draft: messyDraft() }, attachTo: document.body, }) await nextTick() wrapper.vm.autoLayout() const emitted = wrapper.emitted('graph-change') const graph = emitted[emitted.length - 1][0] // 编码稳定,上层才能按 nodeKey / edgeKey 回填审批人与条件 expect(graph.nodes.map((item) => item.id).toSorted()).toEqual(['end', 'start', 'user_task_1']) expect(graph.edges.map((item) => item.id).toSorted()).toEqual(['edge_1', 'edge_2']) expect(graph.edges.map((item) => [item.sourceNodeId, item.targetNodeId])).toEqual([ ['start', 'user_task_1'], ['user_task_1', 'end'], ]) wrapper.unmount() }) })