ReadViewSDK/ReadViewDemo/ReadViewDemoUITests/Helpers/DemoReaderState.swift
shenlei 83dfa40299 完善搜索定位与内存监测回归验证
本次提交围绕搜索链路的稳定性、定位恢复体验以及长章节内存优化的验证能力进行了补强。

主要改动:

1. 调整阅读器搜索栏、搜索协调器与定位恢复逻辑,改善搜索结果跳转、状态同步与相关 UI 行为。

2. 补充内存探针接入点与上下文记录,便于跟踪阅读过程中的内存占用变化。

3. 更新 RDURLReaderController、ReaderContext 与工具视图相关实现,使调试与观测链路更完整。

4. 新增 MemoryFootprintTests,并同步更新 DemoReaderState 与 SearchTests,用 UI 测试覆盖搜索与内存相关回归场景。
2026-07-09 18:46:04 +09:00

95 lines
4.1 KiB
Swift

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]
}
private func decodedField(_ key: String) -> String? {
guard let rawValue = fields[key], rawValue != "nil" else { return nil }
return rawValue.removingPercentEncoding ?? rawValue
}
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 bookmarks: Int? { fields["bookmarks"].flatMap(Int.init) }
var selection: Int? { fields["selection"].flatMap(Int.init) }
var href: String? { decodedField("href") }
var progression: Double? { fields["progression"].flatMap(Double.init) }
var cfi: String? { decodedField("cfi") }
var lastCFI: String? { decodedField("lastCFI") }
var rangeCFI: String? { decodedField("rangeCFI") }
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) }
var windowSize: Int? { fields["windowSize"].flatMap(Int.init) }
var parseMs: Int? { fields["parseMs"].flatMap(Int.init) }
var parseConcurrency: Int? { fields["parseConcurrency"].flatMap(Int.init) }
var inspectable: Int? { fields["inspectable"].flatMap(Int.init) }
var streamedResources: Int? { fields["streamedResources"].flatMap(Int.init) }
var inMemoryResources: Int? { fields["inMemoryResources"].flatMap(Int.init) }
var resourceFailures: Int? { fields["resourceFailures"].flatMap(Int.init) }
var cacheFiles: Int? { fields["cacheFiles"].flatMap(Int.init) }
var cacheBytes: Int? { fields["cacheBytes"].flatMap(Int.init) }
var externalLinks: Int? { fields["externalLinks"].flatMap(Int.init) }
var lastExternalURL: String? { decodedField("lastExternalURL") }
var lastError: String? { decodedField("lastError") }
var searchMatchText: String? { decodedField("searchMatchText") }
var footprintMB: Double? { fields["footprintMB"].flatMap(Double.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
}
}