1. Configurable chapter window size (onDemandChapterWindowSize: 3-15) - Parameterized window radius in RDEPUBChapterRuntimeStore - Updated RDEPUBChapterWindowCoordinator to use configurable radius - RDEPUBChapterWindowSnapshot.from() accepts chapter array instead of fixed prev/next - Even numbers round up to odd (4→5), min 3, max 15 2. Configurable metadata parsing concurrency (metadataParsingConcurrency) - Default equals CPU core count - Parallel execution via OperationQueue in paginateMetadataOnly - Each worker creates independent builder instance - NSLock protects result aggregation 3. Per-chapter and total wall-clock timing instrumentation - Separated render vs I/O timing per chapter - Summary log with wallClockMs, renderTotalMs, writeTotalMs, avgRenderMs - Timing stored in RDEPUBReaderContext for test access 4. UI automation test infrastructure - Added --demo-window-size, --demo-concurrency, --demo-clear-cache launch args - DemoReaderState exposes windowSize, parseMs, parseConcurrency - ConfigurableWindowTests: 5 test cases for window size 3/5/15 - ConcurrentParsingTests: 4 test cases for concurrency 2/4 - MetadataParseBenchmarkTests: serial vs parallel benchmark 5. Bug fixes - Fixed page snap-back during background parsing (isUserInteracting check) - Reduced BookPageMap refresh frequency from 16 to 32 chapters - Moved waitForReadingInteractionToSettle outside operation loop 6. Design doc: dual-layer PageMap (estimated + precise mixed)
83 lines
3.6 KiB
Swift
83 lines
3.6 KiB
Swift
import XCTest
|
||
|
||
final class ConfigurableWindowTests: XCTestCase {
|
||
private let app = XCUIApplication()
|
||
private let largeBookQuery = "凡人修仙传"
|
||
|
||
override func setUpWithError() throws {
|
||
continueAfterFailure = false
|
||
}
|
||
|
||
func testWindowSize5PreloadsMoreChapters() throws {
|
||
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true, windowSize: 5)
|
||
app.waitForReader(timeout: 20)
|
||
|
||
let state = app.waitForDemoReaderState(timeout: 15, description: "window=5 预加载更多章节") { state in
|
||
state.mode == "bookPageMap" && state.knownChapters != nil
|
||
}
|
||
|
||
XCTAssertEqual(state.windowSize, 5, "windowSize 应为 5")
|
||
XCTAssertTrue((state.knownChapters ?? 0) >= 3, "window=5 首开应至少预加载 3 章(当前章 + 相邻),实际=\(state.knownChapters ?? 0)")
|
||
XCTAssertTrue(app.otherElements[IDs.readerContent].waitForExistence(timeout: 5), "window=5 首开应可阅读")
|
||
}
|
||
|
||
func testWindowSize3MinimumWorks() throws {
|
||
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true, windowSize: 3)
|
||
app.waitForReader(timeout: 20)
|
||
|
||
let state = app.waitForDemoReaderState(timeout: 15, description: "window=3 最小窗口") { state in
|
||
state.mode == "bookPageMap" && state.knownChapters != nil
|
||
}
|
||
|
||
XCTAssertEqual(state.windowSize, 3, "windowSize 应为 3")
|
||
XCTAssertTrue(app.otherElements[IDs.readerContent].waitForExistence(timeout: 5), "window=3 应可阅读")
|
||
}
|
||
|
||
func testWindowSize15MaximumWorks() throws {
|
||
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true, windowSize: 15)
|
||
app.waitForReader(timeout: 20)
|
||
|
||
let state = app.waitForDemoReaderState(timeout: 15, description: "window=15 最大窗口") { state in
|
||
state.mode == "bookPageMap" && state.knownChapters != nil
|
||
}
|
||
|
||
XCTAssertEqual(state.windowSize, 15, "windowSize 应为 15")
|
||
XCTAssertTrue((state.knownChapters ?? 0) >= 3, "window=15 首开应预加载多章")
|
||
XCTAssertTrue(app.otherElements[IDs.readerContent].waitForExistence(timeout: 5), "window=15 应可阅读")
|
||
}
|
||
|
||
func testWindowSize5ChapterNavigationSmooth() throws {
|
||
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true, windowSize: 5)
|
||
app.waitForReader(timeout: 20)
|
||
|
||
_ = app.waitForDemoReaderState(timeout: 15, description: "等待首章加载") { state in
|
||
state.mode == "bookPageMap" && (state.page ?? 0) >= 1
|
||
}
|
||
|
||
let paging = app.collectionViews[IDs.readerPaging]
|
||
XCTAssertTrue(paging.waitForExistence(timeout: 5), "分页视图不存在")
|
||
|
||
// 快速连续翻页,window=5 应该有更多章在缓存中
|
||
for _ in 0..<6 {
|
||
paging.swipeLeft()
|
||
RunLoop.current.run(until: Date().addingTimeInterval(0.3))
|
||
}
|
||
|
||
let afterSwipe = app.currentDemoReaderState()
|
||
XCTAssertNotNil(afterSwipe, "翻页后应能读取状态")
|
||
XCTAssertTrue((afterSwipe?.page ?? 0) > 1, "连续翻页后页码应推进")
|
||
}
|
||
|
||
func testEvenWindowSizeRoundsUp() throws {
|
||
// 传 4 应被归一化为 5
|
||
app.launchAndOpenSampleBook(bookTitleQuery: largeBookQuery, resetsReaderState: true, windowSize: 4)
|
||
app.waitForReader(timeout: 20)
|
||
|
||
let state = app.waitForDemoReaderState(timeout: 15, description: "偶数窗口向上取奇") { state in
|
||
state.mode == "bookPageMap" && state.windowSize != nil
|
||
}
|
||
|
||
XCTAssertEqual(state.windowSize, 5, "windowSize=4 应被归一化为 5")
|
||
}
|
||
}
|