完善搜索定位与内存监测回归验证

本次提交围绕搜索链路的稳定性、定位恢复体验以及长章节内存优化的验证能力进行了补强。

主要改动:

1. 调整阅读器搜索栏、搜索协调器与定位恢复逻辑,改善搜索结果跳转、状态同步与相关 UI 行为。

2. 补充内存探针接入点与上下文记录,便于跟踪阅读过程中的内存占用变化。

3. 更新 RDURLReaderController、ReaderContext 与工具视图相关实现,使调试与观测链路更完整。

4. 新增 MemoryFootprintTests,并同步更新 DemoReaderState 与 SearchTests,用 UI 测试覆盖搜索与内存相关回归场景。
This commit is contained in:
shenlei
2026-07-09 18:46:04 +09:00
parent c4be426299
commit 83dfa40299
11 changed files with 338 additions and 89 deletions
@@ -209,7 +209,9 @@ final class SearchTests: XCTestCase {
XCTAssertFalse(searchBar.waitForExistence(timeout: 2), "关闭后搜索栏应消失")
}
// MARK: -
// MARK: -
// chrome
// chrome /
func testSearchBarHidesWhenToolbarsHide() {
app.launchAndOpenSampleBook(searchKeyword: "")
@@ -217,12 +219,15 @@ final class SearchTests: XCTestCase {
app.showReaderChromeIfNeeded()
let searchBar = app.otherElements[IDs.searchBar]
XCTAssertTrue(searchBar.waitForExistence(timeout: 10), "搜索应可见")
XCTAssertTrue(searchBar.waitForExistence(timeout: 10), "搜索面板应可见")
//
//
selectSearchResult(at: 0)
XCTAssertFalse(searchBar.waitForExistence(timeout: 2), "选中结果后搜索面板应关闭")
//
app.hideReaderChromeIfNeeded()
XCTAssertFalse(searchBar.waitForExistence(timeout: 2), "工具栏隐藏时搜索栏也应隐藏")
XCTAssertFalse(searchBar.waitForExistence(timeout: 2), "工具栏隐藏时搜索面板应保持关闭")
}
func testSearchBarRestoresWhenToolbarsShow() {
@@ -233,13 +238,14 @@ final class SearchTests: XCTestCase {
let searchBar = app.otherElements[IDs.searchBar]
XCTAssertTrue(searchBar.waitForExistence(timeout: 10))
//
//
selectSearchResult(at: 0)
app.hideReaderChromeIfNeeded()
XCTAssertFalse(searchBar.waitForExistence(timeout: 2))
//
//
app.showReaderChromeIfNeeded()
XCTAssertTrue(searchBar.waitForExistence(timeout: 3), "工具栏恢复时搜索也应恢复")
XCTAssertTrue(searchBar.waitForExistence(timeout: 3), "工具栏恢复时搜索面板也应恢复")
//
let countLabel = app.staticTexts[IDs.searchCount]
@@ -286,6 +292,9 @@ final class SearchTests: XCTestCase {
}
let rangeCFIBefore = stateBefore.rangeCFI
//
selectSearchResult(at: 0)
//
app.showReaderChromeIfNeeded()
let settingsButton = app.buttons[IDs.readerSettings]
@@ -304,8 +313,8 @@ final class SearchTests: XCTestCase {
}
Thread.sleep(forTimeInterval: 1)
//
app.showReaderChromeIfNeeded()
//
reopenSearchPanel()
let prevButton = app.buttons[IDs.searchPrevious]
let nextButton = app.buttons[IDs.searchNext]
if prevButton.waitForExistence(timeout: 3), prevButton.isEnabled {
@@ -349,6 +358,9 @@ final class SearchTests: XCTestCase {
return
}
//
selectSearchResult(at: 0)
//
app.showReaderChromeIfNeeded()
let settingsButton = app.buttons[IDs.readerSettings]
@@ -367,15 +379,9 @@ final class SearchTests: XCTestCase {
}
Thread.sleep(forTimeInterval: 1)
//
let nextButton = app.buttons[IDs.searchNext]
let prevButton = app.buttons[IDs.searchPrevious]
XCTAssertTrue(nextButton.waitForExistence(timeout: 3), "下一个按钮应存在")
XCTAssertTrue(prevButton.waitForExistence(timeout: 3), "上一个按钮应存在")
// 2
nextButton.tap()
Thread.sleep(forTimeInterval: 0.5)
// 2
reopenSearchPanel()
selectSearchResult(at: 1)
let stateMatch2 = app.waitForDemoReaderState(timeout: 5, description: "match 2 page") {
($0.page ?? 0) > 0 && $0.rangeCFI?.isEmpty == false
}
@@ -383,18 +389,11 @@ final class SearchTests: XCTestCase {
let rangeCFIAtMatch2 = stateMatch2.rangeCFI
//
let content = app.otherElements[IDs.readerContent].firstMatch
if content.waitForExistence(timeout: 3) {
for _ in 0..<4 {
content.swipeLeft()
Thread.sleep(forTimeInterval: 0.3)
}
Thread.sleep(forTimeInterval: 0.5)
}
swipeAwayFromCurrentPage()
// 3
nextButton.tap()
Thread.sleep(forTimeInterval: 0.5)
// 3
reopenSearchPanel()
selectSearchResult(at: 2)
let stateMatch3 = app.waitForDemoReaderState(timeout: 5, description: "match 3 page") {
($0.page ?? 0) > 0 && $0.rangeCFI?.isEmpty == false
}
@@ -402,17 +401,11 @@ final class SearchTests: XCTestCase {
let rangeCFIAtMatch3 = stateMatch3.rangeCFI
//
if content.waitForExistence(timeout: 3) {
for _ in 0..<4 {
content.swipeLeft()
Thread.sleep(forTimeInterval: 0.3)
}
Thread.sleep(forTimeInterval: 0.5)
}
swipeAwayFromCurrentPage()
// 2
prevButton.tap()
Thread.sleep(forTimeInterval: 0.5)
reopenSearchPanel()
selectSearchResult(at: 1)
let stateBack2 = app.waitForDemoReaderState(timeout: 5, description: "back to match 2") {
$0.page == pageAtMatch2 && $0.rangeCFI == rangeCFIAtMatch2
}
@@ -422,17 +415,11 @@ final class SearchTests: XCTestCase {
"重排后搜索跳回第 2 个匹配应命中同一段文本")
//
if content.waitForExistence(timeout: 3) {
for _ in 0..<4 {
content.swipeLeft()
Thread.sleep(forTimeInterval: 0.3)
}
Thread.sleep(forTimeInterval: 0.5)
}
swipeAwayFromCurrentPage()
// 3
nextButton.tap()
Thread.sleep(forTimeInterval: 0.5)
reopenSearchPanel()
selectSearchResult(at: 2)
let stateBack3 = app.waitForDemoReaderState(timeout: 5, description: "back to match 3") {
$0.page == pageAtMatch3 && $0.rangeCFI == rangeCFIAtMatch3
}
@@ -452,4 +439,39 @@ final class SearchTests: XCTestCase {
let searchBar = app.otherElements[IDs.searchBar]
XCTAssertTrue(searchBar.waitForExistence(timeout: 3), "搜索栏应出现")
}
///
private func selectSearchResult(at matchIndex: Int) {
let cell = app.cells["epub.reader.search.result.\(matchIndex)"].firstMatch
XCTAssertTrue(cell.waitForExistence(timeout: 10), "搜索结果第 \(matchIndex) 项应存在")
cell.tap()
RunLoop.current.run(until: Date().addingTimeInterval(0.5))
}
/// chrome
/// chrome
private func reopenSearchPanel() {
app.showReaderChromeIfNeeded()
let searchBar = app.otherElements[IDs.searchBar]
if searchBar.waitForExistence(timeout: 2) {
return
}
let searchButton = app.buttons[IDs.readerSearch]
XCTAssertTrue(searchButton.waitForExistence(timeout: 3), "搜索按钮应存在")
searchButton.tap()
XCTAssertTrue(searchBar.waitForExistence(timeout: 3), "搜索面板应重新打开")
}
/// chrome 使
private func swipeAwayFromCurrentPage() {
app.hideReaderChromeIfNeeded()
let content = app.otherElements[IDs.readerContent].firstMatch
if content.waitForExistence(timeout: 3) {
for _ in 0..<4 {
content.swipeLeft()
Thread.sleep(forTimeInterval: 0.3)
}
Thread.sleep(forTimeInterval: 0.5)
}
}
}