Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-02 15:15:31 +03:00
parent cc6d2533bf
commit 6b05c5566e
22 changed files with 492 additions and 52 deletions

View file

@ -147,7 +147,9 @@ abstract class BaseTestCase : TestCase(
"NEW_ONRAMP_MAIN_ENABLED" to true,
"HOT_WALLET_ENABLED" to true,
"YIELD_SUPPLY_FEATURE_ENABLED" to true,
"ACCOUNTS_FEATURE_ENABLED" to true
"ACCOUNTS_FEATURE_ENABLED" to true,
"FEED_ENABLED" to true,
"GASLESS_TRANSACTIONS_ENABLED" to true,
)
)
}

View file

@ -1,5 +1,9 @@
package com.tangem.common.extensions
import androidx.compose.ui.test.SemanticsMatcher
import com.tangem.common.utils.LazyListItemNode
import com.tangem.core.ui.components.buttons.actions.HasBadgeKey
import com.tangem.core.ui.components.buttons.actions.IsDimmedKey
import io.github.kakaocup.compose.node.element.KNode
fun assertElementDoesNotExist(
@ -22,4 +26,20 @@ fun assertElementDoesNotExist(
throw e
}
}
}
fun Any.assertIsDimmed(expectedValue: Boolean = true) {
val matcher = SemanticsMatcher.expectValue(IsDimmedKey, expectedValue)
when (this) {
is KNode, is LazyListItemNode -> this.assert(matcher)
else -> throw IllegalArgumentException("Unsupported type: ${this::class}")
}
}
fun Any.assertHasBadge(expectedValue: Boolean = true) {
val matcher = SemanticsMatcher.expectValue(HasBadgeKey, expectedValue)
when (this) {
is KNode, is LazyListItemNode -> this.assert(matcher)
else -> throw IllegalArgumentException("Unsupported type: ${this::class}")
}
}

View file

