From e40706d5c6885a7737a53afaac481113c8b50bc6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 22 Jul 2025 11:54:49 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../common/extensions/SemanticMatchers.kt | 9 + .../tangem/screens/MainScreenPageObject.kt | 36 ++- .../screens/OrganizeTokensPageObject.kt | 113 ++++++++++ .../com/tangem/tests/OrganizeTokensTest.kt | 206 ++++++++++++++++++ .../core/ui/components/token/TokenItem.kt | 14 +- .../token/internal/NonFiatContentBlock.kt | 5 +- .../tangem/core/ui/test/MainScreenTestTags.kt | 7 +- .../ui/test/OrganizeTokensScreenTestTags.kt | 16 ++ .../core/ui/test/TokenElementsTestTags.kt | 10 + .../organizetokens/OrganizeTokensScreen.kt | 32 ++- .../MultiCurrencyOrganizeButton.kt | 4 +- 11 files changed, 428 insertions(+), 24 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/tangem/common/extensions/SemanticMatchers.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/screens/OrganizeTokensPageObject.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/test/OrganizeTokensScreenTestTags.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt diff --git a/app/src/androidTest/kotlin/com/tangem/common/extensions/SemanticMatchers.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/SemanticMatchers.kt new file mode 100644 index 0000000000..4bf468227f --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/SemanticMatchers.kt @@ -0,0 +1,9 @@ +package com.tangem.common.extensions + +import androidx.compose.ui.test.SemanticsMatcher +import com.tangem.core.ui.utils.LazyListItemPositionSemantics +import io.github.kakaocup.compose.node.builder.ViewBuilder + +fun ViewBuilder.hasLazyListItemPosition(position: Int) = apply { + addSemanticsMatcher(SemanticsMatcher.expectValue(LazyListItemPositionSemantics, position)) +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt index 9a7dd37c66..c9a08a06f8 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt @@ -4,7 +4,9 @@ import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.hasLazyListItemPosition import com.tangem.common.utils.LazyListItemNode +import com.tangem.core.ui.test.TokenElementsTestTags import com.tangem.core.ui.test.MainScreenTestTags import com.tangem.core.ui.utils.LazyListItemPositionSemantics import com.tangem.feature.wallet.impl.R @@ -14,6 +16,7 @@ import io.github.kakaocup.compose.node.element.KNode import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode import io.github.kakaocup.kakao.common.utilities.getResourceString import androidx.compose.ui.test.hasTestTag as withTestTag +import androidx.compose.ui.test.hasText as withText class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : ComposeScreen(semanticsProvider = semanticsProvider) { @@ -45,7 +48,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) hasText(tokenTitle) }.child { - hasTestTag(MainScreenTestTags.TOKEN_FIAT_AMOUNT) + hasTestTag(TokenElementsTestTags.TOKEN_FIAT_AMOUNT) useUnmergedTree = true } } @@ -63,6 +66,37 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) } } + @OptIn(ExperimentalTestApi::class) + fun organizeTokensButton(): KNode { + return lazyList.childWith { + hasTestTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON) + }.child { + hasText(getResourceString(R.string.organize_tokens_title)) + useUnmergedTree = true + } + } + + fun tokenNetworkGroupTitle(tokenNetwork: String): KNode { + return lazyList.child { + hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) + hasAnyChild(withText(tokenNetwork)) + useUnmergedTree = true + } + } + + @OptIn(ExperimentalTestApi::class) + fun tokenWithTitleAndPosition(tokenTitle: String, index: Int): KNode { + return lazyList.childWith { + hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) + hasText(tokenTitle) + hasLazyListItemPosition(index) + }.child { + hasTestTag(TokenElementsTestTags.TOKEN_TITLE) + useUnmergedTree = true + } + } + + /** * This assertion is required to properly verify the token's absence in the semantic tree. * Tests will fail if assertIsNotDisplayed() or assertDoesNotExist() are used instead. diff --git a/app/src/androidTest/kotlin/com/tangem/screens/OrganizeTokensPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/OrganizeTokensPageObject.kt new file mode 100644 index 0000000000..f7a76181e8 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/OrganizeTokensPageObject.kt @@ -0,0 +1,113 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.hasLazyListItemPosition +import com.tangem.common.utils.LazyListItemNode +import com.tangem.core.ui.test.TokenElementsTestTags +import com.tangem.core.ui.test.OrganizeTokensScreenTestTags +import com.tangem.core.ui.utils.LazyListItemPositionSemantics +import com.tangem.feature.wallet.impl.R +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode +import io.github.kakaocup.kakao.common.utilities.getResourceString +import androidx.compose.ui.test.hasText as withText +import androidx.compose.ui.test.hasTestTag as withTestTag +import androidx.compose.ui.test.hasAnySibling as withAnySibling +import androidx.compose.ui.test.hasAnyChild as withAnyChild + +class OrganizeTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + // region TopBar + val title: KNode = child { + hasText(getResourceString(R.string.organize_tokens_title)) + } + + private val topBarGroupButton: KNode = child { + hasTestTag(OrganizeTokensScreenTestTags.GROUP_BUTTON) + } + + val groupButton: KNode = topBarGroupButton.child { + hasText(getResourceString(R.string.organize_tokens_group)) + useUnmergedTree = true + } + + val ungroupButton: KNode = topBarGroupButton.child { + hasText(getResourceString(R.string.organize_tokens_ungroup)) + useUnmergedTree = true + } + + val sortByBalanceButton: KNode = child { + hasTestTag(OrganizeTokensScreenTestTags.SORT_BY_BALANCE_BUTTON) + useUnmergedTree = true + } + // endregion TopBar + + private val lazyList = KLazyListNode( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST) }, + itemTypeBuilder = { + itemType(::LazyListItemNode) + }, + positionMatcher = { position -> + SemanticsMatcher.expectValue( + LazyListItemPositionSemantics, + position + ) + } + ) + + val applyButton: KNode = child { + hasTestTag(OrganizeTokensScreenTestTags.APPLY_BUTTON) + useUnmergedTree = true + } + + val cancelButton: KNode = child { + hasTestTag(OrganizeTokensScreenTestTags.CANCEL_BUTTON) + useUnmergedTree = true + } + + fun tokenWithTitle(tokenTitle: String): KNode { + return lazyList.child { + hasTestTag(OrganizeTokensScreenTestTags.TOKEN_LIST_ITEM) + hasAnyDescendant(withText(tokenTitle)) + useUnmergedTree = true + } + } + + fun tokenNetworkGroupTitle(tokenNetwork: String): KNode { + return lazyList.child { + hasTestTag(OrganizeTokensScreenTestTags.GROUP_TITLE_ITEM) + hasAnyChild(withText(tokenNetwork)) + useUnmergedTree = true + } + } + + fun tokenWithTitleAndPosition(tokenTitle: String, index: Int): KNode { + return lazyList.child { + hasLazyListItemPosition(index) + hasTestTag(OrganizeTokensScreenTestTags.TOKEN_LIST_ITEM) + hasAnyDescendant(withText(tokenTitle)) + useUnmergedTree = true + } + } + + fun tokenDraggableButton(tokenTitle: String): KNode { + return lazyList.child { + hasTestTag(OrganizeTokensScreenTestTags.DRAGGABLE_IMAGE) + useUnmergedTree = true + hasParent(withTestTag(TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK) + .and(withAnySibling(withTestTag(TokenElementsTestTags.TOKEN_TITLE) + .and(withAnyChild(withText(tokenTitle)))) + ) + ) + } + } +} + +internal fun BaseTestCase.onOrganizeTokensScreen(function: OrganizeTokensPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt new file mode 100644 index 0000000000..3856e30249 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt @@ -0,0 +1,206 @@ +package com.tangem.tests + +import androidx.compose.ui.test.onAllNodesWithText +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.clickWithAssertion +import com.tangem.scenarios.OpenMainScreenScenario +import com.tangem.screens.onMainScreen +import com.tangem.screens.onOrganizeTokensScreen +import dagger.hilt.android.testing.HiltAndroidTest +import io.qameta.allure.kotlin.AllureId +import io.qameta.allure.kotlin.junit4.DisplayName +import org.junit.Test + +@HiltAndroidTest +class OrganizeTokensTest : BaseTestCase() { + + @AllureId("2755") + @DisplayName("Organize tokens: group tokens") + @Test + fun groupTokensTest() { + setupHooks().run { + val tokenTitle = "Ethereum" + val tokenNetwork = "Ethereum network" + step("Open 'Main Screen'") { + scenario(OpenMainScreenScenario(composeTestRule)) + } + step("Click on 'Synchronize addresses' button" ) { + onMainScreen { synchronizeAddressesButton.clickWithAssertion() } + } + step("Click 'Organize tokens' button") { + onMainScreen { organizeTokensButton().clickWithAssertion() } + } + step("Assert 'Organize tokens' screen is opened") { + onOrganizeTokensScreen { + title.assertIsDisplayed() + tokenWithTitle(tokenTitle).assertIsDisplayed() + } + } + step("Click 'Group' button") { + onOrganizeTokensScreen { groupButton.clickWithAssertion() } + } + step("Assert tokens were grouped on 'Organize tokens' screen") { + onOrganizeTokensScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsDisplayed() } + } + step("Click 'Apply' button") { + onOrganizeTokensScreen { applyButton.clickWithAssertion() } + } + step("Assert tokens were grouped on 'Main screen'") { + onMainScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsDisplayed() } + } + step("Click 'Organize tokens' button") { + onMainScreen { organizeTokensButton().clickWithAssertion() } + } + step("Assert 'Organize tokens' screen is opened") { + onOrganizeTokensScreen { + title.assertIsDisplayed() + tokenWithTitle(tokenTitle).assertIsDisplayed() + } + } + step("Click 'Ungroup' button") { + onOrganizeTokensScreen { ungroupButton.clickWithAssertion() } + } + step("Assert tokens were ungrouped on 'Organize tokens' screen") { + onOrganizeTokensScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsNotDisplayed() } + } + step("Click 'Apply' button") { + onOrganizeTokensScreen { applyButton.clickWithAssertion() } + } + step("Assert tokens were ungrouped on 'Main screen'") { + onMainScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsNotDisplayed() } + } + } + } + + @AllureId("2752") + @DisplayName("Organize tokens: check position of tokens") + @Test + fun checkPositionOfTokensTest() { + setupHooks().run { + val ethereumTitle = "Ethereum" + val bitcoinTitle = "Bitcoin" + step("Open 'Main Screen'") { + scenario(OpenMainScreenScenario(composeTestRule)) + } + step("Click on 'Synchronize addresses' button" ) { + onMainScreen { synchronizeAddressesButton.clickWithAssertion() } + } + step("Check positions of tokens on 'Main Screen'") { + onMainScreen { + tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed() + } + } + step("Click 'Organize tokens' button") { + onMainScreen { organizeTokensButton().clickWithAssertion() } + } + step("Check positions of tokens on 'Organize tokens' screen") { + onOrganizeTokensScreen { + tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 2).assertIsDisplayed() + } + } + } + } + + @AllureId("2753") + @DisplayName("Organize tokens: check position of tokens") + // toDo: on test build there is not ability to drag element + // @Test + fun checkCustomTokensOrderTest() { + setupHooks().run { + val ethereumTitle = "Ethereum" + val bitcoinTitle = "Bitcoin" + step("Open 'Main Screen'") { + scenario(OpenMainScreenScenario(composeTestRule)) + } + step("Click on 'Synchronize addresses' button" ) { + onMainScreen { synchronizeAddressesButton.clickWithAssertion() } + } + step("Check positions of tokens on 'Main Screen'") { + onMainScreen { + tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed() + } + } + step("Click 'Organize tokens' button") { + onMainScreen { organizeTokensButton().clickWithAssertion() } + } + step("Drag $bitcoinTitle down on 'Organize tokens' screen") { + composeTestRule.waitUntil(timeoutMillis = 100_000) { + composeTestRule.onAllNodesWithText("Data loaded").fetchSemanticsNodes().isNotEmpty() + } + onOrganizeTokensScreen { + tokenWithTitleAndPosition(bitcoinTitle, 2).assertIsDisplayed() + } + } + step("Click 'Apply' button") { + onOrganizeTokensScreen { applyButton.clickWithAssertion() } + } + step("Check positions of tokens on 'Main Screen'") { + onMainScreen { + tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 0).assertIsDisplayed() + } + } + } + } + + @AllureId("2754") + @DisplayName("Organize tokens: sort by balance") + @Test + fun checkSortByBalanceTest() { + setupHooks().run { + val ethereumTitle = "Ethereum" + val bitcoinTitle = "Bitcoin" + val polygonTitle = "Polygon" + step("Open 'Main Screen'") { + scenario(OpenMainScreenScenario(composeTestRule)) + } + step("Click on 'Synchronize addresses' button" ) { + onMainScreen { synchronizeAddressesButton.clickWithAssertion() } + } + step("Check positions of tokens on 'Main Screen'") { + onMainScreen { + tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed() + } + } + step("Click 'Organize tokens' button") { + onMainScreen { organizeTokensButton().clickWithAssertion() } + } + step("Check positions of tokens on 'Organize tokens' screen") { + onOrganizeTokensScreen { + tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 2).assertIsDisplayed() + tokenWithTitleAndPosition(polygonTitle, 3).assertIsDisplayed() + } + } + step("Click 'By Balance' button") { + onOrganizeTokensScreen { + sortByBalanceButton.clickWithAssertion() + } + } + step("Check positions of tokens by balance on 'Organize tokens' screen") { + onOrganizeTokensScreen { + tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed() + tokenWithTitleAndPosition(bitcoinTitle, 3).assertIsDisplayed() + } + } + step("Click 'Apply' button") { + onOrganizeTokensScreen { applyButton.clickWithAssertion() } + } + step("Check positions of tokens by balance on 'Organize tokens' screen") { + onMainScreen { + tokenWithTitleAndPosition(ethereumTitle, 0).assertIsDisplayed() + tokenWithTitleAndPosition(polygonTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(bitcoinTitle, 2).assertIsDisplayed() + } + } + } + } + + +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt index 135ace246d..284885331e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt @@ -31,7 +31,7 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.test.MainScreenTestTags +import com.tangem.core.ui.test.TokenElementsTestTags import kotlinx.collections.immutable.persistentListOf import org.burnoutcrew.reorderable.ReorderableLazyListState import java.util.UUID @@ -103,7 +103,7 @@ fun TokenItem( modifier = Modifier .layoutId(layoutId = LayoutId.ICON) .padding(end = TangemTheme.dimens.spacing8) - .testTag(MainScreenTestTags.TOKEN_ICON), + .testTag(TokenElementsTestTags.TOKEN_ICON), ) TokenTitle( @@ -112,7 +112,7 @@ fun TokenItem( .layoutId(layoutId = LayoutId.TITLE) .padding(end = TangemTheme.dimens.spacing8) .padding(bottom = betweenRowsMargin) - .testTag(MainScreenTestTags.TOKEN_TITLE), + .testTag(TokenElementsTestTags.TOKEN_TITLE), ) TokenFiatAmount( @@ -121,7 +121,7 @@ fun TokenItem( modifier = Modifier .layoutId(layoutId = LayoutId.FIAT_AMOUNT) .padding(bottom = betweenRowsMargin) - .testTag(MainScreenTestTags.TOKEN_FIAT_AMOUNT), + .testTag(TokenElementsTestTags.TOKEN_FIAT_AMOUNT), ) TokenPrice( @@ -129,7 +129,7 @@ fun TokenItem( modifier = Modifier .layoutId(layoutId = LayoutId.CRYPTO_PRICE) .padding(end = TangemTheme.dimens.spacing8) - .testTag(MainScreenTestTags.TOKEN_PRICE), + .testTag(TokenElementsTestTags.TOKEN_PRICE), ) TokenCryptoAmount( @@ -137,7 +137,7 @@ fun TokenItem( isBalanceHidden = isBalanceHidden, modifier = Modifier .layoutId(layoutId = LayoutId.CRYPTO_AMOUNT) - .testTag(MainScreenTestTags.TOKEN_CRYPTO_AMOUNT), + .testTag(TokenElementsTestTags.TOKEN_CRYPTO_AMOUNT), ) NonFiatContentBlock( @@ -145,7 +145,7 @@ fun TokenItem( reorderableTokenListState = reorderableTokenListState, modifier = Modifier .layoutId(layoutId = LayoutId.NON_FIAT_CONTENT) - .testTag(MainScreenTestTags.TOKEN_NO_ADDRESS), + .testTag(TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK), ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/NonFiatContentBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/NonFiatContentBlock.kt index 7682b67444..d326b2d640 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/NonFiatContentBlock.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/NonFiatContentBlock.kt @@ -9,12 +9,14 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import com.tangem.core.ui.R import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.test.OrganizeTokensScreenTestTags import org.burnoutcrew.reorderable.ReorderableLazyListState import org.burnoutcrew.reorderable.detectReorder @@ -53,7 +55,8 @@ private fun DraggableImage(reorderableTokenListState: ReorderableLazyListState?) } else { Modifier }, - ), + ) + .testTag(OrganizeTokensScreenTestTags.DRAGGABLE_IMAGE), contentAlignment = Alignment.Center, ) { Icon( diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt index 9c1a0b31f8..f635ce4544 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt @@ -5,12 +5,7 @@ object MainScreenTestTags { const val MORE_BUTTON = "MAIN_SCREEN_MORE_BUTTON" const val TOP_BAR = "MAIN_SCREEN_TOP_BAR" const val TOKEN_LIST_ITEM = "MAIN_SCREEN_TOKEN_LIST_ITEM" - const val TOKEN_TITLE = "MAIN_SCREEN_TOKEN_TITLE" - const val TOKEN_ICON = "MAIN_SCREEN_TOKEN_ICON" - const val TOKEN_PRICE = "MAIN_SCREEN_TOKEN_PRICE" - const val TOKEN_FIAT_AMOUNT = "MAIN_SCREEN_TOKEN_FIAT_AMOUNT" - const val TOKEN_CRYPTO_AMOUNT = "MAIN_SCREEN_TOKEN_CRYPTO_AMOUNT" - const val TOKEN_NO_ADDRESS = "MAIN_SCREEN_TOKEN_NO_ADDRESS" const val WALLET_BALANCE = "MAIN_SCREEN_WALLET_BALANCE" const val WALLET_LIST_ITEM = "MAIN_SCREEN_WALLET_LIST_ITEM" + const val ORGANIZE_TOKENS_BUTTON = "MAIN_SCREEN_ORGANIZE_TOKENS_BUTTON" } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/OrganizeTokensScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/OrganizeTokensScreenTestTags.kt new file mode 100644 index 0000000000..1bcf552c56 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/OrganizeTokensScreenTestTags.kt @@ -0,0 +1,16 @@ +package com.tangem.core.ui.test + +object OrganizeTokensScreenTestTags { + // region OrganizeTokensTopBar + const val GROUP_BUTTON = "ORGANIZE_TOKENS_GROUP_BUTTON" + const val SORT_BY_BALANCE_BUTTON = "SORT_BY_BALANCE_BUTTON" + // endregion OrganizeTokensTopBar + + const val TOKENS_LAZY_LIST = "ORGANIZE_TOKENS_TOKENS_LAZY_LIST" + const val TOKEN_LIST_ITEM = "ORGANIZE_TOKENS_TOKEN_LIST_ITEM" + const val GROUP_TITLE_ITEM = "ORGANIZE_TOKENS_GROUP_TITLE_ITEM" + const val DRAGGABLE_IMAGE = "ORGANIZE_TOKENS_DRAGGABLE_IMAGE" + + const val APPLY_BUTTON = "ORGANIZE_TOKENS_APPLY_BUTTON" + const val CANCEL_BUTTON = "ORGANIZE_TOKENS_CANCEL_BUTTON" +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt new file mode 100644 index 0000000000..a6c08721f7 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt @@ -0,0 +1,10 @@ +package com.tangem.core.ui.test + +object TokenElementsTestTags { + const val TOKEN_TITLE = "TOKEN_TITLE" + const val TOKEN_ICON = "TOKEN_ICON" + const val TOKEN_PRICE = "TOKEN_PRICE" + const val TOKEN_FIAT_AMOUNT = "TOKEN_FIAT_AMOUNT" + const val TOKEN_CRYPTO_AMOUNT = "TOKEN_CRYPTO_AMOUNT" + const val TOKEN_NON_FIAT_BLOCK = "TOKEN_NON_FIAT_BLOCK" +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt index 9a09f58f26..0dab60619c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt @@ -20,6 +20,8 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider @@ -38,7 +40,9 @@ import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.reordarable.ReorderableItem import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.OrganizeTokensScreenTestTags import com.tangem.core.ui.utils.WindowInsetsZero +import com.tangem.core.ui.utils.lazyListItemPosition import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem @@ -122,7 +126,8 @@ private fun TokenList( LazyColumn( modifier = Modifier .align(Alignment.TopCenter) - .reorderable(reorderableListState), + .reorderable(reorderableListState) + .testTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST), state = reorderableListState.listState, contentPadding = listContentPadding, ) { @@ -173,16 +178,18 @@ private fun LazyItemScope.DraggableItem( ) { isItemDragging -> isDragging = isItemDragging - val modifierWithBackground = itemModifier.background(color = TangemTheme.colors.background.primary) + val modifierWithBackground = itemModifier + .background(color = TangemTheme.colors.background.primary) + .semantics { lazyListItemPosition = index } when (item) { is DraggableItem.GroupHeader -> DraggableGroupTitleItem( state = item.groupTitle, reorderableTokenListState = reorderableState, - modifier = modifierWithBackground, + modifier = modifierWithBackground.testTag(OrganizeTokensScreenTestTags.GROUP_TITLE_ITEM), ) is DraggableItem.Token -> TokenItem( - modifier = modifierWithBackground, + modifier = modifierWithBackground.testTag(OrganizeTokensScreenTestTags.TOKEN_LIST_ITEM), state = item.tokenItemState, reorderableTokenListState = reorderableState, isBalanceHidden = isBalanceHidden, @@ -201,6 +208,7 @@ private fun LazyItemScope.DraggableItem( } } +@Suppress("LongMethod") @Composable private fun TopBar( config: OrganizeTokensState.HeaderConfig, @@ -246,7 +254,9 @@ private fun TopBar( horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { RoundedActionButton( - modifier = Modifier.weight(1f), + modifier = Modifier + .weight(1f) + .testTag(OrganizeTokensScreenTestTags.SORT_BY_BALANCE_BUTTON), config = ActionButtonConfig( text = TextReference.Res(id = R.string.organize_tokens_sort_by_balance), iconResId = R.drawable.ic_sort_24, @@ -257,7 +267,9 @@ private fun TopBar( color = TangemTheme.colors.background.primary, ) RoundedActionButton( - modifier = Modifier.weight(1f), + modifier = Modifier + .weight(1f) + .testTag(OrganizeTokensScreenTestTags.GROUP_BUTTON), config = ActionButtonConfig( text = TextReference.Res( id = if (config.isGrouped) { @@ -286,12 +298,16 @@ private fun Actions(config: OrganizeTokensState.ActionsConfig, modifier: Modifie horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { SecondaryButton( - modifier = Modifier.weight(1f), + modifier = Modifier + .weight(1f) + .testTag(OrganizeTokensScreenTestTags.CANCEL_BUTTON), text = stringResourceSafe(id = R.string.common_cancel), onClick = config.onCancelClick, ) PrimaryButton( - modifier = Modifier.weight(1f), + modifier = Modifier + .weight(1f) + .testTag(OrganizeTokensScreenTestTags.APPLY_BUTTON), text = stringResourceSafe(id = R.string.common_apply), onClick = config.onApplyClick, showProgress = config.showApplyProgress, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt index 8b981deeb7..8597700ca0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt @@ -2,9 +2,11 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrenc import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.buttons.actions.RoundedActionButton import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.test.MainScreenTestTags import com.tangem.feature.wallet.impl.R private const val ORGANIZE_BUTTON_CONTENT_TYPE = "OrganizeTokensButton" @@ -24,7 +26,7 @@ internal fun LazyListScope.organizeTokensButton( ) { item(key = ORGANIZE_BUTTON_CONTENT_TYPE, contentType = ORGANIZE_BUTTON_CONTENT_TYPE) { RoundedActionButton( - modifier = modifier, + modifier = modifier.testTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON), config = ActionButtonConfig( text = resourceReference(id = R.string.organize_tokens_title), iconResId = R.drawable.ic_filter_24,