Browse Source

Remove dependencies to other presenters to RoomMembersModerationPresenter.

Move canDisplayModerationActions from presenter API to the state it emits.
pull/3618/head
Benoit Marty 2 weeks ago committed by Benoit Marty
parent
commit
2e5450c4b4
  1. 23
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomDetailsModule.kt
  2. 6
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt
  3. 174
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt
  4. 184
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt
  5. 1
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationState.kt
  6. 2
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStatePreviewProvider.kt
  7. 29
      features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt
  8. 34
      features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/FakeRoomMembersModerationPresenter.kt
  9. 47
      features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/RoomMembersModerationPresenterTest.kt

23
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomDetailsModule.kt

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.roomdetails.impl.di
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope
@Module
@ContributesTo(RoomScope::class)
interface RoomDetailsModule {
@Binds
fun bindRoomMembersModerationPresenter(presenter: RoomMembersModerationPresenter): Presenter<RoomMembersModerationState>
}

6
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt

@ -21,7 +21,7 @@ import dagger.assisted.Assisted @@ -21,7 +21,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
@ -40,7 +40,7 @@ class RoomMemberListPresenter @AssistedInject constructor( @@ -40,7 +40,7 @@ class RoomMemberListPresenter @AssistedInject constructor(
private val room: MatrixRoom,
private val roomMemberListDataSource: RoomMemberListDataSource,
private val coroutineDispatchers: CoroutineDispatchers,
private val roomMembersModerationPresenter: RoomMembersModerationPresenter,
private val roomMembersModerationPresenter: Presenter<RoomMembersModerationState>,
@Assisted private val navigator: RoomMemberListNavigator,
) : Presenter<RoomMemberListState> {
@AssistedFactory
@ -136,7 +136,7 @@ class RoomMemberListPresenter @AssistedInject constructor( @@ -136,7 +136,7 @@ class RoomMemberListPresenter @AssistedInject constructor(
is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active
is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query
is RoomMemberListEvents.RoomMemberSelected -> coroutineScope.launch {
if (roomMembersModerationPresenter.canDisplayModerationActions()) {
if (roomModerationState.canDisplayModerationActions) {
roomModerationState.eventSink(RoomMembersModerationEvents.SelectRoomMember(event.roomMember))
} else {
navigator.openRoomMemberDetails(event.roomMember.userId)

174
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt

@ -1,174 +0,0 @@ @@ -1,174 +0,0 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.roomdetails.impl.members.moderation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.squareup.anvil.annotations.ContributesBinding
import im.vector.app.features.analytics.plan.RoomModeration
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.finally
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
import io.element.android.libraries.matrix.api.room.powerlevels.canKick
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import javax.inject.Inject
@ContributesBinding(RoomScope::class)
class DefaultRoomMembersModerationPresenter @Inject constructor(
private val room: MatrixRoom,
private val dispatchers: CoroutineDispatchers,
private val analyticsService: AnalyticsService,
) : RoomMembersModerationPresenter {
private var selectedMember by mutableStateOf<RoomMember?>(null)
private suspend fun canBan() = room.canBan().getOrDefault(false)
private suspend fun canKick() = room.canKick().getOrDefault(false)
override suspend fun canDisplayModerationActions(): Boolean {
return !room.isDm && (canBan() || canKick())
}
@Composable
override fun present(): RoomMembersModerationState {
val coroutineScope = rememberCoroutineScope()
var moderationActions by remember { mutableStateOf(persistentListOf<ModerationAction>()) }
val kickUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
val banUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
val unbanUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
val canDisplayBannedUsers by produceState(initialValue = false) {
value = !room.isDm && canBan()
}
fun handleEvent(event: RoomMembersModerationEvents) {
when (event) {
is RoomMembersModerationEvents.SelectRoomMember -> {
coroutineScope.launch {
selectedMember = event.roomMember
if (event.roomMember.membership == RoomMembershipState.BAN && canBan()) {
unbanUserAsyncAction.value = AsyncAction.Confirming
} else {
moderationActions = buildList {
add(ModerationAction.DisplayProfile(event.roomMember.userId))
val currentUserMemberPowerLevel = room.userRole(room.sessionId).getOrDefault(RoomMember.Role.USER).powerLevel
if (currentUserMemberPowerLevel > event.roomMember.powerLevel) {
if (canKick()) {
add(ModerationAction.KickUser(event.roomMember.userId))
}
if (canBan()) {
add(ModerationAction.BanUser(event.roomMember.userId))
}
}
}.toPersistentList()
}
}
}
is RoomMembersModerationEvents.KickUser -> {
moderationActions = persistentListOf()
selectedMember?.let {
coroutineScope.kickUser(it.userId, kickUserAsyncAction)
}
}
is RoomMembersModerationEvents.BanUser -> {
if (banUserAsyncAction.value.isConfirming()) {
moderationActions = persistentListOf()
selectedMember?.let {
coroutineScope.banUser(it.userId, banUserAsyncAction)
}
} else {
banUserAsyncAction.value = AsyncAction.Confirming
}
}
is RoomMembersModerationEvents.UnbanUser -> {
if (unbanUserAsyncAction.value.isConfirming()) {
moderationActions = persistentListOf()
selectedMember?.let {
coroutineScope.unbanUser(it.userId, unbanUserAsyncAction)
}
} else {
unbanUserAsyncAction.value = AsyncAction.Confirming
}
}
is RoomMembersModerationEvents.Reset -> {
selectedMember = null
moderationActions = persistentListOf()
kickUserAsyncAction.value = AsyncAction.Uninitialized
banUserAsyncAction.value = AsyncAction.Uninitialized
unbanUserAsyncAction.value = AsyncAction.Uninitialized
}
}
}
return RoomMembersModerationState(
selectedRoomMember = selectedMember,
actions = moderationActions,
kickUserAsyncAction = kickUserAsyncAction.value,
banUserAsyncAction = banUserAsyncAction.value,
unbanUserAsyncAction = unbanUserAsyncAction.value,
canDisplayBannedUsers = canDisplayBannedUsers,
eventSink = { handleEvent(it) },
)
}
private fun CoroutineScope.kickUser(
userId: UserId,
kickUserAction: MutableState<AsyncAction<Unit>>,
) = runActionAndWaitForMembershipChange(kickUserAction) {
analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember))
room.kickUser(userId).finally { selectedMember = null }
}
private fun CoroutineScope.banUser(
userId: UserId,
banUserAction: MutableState<AsyncAction<Unit>>,
) = runActionAndWaitForMembershipChange(banUserAction) {
analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember))
room.banUser(userId).finally { selectedMember = null }
}
private fun CoroutineScope.unbanUser(
userId: UserId,
unbanUserAction: MutableState<AsyncAction<Unit>>,
) = runActionAndWaitForMembershipChange(unbanUserAction) {
analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember))
room.unbanUser(userId).finally { selectedMember = null }
}
private fun <T> CoroutineScope.runActionAndWaitForMembershipChange(action: MutableState<AsyncAction<T>>, block: suspend () -> Result<T>) {
launch(dispatchers.io) {
action.runUpdatingState {
val result = block()
if (result.isSuccess) {
room.membersStateFlow.drop(1).take(1)
}
result
}
}
}
}

184
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt

@ -7,20 +7,180 @@ @@ -7,20 +7,180 @@
package io.element.android.features.roomdetails.impl.members.moderation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.squareup.anvil.annotations.ContributesBinding
import im.vector.app.features.analytics.plan.RoomModeration
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.finally
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
import io.element.android.libraries.matrix.api.room.powerlevels.canKick
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import javax.inject.Inject
interface RoomMembersModerationPresenter : Presenter<RoomMembersModerationState> {
suspend fun canDisplayModerationActions(): Boolean
fun dummyState() = RoomMembersModerationState(
selectedRoomMember = null,
actions = persistentListOf(),
kickUserAsyncAction = AsyncAction.Uninitialized,
banUserAsyncAction = AsyncAction.Uninitialized,
unbanUserAsyncAction = AsyncAction.Uninitialized,
canDisplayBannedUsers = false,
eventSink = {}
)
class RoomMembersModerationPresenter @Inject constructor(
private val room: MatrixRoom,
private val dispatchers: CoroutineDispatchers,
private val analyticsService: AnalyticsService,
) : Presenter<RoomMembersModerationState> {
private var selectedMember by mutableStateOf<RoomMember?>(null)
private suspend fun canBan() = room.canBan().getOrDefault(false)
private suspend fun canKick() = room.canKick().getOrDefault(false)
@Composable
override fun present(): RoomMembersModerationState {
val coroutineScope = rememberCoroutineScope()
var moderationActions by remember { mutableStateOf(persistentListOf<ModerationAction>()) }
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val canDisplayModerationActions by produceState(
initialValue = false,
key1 = syncUpdateFlow.value
) {
value = !room.isDm && (canBan() || canKick())
}
val kickUserAsyncAction =
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
val banUserAsyncAction =
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
val unbanUserAsyncAction =
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
val canDisplayBannedUsers by produceState(initialValue = false) {
value = !room.isDm && canBan()
}
fun handleEvent(event: RoomMembersModerationEvents) {
when (event) {
is RoomMembersModerationEvents.SelectRoomMember -> {
coroutineScope.launch {
selectedMember = event.roomMember
if (event.roomMember.membership == RoomMembershipState.BAN && canBan()) {
unbanUserAsyncAction.value = AsyncAction.Confirming
} else {
moderationActions = buildList {
add(ModerationAction.DisplayProfile(event.roomMember.userId))
val currentUserMemberPowerLevel = room.userRole(room.sessionId)
.getOrDefault(RoomMember.Role.USER).powerLevel
if (currentUserMemberPowerLevel > event.roomMember.powerLevel) {
if (canKick()) {
add(ModerationAction.KickUser(event.roomMember.userId))
}
if (canBan()) {
add(ModerationAction.BanUser(event.roomMember.userId))
}
}
}.toPersistentList()
}
}
}
is RoomMembersModerationEvents.KickUser -> {
moderationActions = persistentListOf()
selectedMember?.let {
coroutineScope.kickUser(it.userId, kickUserAsyncAction)
}
}
is RoomMembersModerationEvents.BanUser -> {
if (banUserAsyncAction.value.isConfirming()) {
moderationActions = persistentListOf()
selectedMember?.let {
coroutineScope.banUser(it.userId, banUserAsyncAction)
}
} else {
banUserAsyncAction.value = AsyncAction.Confirming
}
}
is RoomMembersModerationEvents.UnbanUser -> {
if (unbanUserAsyncAction.value.isConfirming()) {
moderationActions = persistentListOf()
selectedMember?.let {
coroutineScope.unbanUser(it.userId, unbanUserAsyncAction)
}
} else {
unbanUserAsyncAction.value = AsyncAction.Confirming
}
}
is RoomMembersModerationEvents.Reset -> {
selectedMember = null
moderationActions = persistentListOf()
kickUserAsyncAction.value = AsyncAction.Uninitialized
banUserAsyncAction.value = AsyncAction.Uninitialized
unbanUserAsyncAction.value = AsyncAction.Uninitialized
}
}
}
return RoomMembersModerationState(
canDisplayModerationActions = canDisplayModerationActions,
selectedRoomMember = selectedMember,
actions = moderationActions,
kickUserAsyncAction = kickUserAsyncAction.value,
banUserAsyncAction = banUserAsyncAction.value,
unbanUserAsyncAction = unbanUserAsyncAction.value,
canDisplayBannedUsers = canDisplayBannedUsers,
eventSink = { handleEvent(it) },
)
}
private fun CoroutineScope.kickUser(
userId: UserId,
kickUserAction: MutableState<AsyncAction<Unit>>,
) = runActionAndWaitForMembershipChange(kickUserAction) {
analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember))
room.kickUser(userId).finally { selectedMember = null }
}
private fun CoroutineScope.banUser(
userId: UserId,
banUserAction: MutableState<AsyncAction<Unit>>,
) = runActionAndWaitForMembershipChange(banUserAction) {
analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember))
room.banUser(userId).finally { selectedMember = null }
}
private fun CoroutineScope.unbanUser(
userId: UserId,
unbanUserAction: MutableState<AsyncAction<Unit>>,
) = runActionAndWaitForMembershipChange(unbanUserAction) {
analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember))
room.unbanUser(userId).finally { selectedMember = null }
}
private fun <T> CoroutineScope.runActionAndWaitForMembershipChange(
action: MutableState<AsyncAction<T>>,
block: suspend () -> Result<T>
) {
launch(dispatchers.io) {
action.runUpdatingState {
val result = block()
if (result.isSuccess) {
room.membersStateFlow.drop(1).take(1)
}
result
}
}
}
}

