Files
ReadViewSDK/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/SelectionAnnotateTests.swift
T
shenlei 3e60bf1869 修复阅读器资源、朗读与持久化稳定性
阅读器在多会话朗读、加密 EPUB 资源、并发打开同一本书和多 UserDefaults 容器等场景下,存在旧异步结果覆盖新状态、锁屏控制串会话、大型明文资源被误拒绝、解压半成品被复用及标注跨容器混用的风险;同时高亮、书签、搜索和设置面板的空状态与自动化可访问性入口不完整。\n\n本次为朗读会话引入代次和当前 utterance 校验,远程控制改为仅响应当前会话并按自身 token 清理;加密资源 provider 先判定是否实际返回解密数据,明文大资源继续流式读取;解压缓存串行化以避免并发复用未完成目录。书签与高亮迁移至受保护文件,按 UserDefaults 容器隔离命名空间,保留标准容器旧文件兼容并确保新副本成功落盘后才删除历史数据。\n\n同步调整缓存淘汰、FoundationModels 弱链接、搜索/标注/设置面板交互与 UI 测试,并更新 Pod 生成配置及 API、风险文档。已执行 git diff --check 和 ReadViewDemo Debug Simulator 构建,结果为 BUILD SUCCEEDED。
2026-07-30 20:52:21 +09:00

160 lines
6.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import XCTest
/// 选区批注动作和高亮持久化测试
/// 覆盖:批注菜单动作创建高亮、高亮跨重启存活、重排后高亮跳转页码正确
final class SelectionAnnotateTests: XCTestCase {
private let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
}
/// 点击批注菜单项应创建高亮
func testAnnotateMenuActionCreatesHighlight() throws {
app.launchAndOpenSampleBook()
app.waitForReader()
// 长按并拖拽选中文本
let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch
guard selectionText.waitForExistence(timeout: 5) else {
throw XCTSkip("文本选区元素不存在")
}
let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5))
let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5))
start.press(forDuration: 0.8, thenDragTo: end)
// 等待选区状态
Thread.sleep(forTimeInterval: 0.5)
// 点击批注菜单项
let annotateItem = app.menuItems["注释"]
guard annotateItem.waitForExistence(timeout: 3) else {
throw XCTSkip("批注菜单项不存在")
}
annotateItem.tap()
// 验证高亮数量变为 1
app.waitForDemoReaderState(timeout: 5, description: "highlights=1") { $0.highlights == 1 }
}
/// 高亮跨重启存活
func testHighlightSurvivesRestart() throws {
app.launchAndOpenSampleBook()
app.waitForReader()
// 创建高亮
let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch
guard selectionText.waitForExistence(timeout: 5) else {
throw XCTSkip("文本选区元素不存在")
}
let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5))
let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5))
start.press(forDuration: 0.8, thenDragTo: end)
Thread.sleep(forTimeInterval: 0.5)
let annotateItem = app.menuItems["注释"]
guard annotateItem.waitForExistence(timeout: 3) else {
throw XCTSkip("批注菜单项不存在")
}
annotateItem.tap()
// 等待高亮创建完成
app.waitForDemoReaderState(timeout: 5, description: "highlights=1") { $0.highlights == 1 }
// 关闭阅读器
app.buttons[IDs.readerBack].tap()
// 重新打开(不重置状态)
app.launchAndOpenSampleBook(resetsReaderState: false)
app.waitForReader()
// 验证高亮仍然存在
app.waitForDemoReaderState(timeout: 8, description: "highlights=1 after restart") { $0.highlights == 1 }
}
/// 重排后高亮跳转页码应正确(多页覆盖)
/// 回归:chapterOffset(for:) 曾优先使用 row/column 查表而非存储的 chapterOffset
/// 导致重新排版后跳转到错误的页面
func testHighlightNavigationAfterRepagination() throws {
// 测试多页:在不同页面创建高亮,改变字体后跳转,验证页码正确
let testPages = [2, 3, 5]
for page in testPages {
// 每轮重新启动以确保干净状态
app.launchAndOpenSampleBook(pageNumber: page)
app.waitForReader()
let stateBefore = app.waitForDemoReaderState(timeout: 5, description: "at page \(page)") { $0.page == page }
XCTAssertEqual(stateBefore.page, page, "应成功打开第 \(page) 页")
// 创建高亮
let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch
guard selectionText.waitForExistence(timeout: 5) else {
throw XCTSkip("文本选区元素不存在")
}
let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.2, dy: 0.5))
let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
start.press(forDuration: 0.8, thenDragTo: end)
Thread.sleep(forTimeInterval: 0.5)
let highlightItem = app.menuItems["划线"]
guard highlightItem.waitForExistence(timeout: 3) else {
throw XCTSkip("高亮菜单项不存在")
}
highlightItem.tap()
app.waitForDemoReaderState(timeout: 5, description: "highlights=1 on page \(page)") { $0.highlights == 1 }
// 改变字体大小触发重新排版
app.showReaderChromeIfNeeded()
let settingsButton = app.buttons[IDs.readerSettings]
XCTAssertTrue(settingsButton.waitForExistence(timeout: 3), "设置按钮应存在")
settingsButton.tap()
XCTAssertTrue(app.adjustSettingsFontSize(toNormalizedPosition: 0.9), "字号滑块应存在")
Thread.sleep(forTimeInterval: 0.5)
let doneButton = app.buttons[IDs.settingsDone]
if doneButton.waitForExistence(timeout: 3) {
doneButton.tap()
}
Thread.sleep(forTimeInterval: 1)
// 翻到其他页
let content = app.otherElements[IDs.readerContent].firstMatch
if content.waitForExistence(timeout: 3) {
for _ in 0..<3 {
content.swipeLeft()
Thread.sleep(forTimeInterval: 0.3)
}
Thread.sleep(forTimeInterval: 0.5)
}
// 通过高亮面板跳转回高亮位置
app.showReaderChromeIfNeeded()
let highlightsButton = app.buttons[IDs.readerHighlights]
XCTAssertTrue(highlightsButton.waitForExistence(timeout: 3), "高亮列表按钮应存在")
highlightsButton.tap()
let highlightsTable = app.tables[IDs.readerHighlightsTable]
XCTAssertTrue(highlightsTable.waitForExistence(timeout: 5), "高亮列表应出现")
let firstCell = highlightsTable.cells.firstMatch
XCTAssertTrue(firstCell.waitForExistence(timeout: 3), "高亮列表应有至少一个条目")
firstCell.tap()
// 等待跳转完成
Thread.sleep(forTimeInterval: 1)
// 验证跳转后的页码应为创建高亮时的页码
let stateAfter = app.waitForDemoReaderState(timeout: 5, description: "back at page \(page) after repagination") {
$0.page == page
}
XCTAssertEqual(stateAfter.page, page, "重排后高亮跳转应回到第 \(page) 页,实际:\(stateAfter.page ?? -1)")
}
}
}