ReadViewSDK/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/PDFAnnotationTests.swift

241 lines
10 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import XCTest
/// PDF
///
/// `host-fixture` 1
/// `(0.18, 0.20)...(0.65, 0.20)`
/// `none` /OCR 使 `(0.15, 0.70)...(0.60, 0.84)`
final class PDFAnnotationTests: XCTestCase {
private let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
}
func testPDFHostFixtureLongPressSelectsTextWithoutTurningPage() {
let textLayer = openPDF(textSource: "host-fixture")
selectFixtureText(in: textLayer)
let fields = waitForTextLayer(textLayer, description: "host text selection") {
$0["source"] == "text" && ($0["rects"].flatMap(Int.init) ?? 0) > 0
}
XCTAssertFalse(fields["text", default: ""].isEmpty, "文字选区应公开已选文本")
assertReaderPageRemains(1, duration: 0.6)
}
func testPDFHostFixtureCopiesSelectedText() {
let textLayer = openPDF(textSource: "host-fixture")
selectFixtureText(in: textLayer)
let copyMenuItem = app.menuItems[IDs.pdfSelectionMenuCopy]
XCTAssertTrue(copyMenuItem.waitForExistence(timeout: 5), "选区菜单缺少复制选项")
copyMenuItem.tap()
let state = app.waitForDemoReaderState(timeout: 5, description: "copy=success and copiedText") {
$0["copy"] == "success" && !($0["copiedText"] ?? "").isEmpty
}
XCTAssertFalse((state["copiedText"] ?? "").isEmpty, "复制成功后应记录被复制的文本")
}
func testPDFHostFixtureCreatesTextHighlight() {
let textLayer = openPDF(textSource: "host-fixture")
selectFixtureText(in: textLayer)
let highlightMenuItem = app.menuItems[IDs.pdfSelectionMenuHighlight]
XCTAssertTrue(highlightMenuItem.waitForExistence(timeout: 5), "选区菜单缺少高亮选项")
highlightMenuItem.tap()
let fields = waitForTextLayer(textLayer, description: "one saved text highlight") {
($0["annotations"].flatMap(Int.init) ?? 0) == 1
}
XCTAssertEqual(fields["notes"], "0", "纯高亮不应额外创建笔记")
}
func testPDFAnnotationPersistsAfterClosingAndReopeningBook() {
let textLayer = openPDF(textSource: "host-fixture")
selectFixtureText(in: textLayer)
let highlightMenuItem = app.menuItems[IDs.pdfSelectionMenuHighlight]
XCTAssertTrue(highlightMenuItem.waitForExistence(timeout: 5))
highlightMenuItem.tap()
_ = waitForTextLayer(textLayer, description: "saved annotation before reopening") {
($0["annotations"].flatMap(Int.init) ?? 0) == 1
}
// reset
app.terminate()
app.launchAndOpenSamplePDF(
bookTitleQuery: "英汉大词典",
resetsReaderState: false,
pdfTextSource: "host-fixture"
)
app.waitForReader()
let reopenedTextLayer = app.otherElements[IDs.pdfTextLayer].firstMatch
XCTAssertTrue(reopenedTextLayer.waitForExistence(timeout: 8))
_ = waitForTextLayer(reopenedTextLayer, description: "persisted annotation after reopening") {
($0["annotations"].flatMap(Int.init) ?? 0) == 1
}
}
func testPDFAnnotationsPanelListsSavedHighlight() {
let textLayer = openPDF(textSource: "host-fixture")
selectFixtureText(in: textLayer)
let highlightMenuItem = app.menuItems[IDs.pdfSelectionMenuHighlight]
XCTAssertTrue(highlightMenuItem.waitForExistence(timeout: 5))
highlightMenuItem.tap()
_ = waitForTextLayer(textLayer, description: "saved highlight before opening annotations") {
($0["annotations"].flatMap(Int.init) ?? 0) == 1
}
app.showReaderChromeIfNeeded(requiredButtonIDs: [IDs.pdfAnnotations])
let annotationsButton = app.buttons[IDs.pdfAnnotations]
XCTAssertTrue(annotationsButton.waitForExistence(timeout: 5), "底部工具栏缺少笔记入口")
annotationsButton.tap()
let table = app.tables[IDs.pdfAnnotationsTable]
XCTAssertTrue(table.waitForExistence(timeout: 5), "笔记列表页未打开")
XCTAssertGreaterThan(table.cells.count, 0, "笔记列表应显示刚创建的高亮")
}
func testPDFHostFixtureCreatesAnnotation() {
let textLayer = openPDF(textSource: "host-fixture")
selectFixtureText(in: textLayer)
let annotateMenuItem = app.menuItems[IDs.pdfSelectionMenuAnnotate]
XCTAssertTrue(annotateMenuItem.waitForExistence(timeout: 5), "选区菜单缺少注释选项")
annotateMenuItem.tap()
let editor = app.otherElements[IDs.pdfAnnotationEditor]
XCTAssertTrue(editor.waitForExistence(timeout: 5), "注释编辑页未出现")
let input = app.textViews[IDs.pdfAnnotationInput]
XCTAssertTrue(input.waitForExistence(timeout: 5), "注释输入框未出现")
input.tap()
input.typeText("fixture note")
let saveButton = app.buttons[IDs.pdfAnnotationSave]
XCTAssertTrue(saveButton.waitForExistence(timeout: 5), "注释编辑页缺少保存按钮")
saveButton.tap()
let fields = waitForTextLayer(textLayer, description: "one saved note annotation") {
($0["annotations"].flatMap(Int.init) ?? 0) == 1
&& ($0["notes"].flatMap(Int.init) ?? 0) == 1
}
XCTAssertEqual(fields["notes"], "1", "保存注释后页面应显示笔记标记")
}
func testPDFWithoutOCRCreatesRegionHighlightWithoutCopy() {
let textLayer = openPDF(textSource: "none")
_ = waitForTextLayer(textLayer, description: "no OCR text runs") {
($0["textRuns"].flatMap(Int.init) ?? -1) == 0
}
let start = textLayer.coordinate(withNormalizedOffset: CGVector(dx: 0.15, dy: 0.70))
let end = textLayer.coordinate(withNormalizedOffset: CGVector(dx: 0.60, dy: 0.84))
start.press(forDuration: 0.55, thenDragTo: end)
_ = waitForTextLayer(textLayer, description: "region selection") {
$0["source"] == "region" && ($0["rects"].flatMap(Int.init) ?? 0) > 0
}
let highlightMenuItem = app.menuItems[IDs.pdfSelectionMenuHighlight]
XCTAssertTrue(highlightMenuItem.waitForExistence(timeout: 5), "区域选区应弹出带高亮选项的菜单")
XCTAssertFalse(
app.menuItems[IDs.pdfSelectionMenuCopy].waitForExistence(timeout: 1),
"无 OCR 的区域选区不应提供复制选项"
)
highlightMenuItem.tap()
_ = waitForTextLayer(textLayer, description: "one saved region highlight") {
($0["annotations"].flatMap(Int.init) ?? 0) == 1
}
assertReaderPageRemains(1, duration: 0.4)
}
@discardableResult
private func openPDF(textSource: String) -> XCUIElement {
app.launchAndOpenSamplePDF(
bookTitleQuery: "英汉大词典",
resetsReaderState: true,
pdfTextSource: textSource
)
app.waitForReader()
app.waitForDemoReaderState(timeout: 8, description: "PDF page 1") {
$0.mode == "pdf" && $0.page == 1
}
let textLayer = app.otherElements[IDs.pdfTextLayer].firstMatch
XCTAssertTrue(textLayer.waitForExistence(timeout: 8), "PDF 图片文字层未出现")
return textLayer
}
private func selectFixtureText(in textLayer: XCUIElement) {
_ = waitForTextLayer(textLayer, description: "host fixture text runs") {
($0["textRuns"].flatMap(Int.init) ?? 0) >= 2
}
let start = textLayer.coordinate(withNormalizedOffset: CGVector(dx: 0.18, dy: 0.20))
let end = textLayer.coordinate(withNormalizedOffset: CGVector(dx: 0.65, dy: 0.20))
start.press(forDuration: 0.55, thenDragTo: end)
XCTAssertTrue(
app.menuItems[IDs.pdfSelectionMenuHighlight].waitForExistence(timeout: 5),
"长按拖选文字后未弹出选区菜单"
)
}
@discardableResult
private func waitForTextLayer(
_ textLayer: XCUIElement,
timeout: TimeInterval = 6,
description: String,
where predicate: ([String: String]) -> Bool
) -> [String: String] {
let deadline = Date().addingTimeInterval(timeout)
var lastFields: [String: String] = [:]
while Date() < deadline {
lastFields = textLayerFields(textLayer)
if predicate(lastFields) {
return lastFields
}
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
}
XCTFail("PDF 文本层未满足 \(description),当前:\(String(describing: textLayer.value))")
return lastFields
}
private func textLayerFields(_ textLayer: XCUIElement) -> [String: String] {
guard let rawValue = textLayer.value as? String else { return [:] }
return rawValue
.split(separator: ";", omittingEmptySubsequences: true)
.reduce(into: [:]) { fields, token in
let parts = token.split(separator: "=", maxSplits: 1, omittingEmptySubsequences: false)
guard parts.count == 2 else { return }
fields[String(parts[0])] = String(parts[1])
}
}
private func assertReaderPageRemains(
_ page: Int,
duration: TimeInterval,
file: StaticString = #filePath,
line: UInt = #line
) {
let deadline = Date().addingTimeInterval(duration)
repeat {
guard app.currentDemoReaderState()?.page == page else {
XCTFail(
"PDF 选择手势意外翻页,当前状态:\(app.currentDemoReaderState()?.rawValue ?? "<missing>")",
file: file,
line: line
)
return
}
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
} while Date() < deadline
}
}