ReadViewSDK/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/ReaderToolbarTests.swift

243 lines
11 KiB
Swift

import XCTest
final class ReaderToolbarTests: XCTestCase {
private let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
}
func testTapCenterShowsToolbars() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
XCTAssertTrue(app.otherElements[IDs.readerTopToolbar].waitForExistence(timeout: 5))
XCTAssertTrue(app.otherElements[IDs.readerBottomToolbar].waitForExistence(timeout: 5))
XCTAssertTrue(app.buttons[IDs.readerBack].exists)
XCTAssertTrue(app.buttons[IDs.readerSettings].exists)
}
func testPDFTapCenterShowsAndHidesToolbars() {
app.launchAndOpenSamplePDF()
app.waitForReader()
app.waitForDemoReaderState(timeout: 5, description: "mode=pdf") { $0.mode == "pdf" }
app.showReaderChromeIfNeeded()
XCTAssertTrue(app.otherElements[IDs.readerTopToolbar].waitForExistence(timeout: 3))
XCTAssertTrue(app.otherElements[IDs.readerBottomToolbar].waitForExistence(timeout: 3))
app.hideReaderChromeIfNeeded()
XCTAssertFalse(app.buttons[IDs.readerBack].waitForExistence(timeout: 2), "PDF 点击内容区后工具栏应隐藏")
}
func testPDFTocButtonOpensNavigationSheet() {
app.launchAndOpenSamplePDF()
app.waitForReader()
app.waitForDemoReaderState(timeout: 5, description: "mode=pdf") { $0.mode == "pdf" }
app.showReaderChromeIfNeeded(requiredButtonIDs: [IDs.readerToc])
let tocButton = app.buttons[IDs.readerToc]
XCTAssertTrue(tocButton.waitForExistence(timeout: 5))
tocButton.tap()
XCTAssertTrue(app.tables[IDs.readerTocTable].waitForExistence(timeout: 5), "PDF 目录面板未出现")
}
func testPDFSettingsPanelProvidesUsableOptions() {
app.launchAndOpenSamplePDF()
app.waitForReader()
app.waitForDemoReaderState(timeout: 5, description: "mode=pdf") { $0.mode == "pdf" }
app.showReaderChromeIfNeeded(requiredButtonIDs: [IDs.readerSettings])
let settingsButton = app.buttons[IDs.readerSettings]
XCTAssertTrue(settingsButton.waitForExistence(timeout: 5))
settingsButton.tap()
let settingsPanel = app.scrollViews[IDs.settingsScroll]
XCTAssertTrue(settingsPanel.waitForExistence(timeout: 5), "PDF 设置面板未出现")
XCTAssertTrue(app.sliders[IDs.settingsBrightness].waitForExistence(timeout: 3), "PDF 亮度设置未出现")
XCTAssertTrue(app.segmentedControls[IDs.settingsDisplayType].waitForExistence(timeout: 3), "PDF 翻页方式设置未出现")
XCTAssertTrue(app.buttons[IDs.settingsTheme(5)].waitForExistence(timeout: 3), "PDF 主题设置未出现")
app.segmentedControls[IDs.settingsDisplayType].buttons["竖滑"].tap()
app.buttons[IDs.settingsTheme(5)].tap()
app.buttons[IDs.settingsDone].tap()
app.waitForDemoReaderState(timeout: 5, description: "display changed") { $0.display == "verticalscroll" }
app.waitForDemoReaderState(timeout: 5, description: "theme changed") { $0.fields["theme"] == "5" }
}
func testPDFLaunchHonorsDisplayTypeArgument() {
app.launchAndOpenSampleBook(bookTitleQuery: "英汉大词典", displayType: "verticalscroll")
app.waitForReader()
app.waitForDemoReaderState(timeout: 5, description: "PDF vertical launch mode") {
$0.mode == "pdf" && $0.display == "verticalscroll"
}
}
func testPDFPageCurlSupportsPinchAndDraggingAfterDoubleTapZoom() {
app.launchAndOpenSamplePDF(bookTitleQuery: "英汉大词典")
app.waitForReader()
app.waitForDemoReaderState(timeout: 5, description: "PDF page curl") {
$0.mode == "pdf" && $0.display == "pagecurl"
}
let content = app.otherElements[IDs.readerContent].firstMatch
XCTAssertTrue(content.waitForExistence(timeout: 5), "PDF 阅读页面未出现")
XCTAssertTrue(waitForViewport(content, timeout: 3) { $0.scale <= 1.05 }, "PDF 初始页面不是适配状态")
content.pinch(withScale: 1.8, velocity: 1)
XCTAssertTrue(waitForViewport(content, timeout: 3) { $0.scale >= 1.4 }, "仿真翻页下双指缩放未生效")
assertReaderPageRemains(1, duration: 0.4)
content.doubleTap()
XCTAssertTrue(waitForViewport(content, timeout: 3) { $0.scale <= 1.05 }, "双击未能还原 PDF 页面")
content.doubleTap()
guard let beforeDrag = waitForStableViewport(content, timeout: 3, stableDuration: 0.3, where: { $0.scale >= 1.8 }) else {
XCTFail("双击后 PDF 未进入稳定的放大状态")
return
}
let dragStart = content.coordinate(withNormalizedOffset: CGVector(dx: 0.75, dy: 0.5))
let dragEnd = content.coordinate(withNormalizedOffset: CGVector(dx: 0.25, dy: 0.5))
dragStart.press(forDuration: 0.05, thenDragTo: dragEnd)
let didMoveContent = waitForViewport(content, timeout: 3) {
$0.scale >= 1.8 && $0.offsetX - beforeDrag.offsetX > 30
}
XCTAssertTrue(
didMoveContent,
"PDF 双击放大后无法拖动内容;拖动前=\(beforeDrag),拖动后=\(String(describing: content.value))"
)
assertReaderPageRemains(1, duration: 0.4)
}
func testPDFPageCurlSingleFingerTurnsPageAtFittedScale() {
app.launchAndOpenSamplePDF(bookTitleQuery: "英汉大词典")
app.waitForReader()
app.waitForDemoReaderState(timeout: 5, description: "PDF page curl at page 1") {
$0.mode == "pdf" && $0.display == "pagecurl" && $0.page == 1
}
let content = app.otherElements[IDs.readerContent].firstMatch
XCTAssertTrue(content.waitForExistence(timeout: 5), "PDF 阅读页面未出现")
XCTAssertTrue(waitForViewport(content, timeout: 3) { $0.scale <= 1.05 }, "PDF 初始页面不是适配状态")
let dragStart = content.coordinate(withNormalizedOffset: CGVector(dx: 0.60, dy: 0.5))
let dragEnd = content.coordinate(withNormalizedOffset: CGVector(dx: 0.20, dy: 0.5))
dragStart.press(forDuration: 0.05, thenDragTo: dragEnd)
app.waitForReaderPage(2, timeout: 5)
}
func testPDFSwitchingFromZoomedCurlKeepsScrollModeInteractive() {
app.launchAndOpenSamplePDF(bookTitleQuery: "英汉大词典")
app.waitForReader()
app.waitForDemoReaderState(timeout: 5, description: "PDF page curl at page 1") {
$0.mode == "pdf" && $0.display == "pagecurl" && $0.page == 1
}
let content = app.otherElements[IDs.readerContent].firstMatch
XCTAssertTrue(content.waitForExistence(timeout: 5), "PDF 阅读页面未出现")
content.doubleTap()
XCTAssertNotNil(
waitForStableViewport(content, timeout: 3, stableDuration: 0.3, where: { $0.scale >= 1.8 }),
"PDF 双击后未放大"
)
app.showReaderChromeIfNeeded(requiredButtonIDs: [IDs.readerSettings])
app.buttons[IDs.readerSettings].tap()
XCTAssertTrue(app.scrollViews[IDs.settingsScroll].waitForExistence(timeout: 5), "PDF 设置面板未出现")
app.segmentedControls[IDs.settingsDisplayType].buttons["横滑"].tap()
app.buttons[IDs.settingsDone].tap()
app.waitForDemoReaderState(timeout: 5, description: "display=horizontalscroll") {
$0.display == "horizontalscroll" && $0.page == 1
}
let paging = app.collectionViews[IDs.readerPaging]
XCTAssertTrue(paging.waitForExistence(timeout: 5), "PDF 横滑视图未出现")
paging.swipeLeft()
app.waitForReaderPage(2, timeout: 5)
}
private func waitForStableViewport(
_ element: XCUIElement,
timeout: TimeInterval,
stableDuration: TimeInterval,
where predicate: ((scale: Double, offsetX: Double, offsetY: Double)) -> Bool
) -> (scale: Double, offsetX: Double, offsetY: Double)? {
let deadline = Date().addingTimeInterval(timeout)
var previous: (scale: Double, offsetX: Double, offsetY: Double)?
var stableSince: Date?
repeat {
guard let current = viewport(of: element), predicate(current) else {
previous = nil
stableSince = nil
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
continue
}
if let previous,
abs(current.scale - previous.scale) <= 0.01,
abs(current.offsetX - previous.offsetX) <= 1,
abs(current.offsetY - previous.offsetY) <= 1 {
if let stableSince, Date().timeIntervalSince(stableSince) >= stableDuration {
return current
}
} else {
stableSince = Date()
}
previous = current
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
} while Date() < deadline
return nil
}
private func assertReaderPageRemains(
_ page: Int,
duration: TimeInterval,
file: StaticString = #filePath,
line: UInt = #line
) {
let deadline = Date().addingTimeInterval(duration)
repeat {
guard let state = app.currentDemoReaderState(), state.page == page else {
XCTFail(
"PDF 手势后发生了意外翻页,当前状态:\(app.currentDemoReaderState()?.rawValue ?? "<missing>")",
file: file,
line: line
)
return
}
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
} while Date() < deadline
}
private func waitForViewport(
_ element: XCUIElement,
timeout: TimeInterval,
predicate: ((scale: Double, offsetX: Double, offsetY: Double)) -> Bool
) -> Bool {
let deadline = Date().addingTimeInterval(timeout)
repeat {
if let viewport = viewport(of: element), predicate(viewport) { return true }
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
} while Date() < deadline
return false
}
private func viewport(of element: XCUIElement) -> (scale: Double, offsetX: Double, offsetY: Double)? {
guard let value = element.value as? String else { return nil }
let pattern = #"zoom=([0-9.]+) offset=(-?[0-9.]+),(-?[0-9.]+)"#
guard let regex = try? NSRegularExpression(pattern: pattern),
let match = regex.firstMatch(in: value, range: NSRange(value.startIndex..., in: value)),
let scaleRange = Range(match.range(at: 1), in: value),
let xRange = Range(match.range(at: 2), in: value),
let yRange = Range(match.range(at: 3), in: value),
let scale = Double(value[scaleRange]),
let offsetX = Double(value[xRange]),
let offsetY = Double(value[yRange]) else { return nil }
return (scale, offsetX, offsetY)
}
}