Add tests for space manage rooms mode

This commit is contained in:
ganfra
2026-01-14 17:47:20 +01:00
parent bd58e07220
commit efdf2020fe
3 changed files with 356 additions and 1 deletions

View File

@@ -22,16 +22,18 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomType
import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import io.element.android.libraries.matrix.api.spaces.SpaceService
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
import io.element.android.libraries.matrix.test.A_ROOM_ID_3
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
@@ -355,6 +357,213 @@ class SpacePresenterTest {
}
}
@Test
fun `present - enter manage mode`() = runTest {
val presenter = createSpacePresenter()
presenter.test {
val state = awaitItem()
assertThat(state.isManageMode).isFalse()
state.eventSink(SpaceEvents.EnterManageMode)
val manageModeState = awaitItem()
assertThat(manageModeState.isManageMode).isTrue()
assertThat(manageModeState.selectedRoomIds).isEmpty()
}
}
@Test
fun `present - exit manage mode clears selection`() = runTest {
val presenter = createSpacePresenter()
presenter.test {
val initialState = awaitItem()
initialState.eventSink(SpaceEvents.EnterManageMode)
initialState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
initialState.eventSink(SpaceEvents.ExitManageMode)
val finalState = expectMostRecentItem()
assertThat(finalState.isManageMode).isFalse()
assertThat(finalState.selectedRoomIds).isEmpty()
}
}
@Test
fun `present - toggle room selection`() = runTest {
val presenter = createSpacePresenter()
presenter.test {
val initialState = awaitItem()
initialState.eventSink(SpaceEvents.EnterManageMode)
// Select a room
initialState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
var latestState = expectMostRecentItem()
assertThat(latestState.selectedRoomIds).containsExactly(A_ROOM_ID)
// Deselect the room
latestState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
latestState = expectMostRecentItem()
assertThat(latestState.selectedRoomIds).isEmpty()
}
}
@Test
fun `present - remove rooms success`() = runTest {
val removeChildFromSpaceResult = lambdaRecorder<RoomId, RoomId, Result<Unit>> { _, _ ->
Result.success(Unit)
}
val aRoom = aSpaceRoom(
roomId = A_ROOM_ID,
roomType = RoomType.Room,
)
val fakeSpaceRoomList = FakeSpaceRoomList(
initialSpaceRoomsValue = listOf(aRoom),
paginateResult = { Result.success(Unit) },
)
val presenter = createSpacePresenter(
spaceRoomList = fakeSpaceRoomList,
spaceService = FakeSpaceService(
removeChildFromSpaceResult = removeChildFromSpaceResult,
),
)
presenter.test {
awaitItem() // Initial empty state
advanceUntilIdle()
val stateWithChildren = awaitItem()
assertThat(stateWithChildren.children).hasSize(1)
stateWithChildren.eventSink(SpaceEvents.EnterManageMode)
stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
stateWithChildren.eventSink(SpaceEvents.RemoveSelectedRooms)
stateWithChildren.eventSink(SpaceEvents.ConfirmRoomRemoval)
advanceUntilIdle()
val successState = expectMostRecentItem()
assertThat(successState.removeRoomsAction).isEqualTo(AsyncAction.Success(Unit))
assertThat(successState.isManageMode).isFalse()
assertThat(successState.children).isEmpty()
removeChildFromSpaceResult.assertions().isCalledOnce()
}
}
@Test
fun `present - remove rooms partial failure`() = runTest {
val aRoom1 = aSpaceRoom(
roomId = A_ROOM_ID,
roomType = RoomType.Room,
)
val aRoom2 = aSpaceRoom(
roomId = A_ROOM_ID_2,
roomType = RoomType.Room,
)
val removeChildFromSpaceResult = lambdaRecorder<RoomId, RoomId, Result<Unit>> { _, childId ->
if (childId == A_ROOM_ID_2) Result.failure(AN_EXCEPTION)
else Result.success(Unit)
}
val fakeSpaceRoomList = FakeSpaceRoomList(
initialSpaceRoomsValue = listOf(aRoom1, aRoom2),
paginateResult = { Result.success(Unit) },
)
val presenter = createSpacePresenter(
spaceRoomList = fakeSpaceRoomList,
spaceService = FakeSpaceService(
removeChildFromSpaceResult = removeChildFromSpaceResult,
),
)
presenter.test {
awaitItem() // Initial empty state
advanceUntilIdle()
val stateWithChildren = awaitItem()
assertThat(stateWithChildren.children).hasSize(2)
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()
// Successfully removed room should be filtered out
assertThat(failureState.children.map { it.roomId }).doesNotContain(A_ROOM_ID)
// Failed room should still be present
assertThat(failureState.children.map { it.roomId }).contains(A_ROOM_ID_2)
removeChildFromSpaceResult.assertions().isCalledExactly(2)
}
}
@Test
fun `present - children filtered in manage mode shows only rooms`() = runTest {
val aRoom = aSpaceRoom(
roomId = A_ROOM_ID,
roomType = RoomType.Room,
)
val aSubSpace = aSpaceRoom(
roomId = A_ROOM_ID_2,
roomType = RoomType.Space,
)
val fakeSpaceRoomList = FakeSpaceRoomList(
initialSpaceRoomsValue = listOf(aRoom, aSubSpace),
paginateResult = { Result.success(Unit) },
)
val presenter = createSpacePresenter(spaceRoomList = fakeSpaceRoomList)
presenter.test {
awaitItem() // Initial empty state
advanceUntilIdle()
val stateWithChildren = awaitItem()
// Both room and space visible initially
assertThat(stateWithChildren.children).hasSize(2)
assertThat(stateWithChildren.isManageMode).isFalse()
stateWithChildren.eventSink(SpaceEvents.EnterManageMode)
val manageModeState = expectMostRecentItem()
// Only rooms visible in manage mode
assertThat(manageModeState.children).hasSize(1)
assertThat(manageModeState.children.first().roomId).isEqualTo(A_ROOM_ID)
assertThat(manageModeState.children.first().isSpace).isFalse()
}
}
@Test
fun `present - removed rooms persist after flow update`() = runTest {
val removeChildFromSpaceResult = lambdaRecorder<RoomId, RoomId, Result<Unit>> { _, _ ->
Result.success(Unit)
}
val aRoom1 = aSpaceRoom(
roomId = A_ROOM_ID,
roomType = RoomType.Room,
)
val aRoom2 = aSpaceRoom(
roomId = A_ROOM_ID_2,
roomType = RoomType.Room,
)
val aRoom3 = aSpaceRoom(
roomId = A_ROOM_ID_3,
roomType = RoomType.Room,
)
val spaceRoomList = FakeSpaceRoomList(
initialSpaceRoomsValue = listOf(aRoom1, aRoom2),
paginateResult = { Result.success(Unit) },
)
val presenter = createSpacePresenter(
spaceRoomList = spaceRoomList,
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.RemoveSelectedRooms)
stateWithChildren.eventSink(SpaceEvents.ConfirmRoomRemoval)
advanceUntilIdle()
val successState = expectMostRecentItem()
assertThat(successState.children.map { it.roomId }).doesNotContain(A_ROOM_ID)
// Emit new flow update with a new room added (simulating server refresh)
spaceRoomList.emitSpaceRooms(listOf(aRoom1, aRoom2, aRoom3))
advanceUntilIdle()
val afterFlowUpdate = awaitItem()
// A_ROOM_ID should still be filtered out even though it's in the new emission
assertThat(afterFlowUpdate.children.map { it.roomId }).doesNotContain(A_ROOM_ID)
// But the other rooms should be present
assertThat(afterFlowUpdate.children.map { it.roomId }).contains(A_ROOM_ID_2)
assertThat(afterFlowUpdate.children.map { it.roomId }).contains(A_ROOM_ID_3)
}
}
private fun TestScope.createSpacePresenter(
client: MatrixClient = FakeMatrixClient(),
room: BaseRoom = FakeBaseRoom(),

View File

@@ -14,6 +14,8 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
import io.element.android.libraries.matrix.test.A_ROOM_ID_3
import io.element.android.libraries.matrix.api.room.RoomType
import io.element.android.libraries.previewutils.room.aSpaceRoom
import org.junit.Test
class SpaceStateTest {
@@ -45,4 +47,80 @@ class SpaceStateTest {
)
assertThat(state.isJoining(A_ROOM_ID)).isTrue()
}
@Test
fun `test isSelected returns true for selected room`() {
val state = aSpaceState(
selectedRoomIds = setOf(A_ROOM_ID)
)
assertThat(state.isSelected(A_ROOM_ID)).isTrue()
}
@Test
fun `test isSelected returns false for non-selected room`() {
val state = aSpaceState(
selectedRoomIds = setOf(A_ROOM_ID)
)
assertThat(state.isSelected(A_ROOM_ID_2)).isFalse()
}
@Test
fun `test showManageRoomsAction true when canManageRooms and has room children`() {
val state = aSpaceState(
canManageRooms = true,
children = listOf(aSpaceRoom(roomType = RoomType.Room))
)
assertThat(state.showManageRoomsAction).isTrue()
}
@Test
fun `test showManageRoomsAction false when canManageRooms but children empty`() {
val state = aSpaceState(
canManageRooms = true,
children = emptyList()
)
assertThat(state.showManageRoomsAction).isFalse()
}
@Test
fun `test showManageRoomsAction false when canManageRooms but only space children`() {
val state = aSpaceState(
canManageRooms = true,
children = listOf(aSpaceRoom(roomType = RoomType.Space))
)
assertThat(state.showManageRoomsAction).isFalse()
}
@Test
fun `test showManageRoomsAction false when has room children but canManageRooms false`() {
val state = aSpaceState(
canManageRooms = false,
children = listOf(aSpaceRoom(roomType = RoomType.Room))
)
assertThat(state.showManageRoomsAction).isFalse()
}
@Test
fun `test selectedCount returns correct count`() {
val state = aSpaceState(
selectedRoomIds = setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)
)
assertThat(state.selectedCount).isEqualTo(3)
}
@Test
fun `test isRemoveButtonEnabled true when selectedRoomIds not empty`() {
val state = aSpaceState(
selectedRoomIds = setOf(A_ROOM_ID)
)
assertThat(state.isRemoveButtonEnabled).isTrue()
}
@Test
fun `test isRemoveButtonEnabled false when selectedRoomIds empty`() {
val state = aSpaceState(
selectedRoomIds = emptySet()
)
assertThat(state.isRemoveButtonEnabled).isFalse()
}
}

