fix(diagram): CONDITION 多出口分支分列——不等长/空分支不再被拓扑分层压成单列直穿(用户实测 def-32 或签流程报错)。spreadConditionBranches 后处理:各出口分支按列对称展开(链形分支逐节点对列中心,内嵌并行回退整块平移),隐式汇合点(各分支可达集交集 BFS 最早节点)吸附回条件节点中线;等长同层分支为恒等变换不回归(golden 8/8)
build / build-push (push) Successful in 52s

This commit is contained in:
chenxing
2026-10-12 08:59:05 +08:00
parent 6cd33abc67
commit 6f68e1ebe7
2 changed files with 191 additions and 0 deletions
@@ -183,6 +183,60 @@ class DiagramLayoutServiceTest {
}
}
@Test
void conditionUnequalBranches_forkIntoColumns_joinStaysOnAxis() {
// def-32 同款(2026-10-12 用户报错场景):校长室线=抄送→科室审批(两层),其他线=直达结束;
// 修复前各层退化单列、跳层出口边直穿中间节点。修复后:分支成员独占一列,隐式汇合点(end)
// 留在条件节点中线上,cond→end 直落边在自己的车道里(不与分支列水平相交)。
List<FlowModels.NodeVo> nodes = new ArrayList<>(Arrays.asList(
node("start", FlowDefModel.TYPE_START),
node("n1", FlowDefModel.TYPE_APPROVAL),
node("cond", FlowDefModel.TYPE_CONDITION),
node("cc1", FlowDefModel.TYPE_NOTIFY),
node("n3", FlowDefModel.TYPE_APPROVAL),
node("end", FlowDefModel.TYPE_END)));
Map<String, int[]> box = layout(nodes,
edge("start", "n1"), edge("n1", "cond"),
edge("cond", "cc1"), edge("cond", "end"),
edge("cc1", "n3"), edge("n3", "end"));
assertEquals(cx(box.get("cc1")), cx(box.get("n3")), 1, "校长室线成员应同列");
assertEquals(cx(box.get("cond")), cx(box.get("end")), 1, "隐式汇合点应留在条件节点中线");
assertEquals((DiagramLayoutService.SLOT_W + DiagramLayoutService.GAP_X) / 2,
Math.abs(cx(box.get("cond")) - cx(box.get("cc1"))), 1, "空分支时非空分支占 ±列距/2 位(与等长分支对称口径一致)");
// 直落边车道无遮挡:cc1/n3 的盒体不得横跨 cond→end 垂直线(x=条件节点中线)
int axis = cx(box.get("cond"));
for (String m : new String[]{"cc1", "n3"}) {
int[] b = box.get(m);
assertTrue(b[0] + b[2] <= axis || b[0] >= axis, m + " 盒体跨中线会被直落边穿过");
}
for (int[] b : box.values()) {
assertTrue(b[0] >= 0 && b[1] >= 0, "归零后不得有负坐标");
}
}
@Test
void conditionBothBranchesNonEmpty_multiLevelSideBySide() {
// 两分支均多层且长度不等:A 线两层、B 线一层——各自成列、汇合点回中线
List<FlowModels.NodeVo> nodes = new ArrayList<>(Arrays.asList(
node("start", FlowDefModel.TYPE_START),
node("cond", FlowDefModel.TYPE_CONDITION),
node("a1", FlowDefModel.TYPE_APPROVAL),
node("a2", FlowDefModel.TYPE_APPROVAL),
node("b1", FlowDefModel.TYPE_APPROVAL),
node("end", FlowDefModel.TYPE_END)));
Map<String, int[]> box = layout(nodes,
edge("start", "cond"),
edge("cond", "a1"), edge("cond", "b1"),
edge("a1", "a2"), edge("a2", "end"), edge("b1", "end"));
assertEquals(cx(box.get("a1")), cx(box.get("a2")), 1, "A 线成员同列");
assertEquals(cx(box.get("cond")), cx(box.get("end")), 1, "汇合点在中线");
// A/B 两列分居中线两侧(±列距/2 对称)
assertTrue((cx(box.get("a1")) < cx(box.get("cond"))) != (cx(box.get("b1")) < cx(box.get("cond"))),
"两分支应分居中线两侧");
assertEquals(DiagramLayoutService.SLOT_W + DiagramLayoutService.GAP_X,
Math.abs(cx(box.get("b1")) - cx(box.get("a1"))), 1, "两分支列距");
}
@Test
void cycle_defensive_fallbackLevelStillPositionsAllNodes() {
List<FlowModels.NodeVo> nodes = new ArrayList<>(Arrays.asList(