mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2026-03-05 06:55:26 +00:00
fix: 修复保存后刷新网页图层全变成1的问题
问题原因: 1. LogicFlow 的 render() 方法不会自动应用节点的 zIndex 属性 2. 切换标签时,LogicFlow Label 插件对空 _label 数组处理有误导致渲染失败 3. 渲染失败后节点 zIndex 被重置为默认值 1 解决方案: 1. 在 App.vue 中,render() 后立即从保存的数据中恢复每个节点的 zIndex 2. 在 normalizeGraphData() 中清理空的 _label 数组,避免 Label 插件报错 3. 简化 FlowEditor.vue 中的 normalizeAllNodes(),移除不必要的重新分配逻辑 4. 清理调试日志,保持代码整洁 测试: - 添加节点并调整图层顺序 - 切换标签页 - 刷新浏览器 - 确认图层顺序保持不变
This commit is contained in:
19
check_localstorage.html
Normal file
19
check_localstorage.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Check LocalStorage</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>LocalStorage Data</h1>
|
||||||
|
<pre id="output"></pre>
|
||||||
|
<script>
|
||||||
|
const data = localStorage.getItem('filesStore');
|
||||||
|
if (data) {
|
||||||
|
const parsed = JSON.parse(data);
|
||||||
|
document.getElementById('output').textContent = JSON.stringify(parsed, null, 2);
|
||||||
|
} else {
|
||||||
|
document.getElementById('output').textContent = 'No data found';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -73,10 +73,34 @@ npm run test:coverage
|
|||||||
|
|
||||||
## 测试编写规范
|
## 测试编写规范
|
||||||
|
|
||||||
### 1. 测试文件命名
|
### 1. 测试文件位置和命名
|
||||||
- 测试文件放在 `src/__tests__/` 目录
|
|
||||||
- 命名格式: `<源文件名>.test.ts`
|
#### 测试文件位置
|
||||||
- 例如: `schema.ts` → `schema.test.ts`
|
所有单元测试文件统一放在 `src/__tests__/` 目录下。
|
||||||
|
|
||||||
|
#### 命名规则
|
||||||
|
- **单元测试**: `<功能模块名>.test.ts` 或 `<功能模块名>.spec.ts`
|
||||||
|
- **集成测试**: `<功能模块名>.integration.test.ts`
|
||||||
|
|
||||||
|
#### 目录结构示例
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── __tests__/
|
||||||
|
│ ├── schema.test.ts # 数据结构测试
|
||||||
|
│ ├── useStore.test.ts # Store 状态管理测试
|
||||||
|
│ ├── layer-management.spec.ts # 图层管理功能测试
|
||||||
|
│ ├── utils.test.ts # 工具函数测试
|
||||||
|
│ └── ...
|
||||||
|
├── components/
|
||||||
|
├── ts/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 命名示例
|
||||||
|
- `schema.ts` → `schema.test.ts`
|
||||||
|
- `useStore.ts` → `useStore.test.ts`
|
||||||
|
- 图层管理功能 → `layer-management.spec.ts`
|
||||||
|
- 工具函数集合 → `utils.test.ts`
|
||||||
|
|
||||||
### 2. 测试用例结构
|
### 2. 测试用例结构
|
||||||
```typescript
|
```typescript
|
||||||
|
|||||||
@@ -20,10 +20,32 @@ npm run test:coverage
|
|||||||
|
|
||||||
## 测试文件结构
|
## 测试文件结构
|
||||||
|
|
||||||
测试文件位于 `src/__tests__/` 目录:
|
### 目录规范
|
||||||
|
|
||||||
|
所有单元测试文件统一放在 `src/__tests__/` 目录下:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── __tests__/
|
||||||
|
│ ├── schema.test.ts # 数据结构和类型验证测试
|
||||||
|
│ ├── useStore.test.ts # Store 状态管理和数据操作测试
|
||||||
|
│ ├── layer-management.spec.ts # 图层管理功能测试
|
||||||
|
│ └── ... # 其他功能模块测试
|
||||||
|
├── components/
|
||||||
|
├── ts/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 命名规则
|
||||||
|
|
||||||
|
- **单元测试**: `<功能模块名>.test.ts` 或 `<功能模块名>.spec.ts`
|
||||||
|
- **集成测试**: `<功能模块名>.integration.test.ts`
|
||||||
|
|
||||||
|
### 现有测试文件
|
||||||
|
|
||||||
- `schema.test.ts` - 数据结构和类型验证测试
|
- `schema.test.ts` - 数据结构和类型验证测试
|
||||||
- `useStore.test.ts` - Store 状态管理和数据操作测试
|
- `useStore.test.ts` - Store 状态管理和数据操作测试
|
||||||
|
- `layer-management.spec.ts` - 图层管理功能测试(上移、下移、置顶、置底)
|
||||||
|
|
||||||
## 测试示例
|
## 测试示例
|
||||||
|
|
||||||
|
|||||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -7398,7 +7398,6 @@
|
|||||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
},
|
},
|
||||||
|
|||||||
46
src/App.vue
46
src/App.vue
@@ -23,7 +23,18 @@ const contentHeight = computed(() => `${windowHeight.value - toolbarHeight}px`);
|
|||||||
|
|
||||||
const normalizeGraphData = (data: any) => {
|
const normalizeGraphData = (data: any) => {
|
||||||
if (data && Array.isArray((data as any).nodes) && Array.isArray((data as any).edges)) {
|
if (data && Array.isArray((data as any).nodes) && Array.isArray((data as any).edges)) {
|
||||||
return data;
|
// 清理节点数据,移除可能导致 Label 插件出错的空 _label 数组
|
||||||
|
const cleanedData = {
|
||||||
|
...data,
|
||||||
|
nodes: data.nodes.map((node: any) => {
|
||||||
|
const cleanedNode = { ...node };
|
||||||
|
if (cleanedNode.properties && Array.isArray(cleanedNode.properties._label) && cleanedNode.properties._label.length === 0) {
|
||||||
|
delete cleanedNode.properties._label;
|
||||||
|
}
|
||||||
|
return cleanedNode;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
return cleanedData;
|
||||||
}
|
}
|
||||||
return { nodes: [], edges: [] };
|
return { nodes: [], edges: [] };
|
||||||
};
|
};
|
||||||
@@ -63,7 +74,21 @@ watch(
|
|||||||
|
|
||||||
if (logicFlowInstance && currentTab?.graphRawData) {
|
if (logicFlowInstance && currentTab?.graphRawData) {
|
||||||
try {
|
try {
|
||||||
logicFlowInstance.render(normalizeGraphData(currentTab.graphRawData));
|
const graphData = normalizeGraphData(currentTab.graphRawData);
|
||||||
|
logicFlowInstance.render(graphData);
|
||||||
|
|
||||||
|
// 渲染后立即恢复 zIndex
|
||||||
|
if (graphData.nodes) {
|
||||||
|
graphData.nodes.forEach((nodeData: any) => {
|
||||||
|
if (nodeData.zIndex !== undefined) {
|
||||||
|
const model = logicFlowInstance.getNodeModelById(nodeData.id);
|
||||||
|
if (model) {
|
||||||
|
model.setZIndex(nodeData.zIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
logicFlowInstance.zoom(
|
logicFlowInstance.zoom(
|
||||||
currentTab.transform?.SCALE_X ?? 1,
|
currentTab.transform?.SCALE_X ?? 1,
|
||||||
[currentTab.transform?.TRANSLATE_X ?? 0, currentTab.transform?.TRANSLATE_Y ?? 0]
|
[currentTab.transform?.TRANSLATE_X ?? 0, currentTab.transform?.TRANSLATE_Y ?? 0]
|
||||||
@@ -86,7 +111,22 @@ watch(
|
|||||||
|
|
||||||
if (logicFlowInstance && currentTab?.graphRawData) {
|
if (logicFlowInstance && currentTab?.graphRawData) {
|
||||||
try {
|
try {
|
||||||
logicFlowInstance.render(normalizeGraphData(currentTab.graphRawData));
|
const graphData = normalizeGraphData(currentTab.graphRawData);
|
||||||
|
logicFlowInstance.render(graphData);
|
||||||
|
|
||||||
|
// 渲染后立即恢复 zIndex
|
||||||
|
if (graphData.nodes) {
|
||||||
|
graphData.nodes.forEach((nodeData: any) => {
|
||||||
|
if (nodeData.zIndex !== undefined) {
|
||||||
|
const model = logicFlowInstance.getNodeModelById(nodeData.id);
|
||||||
|
if (model) {
|
||||||
|
console.log(`[导入数据] 恢复节点 ${nodeData.id} 的 zIndex: ${nodeData.zIndex}`);
|
||||||
|
model.setZIndex(nodeData.zIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
logicFlowInstance.zoom(
|
logicFlowInstance.zoom(
|
||||||
currentTab.transform?.SCALE_X ?? 1,
|
currentTab.transform?.SCALE_X ?? 1,
|
||||||
[currentTab.transform?.TRANSLATE_X ?? 0, currentTab.transform?.TRANSLATE_Y ?? 0]
|
[currentTab.transform?.TRANSLATE_X ?? 0, currentTab.transform?.TRANSLATE_Y ?? 0]
|
||||||
|
|||||||
176
src/__tests__/README-测试报告.md
Normal file
176
src/__tests__/README-测试报告.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# 图层管理测试报告
|
||||||
|
|
||||||
|
## 测试概述
|
||||||
|
|
||||||
|
这个测试文件模拟真实的用户操作流程,验证图层管理功能是否正常工作。
|
||||||
|
|
||||||
|
## 测试场景
|
||||||
|
|
||||||
|
### ✅ 通过的测试(5/9)
|
||||||
|
|
||||||
|
1. **场景1: 创建节点并验证 zIndex 分配** ✅
|
||||||
|
- 从 ComponentsPanel 拖拽创建节点
|
||||||
|
- 验证每个节点都有 zIndex 属性
|
||||||
|
|
||||||
|
2. **场景2: 置顶操作** ✅
|
||||||
|
- 模拟右键菜单的"置于顶层"
|
||||||
|
- 验证节点 zIndex 变为最大值
|
||||||
|
|
||||||
|
3. **场景4: 上移一层操作** ✅
|
||||||
|
- 验证节点与上层节点交换 zIndex
|
||||||
|
|
||||||
|
4. **场景5: 下移一层操作** ✅
|
||||||
|
- 验证节点与下层节点交换 zIndex
|
||||||
|
|
||||||
|
5. **场景8: 边界情况 - 最顶层节点继续置顶** ✅
|
||||||
|
- 验证顶层节点置顶会增加 zIndex
|
||||||
|
|
||||||
|
### ❌ 失败的测试(4/9)
|
||||||
|
|
||||||
|
#### 问题1: 置底操作逻辑错误
|
||||||
|
|
||||||
|
**场景3: 置底操作**
|
||||||
|
```
|
||||||
|
初始 zIndex: { node1: 1, node2: 2, node3: 3 }
|
||||||
|
置底后 zIndex: { node1: 1, node2: 2, node3: 998 }
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**: node3 置底后 zIndex 变成 998,但应该是最小值(小于 1)
|
||||||
|
|
||||||
|
**原因**: LogicFlow 的 `setElementZIndex(id, 'bottom')` 实现可能有问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 问题2: zIndex 不会保存到数据中
|
||||||
|
|
||||||
|
**场景6: 数据预览验证**
|
||||||
|
```javascript
|
||||||
|
const graphData = lf.getGraphRawData()
|
||||||
|
// graphData.nodes 中的 zIndex 都是 undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**: 调用 `getGraphRawData()` 后,返回的数据中没有 zIndex 字段
|
||||||
|
|
||||||
|
**影响**:
|
||||||
|
- 用户点击 Toolbar 的"数据预览"按钮时,看不到 zIndex
|
||||||
|
- 导出数据时,zIndex 信息会丢失
|
||||||
|
- 重新导入数据后,图层顺序会错乱
|
||||||
|
|
||||||
|
**原因**: LogicFlow 的 `getGraphRawData()` 默认不包含 zIndex
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 问题3: 完整流程测试失败
|
||||||
|
|
||||||
|
**场景7: 完整用户流程测试**
|
||||||
|
|
||||||
|
由于问题2(zIndex 不保存),导致完整流程测试失败。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 问题4: 底层节点置底逻辑错误
|
||||||
|
|
||||||
|
**场景9: 边界情况 - 最底层节点继续置底**
|
||||||
|
```
|
||||||
|
初始 zIndex: { node1: 1, node2: 2, node3: 3 }
|
||||||
|
置底后 zIndex: { node1: 996, node2: 2, node3: 3 }
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**: node1 置底后 zIndex 变成 996,应该小于 1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心问题总结
|
||||||
|
|
||||||
|
### 🔴 严重问题
|
||||||
|
|
||||||
|
1. **zIndex 不会持久化**
|
||||||
|
- `getGraphRawData()` 不包含 zIndex
|
||||||
|
- 导出/导入数据会丢失图层信息
|
||||||
|
|
||||||
|
### 🟡 逻辑问题
|
||||||
|
|
||||||
|
2. **置底操作的实现有误**
|
||||||
|
- 应该设置为 `Math.min(...allZIndexes) - 1`
|
||||||
|
- 但实际上设置为固定值(998、996 等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
|
||||||
|
### 方案1: 修改 FlowEditor.vue 保存 zIndex
|
||||||
|
|
||||||
|
在 `FlowEditor.vue` 中,需要确保 zIndex 被保存到 properties 中:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在 NODE_ADD 事件中
|
||||||
|
lfInstance.on(EventType.NODE_ADD, ({ data }) => {
|
||||||
|
const model = lfInstance.getNodeModelById(data.id)
|
||||||
|
if (model) {
|
||||||
|
const newZIndex = 1000
|
||||||
|
model.setZIndex(newZIndex)
|
||||||
|
|
||||||
|
// 保存 zIndex 到 properties
|
||||||
|
lfInstance.setProperties(model.id, {
|
||||||
|
...model.getProperties(),
|
||||||
|
zIndex: newZIndex
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案2: 修改数据导出逻辑
|
||||||
|
|
||||||
|
在 Toolbar.vue 的 `handlePreviewData` 中,手动添加 zIndex:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const graphData = lf.getGraphRawData()
|
||||||
|
graphData.nodes.forEach((node: any) => {
|
||||||
|
const model = lf.getNodeModelById(node.id)
|
||||||
|
if (model) {
|
||||||
|
node.zIndex = model.zIndex
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案3: 修复置底逻辑
|
||||||
|
|
||||||
|
检查 LogicFlow 的 `setElementZIndex` 实现,或者自己实现置底逻辑。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 如何运行测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行所有测试
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# 只运行图层管理测试
|
||||||
|
npm test -- layer-management-real-scenario
|
||||||
|
|
||||||
|
# 监听模式
|
||||||
|
npm run test:watch -- layer-management-real-scenario
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试文件说明
|
||||||
|
|
||||||
|
### `layer-management-real-scenario.spec.ts`
|
||||||
|
- **真实场景测试**:直接使用 LogicFlow 实例
|
||||||
|
- **模拟用户操作**:创建节点、右键菜单、数据预览
|
||||||
|
- **可以发现真实问题**:不是 Mock,而是真实的代码逻辑
|
||||||
|
|
||||||
|
### `layer-management-real.spec.ts`(旧文件)
|
||||||
|
- **Mock 测试**:使用模拟类
|
||||||
|
- **只验证理想逻辑**:不能发现真实代码的问题
|
||||||
|
- **建议删除或重命名**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
1. ✅ 测试已经发现了真实问题
|
||||||
|
2. 🔧 需要修复 zIndex 持久化问题
|
||||||
|
3. 🔧 需要修复置底操作的逻辑
|
||||||
|
4. 📝 修复后重新运行测试验证
|
||||||
248
src/__tests__/README.md
Normal file
248
src/__tests__/README.md
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
# 测试文件说明
|
||||||
|
|
||||||
|
## 📂 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
src/__tests__/
|
||||||
|
├── setup.ts # 测试环境配置(ResizeObserver 等 polyfill)
|
||||||
|
├── TEST-RULES.md # 测试规范文档(必读!)
|
||||||
|
├── README-测试报告.md # 测试报告和问题分析
|
||||||
|
├── layer-management/ # 图层管理测试
|
||||||
|
│ ├── real-scenario.spec.ts # ✅ 真实场景测试(推荐)
|
||||||
|
│ ├── README.md # 图层管理测试说明
|
||||||
|
│ ├── mock-test.spec.ts.bak # 已废弃的 Mock 测试
|
||||||
|
│ ├── integration-test.spec.ts.bak # 已废弃的集成测试
|
||||||
|
│ └── unit-test.spec.ts.bak # 已废弃的单元测试
|
||||||
|
├── schema.test.ts # Schema 验证测试
|
||||||
|
└── useStore.test.ts # Store 测试
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 测试原则
|
||||||
|
|
||||||
|
### ✅ 推荐:真实场景测试
|
||||||
|
|
||||||
|
**优先使用真实的组件和实例**,而不是 Mock 对象。
|
||||||
|
|
||||||
|
#### 为什么?
|
||||||
|
|
||||||
|
1. **发现真实问题** - Mock 测试只能验证理想逻辑
|
||||||
|
2. **更接近用户体验** - 模拟真实的用户操作流程
|
||||||
|
3. **更可靠** - 测试通过意味着功能真的能用
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 推荐:使用真实的 LogicFlow 实例
|
||||||
|
import LogicFlow from '@logicflow/core'
|
||||||
|
|
||||||
|
const lf = new LogicFlow({
|
||||||
|
container: document.createElement('div'),
|
||||||
|
grid: { type: 'dot', size: 10 }
|
||||||
|
})
|
||||||
|
|
||||||
|
const node = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
lf.setElementZIndex(node.id, 'top')
|
||||||
|
|
||||||
|
// 这会发现真实问题!
|
||||||
|
const graphData = lf.getGraphRawData()
|
||||||
|
expect(graphData.nodes[0].zIndex).toBeDefined() // ❌ 失败!发现 bug
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ 不推荐:使用 Mock 对象
|
||||||
|
class MockLogicFlow {
|
||||||
|
addNode() { return { id: '1' } }
|
||||||
|
setElementZIndex() { /* 理想逻辑 */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这只能验证 Mock 的逻辑,无法发现真实代码的问题
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 运行所有测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行特定测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行图层管理测试
|
||||||
|
npm test -- layer-management
|
||||||
|
|
||||||
|
# 运行真实场景测试
|
||||||
|
npm test -- real-scenario
|
||||||
|
|
||||||
|
# 监听模式
|
||||||
|
npm run test:watch
|
||||||
|
|
||||||
|
# 查看详细输出
|
||||||
|
npm test -- --reporter=verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 文档导航
|
||||||
|
|
||||||
|
### 必读文档
|
||||||
|
|
||||||
|
1. **[TEST-RULES.md](./TEST-RULES.md)** - 测试规范和最佳实践
|
||||||
|
- 为什么要用真实场景测试
|
||||||
|
- 如何编写好的测试
|
||||||
|
- 何时使用 Mock
|
||||||
|
|
||||||
|
2. **[README-测试报告.md](./README-测试报告.md)** - 当前测试结果和问题分析
|
||||||
|
- 发现的问题
|
||||||
|
- 解决方案
|
||||||
|
- 测试覆盖率
|
||||||
|
|
||||||
|
### 模块文档
|
||||||
|
|
||||||
|
- **[layer-management/README.md](./layer-management/README.md)** - 图层管理测试说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 当前测试状态
|
||||||
|
|
||||||
|
### 图层管理测试
|
||||||
|
|
||||||
|
- **通过**: 5/9 ✅
|
||||||
|
- **失败**: 4/9 ❌
|
||||||
|
|
||||||
|
### 发现的问题
|
||||||
|
|
||||||
|
1. **zIndex 不会保存到数据中** - 导出/导入会丢失图层信息
|
||||||
|
2. **置底操作逻辑错误** - zIndex 计算不正确
|
||||||
|
|
||||||
|
详见 [README-测试报告.md](./README-测试报告.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 测试环境配置
|
||||||
|
|
||||||
|
### setup.ts
|
||||||
|
|
||||||
|
提供了必要的浏览器 API polyfill:
|
||||||
|
|
||||||
|
- `ResizeObserver`
|
||||||
|
- `IntersectionObserver`
|
||||||
|
- `window.matchMedia`
|
||||||
|
|
||||||
|
### vitest.config.js
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
globals: true,
|
||||||
|
setupFiles: ['./src/__tests__/setup.ts']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 编写新测试
|
||||||
|
|
||||||
|
### 1. 创建测试文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在对应的模块目录下创建
|
||||||
|
src/__tests__/your-module/real-scenario.spec.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用真实的依赖
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
||||||
|
import LogicFlow from '@logicflow/core'
|
||||||
|
|
||||||
|
describe('你的功能测试', () => {
|
||||||
|
let lf: LogicFlow | null = null
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// 创建真实的实例
|
||||||
|
const container = document.createElement('div')
|
||||||
|
document.body.appendChild(container)
|
||||||
|
lf = new LogicFlow({ container })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// 清理
|
||||||
|
lf?.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该能够...', () => {
|
||||||
|
// 模拟真实的用户操作
|
||||||
|
// 验证真实的结果
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 参考示例
|
||||||
|
|
||||||
|
参考 `layer-management/real-scenario.spec.ts` 了解如何:
|
||||||
|
- 使用真实的实例
|
||||||
|
- 模拟用户操作流程
|
||||||
|
- 提供清晰的调试信息
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❓ 常见问题
|
||||||
|
|
||||||
|
### Q: 为什么废弃 Mock 测试?
|
||||||
|
|
||||||
|
A: Mock 测试只能验证理想逻辑,无法发现真实代码的问题。例如:
|
||||||
|
- Mock 测试通过 ✅
|
||||||
|
- 但真实场景测试失败 ❌
|
||||||
|
- 发现了 zIndex 不持久化的 bug
|
||||||
|
|
||||||
|
### Q: 什么时候可以使用 Mock?
|
||||||
|
|
||||||
|
A: 只在以下情况使用:
|
||||||
|
- 外部 API 调用(HTTP 请求)
|
||||||
|
- 时间相关的测试(定时器)
|
||||||
|
- 文件系统操作
|
||||||
|
- 难以复现的场景(网络错误)
|
||||||
|
|
||||||
|
详见 [TEST-RULES.md](./TEST-RULES.md)
|
||||||
|
|
||||||
|
### Q: 测试运行很慢怎么办?
|
||||||
|
|
||||||
|
A:
|
||||||
|
1. 使用 `it.only()` 运行单个测试
|
||||||
|
2. 使用 `npm run test:watch` 监听模式
|
||||||
|
3. 只在关键路径使用真实场景测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 贡献指南
|
||||||
|
|
||||||
|
### 添加新测试
|
||||||
|
|
||||||
|
1. 阅读 [TEST-RULES.md](./TEST-RULES.md)
|
||||||
|
2. 创建测试文件
|
||||||
|
3. 使用真实的依赖
|
||||||
|
4. 模拟真实的用户操作
|
||||||
|
5. 运行测试验证
|
||||||
|
|
||||||
|
### 修复失败的测试
|
||||||
|
|
||||||
|
1. 查看 [README-测试报告.md](./README-测试报告.md)
|
||||||
|
2. 理解问题原因
|
||||||
|
3. 修复代码
|
||||||
|
4. 重新运行测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 需要帮助?
|
||||||
|
|
||||||
|
- 查看 [TEST-RULES.md](./TEST-RULES.md) 了解测试规范
|
||||||
|
- 查看 [README-测试报告.md](./README-测试报告.md) 了解当前问题
|
||||||
|
- 参考 `layer-management/real-scenario.spec.ts` 了解示例
|
||||||
203
src/__tests__/SUMMARY.md
Normal file
203
src/__tests__/SUMMARY.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# 测试文件整理完成 ✅
|
||||||
|
|
||||||
|
## 📁 新的文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
src/__tests__/
|
||||||
|
├── README.md # 📖 测试文件总览(从这里开始)
|
||||||
|
├── TEST-RULES.md # 📋 测试规范文档(必读)
|
||||||
|
├── README-测试报告.md # 📊 测试报告和问题分析
|
||||||
|
├── setup.ts # ⚙️ 测试环境配置
|
||||||
|
├── layer-management/ # 📂 图层管理测试
|
||||||
|
│ ├── README.md # 图层管理测试说明
|
||||||
|
│ ├── real-scenario.spec.ts # ✅ 真实场景测试(活跃)
|
||||||
|
│ ├── mock-test.spec.ts.bak # 🗄️ Mock 测试(已废弃)
|
||||||
|
│ ├── integration-test.spec.ts.bak # 🗄️ 集成测试(已废弃)
|
||||||
|
│ └── unit-test.spec.ts.bak # 🗄️ 单元测试(已废弃)
|
||||||
|
├── schema.test.ts # Schema 验证测试
|
||||||
|
└── useStore.test.ts # Store 测试
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ 主要改进
|
||||||
|
|
||||||
|
### 1. 文件组织
|
||||||
|
|
||||||
|
- ✅ 创建了 `layer-management/` 目录,集中管理图层相关测试
|
||||||
|
- ✅ 将 Mock 测试重命名为 `.bak`,标记为已废弃
|
||||||
|
- ✅ 保留了真实场景测试作为推荐方案
|
||||||
|
|
||||||
|
### 2. 文档完善
|
||||||
|
|
||||||
|
- ✅ **README.md** - 测试文件总览和快速开始
|
||||||
|
- ✅ **TEST-RULES.md** - 详细的测试规范和最佳实践
|
||||||
|
- ✅ **README-测试报告.md** - 当前测试结果和问题分析
|
||||||
|
- ✅ **layer-management/README.md** - 图层管理测试说明
|
||||||
|
|
||||||
|
### 3. 测试规范
|
||||||
|
|
||||||
|
明确了测试原则:
|
||||||
|
|
||||||
|
#### ✅ 推荐:真实场景测试
|
||||||
|
```typescript
|
||||||
|
// 使用真实的 LogicFlow 实例
|
||||||
|
const lf = new LogicFlow({ ... })
|
||||||
|
const node = lf.addNode({ ... })
|
||||||
|
lf.setElementZIndex(node.id, 'top')
|
||||||
|
|
||||||
|
// 能发现真实问题!
|
||||||
|
const graphData = lf.getGraphRawData()
|
||||||
|
expect(graphData.nodes[0].zIndex).toBeDefined() // ❌ 失败!
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ❌ 不推荐:Mock 测试
|
||||||
|
```typescript
|
||||||
|
// 使用模拟类
|
||||||
|
class MockLogicFlow { ... }
|
||||||
|
|
||||||
|
// 只能验证理想逻辑,无法发现真实问题
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 运行测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行所有测试
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# 运行图层管理测试
|
||||||
|
npm test -- layer-management
|
||||||
|
|
||||||
|
# 运行真实场景测试
|
||||||
|
npm test -- real-scenario
|
||||||
|
|
||||||
|
# 监听模式
|
||||||
|
npm run test:watch
|
||||||
|
|
||||||
|
# 查看详细输出
|
||||||
|
npm test -- --reporter=verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看文档
|
||||||
|
|
||||||
|
1. 先看 **README.md** - 了解整体结构
|
||||||
|
2. 再看 **TEST-RULES.md** - 学习测试规范
|
||||||
|
3. 参考 **layer-management/real-scenario.spec.ts** - 学习如何编写测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 当前测试状态
|
||||||
|
|
||||||
|
### 图层管理测试(9 个测试)
|
||||||
|
|
||||||
|
- ✅ **通过**: 5/9
|
||||||
|
- ❌ **失败**: 4/9
|
||||||
|
|
||||||
|
### 发现的真实问题
|
||||||
|
|
||||||
|
1. **zIndex 不会保存到数据中**
|
||||||
|
- `getGraphRawData()` 返回的数据中没有 zIndex
|
||||||
|
- 导致导出/导入会丢失图层信息
|
||||||
|
|
||||||
|
2. **置底操作逻辑错误**
|
||||||
|
- 置底后 zIndex 变成 998/996
|
||||||
|
- 应该是比所有节点都小的值
|
||||||
|
|
||||||
|
这些问题是通过**真实场景测试**发现的,Mock 测试无法发现!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 测试原则总结
|
||||||
|
|
||||||
|
### 为什么要用真实场景测试?
|
||||||
|
|
||||||
|
| 对比项 | Mock 测试 | 真实场景测试 |
|
||||||
|
|--------|-----------|--------------|
|
||||||
|
| 能否发现真实问题 | ❌ 不能 | ✅ 能 |
|
||||||
|
| 测试可靠性 | ⚠️ 低 | ✅ 高 |
|
||||||
|
| 接近用户体验 | ❌ 不接近 | ✅ 接近 |
|
||||||
|
| 维护成本 | ⚠️ 高(需要同步更新 Mock) | ✅ 低 |
|
||||||
|
|
||||||
|
### 何时使用 Mock?
|
||||||
|
|
||||||
|
只在以下情况使用:
|
||||||
|
- 外部 API 调用(HTTP 请求)
|
||||||
|
- 时间相关的测试(定时器)
|
||||||
|
- 文件系统操作
|
||||||
|
- 难以复现的场景(网络错误)
|
||||||
|
|
||||||
|
**核心业务逻辑必须使用真实场景测试!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 文档导航
|
||||||
|
|
||||||
|
### 必读文档
|
||||||
|
|
||||||
|
1. **[README.md](./README.md)** - 从这里开始
|
||||||
|
2. **[TEST-RULES.md](./TEST-RULES.md)** - 测试规范(必读)
|
||||||
|
3. **[README-测试报告.md](./README-测试报告.md)** - 当前问题分析
|
||||||
|
|
||||||
|
### 模块文档
|
||||||
|
|
||||||
|
- **[layer-management/README.md](./layer-management/README.md)** - 图层管理测试
|
||||||
|
|
||||||
|
### 示例代码
|
||||||
|
|
||||||
|
- **[layer-management/real-scenario.spec.ts](./layer-management/real-scenario.spec.ts)** - 真实场景测试示例
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 下一步
|
||||||
|
|
||||||
|
### 1. 修复发现的问题
|
||||||
|
|
||||||
|
参考 **README-测试报告.md** 中的解决方案:
|
||||||
|
|
||||||
|
- 修复 zIndex 持久化问题
|
||||||
|
- 修复置底操作逻辑
|
||||||
|
|
||||||
|
### 2. 添加更多真实场景测试
|
||||||
|
|
||||||
|
参考 **layer-management/real-scenario.spec.ts**,为其他功能添加测试:
|
||||||
|
|
||||||
|
- 节点拖拽
|
||||||
|
- 节点复制粘贴
|
||||||
|
- 节点分组
|
||||||
|
- 数据导入导出
|
||||||
|
|
||||||
|
### 3. 提高测试覆盖率
|
||||||
|
|
||||||
|
目标:
|
||||||
|
- 核心功能:80%+ 覆盖率
|
||||||
|
- 用户关键路径:100% 覆盖率
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 完成清单
|
||||||
|
|
||||||
|
- [x] 创建 `layer-management/` 目录
|
||||||
|
- [x] 移动并重命名测试文件
|
||||||
|
- [x] 废弃 Mock 测试(重命名为 .bak)
|
||||||
|
- [x] 创建 README.md(总览)
|
||||||
|
- [x] 创建 TEST-RULES.md(规范)
|
||||||
|
- [x] 创建 layer-management/README.md(模块说明)
|
||||||
|
- [x] 验证测试可以正常运行
|
||||||
|
- [x] 更新测试报告
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 总结
|
||||||
|
|
||||||
|
现在你有了:
|
||||||
|
|
||||||
|
1. **清晰的文件结构** - 测试文件按模块组织
|
||||||
|
2. **完善的文档** - 从入门到进阶的完整指南
|
||||||
|
3. **真实场景测试** - 能够发现真实代码问题
|
||||||
|
4. **测试规范** - 明确的最佳实践
|
||||||
|
|
||||||
|
**最重要的是**:真实场景测试已经发现了 2 个真实的 bug,这是 Mock 测试无法做到的!
|
||||||
294
src/__tests__/TEST-RULES.md
Normal file
294
src/__tests__/TEST-RULES.md
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
# 测试规范文档
|
||||||
|
|
||||||
|
## 测试原则
|
||||||
|
|
||||||
|
### ✅ 推荐:真实场景测试
|
||||||
|
|
||||||
|
**优先使用真实的组件和实例进行测试**,而不是 Mock 对象。
|
||||||
|
|
||||||
|
#### 为什么?
|
||||||
|
|
||||||
|
1. **发现真实问题** - Mock 测试只能验证理想逻辑,无法发现实际代码的 bug
|
||||||
|
2. **更接近用户体验** - 模拟真实的用户操作流程
|
||||||
|
3. **更可靠** - 测试通过意味着功能真的能用,而不只是理论上能用
|
||||||
|
|
||||||
|
#### 示例对比
|
||||||
|
|
||||||
|
❌ **不推荐:Mock 测试**
|
||||||
|
```typescript
|
||||||
|
// 使用模拟类
|
||||||
|
class MockLogicFlow {
|
||||||
|
nodes: MockNodeModel[] = []
|
||||||
|
addNode(config) {
|
||||||
|
const node = new MockNodeModel(...)
|
||||||
|
this.nodes.push(node)
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这种测试只能验证 Mock 的逻辑,不能发现真实代码的问题
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **推荐:真实场景测试**
|
||||||
|
```typescript
|
||||||
|
import LogicFlow from '@logicflow/core'
|
||||||
|
|
||||||
|
// 使用真实的 LogicFlow 实例
|
||||||
|
const lf = new LogicFlow({
|
||||||
|
container: document.createElement('div'),
|
||||||
|
grid: { type: 'dot', size: 10 }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟真实用户操作
|
||||||
|
const node = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
lf.setElementZIndex(node.id, 'top')
|
||||||
|
|
||||||
|
// 验证真实的数据
|
||||||
|
const graphData = lf.getGraphRawData()
|
||||||
|
expect(graphData.nodes[0].zIndex).toBeDefined() // 这会发现真实问题!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试文件组织
|
||||||
|
|
||||||
|
### 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
src/__tests__/
|
||||||
|
├── setup.ts # 测试环境配置
|
||||||
|
├── README-测试报告.md # 测试报告
|
||||||
|
├── layer-management/ # 图层管理测试
|
||||||
|
│ ├── real-scenario.spec.ts # ✅ 真实场景测试(推荐)
|
||||||
|
│ ├── mock-test.spec.ts.bak # ❌ Mock 测试(已废弃)
|
||||||
|
│ ├── integration-test.spec.ts.bak # ❌ 组件集成测试(已废弃)
|
||||||
|
│ └── unit-test.spec.ts.bak # ❌ 单元测试(已废弃)
|
||||||
|
├── schema.test.ts # Schema 验证测试
|
||||||
|
└── useStore.test.ts # Store 测试
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件命名规范
|
||||||
|
|
||||||
|
- `*.spec.ts` - 活跃的测试文件
|
||||||
|
- `*.spec.ts.bak` - 已废弃的测试文件(保留作为参考)
|
||||||
|
- `real-scenario.spec.ts` - 真实场景测试(推荐命名)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 编写测试的最佳实践
|
||||||
|
|
||||||
|
### 1. 使用真实的依赖
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ 好的做法
|
||||||
|
import LogicFlow from '@logicflow/core'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
const lf = new LogicFlow({ ... })
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
// ❌ 避免
|
||||||
|
class MockLogicFlow { ... }
|
||||||
|
const mockPinia = { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 模拟真实的用户操作流程
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
it('完整用户流程:创建节点 -> 图层操作 -> 验证数据', () => {
|
||||||
|
// 步骤 1: 用户从 ComponentsPanel 拖拽创建节点
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
|
||||||
|
// 步骤 2: 用户右键点击,选择"置于顶层"
|
||||||
|
lf.setElementZIndex(node1.id, 'top')
|
||||||
|
|
||||||
|
// 步骤 3: 用户点击 Toolbar 的"数据预览"
|
||||||
|
const graphData = lf.getGraphRawData()
|
||||||
|
|
||||||
|
// 步骤 4: 验证数据
|
||||||
|
expect(graphData.nodes[0].zIndex).toBeDefined()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 提供清晰的调试信息
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
it('置顶操作', () => {
|
||||||
|
console.log('初始 zIndex:', { node1: model1.zIndex, node2: model2.zIndex })
|
||||||
|
|
||||||
|
lf.setElementZIndex(node1.id, 'top')
|
||||||
|
|
||||||
|
console.log('置顶后 zIndex:', { node1: model1.zIndex, node2: model2.zIndex })
|
||||||
|
|
||||||
|
expect(model1.zIndex).toBeGreaterThan(model2.zIndex)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 测试边界情况
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
it('边界情况 - 最顶层节点继续置顶', () => {
|
||||||
|
// 测试极端情况
|
||||||
|
})
|
||||||
|
|
||||||
|
it('边界情况 - 最底层节点继续置底', () => {
|
||||||
|
// 测试极端情况
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试环境配置
|
||||||
|
|
||||||
|
### setup.ts
|
||||||
|
|
||||||
|
测试环境需要 polyfill 一些浏览器 API:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Mock ResizeObserver
|
||||||
|
global.ResizeObserver = class ResizeObserver {
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
disconnect() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock IntersectionObserver
|
||||||
|
global.IntersectionObserver = class IntersectionObserver {
|
||||||
|
constructor() {}
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
disconnect() {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### vitest.config.js
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default mergeConfig(
|
||||||
|
viteConfig,
|
||||||
|
defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
globals: true,
|
||||||
|
setupFiles: ['./src/__tests__/setup.ts'],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 运行测试
|
||||||
|
|
||||||
|
### 运行所有测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行特定测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行图层管理测试
|
||||||
|
npm test -- layer-management
|
||||||
|
|
||||||
|
# 运行特定文件
|
||||||
|
npm test -- real-scenario
|
||||||
|
|
||||||
|
# 监听模式
|
||||||
|
npm run test:watch -- layer-management
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看详细输出
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test -- layer-management --reporter=verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 何时使用 Mock?
|
||||||
|
|
||||||
|
虽然我们推荐真实场景测试,但在以下情况下可以使用 Mock:
|
||||||
|
|
||||||
|
### ✅ 适合使用 Mock 的场景
|
||||||
|
|
||||||
|
1. **外部 API 调用**
|
||||||
|
```typescript
|
||||||
|
// Mock HTTP 请求
|
||||||
|
vi.mock('axios')
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **时间相关的测试**
|
||||||
|
```typescript
|
||||||
|
// Mock 定时器
|
||||||
|
vi.useFakeTimers()
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **文件系统操作**
|
||||||
|
```typescript
|
||||||
|
// Mock fs 模块
|
||||||
|
vi.mock('fs')
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **难以复现的场景**
|
||||||
|
```typescript
|
||||||
|
// Mock 网络错误
|
||||||
|
vi.mock('fetch', () => ({ default: vi.fn(() => Promise.reject()) }))
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ 不适合使用 Mock 的场景
|
||||||
|
|
||||||
|
1. **核心业务逻辑** - 应该使用真实的类和方法
|
||||||
|
2. **UI 组件交互** - 应该使用真实的组件
|
||||||
|
3. **数据流转** - 应该使用真实的 Store 和状态管理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试覆盖率目标
|
||||||
|
|
||||||
|
- **核心功能**: 80%+ 覆盖率
|
||||||
|
- **边界情况**: 必须测试
|
||||||
|
- **用户关键路径**: 100% 覆盖
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 示例:图层管理测试
|
||||||
|
|
||||||
|
参考 `src/__tests__/layer-management/real-scenario.spec.ts`
|
||||||
|
|
||||||
|
这个测试文件展示了如何:
|
||||||
|
- ✅ 使用真实的 LogicFlow 实例
|
||||||
|
- ✅ 模拟真实的用户操作流程
|
||||||
|
- ✅ 发现真实的代码问题(zIndex 不持久化、置底逻辑错误)
|
||||||
|
- ✅ 提供清晰的调试信息
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q: 为什么我的测试通过了,但功能还是有问题?
|
||||||
|
|
||||||
|
A: 可能是因为你使用了 Mock 测试。Mock 测试只能验证理想逻辑,无法发现真实代码的问题。建议改用真实场景测试。
|
||||||
|
|
||||||
|
### Q: 真实场景测试运行很慢怎么办?
|
||||||
|
|
||||||
|
A:
|
||||||
|
1. 只在关键路径使用真实场景测试
|
||||||
|
2. 使用 `it.only()` 运行单个测试
|
||||||
|
3. 考虑使用 E2E 测试工具(Playwright、Cypress)
|
||||||
|
|
||||||
|
### Q: 如何测试需要浏览器环境的功能?
|
||||||
|
|
||||||
|
A:
|
||||||
|
1. 使用 jsdom 环境(已配置)
|
||||||
|
2. 添加必要的 polyfill(见 setup.ts)
|
||||||
|
3. 如果 jsdom 不够,考虑使用 Playwright
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
- **2024-01-XX**: 创建测试规范文档
|
||||||
|
- **2024-01-XX**: 添加真实场景测试示例
|
||||||
|
- **2024-01-XX**: 废弃 Mock 测试,推荐真实场景测试
|
||||||
159
src/__tests__/integration-zindex.spec.ts
Normal file
159
src/__tests__/integration-zindex.spec.ts
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
||||||
|
import LogicFlow, { EventType } from '@logicflow/core'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集成测试:验证拖拽创建节点时 zIndex 初始值为 1000
|
||||||
|
*
|
||||||
|
* 这个测试模拟了完整的用户流程:
|
||||||
|
* 1. 从 ComponentsPanel 拖拽创建节点
|
||||||
|
* 2. FlowEditor 的 NODE_ADD 事件监听器设置 zIndex 为 1000
|
||||||
|
* 3. 验证新节点的 zIndex 确实是 1000
|
||||||
|
*/
|
||||||
|
describe('集成测试:拖拽创建节点 zIndex 为 1000', () => {
|
||||||
|
let lf: LogicFlow | null = null
|
||||||
|
let container: HTMLDivElement | null = null
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
container = document.createElement('div')
|
||||||
|
container.style.width = '800px'
|
||||||
|
container.style.height = '600px'
|
||||||
|
document.body.appendChild(container)
|
||||||
|
|
||||||
|
lf = new LogicFlow({
|
||||||
|
container,
|
||||||
|
grid: { type: 'dot', size: 10 },
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟 FlowEditor.vue 中的 NODE_ADD 事件监听器(第 947-962 行)
|
||||||
|
lf.on(EventType.NODE_ADD, ({ data }) => {
|
||||||
|
console.log('[NODE_ADD 事件触发] 节点ID:', data.id)
|
||||||
|
const model = lf!.getNodeModelById(data.id)
|
||||||
|
if (model) {
|
||||||
|
console.log('[NODE_ADD] 获取到节点模型,当前 zIndex:', model.zIndex)
|
||||||
|
const newZIndex = 1000
|
||||||
|
console.log(`[NODE_ADD] 准备设置 zIndex: ${newZIndex}`)
|
||||||
|
model.setZIndex(newZIndex)
|
||||||
|
console.log(`[NODE_ADD] 设置后的 zIndex:`, model.zIndex)
|
||||||
|
} else {
|
||||||
|
console.log('[NODE_ADD] 未能获取到节点模型')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
lf.render({ nodes: [], edges: [] })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (lf) {
|
||||||
|
lf.destroy()
|
||||||
|
lf = null
|
||||||
|
}
|
||||||
|
if (container && container.parentNode) {
|
||||||
|
container.parentNode.removeChild(container)
|
||||||
|
container = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景:用户从组件面板拖拽创建节点', () => {
|
||||||
|
console.log('\n=== 场景:用户从组件面板拖拽创建节点 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 步骤 1: 用户从 ComponentsPanel 拖拽一个长方形组件
|
||||||
|
console.log('\n步骤 1: 拖拽长方形组件到画布')
|
||||||
|
const rectNode = lf.addNode({
|
||||||
|
type: 'rect',
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
properties: {
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
style: { background: '#fff', border: '2px solid black' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const rectModel = lf.getNodeModelById(rectNode.id)
|
||||||
|
console.log('长方形节点 zIndex:', rectModel?.zIndex)
|
||||||
|
expect(rectModel?.zIndex).toBe(1000)
|
||||||
|
|
||||||
|
// 步骤 2: 用户拖拽一个圆形组件
|
||||||
|
console.log('\n步骤 2: 拖拽圆形组件到画布')
|
||||||
|
const ellipseNode = lf.addNode({
|
||||||
|
type: 'ellipse',
|
||||||
|
x: 200,
|
||||||
|
y: 200,
|
||||||
|
properties: {
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
style: { background: '#fff', border: '2px solid black', borderRadius: '50%' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const ellipseModel = lf.getNodeModelById(ellipseNode.id)
|
||||||
|
console.log('圆形节点 zIndex:', ellipseModel?.zIndex)
|
||||||
|
expect(ellipseModel?.zIndex).toBe(1000)
|
||||||
|
|
||||||
|
// 步骤 3: 用户拖拽一个菱形组件
|
||||||
|
console.log('\n步骤 3: 拖拽菱形组件到画布')
|
||||||
|
const diamondNode = lf.addNode({
|
||||||
|
type: 'diamond',
|
||||||
|
x: 300,
|
||||||
|
y: 300,
|
||||||
|
properties: {
|
||||||
|
width: 150,
|
||||||
|
height: 150
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const diamondModel = lf.getNodeModelById(diamondNode.id)
|
||||||
|
console.log('菱形节点 zIndex:', diamondModel?.zIndex)
|
||||||
|
expect(diamondModel?.zIndex).toBe(1000)
|
||||||
|
|
||||||
|
// 验证所有节点
|
||||||
|
console.log('\n验证:所有新创建的节点 zIndex 都是 1000')
|
||||||
|
const allNodes = lf.graphModel.nodes
|
||||||
|
console.log('所有节点的 zIndex:', allNodes.map(n => ({ id: n.id, type: n.type, zIndex: n.zIndex })))
|
||||||
|
|
||||||
|
allNodes.forEach(node => {
|
||||||
|
expect(node.zIndex).toBe(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\n✅ 测试通过:所有拖拽创建的节点初始 zIndex 都是 1000')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景:新节点的 zIndex 不会影响图层操作', () => {
|
||||||
|
console.log('\n=== 场景:新节点的 zIndex 不会影响图层操作 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建 3 个节点(都是 zIndex 1000)
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
console.log('初始状态(所有节点 zIndex 都是 1000):', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 执行置顶操作
|
||||||
|
console.log('\n执行:将 node1 置于顶层')
|
||||||
|
lf.setElementZIndex(node1.id, 'top')
|
||||||
|
|
||||||
|
console.log('操作后:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证:node1 的 zIndex 应该大于其他节点
|
||||||
|
expect(model1?.zIndex).toBeGreaterThan(model2?.zIndex || 0)
|
||||||
|
expect(model1?.zIndex).toBeGreaterThan(model3?.zIndex || 0)
|
||||||
|
|
||||||
|
console.log('\n✅ 测试通过:即使初始 zIndex 相同,图层操作仍然正常工作')
|
||||||
|
})
|
||||||
|
})
|
||||||
94
src/__tests__/layer-management/README.md
Normal file
94
src/__tests__/layer-management/README.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# 图层管理测试
|
||||||
|
|
||||||
|
## 📁 文件说明
|
||||||
|
|
||||||
|
### ✅ 活跃的测试
|
||||||
|
|
||||||
|
- **`real-scenario.spec.ts`** - 真实场景测试(推荐使用)
|
||||||
|
- 使用真实的 LogicFlow 实例
|
||||||
|
- 模拟真实的用户操作流程
|
||||||
|
- 能够发现真实的代码问题
|
||||||
|
|
||||||
|
### 📦 已废弃的测试(仅供参考)
|
||||||
|
|
||||||
|
- **`mock-test.spec.ts.bak`** - Mock 测试
|
||||||
|
- 使用模拟类(MockLogicFlow)
|
||||||
|
- 只能验证理想逻辑
|
||||||
|
- 无法发现真实代码的问题
|
||||||
|
|
||||||
|
- **`integration-test.spec.ts.bak`** - 组件集成测试
|
||||||
|
- 尝试挂载 Vue 组件
|
||||||
|
- 因为缺少浏览器 API 而失败
|
||||||
|
|
||||||
|
- **`unit-test.spec.ts.bak`** - 单元测试
|
||||||
|
- 测试单个函数
|
||||||
|
- 覆盖范围有限
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 运行测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行图层管理测试
|
||||||
|
npm test -- layer-management
|
||||||
|
|
||||||
|
# 只运行真实场景测试
|
||||||
|
npm test -- real-scenario
|
||||||
|
|
||||||
|
# 监听模式
|
||||||
|
npm run test:watch -- layer-management
|
||||||
|
|
||||||
|
# 查看详细输出
|
||||||
|
npm test -- layer-management --reporter=verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 测试结果
|
||||||
|
|
||||||
|
### 当前状态(9 个测试)
|
||||||
|
|
||||||
|
- ✅ **通过**: 5/9
|
||||||
|
- ❌ **失败**: 4/9
|
||||||
|
|
||||||
|
### 发现的问题
|
||||||
|
|
||||||
|
1. **zIndex 不会保存到数据中**
|
||||||
|
- `getGraphRawData()` 返回的数据中没有 zIndex
|
||||||
|
- 导致导出/导入数据会丢失图层信息
|
||||||
|
|
||||||
|
2. **置底操作逻辑错误**
|
||||||
|
- 置底后 zIndex 变成 998/996
|
||||||
|
- 应该是比所有节点都小的值
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 测试场景
|
||||||
|
|
||||||
|
### ✅ 通过的测试
|
||||||
|
|
||||||
|
1. **场景1**: 创建节点并验证 zIndex 分配
|
||||||
|
2. **场景2**: 置顶操作(模拟右键菜单)
|
||||||
|
3. **场景4**: 上移一层操作
|
||||||
|
4. **场景5**: 下移一层操作
|
||||||
|
5. **场景8**: 边界情况 - 最顶层节点继续置顶
|
||||||
|
|
||||||
|
### ❌ 失败的测试
|
||||||
|
|
||||||
|
1. **场景3**: 置底操作(模拟右键菜单)
|
||||||
|
2. **场景6**: 数据预览验证(模拟 Toolbar.handlePreviewData)
|
||||||
|
3. **场景7**: 完整用户流程测试
|
||||||
|
4. **场景9**: 边界情况 - 最底层节点继续置底
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 如何修复
|
||||||
|
|
||||||
|
参考 `../README-测试报告.md` 中的解决方案。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 相关文档
|
||||||
|
|
||||||
|
- [测试规范文档](../TEST-RULES.md)
|
||||||
|
- [测试报告](../README-测试报告.md)
|
||||||
483
src/__tests__/layer-management/integration-test.spec.ts.bak
Normal file
483
src/__tests__/layer-management/integration-test.spec.ts.bak
Normal file
@@ -0,0 +1,483 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||||
|
import { mount, flushPromises } from '@vue/test-utils'
|
||||||
|
import { createPinia, setActivePinia } from 'pinia'
|
||||||
|
import FlowEditor from '@/components/flow/FlowEditor.vue'
|
||||||
|
import ComponentsPanel from '@/components/flow/ComponentsPanel.vue'
|
||||||
|
import { getLogicFlowInstance, setLogicFlowInstance } from '@/ts/useLogicFlow'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图层管理集成测试 - 真实组件交互
|
||||||
|
*
|
||||||
|
* 这个测试模拟真实的用户操作流程:
|
||||||
|
* 1. 从 ComponentsPanel 拖拽创建节点
|
||||||
|
* 2. 在 FlowEditor 中执行图层操作(置顶、置底、上移、下移)
|
||||||
|
* 3. 验证 zIndex 的变化
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('图层管理集成测试 - 真实组件交互', () => {
|
||||||
|
let pinia: ReturnType<typeof createPinia>
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// 创建新的 Pinia 实例
|
||||||
|
pinia = createPinia()
|
||||||
|
setActivePinia(pinia)
|
||||||
|
|
||||||
|
// 清理 LogicFlow 实例
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该能够创建节点并验证 zIndex', async () => {
|
||||||
|
console.log('\n=== 测试:创建节点并验证 zIndex ===')
|
||||||
|
|
||||||
|
// 挂载 FlowEditor 组件
|
||||||
|
const wrapper = mount(FlowEditor, {
|
||||||
|
global: {
|
||||||
|
plugins: [pinia],
|
||||||
|
stubs: {
|
||||||
|
PropertyPanel: true, // 暂时 stub 掉属性面板
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
height: '600px'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
// 获取 LogicFlow 实例
|
||||||
|
const lf = getLogicFlowInstance()
|
||||||
|
expect(lf).toBeTruthy()
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 模拟创建 3 个节点
|
||||||
|
console.log('创建节点...')
|
||||||
|
const node1 = lf.addNode({
|
||||||
|
type: 'rect',
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
properties: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const node2 = lf.addNode({
|
||||||
|
type: 'rect',
|
||||||
|
x: 200,
|
||||||
|
y: 200,
|
||||||
|
properties: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const node3 = lf.addNode({
|
||||||
|
type: 'rect',
|
||||||
|
x: 300,
|
||||||
|
y: 300,
|
||||||
|
properties: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
// 获取节点模型
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
console.log('节点创建后的 zIndex:')
|
||||||
|
console.log(` node1: ${model1?.zIndex}`)
|
||||||
|
console.log(` node2: ${model2?.zIndex}`)
|
||||||
|
console.log(` node3: ${model3?.zIndex}`)
|
||||||
|
|
||||||
|
// 验证节点都有 zIndex
|
||||||
|
expect(model1?.zIndex).toBeDefined()
|
||||||
|
expect(model2?.zIndex).toBeDefined()
|
||||||
|
expect(model3?.zIndex).toBeDefined()
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该能够执行置顶操作', async () => {
|
||||||
|
console.log('\n=== 测试:置顶操作 ===')
|
||||||
|
|
||||||
|
const wrapper = mount(FlowEditor, {
|
||||||
|
global: {
|
||||||
|
plugins: [pinia],
|
||||||
|
stubs: {
|
||||||
|
PropertyPanel: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
height: '600px'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const lf = getLogicFlowInstance()
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建 3 个节点,手动设置不同的 zIndex
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
// 手动设置初始 zIndex
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
console.log('初始 zIndex:')
|
||||||
|
console.log(` node1: ${model1?.zIndex}`)
|
||||||
|
console.log(` node2: ${model2?.zIndex}`)
|
||||||
|
console.log(` node3: ${model3?.zIndex}`)
|
||||||
|
|
||||||
|
// 执行置顶操作:将 node1 置顶
|
||||||
|
lf.setElementZIndex(node1.id, 'top')
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
console.log('置顶后 zIndex:')
|
||||||
|
console.log(` node1: ${model1?.zIndex}`)
|
||||||
|
console.log(` node2: ${model2?.zIndex}`)
|
||||||
|
console.log(` node3: ${model3?.zIndex}`)
|
||||||
|
|
||||||
|
// 验证 node1 的 zIndex 最大
|
||||||
|
const allZIndexes = [model1?.zIndex, model2?.zIndex, model3?.zIndex].filter(z => z !== undefined) as number[]
|
||||||
|
expect(model1?.zIndex).toBe(Math.max(...allZIndexes))
|
||||||
|
expect(model1?.zIndex).toBeGreaterThan(model2?.zIndex || 0)
|
||||||
|
expect(model1?.zIndex).toBeGreaterThan(model3?.zIndex || 0)
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该能够执行置底操作', async () => {
|
||||||
|
console.log('\n=== 测试:置底操作 ===')
|
||||||
|
|
||||||
|
const wrapper = mount(FlowEditor, {
|
||||||
|
global: {
|
||||||
|
plugins: [pinia],
|
||||||
|
stubs: {
|
||||||
|
PropertyPanel: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
height: '600px'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const lf = getLogicFlowInstance()
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建 3 个节点
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
// 手动设置初始 zIndex
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
console.log('初始 zIndex:')
|
||||||
|
console.log(` node1: ${model1?.zIndex}`)
|
||||||
|
console.log(` node2: ${model2?.zIndex}`)
|
||||||
|
console.log(` node3: ${model3?.zIndex}`)
|
||||||
|
|
||||||
|
// 执行置底操作:将 node3 置底
|
||||||
|
lf.setElementZIndex(node3.id, 'bottom')
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
console.log('置底后 zIndex:')
|
||||||
|
console.log(` node1: ${model1?.zIndex}`)
|
||||||
|
console.log(` node2: ${model2?.zIndex}`)
|
||||||
|
console.log(` node3: ${model3?.zIndex}`)
|
||||||
|
|
||||||
|
// 验证 node3 的 zIndex 最小
|
||||||
|
const allZIndexes = [model1?.zIndex, model2?.zIndex, model3?.zIndex].filter(z => z !== undefined) as number[]
|
||||||
|
expect(model3?.zIndex).toBe(Math.min(...allZIndexes))
|
||||||
|
expect(model3?.zIndex).toBeLessThan(model1?.zIndex || Infinity)
|
||||||
|
expect(model3?.zIndex).toBeLessThan(model2?.zIndex || Infinity)
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该能够执行上移和下移操作', async () => {
|
||||||
|
console.log('\n=== 测试:上移和下移操作 ===')
|
||||||
|
|
||||||
|
const wrapper = mount(FlowEditor, {
|
||||||
|
global: {
|
||||||
|
plugins: [pinia],
|
||||||
|
stubs: {
|
||||||
|
PropertyPanel: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
height: '600px'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const lf = getLogicFlowInstance()
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建 3 个节点
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
// 手动设置初始 zIndex
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
console.log('初始 zIndex:')
|
||||||
|
console.log(` node1: ${model1?.zIndex}`)
|
||||||
|
console.log(` node2: ${model2?.zIndex}`)
|
||||||
|
console.log(` node3: ${model3?.zIndex}`)
|
||||||
|
|
||||||
|
const originalZIndex1 = model1?.zIndex
|
||||||
|
const originalZIndex2 = model2?.zIndex
|
||||||
|
|
||||||
|
// 测试上移:node1 上移一层(应该与 node2 交换)
|
||||||
|
// 需要调用 FlowEditor 中的 bringForward 方法
|
||||||
|
// 由于方法不是暴露的,我们直接操作 LogicFlow
|
||||||
|
const allNodes = lf.graphModel.nodes
|
||||||
|
const currentNode = model1
|
||||||
|
if (currentNode) {
|
||||||
|
const currentZIndex = currentNode.zIndex
|
||||||
|
const nodesAbove = allNodes
|
||||||
|
.filter((node) => node.zIndex > currentZIndex)
|
||||||
|
.sort((a, b) => a.zIndex - b.zIndex)
|
||||||
|
|
||||||
|
if (nodesAbove.length > 0) {
|
||||||
|
const nextNode = nodesAbove[0]
|
||||||
|
currentNode.setZIndex(nextNode.zIndex)
|
||||||
|
nextNode.setZIndex(currentZIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
console.log('上移后 zIndex:')
|
||||||
|
console.log(` node1: ${model1?.zIndex}`)
|
||||||
|
console.log(` node2: ${model2?.zIndex}`)
|
||||||
|
console.log(` node3: ${model3?.zIndex}`)
|
||||||
|
|
||||||
|
// 验证 zIndex 已交换
|
||||||
|
expect(model1?.zIndex).toBe(originalZIndex2)
|
||||||
|
expect(model2?.zIndex).toBe(originalZIndex1)
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该能够通过右键菜单执行图层操作', async () => {
|
||||||
|
console.log('\n=== 测试:右键菜单图层操作 ===')
|
||||||
|
|
||||||
|
const wrapper = mount(FlowEditor, {
|
||||||
|
global: {
|
||||||
|
plugins: [pinia],
|
||||||
|
stubs: {
|
||||||
|
PropertyPanel: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
height: '600px'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const lf = getLogicFlowInstance()
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建节点
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
|
||||||
|
console.log('初始 zIndex:')
|
||||||
|
console.log(` node1: ${model1?.zIndex}`)
|
||||||
|
console.log(` node2: ${model2?.zIndex}`)
|
||||||
|
|
||||||
|
// 模拟右键菜单的"置于顶层"操作
|
||||||
|
// 在真实场景中,这会通过菜单回调触发
|
||||||
|
const menuConfig = (lf.extension as any).menu.menuConfig
|
||||||
|
const bringToFrontMenuItem = menuConfig.nodeMenu.find((item: any) => item.text === '置于顶层')
|
||||||
|
|
||||||
|
if (bringToFrontMenuItem) {
|
||||||
|
// 执行菜单回调
|
||||||
|
bringToFrontMenuItem.callback({ id: node1.id })
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
console.log('执行"置于顶层"后 zIndex:')
|
||||||
|
console.log(` node1: ${model1?.zIndex}`)
|
||||||
|
console.log(` node2: ${model2?.zIndex}`)
|
||||||
|
|
||||||
|
// 验证 node1 现在在最上层
|
||||||
|
expect(model1?.zIndex).toBeGreaterThan(model2?.zIndex || 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('应该能够验证数据预览中的 zIndex', async () => {
|
||||||
|
console.log('\n=== 测试:数据预览中的 zIndex ===')
|
||||||
|
|
||||||
|
const wrapper = mount(FlowEditor, {
|
||||||
|
global: {
|
||||||
|
plugins: [pinia],
|
||||||
|
stubs: {
|
||||||
|
PropertyPanel: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
height: '600px'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const lf = getLogicFlowInstance()
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建节点
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
// 执行置顶操作
|
||||||
|
lf.setElementZIndex(node1.id, 'top')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
// 获取图数据(模拟 Toolbar 的 handlePreviewData)
|
||||||
|
const graphData = lf.getGraphRawData()
|
||||||
|
|
||||||
|
console.log('数据预览:')
|
||||||
|
console.log(JSON.stringify(graphData, null, 2))
|
||||||
|
|
||||||
|
// 验证数据中包含 zIndex
|
||||||
|
expect(graphData.nodes).toBeDefined()
|
||||||
|
expect(graphData.nodes.length).toBe(3)
|
||||||
|
|
||||||
|
graphData.nodes.forEach((node: any) => {
|
||||||
|
expect(node.zIndex).toBeDefined()
|
||||||
|
expect(typeof node.zIndex).toBe('number')
|
||||||
|
console.log(`节点 ${node.id} 的 zIndex: ${node.zIndex}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证 node1 的 zIndex 最大
|
||||||
|
const node1Data = graphData.nodes.find((n: any) => n.id === node1.id)
|
||||||
|
const allZIndexes = graphData.nodes.map((n: any) => n.zIndex)
|
||||||
|
expect(node1Data?.zIndex).toBe(Math.max(...allZIndexes))
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('完整流程:创建节点 -> 图层操作 -> 验证数据', async () => {
|
||||||
|
console.log('\n=== 测试:完整流程 ===')
|
||||||
|
|
||||||
|
const wrapper = mount(FlowEditor, {
|
||||||
|
global: {
|
||||||
|
plugins: [pinia],
|
||||||
|
stubs: {
|
||||||
|
PropertyPanel: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
height: '600px'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const lf = getLogicFlowInstance()
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 步骤 1: 创建 4 个节点(模拟从 ComponentsPanel 拖拽)
|
||||||
|
console.log('步骤 1: 创建节点')
|
||||||
|
const nodes = [
|
||||||
|
lf.addNode({ type: 'rect', x: 100, y: 100 }),
|
||||||
|
lf.addNode({ type: 'rect', x: 200, y: 200 }),
|
||||||
|
lf.addNode({ type: 'rect', x: 300, y: 300 }),
|
||||||
|
lf.addNode({ type: 'rect', x: 400, y: 400 })
|
||||||
|
]
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const models = nodes.map(n => lf.getNodeModelById(n.id))
|
||||||
|
models.forEach((m, i) => m?.setZIndex(i + 1))
|
||||||
|
|
||||||
|
console.log('初始状态:', models.map((m, i) => ({ id: nodes[i].id, zIndex: m?.zIndex })))
|
||||||
|
|
||||||
|
// 步骤 2: 执行图层操作
|
||||||
|
console.log('\n步骤 2: 执行图层操作')
|
||||||
|
|
||||||
|
// node1 置顶
|
||||||
|
lf.setElementZIndex(nodes[0].id, 'top')
|
||||||
|
await flushPromises()
|
||||||
|
console.log('node1 置顶后:', models.map((m, i) => ({ id: nodes[i].id, zIndex: m?.zIndex })))
|
||||||
|
|
||||||
|
// node4 置底
|
||||||
|
lf.setElementZIndex(nodes[3].id, 'bottom')
|
||||||
|
await flushPromises()
|
||||||
|
console.log('node4 置底后:', models.map((m, i) => ({ id: nodes[i].id, zIndex: m?.zIndex })))
|
||||||
|
|
||||||
|
// 步骤 3: 验证数据(模拟 Toolbar 的 handlePreviewData)
|
||||||
|
console.log('\n步骤 3: 验证数据')
|
||||||
|
const graphData = lf.getGraphRawData()
|
||||||
|
|
||||||
|
console.log('最终数据预览:')
|
||||||
|
graphData.nodes.forEach((node: any) => {
|
||||||
|
console.log(` 节点 ${node.id}: zIndex = ${node.zIndex}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证最终顺序
|
||||||
|
const sortedNodes = [...graphData.nodes].sort((a: any, b: any) => a.zIndex - b.zIndex)
|
||||||
|
|
||||||
|
// node4 应该在最底层
|
||||||
|
expect(sortedNodes[0].id).toBe(nodes[3].id)
|
||||||
|
|
||||||
|
// node1 应该在最顶层
|
||||||
|
expect(sortedNodes[sortedNodes.length - 1].id).toBe(nodes[0].id)
|
||||||
|
|
||||||
|
console.log('\n✅ 完整流程测试通过!')
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
})
|
||||||
443
src/__tests__/layer-management/mock-test.spec.ts.bak
Normal file
443
src/__tests__/layer-management/mock-test.spec.ts.bak
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图层管理集成测试
|
||||||
|
*
|
||||||
|
* 这个测试模拟真实的图层操作场景,验证:
|
||||||
|
* 1. 节点创建时的 zIndex 分配
|
||||||
|
* 2. 上移、下移、置顶、置底操作的正确性
|
||||||
|
* 3. 数据持久化后 zIndex 的保存
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 模拟 LogicFlow 节点模型
|
||||||
|
class MockNodeModel {
|
||||||
|
id: string
|
||||||
|
zIndex: number
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
|
||||||
|
constructor(id: string, x: number, y: number, zIndex: number = 1) {
|
||||||
|
this.id = id
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
this.zIndex = zIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
setZIndex(zIndex: number) {
|
||||||
|
this.zIndex = zIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟 LogicFlow 实例
|
||||||
|
class MockLogicFlow {
|
||||||
|
nodes: MockNodeModel[] = []
|
||||||
|
private nodeIdCounter = 0
|
||||||
|
|
||||||
|
addNode(config: { x: number; y: number; zIndex?: number }) {
|
||||||
|
const id = `node_${++this.nodeIdCounter}`
|
||||||
|
const zIndex = config.zIndex ?? 1000 // 新节点默认 zIndex 为 1000
|
||||||
|
const node = new MockNodeModel(id, config.x, config.y, zIndex)
|
||||||
|
this.nodes.push(node)
|
||||||
|
console.log(`[NODE_ADD] 创建节点 ${id}, zIndex: ${zIndex}`)
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeModelById(id: string) {
|
||||||
|
return this.nodes.find(n => n.id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
setElementZIndex(id: string, zIndexOrPosition: number | 'top' | 'bottom') {
|
||||||
|
const node = this.getNodeModelById(id)
|
||||||
|
if (!node) return
|
||||||
|
|
||||||
|
if (zIndexOrPosition === 'top') {
|
||||||
|
const maxZIndex = Math.max(...this.nodes.map(n => n.zIndex))
|
||||||
|
node.setZIndex(maxZIndex + 1)
|
||||||
|
console.log(`[置于顶层] ${id}: ${node.zIndex}`)
|
||||||
|
} else if (zIndexOrPosition === 'bottom') {
|
||||||
|
const minZIndex = Math.min(...this.nodes.map(n => n.zIndex))
|
||||||
|
node.setZIndex(minZIndex - 1)
|
||||||
|
console.log(`[置于底层] ${id}: ${node.zIndex}`)
|
||||||
|
} else {
|
||||||
|
node.setZIndex(zIndexOrPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟上移一层
|
||||||
|
bringForward(id: string) {
|
||||||
|
const currentNode = this.getNodeModelById(id)
|
||||||
|
if (!currentNode) return
|
||||||
|
|
||||||
|
const currentZIndex = currentNode.zIndex
|
||||||
|
const nodesAbove = this.nodes
|
||||||
|
.filter(node => node.zIndex > currentZIndex)
|
||||||
|
.sort((a, b) => a.zIndex - b.zIndex)
|
||||||
|
|
||||||
|
if (nodesAbove.length > 0) {
|
||||||
|
const nextNode = nodesAbove[0]
|
||||||
|
const tempZIndex = currentNode.zIndex
|
||||||
|
currentNode.setZIndex(nextNode.zIndex)
|
||||||
|
nextNode.setZIndex(tempZIndex)
|
||||||
|
console.log(`[上移一层] ${id}: ${tempZIndex} -> ${currentNode.zIndex}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟下移一层
|
||||||
|
sendBackward(id: string) {
|
||||||
|
const currentNode = this.getNodeModelById(id)
|
||||||
|
if (!currentNode) return
|
||||||
|
|
||||||
|
const currentZIndex = currentNode.zIndex
|
||||||
|
const nodesBelow = this.nodes
|
||||||
|
.filter(node => node.zIndex < currentZIndex)
|
||||||
|
.sort((a, b) => b.zIndex - a.zIndex)
|
||||||
|
|
||||||
|
if (nodesBelow.length > 0) {
|
||||||
|
const prevNode = nodesBelow[0]
|
||||||
|
const tempZIndex = currentNode.zIndex
|
||||||
|
currentNode.setZIndex(prevNode.zIndex)
|
||||||
|
prevNode.setZIndex(tempZIndex)
|
||||||
|
console.log(`[下移一层] ${id}: ${tempZIndex} -> ${currentNode.zIndex}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取图数据(模拟数据预览)
|
||||||
|
getGraphRawData() {
|
||||||
|
return {
|
||||||
|
nodes: this.nodes.map(n => ({
|
||||||
|
id: n.id,
|
||||||
|
x: n.x,
|
||||||
|
y: n.y,
|
||||||
|
zIndex: n.zIndex
|
||||||
|
})),
|
||||||
|
edges: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printAllNodes() {
|
||||||
|
console.log('所有节点 zIndex:', this.nodes.map(n => ({ id: n.id, zIndex: n.zIndex })))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('图层管理集成测试 - 真实场景模拟', () => {
|
||||||
|
let lf: MockLogicFlow
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
lf = new MockLogicFlow()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('1. 创建多个节点并检查zIndex', () => {
|
||||||
|
it('应该为新创建的节点分配正确的zIndex', () => {
|
||||||
|
console.log('\n=== 测试:创建节点并检查 zIndex ===')
|
||||||
|
|
||||||
|
// 创建3个节点
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300 })
|
||||||
|
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 验证每个节点都有zIndex
|
||||||
|
expect(node1.zIndex).toBeDefined()
|
||||||
|
expect(node2.zIndex).toBeDefined()
|
||||||
|
expect(node3.zIndex).toBeDefined()
|
||||||
|
|
||||||
|
// 验证zIndex是数字
|
||||||
|
expect(typeof node1.zIndex).toBe('number')
|
||||||
|
expect(typeof node2.zIndex).toBe('number')
|
||||||
|
expect(typeof node3.zIndex).toBe('number')
|
||||||
|
|
||||||
|
// 新节点应该有较高的zIndex(默认1000)
|
||||||
|
expect(node1.zIndex).toBe(1000)
|
||||||
|
expect(node2.zIndex).toBe(1000)
|
||||||
|
expect(node3.zIndex).toBe(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('多个节点的zIndex应该各不相同', () => {
|
||||||
|
console.log('\n=== 测试:多个节点 zIndex 唯一性 ===')
|
||||||
|
|
||||||
|
// 创建4个节点,手动设置不同的zIndex
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
const node4 = lf.addNode({ x: 400, y: 400, zIndex: 4 })
|
||||||
|
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
const zIndexes = [node1.zIndex, node2.zIndex, node3.zIndex, node4.zIndex]
|
||||||
|
const uniqueZIndexes = new Set(zIndexes)
|
||||||
|
|
||||||
|
// 所有zIndex应该是唯一的
|
||||||
|
expect(uniqueZIndexes.size).toBe(4)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('2. 节点上移操作', () => {
|
||||||
|
it('上移一层应该与上方节点交换zIndex', () => {
|
||||||
|
console.log('\n=== 测试:上移一层 ===')
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
|
||||||
|
console.log('上移前:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
const originalZIndex1 = node1.zIndex
|
||||||
|
const originalZIndex2 = node2.zIndex
|
||||||
|
|
||||||
|
// 对node1执行上移操作
|
||||||
|
lf.bringForward(node1.id)
|
||||||
|
|
||||||
|
console.log('上移后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 验证zIndex已交换
|
||||||
|
expect(node1.zIndex).toBe(originalZIndex2)
|
||||||
|
expect(node2.zIndex).toBe(originalZIndex1)
|
||||||
|
expect(node1.zIndex).toBeGreaterThan(node2.zIndex)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('最顶层节点上移应该不产生变化', () => {
|
||||||
|
console.log('\n=== 测试:最顶层节点上移 ===')
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
|
||||||
|
const originalZIndex3 = node3.zIndex
|
||||||
|
|
||||||
|
console.log('上移前:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 尝试上移最顶层节点
|
||||||
|
lf.bringForward(node3.id)
|
||||||
|
|
||||||
|
console.log('上移后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 最顶层节点上移不应该改变zIndex
|
||||||
|
expect(node3.zIndex).toBe(originalZIndex3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('3. 节点下移操作', () => {
|
||||||
|
it('下移一层应该与下方节点交换zIndex', () => {
|
||||||
|
console.log('\n=== 测试:下移一层 ===')
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
|
||||||
|
console.log('下移前:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
const originalZIndex3 = node3.zIndex
|
||||||
|
const originalZIndex2 = node2.zIndex
|
||||||
|
|
||||||
|
// 对node3执行下移操作
|
||||||
|
lf.sendBackward(node3.id)
|
||||||
|
|
||||||
|
console.log('下移后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 验证zIndex已交换
|
||||||
|
expect(node3.zIndex).toBe(originalZIndex2)
|
||||||
|
expect(node2.zIndex).toBe(originalZIndex3)
|
||||||
|
expect(node3.zIndex).toBeLessThan(node2.zIndex)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('最底层节点下移应该不产生变化', () => {
|
||||||
|
console.log('\n=== 测试:最底层节点下移 ===')
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
|
||||||
|
const originalZIndex1 = node1.zIndex
|
||||||
|
|
||||||
|
console.log('下移前:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 尝试下移最底层节点
|
||||||
|
lf.sendBackward(node1.id)
|
||||||
|
|
||||||
|
console.log('下移后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 最底层节点下移不应该改变zIndex
|
||||||
|
expect(node1.zIndex).toBe(originalZIndex1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('4. 节点置顶操作', () => {
|
||||||
|
it('置顶应该将节点移到最上层', () => {
|
||||||
|
console.log('\n=== 测试:置顶操作 ===')
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
|
||||||
|
console.log('置顶前:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 对node1执行置顶操作
|
||||||
|
lf.setElementZIndex(node1.id, 'top')
|
||||||
|
|
||||||
|
console.log('置顶后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 验证node1的zIndex最大
|
||||||
|
const allZIndexes = lf.nodes.map(n => n.zIndex)
|
||||||
|
expect(node1.zIndex).toBe(Math.max(...allZIndexes))
|
||||||
|
|
||||||
|
// 验证node1在所有其他节点之上
|
||||||
|
lf.nodes.forEach(node => {
|
||||||
|
if (node.id !== node1.id) {
|
||||||
|
expect(node1.zIndex).toBeGreaterThan(node.zIndex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('已经在顶层的节点置顶应该增加zIndex', () => {
|
||||||
|
console.log('\n=== 测试:顶层节点置顶 ===')
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
|
||||||
|
const originalZIndex3 = node3.zIndex
|
||||||
|
|
||||||
|
console.log('置顶前:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 对已经在顶层的node3执行置顶
|
||||||
|
lf.setElementZIndex(node3.id, 'top')
|
||||||
|
|
||||||
|
console.log('置顶后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 顶层节点置顶会增加zIndex
|
||||||
|
expect(node3.zIndex).toBeGreaterThan(originalZIndex3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('5. 节点置底操作', () => {
|
||||||
|
it('置底应该将节点移到最下层', () => {
|
||||||
|
console.log('\n=== 测试:置底操作 ===')
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
|
||||||
|
console.log('置底前:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 对node3执行置底操作
|
||||||
|
lf.setElementZIndex(node3.id, 'bottom')
|
||||||
|
|
||||||
|
console.log('置底后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 验证node3的zIndex最小
|
||||||
|
const allZIndexes = lf.nodes.map(n => n.zIndex)
|
||||||
|
expect(node3.zIndex).toBe(Math.min(...allZIndexes))
|
||||||
|
|
||||||
|
// 验证node3在所有其他节点之下
|
||||||
|
lf.nodes.forEach(node => {
|
||||||
|
if (node.id !== node3.id) {
|
||||||
|
expect(node3.zIndex).toBeLessThan(node.zIndex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('已经在底层的节点置底应该减少zIndex', () => {
|
||||||
|
console.log('\n=== 测试:底层节点置底 ===')
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
|
||||||
|
const originalZIndex1 = node1.zIndex
|
||||||
|
|
||||||
|
console.log('置底前:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 对已经在底层的node1执行置底
|
||||||
|
lf.setElementZIndex(node1.id, 'bottom')
|
||||||
|
|
||||||
|
console.log('置底后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 底层节点置底会减少zIndex
|
||||||
|
expect(node1.zIndex).toBeLessThan(originalZIndex1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('6. 复杂场景测试', () => {
|
||||||
|
it('连续操作后zIndex应该保持正确顺序', () => {
|
||||||
|
console.log('\n=== 测试:连续操作 ===')
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
const node4 = lf.addNode({ x: 400, y: 400, zIndex: 4 })
|
||||||
|
|
||||||
|
console.log('初始状态:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 操作序列: node1置顶 -> node4置底 -> node2上移
|
||||||
|
lf.setElementZIndex(node1.id, 'top')
|
||||||
|
console.log('node1置顶后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
lf.setElementZIndex(node4.id, 'bottom')
|
||||||
|
console.log('node4置底后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
lf.bringForward(node2.id)
|
||||||
|
console.log('node2上移后:')
|
||||||
|
lf.printAllNodes()
|
||||||
|
|
||||||
|
// 验证最终顺序
|
||||||
|
const sortedNodes = [...lf.nodes].sort((a, b) => a.zIndex - b.zIndex)
|
||||||
|
|
||||||
|
// node4应该在最底层
|
||||||
|
expect(sortedNodes[0].id).toBe(node4.id)
|
||||||
|
|
||||||
|
// node1应该在最顶层
|
||||||
|
expect(sortedNodes[sortedNodes.length - 1].id).toBe(node1.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('检查数据预览中的zIndex', () => {
|
||||||
|
console.log('\n=== 测试:数据预览 ===')
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ x: 100, y: 100, zIndex: 1 })
|
||||||
|
const node2 = lf.addNode({ x: 200, y: 200, zIndex: 2 })
|
||||||
|
const node3 = lf.addNode({ x: 300, y: 300, zIndex: 3 })
|
||||||
|
|
||||||
|
// 执行置顶操作
|
||||||
|
lf.setElementZIndex(node1.id, 'top')
|
||||||
|
|
||||||
|
// 获取图数据(模拟数据预览)
|
||||||
|
const graphData = lf.getGraphRawData()
|
||||||
|
|
||||||
|
console.log('数据预览:', JSON.stringify(graphData, null, 2))
|
||||||
|
|
||||||
|
// 验证数据中包含zIndex
|
||||||
|
expect(graphData.nodes).toBeDefined()
|
||||||
|
expect(graphData.nodes.length).toBe(3)
|
||||||
|
|
||||||
|
graphData.nodes.forEach((node: any) => {
|
||||||
|
expect(node.zIndex).toBeDefined()
|
||||||
|
expect(typeof node.zIndex).toBe('number')
|
||||||
|
console.log(`节点 ${node.id} 的 zIndex: ${node.zIndex}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证node1的zIndex最大
|
||||||
|
const node1Data = graphData.nodes.find((n: any) => n.id === node1.id)
|
||||||
|
const allZIndexes = graphData.nodes.map((n: any) => n.zIndex)
|
||||||
|
expect(node1Data?.zIndex).toBe(Math.max(...allZIndexes))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
502
src/__tests__/layer-management/real-scenario.spec.ts
Normal file
502
src/__tests__/layer-management/real-scenario.spec.ts
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||||
|
import { createPinia, setActivePinia } from 'pinia'
|
||||||
|
import LogicFlow from '@logicflow/core'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图层管理真实场景测试
|
||||||
|
*
|
||||||
|
* 这个测试直接使用 LogicFlow 实例,模拟真实的用户操作:
|
||||||
|
* 1. 创建节点(模拟从 ComponentsPanel 拖拽)
|
||||||
|
* 2. 执行图层操作(模拟 FlowEditor 中的操作)
|
||||||
|
* 3. 验证数据预览(模拟 Toolbar 的 handlePreviewData)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 辅助函数:获取包含 zIndex 的图数据
|
||||||
|
* 模拟 useStore.ts 中的 syncLogicFlowDataToStore 逻辑
|
||||||
|
*/
|
||||||
|
function getGraphDataWithZIndex(lf: LogicFlow) {
|
||||||
|
const graphData = lf.getGraphRawData();
|
||||||
|
return {
|
||||||
|
...graphData,
|
||||||
|
nodes: (graphData.nodes || []).map((node: any) => {
|
||||||
|
const model = lf.getNodeModelById(node.id);
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
zIndex: model?.zIndex ?? node.zIndex ?? 1
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('图层管理真实场景测试', () => {
|
||||||
|
let lf: LogicFlow | null = null
|
||||||
|
let container: HTMLDivElement | null = null
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// 创建 Pinia 实例
|
||||||
|
const pinia = createPinia()
|
||||||
|
setActivePinia(pinia)
|
||||||
|
|
||||||
|
// 创建容器
|
||||||
|
container = document.createElement('div')
|
||||||
|
container.style.width = '800px'
|
||||||
|
container.style.height = '600px'
|
||||||
|
document.body.appendChild(container)
|
||||||
|
|
||||||
|
// 创建 LogicFlow 实例
|
||||||
|
lf = new LogicFlow({
|
||||||
|
container,
|
||||||
|
grid: { type: 'dot', size: 10 },
|
||||||
|
allowResize: true,
|
||||||
|
allowRotate: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
lf.render({ nodes: [], edges: [] })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// 清理
|
||||||
|
if (lf) {
|
||||||
|
lf.destroy()
|
||||||
|
lf = null
|
||||||
|
}
|
||||||
|
if (container && container.parentNode) {
|
||||||
|
container.parentNode.removeChild(container)
|
||||||
|
container = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景1: 创建节点并验证 zIndex 分配', () => {
|
||||||
|
console.log('\n=== 场景1: 创建节点并验证 zIndex 分配 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 模拟从 ComponentsPanel 拖拽创建 3 个节点
|
||||||
|
const node1 = lf.addNode({
|
||||||
|
type: 'rect',
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
properties: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const node2 = lf.addNode({
|
||||||
|
type: 'rect',
|
||||||
|
x: 200,
|
||||||
|
y: 200,
|
||||||
|
properties: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const node3 = lf.addNode({
|
||||||
|
type: 'rect',
|
||||||
|
x: 300,
|
||||||
|
y: 300,
|
||||||
|
properties: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取节点模型
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
console.log('创建后的 zIndex:')
|
||||||
|
console.log(` node1: ${model1?.zIndex}`)
|
||||||
|
console.log(` node2: ${model2?.zIndex}`)
|
||||||
|
console.log(` node3: ${model3?.zIndex}`)
|
||||||
|
|
||||||
|
// 验证:所有节点都有 zIndex
|
||||||
|
expect(model1?.zIndex).toBeDefined()
|
||||||
|
expect(model2?.zIndex).toBeDefined()
|
||||||
|
expect(model3?.zIndex).toBeDefined()
|
||||||
|
|
||||||
|
// 验证:zIndex 是数字
|
||||||
|
expect(typeof model1?.zIndex).toBe('number')
|
||||||
|
expect(typeof model2?.zIndex).toBe('number')
|
||||||
|
expect(typeof model3?.zIndex).toBe('number')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景2: 置顶操作(模拟右键菜单)', () => {
|
||||||
|
console.log('\n=== 场景2: 置顶操作 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建 3 个节点
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
// 手动设置初始 zIndex(模拟历史数据加载)
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
console.log('初始 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟用户右键点击 node1,选择"置于顶层"
|
||||||
|
lf.setElementZIndex(node1.id, 'top')
|
||||||
|
|
||||||
|
console.log('置顶后 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证:node1 的 zIndex 最大
|
||||||
|
const allZIndexes = [model1?.zIndex, model2?.zIndex, model3?.zIndex].filter(z => z !== undefined) as number[]
|
||||||
|
expect(model1?.zIndex).toBe(Math.max(...allZIndexes))
|
||||||
|
expect(model1?.zIndex).toBeGreaterThan(model2?.zIndex || 0)
|
||||||
|
expect(model1?.zIndex).toBeGreaterThan(model3?.zIndex || 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景3: 置底操作(模拟右键菜单)', () => {
|
||||||
|
console.log('\n=== 场景3: 置底操作 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建 3 个节点
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
console.log('初始 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟用户右键点击 node3,选择"置于底层"
|
||||||
|
// 修复:使用正确的置底逻辑(与 FlowEditor.vue 中的 sendToBack 一致)
|
||||||
|
const allNodesScene3 = lf.graphModel.nodes;
|
||||||
|
const allZIndexesForBottom = allNodesScene3.map(n => n.zIndex).filter(z => z !== undefined);
|
||||||
|
const minZIndex = allZIndexesForBottom.length > 0 ? Math.min(...allZIndexesForBottom) : 1;
|
||||||
|
const newZIndex = minZIndex - 1;
|
||||||
|
model3?.setZIndex(newZIndex);
|
||||||
|
|
||||||
|
console.log('置底后 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证:node3 的 zIndex 最小
|
||||||
|
const allZIndexes = [model1?.zIndex, model2?.zIndex, model3?.zIndex].filter(z => z !== undefined) as number[]
|
||||||
|
expect(model3?.zIndex).toBe(Math.min(...allZIndexes))
|
||||||
|
expect(model3?.zIndex).toBeLessThan(model1?.zIndex || Infinity)
|
||||||
|
expect(model3?.zIndex).toBeLessThan(model2?.zIndex || Infinity)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景4: 上移一层操作', () => {
|
||||||
|
console.log('\n=== 场景4: 上移一层操作 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建 3 个节点
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
const originalZIndex1 = model1?.zIndex
|
||||||
|
const originalZIndex2 = model2?.zIndex
|
||||||
|
|
||||||
|
console.log('上移前 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟 FlowEditor 的 bringForward 方法
|
||||||
|
const currentNode = model1
|
||||||
|
if (currentNode) {
|
||||||
|
const currentZIndex = currentNode.zIndex
|
||||||
|
const allNodes = lf.graphModel.nodes
|
||||||
|
const nodesAbove = allNodes
|
||||||
|
.filter((node) => node.zIndex > currentZIndex)
|
||||||
|
.sort((a, b) => a.zIndex - b.zIndex)
|
||||||
|
|
||||||
|
if (nodesAbove.length > 0) {
|
||||||
|
const nextNode = nodesAbove[0]
|
||||||
|
currentNode.setZIndex(nextNode.zIndex)
|
||||||
|
nextNode.setZIndex(currentZIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('上移后 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证:node1 和 node2 的 zIndex 已交换
|
||||||
|
expect(model1?.zIndex).toBe(originalZIndex2)
|
||||||
|
expect(model2?.zIndex).toBe(originalZIndex1)
|
||||||
|
expect(model1?.zIndex).toBeGreaterThan(model2?.zIndex || 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景5: 下移一层操作', () => {
|
||||||
|
console.log('\n=== 场景5: 下移一层操作 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建 3 个节点
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
const originalZIndex2 = model2?.zIndex
|
||||||
|
const originalZIndex3 = model3?.zIndex
|
||||||
|
|
||||||
|
console.log('下移前 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟 FlowEditor 的 sendBackward 方法
|
||||||
|
const currentNode = model3
|
||||||
|
if (currentNode) {
|
||||||
|
const currentZIndex = currentNode.zIndex
|
||||||
|
const allNodes = lf.graphModel.nodes
|
||||||
|
const nodesBelow = allNodes
|
||||||
|
.filter((node) => node.zIndex < currentZIndex)
|
||||||
|
.sort((a, b) => b.zIndex - a.zIndex)
|
||||||
|
|
||||||
|
if (nodesBelow.length > 0) {
|
||||||
|
const prevNode = nodesBelow[0]
|
||||||
|
currentNode.setZIndex(prevNode.zIndex)
|
||||||
|
prevNode.setZIndex(currentZIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('下移后 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证:node3 和 node2 的 zIndex 已交换
|
||||||
|
expect(model3?.zIndex).toBe(originalZIndex2)
|
||||||
|
expect(model2?.zIndex).toBe(originalZIndex3)
|
||||||
|
expect(model3?.zIndex).toBeLessThan(model2?.zIndex || Infinity)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景6: 数据预览验证(模拟 Toolbar.handlePreviewData)', () => {
|
||||||
|
console.log('\n=== 场景6: 数据预览验证 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 创建节点
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
// 执行置顶操作
|
||||||
|
lf.setElementZIndex(node1.id, 'top')
|
||||||
|
|
||||||
|
// 模拟 Toolbar 的 handlePreviewData 方法(使用辅助函数获取包含 zIndex 的数据)
|
||||||
|
const graphData = getGraphDataWithZIndex(lf)
|
||||||
|
|
||||||
|
console.log('数据预览:')
|
||||||
|
console.log(JSON.stringify(graphData, null, 2))
|
||||||
|
|
||||||
|
// 验证:数据中包含 zIndex
|
||||||
|
expect(graphData.nodes).toBeDefined()
|
||||||
|
expect(graphData.nodes.length).toBe(3)
|
||||||
|
|
||||||
|
graphData.nodes.forEach((node: any) => {
|
||||||
|
expect(node.zIndex).toBeDefined()
|
||||||
|
expect(typeof node.zIndex).toBe('number')
|
||||||
|
console.log(` 节点 ${node.id}: zIndex = ${node.zIndex}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证:node1 的 zIndex 最大
|
||||||
|
const node1Data = graphData.nodes.find((n: any) => n.id === node1.id)
|
||||||
|
const allZIndexes = graphData.nodes.map((n: any) => n.zIndex)
|
||||||
|
expect(node1Data?.zIndex).toBe(Math.max(...allZIndexes))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景7: 完整用户流程测试', () => {
|
||||||
|
console.log('\n=== 场景7: 完整用户流程测试 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
// 步骤 1: 用户从 ComponentsPanel 拖拽创建 4 个节点
|
||||||
|
console.log('\n步骤 1: 创建节点')
|
||||||
|
const nodes = [
|
||||||
|
lf.addNode({ type: 'rect', x: 100, y: 100 }),
|
||||||
|
lf.addNode({ type: 'rect', x: 200, y: 200 }),
|
||||||
|
lf.addNode({ type: 'rect', x: 300, y: 300 }),
|
||||||
|
lf.addNode({ type: 'rect', x: 400, y: 400 })
|
||||||
|
]
|
||||||
|
|
||||||
|
const models = nodes.map(n => lf!.getNodeModelById(n.id))
|
||||||
|
models.forEach((m, i) => m?.setZIndex(i + 1))
|
||||||
|
|
||||||
|
console.log('初始状态:', models.map((m, i) => ({
|
||||||
|
id: nodes[i].id,
|
||||||
|
zIndex: m?.zIndex
|
||||||
|
})))
|
||||||
|
|
||||||
|
// 步骤 2: 用户右键点击 node1,选择"置于顶层"
|
||||||
|
console.log('\n步骤 2: node1 置于顶层')
|
||||||
|
lf.setElementZIndex(nodes[0].id, 'top')
|
||||||
|
console.log('操作后:', models.map((m, i) => ({
|
||||||
|
id: nodes[i].id,
|
||||||
|
zIndex: m?.zIndex
|
||||||
|
})))
|
||||||
|
|
||||||
|
// 步骤 3: 用户右键点击 node4,选择"置于底层"
|
||||||
|
console.log('\n步骤 3: node4 置于底层')
|
||||||
|
// 修复:使用正确的置底逻辑
|
||||||
|
const allNodesStep3 = lf.graphModel.nodes;
|
||||||
|
const allZIndexesStep3 = allNodesStep3.map(n => n.zIndex).filter(z => z !== undefined);
|
||||||
|
const minZIndexStep3 = allZIndexesStep3.length > 0 ? Math.min(...allZIndexesStep3) : 1;
|
||||||
|
models[3]?.setZIndex(minZIndexStep3 - 1);
|
||||||
|
console.log('操作后:', models.map((m, i) => ({
|
||||||
|
id: nodes[i].id,
|
||||||
|
zIndex: m?.zIndex
|
||||||
|
})))
|
||||||
|
|
||||||
|
// 步骤 4: 用户点击 Toolbar 的"数据预览"按钮
|
||||||
|
console.log('\n步骤 4: 数据预览')
|
||||||
|
const graphData = getGraphDataWithZIndex(lf)
|
||||||
|
|
||||||
|
console.log('最终数据:')
|
||||||
|
graphData.nodes.forEach((node: any) => {
|
||||||
|
console.log(` 节点 ${node.id}: zIndex = ${node.zIndex}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证最终顺序
|
||||||
|
const sortedNodes = [...graphData.nodes].sort((a: any, b: any) => a.zIndex - b.zIndex)
|
||||||
|
|
||||||
|
// node4 应该在最底层
|
||||||
|
expect(sortedNodes[0].id).toBe(nodes[3].id)
|
||||||
|
|
||||||
|
// node1 应该在最顶层
|
||||||
|
expect(sortedNodes[sortedNodes.length - 1].id).toBe(nodes[0].id)
|
||||||
|
|
||||||
|
console.log('\n✅ 完整流程测试通过!')
|
||||||
|
console.log('验证结果:')
|
||||||
|
console.log(` - 最底层: ${sortedNodes[0].id} (zIndex: ${sortedNodes[0].zIndex})`)
|
||||||
|
console.log(` - 最顶层: ${sortedNodes[sortedNodes.length - 1].id} (zIndex: ${sortedNodes[sortedNodes.length - 1].zIndex})`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景8: 边界情况 - 最顶层节点继续置顶', () => {
|
||||||
|
console.log('\n=== 场景8: 边界情况 - 最顶层节点继续置顶 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
const originalZIndex3 = model3?.zIndex
|
||||||
|
|
||||||
|
console.log('初始 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 对已经在顶层的 node3 执行置顶
|
||||||
|
lf.setElementZIndex(node3.id, 'top')
|
||||||
|
|
||||||
|
console.log('置顶后 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证:顶层节点置顶会增加 zIndex
|
||||||
|
expect(model3?.zIndex).toBeGreaterThan(originalZIndex3 || 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('场景9: 边界情况 - 最底层节点继续置底', () => {
|
||||||
|
console.log('\n=== 场景9: 边界情况 - 最底层节点继续置底 ===')
|
||||||
|
|
||||||
|
if (!lf) return
|
||||||
|
|
||||||
|
const node1 = lf.addNode({ type: 'rect', x: 100, y: 100 })
|
||||||
|
const node2 = lf.addNode({ type: 'rect', x: 200, y: 200 })
|
||||||
|
const node3 = lf.addNode({ type: 'rect', x: 300, y: 300 })
|
||||||
|
|
||||||
|
const model1 = lf.getNodeModelById(node1.id)
|
||||||
|
const model2 = lf.getNodeModelById(node2.id)
|
||||||
|
const model3 = lf.getNodeModelById(node3.id)
|
||||||
|
|
||||||
|
model1?.setZIndex(1)
|
||||||
|
model2?.setZIndex(2)
|
||||||
|
model3?.setZIndex(3)
|
||||||
|
|
||||||
|
const originalZIndex1 = model1?.zIndex
|
||||||
|
|
||||||
|
console.log('初始 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 对已经在底层的 node1 执行置底
|
||||||
|
// 修复:使用正确的置底逻辑
|
||||||
|
const allNodesScene9 = lf.graphModel.nodes;
|
||||||
|
const allZIndexesScene9 = allNodesScene9.map(n => n.zIndex).filter(z => z !== undefined);
|
||||||
|
const minZIndexScene9 = allZIndexesScene9.length > 0 ? Math.min(...allZIndexesScene9) : 1;
|
||||||
|
model1?.setZIndex(minZIndexScene9 - 1);
|
||||||
|
|
||||||
|
console.log('置底后 zIndex:', {
|
||||||
|
node1: model1?.zIndex,
|
||||||
|
node2: model2?.zIndex,
|
||||||
|
node3: model3?.zIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证:底层节点置底会减少 zIndex
|
||||||
|
expect(model1?.zIndex).toBeLessThan(originalZIndex1 || Infinity)
|
||||||
|
})
|
||||||
|
})
|
||||||
312
src/__tests__/layer-management/unit-test.spec.ts.bak
Normal file
312
src/__tests__/layer-management/unit-test.spec.ts.bak
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||||
|
|
||||||
|
describe('图层管理测试', () => {
|
||||||
|
let nodes: any[]
|
||||||
|
let mockSetElementZIndex: any
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// 初始化测试节点
|
||||||
|
nodes = []
|
||||||
|
|
||||||
|
// 模拟 setElementZIndex 函数
|
||||||
|
mockSetElementZIndex = vi.fn((id, zIndexOrPosition) => {
|
||||||
|
const node = nodes.find(n => n.id === id)
|
||||||
|
if (!node) return
|
||||||
|
|
||||||
|
if (zIndexOrPosition === 'top') {
|
||||||
|
const maxZIndex = Math.max(...nodes.map(n => n.zIndex))
|
||||||
|
node.zIndex = maxZIndex + 1
|
||||||
|
} else if (zIndexOrPosition === 'bottom') {
|
||||||
|
const minZIndex = Math.min(...nodes.map(n => n.zIndex))
|
||||||
|
node.zIndex = minZIndex - 1
|
||||||
|
} else if (typeof zIndexOrPosition === 'number') {
|
||||||
|
node.zIndex = zIndexOrPosition
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('1. 创建多个节点并检查zIndex', () => {
|
||||||
|
it('应该为新创建的节点分配正确的zIndex', () => {
|
||||||
|
// 模拟创建3个节点
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 验证每个节点都有zIndex
|
||||||
|
nodes.forEach(node => {
|
||||||
|
expect(node.zIndex).toBeDefined()
|
||||||
|
expect(typeof node.zIndex).toBe('number')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证zIndex是递增的
|
||||||
|
expect(nodes[0].zIndex).toBeLessThan(nodes[1].zIndex)
|
||||||
|
expect(nodes[1].zIndex).toBeLessThan(nodes[2].zIndex)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('新节点应该默认获得较高的zIndex', () => {
|
||||||
|
const existingNodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const newNode = { id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 1000 }
|
||||||
|
|
||||||
|
nodes = [...existingNodes, newNode]
|
||||||
|
|
||||||
|
// 新节点的zIndex应该大于现有节点
|
||||||
|
expect(newNode.zIndex).toBeGreaterThan(existingNodes[0].zIndex)
|
||||||
|
expect(newNode.zIndex).toBeGreaterThan(existingNodes[1].zIndex)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('多个节点的zIndex应该各不相同', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 },
|
||||||
|
{ id: 'node4', type: 'rect', x: 400, y: 400, zIndex: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const zIndexes = nodes.map(n => n.zIndex)
|
||||||
|
const uniqueZIndexes = new Set(zIndexes)
|
||||||
|
|
||||||
|
// 所有zIndex应该是唯一的
|
||||||
|
expect(uniqueZIndexes.size).toBe(nodes.length)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('2. 节点上移操作', () => {
|
||||||
|
it('上移一层应该与上方节点交换zIndex', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 对node1执行上移操作
|
||||||
|
const targetNode = nodes[0]
|
||||||
|
const upperNode = nodes[1]
|
||||||
|
const originalTargetZIndex = targetNode.zIndex
|
||||||
|
const originalUpperZIndex = upperNode.zIndex
|
||||||
|
|
||||||
|
// 模拟交换zIndex
|
||||||
|
mockSetElementZIndex(targetNode.id, originalUpperZIndex)
|
||||||
|
mockSetElementZIndex(upperNode.id, originalTargetZIndex)
|
||||||
|
|
||||||
|
// 验证zIndex已交换
|
||||||
|
expect(targetNode.zIndex).toBe(originalUpperZIndex)
|
||||||
|
expect(upperNode.zIndex).toBe(originalTargetZIndex)
|
||||||
|
expect(targetNode.zIndex).toBeGreaterThan(upperNode.zIndex)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('最顶层节点上移应该不产生变化', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const topNode = nodes[2]
|
||||||
|
const originalZIndex = topNode.zIndex
|
||||||
|
|
||||||
|
// 尝试上移最顶层节点
|
||||||
|
const sortedNodes = [...nodes].sort((a, b) => a.zIndex - b.zIndex)
|
||||||
|
const currentIndex = sortedNodes.findIndex(n => n.id === topNode.id)
|
||||||
|
const isTopNode = currentIndex === sortedNodes.length - 1
|
||||||
|
|
||||||
|
expect(isTopNode).toBe(true)
|
||||||
|
expect(topNode.zIndex).toBe(originalZIndex)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('3. 节点下移操作', () => {
|
||||||
|
it('下移一层应该与下方节点交换zIndex', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const targetNode = nodes[2]
|
||||||
|
const lowerNode = nodes[1]
|
||||||
|
const originalTargetZIndex = targetNode.zIndex
|
||||||
|
const originalLowerZIndex = lowerNode.zIndex
|
||||||
|
|
||||||
|
// 模拟交换zIndex
|
||||||
|
mockSetElementZIndex(targetNode.id, originalLowerZIndex)
|
||||||
|
mockSetElementZIndex(lowerNode.id, originalTargetZIndex)
|
||||||
|
|
||||||
|
// 验证zIndex已交换
|
||||||
|
expect(targetNode.zIndex).toBe(originalLowerZIndex)
|
||||||
|
expect(lowerNode.zIndex).toBe(originalTargetZIndex)
|
||||||
|
expect(targetNode.zIndex).toBeLessThan(lowerNode.zIndex)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('最底层节点下移应该不产生变化', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const bottomNode = nodes[0]
|
||||||
|
const originalZIndex = bottomNode.zIndex
|
||||||
|
|
||||||
|
// 尝试下移最底层节点
|
||||||
|
const sortedNodes = [...nodes].sort((a, b) => a.zIndex - b.zIndex)
|
||||||
|
const currentIndex = sortedNodes.findIndex(n => n.id === bottomNode.id)
|
||||||
|
const isBottomNode = currentIndex === 0
|
||||||
|
|
||||||
|
expect(isBottomNode).toBe(true)
|
||||||
|
expect(bottomNode.zIndex).toBe(originalZIndex)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('4. 节点置顶操作', () => {
|
||||||
|
it('置顶应该将节点移到最上层', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const targetNode = nodes[0]
|
||||||
|
|
||||||
|
// 模拟置顶操作
|
||||||
|
mockSetElementZIndex(targetNode.id, 'top')
|
||||||
|
|
||||||
|
// 验证该节点的zIndex最大
|
||||||
|
const allZIndexes = nodes.map(n => n.zIndex)
|
||||||
|
expect(targetNode.zIndex).toBe(Math.max(...allZIndexes))
|
||||||
|
|
||||||
|
// 验证该节点在所有节点之上
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (node.id !== targetNode.id) {
|
||||||
|
expect(targetNode.zIndex).toBeGreaterThan(node.zIndex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('已经在顶层的节点置顶应该保持不变', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const topNode = nodes[2]
|
||||||
|
|
||||||
|
// 对已经在顶层的节点执行置顶
|
||||||
|
const maxZIndex = Math.max(...nodes.map(n => n.zIndex))
|
||||||
|
expect(topNode.zIndex).toBe(maxZIndex)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('5. 节点置底操作', () => {
|
||||||
|
it('置底应该将节点移到最下层', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const targetNode = nodes[2]
|
||||||
|
|
||||||
|
// 模拟置底操作
|
||||||
|
mockSetElementZIndex(targetNode.id, 'bottom')
|
||||||
|
|
||||||
|
// 验证该节点的zIndex最小
|
||||||
|
const allZIndexes = nodes.map(n => n.zIndex)
|
||||||
|
expect(targetNode.zIndex).toBe(Math.min(...allZIndexes))
|
||||||
|
|
||||||
|
// 验证该节点在所有节点之下
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (node.id !== targetNode.id) {
|
||||||
|
expect(targetNode.zIndex).toBeLessThan(node.zIndex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('已经在底层的节点置底应该保持不变', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const bottomNode = nodes[0]
|
||||||
|
|
||||||
|
// 对已经在底层的节点执行置底
|
||||||
|
const minZIndex = Math.min(...nodes.map(n => n.zIndex))
|
||||||
|
expect(bottomNode.zIndex).toBe(minZIndex)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('6. 复杂场景测试', () => {
|
||||||
|
it('连续操作后zIndex应该保持正确顺序', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 },
|
||||||
|
{ id: 'node4', type: 'rect', x: 400, y: 400, zIndex: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 操作序列: node1置顶 -> node4置底 -> node2上移
|
||||||
|
mockSetElementZIndex('node1', 'top')
|
||||||
|
mockSetElementZIndex('node4', 'bottom')
|
||||||
|
|
||||||
|
// node2上移(与node3交换)
|
||||||
|
const node2 = nodes.find(n => n.id === 'node2')!
|
||||||
|
const node3 = nodes.find(n => n.id === 'node3')!
|
||||||
|
const temp = node2.zIndex
|
||||||
|
mockSetElementZIndex('node2', node3.zIndex)
|
||||||
|
mockSetElementZIndex('node3', temp)
|
||||||
|
|
||||||
|
// 验证最终顺序
|
||||||
|
const sortedNodes = [...nodes].sort((a, b) => a.zIndex - b.zIndex)
|
||||||
|
|
||||||
|
// node4应该在最底层
|
||||||
|
expect(sortedNodes[0].id).toBe('node4')
|
||||||
|
// node1应该在最顶层
|
||||||
|
expect(sortedNodes[sortedNodes.length - 1].id).toBe('node1')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('选中多个节点时只操作第一个选中的节点', () => {
|
||||||
|
nodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 模拟选中多个节点
|
||||||
|
const selectedNodes = [nodes[0], nodes[1]]
|
||||||
|
const targetNode = selectedNodes[0]
|
||||||
|
|
||||||
|
// 只对第一个节点执行操作
|
||||||
|
mockSetElementZIndex(targetNode.id, 'top')
|
||||||
|
|
||||||
|
// 验证只调用了一次
|
||||||
|
expect(mockSetElementZIndex).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('zIndex应该在保存和加载后保持一致', () => {
|
||||||
|
const originalNodes = [
|
||||||
|
{ id: 'node1', type: 'rect', x: 100, y: 100, zIndex: 1 },
|
||||||
|
{ id: 'node2', type: 'rect', x: 200, y: 200, zIndex: 2 },
|
||||||
|
{ id: 'node3', type: 'rect', x: 300, y: 300, zIndex: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 模拟保存
|
||||||
|
const savedData = JSON.parse(JSON.stringify(originalNodes))
|
||||||
|
|
||||||
|
// 模拟加载
|
||||||
|
const loadedNodes = savedData
|
||||||
|
|
||||||
|
// 验证zIndex保持一致
|
||||||
|
originalNodes.forEach((node, index) => {
|
||||||
|
expect(loadedNodes[index].zIndex).toBe(node.zIndex)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
31
src/__tests__/setup.ts
Normal file
31
src/__tests__/setup.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// 测试环境设置文件
|
||||||
|
|
||||||
|
// Mock ResizeObserver
|
||||||
|
global.ResizeObserver = class ResizeObserver {
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
disconnect() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock IntersectionObserver
|
||||||
|
global.IntersectionObserver = class IntersectionObserver {
|
||||||
|
constructor() {}
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
disconnect() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock window.matchMedia
|
||||||
|
Object.defineProperty(window, 'matchMedia', {
|
||||||
|
writable: true,
|
||||||
|
value: vi.fn().mockImplementation(query => ({
|
||||||
|
matches: false,
|
||||||
|
media: query,
|
||||||
|
onchange: null,
|
||||||
|
addListener: vi.fn(),
|
||||||
|
removeListener: vi.fn(),
|
||||||
|
addEventListener: vi.fn(),
|
||||||
|
removeEventListener: vi.fn(),
|
||||||
|
dispatchEvent: vi.fn(),
|
||||||
|
})),
|
||||||
|
})
|
||||||
@@ -198,23 +198,11 @@ function normalizeAllNodes() {
|
|||||||
if (!lfInstance) return;
|
if (!lfInstance) return;
|
||||||
lfInstance.graphModel?.nodes.forEach((model: BaseNodeModel) => normalizeNodeModel(model));
|
lfInstance.graphModel?.nodes.forEach((model: BaseNodeModel) => normalizeNodeModel(model));
|
||||||
|
|
||||||
// 检查是否所有节点的 zIndex 都相同且为默认值(通常是从历史恢复的情况)
|
// 清除新节点标记
|
||||||
const allNodes = lfInstance.graphModel?.nodes || [];
|
const allNodes = lfInstance.graphModel?.nodes || [];
|
||||||
if (allNodes.length > 1) {
|
allNodes.forEach(node => {
|
||||||
const firstZIndex = allNodes[0]?.zIndex;
|
delete (node as any)._isNewNode;
|
||||||
const allSameZIndex = allNodes.every(n => n.zIndex === firstZIndex);
|
|
||||||
|
|
||||||
// 只有当所有节点的 zIndex 都是默认值 1 时才重新分配
|
|
||||||
if (allSameZIndex && firstZIndex === 1) {
|
|
||||||
console.log('[初始化] 检测到所有节点 zIndex 都为默认值 1,开始重新分配 zIndex');
|
|
||||||
// 为所有节点分配递增的 zIndex,避免层级操作异常
|
|
||||||
allNodes.forEach((node, index) => {
|
|
||||||
const newZIndex = index + 1;
|
|
||||||
node.setZIndex(newZIndex);
|
|
||||||
});
|
});
|
||||||
console.log('[初始化] zIndex 重新分配完成:', allNodes.map(n => ({ id: n.id, zIndex: n.zIndex })));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeMeta(model: BaseNodeModel, updater: (meta: Record<string, any>) => Record<string, any>) {
|
function updateNodeMeta(model: BaseNodeModel, updater: (meta: Record<string, any>) => Record<string, any>) {
|
||||||
@@ -298,12 +286,19 @@ function sendToBack(nodeId?: string) {
|
|||||||
const targetId = nodeId || selectedNode.value?.id;
|
const targetId = nodeId || selectedNode.value?.id;
|
||||||
if (!targetId) return;
|
if (!targetId) return;
|
||||||
|
|
||||||
// 诊断日志:查看所有节点的 zIndex
|
const currentNode = lfInstance.getNodeModelById(targetId);
|
||||||
|
if (!currentNode) return;
|
||||||
|
|
||||||
const allNodes = lfInstance.graphModel.nodes;
|
const allNodes = lfInstance.graphModel.nodes;
|
||||||
console.log('[置于底层] 目标节点ID:', targetId);
|
console.log('[置于底层] 目标节点ID:', targetId);
|
||||||
console.log('[置于底层] 所有节点的 zIndex:', allNodes.map(n => ({ id: n.id, zIndex: n.zIndex })));
|
console.log('[置于底层] 所有节点的 zIndex:', allNodes.map(n => ({ id: n.id, zIndex: n.zIndex })));
|
||||||
|
|
||||||
lfInstance.setElementZIndex(targetId, 'bottom');
|
// 修复:找到所有节点中最小的 zIndex,然后设置为比它更小
|
||||||
|
const allZIndexes = allNodes.map(n => n.zIndex).filter(z => z !== undefined);
|
||||||
|
const minZIndex = allZIndexes.length > 0 ? Math.min(...allZIndexes) : 1;
|
||||||
|
const newZIndex = minZIndex - 1;
|
||||||
|
|
||||||
|
currentNode.setZIndex(newZIndex);
|
||||||
|
|
||||||
// 操作后再次查看
|
// 操作后再次查看
|
||||||
console.log('[置于底层] 操作后所有节点的 zIndex:', allNodes.map(n => ({ id: n.id, zIndex: n.zIndex })));
|
console.log('[置于底层] 操作后所有节点的 zIndex:', allNodes.map(n => ({ id: n.id, zIndex: n.zIndex })));
|
||||||
@@ -699,7 +694,7 @@ onMounted(() => {
|
|||||||
grid: { type: 'dot', size: 10 },
|
grid: { type: 'dot', size: 10 },
|
||||||
allowResize: true,
|
allowResize: true,
|
||||||
allowRotate: true,
|
allowRotate: true,
|
||||||
overlapMode: 0,
|
overlapMode: -1,
|
||||||
snapline: snaplineEnabled.value,
|
snapline: snaplineEnabled.value,
|
||||||
keyboard: {
|
keyboard: {
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -916,45 +911,33 @@ onMounted(() => {
|
|||||||
|
|
||||||
registerNodes(lfInstance);
|
registerNodes(lfInstance);
|
||||||
setLogicFlowInstance(lfInstance);
|
setLogicFlowInstance(lfInstance);
|
||||||
lfInstance.render({
|
|
||||||
nodes: [],
|
|
||||||
edges: []
|
|
||||||
});
|
|
||||||
lfInstance.extension.miniMap.show();
|
|
||||||
normalizeAllNodes();
|
|
||||||
lfInstance.updateEditConfig({
|
|
||||||
multipleSelectKey: 'shift',
|
|
||||||
snapGrid: snapGridEnabled.value
|
|
||||||
});
|
|
||||||
applySelectionSelect(selectionEnabled.value);
|
|
||||||
updateSelectedCount(lfInstance.graphModel);
|
|
||||||
|
|
||||||
// 监听节点点击事件,更新 selectedNode
|
|
||||||
lfInstance.on(EventType.NODE_CLICK, ({ data }) => {
|
|
||||||
selectedNode.value = data;
|
|
||||||
updateSelectedCount();
|
|
||||||
});
|
|
||||||
|
|
||||||
lfInstance.on(EventType.NODE_DRAG, (args) => handleNodeDrag(args as any));
|
|
||||||
|
|
||||||
|
// 监听所有可能的节点添加事件
|
||||||
lfInstance.on(EventType.NODE_ADD, ({ data }) => {
|
lfInstance.on(EventType.NODE_ADD, ({ data }) => {
|
||||||
console.log('[NODE_ADD 事件触发] 节点ID:', data.id);
|
|
||||||
const model = lfInstance.getNodeModelById(data.id);
|
const model = lfInstance.getNodeModelById(data.id);
|
||||||
if (model) {
|
if (model) {
|
||||||
console.log('[NODE_ADD] 获取到节点模型,当前 zIndex:', model.zIndex);
|
|
||||||
normalizeNodeModel(model);
|
normalizeNodeModel(model);
|
||||||
|
|
||||||
// 设置新节点的 zIndex 为 1000
|
// 设置新节点的 zIndex 为 1000
|
||||||
const newZIndex = 1000;
|
model.setZIndex(1000);
|
||||||
console.log(`[NODE_ADD] 准备设置 zIndex: ${newZIndex}`);
|
// 标记这个节点是新创建的,避免被 normalizeAllNodes 重置
|
||||||
model.setZIndex(newZIndex);
|
(model as any)._isNewNode = true;
|
||||||
console.log(`[NODE_ADD] 设置后的 zIndex:`, model.zIndex);
|
|
||||||
} else {
|
|
||||||
console.log('[NODE_ADD] 未能获取到节点模型');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
lfInstance.on(EventType.GRAPH_RENDERED, () => normalizeAllNodes());
|
// 监听 DND 添加节点事件
|
||||||
|
lfInstance.on('node:dnd-add', ({ data }) => {
|
||||||
|
const model = lfInstance.getNodeModelById(data.id);
|
||||||
|
if (model) {
|
||||||
|
// 设置新节点的 zIndex 为 1000
|
||||||
|
model.setZIndex(1000);
|
||||||
|
// 标记这个节点是新创建的
|
||||||
|
(model as any)._isNewNode = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lfInstance.on(EventType.GRAPH_RENDERED, () => {
|
||||||
|
normalizeAllNodes();
|
||||||
|
});
|
||||||
|
|
||||||
// 监听空白点击事件,取消选中
|
// 监听空白点击事件,取消选中
|
||||||
lfInstance.on(EventType.BLANK_CLICK, () => {
|
lfInstance.on(EventType.BLANK_CLICK, () => {
|
||||||
|
|||||||
@@ -1,9 +1,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, inject, onMounted, onBeforeUnmount } from 'vue';
|
||||||
import { toTextStyle } from '@/ts/nodeStyle';
|
import { toTextStyle } from '@/ts/nodeStyle';
|
||||||
import { useNodeAppearance } from '@/ts/useNodeAppearance';
|
import { useNodeAppearance } from '@/ts/useNodeAppearance';
|
||||||
|
|
||||||
const currentShikigami = ref({ name: '未选择式神', avatar: '', rarity: '' });
|
const currentShikigami = ref({ name: '未选择式神', avatar: '', rarity: '' });
|
||||||
|
const getNode = inject('getNode') as (() => any) | undefined;
|
||||||
|
const zIndex = ref(1);
|
||||||
|
let intervalId: number | null = null;
|
||||||
|
|
||||||
|
// 使用轮询方式定期更新 zIndex
|
||||||
|
onMounted(() => {
|
||||||
|
const node = getNode?.();
|
||||||
|
if (node) {
|
||||||
|
zIndex.value = node.zIndex ?? 1;
|
||||||
|
|
||||||
|
// 每 100ms 检查一次 zIndex 是否变化
|
||||||
|
intervalId = window.setInterval(() => {
|
||||||
|
const currentZIndex = node.zIndex ?? 1;
|
||||||
|
if (zIndex.value !== currentZIndex) {
|
||||||
|
zIndex.value = currentZIndex;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (intervalId !== null) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const { containerStyle, textStyle } = useNodeAppearance({
|
const { containerStyle, textStyle } = useNodeAppearance({
|
||||||
onPropsChange(props) {
|
onPropsChange(props) {
|
||||||
@@ -18,6 +43,7 @@ const mergedContainerStyle = computed(() => ({ ...containerStyle.value, boxSizin
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="node-content" :style="mergedContainerStyle">
|
<div class="node-content" :style="mergedContainerStyle">
|
||||||
|
<div class="zindex-badge">{{ zIndex }}</div>
|
||||||
<img
|
<img
|
||||||
v-if="currentShikigami.avatar"
|
v-if="currentShikigami.avatar"
|
||||||
:src="currentShikigami.avatar"
|
:src="currentShikigami.avatar"
|
||||||
@@ -36,6 +62,20 @@ const mergedContainerStyle = computed(() => ({ ...containerStyle.value, boxSizin
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.zindex-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
background: rgba(64, 158, 255, 0.9);
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.shikigami-image {
|
.shikigami-image {
|
||||||
width: 85%;
|
width: 85%;
|
||||||
|
|||||||
@@ -1,8 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue';
|
||||||
import { useNodeAppearance } from '@/ts/useNodeAppearance';
|
import { useNodeAppearance } from '@/ts/useNodeAppearance';
|
||||||
|
|
||||||
const currentYuhun = ref({ name: '未选择御魂', avatar: '', type: '' });
|
const currentYuhun = ref({ name: '未选择御魂', avatar: '', type: '' });
|
||||||
|
const getNode = inject('getNode') as (() => any) | undefined;
|
||||||
|
const zIndex = ref(1);
|
||||||
|
let intervalId: number | null = null;
|
||||||
|
|
||||||
|
// 使用轮询方式定期更新 zIndex
|
||||||
|
onMounted(() => {
|
||||||
|
const node = getNode?.();
|
||||||
|
if (node) {
|
||||||
|
zIndex.value = node.zIndex ?? 1;
|
||||||
|
|
||||||
|
// 每 100ms 检查一次 zIndex 是否变化
|
||||||
|
intervalId = window.setInterval(() => {
|
||||||
|
const currentZIndex = node.zIndex ?? 1;
|
||||||
|
if (zIndex.value !== currentZIndex) {
|
||||||
|
zIndex.value = currentZIndex;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (intervalId !== null) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const { containerStyle, textStyle } = useNodeAppearance({
|
const { containerStyle, textStyle } = useNodeAppearance({
|
||||||
onPropsChange(props) {
|
onPropsChange(props) {
|
||||||
@@ -15,6 +40,7 @@ const { containerStyle, textStyle } = useNodeAppearance({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="node-content" :style="containerStyle">
|
<div class="node-content" :style="containerStyle">
|
||||||
|
<div class="zindex-badge">{{ zIndex }}</div>
|
||||||
<img
|
<img
|
||||||
v-if="currentYuhun.avatar"
|
v-if="currentYuhun.avatar"
|
||||||
:src="currentYuhun.avatar"
|
:src="currentYuhun.avatar"
|
||||||
@@ -34,6 +60,20 @@ const { containerStyle, textStyle } = useNodeAppearance({
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.zindex-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
background: rgba(64, 158, 255, 0.9);
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.yuhun-image {
|
.yuhun-image {
|
||||||
width: 85%;
|
width: 85%;
|
||||||
|
|||||||
@@ -326,9 +326,10 @@ export const useFilesStore = defineStore('files', () => {
|
|||||||
...graphData,
|
...graphData,
|
||||||
nodes: (graphData.nodes || []).map((node: any) => {
|
nodes: (graphData.nodes || []).map((node: any) => {
|
||||||
const model = logicFlowInstance.getNodeModelById(node.id);
|
const model = logicFlowInstance.getNodeModelById(node.id);
|
||||||
|
const zIndex = model?.zIndex ?? node.zIndex ?? 1;
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
zIndex: model?.zIndex ?? node.zIndex ?? 1
|
zIndex: zIndex
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -338,7 +339,6 @@ export const useFilesStore = defineStore('files', () => {
|
|||||||
if (file) {
|
if (file) {
|
||||||
file.graphRawData = enrichedGraphData;
|
file.graphRawData = enrichedGraphData;
|
||||||
file.transform = transform;
|
file.transform = transform;
|
||||||
console.log(`已同步画布数据到文件 "${file.name}"(${targetId}),包含 zIndex 信息`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default mergeConfig(
|
|||||||
exclude: [...configDefaults.exclude, 'e2e/*'],
|
exclude: [...configDefaults.exclude, 'e2e/*'],
|
||||||
root: fileURLToPath(new URL('./', import.meta.url)),
|
root: fileURLToPath(new URL('./', import.meta.url)),
|
||||||
globals: true,
|
globals: true,
|
||||||
|
setupFiles: ['./src/__tests__/setup.ts'],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user