Updated on 2026-08-14
This commit is contained in:
commit
23b66545b2
29 changed files with 427 additions and 23 deletions
|
|
@ -2,6 +2,8 @@ package com.tangem.common
|
|||
|
||||
import android.Manifest
|
||||
import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.test.printToLog
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
|
|
@ -79,4 +81,22 @@ abstract class BaseTestCase : TestCase(
|
|||
additionalAfterSection()
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the Compose semantics tree to logcat for debugging UI tests.
|
||||
*
|
||||
* @param useUnmergedTree When true, shows unmerged tree with all individual nodes.
|
||||
* Use for accessing inner elements of compound components.
|
||||
* Default: false (merged tree - accessibility view).
|
||||
* @param tag Log tag for filtering in logcat. Default: "SEMANTIC_TREE".
|
||||
* @param maxDepth Maximum nesting level to print. Use to avoid log overflow.
|
||||
* Default: Int.MAX_VALUE (unlimited depth).
|
||||
*/
|
||||
fun printSemanticTree(
|
||||
useUnmergedTree: Boolean = false,
|
||||
tag: String = "SEMANTIC_TREE",
|
||||
maxDepth: Int = Int.MAX_VALUE)
|
||||
{
|
||||
composeTestRule.onRoot(useUnmergedTree = useUnmergedTree).printToLog(tag, maxDepth)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.common.utils
|
||||
|
||||
import androidx.compose.ui.semantics.SemanticsNode
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import io.github.kakaocup.compose.node.element.lazylist.KLazyListItemNode
|
||||
|
||||
/**
|
||||
* Represents an item node in a LazyList for Compose UI testing.
|
||||
* Allows test actions (clicks, scrolls) and assertions on individual list items.
|
||||
*/
|
||||
class LazyListItemNode(
|
||||
semanticsNode: SemanticsNode,
|
||||
semanticsProvider: SemanticsNodeInteractionsProvider,
|
||||
) : KLazyListItemNode<LazyListItemNode>(semanticsNode, semanticsProvider)
|
||||
|
|
@ -32,9 +32,9 @@ class OpenMainScreenScenario(
|
|||
assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<TopBarPageObject>(testRule) {
|
||||
ComposeScreen.onComposeScreen<MarketsTooltipPageObject>(testRule) {
|
||||
step("Close Markets tooltip"){
|
||||
performClick()
|
||||
contentContainer.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
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.DialogTestTags
|
||||
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
|
||||
|
||||
class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<DialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val dialogContainer: KNode = child {
|
||||
hasTestTag(DialogTestTags.DIALOG_CONTAINER)
|
||||
}
|
||||
|
||||
val cancelButton: KNode = child {
|
||||
hasTestTag(DialogTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_cancel))
|
||||
}
|
||||
|
||||
val hideButton: KNode = child {
|
||||
hasTestTag(DialogTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.token_details_hide_alert_hide))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onDialog(function: DialogPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -1,16 +1,85 @@
|
|||
package com.tangem.screens
|
||||
|
||||
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.utils.LazyListItemNode
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
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.hasTestTag as withTestTag
|
||||
|
||||
class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<MainScreenPageObject>(
|
||||
ComposeScreen<MainScreenPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
private val lazyList = KLazyListNode(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(MainScreenTestTags.SCREEN_CONTAINER) }
|
||||
viewBuilderAction = { hasTestTag(MainScreenTestTags.SCREEN_CONTAINER) },
|
||||
itemTypeBuilder = {
|
||||
itemType(::LazyListItemNode)
|
||||
},
|
||||
positionMatcher = { position ->
|
||||
SemanticsMatcher.expectValue(
|
||||
LazyListItemPositionSemantics,
|
||||
position
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
val synchronizeAddressesButton: KNode = child {
|
||||
hasText(getResourceString(R.string.common_generate_addresses))
|
||||
}
|
||||
|
||||
/**
|
||||
* Find token list item with title and address
|
||||
*/
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun tokenWithTitleAndAddress(tokenTitle: String): KNode {
|
||||
return lazyList.childWith<LazyListItemNode> {
|
||||
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
hasText(tokenTitle)
|
||||
}.child<KNode> {
|
||||
hasTestTag(MainScreenTestTags.TOKEN_FIAT_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find node with wallet balance using lazyList. This construction doesn't affect next step with lazyList.
|
||||
*/
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun walletBalance(): KNode {
|
||||
return lazyList.childWith<LazyListItemNode> {
|
||||
hasAnyDescendant(withTestTag(MainScreenTestTags.WALLET_LIST_ITEM))
|
||||
}.child<KNode> {
|
||||
hasTestTag(MainScreenTestTags.WALLET_BALANCE)
|
||||
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.
|
||||
*/
|
||||
fun assertTokenDoesNotExist(tokenTitle: String) {
|
||||
try {
|
||||
tokenWithTitleAndAddress(tokenTitle).assertExists()
|
||||
throw AssertionError("Token with title '$tokenTitle' should not exist but was found")
|
||||
} catch (e: AssertionError) {
|
||||
if (e.message?.contains("No node found") == true) {
|
||||
return
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onMainScreen(function: MainScreenPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.MarketTooltipTestTags
|
||||
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
|
||||
|
||||
class MarketsTooltipPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<MarketsTooltipPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val contentContainer: KNode = child {
|
||||
hasTestTag(MarketTooltipTestTags.CONTAINER)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onMarketsTooltipScreen(function: MarketsTooltipPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
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.PopUpMenuTestTags
|
||||
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
|
||||
|
||||
class PopUpMenuPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<PopUpMenuPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val popUpContainer: KNode = child {
|
||||
hasTestTag(PopUpMenuTestTags.CONTAINER)
|
||||
}
|
||||
|
||||
val hideTokenButton: KNode = child {
|
||||
hasTestTag(PopUpMenuTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.token_details_hide_token))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onPopUpMenu(function: PopUpMenuPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
|
||||
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
|
||||
|
||||
class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TokenDetailsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val screenContainer: KNode = child {
|
||||
hasTestTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTokenDetailsScreen(function: TokenDetailsPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.TokenDetailsTopBarTestTags
|
||||
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
|
||||
|
||||
class TokenDetailsTopBarPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<TokenDetailsTopBarPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val moreButton: KNode = child {
|
||||
hasTestTag(TokenDetailsTopBarTestTags.MORE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val backButton: KNode = child {
|
||||
hasTestTag(TokenDetailsTopBarTestTags.BACK_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTokenDetailsTopBar(function: TokenDetailsTopBarPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
58
app/src/androidTest/kotlin/com/tangem/tests/HideTokenTest.kt
Normal file
58
app/src/androidTest/kotlin/com/tangem/tests/HideTokenTest.kt
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
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 HideTokenTest : BaseTestCase() {
|
||||
|
||||
@AllureId("880")
|
||||
@DisplayName("Hide token in 'Token details' screen by 'Hide' button in topBar menu")
|
||||
@Test
|
||||
fun hideWalletTokenByHideButtonTest() {
|
||||
val tokenTitle = "Polygon"
|
||||
val balance = "<$0.01"
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button" ) {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Token details screen' open") {
|
||||
onTokenDetailsScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Click 'More button'") {
|
||||
onTokenDetailsTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click 'Hide token' button") {
|
||||
onPopUpMenu {
|
||||
popUpContainer.assertIsDisplayed()
|
||||
hideTokenButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Click 'Hide' button in dialog") {
|
||||
onDialog {
|
||||
dialogContainer.assertIsDisplayed()
|
||||
hideButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Assert token: '$tokenTitle' is not displayed") {
|
||||
onMainScreen { assertTokenDoesNotExist(tokenTitle) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -177,6 +177,13 @@ object WalletMockContent : MockContent {
|
|||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc
|
||||
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.compose.runtime.Immutable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -29,6 +30,7 @@ import com.tangem.core.ui.components.fields.SimpleDialogTextField
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.DialogTestTags
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -216,7 +218,8 @@ private fun TangemDialog(
|
|||
shape = TangemTheme.shapes.roundedCornersLarge,
|
||||
color = TangemTheme.colors.background.primary,
|
||||
)
|
||||
.padding(vertical = TangemTheme.dimens.spacing24),
|
||||
.padding(vertical = TangemTheme.dimens.spacing24)
|
||||
.testTag(DialogTestTags.DIALOG_CONTAINER),
|
||||
) {
|
||||
if (title != null) {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
|
@ -24,6 +25,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.components.ResizableText
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.DialogTestTags
|
||||
import com.tangem.core.ui.utils.MultipleClickPreventer
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -47,7 +49,9 @@ fun TangemButton(
|
|||
val multipleClickPreventer = remember { MultipleClickPreventer.get() }
|
||||
|
||||
Button(
|
||||
modifier = modifier.heightIn(min = size.toHeightDp()),
|
||||
modifier = modifier
|
||||
.heightIn(min = size.toHeightDp())
|
||||
.testTag(DialogTestTags.BUTTON),
|
||||
onClick = {
|
||||
multipleClickPreventer.processEvent { if (!showProgress) onClick() }
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.PopUpMenuTestTags
|
||||
|
||||
@Suppress("ComposableEventParameterNaming")
|
||||
@Composable
|
||||
|
|
@ -23,7 +25,8 @@ fun TangemDropdownItem(item: TangemDropdownMenuItem, dismissParent: () -> Unit,
|
|||
dismissParent()
|
||||
item.onClick()
|
||||
}
|
||||
.padding(vertical = TangemTheme.dimens.spacing8, horizontal = TangemTheme.dimens.spacing16),
|
||||
.padding(vertical = TangemTheme.dimens.spacing8, horizontal = TangemTheme.dimens.spacing16)
|
||||
.testTag(PopUpMenuTestTags.BUTTON),
|
||||
text = item.title.resolveReference(),
|
||||
style = TangemTheme.typography.button.copy(color = item.textColorProvider()),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,11 +17,13 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.*
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.compose.ui.window.PopupPositionProvider
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.PopUpMenuTestTags
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
|
|
@ -138,7 +140,8 @@ private fun DropdownMenuContent(
|
|||
Column(
|
||||
modifier = modifier
|
||||
.width(IntrinsicSize.Max)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
.verticalScroll(rememberScrollState())
|
||||
.testTag(PopUpMenuTestTags.CONTAINER),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.layout.Measurable
|
|||
import androidx.compose.ui.layout.Placeable
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
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
|
||||
|
|
@ -30,6 +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 kotlinx.collections.immutable.persistentListOf
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import java.util.UUID
|
||||
|
|
@ -100,7 +102,8 @@ fun TokenItem(
|
|||
state = state.iconState,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.ICON)
|
||||
.padding(end = TangemTheme.dimens.spacing8),
|
||||
.padding(end = TangemTheme.dimens.spacing8)
|
||||
.testTag(MainScreenTestTags.TOKEN_ICON),
|
||||
)
|
||||
|
||||
TokenTitle(
|
||||
|
|
@ -108,7 +111,8 @@ fun TokenItem(
|
|||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.TITLE)
|
||||
.padding(end = TangemTheme.dimens.spacing8)
|
||||
.padding(bottom = betweenRowsMargin),
|
||||
.padding(bottom = betweenRowsMargin)
|
||||
.testTag(MainScreenTestTags.TOKEN_TITLE),
|
||||
)
|
||||
|
||||
TokenFiatAmount(
|
||||
|
|
@ -116,26 +120,32 @@ fun TokenItem(
|
|||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.FIAT_AMOUNT)
|
||||
.padding(bottom = betweenRowsMargin),
|
||||
.padding(bottom = betweenRowsMargin)
|
||||
.testTag(MainScreenTestTags.TOKEN_FIAT_AMOUNT),
|
||||
)
|
||||
|
||||
TokenPrice(
|
||||
state = state.subtitleState,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.CRYPTO_PRICE)
|
||||
.padding(end = TangemTheme.dimens.spacing8),
|
||||
.padding(end = TangemTheme.dimens.spacing8)
|
||||
.testTag(MainScreenTestTags.TOKEN_PRICE),
|
||||
)
|
||||
|
||||
TokenCryptoAmount(
|
||||
state = state.subtitle2State,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier.layoutId(layoutId = LayoutId.CRYPTO_AMOUNT),
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.CRYPTO_AMOUNT)
|
||||
.testTag(MainScreenTestTags.TOKEN_CRYPTO_AMOUNT),
|
||||
)
|
||||
|
||||
NonFiatContentBlock(
|
||||
state = state,
|
||||
reorderableTokenListState = reorderableTokenListState,
|
||||
modifier = Modifier.layoutId(layoutId = LayoutId.NON_FIAT_CONTENT),
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.NON_FIAT_CONTENT)
|
||||
.testTag(MainScreenTestTags.TOKEN_NO_ADDRESS),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object DialogTestTags {
|
||||
const val DIALOG_CONTAINER = "DIALOG_CONTAINER"
|
||||
const val BUTTON = "DIALOG_BUTTON"
|
||||
}
|
||||
|
|
@ -4,4 +4,13 @@ object MainScreenTestTags {
|
|||
const val SCREEN_CONTAINER = "MAIN_SCREEN_CONTAINER"
|
||||
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"
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object MarketTooltipTestTags {
|
||||
const val CONTAINER = "MARKETS_TOOLTIP_CONTAINER"
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object PopUpMenuTestTags {
|
||||
const val CONTAINER = "POP_UP_MENU_CONTAINER"
|
||||
const val BUTTON = "POP_UP_MENU_BUTTON"
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object TokenDetailsScreenTestTags {
|
||||
const val SCREEN_CONTAINER = "TOKEN_DETAILS_SCREEN_CONTAINER"
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object TokenDetailsTopBarTestTags {
|
||||
const val MORE_BUTTON = "TOKEN_DETAILS_TOP_BAR_MORE_BUTTON"
|
||||
const val BACK_BUTTON = "TOKEN_DETAILS_TOP_BAR_BACK_BUTTON"
|
||||
}
|
||||
29
core/ui/src/main/java/com/tangem/core/ui/utils/LazyList.kt
Normal file
29
core/ui/src/main/java/com/tangem/core/ui/utils/LazyList.kt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import androidx.compose.ui.semantics.SemanticsPropertyKey
|
||||
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
|
||||
|
||||
/**
|
||||
* Semantics key for storing and retrieving the visual position
|
||||
* of an item within a LazyList. Used to verify item ordering
|
||||
* and visibility in UI tests.
|
||||
*/
|
||||
val LazyListItemPositionSemantics = SemanticsPropertyKey<Int>("LazyListItemPosition")
|
||||
|
||||
/**
|
||||
* Provides access to the item position semantics property
|
||||
* through semantics property receiver DSL.
|
||||
*/
|
||||
var SemanticsPropertyReceiver.lazyListItemPosition by LazyListItemPositionSemantics
|
||||
|
||||
/**
|
||||
* Semantics key for storing the total length/count of items
|
||||
* in a LazyList. Enables validation of list completeness in tests.
|
||||
*/
|
||||
val LazyListLengthSemantics = SemanticsPropertyKey<Int>("LazyListLength")
|
||||
|
||||
/**
|
||||
* Provides access to the list length semantics property
|
||||
* through semantics property receiver DSL.
|
||||
*/
|
||||
var SemanticsPropertyReceiver.lazyListLength by LazyListLengthSemantics
|
||||
|
|
@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
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
|
||||
|
|
@ -29,6 +30,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
|||
import com.tangem.core.ui.components.transactions.txHistoryItems
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
|
|
@ -79,7 +81,9 @@ internal fun TokenDetailsScreen(
|
|||
modifier = Modifier.padding(scaffoldPaddings),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.testTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(
|
||||
bottom = TangemTheme.dimens.spacing16 + bottomBarHeight,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
|
|
@ -17,6 +18,7 @@ import com.tangem.core.ui.components.dropdownmenu.TangemDropdownItem
|
|||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenu
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TokenDetailsTopBarTestTags
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarConfig
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
|
|
@ -32,6 +34,7 @@ internal fun TokenDetailsTopAppBar(config: TokenDetailsTopAppBarConfig) {
|
|||
painter = painterResource(id = R.drawable.ic_back_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = "Back",
|
||||
modifier = Modifier.testTag(TokenDetailsTopBarTestTags.BACK_BUTTON),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -46,6 +49,7 @@ internal fun TokenDetailsTopAppBar(config: TokenDetailsTopAppBarConfig) {
|
|||
painter = painterResource(id = R.drawable.ic_more_vertical_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = "More",
|
||||
modifier = Modifier.testTag(TokenDetailsTopBarTestTags.MORE_BUTTON),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ import com.tangem.core.ui.res.LocalWindowSize
|
|||
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.MarketTooltipTestTags
|
||||
import com.tangem.core.ui.utils.lineTo
|
||||
import com.tangem.core.ui.utils.moveTo
|
||||
import com.tangem.core.ui.utils.toPx
|
||||
|
|
@ -490,7 +491,9 @@ private fun MarketsTooltip(
|
|||
|
||||
val slideOffset = 40.dp.toPx()
|
||||
AnimatedVisibility(
|
||||
modifier = modifier.offset { IntOffset(x = 0, y = tooltipOffset.roundToPx()) },
|
||||
modifier = modifier
|
||||
.offset { IntOffset(x = 0, y = tooltipOffset.roundToPx()) }
|
||||
.testTag(MarketTooltipTestTags.CONTAINER),
|
||||
visible = visible,
|
||||
enter = slideIn(
|
||||
animationSpec = spring(
|
||||
|
|
|
|||
|
|
@ -20,11 +20,13 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.WalletCard
|
||||
|
|
@ -63,7 +65,9 @@ internal fun WalletsList(
|
|||
WalletCard(
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier.width(itemWidth),
|
||||
modifier = Modifier
|
||||
.width(itemWidth)
|
||||
.testTag(MainScreenTestTags.WALLET_LIST_ITEM),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import androidx.compose.ui.layout.ContentScale
|
|||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
|
@ -48,6 +49,7 @@ import com.tangem.core.ui.extensions.resolveReference
|
|||
import com.tangem.core.ui.res.TangemDimens
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDropDownItems
|
||||
|
|
@ -278,7 +280,9 @@ private fun Balance(state: WalletCardState, isBalanceHidden: Boolean, modifier:
|
|||
when (state) {
|
||||
is WalletCardState.Content -> {
|
||||
ResizableText(
|
||||
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32),
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size32)
|
||||
.testTag(MainScreenTestTags.WALLET_BALANCE),
|
||||
text = balance,
|
||||
fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
|
|||
|
|
@ -10,13 +10,17 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.Text
|
||||
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.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.tangem.core.ui.components.tokenlist.TokenListItem
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import com.tangem.core.ui.utils.lazyListItemPosition
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -61,11 +65,14 @@ private fun LazyListScope.contentItems(
|
|||
TokenListItem(
|
||||
state = item,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = items.lastIndex,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
),
|
||||
modifier = modifier
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = items.lastIndex,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
.semantics { lazyListItemPosition = index },
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue