Address PR comments

This commit is contained in:
Stefan Ceriu
2026-08-21 11:40:10 +03:00
parent 72d55e485a
commit 8a3468658b
5 changed files with 15 additions and 27 deletions
@@ -177,9 +177,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
updateRooms() updateRooms()
// The subscription above only delivers the current state through an async main queue
// hop, which can end up queued behind the first render and leave the skeletons up on a
// warm launch, where the provider has already published the cached rooms.
if let roomSummaryProvider { if let roomSummaryProvider {
updateRoomListMode(with: roomSummaryProvider.statePublisher.value, updateRoomListMode(with: roomSummaryProvider.statePublisher.value,
hasRooms: !roomSummaryProvider.roomListPublisher.value.isEmpty) hasRooms: !roomSummaryProvider.roomListPublisher.value.isEmpty)
@@ -337,19 +334,16 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
} }
private func updateRoomListMode(with roomSummaryProviderState: RoomSummaryProviderState, hasRooms: Bool) { private func updateRoomListMode(with roomSummaryProviderState: RoomSummaryProviderState, hasRooms: Bool) {
let isLoadingData = !roomSummaryProviderState.isLoaded let roomListMode: HomeScreenRoomListMode = if !roomSummaryProviderState.isLoaded {
let hasNoRooms = roomSummaryProviderState.isLoaded && roomSummaryProviderState.totalNumberOfRooms == 0 .skeletons // Still loading.
// Only the initial transition needs holding back until the rooms have published, an } else if roomSummaryProviderState.totalNumberOfRooms == 0 {
// empty list later on is a filter or a search result. .empty // Loaded, there are no rooms at all.
let isInitialLoad = state.roomListMode == .skeletons } else if hasRooms {
.rooms // Loaded and the summaries have published.
var roomListMode = state.roomListMode } else if state.roomListMode == .skeletons {
if isLoadingData { .skeletons // Loaded but nothing published yet, flipping to .rooms would flash an empty list.
roomListMode = .skeletons } else {
} else if hasNoRooms { .rooms
roomListMode = .empty
} else if hasRooms || !isInitialLoad {
roomListMode = .rooms
} }
guard roomListMode != state.roomListMode else { guard roomListMode != state.roomListMode else {
@@ -68,9 +68,6 @@ struct HomeScreenContent: View {
scrollViewAdapter.scrollView = scrollView scrollViewAdapter.scrollView = scrollView
} }
.onReceive(scrollViewAdapter.didScroll) { _ in .onReceive(scrollViewAdapter.didScroll) { _ in
// Live, undelayed updates while scrolling: pagination needs to see the
// range early enough to grow the list before the user reaches the bottom,
// and the scroll geometry is valid mid-scroll.
sendVisibleRange() sendVisibleRange()
} }
.onReceive(scrollViewAdapter.isScrolling) { _ in .onReceive(scrollViewAdapter.isScrolling) { _ in
+1 -2
View File
@@ -151,8 +151,7 @@ struct RunTests: AsyncParsableCommand {
// -test-iterations is the total number of attempts (initial + retries) // -test-iterations is the total number of attempts (initial + retries)
command += " -retry-tests-on-failure" command += " -retry-tests-on-failure"
command += " -test-iterations \(retries + 1)" command += " -test-iterations \(retries + 1)"
// Repetitions happen in the same process by default, so a runner that exits (e.g. code 75 // Ensure retries still happen when a runner exits early.
// after a failed app launch) takes its own retries down with it and the test gets skipped.
command += " -test-repetition-relaunch-enabled YES" command += " -test-repetition-relaunch-enabled YES"
} }
@@ -399,9 +399,9 @@ final class HomeScreenViewModelTests {
#expect(context.viewState.roomListMode == .skeletons) #expect(context.viewState.roomListMode == .skeletons)
// The provider reports loaded before the first summaries have been published. // The provider reports loaded before the first summaries have been published.
let failure = deferFailure(context.$viewState, timeout: .seconds(1)) { $0.roomListMode != .skeletons }
stateSubject.send(.loaded(totalNumberOfRooms: 8)) stateSubject.send(.loaded(totalNumberOfRooms: 8))
try await Task.sleep(for: .milliseconds(100)) try await failure.fulfill()
#expect(context.viewState.roomListMode == .skeletons)
let deferred = deferFulfillment(context.$viewState) { $0.roomListMode == .rooms } let deferred = deferFulfillment(context.$viewState) { $0.roomListMode == .rooms }
roomListSubject.send(.mockRooms) roomListSubject.send(.mockRooms)
@@ -418,10 +418,10 @@ final class HomeScreenViewModelTests {
try await deferred.fulfill() try await deferred.fulfill()
// A filter or a search without matches empties the list, the mode must not regress. // A filter or a search without matches empties the list, the mode must not regress.
let failure = deferFailure(context.$viewState, timeout: .seconds(1)) { $0.roomListMode != .rooms }
roomListSubject.send([]) roomListSubject.send([])
stateSubject.send(.loaded(totalNumberOfRooms: 9)) stateSubject.send(.loaded(totalNumberOfRooms: 9))
try await Task.sleep(for: .milliseconds(100)) try await failure.fulfill()
#expect(context.viewState.roomListMode == .rooms)
} }
private func setupViewModelWithManualProvider() -> (CurrentValueSubject<[RoomSummary], Never>, CurrentValueSubject<RoomSummaryProviderState, Never>) { private func setupViewModelWithManualProvider() -> (CurrentValueSubject<[RoomSummary], Never>, CurrentValueSubject<RoomSummaryProviderState, Never>) {
@@ -271,8 +271,6 @@ struct RoomDetailsScreenViewModelTests {
#expect(context.viewState.dmRecipientInfo?.member == RoomMemberDetails(withProxy: recipient)) #expect(context.viewState.dmRecipientInfo?.member == RoomMemberDetails(withProxy: recipient))
#expect(!context.viewState.isProcessingIgnoreRequest) #expect(!context.viewState.isProcessingIgnoreRequest)
// Waiting on the transient isProcessingIgnoreRequest transition races the view model's task,
// which can complete before the observation starts. Wait on the alert instead.
let deferredAlert = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil } let deferredAlert = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil }
context.send(viewAction: .unignoreConfirmed) context.send(viewAction: .unignoreConfirmed)