Updated on 2026-08-14
This commit is contained in:
commit
9e49e67cf3
8 changed files with 201 additions and 95 deletions
|
|
@ -55,6 +55,7 @@ sealed interface SwapState {
|
|||
val isFeeCoverage: Boolean,
|
||||
val sendingAmount: BigDecimal,
|
||||
val tronFeeNotificationShowCount: Int,
|
||||
val isAmountSubtractAvailable: Boolean,
|
||||
val isSendingAmountLoading: Boolean = false,
|
||||
val currencyCheck: CryptoCurrencyCheck? = null,
|
||||
val validationResult: Throwable? = null,
|
||||
|
|
|
|||
|
|
@ -110,9 +110,14 @@ class SwapTransferInteractorImpl @Inject constructor(
|
|||
fee = warningsFee,
|
||||
feeCurrencyBalanceAfterTransaction = null,
|
||||
)
|
||||
val isAmountSubtractAvailable = isAmountSubtractAvailable(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = fromTokenInfo.swapCurrencyStatus.currency,
|
||||
fee = fee,
|
||||
)
|
||||
val coverageState = getCoverageState(
|
||||
fromTokenInfo = fromTokenInfo,
|
||||
userWallet = userWallet,
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||
fee = fee,
|
||||
currencyCheck = currencyCheck,
|
||||
)
|
||||
|
|
@ -137,6 +142,7 @@ class SwapTransferInteractorImpl @Inject constructor(
|
|||
isFeeCoverage = coverageState.isFeeCoverage,
|
||||
sendingAmount = coverageState.sendingAmount,
|
||||
tronFeeNotificationShowCount = tronFeeNotificationShowCount,
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||
isSendingAmountLoading = coverageState.isSendingAmountLoading,
|
||||
currencyCheck = currencyCheck,
|
||||
)
|
||||
|
|
@ -156,18 +162,13 @@ class SwapTransferInteractorImpl @Inject constructor(
|
|||
).getOrNull()
|
||||
}
|
||||
|
||||
private suspend fun getCoverageState(
|
||||
private fun getCoverageState(
|
||||
fromTokenInfo: TokenSwapInfo,
|
||||
userWallet: UserWallet,
|
||||
isAmountSubtractAvailable: Boolean,
|
||||
fee: Fee?,
|
||||
currencyCheck: CryptoCurrencyCheck,
|
||||
): CoverageState {
|
||||
val swapCurrencyStatus = fromTokenInfo.swapCurrencyStatus
|
||||
val isAmountSubtractAvailable = isAmountSubtractAvailable(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = swapCurrencyStatus.currency,
|
||||
fee = fee,
|
||||
)
|
||||
val balance = swapCurrencyStatus.status.value.amount ?: BigDecimal.ZERO
|
||||
val reduceAmountBy = currencyCheck.existentialDeposit.orZero()
|
||||
val amount = fromTokenInfo.tokenAmount
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ internal class SwapTransferInteractorImplTest {
|
|||
isFeeCoverage = false,
|
||||
sendingAmount = expectedAmount,
|
||||
tronFeeNotificationShowCount = 0,
|
||||
isAmountSubtractAvailable = false,
|
||||
currencyCheck = currencyCheck,
|
||||
)
|
||||
assertThat(result).isEqualTo(expected)
|
||||
|
|
@ -259,6 +260,7 @@ internal class SwapTransferInteractorImplTest {
|
|||
isFeeCoverage = false,
|
||||
sendingAmount = expectedAmount,
|
||||
tronFeeNotificationShowCount = 0,
|
||||
isAmountSubtractAvailable = false,
|
||||
currencyCheck = currencyCheck,
|
||||
)
|
||||
assertThat(result).isEqualTo(expected)
|
||||
|
|
|
|||
|
|
@ -822,8 +822,7 @@ internal class SwapModel @Inject constructor(
|
|||
transferState = swapState,
|
||||
uiStateHolder = uiState,
|
||||
feePaidCryptoCurrencyStatus = feePaidCryptoCurrency,
|
||||
fee = selectedFee,
|
||||
feeError = feeSelectorRepository.state.value as? FeeSelectorUM.Error,
|
||||
feeSelectorUM = feeSelectorRepository.state.value,
|
||||
)
|
||||
when {
|
||||
uiState.successState != null -> Unit
|
||||
|
|
@ -871,7 +870,7 @@ internal class SwapModel @Inject constructor(
|
|||
feePaidCryptoCurrencyStatus = feePaidCryptoCurrencyStatus ?: dataState.feePaidCryptoCurrency,
|
||||
fee = fee,
|
||||
isTangemPayWithdrawal = isTangemPayWithdrawal(),
|
||||
feeError = feeSelectorRepository.state.value as? FeeSelectorUM.Error,
|
||||
feeSelectorUM = feeSelectorRepository.state.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.feature.swap.ui.transfer
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExceedBalanceNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExceedsBalanceNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExistentialWarningNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addFeeCoverageNotification
|
||||
|
|
@ -18,6 +18,8 @@ import com.tangem.feature.swap.domain.models.SwapAmount
|
|||
import com.tangem.feature.swap.domain.models.ui.SwapState
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils.getTezosThreshold
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTezos
|
||||
|
|
@ -33,22 +35,30 @@ internal class SwapTransferNotificationsFactory @Inject constructor() {
|
|||
@Suppress("LongParameterList")
|
||||
fun getNotifications(
|
||||
transferState: SwapState.Transfer,
|
||||
feeSelectorUM: FeeSelectorUM?,
|
||||
feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
fee: Fee?,
|
||||
actions: UiActions,
|
||||
getFeeError: GetFeeError?,
|
||||
): ImmutableList<NotificationUM> {
|
||||
// The fee selector exposes a single sealed state; narrow it here so call sites pass the raw
|
||||
// FeeSelectorUM and this factory owns the Content/Error/Loading discrimination.
|
||||
val feeContent = feeSelectorUM
|
||||
val getFeeError = (feeSelectorUM as? FeeSelectorUM.Error)?.error
|
||||
return buildList {
|
||||
maybeAddRentExemptionError(transferState)
|
||||
maybeAddDomainWarnings(
|
||||
state = transferState,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = fee,
|
||||
feeSelectorUM = feeContent,
|
||||
onReduceByAmount = actions.onReduceByAmount,
|
||||
onReduceToAmount = actions.onReduceToAmount,
|
||||
)
|
||||
maybeAddNeedReserveToCreateAccountWarning(transferState)
|
||||
maybeAddExceedsBalanceNotification(transferState, onBuyClick = actions.openTokenDetailsScreen)
|
||||
maybeAddExceedsBalanceNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = feeContent,
|
||||
onBuyClick = actions.openTokenDetailsScreen,
|
||||
)
|
||||
maybeAddTooHighOrTooLowNotification(feeContent)
|
||||
addTronNetworkFeesNotification(
|
||||
cryptoCurrencyStatus = transferState.fromTokenInfo.swapCurrencyStatus.status,
|
||||
transferState = transferState,
|
||||
|
|
@ -71,13 +81,14 @@ internal class SwapTransferNotificationsFactory @Inject constructor() {
|
|||
private fun MutableList<NotificationUM>.maybeAddDomainWarnings(
|
||||
state: SwapState.Transfer,
|
||||
feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
fee: Fee?,
|
||||
feeSelectorUM: FeeSelectorUM?,
|
||||
onReduceByAmount: (SwapAmount, BigDecimal) -> Unit,
|
||||
onReduceToAmount: (SwapAmount) -> Unit,
|
||||
) {
|
||||
val swapCurrencyStatus = state.fromTokenInfo.swapCurrencyStatus
|
||||
val amount = state.fromTokenInfo.tokenAmount
|
||||
val balance = swapCurrencyStatus.status.value.amount ?: BigDecimal.ZERO
|
||||
val fee = (feeSelectorUM as? FeeSelectorUM.Content)?.selectedFeeItem?.fee
|
||||
val feeValue = fee?.amount?.value.orZero()
|
||||
val isCardano = BlockchainUtils.isCardano(swapCurrencyStatus.currency.network.rawId)
|
||||
addExistentialWarningNotification(
|
||||
|
|
@ -191,8 +202,9 @@ internal class SwapTransferNotificationsFactory @Inject constructor() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddExceedsBalanceNotification(
|
||||
private fun MutableList<NotificationUM>.maybeAddExceedsBalanceNotifications(
|
||||
transferState: SwapState.Transfer,
|
||||
feeSelectorUM: FeeSelectorUM?,
|
||||
onBuyClick: (CryptoCurrency) -> Unit,
|
||||
) {
|
||||
val cryptoCurrencyStatus = transferState.fromTokenInfo.swapCurrencyStatus.status
|
||||
|
|
@ -206,6 +218,27 @@ internal class SwapTransferNotificationsFactory @Inject constructor() {
|
|||
onAnalyticsEvent = {},
|
||||
onResetAnalyticsEvent = {},
|
||||
)
|
||||
val feeAmount = (feeSelectorUM as? FeeSelectorUM.Content)?.selectedFeeItem?.fee?.amount?.value
|
||||
if (feeAmount != null) {
|
||||
addExceedBalanceNotification(
|
||||
feeAmount = feeAmount,
|
||||
sendingAmount = transferState.sendingAmount,
|
||||
isSubtractionAvailable = transferState.isAmountSubtractAvailable,
|
||||
cryptoCurrencyStatus = transferState.fromTokenInfo.swapCurrencyStatus.status,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CanBeNonNullable")
|
||||
private fun MutableList<NotificationUM>.maybeAddTooHighOrTooLowNotification(feeSelectorUM: FeeSelectorUM?) {
|
||||
val content = feeSelectorUM as? FeeSelectorUM.Content ?: return
|
||||
val (isFeeTooHigh, diff) = FeeCalculationUtils.checkIfCustomFeeTooHigh(feeSelectorUM = content)
|
||||
if (isFeeTooHigh) {
|
||||
add(NotificationUM.Warning.TooHigh(diff))
|
||||
}
|
||||
if (FeeCalculationUtils.checkIfCustomFeeTooLow(feeSelectorUM = content)) {
|
||||
add(NotificationUM.Warning.FeeTooLow)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addTronNetworkFeesNotification(
|
||||
|
|
|
|||
|
|
@ -59,8 +59,7 @@ internal class SwapTransferStateBuilder @Inject constructor(
|
|||
transferState: SwapState.Transfer,
|
||||
uiStateHolder: SwapStateHolder,
|
||||
feePaidCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
fee: Fee?,
|
||||
feeError: FeeSelectorUM.Error?,
|
||||
feeSelectorUM: FeeSelectorUM?,
|
||||
): SwapStateHolder {
|
||||
val fromTokenSwapInfo = transferState.fromTokenInfo
|
||||
val isInsufficientBalance = transferState.isInsufficientBalance
|
||||
|
|
@ -68,10 +67,9 @@ internal class SwapTransferStateBuilder @Inject constructor(
|
|||
val prevAmountField = prevSendCard?.amountField
|
||||
val notifications = notificationsFactory.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
feeCryptoCurrencyStatus = feePaidCryptoCurrencyStatus,
|
||||
fee = fee,
|
||||
actions = actions,
|
||||
getFeeError = feeError?.error,
|
||||
)
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = createSendSwapCardState(
|
||||
|
|
@ -344,14 +342,13 @@ internal class SwapTransferStateBuilder @Inject constructor(
|
|||
feePaidCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
fee: Fee?,
|
||||
isTangemPayWithdrawal: Boolean,
|
||||
feeError: FeeSelectorUM.Error?,
|
||||
feeSelectorUM: FeeSelectorUM?,
|
||||
): SwapStateHolder {
|
||||
val notifications = notificationsFactory.getNotifications(
|
||||
transferState = transferState,
|
||||
feeCryptoCurrencyStatus = feePaidCryptoCurrencyStatus,
|
||||
fee = fee,
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
actions = actions,
|
||||
getFeeError = feeError?.error,
|
||||
)
|
||||
return uiStateHolder.copy(
|
||||
notifications = notifications,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.swap.ui.transfer
|
|||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.Account
|
||||
|
|
@ -18,8 +19,12 @@ import com.tangem.feature.swap.domain.models.ui.SwapState
|
|||
import com.tangem.feature.swap.domain.models.ui.TokenSwapInfo
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.features.send.api.entity.CustomFeeFieldUM
|
||||
import com.tangem.features.send.api.entity.FeeItem
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
|
@ -43,10 +48,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result).isEmpty()
|
||||
|
|
@ -65,10 +69,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Solana.RentInfo>()).hasSize(1)
|
||||
|
|
@ -85,16 +88,13 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
),
|
||||
currencyCheck = buildCurrencyCheck(existentialDeposit = BigDecimal("0.5")),
|
||||
)
|
||||
val fee: Fee = mockk(relaxed = true) {
|
||||
every { amount.value } returns BigDecimal("0.4")
|
||||
}
|
||||
val feeSelectorUM = contentWithFee(feeValue = BigDecimal("0.4"))
|
||||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = fee,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Error.ExistentialDeposit>()).hasSize(1)
|
||||
|
|
@ -113,10 +113,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Error.MinimumAmountError>()).hasSize(1)
|
||||
|
|
@ -131,10 +130,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Cardano.MinAdaValueCharged>()).hasSize(1)
|
||||
|
|
@ -154,10 +152,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Warning.FeeCoverageNotification>()).hasSize(1)
|
||||
|
|
@ -176,10 +173,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
val reserve = result.filterIsInstance<SwapNotificationUM.Warning.NeedReserveToCreateAccount>()
|
||||
|
|
@ -199,10 +195,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<SwapNotificationUM.Warning.ReduceAmount>()).hasSize(1)
|
||||
|
|
@ -220,10 +215,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Error.TokenExceedsBalance>()).hasSize(1)
|
||||
|
|
@ -242,10 +236,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<SwapNotificationUM.Info.TronTokenFee>()).hasSize(1)
|
||||
|
|
@ -264,10 +257,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<SwapNotificationUM.Info.TronTokenFee>()).isEmpty()
|
||||
|
|
@ -285,10 +277,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<SwapNotificationUM.Info.TronTokenFee>()).isEmpty()
|
||||
|
|
@ -302,10 +293,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = errorSelector(GetFeeError.UnknownError),
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = GetFeeError.UnknownError,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Warning.NetworkFeeUnreachable>()).hasSize(1)
|
||||
|
|
@ -319,10 +309,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = errorSelector(GetFeeError.BlockchainErrors.TronActivationError),
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = GetFeeError.BlockchainErrors.TronActivationError,
|
||||
)
|
||||
|
||||
val notifications = result.filterIsInstance<NotificationUM.Warning.TronAccountNotActivated>()
|
||||
|
|
@ -337,10 +326,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = errorSelector(GetFeeError.UnknownError),
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = GetFeeError.UnknownError,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Warning.NetworkFeeUnreachable>()).isEmpty()
|
||||
|
|
@ -353,15 +341,94 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
feeCryptoCurrencyStatus = buildCoinStatus().status,
|
||||
fee = null,
|
||||
actions = actions,
|
||||
getFeeError = null,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Warning.NetworkFeeUnreachable>()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN custom fee below network minimum WHEN getNotifications THEN FeeTooLow is added`() = runTest {
|
||||
val transferState = buildTransferState()
|
||||
val feeSelectorUM = contentWithCustomFeeBelowMinimum(
|
||||
customFeeValue = "0.0001",
|
||||
minimumFeeValue = BigDecimal("0.001"),
|
||||
)
|
||||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
actions = actions,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Warning.FeeTooLow>()).hasSize(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN custom fee at network minimum WHEN getNotifications THEN no FeeTooLow`() = runTest {
|
||||
val transferState = buildTransferState()
|
||||
val feeSelectorUM = contentWithCustomFeeBelowMinimum(
|
||||
customFeeValue = "0.001",
|
||||
minimumFeeValue = BigDecimal("0.001"),
|
||||
)
|
||||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = feeSelectorUM,
|
||||
feeCryptoCurrencyStatus = null,
|
||||
actions = actions,
|
||||
)
|
||||
|
||||
assertThat(result.filterIsInstance<NotificationUM.Warning.FeeTooLow>()).isEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a [FeeSelectorUM.Content] with a Custom fee whose [customFeeValue] is below the choosable
|
||||
* [minimumFeeValue], so
|
||||
* [com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooLow]
|
||||
* reports the fee as too low. The choosable `priority` is left unstubbed (null) so the sibling
|
||||
* `checkIfCustomFeeTooHigh` short-circuits and does not add a spurious TooHigh notification.
|
||||
*/
|
||||
private fun contentWithCustomFeeBelowMinimum(
|
||||
customFeeValue: String,
|
||||
minimumFeeValue: BigDecimal,
|
||||
decimals: Int = 8,
|
||||
): FeeSelectorUM.Content {
|
||||
val customField: CustomFeeFieldUM = mockk(relaxed = true) {
|
||||
every { value } returns customFeeValue
|
||||
every { this@mockk.decimals } returns decimals
|
||||
}
|
||||
val customFeeItem: FeeItem.Custom = mockk(relaxed = true) {
|
||||
every { customValues } returns persistentListOf(customField)
|
||||
}
|
||||
val choosableFees: TransactionFee.Choosable = mockk(relaxed = true) {
|
||||
every { minimum.amount.value } returns minimumFeeValue
|
||||
}
|
||||
return mockk(relaxed = true) {
|
||||
every { selectedFeeItem } returns customFeeItem
|
||||
every { fees } returns choosableFees
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a [FeeSelectorUM.Content] whose selected fee carries [feeValue]. A non-Custom fee item is used so
|
||||
* [com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooHigh]
|
||||
* short-circuits and does not add a spurious TooHigh notification.
|
||||
*/
|
||||
private fun contentWithFee(feeValue: BigDecimal): FeeSelectorUM.Content {
|
||||
val fee: Fee = mockk(relaxed = true) {
|
||||
every { amount.value } returns feeValue
|
||||
}
|
||||
return mockk(relaxed = true) {
|
||||
every { selectedFeeItem } returns FeeItem.Market(fee = fee)
|
||||
}
|
||||
}
|
||||
|
||||
private fun errorSelector(error: GetFeeError): FeeSelectorUM.Error = FeeSelectorUM.Error(error = error)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun buildTransferState(
|
||||
fromTokenInfo: TokenSwapInfo = buildTokenInfo(buildCoinStatus()),
|
||||
|
|
@ -373,6 +440,7 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
isFeeCoverage: Boolean = false,
|
||||
sendingAmount: BigDecimal = fromTokenInfo.tokenAmount.value,
|
||||
tronFeeNotificationShowCount: Int = 0,
|
||||
isAmountSubtractAvailable: Boolean = false,
|
||||
): SwapState.Transfer = SwapState.Transfer(
|
||||
userWallet = coldWallet,
|
||||
fromTokenInfo = fromTokenInfo,
|
||||
|
|
@ -385,6 +453,7 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
isFeeCoverage = isFeeCoverage,
|
||||
sendingAmount = sendingAmount,
|
||||
tronFeeNotificationShowCount = tronFeeNotificationShowCount,
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||
currencyCheck = currencyCheck,
|
||||
validationResult = validationResult,
|
||||
minAdaValue = minAdaValue,
|
||||
|
|
|
|||
|
|
@ -39,12 +39,14 @@ import com.tangem.feature.swap.model.SwapProcessDataState
|
|||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -57,10 +59,9 @@ internal class SwapTransferStateBuilderTest {
|
|||
coEvery {
|
||||
getNotifications(
|
||||
transferState = any(),
|
||||
feeSelectorUM = any(),
|
||||
feeCryptoCurrencyStatus = any(),
|
||||
fee = any(),
|
||||
actions = any(),
|
||||
getFeeError = any(),
|
||||
)
|
||||
} returns persistentListOf()
|
||||
}
|
||||
|
|
@ -72,6 +73,21 @@ internal class SwapTransferStateBuilderTest {
|
|||
isFeeApproximateUseCase = isFeeApproximateUseCase,
|
||||
)
|
||||
|
||||
// PER_CLASS reuses the notificationsFactory mock across tests, so clear its recorded calls (and re-stub)
|
||||
// before each test to keep coVerify(exactly = 1) scoped to the current test.
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(notificationsFactory)
|
||||
coEvery {
|
||||
notificationsFactory.getNotifications(
|
||||
transferState = any(),
|
||||
feeSelectorUM = any(),
|
||||
feeCryptoCurrencyStatus = any(),
|
||||
actions = any(),
|
||||
)
|
||||
} returns persistentListOf()
|
||||
}
|
||||
|
||||
private val userWalletId = UserWalletId(stringValue = "deadbeef")
|
||||
private val coldWallet: UserWallet.Cold = mockk(relaxed = true) {
|
||||
every { walletId } returns userWalletId
|
||||
|
|
@ -123,8 +139,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
transferState = transferState,
|
||||
uiStateHolder = uiState,
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
val portfolioAccount = fromCurrencyStatus.account as Account.CryptoPortfolio
|
||||
|
|
@ -154,10 +169,9 @@ internal class SwapTransferStateBuilderTest {
|
|||
coVerify(exactly = 1) {
|
||||
notificationsFactory.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = any(),
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = any(),
|
||||
getFeeError = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -177,8 +191,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
transferState = transferState,
|
||||
uiStateHolder = uiState,
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
val sendType = (result.sendCardData as SwapCardState.SwapCardData).type as TransactionCardType.Inputtable
|
||||
|
|
@ -197,10 +210,9 @@ internal class SwapTransferStateBuilderTest {
|
|||
coVerify(exactly = 1) {
|
||||
notificationsFactory.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = any(),
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = any(),
|
||||
getFeeError = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -221,8 +233,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
transferState = transferState,
|
||||
uiStateHolder = uiState,
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
val sendType = (result.sendCardData as SwapCardState.SwapCardData).type as TransactionCardType.Inputtable
|
||||
|
|
@ -241,10 +252,9 @@ internal class SwapTransferStateBuilderTest {
|
|||
coVerify(exactly = 1) {
|
||||
notificationsFactory.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = any(),
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = any(),
|
||||
getFeeError = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -265,8 +275,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
transferState = transferState,
|
||||
uiStateHolder = uiState,
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
val portfolioAccount = toCurrencyStatus.account as Account.CryptoPortfolio
|
||||
|
|
@ -291,10 +300,9 @@ internal class SwapTransferStateBuilderTest {
|
|||
coVerify(exactly = 1) {
|
||||
notificationsFactory.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = any(),
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
actions = any(),
|
||||
getFeeError = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -338,10 +346,9 @@ internal class SwapTransferStateBuilderTest {
|
|||
coEvery {
|
||||
notificationsFactory.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = any(),
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = fee,
|
||||
actions = any(),
|
||||
getFeeError = any(),
|
||||
)
|
||||
} returns persistentListOf()
|
||||
|
||||
|
|
@ -353,7 +360,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = fee,
|
||||
isTangemPayWithdrawal = false,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
assertThat(result.swapButton.isEnabled).isTrue()
|
||||
|
|
@ -362,10 +369,9 @@ internal class SwapTransferStateBuilderTest {
|
|||
coVerify(exactly = 1) {
|
||||
notificationsFactory.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = any(),
|
||||
feeCryptoCurrencyStatus = null,
|
||||
fee = fee,
|
||||
actions = any(),
|
||||
getFeeError = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -387,8 +393,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
transferState = transferState,
|
||||
uiStateHolder = baseStateHolder(),
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = mockk(relaxed = true),
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
val sendCard = result.sendCardData as SwapCardState.SwapCardData
|
||||
|
|
@ -422,8 +427,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
transferState = transferState,
|
||||
uiStateHolder = baseStateHolder(),
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
val receiveCard = result.receiveCardData as SwapCardState.SwapCardData
|
||||
|
|
@ -450,8 +454,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
transferState = transferState,
|
||||
uiStateHolder = baseStateHolder(),
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
val receiveCard = result.receiveCardData as SwapCardState.SwapCardData
|
||||
|
|
@ -482,7 +485,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = mockk(relaxed = true),
|
||||
isTangemPayWithdrawal = false,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
val receiveCard = result.receiveCardData as SwapCardState.SwapCardData
|
||||
|
|
@ -517,7 +520,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = fee,
|
||||
isTangemPayWithdrawal = false,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
assertThat(result.transferFooter).isInstanceOf(TextReference.Combined::class.java)
|
||||
|
|
@ -576,7 +579,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = fee,
|
||||
isTangemPayWithdrawal = false,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
assertThat(result.transferFooter).isEqualTo(
|
||||
|
|
@ -622,7 +625,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = fee,
|
||||
isTangemPayWithdrawal = false,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
assertThat(result.transferFooter).isEqualTo(
|
||||
|
|
@ -753,7 +756,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
isTangemPayWithdrawal = true,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
assertThat(result.swapButton.isEnabled).isTrue()
|
||||
|
|
@ -789,7 +792,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
isTangemPayWithdrawal = false,
|
||||
feeError = null,
|
||||
feeSelectorUM = null,
|
||||
)
|
||||
|
||||
assertThat(result.swapButton.isEnabled).isFalse()
|
||||
|
|
@ -939,6 +942,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
isFeeCoverage = isFeeCoverage,
|
||||
sendingAmount = toAmount,
|
||||
tronFeeNotificationShowCount = 0,
|
||||
isAmountSubtractAvailable = false,
|
||||
isSendingAmountLoading = isSendingAmountLoading,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue