test(diagram): DiagramLayoutService golden 用例

This commit is contained in:
chenxing
2026-10-03 13:55:24 +08:00
parent 09c3c0c38c
commit d6898ea674
@@ -0,0 +1,147 @@
package com.yunhai.flowcenter.service;
import com.yunhai.flowcenter.engine.FlowDefModel;
import com.yunhai.flowcenter.model.FlowModels;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* 布局单源 golden 用例(2026-10-03 三视图统一):PNG 与 DiagramVo.positions 共同消费这份几何。
* 数值断言 ±1 容差(int 除法);关系断言(列距/居中/归零)与常量解耦,防调参误报。
*/
class DiagramLayoutServiceTest {
private final DiagramLayoutService service = new DiagramLayoutService();
private FlowModels.NodeVo node(String id, String type) {
FlowModels.NodeVo n = new FlowModels.NodeVo();
n.setId(id);
n.setType(type);
n.setLabel(id);
return n;
}
private FlowModels.EdgeVo edge(String from, String to) {
FlowModels.EdgeVo e = new FlowModels.EdgeVo();
e.setFrom(from);
e.setTo(to);
return e;
}
private Map<String, int[]> layout(List<FlowModels.NodeVo> nodes, FlowModels.EdgeVo... edges) {
return service.layout(nodes, new ArrayList<>(Arrays.asList(edges)));
}
private int cy(int[] b) {
return b[1] + b[3] / 2;
}
@Test
void linear_xPitchByColumn_andZeroMin() {
List<FlowModels.NodeVo> nodes = new ArrayList<>(Arrays.asList(
node("start", FlowDefModel.TYPE_START),
node("a", FlowDefModel.TYPE_APPROVAL),
node("b", FlowDefModel.TYPE_APPROVAL),
node("end", FlowDefModel.TYPE_END)));
Map<String, int[]> box = layout(nodes, edge("start", "a"), edge("a", "b"), edge("b", "end"));
for (FlowModels.NodeVo n : nodes) {
assertNotNull(box.get(n.getId()), "缺坐标:" + n.getId());
}
// 同宽节点(审批→审批)x 差 = 列距 SLOT_W+GAP_X(±1 容差)
assertEquals(DiagramLayoutService.SLOT_W + DiagramLayoutService.GAP_X, box.get("b")[0] - box.get("a")[0], 1);
// 归零:全部坐标非负且最小 x/y 为 0
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
for (int[] b : box.values()) {
assertTrue(b[0] >= 0 && b[1] >= 0);
minX = Math.min(minX, b[0]);
minY = Math.min(minY, b[1]);
}
assertEquals(0, minX);
assertEquals(0, minY);
}
@Test
void conditionBranches_sameColumnBlockVerticallyCentered() {
List<FlowModels.NodeVo> nodes = new ArrayList<>(Arrays.asList(
node("start", FlowDefModel.TYPE_START),
node("c", FlowDefModel.TYPE_CONDITION),
node("a", FlowDefModel.TYPE_APPROVAL),
node("b", FlowDefModel.TYPE_APPROVAL),
node("end", FlowDefModel.TYPE_END)));
Map<String, int[]> box = layout(nodes,
edge("start", "c"), edge("c", "a"), edge("c", "b"), edge("a", "end"), edge("b", "end"));
// 两分支同列不同行,列块垂直居中:两盒中心均值 ≈ 内容区中线(单行列的中心同值)
assertEquals(box.get("a")[0], box.get("b")[0]);
assertTrue(box.get("a")[1] != box.get("b")[1]);
assertEquals(cy(box.get("c")), (cy(box.get("a")) + cy(box.get("b"))) / 2, 1);
}
@Test
void parallelGroup_splitAndJoinShareCenterline() {
List<FlowModels.NodeVo> nodes = new ArrayList<>(Arrays.asList(
node("start", FlowDefModel.TYPE_START),
node("s", FlowDefModel.TYPE_PARALLEL_SPLIT),
node("a", FlowDefModel.TYPE_APPROVAL),
node("b", FlowDefModel.TYPE_APPROVAL),
node("j", FlowDefModel.TYPE_PARALLEL_JOIN),
node("end", FlowDefModel.TYPE_END)));
Map<String, int[]> box = layout(nodes,
edge("start", "s"), edge("s", "a"), edge("s", "b"),
edge("a", "j"), edge("b", "j"), edge("j", "end"));
// split 与 join 同一水平中线,且即两分支的对称轴(centerParallelGroups 生效)
assertEquals(cy(box.get("s")), cy(box.get("j")), 1);
assertEquals(cy(box.get("s")), (cy(box.get("a")) + cy(box.get("b"))) / 2, 1);
}
@Test
void nestedParallel_groupsCentered_noNegativeY() {
List<FlowModels.NodeVo> nodes = new ArrayList<>(Arrays.asList(
node("start", FlowDefModel.TYPE_START),
node("os", FlowDefModel.TYPE_PARALLEL_SPLIT),
node("is", FlowDefModel.TYPE_PARALLEL_SPLIT),
node("c", FlowDefModel.TYPE_APPROVAL),
node("p", FlowDefModel.TYPE_APPROVAL),
node("q", FlowDefModel.TYPE_APPROVAL),
node("ij", FlowDefModel.TYPE_PARALLEL_JOIN),
node("oj", FlowDefModel.TYPE_PARALLEL_JOIN),
node("end", FlowDefModel.TYPE_END)));
Map<String, int[]> box = layout(nodes,
edge("start", "os"),
edge("os", "is"), edge("os", "c"),
edge("is", "p"), edge("is", "q"),
edge("p", "ij"), edge("q", "ij"),
edge("ij", "oj"), edge("c", "oj"),
edge("oj", "end"));
// 内层组:is 与 {p,q} 居中;外层组:os/oj 同中线即分支对称轴
assertEquals(cy(box.get("is")), (cy(box.get("p")) + cy(box.get("q"))) / 2, 1);
assertEquals(cy(box.get("os")), cy(box.get("oj")), 1);
// 全图无 y<0(centerParallelGroups 末段抬回)
for (int[] b : box.values()) {
assertTrue(b[1] >= 0, "出现负 y:" + b[1]);
}
}
@Test
void cycle_defensive_fallbackLevelStillPositionsAllNodes() {
List<FlowModels.NodeVo> nodes = new ArrayList<>(Arrays.asList(
node("start", FlowDefModel.TYPE_START),
node("a", FlowDefModel.TYPE_APPROVAL),
node("b", FlowDefModel.TYPE_APPROVAL)));
Map<String, int[]> box = assertDoesNotThrow(() -> layout(nodes,
edge("start", "a"), edge("a", "b"), edge("b", "a")));
// 环兜底:不抛异常,所有节点都有坐标
for (FlowModels.NodeVo n : nodes) {
assertNotNull(box.get(n.getId()), "缺坐标:" + n.getId());
}
}
}