change (member moderation) : fix all existing tests

This commit is contained in:
ganfra 2025-05-19 22:16:17 +02:00
parent e405bf80a6
commit 0b6c5964a1
12 changed files with 64 additions and 726 deletions

View file

@ -35,6 +35,7 @@ import io.element.android.features.messages.impl.timeline.protection.aTimelinePr
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState
import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider
import io.element.android.features.roomcall.api.aStandByCallState import io.element.android.features.roomcall.api.aStandByCallState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@ -1182,6 +1183,9 @@ class MessagesPresenterTest {
textEditorState = aTextEditorStateMarkdown(initialText = "", initialFocus = false) textEditorState = aTextEditorStateMarkdown(initialText = "", initialFocus = false)
) )
}, },
roomMemberModerationPresenter: Presenter<RoomMemberModerationState> = Presenter {
aRoomMemberModerationState()
},
encryptionService: FakeEncryptionService = FakeEncryptionService(), encryptionService: FakeEncryptionService = FakeEncryptionService(),
actionListEventSink: (ActionListEvents) -> Unit = {}, actionListEventSink: (ActionListEvents) -> Unit = {},
): MessagesPresenter { ): MessagesPresenter {
@ -1199,6 +1203,7 @@ class MessagesPresenterTest {
linkPresenter = { aLinkState() }, linkPresenter = { aLinkState() },
pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() }, pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() },
roomCallStatePresenter = { aStandByCallState() }, roomCallStatePresenter = { aStandByCallState() },
roomMemberModerationPresenter = roomMemberModerationPresenter,
syncService = FakeSyncService(), syncService = FakeSyncService(),
snackbarDispatcher = SnackbarDispatcher(), snackbarDispatcher = SnackbarDispatcher(),
navigator = navigator, navigator = navigator,

View file

@ -53,6 +53,9 @@ import io.element.android.features.messages.impl.timeline.components.receipt.bot
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@ -310,40 +313,42 @@ class MessagesViewTest {
@Test @Test
@Config(qualifiers = "h1024dp") @Config(qualifiers = "h1024dp")
fun `clicking on the avatar of the sender of an Event invoke expected callback`() { fun `clicking on the avatar of the sender of an Event emits the expected event`() {
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<MessagesEvents>()
val state = aMessagesState( val state = aMessagesState(
eventSink = eventsRecorder eventSink = eventsRecorder
) )
val timelineItem = state.timelineState.timelineItems.first() val timelineEvent = state.timelineState.timelineItems.filterIsInstance<TimelineItem.Event>().first()
ensureCalledOnceWithParam( rule.setMessagesView(state = state)
param = (timelineItem as TimelineItem.Event).senderId rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
) { callback -> eventsRecorder.assertSingle(
rule.setMessagesView( MessagesEvents.OnUserClicked(
state = state, MatrixUser(
onUserDataClick = callback, userId = timelineEvent.senderId,
displayName = timelineEvent.senderProfile.getDisplayName(),
avatarUrl = timelineEvent.senderProfile.getAvatarUrl()
)
) )
rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() )
}
} }
@Test @Test
@Config(qualifiers = "h1024dp") @Config(qualifiers = "h1024dp")
fun `clicking on the display name of the sender of an Event invoke expected callback`() { fun `clicking on the display name of the sender of an Event emits expected event`() {
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<MessagesEvents>()
val state = aMessagesState( val state = aMessagesState(eventSink = eventsRecorder)
eventSink = eventsRecorder val timelineEvent = state.timelineState.timelineItems.filterIsInstance<TimelineItem.Event>().first()
) rule.setMessagesView(state = state)
val timelineItem = state.timelineState.timelineItems.first() rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
ensureCalledOnceWithParam( eventsRecorder.assertSingle(
param = (timelineItem as TimelineItem.Event).senderId MessagesEvents.OnUserClicked(
) { callback -> MatrixUser(
rule.setMessagesView( userId = timelineEvent.senderId,
state = state, displayName = timelineEvent.senderProfile.getDisplayName(),
onUserDataClick = callback, avatarUrl = timelineEvent.senderProfile.getAvatarUrl()
)
) )
rule.onNodeWithTag(TestTags.timelineItemSenderName.value, useUnmergedTree = true).performClick() )
}
} }
@Test @Test

View file

@ -22,6 +22,7 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemList
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemFileContent
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.EventsRecorder
@ -99,7 +100,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setPinne
state: PinnedMessagesListState, state: PinnedMessagesListState,
onBackClick: () -> Unit = EnsureNeverCalled(), onBackClick: () -> Unit = EnsureNeverCalled(),
onEventClick: (event: TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), onEventClick: (event: TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(), onUserDataClick: (MatrixUser) -> Unit = EnsureNeverCalledWithParam(),
onLinkClick: (Link) -> Unit = EnsureNeverCalledWithParam(), onLinkClick: (Link) -> Unit = EnsureNeverCalledWithParam(),
onLinkLongClick: (Link) -> Unit = EnsureNeverCalledWithParam(), onLinkLongClick: (Link) -> Unit = EnsureNeverCalledWithParam(),
) { ) {

View file

@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EnsureNeverCalledWithParam
@ -175,7 +176,7 @@ class TimelineViewTest {
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimelineView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimelineView(
state: TimelineState, state: TimelineState,
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(), timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(), onUserDataClick: (MatrixUser) -> Unit = EnsureNeverCalledWithParam(),
onLinkClick: (Link) -> Unit = EnsureNeverCalledWithParam(), onLinkClick: (Link) -> Unit = EnsureNeverCalledWithParam(),
onMessageClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), onMessageClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
onMessageLongClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), onMessageLongClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),

View file

@ -29,7 +29,7 @@ import io.element.android.services.analytics.api.AnalyticsService
class RoomMemberListNode @AssistedInject constructor( class RoomMemberListNode @AssistedInject constructor(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
presenterFactory: RoomMemberListPresenter.Factory, private val presenter: RoomMemberListPresenter,
private val analyticsService: AnalyticsService, private val analyticsService: AnalyticsService,
private val roomMemberModerationRenderer: RoomMemberModerationRenderer, private val roomMemberModerationRenderer: RoomMemberModerationRenderer,
) : Node(buildContext, plugins = plugins), RoomMemberListNavigator { ) : Node(buildContext, plugins = plugins), RoomMemberListNavigator {
@ -39,7 +39,6 @@ class RoomMemberListNode @AssistedInject constructor(
} }
private val callbacks = plugins<Callback>() private val callbacks = plugins<Callback>()
private val presenter = presenterFactory.create(this)
init { init {
lifecycle.subscribe( lifecycle.subscribe(

View file

@ -16,8 +16,6 @@ import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationAction
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
@ -53,12 +51,7 @@ class RoomMemberListPresenter @AssistedInject constructor(
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val roomMembersModerationPresenter: Presenter<RoomMemberModerationState>, private val roomMembersModerationPresenter: Presenter<RoomMemberModerationState>,
private val encryptionService: EncryptionService, private val encryptionService: EncryptionService,
@Assisted private val navigator: RoomMemberListNavigator,
) : Presenter<RoomMemberListState> { ) : Presenter<RoomMemberListState> {
@AssistedFactory
interface Factory {
fun create(navigator: RoomMemberListNavigator): RoomMemberListPresenter
}
@Composable @Composable
override fun present(): RoomMemberListState { override fun present(): RoomMemberListState {
@ -168,10 +161,8 @@ class RoomMemberListPresenter @AssistedInject constructor(
is RoomMemberListEvents.RoomMemberSelected -> is RoomMemberListEvents.RoomMemberSelected ->
if (event.roomMember.membership == RoomMembershipState.BAN) { if (event.roomMember.membership == RoomMembershipState.BAN) {
roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser, event.roomMember.toMatrixUser())) roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser, event.roomMember.toMatrixUser()))
} else if (!isDm.value && (roomModerationState.canBan || roomModerationState.canKick)) {
roomModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.roomMember.toMatrixUser()))
} else { } else {
navigator.openRoomMemberDetails(event.roomMember.userId) roomModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.roomMember.toMatrixUser()))
} }
} }
} }

View file

