From 76bc87275c85c1cf5ab788b419d462fb820a7a5f Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 27 Jan 2025 22:43:10 +0100 Subject: [PATCH] feat(security&privacy) : add all tests for EditRoomAddress classes --- .../ConfigureRoomPresenterTest.kt | 1 + .../SecurityAndPrivacyNavigator.kt | 4 +- .../EditRoomAddressPresenter.kt | 2 +- .../EditRoomAddressStateProvider.kt | 12 +- .../FakeSecurityAndPrivacyNavigator.kt | 6 +- .../SecurityAndPrivacyPresenterTest.kt | 14 + .../EditRoomAddressPresenterTest.kt | 355 ++++++++++++++++++ .../EditRoomAddressViewTest.kt | 123 ++++++ .../ui/room/address/RoomAddressField.kt | 4 +- .../android/libraries/testtags/TestTags.kt | 7 + 10 files changed, 515 insertions(+), 13 deletions(-) create mode 100644 features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/editroomaddress/EditRoomAddressPresenterTest.kt create mode 100644 features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/editroomaddress/EditRoomAddressViewTest.kt diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt index 714c375d03..60698b8e98 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.media.AvatarAction +import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.api.MediaPreProcessor diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyNavigator.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyNavigator.kt index 8b6fb3312e..ade6479fd9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyNavigator.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyNavigator.kt @@ -14,7 +14,7 @@ import com.bumble.appyx.navmodel.backstack.operation.push interface SecurityAndPrivacyNavigator : Plugin { fun openEditRoomAddress() - fun closeEditorRoomAddress() + fun closeEditRoomAddress() } class BackstackSecurityAndPrivacyNavigator( @@ -24,7 +24,7 @@ class BackstackSecurityAndPrivacyNavigator( backStack.push(SecurityAndPrivacyFlowNode.NavTarget.EditRoomAddress) } - override fun closeEditorRoomAddress() { + override fun closeEditRoomAddress() { backStack.pop() } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt index 3402e2f174..884034ca1e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt @@ -128,7 +128,7 @@ class EditRoomAddressPresenter @AssistedInject constructor( room.updateCanonicalAlias(savedCanonicalAlias, newAlternativeAliases).getOrThrow() } } - navigator.closeEditorRoomAddress() + navigator.closeEditRoomAddress() }.runCatchingUpdatingState(saveAction) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressStateProvider.kt index 733e9cb819..6ef8658bcd 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressStateProvider.kt @@ -14,15 +14,15 @@ import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity open class EditRoomAddressStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aEditRoomAddressState(), - aEditRoomAddressState(roomAddressValidity = RoomAddressValidity.NotAvailable), - aEditRoomAddressState(roomAddressValidity = RoomAddressValidity.InvalidSymbols), - aEditRoomAddressState(roomAddressValidity = RoomAddressValidity.Valid), - aEditRoomAddressState(roomAddressValidity = RoomAddressValidity.Valid, saveAction = AsyncAction.Loading), + anEditRoomAddressState(), + anEditRoomAddressState(roomAddressValidity = RoomAddressValidity.NotAvailable), + anEditRoomAddressState(roomAddressValidity = RoomAddressValidity.InvalidSymbols), + anEditRoomAddressState(roomAddressValidity = RoomAddressValidity.Valid), + anEditRoomAddressState(roomAddressValidity = RoomAddressValidity.Valid, saveAction = AsyncAction.Loading), ) } -fun aEditRoomAddressState( +fun anEditRoomAddressState( roomAddress: String = "therapy", roomAddressValidity: RoomAddressValidity = RoomAddressValidity.Unknown, homeserverName: String = ":myserver.org", diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/FakeSecurityAndPrivacyNavigator.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/FakeSecurityAndPrivacyNavigator.kt index e6c91096f3..e4dfbcdbba 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/FakeSecurityAndPrivacyNavigator.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/FakeSecurityAndPrivacyNavigator.kt @@ -12,13 +12,13 @@ import io.element.android.tests.testutils.lambda.lambdaError class FakeSecurityAndPrivacyNavigator( private val openEditRoomAddressLambda: () -> Unit = { lambdaError() }, - private val closeEditorRoomAddressLambda: () -> Unit = { lambdaError() }, + private val closeEditRoomAddressLambda: () -> Unit = { lambdaError() }, ) : SecurityAndPrivacyNavigator { override fun openEditRoomAddress() { openEditRoomAddressLambda() } - override fun closeEditorRoomAddress() { - closeEditorRoomAddressLambda() + override fun closeEditRoomAddress() { + closeEditRoomAddressLambda() } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/SecurityAndPrivacyPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/SecurityAndPrivacyPresenterTest.kt index aca3e97c54..c46cdc9d9c 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/SecurityAndPrivacyPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/SecurityAndPrivacyPresenterTest.kt @@ -192,6 +192,20 @@ class SecurityAndPrivacyPresenterTest { } } + @Test + fun `present - edit room address`() = runTest { + val openEditRoomAddressLambda = lambdaRecorder { } + val navigator = FakeSecurityAndPrivacyNavigator(openEditRoomAddressLambda) + val presenter = createSecurityAndPrivacyPresenter(navigator = navigator) + presenter.test { + skipItems(1) + with(awaitItem()) { + eventSink(SecurityAndPrivacyEvents.EditRoomAddress) + } + assert(openEditRoomAddressLambda).isCalledOnce() + } + } + @Test fun `present - save success`() = runTest { val enableEncryptionLambda = lambdaRecorder> { Result.success(Unit) } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/editroomaddress/EditRoomAddressPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/editroomaddress/EditRoomAddressPresenterTest.kt new file mode 100644 index 0000000000..0210ddac82 --- /dev/null +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/editroomaddress/EditRoomAddressPresenterTest.kt @@ -0,0 +1,355 @@ +/* + * Copyright 2025 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.securityandprivacy.editroomaddress + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyNavigator +import io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress.EditRoomAddressEvents +import io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress.EditRoomAddressPresenter +import io.element.android.features.roomdetails.securityandprivacy.FakeSecurityAndPrivacyNavigator +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias +import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper +import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity +import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test +import kotlinx.coroutines.test.runTest +import org.junit.Test +import java.util.Optional + +class EditRoomAddressPresenterTest { + + @Test + fun `present - initial state no address`() = runTest { + val presenter = createEditRoomAddressPresenter() + presenter.test { + with(awaitItem()) { + assertThat(homeserverName).isEqualTo("matrix.org") + assertThat(canBeSaved).isFalse() + assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Unknown) + assertThat(roomAddress).isEmpty() + } + } + } + + @Test + fun `present - initial state address matching own homeserver`() = runTest { + val room = FakeMatrixRoom( + canonicalAlias = RoomAlias("#canonical:matrix.org"), + ) + val presenter = createEditRoomAddressPresenter(room = room) + presenter.test { + with(awaitItem()) { + assertThat(homeserverName).isEqualTo("matrix.org") + assertThat(canBeSaved).isFalse() + assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Unknown) + assertThat(roomAddress).isEqualTo("canonical") + } + } + } + + @Test + fun `present - initial state address not matching own homeserver`() = runTest { + val room = FakeMatrixRoom( + canonicalAlias = RoomAlias("#canonical:notmatrix.org"), + ) + val presenter = createEditRoomAddressPresenter(room = room) + presenter.test { + with(awaitItem()) { + assertThat(homeserverName).isEqualTo("matrix.org") + assertThat(canBeSaved).isFalse() + assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Unknown) + assertThat(roomAddress).isEmpty() + } + } + } + + @Test + fun `present - room address change invalid state`() = runTest { + val roomAliasHelper = FakeRoomAliasHelper( + isRoomAliasValidLambda = { false } + ) + val presenter = createEditRoomAddressPresenter(roomAliasHelper = roomAliasHelper) + presenter.test { + with(awaitItem()) { + eventSink(EditRoomAddressEvents.RoomAddressChanged("invalid")) + } + with(awaitItem()) { + assertThat(roomAddress).isEqualTo("invalid") + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Unknown) + } + with(awaitItem()) { + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.InvalidSymbols) + assertThat(canBeSaved).isFalse() + } + } + } + + @Test + fun `present - room address change valid state`() = runTest { + val presenter = createEditRoomAddressPresenter() + presenter.test { + with(awaitItem()) { + eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + } + with(awaitItem()) { + assertThat(roomAddress).isEqualTo("valid") + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Unknown) + } + with(awaitItem()) { + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Valid) + assertThat(canBeSaved).isTrue() + } + } + } + + @Test + fun `present - room address change alias unavailable`() = runTest { + val client = createMatrixClient(isAliasAvailable = false) + val presenter = createEditRoomAddressPresenter(client = client) + presenter.test { + with(awaitItem()) { + eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + } + with(awaitItem()) { + assertThat(roomAddress).isEqualTo("valid") + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Unknown) + } + with(awaitItem()) { + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.NotAvailable) + assertThat(canBeSaved).isFalse() + } + } + } + + @Test + fun `present - save success no current alias`() = runTest { + val publishAliasInRoomDirectoryResult = lambdaRecorder> { _ -> Result.success(true) } + val updateCanonicalAliasResult = lambdaRecorder, Result> { _, _ -> Result.success(Unit) } + val removeAliasFromRoomDirectoryResult = lambdaRecorder> { _ -> Result.success(true) } + val closeEditAddressLambda = lambdaRecorder { } + val navigator = FakeSecurityAndPrivacyNavigator( + closeEditRoomAddressLambda = closeEditAddressLambda + ) + val room = FakeMatrixRoom( + updateCanonicalAliasResult = updateCanonicalAliasResult, + publishRoomAliasInRoomDirectoryResult = publishAliasInRoomDirectoryResult + ) + val presenter = createEditRoomAddressPresenter(room = room, navigator = navigator) + presenter.test { + with(awaitItem()) { + eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + } + skipItems(1) + with(awaitItem()) { + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Valid) + assertThat(canBeSaved).isTrue() + eventSink(EditRoomAddressEvents.Save) + } + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Loading) + } + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) + } + + val createdAlias = RoomAlias("#valid:matrix.org") + assert(updateCanonicalAliasResult) + .isCalledOnce() + .with(value(createdAlias), value(emptyList())) + + assert(publishAliasInRoomDirectoryResult) + .isCalledOnce() + .with(value(createdAlias)) + + assert(removeAliasFromRoomDirectoryResult).isNeverCalled() + + assert(closeEditAddressLambda).isCalledOnce() + } + } + + @Test + fun `present - save success current canonical alias from own homeserver`() = runTest { + val publishAliasInRoomDirectoryResult = lambdaRecorder> { _ -> Result.success(true) } + val removeAliasFromRoomDirectoryResult = lambdaRecorder> { _ -> Result.success(true) } + val updateCanonicalAliasResult = lambdaRecorder, Result> { _, _ -> Result.success(Unit) } + val closeEditAddressLambda = lambdaRecorder { } + + val navigator = FakeSecurityAndPrivacyNavigator(closeEditRoomAddressLambda = closeEditAddressLambda) + val canonicalAlias = RoomAlias("#canonical:matrix.org") + val room = FakeMatrixRoom( + canonicalAlias = canonicalAlias, + updateCanonicalAliasResult = updateCanonicalAliasResult, + publishRoomAliasInRoomDirectoryResult = publishAliasInRoomDirectoryResult, + removeRoomAliasFromRoomDirectoryResult = removeAliasFromRoomDirectoryResult + ) + val presenter = createEditRoomAddressPresenter(room = room, navigator = navigator) + presenter.test { + with(awaitItem()) { + eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + } + skipItems(1) + with(awaitItem()) { + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Valid) + assertThat(canBeSaved).isTrue() + eventSink(EditRoomAddressEvents.Save) + } + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Loading) + } + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) + } + + val createdAlias = RoomAlias("#valid:matrix.org") + assert(updateCanonicalAliasResult) + .isCalledOnce() + .with(value(createdAlias), value(emptyList())) + + assert(publishAliasInRoomDirectoryResult) + .isCalledOnce() + .with(value(createdAlias)) + + assert(removeAliasFromRoomDirectoryResult) + .isCalledOnce() + .with(value(canonicalAlias)) + + assert(closeEditAddressLambda).isCalledOnce() + } + } + + @Test + fun `present - save success current canonical alias from other homeserver`() = runTest { + val publishAliasInRoomDirectoryResult = lambdaRecorder> { _ -> Result.success(true) } + val removeAliasFromRoomDirectoryResult = lambdaRecorder> { _ -> Result.success(true) } + val updateCanonicalAliasResult = lambdaRecorder, Result> { _, _ -> Result.success(Unit) } + val closeEditAddressLambda = lambdaRecorder { } + + val navigator = FakeSecurityAndPrivacyNavigator(closeEditRoomAddressLambda = closeEditAddressLambda) + val canonicalAlias = RoomAlias("#canonical:notmatrix.org") + val room = FakeMatrixRoom( + canonicalAlias = canonicalAlias, + updateCanonicalAliasResult = updateCanonicalAliasResult, + publishRoomAliasInRoomDirectoryResult = publishAliasInRoomDirectoryResult, + removeRoomAliasFromRoomDirectoryResult = removeAliasFromRoomDirectoryResult + ) + val presenter = createEditRoomAddressPresenter(room = room, navigator = navigator) + presenter.test { + with(awaitItem()) { + eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + } + skipItems(1) + with(awaitItem()) { + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Valid) + assertThat(canBeSaved).isTrue() + eventSink(EditRoomAddressEvents.Save) + } + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Loading) + } + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) + } + + val createdAlias = RoomAlias("#valid:matrix.org") + assert(updateCanonicalAliasResult) + .isCalledOnce() + .with(value(canonicalAlias), value(listOf(createdAlias))) + + assert(publishAliasInRoomDirectoryResult) + .isCalledOnce() + .with(value(createdAlias)) + + assert(removeAliasFromRoomDirectoryResult).isNeverCalled() + + assert(closeEditAddressLambda).isCalledOnce() + } + } + + @Test + fun `present - save failure`() = runTest { + val closeEditAddressLambda = lambdaRecorder { } + val navigator = FakeSecurityAndPrivacyNavigator( + closeEditRoomAddressLambda = closeEditAddressLambda + ) + val presenter = createEditRoomAddressPresenter(navigator = navigator) + presenter.test { + with(awaitItem()) { + eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + } + skipItems(1) + with(awaitItem()) { + assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Valid) + assertThat(canBeSaved).isTrue() + eventSink(EditRoomAddressEvents.Save) + } + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Loading) + } + with(awaitItem()) { + assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) + } + + assert(closeEditAddressLambda).isNeverCalled() + } + } + + @Test + fun `present - dismiss error`() = runTest { + val presenter = createEditRoomAddressPresenter() + presenter.test { + with(awaitItem()) { + eventSink(EditRoomAddressEvents.Save) + } + with(awaitItem()) { + assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) + eventSink(EditRoomAddressEvents.DismissError) + } + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) + } + } + } + + private fun createMatrixClient(isAliasAvailable: Boolean = true) = FakeMatrixClient( + userIdServerNameLambda = { "matrix.org" }, + resolveRoomAliasResult = { + val resolvedRoomAlias = if (isAliasAvailable) { + Optional.empty() + } else { + Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList())) + } + Result.success(resolvedRoomAlias) + } + ) + + private fun createEditRoomAddressPresenter( + client: FakeMatrixClient = createMatrixClient(), + room: MatrixRoom = FakeMatrixRoom(), + navigator: SecurityAndPrivacyNavigator = FakeSecurityAndPrivacyNavigator(), + roomAliasHelper: RoomAliasHelper = FakeRoomAliasHelper() + ): EditRoomAddressPresenter { + return EditRoomAddressPresenter( + room = room, + client = client, + roomAliasHelper = roomAliasHelper, + navigator = navigator + ) + } +} diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/editroomaddress/EditRoomAddressViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/editroomaddress/EditRoomAddressViewTest.kt new file mode 100644 index 0000000000..75924e8f59 --- /dev/null +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/securityandprivacy/editroomaddress/EditRoomAddressViewTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2025 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.securityandprivacy.editroomaddress + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performTextInput +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress.EditRoomAddressEvents +import io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress.EditRoomAddressState +import io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress.EditRoomAddressView +import io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress.anEditRoomAddressState +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity +import io.element.android.libraries.testtags.TestTags +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.pressBack +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class EditRoomAddressViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `click on back invokes expected callback`() { + ensureCalledOnce { callback -> + rule.setEditRoomAddressView(onBackClick = callback) + rule.pressBack() + } + } + + @Test + fun `click on disabled save doesn't emit event`() { + val recorder = EventsRecorder(expectEvents = false) + val state = anEditRoomAddressState(eventSink = recorder) + rule.setEditRoomAddressView(state) + rule.clickOn(CommonStrings.action_save) + recorder.assertEmpty() + } + + @Test + fun `click on enabled save emits the expected event`() { + val recorder = EventsRecorder() + val state = anEditRoomAddressState( + roomAddress = "room", + roomAddressValidity = RoomAddressValidity.Valid, + eventSink = recorder + ) + rule.setEditRoomAddressView(state) + rule.clickOn(CommonStrings.action_save) + recorder.assertSingle(EditRoomAddressEvents.Save) + } + + @Test + fun `text changes on text field emits the expected event`() { + val recorder = EventsRecorder() + val state = anEditRoomAddressState( + roomAddress = "", + eventSink = recorder + ) + rule.setEditRoomAddressView(state) + + rule.onNodeWithTag(TestTags.roomAddressField.value).performTextInput("alias") + recorder.assertSingle(EditRoomAddressEvents.RoomAddressChanged("alias")) + } + + @Test + fun `click on dismiss error emits the expected event`() { + val recorder = EventsRecorder() + val state = anEditRoomAddressState( + roomAddress = "", + saveAction = AsyncAction.Failure(IllegalStateException()), + eventSink = recorder + ) + rule.setEditRoomAddressView(state) + rule.clickOn(CommonStrings.action_cancel) + recorder.assertSingle(EditRoomAddressEvents.DismissError) + } + + @Test + fun `click on retry error emits the expected event`() { + val recorder = EventsRecorder() + val state = anEditRoomAddressState( + roomAddress = "", + saveAction = AsyncAction.Failure(IllegalStateException()), + eventSink = recorder + ) + rule.setEditRoomAddressView(state) + rule.clickOn(CommonStrings.action_retry) + recorder.assertSingle(EditRoomAddressEvents.Save) + } + + +} + +private fun AndroidComposeTestRule.setEditRoomAddressView( + state: EditRoomAddressState = anEditRoomAddressState( + eventSink = EventsRecorder(expectEvents = false), + ), + onBackClick: () -> Unit = EnsureNeverCalled(), +) { + setContent { + EditRoomAddressView( + state = state, + onBackClick = onBackClick, + ) + } +} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt index 590c170474..c5b3a56514 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt @@ -15,6 +15,8 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextField +import io.element.android.libraries.testtags.TestTags +import io.element.android.libraries.testtags.testTag import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -28,7 +30,7 @@ fun RoomAddressField( modifier: Modifier = Modifier, ) { TextField( - modifier = modifier, + modifier = modifier.testTag(TestTags.roomAddressField), value = address, label = label, leadingIcon = { diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt index a755750184..63da929c94 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt @@ -111,4 +111,11 @@ object TestTags { * Generic call to action. */ val callToAction = TestTag("call_to_action") + + /** + * Room address field. + * + */ + val roomAddressField = TestTag("room_address_field") + }