SDK 化收尾: - 新增 RDPDFReaderView 本地 pod(阅读容器、成品控制器、划线/注释、画笔、 目录/设置面板、主题与持久化契约),Demo 精简为 PDFKit 页面提供者 + 持久化宿主 - Podfile/工程接入 RDPDFReaderView 与 SnapKit,示例书改用 PDF 阅读器示例文件 手机横屏单页竖滑修复: - cell 高度改为宽度适配后的纸张高度(sizeForItemAt + 宿主按页提供纵横比), 纵向超出一屏的内容由外层 collectionView 连续滚动承接,不再被裁掉 - 页面内部不再开启同向嵌套的兜底竖向拖动,修复外层滚动被吞、无法滚到下一页 - 修复 applyContentSize 先评估拖动开关后写 contentSize 的顺序缺陷 - 页码按视口中心命中 cell 计算,补 didEndDragging 兜底;旋转重锚点与 transitionToPage 改用真实布局位置 - 横竖屏同为竖滑时也重建 cell 并刷新全部可见页的适配配置 - 横屏单页点击不再跳页,只显隐工具栏;翻页交给连续竖滑 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
173 lines
5.8 KiB
Swift
173 lines
5.8 KiB
Swift
import XCTest
|
|
|
|
extension XCUIApplication {
|
|
func launchAndOpenSamplePDF(
|
|
bookTitleQuery: String = "PDF阅读器示例",
|
|
resetsReaderState: Bool = true,
|
|
pdfTextSource: String? = nil
|
|
) {
|
|
launchAndOpenSampleBook(
|
|
bookTitleQuery: bookTitleQuery,
|
|
displayType: "pagecurl",
|
|
resetsReaderState: resetsReaderState,
|
|
pdfTextSource: pdfTextSource
|
|
)
|
|
}
|
|
|
|
func launchAndOpenSampleBook(
|
|
bookTitleQuery: String = "回归验证样本",
|
|
displayType: String? = nil,
|
|
pageNumber: Int? = nil,
|
|
resetsReaderState: Bool = true,
|
|
windowSize: Int? = nil,
|
|
concurrency: Int? = nil,
|
|
clearsCache: Bool = false,
|
|
corruptsCache: Bool = false,
|
|
allowsInspectableWebViews: Bool = false,
|
|
enablesVerboseWebLogs: Bool = false,
|
|
searchKeyword: String? = nil,
|
|
pdfTextSource: String? = nil
|
|
) {
|
|
var args = ["--demo-book-title", bookTitleQuery]
|
|
if resetsReaderState {
|
|
args.append("--demo-reset-state")
|
|
}
|
|
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]
|
|
}
|
|
if let pageNumber {
|
|
args += ["--demo-page", "\(pageNumber)"]
|
|
}
|
|
if let windowSize {
|
|
args += ["--demo-window-size", "\(windowSize)"]
|
|
}
|
|
if let concurrency {
|
|
args += ["--demo-concurrency", "\(concurrency)"]
|
|
}
|
|
if let searchKeyword {
|
|
args += ["--demo-search-keyword", searchKeyword]
|
|
}
|
|
if let pdfTextSource {
|
|
args += ["--demo-pdf-text-source", pdfTextSource]
|
|
}
|
|
launchArguments = args
|
|
launch()
|
|
}
|
|
|
|
@discardableResult
|
|
func waitForReader(timeout: TimeInterval = 12) -> XCUIElement {
|
|
let state = staticTexts[IDs.demoReaderState]
|
|
XCTAssertTrue(state.waitForExistence(timeout: timeout), "阅读器状态标签未出现")
|
|
let parsed = DemoReaderState(rawValue: state.label)
|
|
XCTAssertTrue(parsed.isOpened, "阅读器未进入 opened 状态:\(state.label)")
|
|
return state
|
|
}
|
|
|
|
func waitForReaderPage(_ pageNumber: Int, timeout: TimeInterval = 12) {
|
|
_ = waitForDemoReaderState(timeout: timeout, description: "page=\(pageNumber)") { state in
|
|
state.page == pageNumber
|
|
}
|
|
}
|
|
|
|
func showReaderChromeIfNeeded(
|
|
requiredButtonIDs: [String] = [IDs.readerBack, IDs.readerSettings],
|
|
timeout: TimeInterval = 5
|
|
) {
|
|
if readerChromeIsVisible(requiredButtonIDs: requiredButtonIDs) {
|
|
return
|
|
}
|
|
|
|
let deadline = Date().addingTimeInterval(timeout)
|
|
while Date() < deadline {
|
|
let content = otherElements[IDs.readerContent].firstMatch
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
func hideReaderChromeIfNeeded() {
|
|
for _ in 0..<3 {
|
|
guard buttons[IDs.readerBack].exists else { return }
|
|
|
|
let content = otherElements[IDs.readerContent].firstMatch
|
|
if content.waitForExistence(timeout: 2) {
|
|
content.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.6)).tap()
|
|
}
|
|
RunLoop.current.run(until: Date().addingTimeInterval(0.5))
|
|
}
|
|
}
|
|
|
|
func waitForReaderState(containing expectedText: String, timeout: TimeInterval = 8) {
|
|
let state = staticTexts[IDs.demoReaderState]
|
|
let deadline = Date().addingTimeInterval(timeout)
|
|
var lastLabel = "<missing>"
|
|
|
|
while Date() < deadline {
|
|
if state.exists {
|
|
lastLabel = state.label
|
|
if lastLabel.contains(expectedText) {
|
|
return
|
|
}
|
|
}
|
|
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
|
|
}
|
|
|
|
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].firstMatch
|
|
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 }
|
|
}
|
|
}
|