@ -235,6 +235,11 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
useUnmergedTree = true
}
val searchThroughMarketPlaceholder: KNode = child {
hasText(getResourceString(R.string.markets_search_header_title))
useUnmergedTree = true
}
fun tokenNetworkGroupTitle(tokenNetwork: String): KNode {
return lazyList.child {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)

View file

@ -48,6 +48,11 @@ class MarketsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
useUnmergedTree = true
}
val searchThroughMarketPlaceholder: KNode = child {
hasText(getResourceString(R.string.markets_search_header_title))
useUnmergedTree = true
}
fun tokenWithTitle(title: String): KNode {
return lazyList.child<KNode> {
hasText(title)

View file

@ -0,0 +1,33 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags
import com.tangem.core.ui.test.TokenElementsTestTags
import com.tangem.features.onramp.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.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
class MarketsTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<MarketsTokenDetailsPageObject>(semanticsProvider = semanticsProvider) {
val swapPortfolioQuickActionButton: KNode = child {
hasTestTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON)
hasText(getResourceString(R.string.common_swap), substring = true)
}
fun tokenWithTitle(title: String): KNode = child {
hasAnyAncestor(withTestTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_TOKEN_ITEM))
hasTestTag(TokenElementsTestTags.TOKEN_TITLE)
hasAnySibling(withTestTag(TokenElementsTestTags.TOKEN_ICON))
hasAnyChild(withText(title))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onMarketsTokenDetailsScreen(function: MarketsTokenDetailsPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,70 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.SearchBarTestTags
import com.tangem.core.ui.test.SwapSelectTokenScreenTestTags
import com.tangem.core.ui.test.TokenElementsTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
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.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasText as withText
class SwapSelectTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SwapSelectTokenPageObject>(semanticsProvider = semanticsProvider) {
val title: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(R.string.common_swap))
useUnmergedTree = true
}
val closeButton: KNode = child {
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
}
val youSwapTitle: KNode = child {
hasText(getResourceString(R.string.swapping_from_title))
useUnmergedTree = true
}
val youSwapBlock: KNode = child {
hasTestTag(SwapSelectTokenScreenTestTags.YOU_SWAP_BLOCK)
hasAnyDescendant(withText(getResourceString(R.string.action_buttons_you_want_to_swap)))
useUnmergedTree = true
}
val youReceiveTitle: KNode = child {
hasText(getResourceString(R.string.swapping_to_title))
useUnmergedTree = true
}
val youReceiveBlock: KNode = child {
hasTestTag(SwapSelectTokenScreenTestTags.YOU_SWAP_BLOCK)
hasAnyDescendant(withText(getResourceString(R.string.action_buttons_you_want_to_receive)))
useUnmergedTree = true
}
val searchBarIcon: KNode = child {
hasTestTag(SearchBarTestTags.ICON)
useUnmergedTree = true
}
val searchBarPlaceholderText: KNode = child {
hasTestTag(SearchBarTestTags.PLACEHOLDER_TEXT)
useUnmergedTree = true
}
fun tokenWithName(tokenName: String): KNode = child {
hasTestTag(TokenElementsTestTags.TOKEN_TITLE)
hasAnyChild(withText(tokenName))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onSwapSelectTokenScreen(function: SwapSelectTokenPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -13,6 +13,10 @@ import androidx.compose.ui.test.hasTestTag as withTestTag
class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SwapTokenPageObject>(semanticsProvider = semanticsProvider) {
val container: KNode = child {
hasTestTag(SwapTokenScreenTestTags.CONTAINER)
}
val title: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(R.string.common_swap))
@ -78,6 +82,7 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
fun tokenSymbol(symbol: String): KNode = child {
hasTestTag(SwapTokenScreenTestTags.TOKEN_SYMBOL)
hasText(symbol)
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.tests.actionButtons
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.SwipeDirection
import com.tangem.common.extensions.assertIsDimmed
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.swipeVertical
import com.tangem.common.utils.resetWireMockScenarioState
@ -60,8 +61,6 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
@Test
fun checkActionButtonsStateTest() {
val tokenTitle = "Bitcoin"
val actionButtonIsNotDimmed = "Action button is not dimmed"
val actionButtonIsDimmed = "Action button is dimmed"
setupHooks().run {
step("Open 'Main Screen'") {
@ -75,19 +74,19 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Receive' button is not dimmed") {
onTokenDetailsScreen { receiveButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
onTokenDetailsScreen { receiveButton().assertIsDimmed(false) }
}
step("Assert 'Buy' button is not dimmed") {
onTokenDetailsScreen { buyButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
onTokenDetailsScreen { buyButton().assertIsDimmed(false) }
}
step("Assert 'Send' button is not dimmed") {
onTokenDetailsScreen { sendButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
onTokenDetailsScreen { sendButton().assertIsDimmed(false) }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
onTokenDetailsScreen { swapButton().assertIsDimmed() }
}
step("Assert 'Sell' button is dimmed") {
onTokenDetailsScreen { sellButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
onTokenDetailsScreen { sellButton().assertIsDimmed() }
}
}
}
@ -130,7 +129,6 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
@Test
fun checkSwapButtonProviderErrorTest() {
val tokenTitle = "POL (ex-MATIC)"
val actionButtonIsDimmed = "Action button is dimmed"
setupHooks().run {
step("Open 'Main Screen'") {
@ -147,7 +145,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
onTokenDetailsScreen { swapButton().assertIsDimmed() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
@ -162,7 +160,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
onSwapIsNotSupportedDialog { okButton.performClick() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
onTokenDetailsScreen { swapButton().assertIsDimmed() }
}
}
}
@ -172,7 +170,6 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
@Test
fun checkSwapButtonExpressErrorTest() {
val tokenTitle = "Polygon"
val actionButtonIsDimmed = "Action button is dimmed"
val scenarioName = "express_api_assets"
val scenarioState = "Error"
@ -198,7 +195,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
onTokenDetailsScreen { swapButton().assertIsDimmed() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
@ -213,7 +210,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
onOperationIsUnavailableDialog { okButton.performClick() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
onTokenDetailsScreen { swapButton().assertIsDimmed() }
}
}
}

View file

@ -0,0 +1,72 @@
package com.tangem.tests.swap
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.onMainScreen
import com.tangem.screens.onSwapSelectTokenScreen
import com.tangem.screens.onSwapStoriesScreen
import com.tangem.screens.onSwapTokenScreen
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 SwapSelectTokenScreenTest : BaseTestCase() {
@AllureId("2829")
@DisplayName("Open 'Swap select token' screen from 'Main' screen")
@Test
fun openSwapSelectTokenScreenFromMainScreenTest() {
val swapTokenName = "Ethereum"
val receiveTokenName = "Polygon"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on 'Swap' button") {
onMainScreen { swapButton.performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
}
step("Assert 'Swap select token' screen title is displayed") {
onSwapSelectTokenScreen { title.assertIsDisplayed() }
}
step("Assert 'You swap' title is displayed") {
onSwapSelectTokenScreen { youSwapTitle.assertIsDisplayed() }
}
step("Assert 'You swap' block is displayed") {
onSwapSelectTokenScreen { youSwapBlock.assertIsDisplayed() }
}
step("Assert search icon is displayed") {
onSwapSelectTokenScreen { searchBarIcon.assertIsDisplayed() }
}
step("Assert search placeholder is displayed") {
onSwapSelectTokenScreen { searchBarPlaceholderText.assertIsDisplayed() }
}
step("Click on token with name '$swapTokenName'") {
onSwapSelectTokenScreen { tokenWithName(swapTokenName).performClick() }
}
step("Assert 'You receive' title is displayed") {
onSwapSelectTokenScreen { youReceiveTitle.assertIsDisplayed() }
}
step("Assert 'You receive' block is displayed") {
onSwapSelectTokenScreen { youReceiveBlock.assertIsDisplayed() }
}
step("Click on token with name '$receiveTokenName'") {
onSwapSelectTokenScreen { tokenWithName(receiveTokenName).performClick() }
}
step("Assert 'Swap token' screen is opened") {
onSwapTokenScreen { container.assertIsDisplayed() }
}
}
}
}

View file

@ -0,0 +1,141 @@
package com.tangem.tests.swap
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.assertHasBadge
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.*
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 SwapStoriesTest : BaseTestCase() {
@AllureId("5453")
@DisplayName("Check 'Swap' button badge on 'Main' screen")
@Test
fun checkMainScreenSwapButtonBadgeTest() {
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Assert 'Swap' button has badge") {
onMainScreen { swapButton.assertHasBadge() }
}
step("Click on 'Swap' button") {
onMainScreen { swapButton.performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
}
step("Assert 'Swap' screen title is displayed") {
onSwapTokenScreen { title.assertIsDisplayed() }
}
step("Click on 'Close' button") {
onSwapTokenScreen { closeButton.performClick() }
}
step("Assert 'Swap' button has not badge") {
onMainScreen { swapButton.assertHasBadge(false) }
}
}
}
@AllureId("5454")
@DisplayName("Check 'Swap' button badge on token details screen")
@Test
fun checkTokenDetailsScreenSwapButtonTest() {
val tokenName = "Ethereum"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
}
step("Assert 'Swap' button has badge") {
onTokenDetailsScreen { swapButton().assertHasBadge() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
}
step("Assert 'Swap' screen title is displayed") {
onSwapTokenScreen { title.assertIsDisplayed() }
}
step("Click on 'Close' button") {
onSwapTokenScreen { closeButton.performClick() }
}
step("Assert 'Swap' button has not badge") {
onTokenDetailsScreen { swapButton().assertHasBadge(false) }
}
}
}
@AllureId("5455")
@DisplayName("Check 'Swap' button badge on token details in 'Market' screen")
@Test
fun checkMarketTokenDetailsScreenSwapButtonTest() {
val tokenName = "Ethereum"
val badgeShown = "Badge shown"
val badgeHidden = "Badge hidden"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Markets' screen") {
onMainScreen { searchThroughMarketPlaceholder.performClick() }
waitForIdle()
}
step("") {
onMarketsScreen { searchThroughMarketPlaceholder.performClick() }
}
step("Click on $tokenName token") {
waitForIdle()
onMarketsScreen { tokenWithTitle(tokenName).clickWithAssertion() }
}
step("Click on $tokenName token") {
waitForIdle()
onMarketsTokenDetailsScreen { tokenWithTitle(tokenName).performClick() }
}
step("Assert 'Swap' button has badge") {
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertIsDisplayed() }
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeShown) }
}
step("Click on 'Swap' button") {
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
}
step("Assert 'Swap' screen title is displayed") {
onSwapTokenScreen { title.assertIsDisplayed() }
}
step("Click on 'Close' button") {
onSwapTokenScreen { closeButton.performClick() }
}
step("Assert 'Swap' button has not badge") {
onMarketsTokenDetailsScreen { swapPortfolioQuickActionButton.assertContentDescriptionEquals(badgeHidden) }
}
}
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.tests
package com.tangem.tests.swap
import androidx.compose.ui.test.hasText
import com.tangem.common.BaseTestCase
@ -19,7 +19,7 @@ import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class SwapTokenTest : BaseTestCase() {
class SwapTokenScreenTest : BaseTestCase() {
@ApiEnv(
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
@ -43,7 +43,7 @@ class SwapTokenTest : BaseTestCase() {
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
}
step("Click on token with name: '$tokenTitle'") {
step("Assert title: '$tokenTitle' is displayed") {
onTokenDetailsScreen { title.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
@ -123,7 +123,7 @@ class SwapTokenTest : BaseTestCase() {
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
}
step("Click on token with name: '$tokenTitle'") {
step("Assert title: '$tokenTitle' is displayed") {
onTokenDetailsScreen { title.assertIsDisplayed() }
}
step("Turn off Wi-Fi and Mobile Data") {
@ -171,7 +171,7 @@ class SwapTokenTest : BaseTestCase() {
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
}
step("Click on token with name: '$tokenTitle'") {
step("Assert title: '$tokenTitle' is displayed") {
onTokenDetailsScreen { title.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
@ -239,4 +239,55 @@ class SwapTokenTest : BaseTestCase() {
}
}
}
@AllureId("2828")
@DisplayName("Swap: network fee")
@Test
fun goToTokenSwapTest() {
val swapTokenSymbol = "POL"
val receiveTokenSymbol = "ETH"
val tokenTitle = "Polygon"
setupHooks().run {
resetWireMockScenarios()
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
}
step("Assert title: '$tokenTitle' is displayed") {
onTokenDetailsScreen { title.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
}
step("Assert 'Swap' screen title is displayed") {
onSwapTokenScreen { title.assertIsDisplayed() }
}
step("Assert 'Close' button is displayed") {
onSwapTokenScreen { closeButton.assertIsDisplayed() }
}
step("Assert 'Swap tokens on screen' button is displayed") {
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT) {
swapTokensOnscreenButton.assertIsDisplayed()
}
}
}
step("Assert token symbol: '$swapTokenSymbol' is displayed") {
onSwapTokenScreen { tokenSymbol(swapTokenSymbol).assertIsDisplayed() }
}
step("Assert token symbol: '$receiveTokenSymbol' is displayed") {
onSwapTokenScreen { tokenSymbol(receiveTokenSymbol).assertIsDisplayed() }
}
}
}
}

View file

@ -0,0 +1,10 @@
package com.tangem.core.ui.components.buttons.actions
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
val IsDimmedKey = SemanticsPropertyKey<Boolean>("IsDimmed")
val HasBadgeKey = SemanticsPropertyKey<Boolean>("HasBadge")
var SemanticsPropertyReceiver.isDimmed by IsDimmedKey
var SemanticsPropertyReceiver.hasBadge by HasBadgeKey

View file

@ -22,7 +22,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -136,11 +135,8 @@ fun ActionBaseButton(
}
.clip(shape)
.semantics {
contentDescription = if (config.shouldDimContent) {
"Action button is dimmed"
} else {
"Action button is not dimmed"
}
isDimmed = config.shouldDimContent
hasBadge = config.shouldShowBadge
}
.combinedClickable(
enabled = config.isEnabled,

View file

@ -38,6 +38,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.BaseSearchBarTestTags
import com.tangem.core.ui.test.SearchBarTestTags
@Composable
fun SearchBar(
@ -126,7 +127,9 @@ private fun DecorationBox(
contentPadding = PaddingValues(all = TangemTheme.dimens.spacing12),
leadingIcon = {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size20),
modifier = Modifier
.size(TangemTheme.dimens.size20)
.testTag(SearchBarTestTags.ICON),
painter = painterResource(id = R.drawable.ic_search_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
@ -137,6 +140,7 @@ private fun DecorationBox(
state = state,
focusManager = focusManager,
keyboardController = keyboardController,
modifier = Modifier.testTag(SearchBarTestTags.CLEAR_BUTTON),
)
},
placeholder = {
@ -146,6 +150,7 @@ private fun DecorationBox(
style = TangemTheme.typography.body2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.testTag(SearchBarTestTags.PLACEHOLDER_TEXT),
)
},
)

View file

@ -0,0 +1,8 @@
package com.tangem.core.ui.test
object MarketTokenDetailsBottomSheetTestTags {
const val PORTFOLIO_QUICK_ACTION_BUTTON = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_QUICK_ACTION_BUTTON"
const val PORTFOLIO_QUICK_ACTION_BUTTON_TITLE = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_QUICK_ACTION_BUTTON_TITLE"
const val PORTFOLIO_QUICK_ACTION_BUTTON_ICON = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_QUICK_ACTION_BUTTON_ICON"
const val PORTFOLIO_TOKEN_ITEM = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_TOKEN_ITEM"
}

View file

@ -0,0 +1,7 @@
package com.tangem.core.ui.test
object SearchBarTestTags {
const val ICON = "SEARCH_BAR_ICON"
const val CLEAR_BUTTON = "SEARCH_BAR_CLEAR_BUTTON"
const val PLACEHOLDER_TEXT = "SEARCH_BAR_PLACEHOLDER_TEXT"
}

View file

@ -0,0 +1,6 @@
package com.tangem.core.ui.test
object SwapSelectTokenScreenTestTags {
const val YOU_SWAP_BLOCK = "SWAP_SELECT_TOKEN_SCREEN_YOU_SWAP_BLOCK"
const val CHOOSE_TOKEN_TEXT = "SWAP_SELECT_TOKEN_SCREEN_CHOOSE_TOKEN_TEXT"
}

View file

@ -1,6 +1,7 @@
package com.tangem.core.ui.test
object SwapTokenScreenTestTags {
const val CONTAINER = "SWAP_TOKEN_SCREEN_CONTAINER"
const val SWAP_BLOCK_HEADER = "SWAP_TOKEN_SCREEN_SWAP_BLOCK"
const val BALANCE = "SWAP_TOKEN_SCREEN_BALANCE"
const val SWAP_TEXT_FIELD = "SWAP_TOKEN_SCREEN_SWAP_TEXT_FIELD"

View file

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
@ -17,6 +18,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.PortfolioTokenUM
import com.tangem.features.feed.impl.R
@ -49,6 +51,7 @@ internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifie
start = TangemTheme.dimens.spacing10,
end = TangemTheme.dimens.spacing12,
),
modifier = Modifier.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_TOKEN_ITEM),
)
PortfolioQuickActions(

View file

@ -1,45 +1,29 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.*
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
@ -51,6 +35,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MarketTokenDetailsBottomSheetTestTags
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.QuickActionUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -151,7 +136,8 @@ private fun AnimatedVisibilityScope.QuickActionItem(
onClick()
},
)
.padding(horizontal = TangemTheme.dimens.spacing14, vertical = TangemTheme.dimens.spacing4),
.padding(horizontal = TangemTheme.dimens.spacing14, vertical = TangemTheme.dimens.spacing4)
.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
) {
@ -168,6 +154,7 @@ private fun AnimatedVisibilityScope.QuickActionItem(
text = state.title.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON_TITLE),
)
Text(
text = state.description.resolveReference(),
@ -192,6 +179,13 @@ private fun AnimatedVisibilityScope.QuickActionIcon(state: QuickActionUM) {
shape = CircleShape,
)
.size(TangemTheme.dimens.size32)
.semantics {
contentDescription = if (state is QuickActionUM.Exchange && state.shouldShowBadge) {
"Badge shown"
} else {
"Badge hidden"
}
}
.drawWithContent {
drawContent()
if (state is QuickActionUM.Exchange && state.shouldShowBadge) {
@ -202,7 +196,8 @@ private fun AnimatedVisibilityScope.QuickActionIcon(state: QuickActionUM) {
) {
Icon(
modifier = Modifier
.requiredSize(TangemTheme.dimens.size16),
.requiredSize(TangemTheme.dimens.size16)
.testTag(MarketTokenDetailsBottomSheetTestTags.PORTFOLIO_QUICK_ACTION_BUTTON_ICON),
imageVector = ImageVector.vectorResource(id = state.icon),
contentDescription = null,
tint = TangemTheme.colors.button.primary,

View file

@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -30,6 +31,7 @@ import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SwapSelectTokenScreenTestTags
import com.tangem.core.ui.utils.dashedBorder
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.swap.entity.ExchangeCardUM
@ -50,7 +52,8 @@ internal fun ExchangeCard(state: ExchangeCardUM, isBalanceHidden: Boolean, modif
.fillMaxWidth()
.heightIn(min = 116.dp)
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.primary),
.background(TangemTheme.colors.background.primary)
.testTag(SwapSelectTokenScreenTestTags.YOU_SWAP_BLOCK),
verticalArrangement = Arrangement.SpaceBetween,
) {
Title(
@ -157,6 +160,7 @@ private fun EmptyTokenBlock(text: TextReference, modifier: Modifier = Modifier)
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.body2,
modifier = Modifier.testTag(SwapSelectTokenScreenTestTags.CHOOSE_TOKEN_TEXT),
)
}
}

View file

@ -8,11 +8,13 @@ import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import com.tangem.common.ui.bottomsheet.permission.GiveTxPermissionBottomSheet
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.SwapTokenScreenTestTags
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent
import com.tangem.feature.swap.models.SwapStateHolder
@ -50,7 +52,9 @@ internal fun SwapScreen(stateHolder: SwapStateHolder, feeSelectorBlockComponent:
} else {
null
},
modifier = Modifier.padding(scaffoldPaddings),
modifier = Modifier
.padding(scaffoldPaddings)
.testTag(SwapTokenScreenTestTags.CONTAINER),
)
if (stateHolder.bottomSheetConfig != null) {