diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomDetailsModule.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomDetailsModule.kt new file mode 100644 index 0000000000..0cabbdd09c --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomDetailsModule.kt @@ -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 +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 4ef746b0e7..c6fd2bc0b8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -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( private val room: MatrixRoom, private val roomMemberListDataSource: RoomMemberListDataSource, private val coroutineDispatchers: CoroutineDispatchers, - private val roomMembersModerationPresenter: RoomMembersModerationPresenter, + private val roomMembersModerationPresenter: Presenter, @Assisted private val navigator: RoomMemberListNavigator, ) : Presenter { @AssistedFactory @@ -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) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt deleted file mode 100644 index e2081f6c6f..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt +++ /dev/null @@ -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(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()) } - - val kickUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } - val banUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } - val unbanUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } - - 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>, - ) = runActionAndWaitForMembershipChange(kickUserAction) { - analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember)) - room.kickUser(userId).finally { selectedMember = null } - } - - private fun CoroutineScope.banUser( - userId: UserId, - banUserAction: MutableState>, - ) = runActionAndWaitForMembershipChange(banUserAction) { - analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember)) - room.banUser(userId).finally { selectedMember = null } - } - - private fun CoroutineScope.unbanUser( - userId: UserId, - unbanUserAction: MutableState>, - ) = runActionAndWaitForMembershipChange(unbanUserAction) { - analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember)) - room.unbanUser(userId).finally { selectedMember = null } - } - - private fun CoroutineScope.runActionAndWaitForMembershipChange(action: MutableState>, block: suspend () -> Result) { - launch(dispatchers.io) { - action.runUpdatingState { - val result = block() - if (result.isSuccess) { - room.membersStateFlow.drop(1).take(1) - } - result - } - } - } -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt index cdc3ecce03..a2dce0fe2d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt @@ -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 { - 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 { + private var selectedMember by mutableStateOf(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()) } + + 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) } + val banUserAsyncAction = + remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } + val unbanUserAsyncAction = + remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } + + 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>, + ) = runActionAndWaitForMembershipChange(kickUserAction) { + analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember)) + room.kickUser(userId).finally { selectedMember = null } + } + + private fun CoroutineScope.banUser( + userId: UserId, + banUserAction: MutableState>, + ) = runActionAndWaitForMembershipChange(banUserAction) { + analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember)) + room.banUser(userId).finally { selectedMember = null } + } + + private fun CoroutineScope.unbanUser( + userId: UserId, + unbanUserAction: MutableState>, + ) = runActionAndWaitForMembershipChange(unbanUserAction) { + analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember)) + room.unbanUser(userId).finally { selectedMember = null } + } + + private fun CoroutineScope.runActionAndWaitForMembershipChange( + action: MutableState>, + block: suspend () -> Result + ) { + launch(dispatchers.io) { + action.runUpdatingState { + val result = block() + if (result.isSuccess) { + room.membersStateFlow.drop(1).take(1) + } + result + } + } + } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationState.kt index c906ebd561..18db706438 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationState.kt +++ b/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 import kotlinx.collections.immutable.ImmutableList data class RoomMembersModerationState( + val canDisplayModerationActions: Boolean, val selectedRoomMember: RoomMember?, val actions: ImmutableList, val kickUserAsyncAction: AsyncAction, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStatePreviewProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStatePreviewProvider.kt index 18030139e5..d7fb509995 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStatePreviewProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationStatePreviewProvider.kt @@ -71,6 +71,7 @@ class RoomMembersModerationStatePreviewProvider : PreviewParameterProvider = emptyList(), kickUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, @@ -79,6 +80,7 @@ fun aRoomMembersModerationState( canDisplayBannedUsers: Boolean = false, eventSink: (RoomMembersModerationEvents) -> Unit = {}, ) = RoomMembersModerationState( + canDisplayModerationActions = canDisplayModerationActions, selectedRoomMember = selectedRoomMember, actions = actions.toPersistentList(), kickUserAsyncAction = kickUserAsyncAction, diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt index 6ba283d7ef..e9414f90be 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt +++ b/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 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 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 { @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 { @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() + 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 { }.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( 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 ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/FakeRoomMembersModerationPresenter.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/FakeRoomMembersModerationPresenter.kt deleted file mode 100644 index 96745130b7..0000000000 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/FakeRoomMembersModerationPresenter.kt +++ /dev/null @@ -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 - } -} diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/RoomMembersModerationPresenterTest.kt similarity index 89% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt rename to features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/RoomMembersModerationPresenterTest.kt index 926ca659d5..c2acd0a7de 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt +++ b/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 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 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { ).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 { 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 { 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 { } } - 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,