Call spaceRoomList.reset when exiting add/remove room flow
This commit is contained in:
@@ -14,4 +14,5 @@ sealed interface AddRoomToSpaceEvent {
|
|||||||
data class OnSearchActiveChanged(val active: Boolean) : AddRoomToSpaceEvent
|
data class OnSearchActiveChanged(val active: Boolean) : AddRoomToSpaceEvent
|
||||||
data object Save : AddRoomToSpaceEvent
|
data object Save : AddRoomToSpaceEvent
|
||||||
data object ResetSaveAction : AddRoomToSpaceEvent
|
data object ResetSaveAction : AddRoomToSpaceEvent
|
||||||
|
data object Dismiss : AddRoomToSpaceEvent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class AddRoomToSpaceNode(
|
|||||||
val state by stateFlow.collectAsState()
|
val state by stateFlow.collectAsState()
|
||||||
AddRoomToSpaceView(
|
AddRoomToSpaceView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = ::navigateUp,
|
onBackClick = callback::onFinish,
|
||||||
onRoomsAdded = callback::onFinish,
|
onRoomsAdded = callback::onFinish,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import io.element.android.libraries.designsystem.theme.components.SearchBarResul
|
|||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
||||||
import io.element.android.libraries.matrix.api.spaces.resetAndWaitForFullReload
|
|
||||||
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@@ -50,6 +49,8 @@ class AddRoomToSpacePresenter(
|
|||||||
val searchQuery = rememberTextFieldState()
|
val searchQuery = rememberTextFieldState()
|
||||||
var isSearchActive by remember { mutableStateOf(false) }
|
var isSearchActive by remember { mutableStateOf(false) }
|
||||||
val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||||
|
// Track whether any rooms were added (for conditional reset on Dismiss)
|
||||||
|
var hasAddedRooms by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val dataSource = remember { dataSourceFactory.create(coroutineScope) }
|
val dataSource = remember { dataSourceFactory.create(coroutineScope) }
|
||||||
@@ -96,6 +97,9 @@ class AddRoomToSpacePresenter(
|
|||||||
dataSource = dataSource,
|
dataSource = dataSource,
|
||||||
saveAction = saveAction,
|
saveAction = saveAction,
|
||||||
onPartialSuccess = { successfullyAdded ->
|
onPartialSuccess = { successfullyAdded ->
|
||||||
|
if (successfullyAdded.isNotEmpty()) {
|
||||||
|
hasAddedRooms = true
|
||||||
|
}
|
||||||
selectedRooms = selectedRooms.filterNot { it.roomId in successfullyAdded }.toImmutableList()
|
selectedRooms = selectedRooms.filterNot { it.roomId in successfullyAdded }.toImmutableList()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -103,6 +107,11 @@ class AddRoomToSpacePresenter(
|
|||||||
AddRoomToSpaceEvent.ResetSaveAction -> {
|
AddRoomToSpaceEvent.ResetSaveAction -> {
|
||||||
saveAction.value = AsyncAction.Uninitialized
|
saveAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
|
AddRoomToSpaceEvent.Dismiss -> {
|
||||||
|
if (hasAddedRooms) {
|
||||||
|
coroutineScope.launch { spaceRoomList.reset() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ fun AddRoomToSpaceView(
|
|||||||
if (state.isSearchActive) {
|
if (state.isSearchActive) {
|
||||||
state.eventSink(AddRoomToSpaceEvent.OnSearchActiveChanged(false))
|
state.eventSink(AddRoomToSpaceEvent.OnSearchActiveChanged(false))
|
||||||
} else {
|
} else {
|
||||||
|
state.eventSink(AddRoomToSpaceEvent.Dismiss)
|
||||||
onBackClick()
|
onBackClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import kotlinx.collections.immutable.toImmutableSet
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -136,6 +135,16 @@ class SpacePresenter(
|
|||||||
|
|
||||||
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
|
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
|
||||||
|
|
||||||
|
suspend fun exitManageMode(shouldReset: Boolean) {
|
||||||
|
isManageMode = false
|
||||||
|
selectedRoomIds = emptySet()
|
||||||
|
removedRoomIds = emptySet()
|
||||||
|
if (shouldReset) {
|
||||||
|
// Reset the space room list to see the updates.
|
||||||
|
spaceRoomList.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvent(event: SpaceEvents) {
|
fun handleEvent(event: SpaceEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
// SpaceRoomList is loaded automatically as backend is really slow. Event is kept for future.
|
// SpaceRoomList is loaded automatically as backend is really slow. Event is kept for future.
|
||||||
@@ -168,8 +177,7 @@ class SpacePresenter(
|
|||||||
selectedRoomIds = emptySet()
|
selectedRoomIds = emptySet()
|
||||||
}
|
}
|
||||||
SpaceEvents.ExitManageMode -> {
|
SpaceEvents.ExitManageMode -> {
|
||||||
isManageMode = false
|
localCoroutineScope.launch { exitManageMode(shouldReset = removedRoomIds.isNotEmpty()) }
|
||||||
selectedRoomIds = emptySet()
|
|
||||||
}
|
}
|
||||||
is SpaceEvents.ToggleRoomSelection -> {
|
is SpaceEvents.ToggleRoomSelection -> {
|
||||||
selectedRoomIds = if (event.roomId in selectedRoomIds) {
|
selectedRoomIds = if (event.roomId in selectedRoomIds) {
|
||||||
@@ -202,10 +210,7 @@ class SpacePresenter(
|
|||||||
removeRoomsAction = AsyncAction.Failure(Exception("Failed to remove some rooms"))
|
removeRoomsAction = AsyncAction.Failure(Exception("Failed to remove some rooms"))
|
||||||
} else {
|
} else {
|
||||||
removeRoomsAction = AsyncAction.Success(Unit)
|
removeRoomsAction = AsyncAction.Success(Unit)
|
||||||
isManageMode = false
|
exitManageMode(shouldReset = true)
|
||||||
selectedRoomIds = emptySet()
|
|
||||||
// Reset the space room list to see the updates.
|
|
||||||
spaceRoomList.reset()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.ui.components.aSelectRoomInfo
|
|||||||
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import io.element.android.tests.testutils.lambda.assert
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
@@ -278,6 +279,67 @@ class AddRoomToSpacePresenterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - Dismiss without additions does not call reset`() = runTest {
|
||||||
|
val resetResult = lambdaRecorder<Result<Unit>>(ensureNeverCalled = true) { Result.success(Unit) }
|
||||||
|
val spaceRoomList = FakeSpaceRoomList(
|
||||||
|
paginateResult = { Result.success(Unit) },
|
||||||
|
resetResult = resetResult,
|
||||||
|
)
|
||||||
|
val presenter = createAddRoomToSpacePresenter(spaceRoomList = spaceRoomList)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
state.eventSink(AddRoomToSpaceEvent.Dismiss)
|
||||||
|
advanceUntilIdle()
|
||||||
|
// reset should NOT be called since no rooms were added
|
||||||
|
assert(resetResult).isNeverCalled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - Dismiss after partial success calls reset`() = runTest {
|
||||||
|
val resetResult = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||||
|
val spaceRoomList = FakeSpaceRoomList(
|
||||||
|
paginateResult = { Result.success(Unit) },
|
||||||
|
resetResult = resetResult,
|
||||||
|
)
|
||||||
|
// Room 1 succeeds, Room 2 fails
|
||||||
|
val addChildToSpaceResult = lambdaRecorder<RoomId, RoomId, Result<Unit>> { _, childId ->
|
||||||
|
if (childId == A_ROOM_ID_2) {
|
||||||
|
Result.failure(AN_EXCEPTION)
|
||||||
|
} else {
|
||||||
|
Result.success(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val spaceService = FakeSpaceService(
|
||||||
|
addChildToSpaceResult = addChildToSpaceResult,
|
||||||
|
)
|
||||||
|
val presenter = createAddRoomToSpacePresenter(
|
||||||
|
spaceRoomList = spaceRoomList,
|
||||||
|
spaceService = spaceService,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
// Select two rooms
|
||||||
|
val room1 = aSelectRoomInfoList()[0]
|
||||||
|
val room2 = aSelectRoomInfoList()[1]
|
||||||
|
state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room1))
|
||||||
|
awaitItem()
|
||||||
|
state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room2))
|
||||||
|
awaitItem()
|
||||||
|
// Save - partial success (one room added, one failed)
|
||||||
|
state.eventSink(AddRoomToSpaceEvent.Save)
|
||||||
|
skipItems(1) // Loading
|
||||||
|
advanceUntilIdle()
|
||||||
|
val failureState = expectMostRecentItem()
|
||||||
|
assertThat(failureState.saveAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
|
// Dismiss after partial success - reset should be called
|
||||||
|
failureState.eventSink(AddRoomToSpaceEvent.Dismiss)
|
||||||
|
advanceUntilIdle()
|
||||||
|
assert(resetResult).isCalledOnce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun TestScope.createAddRoomToSpacePresenter(
|
private fun TestScope.createAddRoomToSpacePresenter(
|
||||||
spaceRoomList: FakeSpaceRoomList = FakeSpaceRoomList(
|
spaceRoomList: FakeSpaceRoomList = FakeSpaceRoomList(
|
||||||
paginateResult = { Result.success(Unit) },
|
paginateResult = { Result.success(Unit) },
|
||||||
|
|||||||
@@ -32,16 +32,19 @@ class AddRoomToSpaceViewTest {
|
|||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking back when search inactive invokes onBackClick`() {
|
fun `clicking back when search inactive emits Dismiss and invokes onBackClick`() {
|
||||||
|
val eventsRecorder = EventsRecorder<AddRoomToSpaceEvent>()
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setAddRoomToSpaceView(
|
rule.setAddRoomToSpaceView(
|
||||||
anAddRoomToSpaceState(
|
anAddRoomToSpaceState(
|
||||||
isSearchActive = false,
|
isSearchActive = false,
|
||||||
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onBackClick = it,
|
onBackClick = it,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
rule.pressBack()
|
||||||
}
|
}
|
||||||
|
eventsRecorder.assertSingle(AddRoomToSpaceEvent.Dismiss)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -350,16 +350,24 @@ class SpacePresenterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - exit manage mode clears selection`() = runTest {
|
fun `present - exit manage mode without removals does not call reset`() = runTest {
|
||||||
val presenter = createSpacePresenter()
|
val resetResult = lambdaRecorder<Result<Unit>>(ensureNeverCalled = true) { Result.success(Unit) }
|
||||||
|
val fakeSpaceRoomList = FakeSpaceRoomList(
|
||||||
|
paginateResult = { Result.success(Unit) },
|
||||||
|
resetResult = resetResult,
|
||||||
|
)
|
||||||
|
val presenter = createSpacePresenter(spaceRoomList = fakeSpaceRoomList)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(SpaceEvents.EnterManageMode)
|
initialState.eventSink(SpaceEvents.EnterManageMode)
|
||||||
initialState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
|
initialState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
|
||||||
initialState.eventSink(SpaceEvents.ExitManageMode)
|
initialState.eventSink(SpaceEvents.ExitManageMode)
|
||||||
|
advanceUntilIdle()
|
||||||
val finalState = expectMostRecentItem()
|
val finalState = expectMostRecentItem()
|
||||||
assertThat(finalState.isManageMode).isFalse()
|
assertThat(finalState.isManageMode).isFalse()
|
||||||
assertThat(finalState.selectedRoomIds).isEmpty()
|
assertThat(finalState.selectedRoomIds).isEmpty()
|
||||||
|
// reset should NOT be called since no rooms were actually removed
|
||||||
|
assert(resetResult).isNeverCalled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,6 +475,56 @@ class SpacePresenterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - exit manage mode after partial failure calls reset`() = runTest {
|
||||||
|
val aRoom1 = aSpaceRoom(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
roomType = RoomType.Room,
|
||||||
|
)
|
||||||
|
val aRoom2 = aSpaceRoom(
|
||||||
|
roomId = A_ROOM_ID_2,
|
||||||
|
roomType = RoomType.Room,
|
||||||
|
)
|
||||||
|
// Room 1 succeeds, Room 2 fails
|
||||||
|
val removeChildFromSpaceResult = lambdaRecorder<RoomId, RoomId, Result<Unit>> { _, childId ->
|
||||||
|
if (childId == A_ROOM_ID_2) {
|
||||||
|
Result.failure(AN_EXCEPTION)
|
||||||
|
} else {
|
||||||
|
Result.success(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val resetResult = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||||
|
val fakeSpaceRoomList = FakeSpaceRoomList(
|
||||||
|
initialSpaceRoomsValue = listOf(aRoom1, aRoom2),
|
||||||
|
paginateResult = { Result.success(Unit) },
|
||||||
|
resetResult = resetResult,
|
||||||
|
)
|
||||||
|
val presenter = createSpacePresenter(
|
||||||
|
spaceRoomList = fakeSpaceRoomList,
|
||||||
|
spaceService = FakeSpaceService(
|
||||||
|
removeChildFromSpaceResult = removeChildFromSpaceResult,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
awaitItem() // Initial empty state
|
||||||
|
advanceUntilIdle()
|
||||||
|
val stateWithChildren = awaitItem()
|
||||||
|
stateWithChildren.eventSink(SpaceEvents.EnterManageMode)
|
||||||
|
stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
|
||||||
|
stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID_2))
|
||||||
|
stateWithChildren.eventSink(SpaceEvents.RemoveSelectedRooms)
|
||||||
|
stateWithChildren.eventSink(SpaceEvents.ConfirmRoomRemoval)
|
||||||
|
advanceUntilIdle()
|
||||||
|
val failureState = expectMostRecentItem()
|
||||||
|
assertThat(failureState.removeRoomsAction.isFailure()).isTrue()
|
||||||
|
// Exit manage mode after partial failure - reset should be called
|
||||||
|
failureState.eventSink(SpaceEvents.ExitManageMode)
|
||||||
|
advanceUntilIdle()
|
||||||
|
expectMostRecentItem()
|
||||||
|
assert(resetResult).isCalledOnce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - children filtered in manage mode shows only rooms`() = runTest {
|
fun `present - children filtered in manage mode shows only rooms`() = runTest {
|
||||||
val aRoom = aSpaceRoom(
|
val aRoom = aSpaceRoom(
|
||||||
|
|||||||
Reference in New Issue
Block a user