chore: checkpoint current milestone work

This commit is contained in:
shen
2026-05-22 13:28:53 +08:00
parent 6d196d64e5
commit 5698aeaead
68 changed files with 5694 additions and 3291 deletions
@@ -34,6 +34,8 @@ ReadViewSDK 当前对 EPUB 有三类渲染路径:
## 2. 关键事实核验(当前代码真实路径)
> 纠偏说明:`.planning/PROJECT.md` 在初始化时曾把“reflowable EPUB 当前路径”概括为偏 `WKWebView` 的历史性表述。经本次代码核验,当前真实主路径是 `.textReflowable` → `RDEPUBTextBookBuilder` → `RDEPUBDTCoreTextRenderer` → CoreText 分页 → `RDEPUBTextContentView`。本设计以代码事实为准,并默认后续计划/实现都按此理解推进。
### 2.1 渲染路径分流(已存在)
- 判定在 `Sources/RDReaderView/EPUBCore/RDEPUBParser+ReadingProfile.swift`
@@ -69,6 +71,12 @@ ReadViewSDK 当前对 EPUB 有三类渲染路径:
- B:**资源解析**(图片/CSS 的相对路径 baseURL)与稳定性保障
- C:在现有分页基础上逐步迭代(先可用,后对齐“避免断页”等高级策略)
### 3.1 第一阶段实施假设(必须遵守)
- H1:**第一期只对齐“管线形态”和“CSS 分层策略”**,即把现有 `.textReflowable` renderer 增强为更接近 WXRead 的 typesetter 输入与样式组织方式。
- H2**第一期不实现 WXRead 对 DTCoreText 的深度魔改**,包括但不限于自定义 CSS 属性体系、复杂附件布局规则、完整的 `WRCoreTextLayouter` / `WRCoreTextLayoutFrame` 等价分页器。
- H3:当开发过程中遇到图片断页、复杂样式缺失、附件布局异常等问题时,默认先作为“第二阶段问题清单”记录;只有在它阻塞 `REND-01` / `STAB-02` 的最小验收时,才允许做局部补丁,而不是扩展为全面重写分页引擎。
## 4. 是否能直接使用 Doc/WXRead 中的 JS/CSS
结论:**不建议、也不应该直接把“来自微信读书 App bundle 的私有 JS/CSS”拷贝进 SDK 作为产品代码**;但可以按以下原则“选择性使用”:
@@ -169,6 +177,8 @@ WXRead 的更高阶策略(参考 `Doc/WXRead/analysis/DTCoreText自定义修
- Phase 2:先保持现有分页算法,只要渲染输入(CSS 分层 + 后处理)到位,就能显著改善一致性。
- Phase 3:针对真实书籍出现的问题,逐条补齐分页规则(问题驱动),避免一开始就引入复杂分页器导致风险扩大。
> 范围约束:如果某个分页问题需要引入“新的复杂分页器”或大规模模拟 `WRCoreTextLayouter` / `WRCoreTextLayoutFrame`,应先暂停并回到方案讨论,不默认并入第一期实现。
### 5.5 与现有高亮/搜索/位置映射的兼容
当前 `.textReflowable` 路径:
@@ -224,7 +234,11 @@ WXRead 的更高阶策略(参考 `Doc/WXRead/analysis/DTCoreText自定义修
### 对应 STAB-01 / STAB-02
- `RDURLReaderController` 打开 `.epub` / `.txt` 主流程不回归
- 2-3 本典型 reflowable EPUB:不崩溃、不白屏、不无限加载;分页/翻页可用
- 至少使用以下 3 类 reflowable EPUB 样本进行回归:
- 样本 A:纯文本/小说类章节为主,验证基础段落、标题、分页与阅读位置恢复
- 样本 B:包含内嵌图片与多段样式的章节,验证图片显示、图片前后分页、基础 CSS 生效
- 样本 C:包含外链与多个 CSS 文件引用的章节,验证 baseURL、样式解析与链接呈现稳定性
- 对以上样本的共同要求:不崩溃、不白屏、不无限加载;分页/翻页可用
## 8. 风险清单与降级策略
@@ -1,398 +0,0 @@
# 书签能力方案讨论
## 需求背景
`Doc/EPUB_MAINTENANCE.md` 已将“书签能力”列为 EPUB 阅读器下一阶段的高优先级能力之一。
当前阅读器已经具备:
1. EPUB 打开与解析
2. WebView / DTCoreText / fixed-layout 三条正文渲染路径
3. 目录跳转、阅读位置恢复、主题与字号调整
4. 高亮、划线、批注、搜索等阅读交互能力
但现阶段“位置记录”仍主要依赖:
1. 自动保存的上次阅读位置
2. 需要选中文本的高亮 / 批注能力
这会遗漏一种非常常见、也非常轻量的阅读诉求:
1. 只想标记当前位置
2. 稍后回来继续读
3. 不需要摘录正文
4. 不需要输入批注
因此,“书签能力”这项需求的目标,不是扩展现有标注功能,而是补上一条面向“位置收藏”的轻量链路,用来覆盖“只记录位置、不做摘录”的常见阅读需求。
## 代码事实
### 已有基础
当前仓库其实已经具备不少可直接复用的基础设施:
1. `RDEPUBLocation` 已能稳定表达 `href + progression + fragment`
2. `RDEPUBReaderController` 已具备“当前位置读取”和“跳转到位置”的主链路
3. `RDEPUBReaderPersistence` 已形成按 `bookIdentifier` 分书持久化的模式
4. 高亮管理页已经具备“列表展示 -> 点击跳转 -> 删除”的成熟交互范式
结论:
1. 书签不需要重新设计位置模型
2. 书签可以复用现有定位和跳转能力
3. 更需要新增的是独立模型、持久化存储位和 UI 入口
### 当前实现存在的问题
#### 问题 1:当前只有单一阅读位置,没有“多书签”能力
`RDEPUBReaderPersistence` 当前只提供:
1. `loadLocation / saveLocation`
2. `loadHighlights / saveHighlights`
3. `loadReaderSettings / saveReaderSettings`
这意味着:
1. 系统只能保存“上次读到哪”
2. 不能保存“用户主动收藏的多个位置”
而这两者的语义并不相同:
1. 阅读位置是自动覆盖的
2. 书签是主动创建、可长期保留多个的
#### 问题 2:当前标注模型不适合直接承载书签
虽然看起来可以用 `RDEPUBHighlight` 勉强模拟书签,但这会带来明显问题:
1. 书签不一定有选中文本
2. 书签不应依赖 `rangeInfo`
3. 书签列表应以“位置”为中心,而不是以“摘录内容”为中心
4. 书签与高亮、划线、批注的展示和管理语义不同
因此,不建议把书签硬塞进现有高亮模型。
#### 问题 3:当前 UI 没有书签入口与状态反馈
`RDEPUBReaderBottomToolView` 当前结构来看,只有:
1. 目录
2. 批注列表
3. 添加标注
4. 设置
当前并没有:
1. 当前页添加 / 取消书签入口
2. 书签列表入口
3. 当前阅读位置是否已加书签的状态反馈
这意味着即便补了数据层,用户仍然无法自然感知和使用这项能力。
## 需求拆解
建议把“书签能力”拆成四部分推进。
### 1. 独立书签模型
需要新增一个面向“位置记录”的独立模型,而不是复用高亮模型。
### 2. 独立书签持久化
需要新增书签读写接口,支持每本书存储多个书签。
### 3. 书签入口与状态反馈
需要让用户在当前页能轻量创建 / 取消书签,并能看到当前位置的书签状态。
### 4. 书签管理与跳转
需要补齐书签列表、点击跳转、删除等基本管理闭环。
## 技术路线对比
### 路线 A:把书签并入 `RDEPUBHighlight`
思路:
1.`RDEPUBHighlight` 增加一个新的 style 或特殊标识
2. 将书签存入 highlights
3. 复用现有标注列表页
优点:
1. 表面上改动范围较小
2. 可以快速借用现有列表管理代码
风险:
1. 模型语义不清晰
2. 书签没有文本选区时会很别扭
3. 后续展示、筛选、持久化逻辑会越来越绕
4. 容易让“书签”和“标注”边界变得混乱
### 路线 B:完全独立一条书签链路
思路:
1. 新建 `RDEPUBBookmark`
2. 新增 `loadBookmarks / saveBookmarks`
3. 新增书签列表页
4. 独立接入添加、删除、跳转、状态更新
优点:
1. 模型语义清晰
2. 更符合产品能力边界
3. 后续扩展空间更好
风险:
1. 会新增一部分 UI 与管理代码
2. 与高亮列表在形态上会有一定重复
### 路线 C:模型独立,交互模式局部复用
思路:
1. 数据模型与持久化独立
2. 定位、跳转和列表交互模式尽量复用现有高亮链路
3. 第一阶段先做轻量闭环,再考虑更复杂管理能力
优点:
1. 语义清晰,同时控制改动范围
2. 能复用现有阅读位置链路和管理交互经验
3. 更适合当前仓库的演进方式
风险:
1. 仍需补一组新的数据与列表代码
2. 需要明确书签状态判定规则,避免重排后误判
### 推荐结论
建议采用:
**主路线:路线 C,模型独立,位置链路复用,UI 先做轻量闭环**
原因:
1. 书签和标注的语义不同,模型应保持独立
2. 当前仓库已有成熟的位置和跳转主链路,可以直接复用
3. 第一阶段先满足“记录位置”的核心需求,不需要把书签做得过重
## 推荐方案
### 方案总览
书签能力第一阶段建议形成如下结构:
1. 新增 `RDEPUBBookmark` 独立模型
2. 扩展 `RDEPUBReaderPersistence`,新增 bookmarks 读写
3.`RDEPUBReaderController` 中接入书签状态、添加、删除、跳转能力
4. 增加轻量级书签入口和书签列表页
5. 支持退出重进后的书签持久化恢复
### 推荐数据模型
建议书签模型至少包含以下字段:
1. `id`
2. `bookIdentifier`
3. `location`
4. `chapterTitle`
5. `displayText` 或摘要信息(可选)
6. `note`(可选)
7. `createdAt`
其中第一阶段真正必要的最小集合是:
1. `id`
2. `bookIdentifier`
3. `location`
4. `createdAt`
建议同时补上 `chapterTitle`,这样书签列表在展示时会更友好,也更接近真实阅读器使用习惯。
### 推荐持久化结构
建议在 `RDEPUBReaderPersistence` 中新增:
1. `loadBookmarks(for:)`
2. `saveBookmarks(_:for:)`
`RDEPUBUserDefaultsPersistence` 中新增:
1. `bookmarksPrefix`
第一阶段继续沿用当前:
1. `UserDefaults`
2. `JSONEncoder / JSONDecoder`
3. `bookIdentifier` 作用域隔离
这样能在最小改动下形成可用闭环。
### 推荐 UI 入口
第一阶段建议至少补两类入口:
1. 当前页添加 / 取消书签入口
2. 书签列表入口
比较自然的放置方式是:
1. 顶部工具栏增加“当前页书签”切换按钮
2. 底部工具栏增加“书签列表”入口
这样可以把两种动作区分开:
1. “加书签”是当前上下文动作
2. “看书签列表”是全局管理动作
### 推荐交互闭环
第一阶段建议用户路径收敛成下面这条主链路:
1. 用户在当前阅读位置点击书签按钮
2. 如果当前语义位置未加书签,则创建书签
3. 如果当前语义位置已加书签,则取消该书签
4. 用户可从书签列表查看当前书籍的全部书签
5. 点击任意书签后跳转到对应位置
6. 支持从列表中删除书签
## 关键实现建议
### Task K1:新增书签模型
文件:
1. `Sources/RDReaderView/EPUBCore/RDEPUBReadingModels.swift`
改动:
1. 新增 `RDEPUBBookmark: Codable, Equatable`
2. 字段以 `location` 为核心,不依赖选区文本
完成定义:
1. 可以表达一本书中的一个可持久化书签位置
### Task K2:扩展书签持久化协议
文件:
1. `Sources/RDReaderView/EPUBUI/RDEPUBReaderPersistence.swift`
改动:
1. 协议增加 `loadBookmarks / saveBookmarks`
2. `RDEPUBUserDefaultsPersistence` 增加 `bookmarksPrefix`
完成定义:
1. 每本书可以稳定读取和保存多个书签
### Task K3:控制器接入书签状态与操作
文件:
1. `Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
改动:
1. 增加 `activeBookmarks`
2. 打开图书时加载 bookmarks
3. 增加 `addBookmark`
4. 增加 `removeBookmark`
5. 增加 `toggleBookmark`
6. 增加 `go(to: bookmark)`
7. 增加当前阅读位置是否已书签的状态计算
完成定义:
1. 书签与阅读主链路、跳转链路、持久化链路打通
### Task K4:补书签 UI 与列表管理
文件:
1. `Sources/RDReaderView/EPUBUI/RDEPUBReaderBottomToolView.swift`
2. 新增 `Sources/RDReaderView/EPUBUI/RDEPUBReaderBookmarksViewController.swift`
3. 视情况补充顶部工具栏按钮
改动:
1. 添加书签入口
2. 添加书签列表入口
3. 点击书签跳转
4. 支持删除
5. 提供当前位置书签状态反馈
完成定义:
1. 用户能看到、添加、取消、查看和管理书签
## 当前页书签判定建议
“当前位置是否已加书签”不建议简单按“完全相同的 `progression`”判断,因为:
1. 字号变化会触发重分页
2. 视口尺寸变化会导致 progression 细微漂移
3. WebView 和文本分页的进度精度并不完全一致
更合理的判定策略是:
1. 优先比较标准化后的 `href`
2. 如果存在 `fragment`,优先使用 `fragment` 作为更强锚点
3. reflowable 路径允许一定 progression 容差
4. fixed-layout 路径优先按页级位置判断
这样可以减少用户在重排、重进或轻微位置变化时的书签状态抖动。
## 验收矩阵建议
第一阶段至少要覆盖以下验证维度:
1. 当前页可添加书签
2. 同一位置可取消书签
3. 书签列表展示正常
4. 点击书签可以跳转
5. 删除书签后列表更新正确
6. 退出重进后书签仍存在
7. 字号变化后书签仍能回到同章节或邻近语义位置
8. `webInteractive` 路径正常
9. `textReflowable` 路径正常
10. `webFixedLayout` 路径至少能按页恢复
## 风险与注意点
### 风险 1:把书签与高亮强行合并
这样短期看似省事,长期会让模型、持久化和 UI 语义都变重。
### 风险 2:过度依赖精确 progression 匹配
如果书签命中规则太严格,重分页后会显得“不稳定”,用户体验会很差。
### 风险 3:第一阶段把书签做得过重
书签能力的首要目标是“轻量记录位置”,不是一开始就做成复杂的收藏管理系统。
## 推荐实施顺序
如果目标是“最小投入换最大需求闭环”,建议按下面顺序推进:
1. 先补独立 `RDEPUBBookmark` 模型
2. 先扩展 `RDEPUBReaderPersistence` 的书签读写能力
3. 再在 `RDEPUBReaderController` 接入书签主链路
4. 最后补 UI 入口和书签列表页
5. 待第一阶段稳定后,再考虑备注、排序、筛选等增强能力
## 推荐结论
“书签能力”这项需求,建议最终收敛为下面这句话:
**为 EPUB 阅读器补齐一条独立的轻量书签链路,以 `RDEPUBLocation` 为核心记录位置,支持添加、取消、列表管理、跳转与持久化恢复,用来覆盖“只记录位置、不做摘录”的常见阅读需求。**
@@ -1,439 +0,0 @@
# 样书基线验证方案讨论
## 需求背景
`Doc/EPUB_MAINTENANCE.md` 已将“样书基线验证”列为 EPUB 阅读器下一阶段的高优先级能力之一。
当前阅读器已经具备:
1. EPUB 打开与解析
2. WebView / DTCoreText / fixed-layout 三条正文渲染路径
3. 目录跳转、阅读位置恢复、字号与主题调整
4. 高亮、划线、批注、搜索等阅读交互能力
但这些能力目前主要还是依赖“人手点一遍”来判断是否正常,缺少固定样书、固定流程、固定输出格式的基线验证机制。
因此,“样书基线验证”这项需求的目标,不是再增加几本 demo 书,而是基于当前已内置的四本 EPUB 样书,建立一套:
1. 可重复执行
2. 可横向比较
3. 可长期沉淀
4. 可逐步自动化
的阅读器基线验证机制。
## 代码事实
### 已有基础
当前仓库已经具备样书基线验证所需的基本条件:
1. `ReadViewSDKDemo/ReadViewSDKDemo/Resources/` 内已经内置四本 EPUB 样书
2. `RDReaderManager.bundledEPUBURLs(in:)` 会自动枚举 bundle 中所有 `.epub`
3. `RDReaderManager.demoBookItems(in:)` 已将这些样书接入 demo 书架入口
4. `ViewController` 支持通过 `-demo-book-title` 启动参数自动打开指定样书
这意味着当前并不缺“样书载体”或“执行入口”,更缺的是:
1. 哪本样书用于验证哪类能力
2. 每次验证时要记录哪些数据
3. 什么叫验证通过
4. 如何把结果沉淀成可比较的基线
### 当前实现存在的问题
#### 问题 1:四本样书当前只是 demo 资源,不是正式基线夹具
现在这四本书更多承担“可打开、可展示”的职责,但还没有被正式定义为:
1. 固定基线样书
2. 各自覆盖的风险场景
3. 必测能力清单
4. 异常归档与复验对象
#### 问题 2:当前没有统一的基线指标结构
`Doc/EPUB_MAINTENANCE.md` 已明确提到要补齐:
1. 分页耗时
2. 目录命中率
3. 末页事件
4. 设置恢复稳定性
但当前还没有统一规定:
1. 指标如何采集
2. 指标如何记录
3. 指标的通过标准是什么
4. 下次如何与这次比较
#### 问题 3:样书验证目标混杂了性能、正确性与兼容性
“样书基线验证”至少同时包含三类验证目标:
1. 性能基线:分页耗时、页数规模
2. 正确性基线:目录跳转、末页事件、位置恢复
3. 兼容性基线:不同 EPUB 类型在不同渲染路径下是否稳定
如果不拆开,执行时很容易退化成“随便翻一下,感觉没问题”。
## 需求拆解
建议把“样书基线验证”拆成四部分来推进。
### 1. 固定样书集
先把四本样书从“demo 资源”提升为“固定基线夹具”,后续版本迭代尽量不随意替换。
### 2. 固定样书职责
每本样书都明确它主要覆盖哪些风险,而不是所有书都做完全相同的验证。
### 3. 固定基线指标
每次验证时都按同一组字段记录结果,保证可比较。
### 4. 固定验证流程与输出模板
每轮验证都按统一步骤执行,并把结果写入统一模板,避免结论只停留在口头描述。
## 技术路线对比
### 路线 A:完全手工验证
思路:
1. 打开四本样书
2. 手工翻页、点目录、调字号
3. 用主观结论判断是否正常
优点:
1. 上手快
2. 不需要额外改工程
风险:
1. 结果不稳定
2. 不同人执行结论可能不同
3. 无法形成长期可比较的基线
### 路线 B:一开始就全量自动化
思路:
1. 直接把四本样书的打开、翻页、跳转、恢复全部接入 UI 自动化或脚本采集
2. 让基线数据自动产出
优点:
1. 理想状态下最规范
2. 长期收益高
风险:
1. 前期成本大
2. 当前测试基础设施还不完整
3. 部分阅读体验问题在第一阶段更适合半自动确认
### 路线 C:先建立半自动基线,再逐步自动化
思路:
1. 先固定样书矩阵与基线字段
2. 借助现有 demo 自动打开入口执行统一验证流程
3. 先沉淀结构化结果
4. 再将适合自动化的部分逐步纳入 XCTest / XCUITest
优点:
1. 投入和收益平衡更好
2. 能更快形成第一版基线
3. 不会被早期自动化建设阻塞
风险:
1. 需要纪律性执行模板
2. 初期仍有部分验证依赖人工操作
### 推荐结论
建议采用:
**主路线:路线 C,先建立半自动样书基线,再逐步自动化**
原因:
1. 当前仓库已经有四本固定样书和自动打开入口
2. 样书基线验证的第一目标是“有可比较的结果”,而不是“立刻完全自动化”
3. 自动化测试体系尚在建设中,先立住基线矩阵更划算
## 推荐方案
### 方案总览
样书基线验证第一阶段建议形成如下结构:
1. 固定四本样书,不随意替换
2. 每本样书绑定主要验证职责
3. 每轮版本验证按统一流程执行
4. 每次都输出结构化基线表
5. 对异常项保留备注与复验结论
### 四本样书矩阵
> 注:以下矩阵以当前样书名称和文档信息为基础,readingProfile 可在首次正式基线验证时补齐实测结果。
| 样书 | 主要定位 | 重点验证能力 | 适合记录的核心指标 |
|------|----------|--------------|--------------------|
| `爱忘事的熊爷爷.epub` | 轻量快速回归样书 | 打开成功、基础分页、目录基本可用、设置恢复 | 打开耗时、总页数、TOC 基本命中、设置恢复 |
| `张学良传.epub` | 标准长文 reflowable 样书 | 长文分页、目录跳转、字号变化后位置恢复 | 首次分页耗时、TOC 命中率、字号调整后恢复 |
| `宝山辽墓材料与释读.epub` | 复杂结构与学术内容样书 | 复杂 TOC / fragment 命中、图片与正文结构稳定性、搜索与标注抽样 | TOC 命中率、fragment 命中、搜索命中、高亮恢复 |
| `《凡人修仙传》精校版全本.epub` | 大体量长书与压力样书 | 大体量分页稳定性、持久化恢复、末页事件、长时阅读链路 | 分页耗时、总页数、末页事件、重进恢复稳定性 |
### 推荐验证维度
第一阶段建议围绕下面这些维度建立固定基线:
1. 打开是否成功
2. readingProfile 类型
3. spine 数量
4. 总页数是否合理
5. 首次分页耗时
6. TOC 是否可打开
7. TOC 抽样命中率
8. 末页事件是否正常触发
9. 字号调整后位置是否稳定恢复
10. 主题 / 设置是否稳定恢复
11. 重进后阅读位置是否恢复
12. 标注或高亮是否恢复
其中第一阶段最核心的四项,应与 `Doc/EPUB_MAINTENANCE.md` 对齐:
1. 分页耗时
2. 目录命中率
3. 末页事件
4. 设置恢复稳定性
## 推荐执行流程
建议每本样书都按同一流程执行,避免漏项。
### Step B1:冷启动打开样书
目标:
1. 验证样书可正常进入阅读器
2. 记录首次打开与首次分页表现
记录:
1. 是否打开成功
2. readingProfile
3. spine 数量
4. 总页数
5. 首次分页耗时
### Step B2:目录抽样验证
目标:
1. 验证 TOC 是否能正常打开
2. 验证典型目录项是否能跳转到正确章节或语义位置
建议:
1. 每本样书至少抽样 3 到 5 个目录项
2. 包含开头、中间、靠后位置
3. 如果目录层级复杂,额外抽样一个带 fragment 的条目
记录:
1. 抽样条目数
2. 命中数
3. 命中率
4. 失败条目与现象
### Step B3:末页事件验证
目标:
1. 验证阅读器到达末页时的状态变化是否正确
2. 检查不会提前触发或漏触发
记录:
1. 是否到达末页
2. 末页事件是否正常触发
3. 是否出现重复触发或不触发
### Step B4:设置恢复稳定性验证
目标:
1. 修改字号、主题或其他关键阅读设置
2. 验证设置变更后分页是否稳定
3. 验证退出重进后设置是否恢复
记录:
1. 修改的设置项
2. 修改后是否立即生效
3. 重进后是否恢复
4. 是否伴随位置飘移或异常白页
### Step B5:位置与标注恢复抽样验证
目标:
1. 在样书中间位置退出重进
2. 验证阅读位置恢复
3. 抽样验证一条高亮或标注恢复
记录:
1. 退出前位置
2. 重进后位置
3. 是否在同一章节或邻近语义位置
4. 高亮 / 标注是否仍存在
## 基线表模板
建议每轮验证都至少产出两张表:
1. 总览表
2. 单书详情表
### 模板 1:样书基线总览表
| 日期 | 版本 / 分支 | 样书 | readingProfile | spine 数量 | 总页数 | 首次分页耗时 | TOC 抽样 / 命中 | 末页事件 | 设置恢复 | 位置恢复 | 标注恢复 | 结论 | 备注 |
|------|--------------|------|----------------|------------|--------|--------------|-----------------|----------|----------|----------|----------|------|------|
| YYYY-MM-DD | branch / commit | 爱忘事的熊爷爷 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 通过 / 风险 / 失败 | 待填 |
| YYYY-MM-DD | branch / commit | 张学良传 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 通过 / 风险 / 失败 | 待填 |
| YYYY-MM-DD | branch / commit | 宝山辽墓材料与释读 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 通过 / 风险 / 失败 | 待填 |
| YYYY-MM-DD | branch / commit | 《凡人修仙传》精校版全本 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 通过 / 风险 / 失败 | 待填 |
### 模板 2:单书详细验证表
#### 样书信息
| 字段 | 值 |
|------|----|
| 样书名称 | 待填 |
| 文件大小 | 待填 |
| readingProfile | 待填 |
| spine 数量 | 待填 |
| TOC 条目数 | 待填 |
| 总页数 | 待填 |
| 验证日期 | 待填 |
| 验证版本 / 分支 | 待填 |
#### 核心基线结果
| 项目 | 结果 | 备注 |
|------|------|------|
| 打开成功 | 通过 / 失败 | 待填 |
| 首次分页耗时 | 待填 | 单位建议统一为 ms 或 s |
| TOC 打开 | 通过 / 失败 | 待填 |
| TOC 抽样命中率 | 待填 | 例如 `4/5` |
| 末页事件 | 通过 / 风险 / 失败 | 待填 |
| 字号调整后恢复 | 通过 / 风险 / 失败 | 待填 |
| 主题 / 设置恢复 | 通过 / 风险 / 失败 | 待填 |
| 重进后位置恢复 | 通过 / 风险 / 失败 | 待填 |
| 高亮 / 标注恢复 | 通过 / 风险 / 失败 | 待填 |
#### TOC 抽样记录
| 序号 | TOC 条目 | 预期目标 | 实际结果 | 是否命中 | 备注 |
|------|----------|----------|----------|----------|------|
| 1 | 待填 | 待填 | 待填 | 是 / 否 | 待填 |
| 2 | 待填 | 待填 | 待填 | 是 / 否 | 待填 |
| 3 | 待填 | 待填 | 待填 | 是 / 否 | 待填 |
| 4 | 待填 | 待填 | 待填 | 是 / 否 | 待填 |
| 5 | 待填 | 待填 | 待填 | 是 / 否 | 待填 |
#### 异常与复验记录
| 异常编号 | 场景 | 现象 | 复现条件 | 当前结论 | 备注 |
|----------|------|------|----------|----------|------|
| B-001 | 待填 | 待填 | 待填 | 待确认 / 已复现 / 已修复 | 待填 |
## 推荐输出规范
为了让基线真正可比较,建议每轮验证输出时统一遵守以下规范:
### 1. 样书集固定
第一阶段尽量只使用当前这四本 EPUB,不随意更换,避免基线漂移。
### 2. 字段命名固定
例如:
1. `首次分页耗时`
2. `TOC 抽样命中率`
3. `末页事件`
4. `设置恢复`
5. `位置恢复`
尽量不要每次换表头名称。
### 3. 结论分级固定
建议统一使用:
1. `通过`
2. `风险`
3. `失败`
其中:
1. `通过`:功能符合预期,无明显异常
2. `风险`:功能基本可用,但存在偏差、偶发不稳或需继续观察
3. `失败`:功能不符合预期,影响可用性
### 4. 差异说明固定
如果与上次基线相比发生变化,建议在备注中明确:
1. 是否变快
2. 是否变慢
3. 是否命中率下降
4. 是否新增异常
5. 是否已有异常被修复
## 风险与注意点
### 风险 1:把基线验证做成纯主观体验记录
如果只有“感觉正常”“翻起来没问题”这类结论,后续几乎无法比较。
### 风险 2:过度依赖绝对页码
页码会受到字号、行距、视口与渲染策略影响,样书基线更适合比较:
1. 是否能正常分页
2. 是否恢复到同章节或邻近语义位置
3. 是否出现明显回归
而不是执着于固定页号完全一致。
### 风险 3:样书职责不清导致验证重复或漏项
如果四本书都做完全相同的验证,会浪费时间;如果每本书都“随便点几下”,又会漏掉高风险场景。
## 推荐实施顺序
如果目标是“最小投入换最大稳定性提升”,建议按下面顺序推进:
1. 先将四本样书正式定义为固定基线样书
2. 先用本文件中的矩阵与模板跑第一轮人工基线
3. 在第一轮基线中补齐每本样书的实测 `readingProfile`、spine 数量、总页数等信息
4. 统一记录异常项与复验结论
5. 再将其中适合自动化的部分逐步并入 `XCTest / XCUITest`
## 推荐结论
“样书基线验证”这项需求,建议最终收敛为下面这句话:
**基于当前 demo 内置的四本固定 EPUB 样书,建立一套统一矩阵、统一流程、统一模板的基线验证机制,首期重点覆盖分页耗时、目录命中率、末页事件和设置恢复稳定性,并为后续自动化回归提供稳定样书夹具。**
@@ -1,372 +0,0 @@
# 横竖屏切换支持方案讨论
## 需求背景
`Doc/EPUB_MAINTENANCE.md``Doc/ARCHITECTURE.md` 都把“横竖屏切换支持”列为当前阅读器的高优先级待补能力。
目标不是单纯“屏幕旋转后不崩”,而是让 EPUB 阅读器在横竖屏切换后具备以下稳定行为:
1. 正文重新按新视口尺寸分页或重排
2. 阅读位置尽量准确恢复,不明显跳章、跳页
3. 工具栏、目录、标注、搜索状态不异常
4. pageCurl / horizontalScroll / verticalScroll / horizontalCoverScroll 四种展示模式行为一致
5. WebView、DTCoreText、fixed layout 三条正文路径都能正确处理
## 代码事实
### 已有基础
当前仓库并不是完全没有横竖屏处理基础:
- `RDReaderView.layoutSubviews()` 会检测 `previousIsLandscape``isLandscape` 的变化
文件:`Sources/RDReaderView/RDReaderView.swift`
- `RDReaderView.orientationChanged(isNowLandscape:)` 已能:
- 通知 `readerViewOrientationWillChange`
- 更新 `layout.isLandscapeDualPage`
- `pageCurl` 模式下重建 `UIPageViewController`
- 滚动模式下 `invalidateLayout + reloadData + setContentOffset`
- `RDEPUBReaderController.readerViewOrientationWillChange(...)` 当前会直接调用 `repaginatePreservingCurrentLocation()`
- `RDEPUBReaderController.repaginatePreservingCurrentLocation()` 已具备“先记录当前位置,再重新分页,再恢复位置”的主链路
- `RDEPUBLocation` 已采用 `href + progression + lastProgression + fragment` 模型,本身适合应对字号变化和尺寸变化后的重定位
结论:
- 基础设施已经存在
- 真正缺的是“时机是否稳定、入口是否统一、三条正文路径是否都覆盖、是否会重复触发”
### 当前实现存在的问题
#### 问题 1:方向变化回调被 `landscapeDualPageEnabled` 绑定
`RDReaderView.layoutSubviews()` 目前有:
```swift
guard landscapeDualPageEnabled, bounds.width > 0, bounds.height > 0 else { return }
```
这意味着:
- 只有开启 `landscapeDualPageEnabled` 时才会进入方向变化检测
- 如果宿主关闭横屏双页,但正文仍然会因为宽高变化而需要重排,此时不会触发回调
这对以下场景不正确:
1. `textReflowable` 文本书籍在横竖屏切换后,行宽必然变化,应重新分页
2. `webInteractive` 可重排正文在横竖屏切换后,列分页宽度变化,应重新分页
3. fixed layout 在 spread 模式下也可能因视口变化而需要重新生成页面模型
#### 问题 2:重新分页触发时机偏早
当前 `readerViewOrientationWillChange` 是从 `RDReaderView.layoutSubviews()` 中异步抛出。
这有几个风险:
1. 此时上层 VC 的 `view.bounds`、safe area、sheet 布局动画可能还在变化中
2. `paginatePublication()` 如果过早读取 `currentLayoutContext().viewportSize`,可能拿到中间态尺寸
3. `RDReaderView` 自己已经在同一轮变化里做了 `reloadData` / `transitionToPage`,而 `RDEPUBReaderController` 又会发起新一轮分页,容易出现双重刷新
#### 问题 3:职责边界不够清晰
目前横竖屏变化同时由两层在做事:
- `RDReaderView`:处理双页布局、pageCurl 容器重建、滚动模式 offset 恢复
- `RDEPUBReaderController`:处理 EPUB 重新分页
但两层之间没有一个明确的“统一入口”来协调:
1. 是否真的需要重分页
2. 什么时候用最终尺寸重分页
3. 何时忽略重复触发
4. 何时只刷新布局,不重做完整分页
#### 问题 4:文档状态与代码状态不一致
文档把横竖屏切换标为“未实现”,但代码里已经有半套逻辑。
这说明当前更准确的描述应当是:
- “已有局部实现”
- “尚未形成稳定、完整、可验收的横竖屏支持闭环”
## 需求拆解
为了让“横竖屏切换支持”真正完成,建议把需求拆成四部分:
### 1. 稳定感知视口变化
不仅要感知“横屏/竖屏布尔值变化”,还要感知:
- 宽高尺寸变化
- safe area 变化
- iPad 分屏、多窗口、sheet 尺寸变化
因此,真正要监听的不是“orientation”,而是“阅读视口发生了足以影响分页的变化”。
### 2. 统一触发重新分页
所有需要重排正文的场景都应走统一入口,例如:
```swift
handleViewportChange(reason: .orientationTransition)
```
由它统一负责:
1. 读取当前位置
2. 比较旧 viewport 与新 viewport
3. 防抖与去重
4. 发起分页
5. 恢复定位
### 3. 区分“容器布局刷新”和“正文重分页”
不是所有变化都必须触发完整分页,但横竖屏切换通常都需要:
- `textReflowable`:重建 `RDEPUBTextBook`
- `webInteractive`:重跑 `RDEPUBPaginator`
- `webFixedLayout`:重建 fixed spread snapshot
`RDReaderView` 自己的 pageCurl 双页容器重建仍可保留,但不应和正文分页时机互相打架。
### 4. 建立回归验收矩阵
横竖屏切换是高联动场景,至少要验证:
1. 无选区、无工具栏时切换
2. 有目录面板、设置面板、标注列表时切换
3. 搜索命中页、标注页、末页时切换
4. `landscapeDualPageEnabled = true / false`
5. pageCurl / scroll 系列模式
## 技术路线对比
### 路线 A:继续依赖 `RDReaderView.readerViewOrientationWillChange`
思路:
- 保留现有 `layoutSubviews() -> orientationChanged -> delegate`
-`RDEPUBReaderController` 内增强去重、防抖和最终尺寸判断
优点:
1. 改动范围小
2. 能延续现有 `RDReaderView` 双页逻辑
风险:
1. 触发时机仍然偏依赖 `layoutSubviews()`
2. 仍容易与 VC 生命周期中的尺寸变化打架
3. 继续把“正文分页”绑定到“容器方向回调”,抽象层次不够稳定
### 路线 B:由 `RDEPUBReaderController.viewWillTransition(to:with:)` 统一接管
思路:
1.`RDEPUBReaderController` 实现 `viewWillTransition(to:with:)`
2. 利用 `transitionCoordinator` 等待旋转动画接近完成
3. 在 completion 中读取最终 `viewportSize`
4. 统一调用 `handleViewportChange(reason:)`
5. `RDReaderView.readerViewOrientationWillChange` 只保留容器级双页重建职责,不再直接触发正文分页
优点:
1. 分页时机更接近最终尺寸
2. 更符合 UIKit 对旋转和尺寸变化的生命周期
3. 更容易做重复触发抑制
4. 更适合以后扩展到 iPad 分屏、多窗口
风险:
1. 需要重新梳理 `RDReaderView``RDEPUBReaderController` 的职责分界
2. 需要验证 pageCurl 双页重建与正文分页之间的顺序
### 推荐结论
建议采用:
**主路线:路线 B,由 `RDEPUBReaderController.viewWillTransition(to:with:)` 统一接管正文重分页**
**保留 `RDReaderView` 现有方向变化逻辑,但将其职责收敛为容器布局刷新和双页模式内部处理**
原因:
1. 需求本质是“视口变化后的正文重排”,最合理的宿主是控制器,而不是容器内部的 `layoutSubviews()`
2. 现有 `RDReaderView` 回调机制已经能服务内部双页布局,但不适合作为 EPUB 正文分页的唯一入口
3. `viewWillTransition` + `transitionCoordinator` 更容易拿到稳定尺寸,并减少重复分页
## 推荐方案
### 方案总览
引入一条统一的横竖屏处理主链路:
```swift
viewWillTransition(to:with:)
-> scheduleViewportTransition(reason: .orientation)
-> handleViewportChangeIfNeeded(finalViewportSize)
-> repaginatePreservingCurrentLocation()
-> finishPagination(restoreLocation:)
```
同时把 `RDReaderView` 内部职责收敛为:
1. 更新 `layout.isLandscapeDualPage`
2. pageCurl 模式下重建 `UIPageViewController`
3. 滚动模式刷新 collectionView 布局
不再让 EPUBUI 直接在 `readerViewOrientationWillChange` 中立即重新分页。
### 关键改动建议
#### Task O1:增加控制器级旋转入口
文件:
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
改动:
- 实现 `viewWillTransition(to:with:)`
-`transitionCoordinator` completion 中比较新旧 viewport
- 统一调 `handleViewportChange(reason:)`
完成定义:
- 旋转后分页使用最终尺寸,而不是中间态尺寸
#### Task O2:引入 viewport 去重与防抖
文件:
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
改动:
- 保存最近一次已应用的 `viewportSignature`
- 仅当宽高变化超过阈值时才触发重新分页
- 防止 `viewDidLayoutSubviews``orientation callback``viewWillTransition` 连续多次重复触发
推荐结构:
```swift
private struct RDEPUBViewportSignature: Equatable {
let width: CGFloat
let height: CGFloat
let safeTop: CGFloat
let safeBottom: CGFloat
}
```
完成定义:
- 一次横竖屏切换只触发一次有效正文重分页
#### Task O3:调整 `readerViewOrientationWillChange` 职责
文件:
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
- 如有必要 `Sources/RDReaderView/RDReaderView.swift`
改动:
- `RDEPUBReaderController.readerViewOrientationWillChange` 不再直接 `repaginatePreservingCurrentLocation()`
- 保留它作为兼容钩子,或仅做轻量状态标记
完成定义:
- 容器内部布局刷新与正文分页不再相互打架
#### Task O4:放宽 `RDReaderView` 的方向检测前置条件
文件:
- `Sources/RDReaderView/RDReaderView.swift`
改动:
- 去掉 `layoutSubviews()` 中对 `landscapeDualPageEnabled` 的硬性 guard
- 方向变化检测应独立于“是否启用横屏双页”
完成定义:
- 即使 `landscapeDualPageEnabled == false`,视口变化仍可被上层感知并触发重分页
注意事项:
- 双页布局本身仍然只在 `landscapeDualPageEnabled == true` 时开启
- 但“是否需要感知尺寸变化”不能绑定到这个开关
#### Task O5:验证三条正文路径的恢复策略
文件:
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
- `Sources/RDReaderView/EPUBCore/RDEPUBReadingSession.swift`
- `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextBookBuilder.swift`
改动:
- 验证 `restoreReadingLocation()` 在以下路径都稳定:
- `textReflowable`
- `webInteractive`
- `webFixedLayout`
完成定义:
- 横竖屏切换后,恢复位置不明显跳页、跳章
#### Task O6:补文档与验收矩阵
文件:
- `Doc/EPUB_MAINTENANCE.md`
- `Doc/ARCHITECTURE.md`
- 如有必要 `Doc/EPUBUI_功能实现逻辑.md`
改动:
- 将“横竖屏切换未做”更新为更准确的实现状态
- 补充最终采用的触发时机与回归清单
完成定义:
- 文档与代码状态一致
## 风险点
1. **pageCurl 双页重建顺序**
`RDReaderView` 自己会重建 `UIPageViewController`,如果 `RDEPUBReaderController` 同时触发分页,需要验证两者先后顺序
2. **重复分页**
旋转时 UIKit 常伴随多次 layout / safe area 变化,如果没有 `viewportSignature`,很容易多次重排
3. **iPad 场景复杂度**
分屏、多窗口、Stage Manager 下不一定发生“方向变化”,但一定发生“视口变化”,所以方案必须以 viewport 为核心
4. **fixed layout spread 恢复**
横屏双页和竖屏单页之间切换时,恢复到哪一页的左页 / 右页,需要以 `RDEPUBLocation` 为准而不是旧页号
## 推荐实施顺序
1. 先做 `O1 + O2`,把控制器级 viewport 变化入口和防抖建起来
2. 再做 `O3 + O4`,把旧的 delegate 触发职责收窄
3. 然后验证 `O5`,重点跑三条正文路径和四种展示模式
4. 最后做 `O6`,同步文档与验收矩阵
## 验收标准
1. 横竖屏切换后,`textReflowable` 路径会按新尺寸重新分页,并恢复到接近原阅读位置
2. 横竖屏切换后,`webInteractive` 路径会重新分页,并恢复到接近原阅读位置
3. 横竖屏切换后,`webFixedLayout` 路径会按新 spread 规则重建页面模型,并恢复到正确资源
4. `landscapeDualPageEnabled == false` 时,横竖屏切换仍然会触发必要的重排
5. 一次旋转只触发一次有效正文重分页,不出现连续多次闪烁刷新
6. pageCurl / horizontalScroll / verticalScroll / horizontalCoverScroll 四种模式下都不出现明显错页、白页或工具栏错位
## 结论
横竖屏切换支持不是“从零开始新增一套能力”,而是要把当前分散在 `RDReaderView``RDEPUBReaderController` 里的半套逻辑收敛成一个稳定闭环。
最推荐的方向是:
- `RDEPUBReaderController``viewWillTransition(to:with:)` 统一接管正文级重分页
- `RDReaderView` 保留容器级双页布局和 pageCurl 重建职责
-`viewport` 变化而不是 `isLandscape` 布尔值作为真正的分页触发依据
这样既能复用现有代码,又能把“文档标注为未完成”的横竖屏需求收敛成可实现、可回归、可交付的一阶段方案。
@@ -1,418 +0,0 @@
# 自动化测试方案讨论
## 需求背景
`Doc/EPUB_MAINTENANCE.md` 已将“自动化测试”列为 EPUB 阅读器下一阶段的高优先级能力之一。
当前阅读器已经具备较完整的主链路能力,包括:
1. EPUB 解析与打开
2. WebView / DTCoreText / fixed-layout 三条正文渲染路径
3. 目录跳转、阅读位置恢复、主题与字号调整
4. 高亮、划线、批注、搜索等阅读交互能力
但这些能力目前主要依赖手工验证,缺少自动化回归保护。随着横竖屏切换、书签、更多搜索体验和阅读增强能力继续叠加,单靠人工回归会越来越难以稳定覆盖高联动场景。
因此,“自动化测试”这项需求的目标不是单纯增加几条测试用例,而是为 EPUB 阅读器建立一套可持续演进的自动化回归体系,优先保护以下核心链路:
1. 解析
2. 分页
3. 定位恢复
4. 目录跳转
5. 标注恢复
## 代码事实
### 已有基础
当前仓库并不是完全没有可测试的基础:
1. `Sources/RDReaderView/EPUBCore/` 已经把 parser、resourceResolver、location、readingSession、paginator 等能力拆成了相对独立的模块
2. `Sources/RDReaderView/EPUBTextRendering/` 已经将 DTCoreText 渲染、fragment 提取、富文本分页等逻辑收敛到明确的工具类型中
3. `RDEPUBLocation``RDEPUBHighlight`、TOC / spine / manifest 等模型本身适合做 deterministic 的断言
4. `ReadViewSDKDemo` 已经提供真实运行容器,后续可以承载 UI 自动化测试
结论:
1. 核心逻辑并非不可测
2. 更缺的是测试 target、测试夹具、断言策略和回归矩阵
### 当前实现存在的问题
#### 问题 1:工程里还没有测试承载层
`ReadViewSDKDemo.xcodeproj` 当前 target 配置来看,只有 `ReadViewSDKDemo` app target,没有独立的 `Tests``UITests` target。
这意味着当前并没有:
1. 用于跑 XCTest 的基础 target
2. 用于跑 XCUITest 的 UI 测试 target
3. 稳定的测试资源加载方式
#### 问题 2:关键链路缺少自动回归保护
当前高风险能力包括:
1. `RDEPUBParser` 解析 EPUB ZIP / OPF / spine / TOC
2. `RDEPUBResourceResolver.normalizedHref(_:)` 的路径标准化
3. `RDEPUBPaginator``RDEPUBTextPaginationSupport` 的分页结果
4. `RDEPUBReaderController.restoreLocation(_:)` 的定位恢复
5. 高亮 / 批注 persistence 的恢复链路
这些地方一旦回归,通常会表现为:
1. 打不开书
2. 目录跳错
3. 字号变化后位置飘移
4. 标注丢失或恢复失败
而这些问题都很适合被自动化测试尽早捕获。
#### 问题 3:如果直接从 UI 自动化起步,成本和脆弱性都偏高
EPUB 阅读器的很多问题发生在 UI 之下,例如:
1. TOC href 标准化错误
2. `fragment -> offset` 映射偏移
3. 分页结果为空或页数异常
4. `href + progression` 回落恢复逻辑不稳定
如果这些问题都等到 `XCUITest` 才暴露:
1. 定位根因会很慢
2. 测试执行时间会更长
3. 异步加载、动画、WebView 时序会让用例更脆
#### 问题 4:当前还没有固定的样书基线与测试夹具约定
要让自动化测试长期稳定,必须明确:
1. 哪几本样书是测试夹具
2. 每本样书用于验证哪种能力
3. 哪些断言可以依赖“页号”,哪些只能依赖 `href / fragment / progression`
如果没有这层约束,测试很容易随着样书变化或样式调整而频繁漂移。
## 需求拆解
建议把“自动化测试”需求拆成四部分,而不是把所有验证都堆到同一层。
### 1. 建立测试基础设施
先补齐可执行自动化测试的工程结构,包括:
1. `ReadViewSDKDemoTests`
2. `ReadViewSDKDemoUITests`
3. 样书 fixtures 目录
4. 统一的测试资源读取辅助工具
这是后续一切测试工作的前提。
### 2. 建立逻辑层 XCTest
这部分优先覆盖稳定、纯逻辑、可快速执行的能力:
1. parser 解析
2. href 标准化
3. TOC 路径映射
4. location / progression / fragment 相关转换
5. persistence 编解码
目标是让每次改动 parser、模型、定位和持久化逻辑时,都有快速反馈。
### 3. 建立集成层 XCTest
这部分不直接走 UI 自动化,但会驱动真实样书、真实分页和真实恢复链路,重点覆盖:
1. reflowable 样书分页
2. fixed-layout 样书页面模型生成
3. 字号变化后位置恢复
4. 目录跳转命中
5. 标注恢复
目标是把“阅读核心链路”在逻辑层和 UI 层之间补上一层更贴近真实运行的保护网。
### 4. 建立少量高价值 XCUITest
UI 自动化不追求全覆盖,而是做真实用户闭环的冒烟回归,例如:
1. 打开样书进入阅读器
2. 目录跳转
3. 调整字号
4. 创建高亮并重新进入验证恢复
5. 横竖屏切换后继续阅读
目标是证明“用户真的能完成关键操作”,而不是把所有细节都放进 UI 测试里。
## 技术路线对比
### 路线 A:优先建设 XCUITest
思路:
1. 先搭 UI 自动化
2. 用点击、滑动、旋转和断言可见文本的方式覆盖主要能力
优点:
1. 结果直观,容易贴近真实用户行为
2. 可以较快形成“端到端可用”的感知
风险:
1. 调试成本高
2. 受动画、异步、WebView 时序影响大
3. 很难快速判断问题出在 parser、分页还是 UI
4. 执行时间更长,不适合作为最早期主回归层
### 路线 B:优先建设 XCTest
思路:
1. 先为逻辑与集成层补单元测试
2. 把解析、分页、定位恢复等能力尽量在非 UI 层验证
优点:
1. 稳定性更高
2. 运行更快
3. 更利于问题定位
4. 更适合作为日常改动的主回归层
风险:
1. 不能完全证明 UI 交互链路没问题
2. 覆盖不到真实点击、WebView 手势和界面状态同步问题
### 路线 C:分层推进
思路:
1. 先建立测试基础设施
2. 以 XCTest 为主搭建核心保护网
3. 再补少量 XCUITest 做端到端闭环
优点:
1. 投入与收益平衡更好
2. 能优先保护最容易回归的核心能力
3. 既兼顾稳定性,也兼顾真实用户路径
风险:
1. 初期需要先设计测试层次和夹具约定
2. 需要控制 UI 测试范围,避免后续无节制膨胀
### 推荐结论
建议采用:
**主路线:路线 C,分层推进,XCTest 为主,XCUITest 为辅**
原因:
1. 当前最需要保护的是解析、分页、定位恢复这些高风险核心链路
2. 仓库当前还没有测试 target,先从更稳定的 XCTest 起步更合适
3. 少量 XCUITest 足以证明关键阅读流程可用,不必一开始追求全面 UI 覆盖
## 推荐方案
### 方案总览
自动化测试第一阶段建议形成如下结构:
```text
ReadViewSDKDemoTests
- ParserTests
- ResourceResolverTests
- LocationRestoreTests
- TextPaginationTests
- HighlightPersistenceTests
ReadViewSDKDemoUITests
- ReaderSmokeTests
- ReaderNavigationTests
- ReaderAnnotationTests
- ReaderRotationTests
```
同时建立一套最小样书夹具集:
1. `reflowable-basic.epub`
2. `fixed-layout-basic.epub`
3. `image-heavy.epub`
4. `toc-fragment.epub`
每本样书都应明确“它用来验证什么”,而不是仅作为示例文件存在。
### 关键改动建议
#### Task T1:建立测试 target 与基础资源加载能力
文件范围:
1. `ReadViewSDKDemo/ReadViewSDKDemo.xcodeproj`
2. 新增 `ReadViewSDKDemoTests/`
3. 新增 `ReadViewSDKDemoUITests/`
4. 新增测试夹具目录
改动:
1. 增加 unit test target
2. 增加 UI test target
3. 为测试 target 挂载样书与辅助资源
4. 提供统一 `TestBookLoader` / `FixtureLocator` 之类的测试辅助工具
完成定义:
1. 工程可以直接运行测试
2. 测试代码可以稳定读取样书资源
#### Task T2:补第一批逻辑层 XCTest
建议首批覆盖:
1. `RDEPUBParser` 能解析有效样书并产出非空 spine
2. `RDEPUBResourceResolver.normalizedHref(_:)` 能正确处理相对路径、父级路径和 fragment
3. TOC href 能统一映射到与 spine 一致的标准化路径
4. `RDEPUBHighlight` persistence 编解码结果一致
5. 定位模型在常见输入下不会丢 fragment 或 progression
完成定义:
1. parser / resolver / persistence / location 改动有自动回归保护
#### Task T3:补第一批集成层 XCTest
建议首批覆盖:
1. reflowable 样书分页结果非空
2. fixed-layout 样书能建立页模型
3. 字号变化后仍能按 `href + progression` 恢复到原章节附近
4. 目录跳转可命中预期 spine 资源
5. 高亮恢复后仍能定位到对应资源
完成定义:
1. 核心阅读链路具备非 UI 层的真实运行回归能力
#### Task T4:补第一批冒烟型 XCUITest
建议首批覆盖:
1. 打开样书并进入阅读器
2. 打开目录并跳转章节
3. 调整字号后继续阅读
4. 创建一条高亮并重进验证恢复
5. 横竖屏切换后继续阅读
完成定义:
1. 至少有一条真实用户闭环可以证明主功能未损坏
## 断言策略建议
为了降低测试漂移,建议统一以下断言原则:
### 1. 少依赖固定页号
页号非常容易受以下因素影响:
1. 字号
2. 行距
3. 视口尺寸
4. 图片加载时序
因此,自动化测试不应过度依赖“必须是第 N 页”这种断言。
### 2. 优先依赖稳定语义锚点
更推荐的断言对象包括:
1. `href`
2. `fragment`
3. `progression` 所在区间
4. spine index
5. 高亮所属资源
### 3. UI 测试只验证关键闭环
XCUITest 中更适合验证:
1. 页面是否成功进入
2. 关键按钮是否可操作
3. 操作后阅读器状态是否变化
4. 重进后状态是否可恢复
而不适合在 UI 测试中承载大量底层分页细节断言。
## 验收矩阵建议
自动化测试第一阶段至少要覆盖以下维度:
1. `webInteractive`
2. `webFixedLayout`
3. `textReflowable`
4. TOC 跳转
5. 字号变化后恢复
6. 高亮恢复
7. 横竖屏切换
8. 一到两本问题样书回归
如果资源有限,建议先保证“三条渲染路径 + 两条恢复链路”:
1. 三条渲染路径:`webInteractive` / `webFixedLayout` / `textReflowable`
2. 两条恢复链路:阅读位置恢复 / 标注恢复
## 风险与注意点
### 风险 1:测试一开始就绑定大量脆弱 UI 细节
如果测试过度依赖:
1. 动画时长
2. 可见文案位置
3. 某个具体页号
4. WebView 内即时渲染完成时机
后续维护成本会非常高。
### 风险 2:测试夹具过多但目标不清晰
样书数量不是越多越好。更重要的是:
1. 每本样书验证哪类能力
2. 哪些样书是主回归集
3. 哪些样书只在专项验证时使用
### 风险 3:横竖屏与字号变化断言过于绝对
这两类场景更适合验证:
1. 是否仍在同一章节或邻近语义位置
2. 是否仍保留原始 `href / fragment`
3. progression 是否在合理偏差范围内
而不适合验证“必须恢复到完全相同页号”。
## 推荐实施顺序
如果目标是“最小投入换最大稳定性提升”,建议按以下顺序推进:
1. 建立 `Tests` / `UITests` target
2. 补 parser / resolver / location / persistence 的 XCTest
3. 补分页、目录跳转、定位恢复的集成测试
4. 补 3 到 5 条高价值 XCUITest
5. 将横竖屏、问题样书和新增功能逐步并入回归矩阵
## 推荐结论
“自动化测试”这项需求,建议最终收敛为下面这句话:
**为 EPUB 阅读器建立一套分层自动化测试体系,以 XCTest 保护解析、分页、定位恢复、目录跳转和标注恢复等核心链路,再以少量 XCUITest 验证真实阅读闭环。**
这样做的好处是:
1. 能尽快为最容易回归的核心能力建立保护网
2. 不会过早陷入脆弱的全量 UI 自动化
3. 能为横竖屏、书签、搜索和后续阅读增强功能提供稳定回归基础