Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: connection button not shown when partial metadata (WPB-10342) #3280

Merged
merged 7 commits into from
Aug 5, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.userprofile.other

import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import com.wire.android.ui.WireTestTheme
import com.wire.android.ui.home.conversationslist.model.Membership
import com.wire.android.ui.userprofile.other.OtherUserStubs.provideState
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Instant
import org.junit.Rule
import org.junit.Test

class OtherUserProfileGroupTest {
@get:Rule
val composeTestRule by lazy { createComposeRule() }

@Test
fun givenARoleSelectionComponentIsShow_ShouldNotAllowModificationForTempUsers() = runTest {
composeTestRule.setContent {
WireTestTheme {
OtherUserProfileGroup(
provideState(withExpireAt = Instant.DISTANT_FUTURE.toEpochMilliseconds()),
onRemoveFromConversation = {},
openChangeRoleBottomSheet = {},
)
}
}

composeTestRule.onNodeWithContentDescription("Edit").assertDoesNotExist()
}

@Test
fun givenARoleSelectionComponentIsShow_ShouldNotAllowModificationForUsersWithoutMetadata() = runTest {
composeTestRule.setContent {
WireTestTheme {
OtherUserProfileGroup(
provideState(withUserName = "", withFullName = ""),
onRemoveFromConversation = {},
openChangeRoleBottomSheet = {},
)
}
}

composeTestRule.onNodeWithContentDescription("Edit").assertDoesNotExist()
}

@Test
fun givenARoleSelectionComponentIsShow_ShouldNotAllowModificationForFederatedUsers() = runTest {
composeTestRule.setContent {
WireTestTheme {
OtherUserProfileGroup(
provideState(withMembership = Membership.Federated),
onRemoveFromConversation = {},
openChangeRoleBottomSheet = {},
)
}
}

composeTestRule.onNodeWithContentDescription("Edit").assertDoesNotExist()
}

@Test
fun givenARoleSelectionComponentIsShow_ShouldNotAllowModificationForServices() = runTest {
composeTestRule.setContent {
WireTestTheme {
OtherUserProfileGroup(
provideState(withMembership = Membership.Service),
onRemoveFromConversation = {},
openChangeRoleBottomSheet = {},
)
}
}

composeTestRule.onNodeWithContentDescription("Edit").assertDoesNotExist()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.userprofile.other

import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import com.wire.android.ui.WireTestTheme
import com.wire.android.ui.connection.CONNECTION_ACTION_BUTTONS_TEST_TAG
import com.wire.android.ui.home.conversationslist.model.Membership
import com.wire.android.ui.theme.wireDimensions
import com.wire.android.ui.userprofile.other.OtherUserStubs.provideState
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Instant
import org.junit.Rule
import org.junit.Test

class OtherUserProfileScreenTest {
@get:Rule
val composeTestRule by lazy { createComposeRule() }

@Test
fun givenOtherUserProfileFooter_ShouldNotShowConnectButtonForTempUsers() = runTest {
composeTestRule.setContent {
WireTestTheme {
ContentFooter(
state = provideState(withExpireAt = Instant.DISTANT_FUTURE.toEpochMilliseconds()),
maxBarElevation = MaterialTheme.wireDimensions.topBarShadowElevation
)
}
}

composeTestRule.onNodeWithTag(CONNECTION_ACTION_BUTTONS_TEST_TAG).assertDoesNotExist()
}

@Test
fun givenOtherUserProfileFooter_ShouldNotShowConnectButtonForUsersWithoutMetadata() = runTest {
composeTestRule.setContent {
WireTestTheme {
ContentFooter(
state = provideState(withUserName = "", withFullName = ""),
maxBarElevation = MaterialTheme.wireDimensions.topBarShadowElevation
)
}
}

composeTestRule.onNodeWithTag(CONNECTION_ACTION_BUTTONS_TEST_TAG).assertDoesNotExist()
}

@Test
fun givenOtherUserProfileFooter_ShouldNotShowConnectButtonForServices() = runTest {
composeTestRule.setContent {
WireTestTheme {
ContentFooter(
state = provideState(withMembership = Membership.Service),
maxBarElevation = MaterialTheme.wireDimensions.topBarShadowElevation
)
}
}

composeTestRule.onNodeWithTag(CONNECTION_ACTION_BUTTONS_TEST_TAG).assertDoesNotExist()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.userprofile.other

import com.wire.android.ui.home.conversationslist.model.Membership
import com.wire.kalium.logic.data.conversation.Conversation.Member
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.UserId
import kotlinx.datetime.Instant

object OtherUserStubs {
private val baseState = OtherUserProfileState(
userId = UserId("some_user", "domain.com"),
fullName = "name",
userName = "username",
teamName = "team",
email = "email",
groupState = OtherUserProfileGroupState(
groupName = "group name",
role = Member.Role.Member,
isSelfAdmin = true,
conversationId = ConversationId("some_user", "domain.com")
)
)

fun provideState(
withFullName: String = "name",
withUserName: String = "username",
withExpireAt: Long? = null,
withMembership: Membership = Membership.Standard
): OtherUserProfileState {
return baseState.copy(
fullName = withFullName,
userName = withUserName,
expiresAt = withExpireAt?.let { Instant.fromEpochMilliseconds(it) },
membership = withMembership
)
}
}
4 changes: 2 additions & 2 deletions app/src/main/kotlin/com/wire/android/mapper/ContactMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import com.wire.android.model.ImageAsset
import com.wire.android.model.UserAvatarData
import com.wire.android.ui.home.conversationslist.model.Membership
import com.wire.android.ui.home.newconversation.model.Contact
import com.wire.android.ui.userprofile.common.UsernameMapper.mapUserLabel
import com.wire.android.ui.userprofile.common.UsernameMapper
import com.wire.android.util.EMPTY
import com.wire.android.util.ui.WireSessionImageLoader
import com.wire.kalium.logic.data.publicuser.model.UserSearchDetails
Expand All @@ -44,7 +44,7 @@ class ContactMapper
id = id.value,
domain = id.domain,
name = name.orEmpty(),
label = mapUserLabel(otherUser),
label = UsernameMapper.fromOtherUser(otherUser),
avatarData = UserAvatarData(
asset = previewPicture?.let { ImageAsset.UserAvatarAsset(wireSessionImageLoader, it) },
connectionState = connectionStatus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class UIParticipantMapper @Inject constructor(
isMLSVerified = isMLSVerified,
supportedProtocolList = supportedProtocols.orEmpty().toList(),
isUnderLegalHold = isUnderLegalHold,
expiresAt = user.expiresAt
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import com.wire.android.R
Expand Down Expand Up @@ -56,6 +57,8 @@ import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.ConnectionState
import com.wire.kalium.logic.data.user.UserId

const val CONNECTION_ACTION_BUTTONS_TEST_TAG = "connection_buttons"

@Composable
fun ConnectionActionButton(
userId: UserId,
Expand Down Expand Up @@ -98,6 +101,7 @@ fun ConnectionActionButton(
loading = viewModel.actionableState().isPerformingAction,
onClick = viewModel::onCancelConnectionRequest,
clickBlockParams = ClickBlockParams(blockWhenSyncing = true, blockWhenConnecting = true),
modifier = Modifier.testTag(CONNECTION_ACTION_BUTTONS_TEST_TAG),
)

ConnectionState.ACCEPTED -> WirePrimaryButton(
Expand All @@ -108,6 +112,7 @@ fun ConnectionActionButton(
unableStartConversationDialogState.show(UnableStartConversationDialogState(fullName))
}
},
modifier = Modifier.testTag(CONNECTION_ACTION_BUTTONS_TEST_TAG),
)

ConnectionState.IGNORED -> WirePrimaryButton(
Expand All @@ -121,7 +126,8 @@ fun ConnectionActionButton(
contentDescription = stringResource(R.string.content_description_right_arrow),
modifier = Modifier.padding(dimensions().spacing8x)
)
}
},
modifier = Modifier.testTag(CONNECTION_ACTION_BUTTONS_TEST_TAG),
)

ConnectionState.PENDING -> Column {
Expand All @@ -136,7 +142,8 @@ fun ConnectionActionButton(
contentDescription = stringResource(R.string.content_description_right_arrow),
modifier = Modifier.padding(dimensions().spacing8x)
)
}
},
modifier = Modifier.testTag(CONNECTION_ACTION_BUTTONS_TEST_TAG),
)
Spacer(modifier = Modifier.height(dimensions().spacing8x))
WirePrimaryButton(
Expand All @@ -155,7 +162,8 @@ fun ConnectionActionButton(
contentDescription = stringResource(R.string.content_description_right_arrow),
modifier = Modifier.padding(dimensions().spacing8x)
)
}
},
modifier = Modifier.testTag(CONNECTION_ACTION_BUTTONS_TEST_TAG),
)
}

