ReadViewSDK/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/LocationPersistenceTests.swift
shenlei c65c190b71 feat: EPUB阅读器搜索、注释、CFI模块及大书远距跳转优化
- 实现EPUB阅读器搜索功能及选中注释功能
- 优化CFI模块,修复代码审查发现的11个问题
- 实现大书远距目录跳转与后台补全优化方案
- 优化设置面板与章节运行时联动
- 重构及大量改进优化
2026-06-22 20:26:34 +08:00

208 lines
9.2 KiB
Swift

import XCTest
final class LocationPersistenceTests: XCTestCase {
private let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
}
func testReadingPositionRestoredOnReopen() throws {
app.launchAndOpenSampleBook()
app.waitForReader(timeout: 12)
_ = app.waitForDemoReaderState(timeout: 5, description: "存在页码") { $0.page != nil }
let paging = app.collectionViews[IDs.readerPaging]
if paging.waitForExistence(timeout: 5) {
paging.swipeLeft()
}
let stateAfterSwipe = app.waitForDemoReaderState(timeout: 8, description: "翻页后页码变化") { state in
(state.page ?? 0) > 1
}
let pageAfterSwipe = stateAfterSwipe.page ?? 0
guard pageAfterSwipe > 1 else {
throw XCTSkip("当前翻页模式下未能翻到第 2 页")
}
let backButton = app.buttons[IDs.readerBack]
if backButton.waitForExistence(timeout: 3) { backButton.tap() }
app.launchAndOpenSampleBook()
app.waitForReader(timeout: 12)
let restoredState = app.waitForDemoReaderState(timeout: 8, description: "恢复阅读位置") { state in
state.page != nil
}
let restoredPage = restoredState.page ?? 0
XCTAssertEqual(restoredPage, pageAfterSwipe, "重新打开后应恢复到上次阅读页码")
}
func testResetStateLaunchesAtFirstPage() throws {
app.launchAndOpenSampleBook(resetsReaderState: true)
app.waitForReader(timeout: 12)
let state = app.waitForDemoReaderState(timeout: 5, description: "首开第 1 页") { state in
state.page != nil
}
let page = state.page ?? 0
XCTAssertEqual(page, 1, "重置状态后应从第 1 页开始")
}
func testPositionSurvivesFontChange() throws {
app.launchAndOpenSampleBook()
app.waitForReader(timeout: 12)
let paging = app.collectionViews[IDs.readerPaging]
if paging.waitForExistence(timeout: 5) { paging.swipeLeft() }
app.waitForDemoReaderState(timeout: 5, description: "page exists") { $0.page != nil }
app.showReaderChromeIfNeeded()
if app.buttons[IDs.readerSettings].waitForExistence(timeout: 3) { app.buttons[IDs.readerSettings].tap() }
if app.buttons[IDs.settingsFontIncrease].waitForExistence(timeout: 5) { app.buttons[IDs.settingsFontIncrease].tap() }
if app.buttons[IDs.settingsDone].waitForExistence(timeout: 3) { app.buttons[IDs.settingsDone].tap() }
let backButton = app.buttons[IDs.readerBack]
if backButton.waitForExistence(timeout: 3) { backButton.tap() }
app.launchAndOpenSampleBook()
app.waitForReader(timeout: 12)
app.waitForDemoReaderState(timeout: 8, description: "reader=opened") { $0.isOpened }
}
func testPositionSurvivesFontChoiceAndLineHeightChange() throws {
app.launchAndOpenSampleBook(resetsReaderState: true)
app.waitForReader(timeout: 12)
let paging = app.collectionViews[IDs.readerPaging]
if paging.waitForExistence(timeout: 5) {
paging.swipeLeft()
paging.swipeLeft()
}
let stateBefore = app.waitForDemoReaderState(timeout: 8, description: "翻页后定位稳定") { state in
(state.page ?? 0) >= 2 && state.cfi?.isEmpty == false
}
let pageBefore = stateBefore.page ?? 0
let hrefBefore = stateBefore.href
let cfiBefore = stateBefore.cfi
XCTAssertGreaterThanOrEqual(pageBefore, 2, "测试前应先进入第 2 页或之后")
XCTAssertNotNil(hrefBefore, "重排前应存在当前位置 href")
XCTAssertNotNil(cfiBefore, "重排前应存在当前位置 CFI")
app.showReaderChromeIfNeeded()
XCTAssertTrue(app.buttons[IDs.readerSettings].waitForExistence(timeout: 3))
app.buttons[IDs.readerSettings].tap()
let fontChoiceControl = app.segmentedControls[IDs.settingsFontChoice]
XCTAssertTrue(fontChoiceControl.waitForExistence(timeout: 5), "字体选择控件应出现")
if fontChoiceControl.buttons.count > 1 {
fontChoiceControl.buttons.element(boundBy: 1).tap()
}
let lineHeightControl = app.segmentedControls[IDs.settingsLineHeight]
XCTAssertTrue(lineHeightControl.waitForExistence(timeout: 5), "行距控件应出现")
if lineHeightControl.buttons.count > 1 {
lineHeightControl.buttons.element(boundBy: min(1, lineHeightControl.buttons.count - 1)).tap()
}
XCTAssertTrue(app.buttons[IDs.settingsDone].waitForExistence(timeout: 3))
app.buttons[IDs.settingsDone].tap()
let stateAfter = app.waitForDemoReaderState(timeout: 10, description: "改字体与行距后页码保持") { state in
state.page != nil && state.cfi?.isEmpty == false
}
let pageAfter = stateAfter.page ?? 0
XCTAssertEqual(pageAfter, pageBefore, "字体与行距变化后,应尽量保留当前阅读页")
XCTAssertEqual(stateAfter.href, hrefBefore, "重排后应仍定位在同一章节")
XCTAssertEqual(stateAfter.cfi, cfiBefore, "重排后应保持当前阅读锚点的 CFI 不变")
}
func testBookmarkAndHighlightPersistAcrossReopen() throws {
app.launchAndOpenSampleBook(resetsReaderState: true)
app.waitForReader(timeout: 12)
app.showReaderChromeIfNeeded()
XCTAssertTrue(app.buttons[IDs.readerBookmark].waitForExistence(timeout: 3))
app.buttons[IDs.readerBookmark].tap()
app.waitForDemoReaderState(timeout: 5, description: "bookmarks=1") { $0.bookmarks == 1 }
app.hideReaderChromeIfNeeded()
let content = app.otherElements[IDs.readerSelectionText].firstMatch
XCTAssertTrue(content.waitForExistence(timeout: 5))
let start = content.coordinate(withNormalizedOffset: CGVector(dx: 0.2, dy: 0.5))
let end = content.coordinate(withNormalizedOffset: CGVector(dx: 0.55, dy: 0.5))
start.press(forDuration: 0.8, thenDragTo: end)
XCTAssertTrue(app.menuItems["高亮"].waitForExistence(timeout: 3))
app.menuItems["高亮"].tap()
app.waitForDemoReaderState(timeout: 8, description: "highlights=1") { $0.highlights == 1 }
app.showReaderChromeIfNeeded()
app.buttons[IDs.readerBack].tap()
app.launchAndOpenSampleBook(resetsReaderState: false)
app.waitForReader(timeout: 12)
let state = app.waitForDemoReaderState(timeout: 8, description: "bookmark/highlight restored") { state in
state.bookmarks == 1 && state.highlights == 1
}
XCTAssertEqual(state.bookmarks, 1)
XCTAssertEqual(state.highlights, 1)
}
func testHighlightNavigationSurvivesFontChoiceChange() throws {
app.launchAndOpenSampleBook(resetsReaderState: true, pageNumber: 3)
app.waitForReader(timeout: 12)
let initialState = app.waitForDemoReaderState(timeout: 8, description: "初始位于第 3 页") { state in
state.page == 3
}
XCTAssertEqual(initialState.page, 3)
app.hideReaderChromeIfNeeded()
let selectionText = try app.requireSelectionText(expectedPage: 3)
let start = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.25, dy: 0.5))
let end = selectionText.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.5))
start.press(forDuration: 0.8, thenDragTo: end)
let highlightItem = app.menuItems["高亮"]
XCTAssertTrue(highlightItem.waitForExistence(timeout: 3), "应出现高亮菜单")
highlightItem.tap()
app.waitForDemoReaderState(timeout: 8, description: "highlights=1") { $0.highlights == 1 }
app.showReaderChromeIfNeeded()
app.buttons[IDs.readerSettings].tap()
let fontChoiceControl = app.segmentedControls[IDs.settingsFontChoice]
XCTAssertTrue(fontChoiceControl.waitForExistence(timeout: 5), "字体选择控件应出现")
if fontChoiceControl.buttons.count > 1 {
fontChoiceControl.buttons.element(boundBy: 1).tap()
}
app.buttons[IDs.settingsDone].tap()
let content = app.otherElements[IDs.readerContent].firstMatch
if content.waitForExistence(timeout: 3) {
content.swipeLeft()
content.swipeLeft()
Thread.sleep(forTimeInterval: 0.5)
}
app.showReaderChromeIfNeeded()
let highlightsButton = app.buttons[IDs.readerHighlights]
XCTAssertTrue(highlightsButton.waitForExistence(timeout: 3), "高亮面板按钮应存在")
highlightsButton.tap()
let highlightsTable = app.tables[IDs.readerHighlightsTable]
XCTAssertTrue(highlightsTable.waitForExistence(timeout: 5), "高亮列表应出现")
let firstCell = highlightsTable.cells.firstMatch
XCTAssertTrue(firstCell.waitForExistence(timeout: 3), "高亮列表应至少有一个条目")
firstCell.tap()
let restoredState = app.waitForDemoReaderState(timeout: 10, description: "字体变化后高亮跳回原页") { state in
state.page == 3
}
XCTAssertEqual(restoredState.page, 3, "字体变化后,通过高亮面板跳转仍应回到原始高亮页")
}
}