import XCTest final class BookmarkManagementTests: XCTestCase { private let app = XCUIApplication() override func setUpWithError() throws { continueAfterFailure = false } func testBookmarksPanelShowsBookmarkAfterAdding() throws { app.launchAndOpenSampleBook() app.waitForReader(timeout: 12) app.showReaderChromeIfNeeded() let bookmarkButton = app.buttons[IDs.readerBookmark] XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 3), "书签按钮不存在") bookmarkButton.tap() app.waitForDemoReaderState(timeout: 5, description: "bookmarks=1") { $0.bookmarks == 1 } app.showReaderChromeIfNeeded() let bookmarksButton = app.buttons[IDs.readerBookmarks] XCTAssertTrue(bookmarksButton.waitForExistence(timeout: 3), "书签列表按钮不存在") bookmarksButton.tap() let bookmarksTable = app.tables[IDs.readerBookmarksTable] XCTAssertTrue(bookmarksTable.waitForExistence(timeout: 5), "书签表格未出现") XCTAssertTrue(waitForCellCount(in: bookmarksTable, minimum: 1, timeout: 5), "添加书签后面板应有至少 1 行数据") } func testBookmarksPanelEmptyState() throws { app.launchAndOpenSampleBook(resetsReaderState: true) app.waitForReader(timeout: 12) app.showReaderChromeIfNeeded() let bookmarksButton = app.buttons[IDs.readerBookmarks] XCTAssertTrue(bookmarksButton.waitForExistence(timeout: 3), "书签列表按钮不存在") bookmarksButton.tap() XCTAssertTrue(app.tables[IDs.readerBookmarksTable].waitForExistence(timeout: 5), "书签面板未出现") XCTAssertTrue(app.staticTexts[IDs.readerBookmarksEmptyLabel].waitForExistence(timeout: 5), "空状态 label 应存在") XCTAssertTrue(app.staticTexts[IDs.readerBookmarksEmptyLabel].label.contains("暂无"), "空状态应显示暂无书签") } func testDeleteBookmarkFromPanel() throws { app.launchAndOpenSampleBook() app.waitForReader(timeout: 12) app.showReaderChromeIfNeeded() app.buttons[IDs.readerBookmark].tap() app.waitForDemoReaderState(timeout: 5, description: "bookmarks=1") { $0.bookmarks == 1 } app.showReaderChromeIfNeeded() app.buttons[IDs.readerBookmarks].tap() let bookmarksTable = app.tables[IDs.readerBookmarksTable] XCTAssertTrue(bookmarksTable.waitForExistence(timeout: 5)) XCTAssertTrue(waitForCellCount(in: bookmarksTable, minimum: 1, timeout: 5)) bookmarksTable.cells.element(boundBy: 0).tap() let deleteButton = app.buttons["删除书签"] if deleteButton.waitForExistence(timeout: 3) { deleteButton.tap() } XCTAssertTrue(app.staticTexts[IDs.readerBookmarksEmptyLabel].waitForExistence(timeout: 5), "删除后应显示空状态") } func testBookmarkIconStateToggle() throws { app.launchAndOpenSampleBook() app.waitForReader(timeout: 12) app.showReaderChromeIfNeeded() let bookmarkButton = app.buttons[IDs.readerBookmark] XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 3), "书签按钮不存在") bookmarkButton.tap() XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 3), "添加书签后按钮应仍然存在") bookmarkButton.tap() XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 3), "移除书签后按钮应仍然存在") } func testBookmarksButtonDisabledWhenNoBookmarks() throws { app.launchAndOpenSampleBook(resetsReaderState: true) app.waitForReader(timeout: 12) app.showReaderChromeIfNeeded() let bookmarksButton = app.buttons[IDs.readerBookmarks] XCTAssertTrue(bookmarksButton.waitForExistence(timeout: 3), "书签列表按钮不存在") XCTAssertFalse(bookmarksButton.isEnabled, "零书签时书签列表按钮应禁用") } func testBookmarkIndicatorStaysSelectedAtBookmarkedLocation() throws { app.launchAndOpenSampleBook(resetsReaderState: true) app.waitForReader(timeout: 12) app.showReaderChromeIfNeeded() let bookmarkButton = app.buttons[IDs.readerBookmark] XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 3), "书签按钮不存在") bookmarkButton.tap() app.waitForDemoReaderState(timeout: 5, description: "bookmarks=1") { $0.bookmarks == 1 } XCTAssertEqual(bookmarkButton.value as? String, "selected", "当前页已加书签时顶部书签按钮应为选中态") let paging = app.collectionViews[IDs.readerPaging] if paging.waitForExistence(timeout: 5) { paging.swipeLeft() app.showReaderChromeIfNeeded() XCTAssertEqual(bookmarkButton.value as? String, "unselected", "离开书签页后顶部书签按钮应取消选中") paging.swipeRight() app.showReaderChromeIfNeeded() XCTAssertEqual(bookmarkButton.value as? String, "selected", "返回书签页后顶部书签按钮应恢复选中") } } 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 } }