Merge pull request #7369 from hayaksi1/fix/6130-space-left-room-joinable

Hide space children you have left that can only be joined by invitation
This commit is contained in:
Benoit Marty
2026-08-11 17:17:55 +02:00
committed by GitHub
2 changed files with 84 additions and 3 deletions
@@ -35,6 +35,7 @@ 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.join.JoinRoom
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
@@ -117,12 +118,14 @@ class SpacePresenter(
val filteredChildren by remember {
derivedStateOf {
val notRemoved = children.filterNot { it.roomId in removedRoomIds }
val visibleChildren = children
.filterNot { it.roomId in removedRoomIds }
.filterNot { it.cannotBeRejoined() }
if (isManageMode) {
// In manage mode, only show rooms (not spaces)
notRemoved.filter { !it.isSpace }.toImmutableList()
visibleChildren.filter { !it.isSpace }.toImmutableList()
} else {
notRemoved.toImmutableList()
visibleChildren.toImmutableList()
}
}
}
@@ -254,3 +257,7 @@ class SpacePresenter(
}
}
}
private fun SpaceRoom.cannotBeRejoined(): Boolean {
return state == CurrentUserMembership.LEFT && joinRule == JoinRule.Invite
}
@@ -30,11 +30,13 @@ 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.room.join.JoinRule
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
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.A_ROOM_ID_4
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
@@ -537,6 +539,78 @@ class SpacePresenterTest {
}
}
@Test
fun `present - left children which can only be joined by invitation are filtered out`() = runTest {
val leftInviteOnlyRoom = aSpaceRoom(
roomId = A_ROOM_ID,
roomType = RoomType.Room,
state = CurrentUserMembership.LEFT,
joinRule = JoinRule.Invite,
)
val leftPublicRoom = aSpaceRoom(
roomId = A_ROOM_ID_2,
roomType = RoomType.Room,
state = CurrentUserMembership.LEFT,
joinRule = JoinRule.Public,
)
val leftKnockRoom = aSpaceRoom(
roomId = A_ROOM_ID_3,
roomType = RoomType.Room,
state = CurrentUserMembership.LEFT,
joinRule = JoinRule.Knock,
)
val leftRoomWithUnknownJoinRule = aSpaceRoom(
roomId = A_ROOM_ID_4,
roomType = RoomType.Room,
state = CurrentUserMembership.LEFT,
joinRule = null,
)
val fakeSpaceRoomList = FakeSpaceRoomList(
initialSpaceRoomsValue = listOf(leftInviteOnlyRoom, leftPublicRoom, leftKnockRoom, leftRoomWithUnknownJoinRule),
paginateResult = { Result.success(Unit) },
)
val presenter = createSpacePresenter(spaceRoomList = fakeSpaceRoomList)
presenter.test {
awaitItem()
advanceUntilIdle()
val stateWithChildren = expectMostRecentItem()
assertThat(stateWithChildren.children).containsExactly(leftPublicRoom, leftKnockRoom, leftRoomWithUnknownJoinRule)
}
}
@Test
fun `present - children which can only be joined by invitation are kept when not left`() = runTest {
val joinedRoom = aSpaceRoom(
roomId = A_ROOM_ID,
roomType = RoomType.Room,
state = CurrentUserMembership.JOINED,
joinRule = JoinRule.Invite,
)
val invitedRoom = aSpaceRoom(
roomId = A_ROOM_ID_2,
roomType = RoomType.Room,
state = CurrentUserMembership.INVITED,
joinRule = JoinRule.Invite,
)
val roomWithUnknownMembership = aSpaceRoom(
roomId = A_ROOM_ID_3,
roomType = RoomType.Room,
state = null,
joinRule = JoinRule.Invite,
)
val fakeSpaceRoomList = FakeSpaceRoomList(
initialSpaceRoomsValue = listOf(joinedRoom, invitedRoom, roomWithUnknownMembership),
paginateResult = { Result.success(Unit) },
)
val presenter = createSpacePresenter(spaceRoomList = fakeSpaceRoomList)
presenter.test {
awaitItem()
advanceUntilIdle()
val stateWithChildren = expectMostRecentItem()
assertThat(stateWithChildren.children).containsExactly(joinedRoom, invitedRoom, roomWithUnknownMembership)
}
}
@Test
fun `present - removed rooms persist after flow update on partial failure`() = runTest {
// On partial failure, successfully removed rooms should stay filtered even after flow updates