diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt index a446ce4b30..010526dbd1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt @@ -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, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt index 8e3ebc996c..e7ee711544 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt @@ -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() } \ No newline at end of file diff --git a/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt new file mode 100644 index 0000000000..172497b821 --- /dev/null +++ b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt @@ -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(relaxed = true) { + every { isSingleCurrency() } returns isSingleCurrency + every { isSingleWalletWithToken() } returns isSingleWalletWithToken + } + val scanResponse = mockk() + every { scanResponse.cardTypesResolver } returns resolver + return mockk(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), + ) +} \ No newline at end of file