1
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationState.kt

@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember @@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember
import kotlinx.collections.immutable.ImmutableList
data class RoomMembersModerationState(
val canDisplayModerationActions: Boolean,
val selectedRoomMember: RoomMember?,
val actions: ImmutableList<ModerationAction>,
val kickUserAsyncAction: AsyncAction<Unit>,

2
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStatePreviewProvider.kt

@ -71,6 +71,7 @@ class RoomMembersModerationStatePreviewProvider : PreviewParameterProvider<RoomM @@ -71,6 +71,7 @@ class RoomMembersModerationStatePreviewProvider : PreviewParameterProvider<RoomM
}
fun aRoomMembersModerationState(
canDisplayModerationActions: Boolean = false,
selectedRoomMember: RoomMember? = null,
actions: List<ModerationAction> = emptyList(),
kickUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
@ -79,6 +80,7 @@ fun aRoomMembersModerationState( @@ -79,6 +80,7 @@ fun aRoomMembersModerationState(
canDisplayBannedUsers: Boolean = false,
eventSink: (RoomMembersModerationEvents) -> Unit = {},
) = RoomMembersModerationState(
canDisplayModerationActions = canDisplayModerationActions,
selectedRoomMember = selectedRoomMember,
actions = actions.toPersistentList(),
kickUserAsyncAction = kickUserAsyncAction,

29
features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt

@ -19,8 +19,8 @@ import io.element.android.features.roomdetails.impl.members.aRoomMemberList @@ -19,8 +19,8 @@ import io.element.android.features.roomdetails.impl.members.aRoomMemberList
import io.element.android.features.roomdetails.impl.members.aVictor
import io.element.android.features.roomdetails.impl.members.aWalter
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
import io.element.android.features.roomdetails.impl.members.moderation.aRoomMembersModerationState
import io.element.android.features.roomdetails.members.moderation.FakeRoomMembersModerationPresenter
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
@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -194,9 +195,9 @@ class RoomMemberListPresenterTest { @@ -194,9 +195,9 @@ class RoomMemberListPresenterTest {
@Test
fun `present - RoomMemberSelected by default opens the room member details through the navigator`() = runTest {
val navigator = FakeRoomMemberListNavigator()
val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = false)
val roomMembersModerationStateLambda = { aRoomMembersModerationState(canDisplayModerationActions = false) }
val presenter = createPresenter(
moderationPresenter = moderationPresenter,
roomMembersModerationStateLambda = roomMembersModerationStateLambda,
navigator = navigator,
matrixRoom = FakeMatrixRoom(
updateMembersResult = { Result.success(Unit) },
@ -215,17 +216,15 @@ class RoomMemberListPresenterTest { @@ -215,17 +216,15 @@ class RoomMemberListPresenterTest {
@Test
fun `present - RoomMemberSelected will open the moderation options if the current user can use them`() = runTest {
val navigator = FakeRoomMemberListNavigator()
var selectRoomMemberCallCounts = 0
val capturingState = aRoomMembersModerationState(eventSink = { event ->
if (event is RoomMembersModerationEvents.SelectRoomMember) {
selectRoomMemberCallCounts++
}
})
val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = true).apply {
givenState(capturingState)
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMembersModerationStateLambda = {
aRoomMembersModerationState(
canDisplayModerationActions = true,
eventSink = eventsRecorder,
)
}
val presenter = createPresenter(
moderationPresenter = moderationPresenter,
roomMembersModerationStateLambda = roomMembersModerationStateLambda,
navigator = navigator,
matrixRoom = FakeMatrixRoom(
updateMembersResult = { Result.success(Unit) },
@ -237,7 +236,7 @@ class RoomMemberListPresenterTest { @@ -237,7 +236,7 @@ class RoomMemberListPresenterTest {
}.test {
skipItems(1)
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor()))
assertThat(selectRoomMemberCallCounts).isEqualTo(1)
eventsRecorder.assertSingle(RoomMembersModerationEvents.SelectRoomMember(aVictor()))
}
}
}
@ -266,12 +265,12 @@ private fun TestScope.createPresenter( @@ -266,12 +265,12 @@ private fun TestScope.createPresenter(
updateMembersResult = { Result.success(Unit) }
),
roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers),
moderationPresenter: FakeRoomMembersModerationPresenter = FakeRoomMembersModerationPresenter(),
roomMembersModerationStateLambda: () -> RoomMembersModerationState = { aRoomMembersModerationState() },
navigator: RoomMemberListNavigator = object : RoomMemberListNavigator {}
) = RoomMemberListPresenter(
room = matrixRoom,
roomMemberListDataSource = roomMemberListDataSource,
coroutineDispatchers = coroutineDispatchers,
roomMembersModerationPresenter = moderationPresenter,
roomMembersModerationPresenter = { roomMembersModerationStateLambda() },
navigator = navigator
)

34
features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/FakeRoomMembersModerationPresenter.kt

@ -1,34 +0,0 @@ @@ -1,34 +0,0 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.roomdetails.members.moderation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
class FakeRoomMembersModerationPresenter(
private val canDisplayModerationActions: Boolean = true,
) : RoomMembersModerationPresenter {
private var state by mutableStateOf(dummyState())
override suspend fun canDisplayModerationActions(): Boolean {
return canDisplayModerationActions
}
@Composable
override fun present(): RoomMembersModerationState {
return state
}
fun givenState(state: RoomMembersModerationState) {
this.state = state
}
}

47
features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt → features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/RoomMembersModerationPresenterTest.kt

@ -14,9 +14,9 @@ import com.google.common.truth.Truth.assertThat @@ -14,9 +14,9 @@ import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.RoomModeration
import io.element.android.features.roomdetails.impl.members.aRoomMember
import io.element.android.features.roomdetails.impl.members.aVictor
import io.element.android.features.roomdetails.impl.members.moderation.DefaultRoomMembersModerationPresenter
import io.element.android.features.roomdetails.impl.members.moderation.ModerationAction
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
@ -27,20 +27,23 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2 @@ -27,20 +27,23 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.test
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
class DefaultRoomMembersModerationPresenterTest {
class RoomMembersModerationPresenterTest {
@Test
fun `canDisplayModerationActions - when room is DM is false`() = runTest {
val room = FakeMatrixRoom(isDirect = true, isPublic = true, activeMemberCount = 2).apply {
givenRoomInfo(aRoomInfo(isDirect = true, isPublic = false, activeMembersCount = 2))
}
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
assertThat(presenter.canDisplayModerationActions()).isFalse()
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
presenter.test {
assertThat(awaitItem().canDisplayModerationActions).isFalse()
}
}
@Test
@ -51,8 +54,11 @@ class DefaultRoomMembersModerationPresenterTest { @@ -51,8 +54,11 @@ class DefaultRoomMembersModerationPresenterTest {
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
)
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
assertThat(presenter.canDisplayModerationActions()).isTrue()
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
presenter.test {
skipItems(1)
assertThat(awaitItem().canDisplayModerationActions).isTrue()
}
}
@Test
@ -62,8 +68,11 @@ class DefaultRoomMembersModerationPresenterTest { @@ -62,8 +68,11 @@ class DefaultRoomMembersModerationPresenterTest {
activeMemberCount = 10,
canBanResult = { Result.success(true) },
)
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
assertThat(presenter.canDisplayModerationActions()).isTrue()
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
presenter.test {
skipItems(1)
assertThat(awaitItem().canDisplayModerationActions).isTrue()
}
}
@Test
@ -74,7 +83,7 @@ class DefaultRoomMembersModerationPresenterTest { @@ -74,7 +83,7 @@ class DefaultRoomMembersModerationPresenterTest {
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
)
val selectedMember = aVictor()
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -101,7 +110,7 @@ class DefaultRoomMembersModerationPresenterTest { @@ -101,7 +110,7 @@ class DefaultRoomMembersModerationPresenterTest {
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
)
val selectedMember = aRoomMember(A_USER_ID_2, powerLevel = 100L)
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -125,7 +134,7 @@ class DefaultRoomMembersModerationPresenterTest { @@ -125,7 +134,7 @@ class DefaultRoomMembersModerationPresenterTest {
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
)
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -148,7 +157,7 @@ class DefaultRoomMembersModerationPresenterTest { @@ -148,7 +157,7 @@ class DefaultRoomMembersModerationPresenterTest {
kickUserResult = { _, _ -> Result.success(Unit) },
)
val selectedMember = aVictor()
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -176,7 +185,7 @@ class DefaultRoomMembersModerationPresenterTest { @@ -176,7 +185,7 @@ class DefaultRoomMembersModerationPresenterTest {
banUserResult = { _, _ -> Result.success(Unit) },
)
val selectedMember = aVictor()
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -211,7 +220,7 @@ class DefaultRoomMembersModerationPresenterTest { @@ -211,7 +220,7 @@ class DefaultRoomMembersModerationPresenterTest {
).apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(selectedMember)))
}
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -237,7 +246,7 @@ class DefaultRoomMembersModerationPresenterTest { @@ -237,7 +246,7 @@ class DefaultRoomMembersModerationPresenterTest {
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.USER) },
)
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -261,7 +270,7 @@ class DefaultRoomMembersModerationPresenterTest { @@ -261,7 +270,7 @@ class DefaultRoomMembersModerationPresenterTest {
unBanUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
userRoleResult = { Result.success(RoomMember.Role.USER) },
)
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -301,12 +310,12 @@ class DefaultRoomMembersModerationPresenterTest { @@ -301,12 +310,12 @@ class DefaultRoomMembersModerationPresenterTest {
}
}
private fun TestScope.createDefaultRoomMembersModerationPresenter(
private fun TestScope.createRoomMembersModerationPresenter(
matrixRoom: FakeMatrixRoom = FakeMatrixRoom(),
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
): DefaultRoomMembersModerationPresenter {
return DefaultRoomMembersModerationPresenter(
): RoomMembersModerationPresenter {
return RoomMembersModerationPresenter(
room = matrixRoom,
dispatchers = dispatchers,
analyticsService = analyticsService,
Loading…
Cancel
Save