# DOM 渲染功能测试指南

## 🎯 测试目标

验证从 Konva Canvas 迁移到 DOM 渲染后，所有功能正常工作，特别是在微信小程序环境中。

## 🔧 测试环境

### 浏览器环境
- Chrome/Edge (开发调试)
- Safari (iOS 兼容性)
- 微信开发者工具 (小程序环境)

### 设备
- 桌面端 (鼠标操作)
- 移动端 (触摸操作)
- 平板 (双指缩放)

## ✅ 功能测试清单

### 1. 基础绘制功能

#### 1.1 矩形工具
**测试步骤**:
1. 选择矩形工具
2. 在画布上按下鼠标/手指
3. 拖动到目标位置
4. 释放鼠标/手指

**预期结果**:
- ✅ 矩形正确绘制
- ✅ 矩形位置、大小准确
- ✅ 矩形颜色、边框宽度符合设置
- ✅ 可以绘制负方向矩形（从右下往左上拖）

**测试数据**:
```typescript
// 预期数据结构
{
  id: "shape_xxx",
  type: "rect",
  x: 100,
  y: 100,
  width: 200,
  height: 150,
  stroke: "#ff0000",
  strokeWidth: 2
}
```

#### 1.2 笔工具（线条）
**测试步骤**:
1. 选择笔工具
2. 在画布上绘制任意路径
3. 释放鼠标/手指

**预期结果**:
- ✅ 线条流畅，无断点
- ✅ 线条颜色、粗细符合设置
- ✅ 线条端点圆滑（lineCap: round）
- ✅ 快速绘制时不丢失点

**测试数据**:
```typescript
{
  id: "shape_xxx",
  type: "line",
  x: 0,
  y: 0,
  points: [100, 100, 105, 102, 110, 105, ...],
  stroke: "#ff0000",
  strokeWidth: 2
}
```

#### 1.3 文本注释
**测试步骤**:
1. 选择文本工具
2. 点击画布位置
3. 输入文本内容
4. 确认

**预期结果**:
- ✅ 弹出输入框
- ✅ 文本正确显示在点击位置
- ✅ 文本大小、颜色符合设置
- ✅ 支持多行文本
- ✅ 支持特殊字符

**测试用例**:
- 普通文本: "扣1分"
- 数字: "123"
- 特殊字符: "+5", "-2"
- 多行文本: "第一行\n第二行"

#### 1.4 特殊标记

**正确标记 ✓**:
- ✅ 显示红色勾号
- ✅ 位置准确
- ✅ 大小适中（20x20）

**错误标记 ✗**:
- ✅ 显示红色叉号
- ✅ 位置准确
- ✅ 大小适中（20x20）

**半对标记**:
- ✅ 显示勾号+斜线组合
- ✅ 位置准确
- ✅ 大小适中（20x20）

### 2. 擦除功能

#### 2.1 点击擦除
**测试步骤**:
1. 绘制多个不同类型的标记
2. 选择擦除工具
3. 点击各个标记

**预期结果**:
- ✅ 点击矩形内部，矩形被删除
- ✅ 点击线条附近（5px内），线条被删除
- ✅ 点击特殊标记，标记被删除
- ✅ 点击文本，文本被删除
- ✅ 点击空白处，无反应

**碰撞检测测试**:
```typescript
// 矩形边界测试
点击 (100, 100) -> 在矩形内 ✅
点击 (99, 99) -> 在矩形外 ❌
点击 (300, 250) -> 在矩形内 ✅
点击 (301, 251) -> 在矩形外 ❌

// 线条距离测试
点击距离线条 3px -> 命中 ✅
点击距离线条 10px -> 未命中 ❌

// 特殊标记测试（中心点 100, 100）
点击 (100, 100) -> 命中 ✅
点击 (90, 90) -> 命中 ✅
点击 (110, 110) -> 命中 ✅
点击 (89, 89) -> 未命中 ❌
```

#### 2.2 拖动擦除
**测试步骤**:
1. 绘制多个标记
2. 选择擦除工具
3. 按住鼠标/手指拖动经过标记

**预期结果**:
- ✅ 经过的标记被连续删除
- ✅ 擦除流畅，无卡顿

### 3. 撤销/重做功能

#### 3.1 撤销操作
**测试步骤**:
1. 依次绘制：矩形 -> 线条 -> 文本
2. 点击撤销按钮 3 次

**预期结果**:
- ✅ 第1次撤销：文本消失
- ✅ 第2次撤销：线条消失
- ✅ 第3次撤销：矩形消失
- ✅ 第4次撤销：按钮禁用，无反应

**命令栈验证**:
```typescript
// 初始状态
commandStack: []
currentIndex: -1
canUndo: false

// 绘制矩形后
commandStack: [AddShapeCommand(rect)]
currentIndex: 0
canUndo: true

// 绘制线条后
commandStack: [AddShapeCommand(rect), AddShapeCommand(line)]
currentIndex: 1
canUndo: true

// 撤销一次后
commandStack: [AddShapeCommand(rect), AddShapeCommand(line)]
currentIndex: 0
canUndo: true
canRedo: true
```