@ -11,9 +11,8 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState import io.element.android.libraries.architecture.Presenter
import io.element.android.features.roomdetails.impl.members.moderation.aRoomMembersModerationState
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState 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.core.UserId
@ -24,7 +23,6 @@ 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.FakeBaseRoom
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo 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.WarmUpRule
import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -42,12 +40,12 @@ class RoomMemberListPresenterTest {
fun `member loading is done automatically on start, but is async`() = runTest { fun `member loading is done automatically on start, but is async`() = runTest {
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) }, updateMembersResult = { Result.success(Unit) },
canInviteResult = { Result.success(true) } canInviteResult = { Result.success(true) }
).apply { ).apply {
// Needed to avoid discarding the loaded members as a partial and invalid result // Needed to avoid discarding the loaded members as a partial and invalid result
givenRoomInfo(aRoomInfo(joinedMembersCount = 2)) givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
} }
) )
val presenter = createPresenter(joinedRoom = room) val presenter = createPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
@ -97,9 +95,9 @@ class RoomMemberListPresenterTest {
val presenter = createPresenter( val presenter = createPresenter(
joinedRoom = FakeJoinedRoom( joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) }, updateMembersResult = { Result.success(Unit) },
canInviteResult = { Result.success(true) } canInviteResult = { Result.success(true) }
) )
) )
) )
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
@ -204,12 +202,12 @@ class RoomMemberListPresenterTest {
} }
@Test @Test
fun `present - RoomMemberSelected by default opens the room member details through the navigator`() = runTest { fun `present - RoomMemberSelected will open the moderation options when target user is not banned`() = runTest {
val navigator = FakeRoomMemberListNavigator() val roomMemberModerationPresenter= Presenter {
val roomMembersModerationStateLambda = { aRoomMembersModerationState(canDisplayModerationActions = false) } aRoomMemberModerationState(canBan = true, canKick = true)
}
val presenter = createPresenter( val presenter = createPresenter(
roomMembersModerationStateLambda = roomMembersModerationStateLambda, roomMemberModerationPresenter = roomMemberModerationPresenter,
navigator = navigator,
joinedRoom = FakeJoinedRoom( joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) }, updateMembersResult = { Result.success(Unit) },
@ -222,36 +220,6 @@ class RoomMemberListPresenterTest {
}.test { }.test {
skipItems(1) skipItems(1)
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor())) awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor()))
assertThat(navigator.openRoomMemberDetailsCallCount).isEqualTo(1)
}
}
@Test
fun `present - RoomMemberSelected will open the moderation options if the current user can use them`() = runTest {
val navigator = FakeRoomMemberListNavigator()
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMembersModerationStateLambda = {
aRoomMembersModerationState(
canDisplayModerationActions = true,
eventSink = eventsRecorder,
)
}
val presenter = createPresenter(
roomMembersModerationStateLambda = roomMembersModerationStateLambda,
navigator = navigator,
joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) },
canInviteResult = { Result.success(true) }
)
)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor()))
eventsRecorder.assertSingle(RoomMembersModerationEvents.SelectRoomMember(aVictor()))
} }
} }
} }
@ -277,19 +245,19 @@ private fun TestScope.createDataSource(
private fun TestScope.createPresenter( private fun TestScope.createPresenter(
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true), coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
joinedRoom: JoinedRoom = FakeJoinedRoom( joinedRoom: JoinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
updateMembersResult = { Result.success(Unit) } updateMembersResult = { Result.success(Unit) }
) )
), ),
roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers), roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers),
roomMembersModerationStateLambda: () -> RoomMembersModerationState = { aRoomMembersModerationState() },
encryptedService: FakeEncryptionService = FakeEncryptionService(), encryptedService: FakeEncryptionService = FakeEncryptionService(),
navigator: RoomMemberListNavigator = object : RoomMemberListNavigator {} roomMemberModerationPresenter: Presenter<RoomMemberModerationState> = Presenter {
aRoomMemberModerationState()
},
) = RoomMemberListPresenter( ) = RoomMemberListPresenter(
room = joinedRoom, room = joinedRoom,
roomMemberListDataSource = roomMemberListDataSource, roomMemberListDataSource = roomMemberListDataSource,
coroutineDispatchers = coroutineDispatchers, coroutineDispatchers = coroutineDispatchers,
roomMembersModerationPresenter = { roomMembersModerationStateLambda() }, roomMembersModerationPresenter = roomMemberModerationPresenter,
encryptionService = encryptedService, encryptionService = encryptedService,
navigator = navigator,
) )

