change(members): update tests to match new ui and logic

This commit is contained in:
ganfra
2025-11-25 12:54:35 +01:00
parent 8fd34051e0
commit bf45ed3f1f
3 changed files with 198 additions and 314 deletions

View File

@@ -25,7 +25,6 @@ import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.map
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
@@ -59,9 +58,6 @@ class RoomMemberListPresenter(
@Composable
override fun present(): RoomMemberListState {
var searchQuery by rememberSaveable { mutableStateOf("") }
var searchResults by remember {
mutableStateOf<SearchBarResultState<AsyncData<RoomMembers>>>(SearchBarResultState.Initial())
}
val membersState by room.membersStateFlow.collectAsState()
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val canInvite by room.canInviteAsState(syncUpdateFlow.value)
@@ -75,8 +71,8 @@ class RoomMemberListPresenter(
.launchIn(this)
}
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS) }
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
var filteredRoomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
// Update the room members when the screen is loaded

View File

@@ -12,7 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.architecture.map
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.room.RoomMember
@@ -21,108 +21,59 @@ import kotlinx.collections.immutable.persistentListOf
internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMemberListState> {
override val values: Sequence<RoomMemberListState>
get() = roomMemberListStates() + bannedRoomMemberListStates()
get() = sequenceOf(
aRoomMemberListState(
roomMembers = AsyncData.Loading(),
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState(
roomMembers = AsyncData.Failure(Exception("Error details")),
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState(
roomMembers = aLoadedRoomMembers(),
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState(
roomMembers = aLoadedRoomMembers(),
selectedSection = SelectedSection.BANNED,
moderationState = aRoomMemberModerationState(canBan = true),
),
aRoomMemberListState(
roomMembers = aLoadedRoomMembers(),
canInvite = true,
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState(
roomMembers = aLoadedRoomMembers(),
searchQuery = "alice",
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState(
roomMembers = aLoadedRoomMembers(),
searchQuery = "something-with-no-results",
selectedSection = SelectedSection.MEMBERS,
),
)
}
private fun roomMemberListStates(): Sequence<RoomMemberListState> = sequenceOf(
aRoomMemberListState(
roomMembers = AsyncData.Success(
RoomMembers(
invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()),
joined = persistentListOf(anAlice().withIdentity(), aBob().withIdentity(), aWalter().withIdentity()),
banned = persistentListOf(),
),
private fun aLoadedRoomMembers() = AsyncData.Success(
RoomMembers(
invited = persistentListOf(
anInvitedVictor().withIdentity(),
anInvitedWalter().withIdentity(),
),
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState(
roomMembers = AsyncData.Success(
RoomMembers(
invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()),
joined = persistentListOf(
anAlice().withIdentity(identityState = IdentityState.Verified),
aBob().withIdentity(identityState = IdentityState.PinViolation),
aWalter().withIdentity(identityState = IdentityState.VerificationViolation)
),
banned = persistentListOf(),
)
joined = persistentListOf(
anAlice().withIdentity(identityState = IdentityState.Verified),
aBob().withIdentity(identityState = IdentityState.PinViolation),
aCarol().withIdentity(),
aDavid().withIdentity(),
anEve().withIdentity(identityState = IdentityState.VerificationViolation)
),
selectedSection = SelectedSection.MEMBERS,
moderationState = aRoomMemberModerationState(canBan = true)
),
aRoomMemberListState(
roomMembers = AsyncData.Loading(),
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState().copy(
canInvite = true,
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState().copy(
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState().copy(
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState().copy(
searchQuery = "someone",
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState().copy(
searchQuery = "@someone:matrix.org",
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState().copy(
searchQuery = "something-with-no-results",
selectedSection = SelectedSection.MEMBERS,
),
aRoomMemberListState(
roomMembers = AsyncData.Failure(Exception("Error details")),
selectedSection = SelectedSection.MEMBERS,
),
)
private fun bannedRoomMemberListStates(): Sequence<RoomMemberListState> = sequenceOf(
aRoomMemberListState(
roomMembers = AsyncData.Success(
RoomMembers(
invited = persistentListOf(),
joined = persistentListOf(),
banned = persistentListOf(
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(),
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(),
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(),
),
)
banned = persistentListOf(
aBannedMallory().withIdentity(),
aBannedSusie().withIdentity()
),
moderationState = aRoomMemberModerationState(),
selectedSection = SelectedSection.BANNED,
),
aRoomMemberListState(
roomMembers = AsyncData.Loading(
RoomMembers(
invited = persistentListOf(),
joined = persistentListOf(),
banned = persistentListOf(
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(),
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(),
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(),
),
)
),
moderationState = aRoomMemberModerationState(),
selectedSection = SelectedSection.BANNED,
),
aRoomMemberListState(
roomMembers = AsyncData.Success(
RoomMembers(
invited = persistentListOf(),
joined = persistentListOf(),
banned = persistentListOf(),
)
),
moderationState = aRoomMemberModerationState(),
selectedSection = SelectedSection.BANNED,
)
)
@@ -130,14 +81,17 @@ internal fun aRoomMemberListState(
roomMembers: AsyncData<RoomMembers> = AsyncData.Loading(),
moderationState: RoomMemberModerationState = aRoomMemberModerationState(),
selectedSection: SelectedSection = SelectedSection.MEMBERS,
searchQuery: String = "",
canInvite: Boolean = false,
eventSink: (RoomMemberListEvents) -> Unit = {},
) = RoomMemberListState(
roomMembers = roomMembers,
filteredRoomMembers = roomMembers,
searchQuery = "",
canInvite = false,
filteredRoomMembers = roomMembers.map { it.filter(searchQuery) },
searchQuery = searchQuery,
canInvite = canInvite,
moderationState = moderationState,
selectedSection = selectedSection,
eventSink = {}
eventSink = eventSink
)
fun aRoomMemberModerationState(
@@ -176,21 +130,30 @@ fun aRoomMember(
fun aRoomMemberList() = persistentListOf(
anAlice(),
aBob(),
aRoomMember(UserId("@carol:server.org"), "Carol"),
aRoomMember(UserId("@david:server.org"), "David"),
aRoomMember(UserId("@eve:server.org"), "Eve"),
aRoomMember(UserId("@justin:server.org"), "Justin"),
aRoomMember(UserId("@mallory:server.org"), "Mallory"),
aRoomMember(UserId("@susie:server.org"), "Susie"),
aVictor(),
aWalter(),
aCarol(),
aDavid(),
anEve(),
anInvitedVictor(),
anInvitedWalter(),
aBannedSusie(),
aBannedMallory(),
)
fun anEve(): RoomMember = aRoomMember(UserId("@eve:server.org"), "Eve")
fun aDavid(): RoomMember = aRoomMember(UserId("@david:server.org"), "David")
fun aCarol(): RoomMember = aRoomMember(UserId("@carol:server.org"), "Carol")
fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.Admin)
fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.Moderator)
fun aVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE)
fun anInvitedVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE)
fun aWalter() = aRoomMember(UserId("@walter:server.org"), "Walter", membership = RoomMembershipState.INVITE)
fun anInvitedWalter() = aRoomMember(UserId("@walter:server.org"), "Walter", membership = RoomMembershipState.INVITE)
fun aBannedSusie(): RoomMember = aRoomMember(UserId("@susie:server.org"), "Susie", membership = RoomMembershipState.BAN)
fun aBannedMallory(): RoomMember = aRoomMember(UserId("@mallory:server.org"), "Mallory", membership = RoomMembershipState.BAN)
private fun RoomMember.withIdentity(identityState: IdentityState? = null) = RoomMemberWithIdentityState(this, identityState)

View File

@@ -8,36 +8,27 @@
package io.element.android.features.roomdetails.impl.members
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.RoomMembersState
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.test
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.time.withTimeout
import kotlinx.coroutines.withTimeout
import org.junit.Rule
import org.junit.Test
import kotlin.time.Duration.Companion.seconds
@ExperimentalCoroutinesApi
class RoomMemberListPresenterTest {
@@ -45,176 +36,132 @@ class RoomMemberListPresenterTest {
val warmUpRule = WarmUpRule()
@Test
fun `member loading is done automatically on start, but is async`() = runTest {
val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) },
canInviteResult = { Result.success(true) }
).apply {
// Needed to avoid discarding the loaded members as a partial and invalid result
givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
}
)
val presenter = createPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
fun `initial state is loading`() = runTest {
val presenter = createPresenter()
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.roomMembers.isLoading()).isTrue()
assertThat(initialState.filteredRoomMembers.isLoading()).isTrue()
assertThat(initialState.searchQuery).isEmpty()
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
assertThat(initialState.isSearchActive).isFalse()
room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
// Skip item while the new members state is processed
skipItems(1)
val loadedMembersState = awaitItem()
assertThat(loadedMembersState.roomMembers.isLoading()).isFalse()
assertThat(loadedMembersState.roomMembers.dataOrNull()?.invited)
.isEqualTo(listOf(RoomMemberWithIdentityState(aVictor(), null), RoomMemberWithIdentityState(aWalter(), null)))
assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty()
assertThat(initialState.selectedSection).isEqualTo(SelectedSection.MEMBERS)
}
}
@Test
fun `member loading is done automatically when RoomInfo's activeMemberCount changes`() = runTest {
val reloadMembersMutex = Mutex()
val updateMembersLambda = lambdaRecorder<Unit> {
if (reloadMembersMutex.isLocked) {
reloadMembersMutex.unlock()
fun `hide banned section when there is no banned users`() = runTest {
val allRoomMembers = aRoomMemberList()
val noBannedMembers = allRoomMembers
.filterNot { it.membership == RoomMembershipState.BAN }
.toImmutableList()
val room = createFakeJoinedRoom()
.apply {
givenRoomMembersState(RoomMembersState.Ready(allRoomMembers))
}
}
val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
updateMembersResult = updateMembersLambda,
canInviteResult = { Result.success(true) }
).apply {
// Needed to avoid discarding the loaded members as a partial and invalid result
givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
}
)
val presenter = createPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.roomMembers.isLoading()).isTrue()
room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
// Skip item while the new members state is processed
skipItems(1)
val loadedMembersState = awaitItem()
assertThat(loadedMembersState.roomMembers.isLoading()).isFalse()
assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty()
// Assert no events are emitted only with that change
expectNoEvents()
// This will only progress if the `Room.updateMembers()` function is called, triggered by the RoomInfo change
withTimeout(10.seconds) {
reloadMembersMutex.withLock {
launch { room.givenRoomInfo(aRoomInfo(activeMembersCount = 0L)) }
}
}
// Update the room members state as `Room.updateMembers()` would have done with the actual implementation
room.givenRoomMembersState(RoomMembersState.Ready(persistentListOf()))
// Wait for another update
skipItems(1)
// The members should be reloaded now
assertThat(awaitItem().roomMembers.dataOrNull()?.joined).isEmpty()
}
}
@Test
fun `open search`() = runTest {
val presenter = createPresenter(
joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) },
canInviteResult = { Result.success(true) }
)
)
joinedRoom = room,
roomMemberModerationState = aRoomMemberModerationState(canBan = true),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
skipItems(1)
val loadedState = awaitItem()
loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true))
assertThat(loadedState.showBannedSection).isTrue()
loadedState.eventSink(RoomMemberListEvents.ChangeSelectedSection(SelectedSection.BANNED))
val bannedSectionState = awaitItem()
assertThat(bannedSectionState.selectedSection).isEqualTo(SelectedSection.BANNED)
// Now update the room members to have no banned users
room.givenRoomMembersState(RoomMembersState.Ready(noBannedMembers))
skipItems(1)
val searchActiveState = awaitItem()
assertThat(searchActiveState.isSearchActive).isTrue()
val noBannedMembersState = awaitItem()
assertThat(noBannedMembersState.showBannedSection).isFalse()
skipItems(1)
val finalState = awaitItem()
assertThat(finalState.selectedSection).isEqualTo(SelectedSection.MEMBERS)
}
}
@Test
fun `member loading is done automatically on start, but is async`() = runTest {
val room = createFakeJoinedRoom()
val presenter = createPresenter(joinedRoom = room)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.filteredRoomMembers.isLoading()).isTrue()
assertThat(initialState.searchQuery).isEmpty()
room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
// Skip items while the new members state is processed
skipItems(2)
val loadedState = awaitItem()
val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!!
assertThat(loadedRoomMembers.joined).isNotEmpty()
assertThat(loadedRoomMembers.banned).isNotEmpty()
assertThat(loadedRoomMembers.invited).isNotEmpty()
assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse()
assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse()
}
}
@Test
fun `search for something which is not found`() = runTest {
val presenter = createPresenter(
joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) },
canInviteResult = { Result.success(true) }
)
)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val room = createFakeJoinedRoom().apply {
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
}
val presenter = createPresenter(joinedRoom = room)
presenter.test {
skipItems(1)
val loadedState = awaitItem()
loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true))
val searchActiveState = awaitItem()
searchActiveState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something"))
skipItems(1)
val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!!
assertThat(loadedRoomMembers.joined).isNotEmpty()
assertThat(loadedRoomMembers.banned).isNotEmpty()
assertThat(loadedRoomMembers.invited).isNotEmpty()
assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse()
assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse()
loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something"))
val searchQueryUpdatedState = awaitItem()
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("something")
val searchSearchResultDelivered = awaitItem()
assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java)
val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!!
assertThat(emptyRoomMembers.joined).isEmpty()
assertThat(emptyRoomMembers.banned).isEmpty()
assertThat(emptyRoomMembers.invited).isEmpty()
assertThat(emptyRoomMembers.isEmpty(SelectedSection.MEMBERS)).isTrue()
assertThat(emptyRoomMembers.isEmpty(SelectedSection.BANNED)).isTrue()
}
}
@Test
fun `search for something which is found`() = runTest {
val presenter = createPresenter(
joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) },
canInviteResult = { Result.success(true) }
)
)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val room = createFakeJoinedRoom().apply {
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
}
val presenter = createPresenter(joinedRoom = room)
presenter.test {
skipItems(1)
val loadedState = awaitItem()
loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true))
val searchActiveState = awaitItem()
searchActiveState.eventSink(RoomMemberListEvents.UpdateSearchQuery("Alice"))
skipItems(1)
val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!!
assertThat(loadedRoomMembers.joined).isNotEmpty()
assertThat(loadedRoomMembers.banned).isNotEmpty()
assertThat(loadedRoomMembers.invited).isNotEmpty()
assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse()
assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse()
loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("alice"))
val searchQueryUpdatedState = awaitItem()
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("Alice")
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("alice")
val searchSearchResultDelivered = awaitItem()
assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.Results::class.java)
assertThat((searchSearchResultDelivered.searchResults as SearchBarResultState.Results).results.dataOrNull()!!.joined.first().roomMember.displayName)
.isEqualTo("Alice")
val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!!
assertThat(emptyRoomMembers.joined).isNotEmpty()
assertThat(emptyRoomMembers.banned).isEmpty()
assertThat(emptyRoomMembers.invited).isEmpty()
assertThat(emptyRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse()
assertThat(emptyRoomMembers.isEmpty(SelectedSection.BANNED)).isTrue()
}
}
@Test
fun `present - asynchronously sets canInvite when user has correct power level`() = runTest {
val presenter = createPresenter(
joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
canInviteResult = { Result.success(true) },
updateMembersResult = { Result.success(Unit) }
)
)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val presenter = createPresenter()
presenter.test {
skipItems(1)
val loadedState = awaitItem()
assertThat(loadedState.canInvite).isTrue()
@@ -224,17 +171,11 @@ class RoomMemberListPresenterTest {
@Test
fun `present - asynchronously sets canInvite when user does not have correct power level`() = runTest {
val presenter = createPresenter(
joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
canInviteResult = { Result.success(false) },
updateMembersResult = { Result.success(Unit) }
)
joinedRoom = createFakeJoinedRoom(
canInviteResult = { Result.success(false) },
)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
presenter.test {
val loadedState = awaitItem()
assertThat(loadedState.canInvite).isFalse()
}
@@ -243,70 +184,54 @@ class RoomMemberListPresenterTest {
@Test
fun `present - asynchronously sets canInvite when power level check fails`() = runTest {
val presenter = createPresenter(
joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
canInviteResult = { Result.failure(RuntimeException("Eek")) },
updateMembersResult = { Result.success(Unit) }
)
joinedRoom = createFakeJoinedRoom(
canInviteResult = { Result.failure(RuntimeException("Eek")) },
)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
presenter.test {
val loadedState = awaitItem()
assertThat(loadedState.canInvite).isFalse()
}
}
@Test
fun `present - RoomMemberSelected will open the moderation options when target user is not banned`() = runTest {
val roomMemberModerationPresenter = Presenter {
aRoomMemberModerationState(canBan = true, canKick = true)
}
fun `present - RoomMemberSelected will open the moderation options`() = runTest {
val presenter = createPresenter(
roomMemberModerationPresenter = roomMemberModerationPresenter,
joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) },
canInviteResult = { Result.success(true) }
)
)
roomMemberModerationState = aRoomMemberModerationState(canBan = true, canKick = true)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
skipItems(1)
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor()))
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(anInvitedVictor()))
}
}
}
@ExperimentalCoroutinesApi
private fun TestScope.createDataSource(
room: BaseRoom = FakeBaseRoom().apply {
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
},
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers()
) = RoomMemberListDataSource(room, coroutineDispatchers)
private fun createFakeJoinedRoom(
updateMembersResult: () -> Unit = { },
canInviteResult: (UserId) -> Result<Boolean> = { Result.success(true) },
): FakeJoinedRoom {
return FakeJoinedRoom(
baseRoom = FakeBaseRoom(
updateMembersResult = updateMembersResult,
canInviteResult = canInviteResult,
).apply {
// Needed to avoid discarding the loaded members as a partial and invalid result
givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
}
)
}
@ExperimentalCoroutinesApi
private fun TestScope.createPresenter(
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
joinedRoom: JoinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) }
)
),
roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers),
joinedRoom: JoinedRoom = createFakeJoinedRoom(),
encryptedService: FakeEncryptionService = FakeEncryptionService(),
roomMemberModerationPresenter: Presenter<RoomMemberModerationState> = Presenter {
aRoomMemberModerationState()
},
roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(),
) = RoomMemberListPresenter(
room = joinedRoom,
roomMemberListDataSource = roomMemberListDataSource,
coroutineDispatchers = coroutineDispatchers,
roomMembersModerationPresenter = roomMemberModerationPresenter,
roomMembersModerationPresenter = Presenter {
roomMemberModerationState
},
encryptionService = encryptedService,
)