Browse Source
* UX cleanup: user profile. - Move send DM to a CTA button. - Add 'Call' CTA button too when there is a DM with that user and a call is possible. - Add missing tests. * Update screenshots * Add tests for clicking on the avatar --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>pull/2826/head
Jorge Martin Espinosa
4 months ago
committed by
GitHub
38 changed files with 396 additions and 56 deletions
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
UX cleanup: user profile. Move send DM to a call to action button, add 'Call' CTA too. |
@ -0,0 +1,235 @@
@@ -0,0 +1,235 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.userprofile |
||||
|
||||
import androidx.activity.ComponentActivity |
||||
import androidx.compose.ui.test.hasTestTag |
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule |
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule |
||||
import androidx.compose.ui.test.performClick |
||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||
import io.element.android.features.userprofile.shared.R |
||||
import io.element.android.features.userprofile.shared.UserProfileEvents |
||||
import io.element.android.features.userprofile.shared.UserProfileState |
||||
import io.element.android.features.userprofile.shared.UserProfileView |
||||
import io.element.android.features.userprofile.shared.aUserProfileState |
||||
import io.element.android.libraries.architecture.AsyncData |
||||
import io.element.android.libraries.matrix.api.core.RoomId |
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL |
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID |
||||
import io.element.android.libraries.matrix.test.A_USER_NAME |
||||
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.EnsureNeverCalledWithParam |
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams |
||||
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.ensureCalledOnceWithParam |
||||
import io.element.android.tests.testutils.ensureCalledOnceWithTwoParams |
||||
import io.element.android.tests.testutils.pressBack |
||||
import io.element.android.tests.testutils.pressBackKey |
||||
import kotlinx.coroutines.test.runTest |
||||
import org.junit.Rule |
||||
import org.junit.Test |
||||
import org.junit.rules.TestRule |
||||
import org.junit.runner.RunWith |
||||
|
||||
@RunWith(AndroidJUnit4::class) |
||||
class UserProfileViewTest { |
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>() |
||||
|
||||
@Test |
||||
fun `on back key press - the expected callback is called`() = runTest { |
||||
ensureCalledOnce { callback -> |
||||
rule.setUserProfileView( |
||||
goBack = callback, |
||||
) |
||||
rule.pressBackKey() |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `on back button click - the expected callback is called`() = runTest { |
||||
ensureCalledOnce { callback -> |
||||
rule.setUserProfileView( |
||||
goBack = callback, |
||||
) |
||||
rule.pressBack() |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `on avatar clicked - the expected callback is called`() = runTest { |
||||
ensureCalledOnceWithTwoParams(A_USER_NAME, AN_AVATAR_URL) { callback -> |
||||
rule.setUserProfileView( |
||||
state = aUserProfileState(userName = A_USER_NAME, avatarUrl = AN_AVATAR_URL), |
||||
openAvatarPreview = callback, |
||||
) |
||||
rule.onNode(hasTestTag(TestTags.memberDetailAvatar.value)).performClick() |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `on avatar clicked with no avatar - nothing happens`() = runTest { |
||||
val callback = EnsureNeverCalledWithTwoParams<String, String>() |
||||
rule.setUserProfileView( |
||||
state = aUserProfileState(userName = A_USER_NAME, avatarUrl = null), |
||||
openAvatarPreview = callback, |
||||
) |
||||
rule.onNode(hasTestTag(TestTags.memberDetailAvatar.value)).performClick() |
||||
} |
||||
|
||||
@Test |
||||
fun `on Share clicked - the expected callback is called`() = runTest { |
||||
ensureCalledOnce { callback -> |
||||
rule.setUserProfileView( |
||||
onShareUser = callback, |
||||
) |
||||
rule.clickOn(CommonStrings.action_share) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `on Message clicked - the StartDm event is emitted`() = runTest { |
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>() |
||||
rule.setUserProfileView( |
||||
state = aUserProfileState( |
||||
dmRoomId = A_ROOM_ID, |
||||
eventSink = eventsRecorder, |
||||
), |
||||
) |
||||
rule.clickOn(CommonStrings.action_message) |
||||
eventsRecorder.assertSingle(UserProfileEvents.StartDM) |
||||
} |
||||
|
||||
@Test |
||||
fun `on Call clicked - the expected callback is called`() = runTest { |
||||
ensureCalledOnceWithParam(A_ROOM_ID) { callback -> |
||||
rule.setUserProfileView( |
||||
state = aUserProfileState( |
||||
dmRoomId = A_ROOM_ID, |
||||
canCall = true, |
||||
), |
||||
onStartCall = callback, |
||||
) |
||||
rule.clickOn(CommonStrings.action_call) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `on Block user clicked - a BlockUser event is emitted with needsConfirmation`() = runTest { |
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>() |
||||
rule.setUserProfileView( |
||||
state = aUserProfileState( |
||||
eventSink = eventsRecorder, |
||||
), |
||||
) |
||||
rule.clickOn(R.string.screen_dm_details_block_user) |
||||
eventsRecorder.assertSingle(UserProfileEvents.BlockUser(needsConfirmation = true)) |
||||
} |
||||
|
||||
@Test |
||||
fun `on confirming block user - a BlockUser event is emitted without needsConfirmation`() = runTest { |
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>() |
||||
rule.setUserProfileView( |
||||
state = aUserProfileState( |
||||
displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block, |
||||
eventSink = eventsRecorder, |
||||
), |
||||
) |
||||
rule.clickOn(R.string.screen_dm_details_block_alert_action) |
||||
eventsRecorder.assertSingle(UserProfileEvents.BlockUser(needsConfirmation = false)) |
||||
} |
||||
|
||||
@Test |
||||
fun `on canceling blocking a user - a ClearConfirmationDialog event is emitted`() = runTest { |
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>() |
||||
rule.setUserProfileView( |
||||
state = aUserProfileState( |
||||
displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block, |
||||
eventSink = eventsRecorder, |
||||
), |
||||
) |
||||
rule.clickOn(CommonStrings.action_cancel) |
||||
eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) |
||||
} |
||||
|
||||
@Test |
||||
fun `on Unblock user clicked - an UnblockUser event is emitted with needsConfirmation`() = runTest { |
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>() |
||||
rule.setUserProfileView( |
||||
state = aUserProfileState( |
||||
isBlocked = AsyncData.Success(true), |
||||
eventSink = eventsRecorder, |
||||
), |
||||
) |
||||
rule.clickOn(R.string.screen_dm_details_unblock_user) |
||||
eventsRecorder.assertSingle(UserProfileEvents.UnblockUser(needsConfirmation = true)) |
||||
} |
||||
|
||||
@Test |
||||
fun `on confirming Unblock user - an UnblockUser event is emitted without needsConfirmation`() = runTest { |
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>() |
||||
rule.setUserProfileView( |
||||
state = aUserProfileState( |
||||
isBlocked = AsyncData.Success(true), |
||||
displayConfirmationDialog = UserProfileState.ConfirmationDialog.Unblock, |
||||
eventSink = eventsRecorder, |
||||
), |
||||
) |
||||
rule.clickOn(R.string.screen_dm_details_unblock_alert_action) |
||||
eventsRecorder.assertSingle(UserProfileEvents.UnblockUser(needsConfirmation = false)) |
||||
} |
||||
|
||||
@Test |
||||
fun `on canceling unblocking a user - a ClearConfirmationDialog event is emitted`() = runTest { |
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>() |
||||
rule.setUserProfileView( |
||||
state = aUserProfileState( |
||||
isBlocked = AsyncData.Success(true), |
||||
displayConfirmationDialog = UserProfileState.ConfirmationDialog.Unblock, |
||||
eventSink = eventsRecorder, |
||||
), |
||||
) |
||||
rule.clickOn(CommonStrings.action_cancel) |
||||
eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) |
||||
} |
||||
} |
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setUserProfileView( |
||||
state: UserProfileState = aUserProfileState( |
||||
eventSink = EventsRecorder(expectEvents = false), |
||||
), |
||||
onShareUser: () -> Unit = EnsureNeverCalled(), |
||||
onDmStarted: (RoomId) -> Unit = EnsureNeverCalledWithParam(), |
||||
onStartCall: (RoomId) -> Unit = EnsureNeverCalledWithParam(), |
||||
goBack: () -> Unit = EnsureNeverCalled(), |
||||
openAvatarPreview: (String, String) -> Unit = EnsureNeverCalledWithTwoParams(), |
||||
) { |
||||
setContent { |
||||
UserProfileView( |
||||
state = state, |
||||
onShareUser = onShareUser, |
||||
onDmStarted = onDmStarted, |
||||
onStartCall = onStartCall, |
||||
goBack = goBack, |
||||
openAvatarPreview = openAvatarPreview, |
||||
) |
||||
} |
||||
} |
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:8fc6369578a3684c1a88e904848bcb16a7e4cde46c887938db909563c6c54d2d |
||||
size 23031 |
||||
oid sha256:96414480d9e361319abf7543324927300f841633f628da68901d6291b8cfd675 |
||||
size 22667 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:59e3eccfa962e9d667aaa326ab3258214334db558983bc251543835585d78453 |
||||
size 20930 |
||||
oid sha256:7388e54cc1fccc87a3257b099aa832f851aabace145cc09f965431653fc0cf35 |
||||
size 20661 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:07418c91e8efee98f7b9e7d069bf3aef2b674164dad0795719f97ad6b214225b |
||||
size 23550 |
||||
oid sha256:a5ca4c3f9c7376656357f07a6b1777bca92808eeb972f9f7d03bdac89a6c5867 |
||||
size 22987 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:90fe61eb831db5e1f1eb213fcdd690f06f2e547b8ad864680288d529d379d30e |
||||
size 43651 |
||||
oid sha256:2348853735daae624c99658d832382fd834a770b40fde0004089f6378ecb954c |
||||
size 41498 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:a6b0328e4f5a06b59ac5b73f4230bd950bed6847af275b9e6851ffdb9403742b |
||||
size 35523 |
||||
oid sha256:364172de34c530ce2cb4799e0b5ac5aa810068794bab10c9f04f72f709ebd8fa |
||||
size 32482 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:7536568491a41526b48354b60db41e47d40b6677e5b38f464c57715499514171 |
||||
size 24193 |
||||
oid sha256:1a956800e49a48da34a49f7ec4c2647641a1c64af70e4ccbcdbdac0fb0cc5616 |
||||
size 23513 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:902bd8ddad85ee2ea52898a910baf0c710880d94c36f56e22916bf4d15f4469c |
||||
size 25864 |
||||
oid sha256:01308cc0d4361e2685a16ce02526a9efdc4f81b86fdb982b016e090bcafa9bbb |
||||
size 23486 |
||||
|
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:ab8949d70455a9b3ef5d6414b788cb1cddec95fc238df8277dfd41c405a4060c |
||||
size 23581 |
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:96414480d9e361319abf7543324927300f841633f628da68901d6291b8cfd675 |
||||
size 22667 |
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:1744d73991a0987c53d214041a26eaf7a3ce8a2ff4c90739f65f8641c4af921d |
||||
size 22351 |
||||
oid sha256:4a6807e02706f29b5b748cfbbb13f9e1437e38438da1c81fa568709e5f88d7a4 |
||||
size 22041 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:4598d748f84146e66b547a5d9310c7e67477e1eb66d1c58bfb23f28807b7f54d |
||||
size 20263 |
||||
oid sha256:b9b5cf8ae7992a8ef83056daa2fb8ebff0c60310ec675efa0e45faa58c0e58ad |
||||
size 20049 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:3d448afa778c03771454d5493f15748dce3eeb7a7b35cd33cb38bb5a06646f5d |
||||
size 22550 |
||||
oid sha256:e256df9ae5a854a906a21868ecdf21a5d2ce37577a69a1da557949562d1da45d |
||||
size 22193 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:e000e964df93279e8325219a8a9ae74d1f66c533d418fabb88a208bc1165dbed |
||||
size 38724 |
||||
oid sha256:aefcccd96088200097cd12a934b27df29819044ab315f3e0a3e1df9228336669 |
||||
size 36801 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:bf1d5e3367a9684cd4fd8ffebd4c38475fad0a6a6fa122df19281c1a3bf7349f |
||||
size 30814 |
||||
oid sha256:561230a37864cced2286168b6bfe7c9e461177c056386dc08e538643d58b15a6 |
||||
size 28162 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:15c3812a12a76e5701bc1f487703ee32d6920f3c2d790f5b1d1a4b468da28000 |
||||
size 23047 |
||||
oid sha256:125d38504d4500506fcb9d85eb9ce057620e683674efb2e6b33356764a14a1af |
||||
size 22677 |
||||
|
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:a3fefee021f7c3bc6a8b950a129fd7549b05416ff72b32b2b24a4317555f43d8 |
||||
size 22916 |
||||
oid sha256:5600ca86ffac9b33c320432dfb75b6242d7726ef39de0bc0e227b166565fa91f |
||||
size 20622 |
||||
|
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:8cc9c9f6cdf542c4817dfcf54f43ebd3e8c7fc47d36a50e60be28642dfbdf8ae |
||||
size 22868 |
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:4a6807e02706f29b5b748cfbbb13f9e1437e38438da1c81fa568709e5f88d7a4 |
||||
size 22041 |
Loading…
Reference in new issue