ReadViewSDK/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/LargeBookOnDemandTests.swift
shen 6f75b083f7 feat: EPUB 阅读器搜索、选中注释、书签 chrome 状态及大量重构优化
- 新增 RDEPUBReaderSearchCoordinator 与 RDEPUBSelectionState 管理搜索和选中状态
- 新增 BookmarkChromeStateTests、NavigationBackwardTests、SelectionAnnotateTests 等 UI 测试
- 新增多个边界测试 epub 样本(损坏结构、空归档、缺失文件、流式外链验证)
- 重构阅读器 chrome 状态管理,统一 tool bar 与 search bar 交互
- 优化大书分页缓存策略(RDEPUBChapterSummaryDiskCache、RDEPUBPageCountCache)
- 移除废弃的 RDEPUBLocationConverter 和 RDEPUBPageBreakPolicy
- 更新 epub-bridge.js 与 JS bridge 通信协议
- 全面更新现有 UI 测试以适配新的 helper 和状态管理
2026-06-13 22:48:56 +08:00

220 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
final class LargeBookOnDemandTests: XCTestCase {
private let app = XCUIApplication()
private let largeBookQuery = "凡人修仙传"
override func setUpWithError() throws {
continueAfterFailure = false
}
func testLargeBookInitialOpenIsReadableWhilePaginationIsPartial() throws {
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true)
app.waitForReader(timeout: 20)
let partialState = app.waitForDemoReaderState(timeout: 15, description: "大书首开进入局部分页") { state in
state.mode == "bookPageMap" && state.pagination == "partial" && (state.page ?? 0) >= 1
}
XCTAssertEqual(partialState.mode, "bookPageMap")
XCTAssertEqual(partialState.pagination, "partial")
XCTAssertTrue(app.otherElements[IDs.readerContent].waitForExistence(timeout: 5), "首开局部分页阶段应已可阅读")
}
func testLargeBookBackgroundPaginationProgresses() throws {
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true)
app.waitForReader(timeout: 20)
let initialState = app.waitForDemoReaderState(timeout: 15, description: "获取初始分页进度") { state in
state.mode == "bookPageMap" && state.knownChapters != nil && state.buildableChapters != nil
}
let initialKnownChapters = initialState.knownChapters ?? 0
let initialKnownPages = initialState.knownPages ?? 0
let progressedState = app.waitForDemoReaderState(timeout: 45, description: "后台分页推进") { state in
guard state.mode == "bookPageMap" else { return false }
if state.pagination == "full" {
return true
}
return (state.knownChapters ?? 0) > initialKnownChapters || (state.knownPages ?? 0) > initialKnownPages
}
XCTAssertEqual(progressedState.mode, "bookPageMap")
XCTAssertTrue(
(progressedState.knownChapters ?? 0) >= initialKnownChapters ||
(progressedState.knownPages ?? 0) >= initialKnownPages
)
}
func testLargeBookSecondOpenRestoresFromCache() throws {
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true)
app.waitForReader(timeout: 20)
let firstFullState = app.waitForDemoReaderState(timeout: 60, description: "首次打开完成全书缓存") { state in
state.mode == "bookPageMap" && state.pagination == "full" && (state.buildableChapters ?? 0) > 0
}
XCTAssertGreaterThan(firstFullState.cacheFiles ?? 0, 0, "首次全量解析后应生成章节摘要缓存")
app.showReaderChromeIfNeeded()
XCTAssertTrue(app.buttons[IDs.readerBack].waitForExistence(timeout: 5), "返回按钮不存在")
app.buttons[IDs.readerBack].tap()
XCTAssertTrue(app.tables[IDs.demoBooksTable].waitForExistence(timeout: 5), "书架列表未出现")
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: false)
app.waitForReader(timeout: 15)
let reopenedState = app.waitForDemoReaderState(timeout: 10, description: "二开直接恢复完整缓存") { state in
state.mode == "bookPageMap" && state.pagination == "full" && (state.knownChapters ?? 0) == (state.buildableChapters ?? -1)
}
XCTAssertEqual(reopenedState.pagination, "full")
XCTAssertEqual(reopenedState.knownChapters, reopenedState.buildableChapters)
XCTAssertEqual(reopenedState.knownPages, firstFullState.knownPages, "二开命中缓存后总页数应稳定一致")
}
func testCorruptedCacheFallsBackGracefully() throws {
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true, clearsCache: true)
app.waitForReader(timeout: 20)
_ = app.waitForDemoReaderState(timeout: 60, description: "首次打开完成缓存建立") { state in
state.pagination == "full" && (state.cacheFiles ?? 0) > 0
}
app.showReaderChromeIfNeeded()
app.buttons[IDs.readerBack].tap()
XCTAssertTrue(app.tables[IDs.demoBooksTable].waitForExistence(timeout: 5))
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: false, corruptsCache: true)
app.waitForReader(timeout: 20)
let recoveredState = app.waitForDemoReaderState(timeout: 45, description: "损坏缓存后仍可恢复阅读") { state in
state.isOpened && state.pagination != "none" && state.mode == "bookPageMap"
}
XCTAssertEqual(recoveredState.lastError, "none", "缓存损坏不应导致阅读器进入错误状态")
}
func testClearingCacheForcesRebuild() throws {
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true, clearsCache: true)
app.waitForReader(timeout: 20)
_ = app.waitForDemoReaderState(timeout: 60, description: "首次缓存建立") { state in
state.pagination == "full" && (state.cacheFiles ?? 0) > 0
}
app.showReaderChromeIfNeeded()
app.buttons[IDs.readerBack].tap()
XCTAssertTrue(app.tables[IDs.demoBooksTable].waitForExistence(timeout: 5))
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: false, clearsCache: true)
app.waitForReader(timeout: 20)
let rebuiltState = app.waitForDemoReaderState(timeout: 60, description: "清空缓存后重新构建") { state in
state.pagination == "full" && (state.cacheFiles ?? 0) > 0
}
XCTAssertGreaterThan(rebuiltState.cacheFiles ?? 0, 0, "清空缓存后应重新写入缓存文件")
}
func testMiddleChapterOpenPositionStableAfterFullParse() throws {
//
app.launchAndOpenSampleBook(
bookTitleQuery: largeBookQuery,
pageNumber: 50,
resetsReaderState: true
)
app.waitForReader(timeout: 20)
let partialState = app.waitForDemoReaderState(timeout: 15, description: "中间章节首开进入局部分页") { state in
state.mode == "bookPageMap" && state.pagination == "partial" && (state.page ?? 0) >= 1
}
let pageAfterOpen = partialState.page ?? 0
XCTAssertGreaterThan(pageAfterOpen, 1, "应从中间章节打开,当前页=\(pageAfterOpen)")
//
let fullState = app.waitForDemoReaderState(timeout: 90, description: "后台解析完成") { state in
state.pagination == "full"
}
XCTAssertEqual(fullState.pagination, "full", "后台解析应完成")
// Cpending map
let pageAfterParse = fullState.page ?? 0
XCTAssertEqual(pageAfterParse, pageAfterOpen,
"后台解析完成后页码不应跳转:解析前=\(pageAfterOpen) 解析后=\(pageAfterParse)")
// pending map
let paging = app.collectionViews[IDs.readerPaging]
if paging.waitForExistence(timeout: 5) {
paging.swipeLeft()
}
let afterSwipe = app.waitForDemoReaderState(timeout: 10, description: "翻页后页码推进") { state in
state.page != nil && state.page != pageAfterParse
}
XCTAssertGreaterThan(afterSwipe.page ?? 0, pageAfterParse,
"翻页后页码应前进:翻页前=\(pageAfterParse) 翻页后=\(afterSwipe.page ?? 0)")
}
func testMiddleChapterOpenSwipeAfterFullParseResolvesCorrectly() throws {
//
app.launchAndOpenSampleBook(
bookTitleQuery: largeBookQuery,
pageNumber: 30,
resetsReaderState: true
)
app.waitForReader(timeout: 20)
_ = app.waitForDemoReaderState(timeout: 15, description: "局部分页就绪") { state in
state.mode == "bookPageMap" && state.pagination == "partial"
}
//
_ = app.waitForDemoReaderState(timeout: 90, description: "后台解析完成") { state in
state.pagination == "full"
}
// 3
let paging = app.collectionViews[IDs.readerPaging]
XCTAssertTrue(paging.waitForExistence(timeout: 5), "分页视图不存在")
var previousPage = app.currentDemoReaderState()?.page ?? 0
for i in 0..<3 {
paging.swipeLeft()
RunLoop.current.run(until: Date().addingTimeInterval(1))
let currentState = app.currentDemoReaderState()
let currentPage = currentState?.page ?? 0
XCTAssertGreaterThan(currentPage, previousPage,
"\(i + 1) 次翻页后页码应递增:前=\(previousPage) 后=\(currentPage)")
previousPage = currentPage
}
}
func testLargeBookContinuousPagingExtendsKnownPageMap() throws {
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, displayType: "scroll", resetsReaderState: true)
app.waitForReader(timeout: 20)
app.waitForDemoReaderState(timeout: 8, description: "display=horizontalScroll") { $0.display == "horizontalScroll" }
let initialState = app.waitForDemoReaderState(timeout: 15, description: "初始局部分页") { state in
state.mode == "bookPageMap" && state.knownPages != nil && state.page != nil
}
let initialKnownPages = initialState.knownPages ?? 0
let initialPage = initialState.page ?? 0
let paging = app.collectionViews[IDs.readerPaging]
XCTAssertTrue(paging.waitForExistence(timeout: 5), "分页视图不存在")
for _ in 0..<4 {
paging.swipeLeft()
}
let extendedState = app.waitForDemoReaderState(timeout: 20, description: "连续翻页触发扩窗") { state in
guard state.mode == "bookPageMap" else { return false }
let pageAdvanced = (state.page ?? 0) > initialPage
let pageMapExtended = (state.knownPages ?? 0) > initialKnownPages || state.pagination == "full"
return pageAdvanced && pageMapExtended
}
XCTAssertTrue((extendedState.page ?? 0) > initialPage, "连续翻页后当前页应向后推进")
XCTAssertTrue(
(extendedState.knownPages ?? 0) > initialKnownPages || extendedState.pagination == "full",
"连续翻页后已知页图应扩窗或直接完成全量分页"
)
}
}