ReadViewSDK/ReadViewDemo/ReadViewDemoUITests/ReaderUITests/ConfigurableWindowTests.swift
shen d20196ee34 feat: configurable chapter window & parallel metadata parsing with benchmark
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)
2026-06-03 23:38:11 +08:00

83 lines
3.6 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 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")
}
}