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 和状态管理
This commit is contained in:
@@ -9,6 +9,9 @@ extension XCUIApplication {
|
||||
windowSize: Int? = nil,
|
||||
concurrency: Int? = nil,
|
||||
clearsCache: Bool = false,
|
||||
corruptsCache: Bool = false,
|
||||
allowsInspectableWebViews: Bool = false,
|
||||
enablesVerboseWebLogs: Bool = false,
|
||||
searchKeyword: String? = nil
|
||||
) {
|
||||
var args = ["--demo-book-title", bookTitleQuery]
|
||||
@@ -18,6 +21,15 @@ extension XCUIApplication {
|
||||
if clearsCache {
|
||||
args.append("--demo-clear-cache")
|
||||
}
|
||||
if corruptsCache {
|
||||
args.append("--demo-corrupt-cache")
|
||||
}
|
||||
if allowsInspectableWebViews {
|
||||
args.append("--demo-allow-inspectable-webviews")
|
||||
}
|
||||
if enablesVerboseWebLogs {
|
||||
args.append("--demo-enable-verbose-weblogs")
|
||||
}
|
||||
if let displayType {
|
||||
args += ["--demo-display-type", displayType]
|
||||
}
|
||||
@@ -52,16 +64,27 @@ extension XCUIApplication {
|
||||
}
|
||||
}
|
||||
|
||||
func showReaderChromeIfNeeded() {
|
||||
if buttons[IDs.readerBack].exists && buttons[IDs.readerSettings].exists {
|
||||
func showReaderChromeIfNeeded(
|
||||
requiredButtonIDs: [String] = [IDs.readerBack, IDs.readerSettings],
|
||||
timeout: TimeInterval = 5
|
||||
) {
|
||||
if readerChromeIsVisible(requiredButtonIDs: requiredButtonIDs) {
|
||||
return
|
||||
}
|
||||
|
||||
let content = otherElements[IDs.readerContent]
|
||||
if content.waitForExistence(timeout: 3) {
|
||||
content.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
||||
} else {
|
||||
windows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
||||
let deadline = Date().addingTimeInterval(timeout)
|
||||
while Date() < deadline {
|
||||
let content = otherElements[IDs.readerContent]
|
||||
if content.waitForExistence(timeout: 1) {
|
||||
content.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
||||
} else {
|
||||
windows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
||||
}
|
||||
|
||||
RunLoop.current.run(until: Date().addingTimeInterval(0.35))
|
||||
if readerChromeIsVisible(requiredButtonIDs: requiredButtonIDs) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +117,39 @@ extension XCUIApplication {
|
||||
|
||||
XCTFail("阅读器状态未包含 \(expectedText),当前:\(lastLabel)")
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func requireSelectionText(
|
||||
expectedPage: Int? = nil,
|
||||
timeout: TimeInterval = 12
|
||||
) throws -> XCUIElement {
|
||||
let selectionText = otherElements[IDs.readerSelectionText].firstMatch
|
||||
let deadline = Date().addingTimeInterval(timeout)
|
||||
|
||||
while Date() < deadline {
|
||||
if let expectedPage {
|
||||
let state = currentDemoReaderState()
|
||||
if state?.page != expectedPage {
|
||||
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if selectionText.exists {
|
||||
return selectionText
|
||||
}
|
||||
|
||||
let content = otherElements[IDs.readerContent]
|
||||
if content.exists {
|
||||
content.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
||||
}
|
||||
RunLoop.current.run(until: Date().addingTimeInterval(0.2))
|
||||
}
|
||||
|
||||
throw XCTSkip("文本选区元素未出现,当前渲染路径未暴露 \(IDs.readerSelectionText)")
|
||||
}
|
||||
|
||||
private func readerChromeIsVisible(requiredButtonIDs: [String]) -> Bool {
|
||||
requiredButtonIDs.allSatisfy { buttons[$0].exists }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user