Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-03 16:57:00 +05:00
parent 49aede77dc
commit 238e955fdd
3 changed files with 180 additions and 2 deletions

View file

@ -24,7 +24,7 @@ import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.TokensListItemUM2
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListUM
import com.tangem.feature.wallet.presentation.wallet.state.utils.isSingleWalletWithToken
import com.tangem.feature.wallet.presentation.wallet.state.utils.isSingleCurrency
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
@ -171,7 +171,7 @@ internal class WalletTokensListUMConverter(
} else {
R.drawable.ic_filter_24
}
return if (accountList.flattenCurrencies().isNotEmpty() && !selectedWallet.isSingleWalletWithToken()) {
return if (accountList.flattenCurrencies().isNotEmpty() && !selectedWallet.isSingleCurrency()) {
TangemButtonUM(
text = resourceReference(textRes),
isEnabled = accountList.totalFiatBalance !is TotalFiatBalance.Loading,

View file

@ -25,4 +25,8 @@ internal fun UserWallet.isSingleWallet(): Boolean {
internal fun UserWallet.isSingleWalletWithToken(): Boolean {
return this is UserWallet.Cold && scanResponse.cardTypesResolver.isSingleWalletWithToken()
}
internal fun UserWallet.isSingleCurrency(): Boolean {
return this is UserWallet.Cold && scanResponse.cardTypesResolver.isSingleCurrency()
}

View file

@ -0,0 +1,174 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.card.CardTypesResolver
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.core.utils.lceError
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TokensGroupType
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.Account.CryptoPortfolio.Companion.createMainAccount
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListUM
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import java.math.BigDecimal
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class WalletTokensListUMConverterTest {
private val userWalletId = UserWalletId("00")
@AfterEach
fun tearDown() {
unmockkStatic(ScanResponse::cardTypesResolver)
}
@ParameterizedTest
@MethodSource("provideOrganizeButtonModels")
fun `GIVEN wallet type WHEN convert THEN Add and Manage button visibility matches`(model: OrganizeButtonModel) {
// Arrange
val converter = createConverter(
selectedWallet = mockColdWallet(
isSingleCurrency = model.isSingleCurrency,
isSingleWalletWithToken = model.isSingleWalletWithToken,
),
)
// Act
val result = converter.convert(value = nonEmptyAccountList()) as WalletTokensListUM.Content
// Assert
assertThat(result.organizeButtonUM != null).isEqualTo(model.expectedButtonShown)
}
private fun createConverter(selectedWallet: UserWallet): WalletTokensListUMConverter = WalletTokensListUMConverter(
appCurrency = AppCurrency.Default,
selectedWallet = selectedWallet,
clickIntents = mockk(relaxed = true),
yieldModuleApyMap = emptyMap(),
isAccountsModeEnabled = false,
expandedAccounts = emptySet(),
stakingAvailabilityMap = emptyMap(),
isAddAndManageTokensEnabled = true,
shouldShowMainPromo = false,
)
private fun mockColdWallet(isSingleCurrency: Boolean, isSingleWalletWithToken: Boolean): UserWallet.Cold {
mockkStatic(ScanResponse::cardTypesResolver)
val resolver = mockk<CardTypesResolver>(relaxed = true) {
every { isSingleCurrency() } returns isSingleCurrency
every { isSingleWalletWithToken() } returns isSingleWalletWithToken
}
val scanResponse = mockk<ScanResponse>()
every { scanResponse.cardTypesResolver } returns resolver
return mockk<UserWallet.Cold>(relaxed = true) {
every { this@mockk.scanResponse } returns scanResponse
}
}
private fun nonEmptyAccountList(): AccountStatusList {
val tokenList = TokenList.Ungrouped(
currencies = listOf(createLoadedStatus(createToken())),
totalFiatBalance = TotalFiatBalance.Loaded(BigDecimal.ZERO, StatusSource.ACTUAL),
sortedBy = TokensSortType.NONE,
)
return AccountStatusList(
userWalletId = userWalletId,
accountStatuses = listOf(
AccountStatus.CryptoPortfolio(
account = createMainAccount(userWalletId),
tokenList = tokenList,
priceChangeLce = Unit.lceError(),
),
),
totalAccounts = 1,
totalArchivedAccounts = 0,
totalFiatBalance = TotalFiatBalance.Loaded(BigDecimal.ZERO, StatusSource.ACTUAL),
sortType = TokensSortType.NONE,
groupType = TokensGroupType.NONE,
)
}
private fun createToken(): CryptoCurrency.Token {
val network = Network(
id = Network.ID(value = "ethereum", derivationPath = Network.DerivationPath.None),
name = "ethereum",
currencySymbol = "ETH",
derivationPath = Network.DerivationPath.None,
isTestnet = false,
standardType = Network.StandardType.ERC20,
hasFiatFeeRate = false,
canHandleTokens = true,
transactionExtrasType = Network.TransactionExtrasType.NONE,
nameResolvingType = Network.NameResolvingType.NONE,
)
return CryptoCurrency.Token(
id = CryptoCurrency.ID(
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
body = CryptoCurrency.ID.Body.NetworkId(rawId = "ethereum"),
suffix = CryptoCurrency.ID.Suffix.ContractAddress(contractAddress = "0xABCDEF"),
),
network = network,
name = "Token",
symbol = "TKN",
decimals = 18,
iconUrl = null,
isCustom = false,
contractAddress = "0xABCDEF",
)
}
private fun createLoadedStatus(token: CryptoCurrency.Token): CryptoCurrencyStatus {
val value = CryptoCurrencyStatus.Loaded(
amount = BigDecimal.ONE,
fiatAmount = BigDecimal.ZERO,
fiatRate = BigDecimal.ONE,
priceChange = BigDecimal.ZERO,
stakingBalance = null,
yieldSupplyStatus = null,
hasCurrentNetworkTransactions = false,
pendingTransactions = emptySet(),
networkAddress = NetworkAddress.Single(
defaultAddress = NetworkAddress.Address(
value = "addr",
type = NetworkAddress.Address.Type.Primary,
),
),
sources = CryptoCurrencyStatus.Sources(),
)
return CryptoCurrencyStatus(currency = token, value = value)
}
internal data class OrganizeButtonModel(
val isSingleCurrency: Boolean,
val isSingleWalletWithToken: Boolean,
val expectedButtonShown: Boolean,
)
private fun provideOrganizeButtonModels() = listOf(
// Multi-currency wallet (Wallet / Wallet2) — Add & Manage is shown.
OrganizeButtonModel(isSingleCurrency = false, isSingleWalletWithToken = false, expectedButtonShown = true),
// Single-currency wallet without token (e.g. S2C) — Add & Manage must be hidden ([REDACTED_TASK_KEY]).
OrganizeButtonModel(isSingleCurrency = true, isSingleWalletWithToken = false, expectedButtonShown = false),
// Single-currency wallet with token (e.g. NODL) — Add & Manage stays hidden.
OrganizeButtonModel(isSingleCurrency = true, isSingleWalletWithToken = true, expectedButtonShown = false),
)
}