Expand All @@ -172,6 +180,7 @@ fun ConnectionActionButton(
)
},
clickBlockParams = ClickBlockParams(blockWhenSyncing = true, blockWhenConnecting = true),
modifier = Modifier.testTag(CONNECTION_ACTION_BUTTONS_TEST_TAG),
)
}

Expand All @@ -188,7 +197,8 @@ fun ConnectionActionButton(
contentDescription = stringResource(R.string.content_description_right_arrow),
modifier = Modifier.padding(dimensions().spacing8x)
)
}
},
modifier = Modifier.testTag(CONNECTION_ACTION_BUTTONS_TEST_TAG),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ data class UIParticipant(
val isMLSVerified: Boolean = false,
val supportedProtocolList: List<SupportedProtocol> = listOf(),
val isUnderLegalHold: Boolean = false,
val expiresAt: Instant? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,4 @@ data class Contact(
val label: String = "",
val connectionState: ConnectionState,
val membership: Membership
) {
fun isMetadataEmpty(): Boolean {
return name.isEmpty()
}
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@

package com.wire.android.ui.userprofile.common

import com.wire.android.util.ifNotEmpty
import com.wire.kalium.logic.data.user.OtherUser
import com.wire.kalium.logic.data.user.type.UserType

object UsernameMapper {

fun mapUserLabel(otherUser: OtherUser): String = with(otherUser) {
/**
* Returns the username for the given [OtherUser].
* The username is the handle if it exists, otherwise it is the handle@domain for federated users.
*/
fun fromOtherUser(otherUser: OtherUser): String = with(otherUser) {
val userId = otherUser.id
return when (otherUser.userType) {
UserType.FEDERATED -> if (handle != null) "$handle@${userId.domain}" else ""
UserType.FEDERATED -> handle?.ifNotEmpty { "$handle@${userId.domain}" }.orEmpty()
else -> handle.orEmpty()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ fun OtherUserProfileGroup(
value = AnnotatedString(state.groupState!!.role.name.asString()),
isSelfAdmin = state.groupState.isSelfAdmin,
openChangeRoleBottomSheet = openChangeRoleBottomSheet,
isRoleEditable = state.membership.allowsRoleEdition() && !state.isMetadataEmpty()
isRoleEditable = state.membership.allowsRoleEdition() && !state.isMetadataEmpty() && !state.isTemporaryUser()
)
}
}
Expand Down
Loading
Loading