From fb7eb924d8ac0bdb3c01603979a70ed9cbb36406 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 15 May 2024 15:19:46 +0200 Subject: [PATCH] Add UI test on RoomDetailsEditView --- .../impl/edit/RoomDetailsEditStateProvider.kt | 2 +- .../edit/RoomDetailsEditViewTest.kt | 239 ++++++++++++++++++ libraries/matrixui/build.gradle.kts | 1 + .../ui/components/EditableAvatarView.kt | 8 +- .../android/libraries/testtags/TestTags.kt | 5 + 5 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditViewTest.kt diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt index 7b6e16352a..abf4f64b69 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt @@ -39,7 +39,7 @@ open class RoomDetailsEditStateProvider : PreviewParameterProvider() + + @Test + fun `clicking on back invoke back callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder + ), + onBackPressed = callback, + ) + rule.pressBack() + } + } + + @Test + fun `when edition is successful, the expected callback is invoked`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder, + saveAction = AsyncAction.Success(Unit) + ), + onRoomEdited = callback, + ) + } + } + + @Test + fun `when name is changed, the expected Event is emitted`() { + val eventsRecorder = EventsRecorder() + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder, + roomRawName = "Marketing", + ), + ) + rule.onNodeWithText("Marketing").assertHasClickAction() + rule.onNodeWithText("Marketing").performTextInput("A") + eventsRecorder.assertSingle(RoomDetailsEditEvents.UpdateRoomName("AMarketing")) + } + + @Test + fun `when user cannot change name, nothing happen`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder, + roomRawName = "Marketing", + canChangeName = false, + ), + ) + rule.onNodeWithText("Marketing").assertHasNoClickAction() + } + + @Test + fun `when topic is changed, the expected Event is emitted`() { + val eventsRecorder = EventsRecorder() + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder, + roomTopic = "My Topic", + ), + ) + rule.onNodeWithText("My Topic").assertHasClickAction() + rule.onNodeWithText("My Topic").performTextInput("A") + eventsRecorder.assertSingle(RoomDetailsEditEvents.UpdateRoomTopic("AMy Topic")) + } + + @Test + fun `when user cannot change topic, nothing happen`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder, + roomTopic = "My Topic", + canChangeTopic = false, + ), + ) + rule.onNodeWithText("My Topic").assertHasNoClickAction() + } + + @Test + fun `when avatar is changed with action to take photo, the expected Event is emitted`() { + testAvatarChange( + stringActionRes = CommonStrings.action_take_photo, + expectedEvent = RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto), + ) + } + + @Test + fun `when avatar is changed with action to choose photo, the expected Event is emitted`() { + testAvatarChange( + stringActionRes = CommonStrings.action_choose_photo, + expectedEvent = RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto), + ) + } + + @Test + fun `when avatar is changed with action to remove photo, the expected Event is emitted`() { + testAvatarChange( + stringActionRes = CommonStrings.action_remove, + expectedEvent = RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove), + ) + } + + private fun testAvatarChange( + @StringRes stringActionRes: Int, + expectedEvent: RoomDetailsEditEvents.HandleAvatarAction, + ) { + val eventsRecorder = EventsRecorder() + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder, + ), + ) + // Open the bottom sheet + rule.onNode(hasTestTag(TestTags.editAvatar.value)).performClick() + rule.onNodeWithText(rule.activity.getString(stringActionRes)).assertExists() + rule.clickOn(stringActionRes) + eventsRecorder.assertSingle(expectedEvent) + } + + @Test + fun `when user cannot change avatar, nothing happen`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder, + canChangeAvatar = false, + ), + ) + rule.onNode(hasTestTag(TestTags.editAvatar.value)).performClick() + rule.onNodeWithText(rule.activity.getString(CommonStrings.action_take_photo)).assertDoesNotExist() + } + + @Test + fun `when save is clicked, the expected Event is emitted`() { + val eventsRecorder = EventsRecorder() + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder, + saveButtonEnabled = true, + ), + ) + rule.clickOn(CommonStrings.action_save) + eventsRecorder.assertSingle(RoomDetailsEditEvents.Save) + } + + @Test + fun `when save is clicked, but nothing need to be saved, nothing happens`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder, + saveButtonEnabled = false, + ), + ) + rule.clickOn(CommonStrings.action_save) + } + + @Test + fun `when error is shown, closing the dialog emit the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder, + saveAction = AsyncAction.Failure(Throwable("Whelp")), + ), + ) + rule.clickOn(CommonStrings.action_ok) + eventsRecorder.assertSingle(RoomDetailsEditEvents.CancelSaveChanges) + } +} + +private fun AndroidComposeTestRule.setRoomDetailsEditView( + state: RoomDetailsEditState, + onBackPressed: () -> Unit = EnsureNeverCalled(), + onRoomEdited: () -> Unit = EnsureNeverCalled(), +) { + setContent { + RoomDetailsEditView( + state = state, + onBackPressed = onBackPressed, + onRoomEdited = onRoomEdited, + ) + } +} diff --git a/libraries/matrixui/build.gradle.kts b/libraries/matrixui/build.gradle.kts index 6d955a7c05..ed7f3d068d 100644 --- a/libraries/matrixui/build.gradle.kts +++ b/libraries/matrixui/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.core) implementation(projects.libraries.uiStrings) + implementation(projects.libraries.testtags) implementation(libs.coil.compose) implementation(libs.coil.gif) implementation(libs.jsoup) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt index ad35373895..ad9babb4a4 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt @@ -43,6 +43,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.testtags.TestTags +import io.element.android.libraries.testtags.testTag @Composable fun EditableAvatarView( @@ -53,7 +55,10 @@ fun EditableAvatarView( onAvatarClicked: () -> Unit, modifier: Modifier = Modifier, ) { - Column(modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { Box( modifier = Modifier .size(avatarSize.dp) @@ -62,6 +67,7 @@ fun EditableAvatarView( onClick = onAvatarClicked, indication = rememberRipple(bounded = false), ) + .testTag(TestTags.editAvatar) ) { when (avatarUrl?.scheme) { null, "mxc" -> { 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 3046ba3372..5a74fa42f0 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 @@ -64,6 +64,11 @@ object TestTags { */ val memberDetailAvatar = TestTag("member_detail-avatar") + /** + * Edit avatar. + */ + val editAvatar = TestTag("edit-avatar") + /** * Welcome screen. */