#### 3.2 重做操作
**测试步骤**:
1. 绘制标记
2. 撤销
3. 点击重做

**预期结果**:
- ✅ 标记重新出现
- ✅ 位置、样式与原来一致

#### 3.3 撤销后新建
**测试步骤**:
1. 绘制：A -> B -> C
2. 撤销 2 次（剩下 A）
3. 绘制新标记 D

**预期结果**:
- ✅ B、C 从命令栈中移除
- ✅ 不能重做 B、C
- ✅ 可以撤销 D

```typescript
// 撤销2次后
commandStack: [A, B, C]
currentIndex: 0

// 绘制D后
commandStack: [A, D]  // B、C被截断
currentIndex: 1
canRedo: false
```

#### 3.4 清空所有标记
**测试步骤**:
1. 绘制多个标记
2. 点击"清空"按钮
3. 点击撤销

**预期结果**:
- ✅ 所有标记消失
- ✅ 撤销后所有标记恢复

### 4. 缩放功能

#### 4.1 手动缩放
**测试步骤**:
1. 设置缩放比例为 0.5
2. 绘制标记
3. 设置缩放比例为 2.0

**预期结果**:
- ✅ 图片大小正确变化
- ✅ 标记跟随图片缩放
- ✅ 标记位置相对图片不变
- ✅ 新绘制的标记坐标正确

**坐标转换验证**:
```typescript
// 缩放 0.5 时点击屏幕 (100, 100)
实际坐标 = (100 / 0.5, 100 / 0.5) = (200, 200)

// 缩放 2.0 时点击屏幕 (100, 100)
实际坐标 = (100 / 2.0, 100 / 2.0) = (50, 50)
```

#### 4.2 自适应宽度模式
**测试步骤**:
1. 启用自适应宽度
2. 调整浏览器窗口宽度

**预期结果**:
- ✅ 图片宽度自动适应容器
- ✅ 高度按比例缩放
- ✅ 标记位置正确

#### 4.3 双指缩放（触摸设备）
**测试步骤**:
1. 在触摸设备上打开
2. 双指捏合/展开

**预期结果**:
- ✅ 图片跟随手势缩放
- ✅ 显示缩放百分比提示
- ✅ 标记跟随缩放

### 5. 快捷打分功能

#### 5.1 加分模式
**测试步骤**:
1. 设置快捷打分：加分模式，分值 2
2. 启用快捷打分点击模式
3. 点击画布

**预期结果**:
- ✅ 显示 "+2" 文本标记
- ✅ 分数增加 2 分
- ✅ 文本颜色为红色
- ✅ 不能超过满分

**边界测试**:
```typescript
满分: 10
当前: 9
点击加2 -> 分数变为 10 ✅
再次点击 -> 无反应 ✅
```

#### 5.2 减分模式
**测试步骤**:
1. 设置快捷打分：减分模式，分值 1
2. 启用快捷打分点击模式
3. 点击画布

**预期结果**:
- ✅ 显示 "-1" 文本标记
- ✅ 分数减少 1 分
- ✅ 文本颜色为蓝色
- ✅ 不能低于 0 分

### 6. 多图片场景

#### 6.1 多图片渲染
**测试步骤**:
1. 加载包含 3 张图片的题目
2. 在每张图片上绘制不同标记

**预期结果**:
- ✅ 每张图片独立渲染
- ✅ 标记数据互不干扰
- ✅ 撤销操作针对最后操作的图片

#### 6.2 横向/纵向布局
**测试步骤**:
1. 切换图片布局模式

**预期结果**:
- ✅ 横向布局：图片水平排列
- ✅ 纵向布局：图片垂直排列
- ✅ 标记位置不受影响

### 7. 只读模式

#### 7.1 历史记录查看
**测试步骤**:
1. 打开已批改的试卷
2. 查看标记

**预期结果**:
- ✅ 所有标记正确显示
- ✅ 不响应点击事件
- ✅ 不显示工具栏

### 8. 数据持久化

#### 8.1 数据导出
**测试步骤**:
1. 绘制多种标记
2. 导出数据

**预期结果**:
```json
{
  "version": "1.0",
  "shapes": [
    {
      "id": "shape_xxx",
      "type": "rect",
      "x": 100,
      "y": 100,
      "width": 200,
      "height": 150,
      "stroke": "#ff0000",
      "strokeWidth": 2
    }
  ],
  "specialMarks": [
    {
      "id": "mark_xxx",
      "type": "correct",
      "x": 300,
      "y": 200
    }
  ],
  "annotations": [
    {
      "id": "text_xxx",
      "x": 400,
      "y": 300,
      "text": "扣1分",
      "fontSize": 14,
      "color": "#000000"
    }
  ]
}
```

#### 8.2 数据导入
**测试步骤**:
1. 导入上述 JSON 数据
2. 查看渲染结果

