quality(roles and permissions): fix tests
This commit is contained in:
parent
fca62a333e
commit
5f007fd50c
5 changed files with 70 additions and 113 deletions
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue