Files
ReadViewSDK/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/SelectionAnnotateTests.swift
T
shenandshen 6f75b083f7 feat: EPUB 阅读器搜索、选中注释、书签 chrome 状态及大量重构优化
- 新增 RDEPUBReaderSearchCoordinator 与 RDEPUBSelectionState 管理搜索和选中状态
- 新增 BookmarkChromeStateTests、NavigationBackwardTests、SelectionAnnotateTests 等 UI 测试
- 新增多个边界测试 epub 样本(损坏结构、空归档、缺失文件、流式外链验证)
- 重构阅读器 chrome 状态管理,统一 tool bar 与 search bar 交互
- 优化大书分页缓存策略(RDEPUBChapterSummaryDiskCache、RDEPUBPageCountCache)
- 移除废弃的 RDEPUBLocationConverter 和 RDEPUBPageBreakPolicy
- 更新 epub-bridge.js 与 JS bridge 通信协议
- 全面更新现有 UI 测试以适配新的 helper 和状态管理
2026-06-13 22:48:56 +08:00

163 lines
6.7 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()
let fontIncrease = app.buttons[IDs.settingsFontIncrease]
XCTAssertTrue(fontIncrease.waitForExistence(timeout: 3), "字号增大按钮应存在")
fontIncrease.tap()
fontIncrease.tap()
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)")
}
}
}