feat: complete large-book pagination runtime and UI coverage

This commit is contained in:
shenlei
2026-06-03 17:47:16 +08:00
parent 584976ac5f
commit feb05eaf87
53 changed files with 6307 additions and 5126 deletions
@@ -0,0 +1,71 @@
import XCTest
struct DemoReaderState {
let rawValue: String
let fields: [String: String]
init(rawValue: String) {
self.rawValue = rawValue
var parsed: [String: String] = [:]
let separators = CharacterSet.whitespacesAndNewlines.union(CharacterSet(charactersIn: ";"))
for token in rawValue.components(separatedBy: separators) where !token.isEmpty {
guard let delimiterIndex = token.firstIndex(of: "=") else { continue }
let key = String(token[..<delimiterIndex])
let value = String(token[token.index(after: delimiterIndex)...])
parsed[key] = value
}
fields = parsed
}
subscript(key: String) -> String? {
fields[key]
}
var isOpened: Bool { fields["reader"] == "opened" }
var page: Int? { fields["page"].flatMap(Int.init) }
var display: String? { fields["display"] }
var toolbar: String? { fields["toolbar"] }
var highlights: Int? { fields["highlights"].flatMap(Int.init) }
var selection: Int? { fields["selection"].flatMap(Int.init) }
var href: String? { fields["href"] }
var progression: Double? { fields["progression"].flatMap(Double.init) }
var mode: String? { fields["mode"] }
var pagination: String? { fields["pagination"] }
var knownPages: Int? { fields["knownPages"].flatMap(Int.init) }
var knownChapters: Int? { fields["knownChapters"].flatMap(Int.init) }
var buildableChapters: Int? { fields["buildableChapters"].flatMap(Int.init) }
var avoidWidows: Int? { fields["avoidWidows"].flatMap(Int.init) }
var avoidOrphans: Int? { fields["avoidOrphans"].flatMap(Int.init) }
}
extension XCUIApplication {
func currentDemoReaderState() -> DemoReaderState? {
let state = staticTexts[IDs.demoReaderState]
guard state.exists else { return nil }
return DemoReaderState(rawValue: state.label)
}
@discardableResult
func waitForDemoReaderState(
timeout: TimeInterval = 8,
description: String,
where predicate: (DemoReaderState) -> Bool
) -> DemoReaderState {
let stateElement = staticTexts[IDs.demoReaderState]
let deadline = Date().addingTimeInterval(timeout)
var lastState = DemoReaderState(rawValue: "<missing>")
while Date() < deadline {
if stateElement.exists {
lastState = DemoReaderState(rawValue: stateElement.label)
if predicate(lastState) {
return lastState
}
}
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
}
XCTFail("阅读器状态未满足条件: \(description),当前:\(lastState.rawValue)")
return lastState
}
}
@@ -25,12 +25,15 @@ extension XCUIApplication {
func waitForReader(timeout: TimeInterval = 12) -> XCUIElement {
let state = staticTexts[IDs.demoReaderState]
XCTAssertTrue(state.waitForExistence(timeout: timeout), "阅读器状态标签未出现")
XCTAssertTrue(state.label.contains("reader=opened"), "阅读器未进入 opened 状态:\(state.label)")
let parsed = DemoReaderState(rawValue: state.label)
XCTAssertTrue(parsed.isOpened, "阅读器未进入 opened 状态:\(state.label)")
return state
}
func waitForReaderPage(_ pageNumber: Int, timeout: TimeInterval = 12) {
waitForReaderState(containing: "page=\(pageNumber)", timeout: timeout)
_ = waitForDemoReaderState(timeout: timeout, description: "page=\(pageNumber)") { state in
state.page == pageNumber
}
}
func showReaderChromeIfNeeded() {