View file

@ -1,351 +0,0 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.roomdetails.impl.members.moderation
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.RoomModeration
import io.element.android.features.roomdetails.impl.aJoinedRoom
import io.element.android.features.roomdetails.impl.members.aRoomMember
import io.element.android.features.roomdetails.impl.members.aVictor
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomMember
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.A_REASON
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
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 RoomMembersModerationPresenterTest {
@Test
fun `canDisplayModerationActions - when room is DM is false`() = runTest {
val room = aJoinedRoom(
isPublic = true,
activeMemberCount = 2,
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
).apply {
givenRoomInfo(aRoomInfo(isDirect = true, activeMembersCount = 2))
}
val presenter = createRoomMembersModerationPresenter(joinedRoom = room)
presenter.test {
assertThat(awaitItem().canDisplayModerationActions).isFalse()
}
}
@Test
fun `canDisplayModerationActions - when user can kick other users, FF is enabled and room is not a DM returns true`() = runTest {
val room = aJoinedRoom(
activeMemberCount = 10,
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
)
val presenter = createRoomMembersModerationPresenter(joinedRoom = room)
presenter.test {
skipItems(1)
assertThat(awaitItem().canDisplayModerationActions).isTrue()
}
}
@Test
fun `canDisplayModerationActions - when user can ban other users, FF is enabled and room is not a DM returns true`() = runTest {
val room = aJoinedRoom(
activeMemberCount = 10,
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
)
val presenter = createRoomMembersModerationPresenter(joinedRoom = room)
presenter.test {
skipItems(1)
assertThat(awaitItem().canDisplayModerationActions).isTrue()
}
}
@Test
fun `present - SelectRoomMember when the current user has permissions displays member actions`() = runTest {
val room = aJoinedRoom(
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
)
val selectedMember = aVictor()
val presenter = createRoomMembersModerationPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember))
with(awaitItem()) {
assertThat(this.selectedRoomMember).isNotNull()
assertThat(this.selectedRoomMember?.userId).isEqualTo(selectedMember.userId)
assertThat(actions).containsExactly(
ModerationAction.DisplayProfile(selectedMember.userId),
ModerationAction.KickUser(selectedMember.userId),
ModerationAction.BanUser(selectedMember.userId)
)
}
}
}
@Test
fun `present - SelectRoomMember displays only view profile if selected member has same power level as the current user`() = runTest {
val room = aJoinedRoom(
sessionId = A_USER_ID,
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
)
val selectedMember = aRoomMember(A_USER_ID_2, powerLevel = 100L)
val presenter = createRoomMembersModerationPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember))
with(awaitItem()) {
assertThat(this.selectedRoomMember).isNotNull()
assertThat(this.selectedRoomMember?.userId).isEqualTo(selectedMember.userId)
assertThat(actions).containsExactly(
ModerationAction.DisplayProfile(selectedMember.userId),
)
}
}
}
@Test
fun `present - SelectRoomMember displays an unban confirmation dialog when the member is banned`() = runTest {
val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN)
val room = aJoinedRoom(
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
)
val presenter = createRoomMembersModerationPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember))
with(awaitItem()) {
assertThat(selectedRoomMember).isNull()
assertThat(unbanUserAsyncAction).isEqualTo(ConfirmingRoomMemberAction(selectedMember))
}
}
}
@Test
fun `present - Kick requires confirmation and then kicks the user`() = runTest {
val analyticsService = FakeAnalyticsService()
val kickUserResult = lambdaRecorder<UserId, String?, Result<Unit>> { _, _ -> Result.success(Unit) }
val room = aJoinedRoom(
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
kickUserResult = kickUserResult,
)
val selectedMember = aVictor()
val presenter = createRoomMembersModerationPresenter(joinedRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember))
awaitItem().eventSink(RoomMembersModerationEvents.KickUser)
val confirmingState = awaitItem()
assertThat(confirmingState.kickUserAsyncAction).isEqualTo(AsyncAction.ConfirmingNoParams)
// Confirm
confirmingState.eventSink(RoomMembersModerationEvents.DoKickUser(reason = A_REASON))
skipItems(1)
val loadingState = awaitItem()
assertThat(loadingState.actions).isEmpty()
assertThat(loadingState.kickUserAsyncAction).isEqualTo(AsyncAction.Loading)
with(awaitItem()) {
assertThat(kickUserAsyncAction).isEqualTo(AsyncAction.Success(Unit))
assertThat(selectedRoomMember).isNull()
}
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.KickMember))
kickUserResult.assertions().isCalledOnce().with(
value(selectedMember.userId),
value(A_REASON),
)
}
}
@Test
fun `present - BanUser requires confirmation and then bans the user`() = runTest {
val analyticsService = FakeAnalyticsService()
val banUserResult = lambdaRecorder<UserId, String?, Result<Unit>> { _, _ -> Result.success(Unit) }
val room = aJoinedRoom(
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
banUserResult = banUserResult,
)
val selectedMember = aVictor()
val presenter = createRoomMembersModerationPresenter(joinedRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember))
awaitItem().eventSink(RoomMembersModerationEvents.BanUser)
val confirmingState = awaitItem()
assertThat(confirmingState.banUserAsyncAction).isEqualTo(AsyncAction.ConfirmingNoParams)
// Confirm
confirmingState.eventSink(RoomMembersModerationEvents.DoBanUser(reason = A_REASON))
skipItems(1)
val loadingItem = awaitItem()
assertThat(loadingItem.actions).isEmpty()
assertThat(loadingItem.selectedRoomMember).isNull()
assertThat(loadingItem.banUserAsyncAction).isEqualTo(AsyncAction.Loading)
with(awaitItem()) {
assertThat(banUserAsyncAction).isEqualTo(AsyncAction.Success(Unit))
assertThat(selectedRoomMember).isNull()
}
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.BanMember))
banUserResult.assertions().isCalledOnce().with(
value(selectedMember.userId),
value(A_REASON),
)
}
}
@Test
fun `present - UnbanUser requires confirmation and then unbans the user`() = runTest {
val analyticsService = FakeAnalyticsService()
val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN)
val room = aJoinedRoom(
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
unBanUserResult = { _, _ -> Result.success(Unit) },
).apply {
givenRoomMembersState(RoomMembersState.Ready(persistentListOf(selectedMember)))
}
val presenter = createRoomMembersModerationPresenter(joinedRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
// Displays unban confirmation dialog
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(selectedMember))
val confirmingState = awaitItem()
assertThat(confirmingState.selectedRoomMember).isNull()
assertThat(confirmingState.actions).isEmpty()
assertThat(confirmingState.unbanUserAsyncAction).isEqualTo(ConfirmingRoomMemberAction(selectedMember))
// Confirms unban
confirmingState.eventSink(RoomMembersModerationEvents.UnbanUser(selectedMember.userId))
assertThat(awaitItem().unbanUserAsyncAction).isEqualTo(AsyncAction.Loading)
with(awaitItem()) {
assertThat(unbanUserAsyncAction).isEqualTo(AsyncAction.Success(Unit))
assertThat(selectedRoomMember).isNull()
}
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.UnbanMember))
}
}
@Test
fun `present - Reset removes the selected user and actions`() = runTest {
val room = aJoinedRoom(
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
userRoleResult = { Result.success(RoomMember.Role.USER) },
)
val presenter = createRoomMembersModerationPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
// Select a user
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor()))
// Reset state
awaitItem().eventSink(RoomMembersModerationEvents.Reset)
val finalItem = awaitItem()
assertThat(finalItem.selectedRoomMember).isNull()
assertThat(finalItem.actions).isEmpty()
}
}
@Test
fun `present - Reset resets any async actions`() = runTest {
val room = aJoinedRoom(
canKickResult = { Result.success(true) },
canBanResult = { Result.success(true) },
kickUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
banUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
unBanUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
userRoleResult = { Result.success(RoomMember.Role.USER) },
)
val presenter = createRoomMembersModerationPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialItem = awaitItem()
// Kick user and fail
awaitItem().eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor()))
awaitItem().eventSink(RoomMembersModerationEvents.DoKickUser(reason = ""))
skipItems(1)
assertThat(awaitItem().kickUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().kickUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java)
// Reset it
initialItem.eventSink(RoomMembersModerationEvents.Reset)
assertThat(awaitItem().kickUserAsyncAction).isEqualTo(AsyncAction.Uninitialized)
// Ban user and fail
initialItem.eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor()))
awaitItem().eventSink(RoomMembersModerationEvents.DoBanUser(reason = ""))
skipItems(1)
assertThat(awaitItem().banUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().banUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java)
// Reset it
initialItem.eventSink(RoomMembersModerationEvents.Reset)
assertThat(awaitItem().banUserAsyncAction).isEqualTo(AsyncAction.Uninitialized)
// Unban user and fail
initialItem.eventSink(RoomMembersModerationEvents.SelectRoomMember(aVictor().copy(membership = RoomMembershipState.BAN)))
val confirmingState = awaitItem()
assertThat(confirmingState.unbanUserAsyncAction).isInstanceOf(AsyncAction.Confirming::class.java)
confirmingState.eventSink(RoomMembersModerationEvents.UnbanUser(aVictor().userId))
assertThat(awaitItem().unbanUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().unbanUserAsyncAction).isInstanceOf(AsyncAction.Failure::class.java)
// Reset it
initialItem.eventSink(RoomMembersModerationEvents.Reset)
assertThat(awaitItem().unbanUserAsyncAction).isEqualTo(AsyncAction.Uninitialized)
}
}
private fun TestScope.createRoomMembersModerationPresenter(
joinedRoom: FakeJoinedRoom = aJoinedRoom(),
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
): RoomMembersModerationPresenter {
return RoomMembersModerationPresenter(
room = joinedRoom,
dispatchers = dispatchers,
analyticsService = analyticsService,
)
}
}