**预期结果**:
- ✅ 所有标记正确恢复
- ✅ 位置、样式一致

### 9. 性能测试

#### 9.1 大量标记渲染
**测试步骤**:
1. 导入包含 100+ 标记的数据
2. 观察渲染性能

**预期结果**:
- ✅ 初始渲染 < 1s
- ✅ 滚动流畅，无卡顿
- ✅ 内存占用合理

#### 9.2 快速连续绘制
**测试步骤**:
1. 快速绘制多条线条

**预期结果**:
- ✅ 线条流畅，无延迟
- ✅ 不丢失点
- ✅ 不出现重复标记

### 10. 兼容性测试

#### 10.1 微信小程序
**测试步骤**:
1. 在微信开发者工具中运行
2. 测试所有功能

**预期结果**:
- ✅ 所有功能正常
- ✅ 触摸事件响应正常
- ✅ 无控制台错误

#### 10.2 不同分辨率
**测试设备**:
- iPhone SE (375x667)
- iPhone 14 Pro (393x852)
- iPad (768x1024)
- Desktop (1920x1080)

**预期结果**:
- ✅ 布局自适应
- ✅ 标记大小合适
- ✅ 触摸区域足够大

## 🐛 Bug 报告模板

```markdown
### Bug 描述
简要描述问题

### 复现步骤
1. 步骤1
2. 步骤2
3. 步骤3

### 预期行为
应该发生什么

### 实际行为
实际发生了什么

### 环境信息
- 设备: iPhone 14
- 系统: iOS 16.0
- 浏览器: 微信开发者工具
- 版本: 1.0.0

### 截图/录屏
如果可能，提供截图或录屏

### 相关数据
如果涉及数据问题，提供 JSON 数据
```

## 📊 测试报告模板

```markdown
# DOM 渲染功能测试报告

## 测试概况
- 测试日期: 2024-01-01
- 测试人员: XXX
- 测试环境: 微信开发者工具 / Chrome
- 测试版本: 1.0.0

## 测试结果汇总
| 功能模块 | 测试用例数 | 通过 | 失败 | 通过率 |
|---------|-----------|------|------|--------|
| 基础绘制 | 10 | 10 | 0 | 100% |
| 擦除功能 | 5 | 5 | 0 | 100% |
| 撤销重做 | 8 | 8 | 0 | 100% |
| 缩放功能 | 6 | 6 | 0 | 100% |
| 快捷打分 | 4 | 4 | 0 | 100% |
| 多图片 | 3 | 3 | 0 | 100% |
| 只读模式 | 2 | 2 | 0 | 100% |
| 数据持久化 | 4 | 4 | 0 | 100% |
| 性能测试 | 3 | 3 | 0 | 100% |
| 兼容性 | 5 | 5 | 0 | 100% |
| **总计** | **50** | **50** | **0** | **100%** |

## 详细测试结果
[详细记录每个测试用例的结果]

## 发现的问题
[列出发现的所有问题]

## 建议
[提出改进建议]

## 结论
✅ 通过 / ❌ 不通过
```

## 🎯 自动化测试（可选）

### 单元测试示例
```typescript
import { describe, it, expect } from 'vitest'
import { useSimpleDomLayer } from './useMarkingDom'

describe('useMarkingDom', () => {
  it('should add shape correctly', () => {
    const layer = useSimpleDomLayer({...})
    
    // 模拟绘制矩形
    layer.handleMouseDown(mockEvent, mockRect)
    layer.handleMouseMove(mockEvent, mockRect)
    layer.handleMouseUp()
    
    expect(layer.markingData.value.shapes).toHaveLength(1)
    expect(layer.markingData.value.shapes[0].type).toBe('rect')
  })
  
  it('should undo correctly', () => {
    const layer = useSimpleDomLayer({...})
    
    // 添加标记
    layer.handleMouseDown(mockEvent, mockRect)
    layer.handleMouseUp()
    
    expect(layer.markingData.value.shapes).toHaveLength(1)
    
    // 撤销
    layer.undo()
    
    expect(layer.markingData.value.shapes).toHaveLength(0)
  })
})
```

## 📝 测试记录

### 测试日期: ___________
### 测试人员: ___________

| 测试项 | 状态 | 备注 |
|--------|------|------|
| 绘制矩形 | ⬜ | |
| 绘制线条 | ⬜ | |
| 文本注释 | ⬜ | |
| 特殊标记 | ⬜ | |
| 擦除功能 | ⬜ | |
| 撤销操作 | ⬜ | |
| 重做操作 | ⬜ | |
| 清空标记 | ⬜ | |
| 手动缩放 | ⬜ | |
| 自适应宽度 | ⬜ | |
| 快捷打分 | ⬜ | |
| 多图片 | ⬜ | |
| 只读模式 | ⬜ | |
| 数据导出 | ⬜ | |
| 数据导入 | ⬜ | |
| 性能测试 | ⬜ | |
| 小程序兼容 | ⬜ | |

**签名**: ___________