View File

@@ -12,9 +12,11 @@ import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.matrix.test.A_ROOM_ID
@@ -29,6 +31,7 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressBackKey
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
@@ -132,6 +135,71 @@ class SpaceViewTest {
rule.onNodeWithText(A_ROOM_TOPIC).performClick()
eventsRecorder.assertSingle(SpaceEvents.ShowTopicViewer(A_ROOM_TOPIC))
}
@Test
fun `clicking back in manage mode emits ExitManageMode event`() {
val eventsRecorder = EventsRecorder<SpaceEvents>()
rule.setSpaceView(
aSpaceState(
hasMoreToLoad = false,
isManageMode = true,
eventSink = eventsRecorder,
)
)
rule.pressBackKey()
eventsRecorder.assertSingle(SpaceEvents.ExitManageMode)
}
@Test
fun `clicking on room in manage mode emits ToggleRoomSelection event`() {
val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, displayName = A_ROOM_NAME)
val eventsRecorder = EventsRecorder<SpaceEvents>()
rule.setSpaceView(
aSpaceState(
children = listOf(aSpaceRoom),
hasMoreToLoad = false,
isManageMode = true,
eventSink = eventsRecorder,
)
)
rule.onNodeWithText(A_ROOM_NAME).performClick()
eventsRecorder.assertSingle(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
}
@Test
fun `clicking remove button emits RemoveSelectedRooms event`() {
val eventsRecorder = EventsRecorder<SpaceEvents>()
rule.setSpaceView(
aSpaceState(
children = listOf(aSpaceRoom(roomId = A_ROOM_ID)),
hasMoreToLoad = false,
isManageMode = true,
selectedRoomIds = setOf(A_ROOM_ID),
eventSink = eventsRecorder,
)
)
rule.clickOn(CommonStrings.action_remove)
eventsRecorder.assertSingle(SpaceEvents.RemoveSelectedRooms)
}
@Config(qualifiers = "h1024dp")
@Test
fun `clicking confirm in removal dialog emits ConfirmRoomRemoval event`() {
val eventsRecorder = EventsRecorder<SpaceEvents>()
rule.setSpaceView(
aSpaceState(
children = listOf(aSpaceRoom(roomId = A_ROOM_ID)),
hasMoreToLoad = false,
isManageMode = true,
selectedRoomIds = setOf(A_ROOM_ID),
removeRoomsAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder,
)
)
// Click on the Remove button in the confirmation dialog
rule.clickOn(CommonStrings.action_remove, inDialog = true)
eventsRecorder.assertSingle(SpaceEvents.ConfirmRoomRemoval)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceView(