quality(roles and permissions): fix tests

This commit is contained in:
ganfra 2025-11-05 17:54:02 +01:00
parent fca62a333e
commit 5f007fd50c
5 changed files with 70 additions and 113 deletions

View file

@ -1,165 +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.rolesandpermissions
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
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
class RolesAndPermissionPresenterTest {
@Test
fun `present - initial state`() = runTest {
val presenter = createRolesAndPermissionsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
with(awaitItem()) {
assertThat(adminCount).isEqualTo(0)
assertThat(moderatorCount).isEqualTo(0)
assertThat(changeOwnRoleAction).isEqualTo(AsyncAction.Uninitialized)
}
}
}
@Test
fun `present - ChangeOwnRole presents a confirmation dialog`() = runTest {
val presenter = createRolesAndPermissionsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(RolesAndPermissionsEvents.ChangeOwnRole)
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.ConfirmingNoParams)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - DemoteSelfTo changes own role to the specified one`() = runTest(StandardTestDispatcher()) {
val presenter = createRolesAndPermissionsPresenter(
dispatchers = testCoroutineDispatchers(),
room = FakeJoinedRoom(
updateUserRoleResult = { Result.success(Unit) }
),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
runCurrent()
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading)
runCurrent()
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Success(Unit))
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - DemoteSelfTo can handle failures and clean them`() = runTest(StandardTestDispatcher()) {
val room = FakeJoinedRoom(
updateUserRoleResult = { Result.failure(Exception("Failed to update role")) }
)
val presenter = createRolesAndPermissionsPresenter(room = room, dispatchers = testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
runCurrent()
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading)
runCurrent()
assertThat(awaitItem().changeOwnRoleAction).isInstanceOf(AsyncAction.Failure::class.java)
initialState.eventSink(RolesAndPermissionsEvents.CancelPendingAction)
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Uninitialized)
}
}
@Test
fun `present - CancelPendingAction dismisses confirmation dialog too`() = runTest {
val presenter = createRolesAndPermissionsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(RolesAndPermissionsEvents.ChangeOwnRole)
awaitItem().eventSink(RolesAndPermissionsEvents.CancelPendingAction)
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Uninitialized)
}
}
@Test
fun `present - ResetPermissions needs confirmation, then resets permissions`() = runTest {
val analyticsService = FakeAnalyticsService()
val presenter = createRolesAndPermissionsPresenter(
analyticsService = analyticsService,
room = FakeJoinedRoom(
resetPowerLevelsResult = { Result.success(Unit) }
)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(RolesAndPermissionsEvents.ResetPermissions)
// Confirmation
awaitItem().eventSink(RolesAndPermissionsEvents.ResetPermissions)
assertThat(awaitItem().resetPermissionsAction).isEqualTo(AsyncAction.Loading)
assertThat(awaitItem().resetPermissionsAction).isEqualTo(AsyncAction.Success(Unit))
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ResetPermissions))
}
}
@Test
fun `present - ResetPermissions confirmation can be cancelled`() = runTest {
val presenter = createRolesAndPermissionsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(RolesAndPermissionsEvents.ResetPermissions)
awaitItem().eventSink(RolesAndPermissionsEvents.CancelPendingAction)
assertThat(awaitItem().resetPermissionsAction).isEqualTo(AsyncAction.Uninitialized)
}
}
private fun TestScope.createRolesAndPermissionsPresenter(
room: FakeJoinedRoom = FakeJoinedRoom(),
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService()
): RolesAndPermissionsPresenter {
return RolesAndPermissionsPresenter(
room = room,
dispatchers = dispatchers,
analyticsService = analyticsService
)
}
}

View file