View file

@ -1,274 +0,0 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.roomdetails.impl.members.moderation
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTextInput
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.roomdetails.impl.R
import io.element.android.features.roomdetails.impl.members.anAlice
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.test.A_REASON
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressBackKey
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
class RoomMembersModerationViewTest {
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
@Ignore("This test is not passing yet, need to investigate")
@Test
fun `clicking on back emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
actions = listOf(
ModerationAction.DisplayProfile(roomMember.userId),
),
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
rule.pressBackKey()
// Give time for the bottom sheet to animate
rule.mainClock.advanceTimeBy(1_000)
eventsRecorder.assertSingle(RoomMembersModerationEvents.Reset)
}
@Test
fun `clicking on 'See user info' invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>(expectEvents = false)
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
actions = listOf(
ModerationAction.DisplayProfile(roomMember.userId),
),
eventSink = eventsRecorder
)
ensureCalledOnceWithParam(roomMember.userId) { callback ->
rule.setRoomMembersModerationView(
state = state,
onDisplayMemberProfile = callback
)
rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_member_user_info)
}
}
@Config(qualifiers = "h1024dp")
@Test
fun `clicking on 'Remove member' emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
actions = listOf(
ModerationAction.DisplayProfile(roomMember.userId),
ModerationAction.KickUser(roomMember.userId),
),
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_remove)
// Give time for the bottom sheet to animate
rule.mainClock.advanceTimeBy(1_000)
eventsRecorder.assertSingle(RoomMembersModerationEvents.KickUser)
}
@Test
fun `cancelling 'Remove member' confirmation emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
kickUserAsyncAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
// Note: the string key semantics is not perfect here :/
rule.clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(RoomMembersModerationEvents.Reset)
}
@Test
fun `confirming 'Remove member' reason edition then validation emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
kickUserAsyncAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
val reason = rule.activity.getString(CommonStrings.common_reason)
rule.onNodeWithText(reason).performTextInput(A_REASON)
rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_kick_member_confirmation_action)
eventsRecorder.assertSingle(RoomMembersModerationEvents.DoKickUser(reason = A_REASON))
}
@Test
fun `confirming 'Remove member' confirmation emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
kickUserAsyncAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
// Note: the string key semantics is not perfect here :/
rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_kick_member_confirmation_action)
eventsRecorder.assertSingle(RoomMembersModerationEvents.DoKickUser(reason = ""))
}
@Config(qualifiers = "h1024dp")
@Test
fun `clicking on 'Remove and ban member' emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
actions = listOf(
ModerationAction.DisplayProfile(roomMember.userId),
ModerationAction.KickUser(roomMember.userId),
ModerationAction.BanUser(roomMember.userId),
),
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
// Note: the string key semantics is not perfect here :/
rule.clickOn(R.string.screen_room_member_list_manage_member_remove_confirmation_ban)
// Give time for the bottom sheet to animate
rule.mainClock.advanceTimeBy(1_000)
eventsRecorder.assertSingle(RoomMembersModerationEvents.BanUser)
}
@Test
fun `cancelling 'Remove and ban member' confirmation emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
banUserAsyncAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
// Note: the string key semantics is not perfect here :/
rule.clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(RoomMembersModerationEvents.Reset)
}
@Test
fun `confirming 'Remove and ban member' reason edition emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
banUserAsyncAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
val reason = rule.activity.getString(CommonStrings.common_reason)
rule.onNodeWithText(reason).performTextInput(A_REASON)
rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_ban_member_confirmation_action)
eventsRecorder.assertSingle(RoomMembersModerationEvents.DoBanUser(reason = A_REASON))
}
@Test
fun `confirming 'Remove and ban member' confirmation emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
banUserAsyncAction = AsyncAction.ConfirmingNoParams,
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
// Note: the string key semantics is not perfect here :/
rule.clickOn(CommonStrings.screen_bottom_sheet_manage_room_member_ban_member_confirmation_action)
eventsRecorder.assertSingle(RoomMembersModerationEvents.DoBanUser(reason = ""))
}
@Test
fun `cancelling 'Unban member' confirmation emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
unbanUserAsyncAction = ConfirmingRoomMemberAction(roomMember),
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
// Note: the string key semantics is not perfect here :/
rule.clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(RoomMembersModerationEvents.Reset)
}
@Test
fun `confirming 'Unban member' confirmation emits the expected event`() {
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
val roomMember = anAlice()
val state = aRoomMembersModerationState(
selectedRoomMember = roomMember,
unbanUserAsyncAction = ConfirmingRoomMemberAction(roomMember),
eventSink = eventsRecorder
)
rule.setRoomMembersModerationView(
state = state,
)
// Note: the string key semantics is not perfect here :/
rule.clickOn(R.string.screen_room_member_list_manage_member_unban_action)
eventsRecorder.assertSingle(RoomMembersModerationEvents.UnbanUser(roomMember.userId))
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomMembersModerationView(
state: RoomMembersModerationState,
onDisplayMemberProfile: (UserId) -> Unit = EnsureNeverCalledWithParam()
) {
setContent {
RoomMembersModerationView(
state = state,
onDisplayMemberProfile = onDisplayMemberProfile,
)
}
}

View file

@ -7,13 +7,10 @@
package io.element.android.features.roommembermoderation.impl package io.element.android.features.roommembermoderation.impl
import io.element.android.features.roommembermoderation.api.ModerationAction
import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.ModerationActionState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList

View file

@ -12,15 +12,11 @@ import io.element.android.features.roommembermoderation.api.ModerationAction
import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.ModerationActionState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
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.toMatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
class RoomMemberModerationStateProvider : PreviewParameterProvider<InternalRoomMemberModerationState> { class InternalRoomMemberModerationStateProvider : PreviewParameterProvider<InternalRoomMemberModerationState> {
override val values: Sequence<InternalRoomMemberModerationState> override val values: Sequence<InternalRoomMemberModerationState>
get() = sequenceOf( get() = sequenceOf(
aRoomMembersModerationState( aRoomMembersModerationState(

View file

@ -317,7 +317,7 @@ private fun RoomMemberActionsBottomSheet(
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun RoomMembersModerationViewPreview(@PreviewParameter(RoomMemberModerationStateProvider::class) state: InternalRoomMemberModerationState) { internal fun RoomMemberModerationViewPreview(@PreviewParameter(InternalRoomMemberModerationStateProvider::class) state: InternalRoomMemberModerationState) {
ElementPreview { ElementPreview {
Box( Box(
modifier = Modifier modifier = Modifier