import XCTest final class HighlightsManagementTests: XCTestCase { private let app = XCUIApplication() override func setUpWithError() throws { continueAfterFailure = false } func testHighlightsPanelOpens() throws { app.launchAndOpenSampleBook() app.waitForReader(timeout: 12) let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) start.press(forDuration: 0.8, thenDragTo: end) let highlightMenuItem = app.menuItems["高亮"] if highlightMenuItem.waitForExistence(timeout: 3) { highlightMenuItem.tap() } else if app.buttons["高亮"].waitForExistence(timeout: 2) { app.buttons["高亮"].tap() } app.waitForReaderState(containing: "highlights=", timeout: 5) app.showReaderChromeIfNeeded() XCTAssertTrue(app.buttons[IDs.readerHighlights].waitForExistence(timeout: 3), "高亮列表按钮不存在") app.buttons[IDs.readerHighlights].tap() XCTAssertTrue(app.tables[IDs.readerHighlightsTable].waitForExistence(timeout: 5), "高亮管理面板未出现") } func testHighlightsPanelShowsCreatedHighlight() throws { app.launchAndOpenSampleBook() app.waitForReader(timeout: 12) let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) start.press(forDuration: 0.8, thenDragTo: end) let highlightMenuItem = app.menuItems["高亮"] if highlightMenuItem.waitForExistence(timeout: 3) { highlightMenuItem.tap() } app.showReaderChromeIfNeeded() app.buttons[IDs.readerHighlights].tap() let highlightsTable = app.tables[IDs.readerHighlightsTable] XCTAssertTrue(highlightsTable.waitForExistence(timeout: 5), "高亮表格未出现") XCTAssertTrue(waitForCellCount(in: highlightsTable, minimum: 1, timeout: 5), "创建高亮后面板应至少有 1 行数据") } func testHighlightsPanelFilterAll() throws { app.launchAndOpenSampleBook() app.waitForReader(timeout: 12) let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) start.press(forDuration: 0.8, thenDragTo: end) let highlightMenuItem = app.menuItems["高亮"] if highlightMenuItem.waitForExistence(timeout: 3) { highlightMenuItem.tap() } app.showReaderChromeIfNeeded() app.buttons[IDs.readerHighlights].tap() let filterControl = app.segmentedControls[IDs.readerHighlightsFilter] XCTAssertTrue(filterControl.waitForExistence(timeout: 5), "筛选控件未出现") filterControl.buttons.element(boundBy: 0).tap() let highlightsTable = app.tables[IDs.readerHighlightsTable] XCTAssertTrue(highlightsTable.waitForExistence(timeout: 3)) XCTAssertTrue(waitForCellCount(in: highlightsTable, minimum: 1, timeout: 5), "筛选全部后应有高亮数据") } func testHighlightsPanelEmptyState() throws { app.launchAndOpenSampleBook(resetsReaderState: true) app.waitForReader(timeout: 12) app.showReaderChromeIfNeeded() let highlightsButton = app.buttons[IDs.readerHighlights] XCTAssertTrue(highlightsButton.waitForExistence(timeout: 3), "高亮列表按钮不存在") highlightsButton.tap() XCTAssertTrue(app.tables[IDs.readerHighlightsTable].waitForExistence(timeout: 5), "高亮管理面板未出现") XCTAssertTrue(app.staticTexts[IDs.readerHighlightsEmptyLabel].waitForExistence(timeout: 5), "空状态 label 应存在") XCTAssertTrue(app.staticTexts[IDs.readerHighlightsEmptyLabel].label.contains("暂无"), "空状态应显示暂无标注") } func testDeleteHighlightFromPanel() throws { app.launchAndOpenSampleBook() app.waitForReader(timeout: 12) let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch guard selectionText.waitForExistence(timeout: 5) else { throw XCTSkip("文本选区元素不存在") } let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.5)) let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5)) start.press(forDuration: 0.8, thenDragTo: end) let highlightMenuItem = app.menuItems["高亮"] if highlightMenuItem.waitForExistence(timeout: 3) { highlightMenuItem.tap() } app.showReaderChromeIfNeeded() app.buttons[IDs.readerHighlights].tap() let highlightsTable = app.tables[IDs.readerHighlightsTable] XCTAssertTrue(highlightsTable.waitForExistence(timeout: 5)) XCTAssertTrue(waitForCellCount(in: highlightsTable, minimum: 1, timeout: 5)) highlightsTable.cells.element(boundBy: 0).tap() let deleteButton = app.buttons["删除标注"].firstMatch let deleteHighlightButton = app.buttons["删除高亮"].firstMatch if deleteButton.waitForExistence(timeout: 3) { deleteButton.tap() } else if deleteHighlightButton.waitForExistence(timeout: 1) { deleteHighlightButton.tap() } XCTAssertTrue(app.staticTexts[IDs.readerHighlightsEmptyLabel].waitForExistence(timeout: 5), "删除后应显示空状态") } private func waitForCellCount( in table: XCUIElement, minimum: Int, timeout: TimeInterval ) -> Bool { let deadline = Date().addingTimeInterval(timeout) while Date() < deadline { if table.cells.count >= minimum { return true } RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } return table.cells.count >= minimum } }