Skip to content

Commit 4441a25

Browse files
authored
Merge pull request #4765 from element-hq/feature/fga/fix_left_room_membership_change
Fix left room membership change
2 parents 4572419 + 091d41b commit 4441a25

File tree

4 files changed

+118
-15
lines changed

4 files changed

+118
-15
lines changed

libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,12 @@ class RoomMembershipObserver {
2222
private val _updates = MutableSharedFlow<RoomMembershipUpdate>(extraBufferCapacity = 10)
2323
val updates = _updates.asSharedFlow()
2424

25-
suspend fun notifyUserLeftRoom(roomId: RoomId) {
26-
_updates.emit(RoomMembershipUpdate(roomId, false, MembershipChange.LEFT))
27-
}
28-
29-
suspend fun notifyUserDeclinedInvite(roomId: RoomId) {
30-
_updates.emit(RoomMembershipUpdate(roomId, false, MembershipChange.INVITATION_REJECTED))
31-
}
32-
33-
suspend fun notifyUserCanceledKnock(roomId: RoomId) {
34-
_updates.emit(RoomMembershipUpdate(roomId, false, MembershipChange.KNOCK_RETRACTED))
25+
suspend fun notifyUserLeftRoom(roomId: RoomId, membershipBeforeLeft: CurrentUserMembership) {
26+
val membershipChange = when (membershipBeforeLeft) {
27+
CurrentUserMembership.INVITED -> MembershipChange.INVITATION_REJECTED
28+
CurrentUserMembership.KNOCKED -> MembershipChange.KNOCK_RETRACTED
29+
else -> MembershipChange.LEFT
30+
}
31+
_updates.emit(RoomMembershipUpdate(roomId, false, membershipChange))
3532
}
3633
}

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,11 @@ class RustBaseRoom(
137137
}
138138

139139
override suspend fun leave(): Result<Unit> = withContext(roomDispatcher) {
140+
val membershipBeforeLeft = roomInfoFlow.value.currentUserMembership
140141
runCatching {
141142
innerRoom.leave()
142143
}.onSuccess {
143-
roomMembershipObserver.notifyUserLeftRoom(roomId)
144+
roomMembershipObserver.notifyUserLeftRoom(roomId, membershipBeforeLeft)
144145
}
145146
}
146147

libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustRoom.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class FakeRustRoom(
2121
private val roomId: RoomId = A_ROOM_ID,
2222
private val getMembers: () -> RoomMembersIterator = { lambdaError() },
2323
private val getMembersNoSync: () -> RoomMembersIterator = { lambdaError() },
24+
private val leaveLambda: () -> Unit = { lambdaError() },
2425
private val latestEventLambda: () -> EventTimelineItem? = { lambdaError() },
2526
private val roomInfo: RoomInfo = aRustRoomInfo(id = roomId.value),
2627
) : Room(NoPointer) {
@@ -36,6 +37,10 @@ class FakeRustRoom(
3637
return getMembersNoSync()
3738
}
3839

40+
override suspend fun leave() {
41+
leaveLambda()
42+
}
43+
3944
override suspend fun roomInfo(): RoomInfo {
4045
return roomInfo
4146
}

libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,21 @@
77

88
package io.element.android.libraries.matrix.impl.room
99

10+
import app.cash.turbine.TurbineTestContext
11+
import app.cash.turbine.test
1012
import com.google.common.truth.Truth.assertThat
13+
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
14+
import io.element.android.libraries.matrix.api.room.RoomInfo
1115
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
16+
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
1217
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoom
1318
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListService
1419
import io.element.android.libraries.matrix.test.A_DEVICE_ID
1520
import io.element.android.libraries.matrix.test.A_SESSION_ID
1621
import io.element.android.libraries.matrix.test.room.aRoomInfo
1722
import io.element.android.tests.testutils.testCoroutineDispatchers
23+
import kotlinx.coroutines.flow.SharingStarted
24+
import kotlinx.coroutines.flow.shareIn
1825
import kotlinx.coroutines.isActive
1926
import kotlinx.coroutines.test.TestScope
2027
import kotlinx.coroutines.test.runTest
@@ -29,22 +36,115 @@ class RustBaseRoomTest {
2936
assertThat(rustBaseRoom.roomCoroutineScope.isActive).isFalse()
3037
}
3138

32-
private fun TestScope.createRustBaseRoom(): RustBaseRoom {
39+
@Test
40+
fun `when currentUserMembership=JOINED and user leave room succeed then roomMembershipObserver emits change as LEFT`() = runTest {
41+
val roomMembershipObserver = RoomMembershipObserver()
42+
val rustBaseRoom = createRustBaseRoom(
43+
initialRoomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.JOINED),
44+
innerRoom = FakeRustRoom(
45+
leaveLambda = {
46+
// Simulate a successful leave
47+
}
48+
),
49+
roomMembershipObserver = roomMembershipObserver,
50+
)
51+
leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) {
52+
val membershipUpdate = awaitItem()
53+
assertThat(membershipUpdate.roomId).isEqualTo(rustBaseRoom.roomId)
54+
assertThat(membershipUpdate.isUserInRoom).isFalse()
55+
assertThat(membershipUpdate.change).isEqualTo(MembershipChange.LEFT)
56+
}
57+
}
58+
59+
@Test
60+
fun `when currentUserMembership=KNOCKED and user leave room succeed then roomMembershipObserver emits change as KNOCK_RETRACTED`() = runTest {
61+
val roomMembershipObserver = RoomMembershipObserver()
62+
val rustBaseRoom = createRustBaseRoom(
63+
initialRoomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.KNOCKED),
64+
innerRoom = FakeRustRoom(
65+
leaveLambda = {
66+
// Simulate a successful leave
67+
}
68+
),
69+
roomMembershipObserver = roomMembershipObserver,
70+
)
71+
leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) {
72+
val membershipUpdate = awaitItem()
73+
assertThat(membershipUpdate.roomId).isEqualTo(rustBaseRoom.roomId)
74+
assertThat(membershipUpdate.isUserInRoom).isFalse()
75+
assertThat(membershipUpdate.change).isEqualTo(MembershipChange.KNOCK_RETRACTED)
76+
}
77+
}
78+
79+
@Test
80+
fun `when currentUserMembership=INVITED and user leave room succeed then roomMembershipObserver emits change as INVITATION_REJECTED`() = runTest {
81+
val roomMembershipObserver = RoomMembershipObserver()
82+
val rustBaseRoom = createRustBaseRoom(
83+
initialRoomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.INVITED),
84+
innerRoom = FakeRustRoom(
85+
leaveLambda = {
86+
// Simulate a successful leave
87+
}
88+
),
89+
roomMembershipObserver = roomMembershipObserver,
90+
)
91+
leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) {
92+
val membershipUpdate = awaitItem()
93+
assertThat(membershipUpdate.roomId).isEqualTo(rustBaseRoom.roomId)
94+
assertThat(membershipUpdate.isUserInRoom).isFalse()
95+
assertThat(membershipUpdate.change).isEqualTo(MembershipChange.INVITATION_REJECTED)
96+
}
97+
}
98+
99+
@Test
100+
fun `when user leave room fails then roomMembershipObserver emits nothing`() = runTest {
101+
val roomMembershipObserver = RoomMembershipObserver()
102+
val rustBaseRoom = createRustBaseRoom(
103+
initialRoomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.INVITED),
104+
innerRoom = FakeRustRoom(
105+
leaveLambda = { error("Leave failed") }
106+
),
107+
roomMembershipObserver = roomMembershipObserver,
108+
)
109+
leaveRoomAndObserveMembershipChange(roomMembershipObserver, rustBaseRoom) {
110+
// No emit
111+
}
112+
}
113+
114+
private suspend fun TestScope.leaveRoomAndObserveMembershipChange(
115+
roomMembershipObserver: RoomMembershipObserver,
116+
rustBaseRoom: RustBaseRoom,
117+
validate: suspend TurbineTestContext<RoomMembershipObserver.RoomMembershipUpdate>.() -> Unit
118+
) {
119+
val shared = roomMembershipObserver.updates.shareIn(scope = backgroundScope, started = SharingStarted.Eagerly, replay = 1)
120+
rustBaseRoom.leave()
121+
shared.test {
122+
validate()
123+
ensureAllEventsConsumed()
124+
}
125+
rustBaseRoom.destroy()
126+
}
127+
128+
private fun TestScope.createRustBaseRoom(
129+
initialRoomInfo: RoomInfo = aRoomInfo(),
130+
innerRoom: FakeRustRoom = FakeRustRoom(),
131+
roomMembershipObserver: RoomMembershipObserver = RoomMembershipObserver(),
132+
): RustBaseRoom {
33133
val dispatchers = testCoroutineDispatchers()
34134
return RustBaseRoom(
35135
sessionId = A_SESSION_ID,
36136
deviceId = A_DEVICE_ID,
37-
innerRoom = FakeRustRoom(),
137+
innerRoom = innerRoom,
38138
coroutineDispatchers = dispatchers,
39139
roomSyncSubscriber = RoomSyncSubscriber(
40140
roomListService = FakeRustRoomListService(),
41141
dispatchers = dispatchers,
42142
),
43-
roomMembershipObserver = RoomMembershipObserver(),
143+
roomMembershipObserver = roomMembershipObserver,
44144
// Not using backgroundScope here, but the test scope
45145
sessionCoroutineScope = this,
46146
roomInfoMapper = RoomInfoMapper(),
47-
initialRoomInfo = aRoomInfo(),
147+
initialRoomInfo = initialRoomInfo,
48148
)
49149
}
50150
}

0 commit comments

Comments
 (0)