Merge pull request #5786 from element-hq/feature/bma/addAdminConfirmation
Ensure confirmation dialog is displayed when an admin add other admin to a room
This commit is contained in:
@@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
@@ -104,7 +105,11 @@ class ChangeRolesPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
val hasPendingChanges = usersWithRole.value != selectedUsers.value
|
||||
val hasPendingChanges by remember {
|
||||
derivedStateOf {
|
||||
usersWithRole.value.toSet() != selectedUsers.value.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||
fun canChangeMemberRole(userId: UserId): Boolean {
|
||||
@@ -134,16 +139,20 @@ class ChangeRolesPresenter(
|
||||
is ChangeRolesEvent.Save -> {
|
||||
val currentUserIsAdmin = roomInfo.roleOf(room.sessionId) == RoomMember.Role.Admin
|
||||
val isModifyingAdmins = role == RoomMember.Role.Admin
|
||||
val hasChanges = selectedUsers != usersWithRole
|
||||
val isConfirming = saveState.value.isConfirming()
|
||||
val modifyingOwners = role is RoomMember.Role.Owner
|
||||
|
||||
val needsConfirmation = (modifyingOwners || currentUserIsAdmin && isModifyingAdmins) && hasChanges && !isConfirming
|
||||
|
||||
val confirmationValue = if (hasPendingChanges && !isConfirming) {
|
||||
when {
|
||||
modifyingOwners -> ConfirmingModifyingOwners
|
||||
currentUserIsAdmin && isModifyingAdmins -> ConfirmingModifyingAdmins
|
||||
else -> null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
when {
|
||||
needsConfirmation -> {
|
||||
// Confirm modifying users
|
||||
saveState.value = AsyncAction.ConfirmingNoParams
|
||||
confirmationValue != null -> {
|
||||
saveState.value = confirmationValue
|
||||
}
|
||||
!saveState.value.isLoading() -> {
|
||||
roomCoroutineScope.save(usersWithRole.value, selectedUsers, saveState)
|
||||
|
||||
@@ -46,7 +46,7 @@ class ChangeRolesStateProvider : PreviewParameterProvider<ChangeRolesState> {
|
||||
selectedUsers = aMatrixUserList().take(1).toImmutableList(),
|
||||
),
|
||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.ConfirmingCancellation),
|
||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.ConfirmingNoParams),
|
||||
aChangeRolesStateWithSelectedUsers().copy(savingState = ConfirmingModifyingAdmins),
|
||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Loading),
|
||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Success(true)),
|
||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Failure(Exception("boom"))),
|
||||
@@ -58,6 +58,8 @@ class ChangeRolesStateProvider : PreviewParameterProvider<ChangeRolesState> {
|
||||
)
|
||||
),
|
||||
aChangeRolesStateWithOwners(role = RoomMember.Role.Owner(isCreator = false)),
|
||||
aChangeRolesStateWithOwners(role = RoomMember.Role.Owner(isCreator = false))
|
||||
.copy(savingState = ConfirmingModifyingOwners),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -178,29 +178,23 @@ fun ChangeRolesView(
|
||||
onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) }
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
when (state.role) {
|
||||
is RoomMember.Role.Owner -> {
|
||||
ConfirmationDialog(
|
||||
title = stringResource(R.string.screen_room_change_role_confirm_change_owners_title),
|
||||
content = stringResource(R.string.screen_room_change_role_confirm_change_owners_description),
|
||||
submitText = stringResource(CommonStrings.action_continue),
|
||||
onSubmitClick = { state.eventSink(ChangeRolesEvent.Save) },
|
||||
onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) },
|
||||
destructiveSubmit = true,
|
||||
)
|
||||
}
|
||||
is RoomMember.Role.Admin -> {
|
||||
ConfirmationDialog(
|
||||
title = stringResource(R.string.screen_room_change_role_confirm_add_admin_title),
|
||||
content = stringResource(R.string.screen_room_change_role_confirm_add_admin_description),
|
||||
onSubmitClick = { state.eventSink(ChangeRolesEvent.Save) },
|
||||
onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) }
|
||||
)
|
||||
}
|
||||
// No confirmation needed for Moderator or User roles
|
||||
else -> Unit
|
||||
}
|
||||
is ConfirmingModifyingOwners -> {
|
||||
ConfirmationDialog(
|
||||
title = stringResource(R.string.screen_room_change_role_confirm_change_owners_title),
|
||||
content = stringResource(R.string.screen_room_change_role_confirm_change_owners_description),
|
||||
submitText = stringResource(CommonStrings.action_continue),
|
||||
onSubmitClick = { state.eventSink(ChangeRolesEvent.Save) },
|
||||
onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) },
|
||||
destructiveSubmit = true,
|
||||
)
|
||||
}
|
||||
is ConfirmingModifyingAdmins -> {
|
||||
ConfirmationDialog(
|
||||
title = stringResource(R.string.screen_room_change_role_confirm_add_admin_title),
|
||||
content = stringResource(R.string.screen_room_change_role_confirm_add_admin_description),
|
||||
onSubmitClick = { state.eventSink(ChangeRolesEvent.Save) },
|
||||
onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) }
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations 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.rolesandpermissions.impl.roles
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
||||
data object ConfirmingModifyingAdmins : AsyncAction.Confirming
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations 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.rolesandpermissions.impl.roles
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
||||
data object ConfirmingModifyingOwners : AsyncAction.Confirming
|
||||
@@ -8,9 +8,6 @@
|
||||
|
||||
package io.element.android.features.rolesandpermissions.impl.roles
|
||||
|
||||
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.libraries.architecture.AsyncAction
|
||||
@@ -31,6 +28,7 @@ import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues
|
||||
import io.element.android.libraries.previewutils.room.aRoomMemberList
|
||||
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.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -43,9 +41,7 @@ class ChangeRolesPresenterTest {
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createChangeRolesPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
with(awaitItem()) {
|
||||
assertThat(role).isEqualTo(RoomMember.Role.Admin)
|
||||
assertThat(query).isNull()
|
||||
@@ -65,16 +61,14 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().searchResults).isInstanceOf(SearchBarResultState.Results::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - canChangeRole of users with lower power level unless they are owners`() = runTest {
|
||||
fun `present - canChangeRole of users with lower power level unless they are owners - privilegedCreatorRole is true`() = runTest {
|
||||
val creatorUserId = UserId("@creator:matrix.org")
|
||||
val superAdminUserId = UserId("@super_admin:matrix.org")
|
||||
|
||||
@@ -82,6 +76,7 @@ class ChangeRolesPresenterTest {
|
||||
// User is a creator, so they can change roles of other members. So is `creatorUserId`.
|
||||
givenRoomInfo(
|
||||
aRoomInfo(
|
||||
privilegedCreatorRole = true,
|
||||
roomCreators = listOf(sessionId, creatorUserId),
|
||||
roomPowerLevels = RoomPowerLevels(
|
||||
defaultRoomPowerLevelValues(),
|
||||
@@ -99,16 +94,14 @@ class ChangeRolesPresenterTest {
|
||||
|
||||
val roomMemberList = aRoomMemberList() + listOf(
|
||||
// Owner - superadmin
|
||||
aRoomMember(userId = superAdminUserId, role = RoomMember.Role.Owner(isCreator = true)),
|
||||
aRoomMember(userId = superAdminUserId, role = RoomMember.Role.Owner(isCreator = false)),
|
||||
// Owner - creator
|
||||
aRoomMember(userId = creatorUserId, role = RoomMember.Role.Owner(isCreator = true))
|
||||
)
|
||||
givenRoomMembersState(RoomMembersState.Ready(roomMemberList.toImmutableList()))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().run {
|
||||
assertThat(canChangeMemberRole(A_USER_ID_2)).isTrue() // Admin
|
||||
@@ -118,6 +111,53 @@ class ChangeRolesPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - canChangeRole of users with lower power level unless they are owners - privilegedCreatorRole is false`() = runTest {
|
||||
val creatorUserId = UserId("@creator:matrix.org")
|
||||
val superAdminUserId = UserId("@super_admin:matrix.org")
|
||||
|
||||
val room = FakeJoinedRoom().apply {
|
||||
// User is a creator, so they can change roles of other members. So is `creatorUserId`.
|
||||
givenRoomInfo(
|
||||
aRoomInfo(
|
||||
privilegedCreatorRole = false,
|
||||
roomCreators = listOf(sessionId, creatorUserId),
|
||||
roomPowerLevels = RoomPowerLevels(
|
||||
defaultRoomPowerLevelValues(),
|
||||
users = persistentMapOf(
|
||||
// Creator is an admin
|
||||
sessionId to RoomMember.Role.Admin.powerLevel,
|
||||
creatorUserId to RoomMember.Role.Admin.powerLevel,
|
||||
// bob is Admin
|
||||
A_USER_ID_2 to RoomMember.Role.Admin.powerLevel,
|
||||
// carol is Moderator
|
||||
A_USER_ID_3 to RoomMember.Role.Moderator.powerLevel,
|
||||
// super_admin is Owner - Superadmin
|
||||
superAdminUserId to RoomMember.Role.Owner(isCreator = false).powerLevel,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val roomMemberList = aRoomMemberList() + listOf(
|
||||
// Owner - superadmin
|
||||
aRoomMember(userId = superAdminUserId, role = RoomMember.Role.Owner(isCreator = false)),
|
||||
// Owner - creator
|
||||
aRoomMember(userId = creatorUserId, role = RoomMember.Role.Owner(isCreator = true))
|
||||
)
|
||||
givenRoomMembersState(RoomMembersState.Ready(roomMemberList.toImmutableList()))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().run {
|
||||
assertThat(canChangeMemberRole(A_USER_ID_2)).isFalse() // Creator cannot update Admin in this case
|
||||
assertThat(canChangeMemberRole(A_USER_ID_3)).isTrue() // Moderator
|
||||
assertThat(canChangeMemberRole(creatorUserId)).isFalse() // Owner
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when modifying admins, creators are displayed too`() = runTest {
|
||||
val room = FakeJoinedRoom().apply {
|
||||
@@ -129,9 +169,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomMembersState(RoomMembersState.Ready(memberList))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().searchResults.run {
|
||||
assertThat(this).isInstanceOf(SearchBarResultState.Results::class.java)
|
||||
@@ -149,9 +187,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
|
||||
initialState.eventSink(ChangeRolesEvent.ToggleSearchActive)
|
||||
@@ -168,9 +204,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
val initialResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results
|
||||
assertThat(initialResults?.members).hasSize(8)
|
||||
@@ -194,9 +228,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results
|
||||
assertThat(initialResults?.members).hasSize(8)
|
||||
@@ -221,9 +253,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.selectedUsers).hasSize(1)
|
||||
@@ -243,9 +273,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasPendingChanges).isFalse()
|
||||
@@ -272,9 +300,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasPendingChanges).isFalse()
|
||||
@@ -292,9 +318,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasPendingChanges).isFalse()
|
||||
@@ -318,9 +342,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasPendingChanges).isFalse()
|
||||
@@ -349,16 +371,14 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(role = RoomMember.Role.Admin, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.selectedUsers).hasSize(1)
|
||||
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
||||
awaitItem().eventSink(ChangeRolesEvent.Save)
|
||||
val confirmingState = awaitItem()
|
||||
assertThat(confirmingState.savingState).isEqualTo(AsyncAction.ConfirmingNoParams)
|
||||
assertThat(confirmingState.savingState).isEqualTo(ConfirmingModifyingAdmins)
|
||||
confirmingState.eventSink(ChangeRolesEvent.Save)
|
||||
assertThat(awaitItem().savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
||||
@@ -372,9 +392,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(role = RoomMember.Role.Admin, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.selectedUsers).hasSize(1)
|
||||
@@ -383,7 +401,7 @@ class ChangeRolesPresenterTest {
|
||||
|
||||
awaitItem().eventSink(ChangeRolesEvent.Save)
|
||||
val confirmingState = awaitItem()
|
||||
assertThat(confirmingState.savingState).isEqualTo(AsyncAction.ConfirmingNoParams)
|
||||
assertThat(confirmingState.savingState).isEqualTo(ConfirmingModifyingAdmins)
|
||||
|
||||
confirmingState.eventSink(ChangeRolesEvent.CloseDialog)
|
||||
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Uninitialized)
|
||||
@@ -405,9 +423,7 @@ class ChangeRolesPresenterTest {
|
||||
room = room,
|
||||
analyticsService = analyticsService
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.selectedUsers).hasSize(1)
|
||||
@@ -442,9 +458,7 @@ class ChangeRolesPresenterTest {
|
||||
room = room,
|
||||
analyticsService = analyticsService
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.selectedUsers).hasSize(1)
|
||||
@@ -477,9 +491,7 @@ class ChangeRolesPresenterTest {
|
||||
room = room,
|
||||
analyticsService = analyticsService
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.selectedUsers).hasSize(1)
|
||||
@@ -504,9 +516,7 @@ class ChangeRolesPresenterTest {
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(role = RoomMember.Role.Moderator, userId = A_USER_ID)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(role = RoomMember.Role.Moderator, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.selectedUsers).hasSize(1)
|
||||
|
||||
@@ -50,7 +50,6 @@ class ChangeRolesViewTest {
|
||||
),
|
||||
)
|
||||
}.exceptionOrNull()
|
||||
|
||||
assertThat(exception).isNotNull()
|
||||
}
|
||||
|
||||
@@ -63,9 +62,7 @@ class ChangeRolesViewTest {
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.pressBackKey()
|
||||
|
||||
eventsRecorder.assertSingle(ChangeRolesEvent.ToggleSearchActive)
|
||||
}
|
||||
|
||||
@@ -78,9 +75,7 @@ class ChangeRolesViewTest {
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.pressBackKey()
|
||||
|
||||
eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Exit))
|
||||
}
|
||||
|
||||
@@ -93,9 +88,7 @@ class ChangeRolesViewTest {
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.pressBack()
|
||||
|
||||
eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Exit))
|
||||
}
|
||||
|
||||
@@ -108,9 +101,7 @@ class ChangeRolesViewTest {
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.clickOn(CommonStrings.action_save)
|
||||
|
||||
eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Save))
|
||||
}
|
||||
|
||||
@@ -123,9 +114,7 @@ class ChangeRolesViewTest {
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.clickOn(CommonStrings.action_save)
|
||||
|
||||
eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged("")))
|
||||
}
|
||||
|
||||
@@ -139,9 +128,7 @@ class ChangeRolesViewTest {
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.clickOn(CommonStrings.action_ok)
|
||||
|
||||
eventsRecorder.assertSingle(ChangeRolesEvent.Exit)
|
||||
}
|
||||
|
||||
@@ -155,26 +142,22 @@ class ChangeRolesViewTest {
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
|
||||
eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `save confirmation dialog - submit saves the changes`() {
|
||||
fun `save admins confirmation dialog - submit saves the changes`() {
|
||||
val eventsRecorder = EventsRecorder<ChangeRolesEvent>()
|
||||
rule.setChangeRolesContent(
|
||||
state = aChangeRolesState(
|
||||
role = RoomMember.Role.Admin,
|
||||
isSearchActive = true,
|
||||
savingState = AsyncAction.ConfirmingNoParams,
|
||||
savingState = ConfirmingModifyingAdmins,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.clickOn(CommonStrings.action_ok)
|
||||
|
||||
eventsRecorder.assertSingle(ChangeRolesEvent.Save)
|
||||
}
|
||||
|
||||
@@ -185,30 +168,41 @@ class ChangeRolesViewTest {
|
||||
state = aChangeRolesState(
|
||||
role = RoomMember.Role.Owner(isCreator = false),
|
||||
isSearchActive = true,
|
||||
savingState = AsyncAction.ConfirmingNoParams,
|
||||
savingState = ConfirmingModifyingOwners,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
|
||||
eventsRecorder.assertSingle(ChangeRolesEvent.Save)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `save confirmation dialog - cancel removes the dialog`() {
|
||||
fun `save admins confirmation dialog - cancel removes the dialog`() {
|
||||
val eventsRecorder = EventsRecorder<ChangeRolesEvent>()
|
||||
rule.setChangeRolesContent(
|
||||
state = aChangeRolesState(
|
||||
role = RoomMember.Role.Admin,
|
||||
isSearchActive = true,
|
||||
savingState = AsyncAction.ConfirmingNoParams,
|
||||
savingState = ConfirmingModifyingAdmins,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `save owners confirmation dialog - cancel removes the dialog`() {
|
||||
val eventsRecorder = EventsRecorder<ChangeRolesEvent>()
|
||||
rule.setChangeRolesContent(
|
||||
state = aChangeRolesState(
|
||||
role = RoomMember.Role.Owner(isCreator = false),
|
||||
isSearchActive = true,
|
||||
savingState = ConfirmingModifyingOwners,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog)
|
||||
}
|
||||
|
||||
@@ -222,9 +216,7 @@ class ChangeRolesViewTest {
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
|
||||
rule.clickOn(CommonStrings.action_ok)
|
||||
|
||||
eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog)
|
||||
}
|
||||
|
||||
|
||||
@@ -205,6 +205,7 @@ class RoomDetailsPresenter(
|
||||
canReportRoom = canReportRoom,
|
||||
isTombstoned = roomInfo.successorRoom != null,
|
||||
showDebugInfo = isDeveloperModeEnabled,
|
||||
roomVersion = roomInfo.roomVersion,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ data class RoomDetailsState(
|
||||
val canReportRoom: Boolean,
|
||||
val isTombstoned: Boolean,
|
||||
val showDebugInfo: Boolean,
|
||||
val roomVersion: String?,
|
||||
val eventSink: (RoomDetailsEvent) -> Unit
|
||||
) {
|
||||
val roomBadges = buildList {
|
||||
|
||||
@@ -146,6 +146,7 @@ fun aRoomDetailsState(
|
||||
canReportRoom = canReportRoom,
|
||||
isTombstoned = isTombstoned,
|
||||
showDebugInfo = showDebugInfo,
|
||||
roomVersion = "12",
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
|
||||
@@ -264,6 +264,7 @@ fun RoomDetailsView(
|
||||
if (state.showDebugInfo) {
|
||||
DebugInfoSection(
|
||||
roomId = state.roomId,
|
||||
roomVersion = state.roomVersion,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -714,7 +715,10 @@ private fun OtherActionsSection(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DebugInfoSection(roomId: RoomId) {
|
||||
private fun DebugInfoSection(
|
||||
roomId: RoomId,
|
||||
roomVersion: String?,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
PreferenceCategory(showTopDivider = true) {
|
||||
ListItem(
|
||||
@@ -737,6 +741,19 @@ private fun DebugInfoSection(roomId: RoomId) {
|
||||
)
|
||||
},
|
||||
)
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text("Room version")
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = roomVersion ?: "Unknown",
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
},
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Info())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ fun aRoomInfo(
|
||||
notificationCount: Long = 0,
|
||||
userDefinedNotificationMode: RoomNotificationMode? = null,
|
||||
hasRoomCall: Boolean = false,
|
||||
roomPowerLevels: RoomPowerLevels = RoomPowerLevels(
|
||||
roomPowerLevels: RoomPowerLevels? = RoomPowerLevels(
|
||||
values = defaultRoomPowerLevelValues(),
|
||||
users = persistentMapOf(),
|
||||
),
|
||||
|
||||
@@ -23,12 +23,12 @@ fun RoomInfo.getAvatarData(size: AvatarSize) = AvatarData(
|
||||
|
||||
/**
|
||||
* Returns the role of the user in the room.
|
||||
* If the user is a creator, returns [RoomMember.Role.Owner].
|
||||
* If the user is a creator and [RoomInfo.privilegedCreatorRole] is true, returns [RoomMember.Role.Owner].
|
||||
* Otherwise, checks the power levels and returns the corresponding role.
|
||||
* If no specific power level is set for the user, defaults to [RoomMember.Role.User].
|
||||
*/
|
||||
fun RoomInfo.roleOf(userId: UserId): RoomMember.Role {
|
||||
return if (creators.contains(userId)) {
|
||||
return if (privilegedCreatorRole && creators.contains(userId)) {
|
||||
RoomMember.Role.Owner(isCreator = true)
|
||||
} else {
|
||||
roomPowerLevels?.roleOf(userId) ?: RoomMember.Role.User
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations 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.libraries.matrix.ui.model
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
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.A_USER_ID_3
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import org.junit.Test
|
||||
|
||||
class RoomInfoExtensionTest {
|
||||
@Test
|
||||
fun `roleOf returns Owner for creator with privilegedCreatorRole true`() {
|
||||
val roomInfo = aRoomInfo(
|
||||
privilegedCreatorRole = true,
|
||||
roomCreators = listOf(A_USER_ID),
|
||||
)
|
||||
assertThat(roomInfo.roleOf(A_USER_ID)).isEqualTo(RoomMember.Role.Owner(isCreator = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roleOf returns User for not creator with privilegedCreatorRole true`() {
|
||||
val roomInfo = aRoomInfo(
|
||||
privilegedCreatorRole = true,
|
||||
roomCreators = listOf(A_USER_ID),
|
||||
)
|
||||
assertThat(roomInfo.roleOf(A_USER_ID_2)).isEqualTo(RoomMember.Role.User)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roleOf returns User for creator with privilegedCreatorRole false`() {
|
||||
val roomInfo = aRoomInfo(
|
||||
privilegedCreatorRole = false,
|
||||
roomCreators = listOf(A_USER_ID),
|
||||
)
|
||||
assertThat(roomInfo.roleOf(A_USER_ID)).isEqualTo(RoomMember.Role.User)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roleOf returns role from the power level`() {
|
||||
val roomInfo = aRoomInfo(
|
||||
privilegedCreatorRole = false,
|
||||
roomPowerLevels = RoomPowerLevels(
|
||||
values = defaultRoomPowerLevelValues(),
|
||||
users = mapOf(
|
||||
A_USER_ID to 100L, // Admin
|
||||
A_USER_ID_2 to 50L, // Moderator
|
||||
A_USER_ID_3 to 0L, // User
|
||||
).toImmutableMap(),
|
||||
),
|
||||
roomCreators = listOf(A_USER_ID),
|
||||
)
|
||||
assertThat(roomInfo.roleOf(A_USER_ID)).isEqualTo(RoomMember.Role.Admin)
|
||||
assertThat(roomInfo.roleOf(A_USER_ID_2)).isEqualTo(RoomMember.Role.Moderator)
|
||||
assertThat(roomInfo.roleOf(A_USER_ID_3)).isEqualTo(RoomMember.Role.User)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roleOf returns User when the power level is null`() {
|
||||
val roomInfo = aRoomInfo(
|
||||
privilegedCreatorRole = false,
|
||||
roomPowerLevels = null,
|
||||
roomCreators = listOf(A_USER_ID),
|
||||
)
|
||||
assertThat(roomInfo.roleOf(A_USER_ID)).isEqualTo(RoomMember.Role.User)
|
||||
assertThat(roomInfo.roleOf(A_USER_ID_2)).isEqualTo(RoomMember.Role.User)
|
||||
assertThat(roomInfo.roleOf(A_USER_ID_3)).isEqualTo(RoomMember.Role.User)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user