@ -1,202 +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.rolesandpermissions
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.roomdetails.impl.R
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.ensureCalledTimes
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.setSafeContent
import kotlinx.coroutines.test.runTest
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 RolesAndPermissionsViewTest {
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
@Test
fun `click on back invokes expected callback`() {
ensureCalledOnce { callback ->
rule.setRolesAndPermissionsView(
goBack = callback,
)
rule.pressBack()
}
}
@Test
fun `tapping on Admins opens admin list`() {
ensureCalledOnce { callback ->
rule.setRolesAndPermissionsView(
aRolesAndPermissionsState(
roomSupportsOwners = false,
eventSink = EventsRecorder(expectEvents = false)
),
openAdminList = callback,
)
rule.clickOn(R.string.screen_room_roles_and_permissions_admins)
}
}
@Test
fun `tapping on Admins and Owners opens admin list`() {
ensureCalledOnce { callback ->
rule.setRolesAndPermissionsView(
aRolesAndPermissionsState(
roomSupportsOwners = true,
eventSink = EventsRecorder(expectEvents = false)
),
openAdminList = callback,
)
rule.clickOn(R.string.screen_room_roles_and_permissions_admins_and_owners)
}
}
@Test
fun `tapping on Moderators opens moderators list`() {
ensureCalledOnce { callback ->
rule.setRolesAndPermissionsView(
openModeratorList = callback,
)
rule.clickOn(R.string.screen_room_roles_and_permissions_moderators)
}
}
@Test
@Config(qualifiers = "h640dp")
fun `tapping on any of the permission items open the change permissions screen`() {
ensureCalledTimes(3) { callback ->
rule.setRolesAndPermissionsView(
openPermissionScreens = callback,
)
rule.clickOn(R.string.screen_room_roles_and_permissions_room_details)
rule.clickOn(R.string.screen_room_roles_and_permissions_messages_and_content)
rule.clickOn(R.string.screen_room_roles_and_permissions_member_moderation)
}
}
@Test
@Config(qualifiers = "h640dp")
fun `tapping on reset permissions triggers ResetPermissions event`() {
val recorder = EventsRecorder<RolesAndPermissionsEvents>()
rule.setRolesAndPermissionsView(
state = aRolesAndPermissionsState(
eventSink = recorder,
),
)
rule.clickOn(R.string.screen_room_roles_and_permissions_reset)
recorder.assertSingle(RolesAndPermissionsEvents.ResetPermissions)
}
@Test
fun `tapping on Reset in the reset permissions confirmation dialog triggers ResetPermissions event`() {
val recorder = EventsRecorder<RolesAndPermissionsEvents>()
rule.setRolesAndPermissionsView(
state = aRolesAndPermissionsState(
resetPermissionsAction = AsyncAction.ConfirmingNoParams,
eventSink = recorder,
),
)
rule.clickOn(CommonStrings.action_reset)
recorder.assertSingle(RolesAndPermissionsEvents.ResetPermissions)
}
@Test
fun `tapping on Cancel in the reset permissions confirmation dialog triggers CancelPendingAction event`() {
val recorder = EventsRecorder<RolesAndPermissionsEvents>()
rule.setRolesAndPermissionsView(
state = aRolesAndPermissionsState(
resetPermissionsAction = AsyncAction.ConfirmingNoParams,
eventSink = recorder,
),
)
rule.clickOn(CommonStrings.action_cancel)
recorder.assertSingle(RolesAndPermissionsEvents.CancelPendingAction)
}
@Test
fun `tapping on 'Demote to moderator' in the demote self bottom sheet triggers the right event`() {
val recorder = EventsRecorder<RolesAndPermissionsEvents>()
rule.setRolesAndPermissionsView(
state = aRolesAndPermissionsState(
changeOwnRoleAction = AsyncAction.ConfirmingNoParams,
eventSink = recorder,
),
)
rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)
rule.mainClock.advanceTimeBy(1_000L)
recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
}
@Test
fun `tapping on 'Demote to member' in the demote self bottom sheet triggers the right event`() = runTest {
val recorder = EventsRecorder<RolesAndPermissionsEvents>()
rule.setRolesAndPermissionsView(
state = aRolesAndPermissionsState(
changeOwnRoleAction = AsyncAction.ConfirmingNoParams,
eventSink = recorder,
),
)
rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)
rule.mainClock.advanceTimeBy(1_000L)
recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User))
}
@Test
fun `tapping on 'Cancel' in the demote self bottom sheet triggers the right event`() {
val recorder = EventsRecorder<RolesAndPermissionsEvents>()
rule.setRolesAndPermissionsView(
state = aRolesAndPermissionsState(
changeOwnRoleAction = AsyncAction.ConfirmingNoParams,
eventSink = recorder,
),
)
rule.clickOn(CommonStrings.action_cancel)
rule.mainClock.advanceTimeBy(1_000L)
recorder.assertSingle(RolesAndPermissionsEvents.CancelPendingAction)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRolesAndPermissionsView(
state: RolesAndPermissionsState = aRolesAndPermissionsState(
roomSupportsOwners = false,
eventSink = EventsRecorder(expectEvents = false),
),
goBack: () -> Unit = EnsureNeverCalled(),
openAdminList: () -> Unit = EnsureNeverCalled(),
openModeratorList: () -> Unit = EnsureNeverCalled(),
openPermissionScreens: () -> Unit = EnsureNeverCalled(),
) {
setSafeContent {
RolesAndPermissionsView(
state = state,
rolesAndPermissionsNavigator = object : RolesAndPermissionsNavigator {
override fun onBackClick() = goBack()
override fun openAdminList() = openAdminList()
override fun openModeratorList() = openModeratorList()
override fun openEditRoomDetailsPermissions() = openPermissionScreens()
override fun openModerationPermissions() = openPermissionScreens()
override fun openMessagesAndContentPermissions() = openPermissionScreens()
}
)
}
}

View file

@ -1,305 +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.rolesandpermissions.permissions
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.Event
import app.cash.turbine.TurbineTestContext
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
import io.element.android.libraries.matrix.api.room.RoomMember.Role.Admin
import io.element.android.libraries.matrix.api.room.RoomMember.Role.Moderator
import io.element.android.libraries.matrix.api.room.RoomMember.Role.User
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
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.defaultRoomPowerLevelValues
import io.element.android.services.analytics.test.FakeAnalyticsService
import kotlinx.coroutines.test.runTest
import org.junit.Test
class ChangeBaseRoomPermissionsPresenterTest {
@Test
fun `present - initial state`() = runTest {
val section = ChangeRoomPermissionsSection.RoomDetails
val presenter = createChangeRoomPermissionsPresenter(section = section)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
// Initial state, no permissions loaded
awaitItem().run {
assertThat(this.section).isEqualTo(section)
assertThat(this.currentPermissions).isNull()
assertThat(this.items).isNotEmpty()
assertThat(this.hasChanges).isFalse()
assertThat(this.saveAction).isEqualTo(AsyncAction.Uninitialized)
assertThat(this.confirmExitAction).isEqualTo(AsyncAction.Uninitialized)
}
// Updated state, permissions loaded
assertThat(awaitItem().currentPermissions).isEqualTo(defaultPermissions())
}
}
@Test
fun `present - RoomDetails section contains the right items`() = runTest {
val section = ChangeRoomPermissionsSection.RoomDetails
val presenter = createChangeRoomPermissionsPresenter(section = section)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
assertThat(awaitUpdatedItem().items).containsExactly(
RoomPermissionType.ROOM_NAME,
RoomPermissionType.ROOM_AVATAR,
RoomPermissionType.ROOM_TOPIC,
)
}
}
@Test
fun `present - MessagesAndContent section contains the right items`() = runTest {
val section = ChangeRoomPermissionsSection.MessagesAndContent
val presenter = createChangeRoomPermissionsPresenter(section = section)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
assertThat(awaitUpdatedItem().items).containsExactly(
RoomPermissionType.SEND_EVENTS,
RoomPermissionType.REDACT_EVENTS,
)
}
}
@Test
fun `present - MembershipModeration section contains the right items`() = runTest {
val section = ChangeRoomPermissionsSection.MembershipModeration
val presenter = createChangeRoomPermissionsPresenter(section = section)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
assertThat(awaitUpdatedItem().items).containsExactly(
RoomPermissionType.INVITE,
RoomPermissionType.KICK,
RoomPermissionType.BAN,
)
}
}
@Test
fun `present - ChangeMinimumRoleForAction updates the current permissions and hasChanges`() = runTest {
val presenter = createChangeRoomPermissionsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val state = awaitUpdatedItem()
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
assertThat(state.hasChanges).isFalse()
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
awaitItem().run {
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
assertThat(hasChanges).isTrue()
}
}
}
@Test
fun `present - ChangeMinimumRoleForAction works for all actions`() = runTest {
val presenter = createChangeRoomPermissionsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val state = awaitUpdatedItem()
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator))
val items = cancelAndConsumeRemainingEvents()
(items.last() as? Event.Item<ChangeRoomPermissionsState>)?.value?.run {
assertThat(currentPermissions).isEqualTo(
RoomPowerLevelsValues(
invite = Moderator.powerLevel,
kick = Moderator.powerLevel,
ban = Moderator.powerLevel,
redactEvents = Moderator.powerLevel,
sendEvents = Moderator.powerLevel,
roomName = Moderator.powerLevel,
roomAvatar = Moderator.powerLevel,
roomTopic = Moderator.powerLevel,
)
)
}
}
}
@Test
fun `present - Save updates the current permissions and resets hasChanges`() = runTest {
val analyticsService = FakeAnalyticsService()
val presenter = createChangeRoomPermissionsPresenter(
analyticsService = analyticsService,
room = FakeJoinedRoom(
updatePowerLevelsResult = { Result.success(Unit) },
baseRoom = FakeBaseRoom(powerLevelsResult = { Result.success(defaultPermissions()) }),
),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val state = awaitUpdatedItem()
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
assertThat(state.hasChanges).isFalse()
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, User))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Admin))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Admin))
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Admin))
skipItems(7)
assertThat(awaitItem().hasChanges).isTrue()
state.eventSink(ChangeRoomPermissionsEvent.Save)
assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading)
assertThat(awaitItem().hasChanges).isFalse()
awaitItem().run {
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit))
}
assertThat(analyticsService.capturedEvents).containsExactlyElementsIn(
listOf(
RoomModeration(RoomModeration.Action.ChangePermissionsRoomName, RoomModeration.Role.Moderator),
RoomModeration(RoomModeration.Action.ChangePermissionsRoomAvatar, RoomModeration.Role.Moderator),
RoomModeration(RoomModeration.Action.ChangePermissionsRoomTopic, RoomModeration.Role.Moderator),
RoomModeration(RoomModeration.Action.ChangePermissionsSendMessages, RoomModeration.Role.Moderator),
RoomModeration(RoomModeration.Action.ChangePermissionsRedactMessages, RoomModeration.Role.User),
RoomModeration(RoomModeration.Action.ChangePermissionsKickMembers, RoomModeration.Role.Administrator),
RoomModeration(RoomModeration.Action.ChangePermissionsBanMembers, RoomModeration.Role.Administrator),
RoomModeration(RoomModeration.Action.ChangePermissionsInviteUsers, RoomModeration.Role.Administrator),
)
)
}
}
@Test
fun `present - Save will fail if there are not current permissions`() = runTest {
val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(powerLevelsResult = { Result.failure(IllegalStateException("Failed to load power levels")) }),
)
val presenter = createChangeRoomPermissionsPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val state = awaitItem()
assertThat(state.currentPermissions).isNull()
state.eventSink(ChangeRoomPermissionsEvent.Save)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
}
}
@Test
fun `present - Save can handle failures and they can be cleared`() = runTest {
val room = FakeJoinedRoom(
updatePowerLevelsResult = { Result.failure(IllegalStateException("Failed to update power levels")) },
baseRoom = FakeBaseRoom(powerLevelsResult = { Result.success(defaultPermissions()) }),
)
val presenter = createChangeRoomPermissionsPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val state = awaitUpdatedItem()
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
assertThat(state.hasChanges).isFalse()
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
assertThat(awaitItem().hasChanges).isTrue()
state.eventSink(ChangeRoomPermissionsEvent.Save)
assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading)
awaitItem().run {
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
// Couldn't save the changes, so they're still pending
assertThat(hasChanges).isTrue()
assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java)
}
state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions)
awaitItem().run {
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized)
assertThat(hasChanges).isTrue()
}
}
}
@Test
fun `present - Exit does not need a confirmation when there are no pending changes`() = runTest {
val presenter = createChangeRoomPermissionsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val state = awaitUpdatedItem()
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
assertThat(awaitItem().hasChanges).isTrue()
state.eventSink(ChangeRoomPermissionsEvent.Exit)
assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.ConfirmingNoParams)
state.eventSink(ChangeRoomPermissionsEvent.Exit)
assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Success(Unit))
}
}
@Test
fun `present - Exit needs confirmation when there are pending changes`() = runTest {
val presenter = createChangeRoomPermissionsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val state = awaitUpdatedItem()
state.eventSink(ChangeRoomPermissionsEvent.Exit)
assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Success(Unit))
}
}
private fun createChangeRoomPermissionsPresenter(
section: ChangeRoomPermissionsSection = ChangeRoomPermissionsSection.RoomDetails,
room: FakeJoinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(powerLevelsResult = { Result.success(defaultPermissions()) }),
),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
) = ChangeRoomPermissionsPresenter(
section = section,
room = room,
analyticsService = analyticsService,
)
private fun defaultPermissions() = defaultRoomPowerLevelValues()
private suspend fun TurbineTestContext<ChangeRoomPermissionsState>.awaitUpdatedItem(): ChangeRoomPermissionsState {
skipItems(1)
return awaitItem()
}
}

View file

@ -1,186 +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.rolesandpermissions.permissions
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.roomdetails.impl.R
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.clickOnFirst
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressBackKey
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ChangeBaseRoomPermissionsViewTest {
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
@Test
fun `click on back icon invokes Exit`() {
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
rule.setChangeRoomPermissionsRule(
eventsRecorder = recorder,
)
rule.pressBack()
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
}
@Test
fun `click on back key invokes Exit`() {
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
rule.setChangeRoomPermissionsRule(
eventsRecorder = recorder,
)
rule.pressBackKey()
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
}
@Test
fun `when confirming exit with pending changes, using the back key actually exits`() {
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
rule.setChangeRoomPermissionsRule(
state = aChangeRoomPermissionsState(
section = ChangeRoomPermissionsSection.RoomDetails,
hasChanges = true,
eventSink = recorder,
),
eventsRecorder = recorder,
)
rule.pressBackKey()
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
}
@Test
fun `when confirming exit with pending changes, clicking on 'discard' button in the dialog actually exits`() {
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
rule.setChangeRoomPermissionsRule(
state = aChangeRoomPermissionsState(
section = ChangeRoomPermissionsSection.RoomDetails,
hasChanges = true,
confirmExitAction = AsyncAction.ConfirmingNoParams,
eventSink = recorder,
),
eventsRecorder = recorder,
)
rule.clickOn(CommonStrings.action_discard)
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit)
}
@Test
fun `when confirming exit with pending changes, clicking on 'save' button in the dialog saves the changes`() {
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
rule.setChangeRoomPermissionsRule(
state = aChangeRoomPermissionsState(
section = ChangeRoomPermissionsSection.RoomDetails,
hasChanges = true,
confirmExitAction = AsyncAction.ConfirmingNoParams,
eventSink = recorder,
),
eventsRecorder = recorder,
)
rule.clickOnFirst(CommonStrings.action_save)
recorder.assertSingle(ChangeRoomPermissionsEvent.Save)
}
@Test
fun `click on a role item triggers ChangeRole event`() {
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
rule.setChangeRoomPermissionsRule(
eventsRecorder = recorder,
)
val admins = rule.activity.getText(R.string.screen_room_change_permissions_administrators).toString()
val moderators = rule.activity.getText(R.string.screen_room_change_permissions_moderators).toString()
val users = rule.activity.getText(R.string.screen_room_change_permissions_everyone).toString()
rule.onAllNodesWithText(admins).onFirst().performClick()
rule.onAllNodesWithText(moderators).onFirst().performClick()
rule.onAllNodesWithText(users).onFirst().performClick()
recorder.assertList(
listOf(
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Admin),
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Moderator),
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.User),
)
)
}
@Test
fun `click on the Save menu item triggers Save event`() {
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
rule.setChangeRoomPermissionsRule(
state = aChangeRoomPermissionsState(
section = ChangeRoomPermissionsSection.RoomDetails,
hasChanges = true,
eventSink = recorder,
),
eventsRecorder = recorder,
)
rule.clickOn(CommonStrings.action_save)
recorder.assertSingle(ChangeRoomPermissionsEvent.Save)
}
@Test
fun `a successful save exits the screen`() {
ensureCalledOnce { callback ->
rule.setChangeRoomPermissionsRule(
state = aChangeRoomPermissionsState(
section = ChangeRoomPermissionsSection.RoomDetails,
hasChanges = true,
saveAction = AsyncAction.Success(Unit),
),
onBackClick = callback
)
rule.clickOn(CommonStrings.action_save)
}
}
@Test
fun `click on the Ok option in save error dialog triggers ResetPendingAction event`() {
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>()
rule.setChangeRoomPermissionsRule(
state = aChangeRoomPermissionsState(
section = ChangeRoomPermissionsSection.RoomDetails,
hasChanges = true,
saveAction = AsyncAction.Failure(IllegalStateException("Failed to set room power levels")),
eventSink = recorder,
),
eventsRecorder = recorder,
)
rule.clickOn(CommonStrings.action_ok)
recorder.assertSingle(ChangeRoomPermissionsEvent.ResetPendingActions)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChangeRoomPermissionsRule(
eventsRecorder: EventsRecorder<ChangeRoomPermissionsEvent> = EventsRecorder(expectEvents = false),
state: ChangeRoomPermissionsState = aChangeRoomPermissionsState(
section = ChangeRoomPermissionsSection.RoomDetails,
eventSink = eventsRecorder,
),
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
setContent {
ChangeRoomPermissionsView(
state = state,
onBackClick = onBackClick,
)
}
}