Updated on 2026-08-14
This commit is contained in:
parent
6afbb27b6b
commit
fb72f7c913
5 changed files with 323 additions and 44 deletions
|
|
@ -54,6 +54,7 @@ sealed interface SwapState {
|
|||
val isAccountsMode: Boolean,
|
||||
val isFeeCoverage: Boolean,
|
||||
val sendingAmount: BigDecimal,
|
||||
val isSendingAmountLoading: Boolean = false,
|
||||
val currencyCheck: CryptoCurrencyCheck? = null,
|
||||
val validationResult: Throwable? = null,
|
||||
val minAdaValue: BigDecimal? = null,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ import com.tangem.feature.swap.domain.fee.TransactionFeeResult
|
|||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.ui.SwapState
|
||||
import com.tangem.feature.swap.domain.models.ui.TokenSwapInfo
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkAndCalculateSubtractedAmount
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkFeeCoverage
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
|
@ -107,7 +106,7 @@ class SwapTransferInteractorImpl @Inject constructor(
|
|||
fee = warningsFee,
|
||||
feeCurrencyBalanceAfterTransaction = null,
|
||||
)
|
||||
val (isFeeCoverage, sendingAmount) = getCoverageState(
|
||||
val coverageState = getCoverageState(
|
||||
fromTokenInfo = fromTokenInfo,
|
||||
userWallet = userWallet,
|
||||
fee = fee,
|
||||
|
|
@ -130,8 +129,9 @@ class SwapTransferInteractorImpl @Inject constructor(
|
|||
appCurrency = appCurrency,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
isAccountsMode = isAccountsMode,
|
||||
isFeeCoverage = isFeeCoverage,
|
||||
sendingAmount = sendingAmount,
|
||||
isFeeCoverage = coverageState.isFeeCoverage,
|
||||
sendingAmount = coverageState.sendingAmount,
|
||||
isSendingAmountLoading = coverageState.isSendingAmountLoading,
|
||||
currencyCheck = currencyCheck,
|
||||
)
|
||||
}
|
||||
|
|
@ -155,7 +155,7 @@ class SwapTransferInteractorImpl @Inject constructor(
|
|||
userWallet: UserWallet,
|
||||
fee: Fee?,
|
||||
currencyCheck: CryptoCurrencyCheck,
|
||||
): Pair<Boolean, BigDecimal> {
|
||||
): CoverageState {
|
||||
val swapCurrencyStatus = fromTokenInfo.swapCurrencyStatus
|
||||
val isAmountSubtractAvailable = isAmountSubtractAvailable(
|
||||
userWalletId = userWallet.walletId,
|
||||
|
|
@ -173,16 +173,29 @@ class SwapTransferInteractorImpl @Inject constructor(
|
|||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
)
|
||||
val sendingAmount = checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||
cryptoCurrencyStatus = fromTokenInfo.swapCurrencyStatus.status,
|
||||
amountValue = amount.value,
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
// When fee coverage applies, the entered amount can't be sent together with the fee, so the
|
||||
// sent (and therefore received) amount is the entered amount reduced by the fee. This tracks the
|
||||
// input: as the user edits the amount, the received amount changes with it.
|
||||
val sendingAmount = if (isFeeCoverage) {
|
||||
(amount.value - feeValue).coerceAtLeast(BigDecimal.ZERO)
|
||||
} else {
|
||||
amount.value
|
||||
}
|
||||
// While subtraction is possible but the fee has not loaded yet, the final received amount
|
||||
// (entered - fee) is unknown, so it must be shown as loading instead of the un-subtracted value.
|
||||
return CoverageState(
|
||||
isFeeCoverage = isFeeCoverage,
|
||||
sendingAmount = sendingAmount,
|
||||
isSendingAmountLoading = fee == null && isAmountSubtractAvailable,
|
||||
)
|
||||
return isFeeCoverage to sendingAmount
|
||||
}
|
||||
|
||||
private data class CoverageState(
|
||||
val isFeeCoverage: Boolean,
|
||||
val sendingAmount: BigDecimal,
|
||||
val isSendingAmountLoading: Boolean,
|
||||
)
|
||||
|
||||
private suspend fun isAmountSubtractAvailable(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ internal class SwapTransferInteractorImplTest {
|
|||
} returns true.right()
|
||||
|
||||
// entered amount = full balance → balance < amount + fee, balance > fee, balance >= amount
|
||||
// → isFeeCoverage = true, sendingAmount = balance - fee
|
||||
// → isFeeCoverage = true, sendingAmount = entered - fee (== balance - fee at max)
|
||||
val result = sut.updateTransfer(
|
||||
fromSwapCurrencyStatus = fromCurrencyStatus,
|
||||
toSwapCurrencyStatus = toCurrencyStatus,
|
||||
|
|
@ -310,6 +310,113 @@ internal class SwapTransferInteractorImplTest {
|
|||
|
||||
assertThat(result.isFeeCoverage).isTrue()
|
||||
assertThat(result.sendingAmount).isEqualTo(balance - feeValue)
|
||||
assertThat(result.isSendingAmountLoading).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN subtract available and sub-max amount in coverage zone WHEN updateTransfer THEN sendingAmount is entered minus fee`() =
|
||||
runTest {
|
||||
val appCurrency = AppCurrency(code = "USD", name = "US Dollar", symbol = "$")
|
||||
val userWallet: UserWallet = mockk(relaxed = true)
|
||||
val balance = BigDecimal("1.5")
|
||||
val feeValue = BigDecimal("0.2")
|
||||
// entered is below the balance but still within one fee of it → coverage applies, yet the
|
||||
// received amount must track the entered amount (entered - fee), not clamp to balance - fee.
|
||||
val enteredAmount = BigDecimal("1.45")
|
||||
val fromCurrencyStatus = buildCurrencyStatus(
|
||||
rawCurrencyId = FROM_RAW_CURRENCY_ID,
|
||||
decimals = FROM_DECIMALS,
|
||||
fiatRate = BigDecimal.TEN,
|
||||
amount = balance,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
val toCurrencyStatus = buildCurrencyStatus(
|
||||
rawCurrencyId = TO_RAW_CURRENCY_ID,
|
||||
decimals = TO_DECIMALS,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
val fee: Fee = mockk(relaxed = true) {
|
||||
every { amount.value } returns feeValue
|
||||
}
|
||||
every { getSelectedAppCurrencyUseCase() } returns flowOf(appCurrency.right())
|
||||
every { getBalanceHidingSettingsUseCase.isBalanceHidden() } returns flowOf(false)
|
||||
coEvery { isAccountsModeEnabledUseCase.invokeSync() } returns false
|
||||
coEvery {
|
||||
getCurrencyCheckUseCase(
|
||||
userWalletId = any(),
|
||||
currencyStatus = any(),
|
||||
feeCurrencyStatus = any(),
|
||||
amount = any(),
|
||||
fee = any(),
|
||||
feeCurrencyBalanceAfterTransaction = any(),
|
||||
recipientAddress = any(),
|
||||
)
|
||||
} returns buildCurrencyCheck()
|
||||
coEvery {
|
||||
isAmountSubtractAvailableUseCase(any(), any(), any())
|
||||
} returns true.right()
|
||||
|
||||
val result = sut.updateTransfer(
|
||||
fromSwapCurrencyStatus = fromCurrencyStatus,
|
||||
toSwapCurrencyStatus = toCurrencyStatus,
|
||||
fromTokenAmount = enteredAmount.toPlainString(),
|
||||
feePaidCurrencyStatus = null,
|
||||
fee = fee,
|
||||
) as SwapState.Transfer
|
||||
|
||||
assertThat(result.isFeeCoverage).isTrue()
|
||||
assertThat(result.sendingAmount).isEqualTo(enteredAmount - feeValue)
|
||||
// it must NOT clamp to balance - fee
|
||||
assertThat(result.sendingAmount).isNotEqualTo(balance - feeValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN subtract available but fee not loaded yet WHEN updateTransfer THEN isSendingAmountLoading is true`() =
|
||||
runTest {
|
||||
val appCurrency = AppCurrency(code = "USD", name = "US Dollar", symbol = "$")
|
||||
val userWallet: UserWallet = mockk(relaxed = true)
|
||||
val balance = BigDecimal("1.5")
|
||||
val fromCurrencyStatus = buildCurrencyStatus(
|
||||
rawCurrencyId = FROM_RAW_CURRENCY_ID,
|
||||
decimals = FROM_DECIMALS,
|
||||
fiatRate = BigDecimal.TEN,
|
||||
amount = balance,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
val toCurrencyStatus = buildCurrencyStatus(
|
||||
rawCurrencyId = TO_RAW_CURRENCY_ID,
|
||||
decimals = TO_DECIMALS,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
every { getSelectedAppCurrencyUseCase() } returns flowOf(appCurrency.right())
|
||||
every { getBalanceHidingSettingsUseCase.isBalanceHidden() } returns flowOf(false)
|
||||
coEvery { isAccountsModeEnabledUseCase.invokeSync() } returns false
|
||||
coEvery {
|
||||
getCurrencyCheckUseCase(
|
||||
userWalletId = any(),
|
||||
currencyStatus = any(),
|
||||
feeCurrencyStatus = any(),
|
||||
amount = any(),
|
||||
fee = any(),
|
||||
feeCurrencyBalanceAfterTransaction = any(),
|
||||
recipientAddress = any(),
|
||||
)
|
||||
} returns buildCurrencyCheck()
|
||||
coEvery {
|
||||
isAmountSubtractAvailableUseCase(any(), any(), any())
|
||||
} returns true.right()
|
||||
|
||||
// subtraction is possible but the fee has not loaded yet (fee = null) → the received amount
|
||||
// depends on the fee, so it can't be known yet and must be reported as loading.
|
||||
val result = sut.updateTransfer(
|
||||
fromSwapCurrencyStatus = fromCurrencyStatus,
|
||||
toSwapCurrencyStatus = toCurrencyStatus,
|
||||
fromTokenAmount = balance.toPlainString(),
|
||||
feePaidCurrencyStatus = null,
|
||||
fee = null,
|
||||
) as SwapState.Transfer
|
||||
|
||||
assertThat(result.isSendingAmountLoading).isTrue()
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.core.ui.extensions.wrappedList
|
|||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.simple
|
||||
import com.tangem.core.ui.utils.parseBigDecimalOrNull
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.Account
|
||||
|
|
@ -31,11 +32,11 @@ import com.tangem.feature.swap.domain.models.ui.SwapState
|
|||
import com.tangem.feature.swap.domain.models.ui.TokenSwapInfo
|
||||
import com.tangem.feature.swap.model.SwapProcessDataState
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.ui.SwapAmountScreenClickIntents
|
||||
import com.tangem.feature.swap.ui.swapSuccessNavigation
|
||||
import com.tangem.feature.swap.models.SwapButton.Mode
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.ui.SwapAmountScreenClickIntents
|
||||
import com.tangem.feature.swap.ui.swapSuccessNavigation
|
||||
import com.tangem.features.send.api.utils.formatFooterFiatFee
|
||||
import com.tangem.features.send.api.utils.getTronTokenFeeSendingText
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
|
@ -59,11 +60,9 @@ internal class SwapTransferStateBuilder @Inject constructor(
|
|||
fee: Fee?,
|
||||
): SwapStateHolder {
|
||||
val fromTokenSwapInfo = transferState.fromTokenInfo
|
||||
val toTokenSwapInfo = transferState.toTokenInfo
|
||||
val isInsufficientBalance = transferState.isInsufficientBalance
|
||||
val prevSendCard = uiStateHolder.sendCardData as? SwapCardState.SwapCardData
|
||||
val prevAmountField = prevSendCard?.amountField
|
||||
val displayValue = prevAmountField?.value.orEmpty()
|
||||
val notifications = notificationsFactory.getNotifications(
|
||||
transferState = transferState,
|
||||
feeCryptoCurrencyStatus = feePaidCryptoCurrencyStatus,
|
||||
|
|
@ -75,25 +74,14 @@ internal class SwapTransferStateBuilder @Inject constructor(
|
|||
return uiStateHolder.copy(
|
||||
sendCardData = createSendSwapCardState(
|
||||
actions = actions,
|
||||
displayValue = displayValue,
|
||||
tokenSwapInfo = fromTokenSwapInfo,
|
||||
appCurrency = transferState.appCurrency,
|
||||
isAccountsMode = transferState.isAccountsMode,
|
||||
isFromCard = true,
|
||||
isBalanceHidden = transferState.isBalanceHidden,
|
||||
isInsufficientBalance = isInsufficientBalance,
|
||||
prevAmountField = prevAmountField,
|
||||
),
|
||||
receiveCardData = createSendSwapCardState(
|
||||
actions = actions,
|
||||
displayValue = displayValue,
|
||||
tokenSwapInfo = toTokenSwapInfo,
|
||||
appCurrency = transferState.appCurrency,
|
||||
isAccountsMode = transferState.isAccountsMode,
|
||||
isFromCard = false,
|
||||
isBalanceHidden = transferState.isBalanceHidden,
|
||||
isInsufficientBalance = isInsufficientBalance,
|
||||
),
|
||||
receiveCardData = createReceiveCard(actions = actions, transferState = transferState),
|
||||
isInsufficientFunds = isInsufficientBalance,
|
||||
swapButton = SwapButton(
|
||||
walletInteractionIcon = walletInterationIcon(transferState.userWallet),
|
||||
|
|
@ -109,14 +97,12 @@ internal class SwapTransferStateBuilder @Inject constructor(
|
|||
@Suppress("LongParameterList")
|
||||
private fun createSendSwapCardState(
|
||||
actions: UiActions,
|
||||
displayValue: String,
|
||||
tokenSwapInfo: TokenSwapInfo,
|
||||
appCurrency: AppCurrency,
|
||||
isAccountsMode: Boolean,
|
||||
isFromCard: Boolean,
|
||||
isBalanceHidden: Boolean,
|
||||
isInsufficientBalance: Boolean,
|
||||
prevAmountField: AmountFieldModel? = null,
|
||||
prevAmountField: AmountFieldModel?,
|
||||
): SwapCardState {
|
||||
val swapCurrencyStatus = tokenSwapInfo.swapCurrencyStatus
|
||||
val currency = swapCurrencyStatus.currency
|
||||
|
|
@ -126,7 +112,7 @@ internal class SwapTransferStateBuilder @Inject constructor(
|
|||
actions = actions,
|
||||
swapCurrencyStatus = tokenSwapInfo.swapCurrencyStatus,
|
||||
isAccountsMode = isAccountsMode,
|
||||
isFromCard = isFromCard,
|
||||
isFromCard = true,
|
||||
isInsufficientBalance = isInsufficientBalance,
|
||||
),
|
||||
currencyIconState = iconConverter.convert(
|
||||
|
|
@ -140,18 +126,61 @@ internal class SwapTransferStateBuilder @Inject constructor(
|
|||
balance = swapCurrencyStatus.status.getFormattedAmount(),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
appCurrency = appCurrency,
|
||||
amountField = if (isFromCard) {
|
||||
buildAmountField(
|
||||
actions = actions,
|
||||
prevAmountField = prevAmountField,
|
||||
swapCurrencyStatus = swapCurrencyStatus,
|
||||
appCurrency = appCurrency,
|
||||
)
|
||||
amountField = buildAmountField(
|
||||
actions = actions,
|
||||
prevAmountField = prevAmountField,
|
||||
swapCurrencyStatus = swapCurrencyStatus,
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the read-only receive card from [SwapState.Transfer.sendingAmount] — the amount that will
|
||||
* actually be received, already reduced by the fee when fee coverage applies. While the reduced amount
|
||||
* is not yet known (fee still loading) the amount and fiat fields are null, which makes the read-only
|
||||
* card render a shimmer instead of the un-subtracted value.
|
||||
*/
|
||||
private fun createReceiveCard(actions: UiActions, transferState: SwapState.Transfer): SwapCardState {
|
||||
val toTokenSwapInfo = transferState.toTokenInfo
|
||||
val swapCurrencyStatus = toTokenSwapInfo.swapCurrencyStatus
|
||||
val currency = swapCurrencyStatus.currency
|
||||
val appCurrency = transferState.appCurrency
|
||||
val sendingAmount = transferState.sendingAmount
|
||||
// No reduction can happen when the balance is insufficient (fee coverage requires balance >= amount),
|
||||
// so there is nothing to wait for — show the amount instead of a shimmer.
|
||||
val isLoading = transferState.isSendingAmountLoading && !transferState.isInsufficientBalance
|
||||
val fiatRate = swapCurrencyStatus.status.value.fiatRate
|
||||
|
||||
return SwapCardState.SwapCardData(
|
||||
type = createSendTransactionCardType(
|
||||
actions = actions,
|
||||
swapCurrencyStatus = swapCurrencyStatus,
|
||||
isAccountsMode = transferState.isAccountsMode,
|
||||
isFromCard = false,
|
||||
isInsufficientBalance = transferState.isInsufficientBalance,
|
||||
),
|
||||
currencyIconState = iconConverter.convert(
|
||||
value = swapCurrencyStatus.status,
|
||||
),
|
||||
tokenSymbol = stringReference(currency.symbol),
|
||||
amountEquivalent = if (isLoading) {
|
||||
null
|
||||
} else {
|
||||
// Read-only receive card mirrors the same display value the "from" card shows in transfer mode.
|
||||
getFormattedFiatAmount(appCurrency = appCurrency, amount = fiatRate?.multiply(sendingAmount))
|
||||
},
|
||||
balance = swapCurrencyStatus.status.getFormattedAmount(),
|
||||
isBalanceHidden = transferState.isBalanceHidden,
|
||||
appCurrency = appCurrency,
|
||||
amountField = if (isLoading) {
|
||||
null
|
||||
} else {
|
||||
val value = sendingAmount.format {
|
||||
simple(decimals = currency.decimals)
|
||||
}
|
||||
displayAmountField(
|
||||
actions = actions,
|
||||
value = displayValue,
|
||||
value = value,
|
||||
swapCurrencyStatus = swapCurrencyStatus,
|
||||
appCurrency = appCurrency,
|
||||
)
|
||||
|
|
@ -324,6 +353,10 @@ internal class SwapTransferStateBuilder @Inject constructor(
|
|||
)
|
||||
return uiStateHolder.copy(
|
||||
notifications = notifications,
|
||||
// Rebuild the receive card from the refreshed transferState: this path runs after the fee
|
||||
// selector resolves, when sendingAmount may have just been reduced by the fee. Only the
|
||||
// receive card is rebuilt to avoid clobbering the user's in-progress input on the "from" card.
|
||||
receiveCardData = createReceiveCard(actions = actions, transferState = transferState),
|
||||
swapButton = uiStateHolder.swapButton.copy(
|
||||
isEnabled = getTransferButtonEnabled(notifications, fee, isTangemPayWithdrawal),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.core.ui.format.bigdecimal.crypto
|
|||
import com.tangem.core.ui.format.bigdecimal.fee
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
|
@ -371,6 +372,123 @@ internal class SwapTransferStateBuilderTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fee coverage reduces amount WHEN createTransferState THEN receive card shows reduced sendingAmount`() =
|
||||
runTest {
|
||||
// entered 1.5, but fee coverage reduces the received (sending) amount to 1.3
|
||||
val sendingAmount = BigDecimal("1.3")
|
||||
val transferState = buildTransferState(
|
||||
fromAmount = BigDecimal("1.5"),
|
||||
toAmount = sendingAmount,
|
||||
isAccountsMode = false,
|
||||
isFeeCoverage = true,
|
||||
)
|
||||
|
||||
val result = sut.createTransferState(
|
||||
actions = actions,
|
||||
transferState = transferState,
|
||||
uiStateHolder = baseStateHolder(),
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = mockk(relaxed = true),
|
||||
)
|
||||
|
||||
val sendCard = result.sendCardData as SwapCardState.SwapCardData
|
||||
val receiveCard = result.receiveCardData as SwapCardState.SwapCardData
|
||||
val expectedFiat = stringReference(
|
||||
toCurrencyStatus.status.value.fiatRate!!.multiply(sendingAmount).format {
|
||||
fiat(fiatCurrencyCode = AppCurrency.Default.code, fiatCurrencySymbol = AppCurrency.Default.symbol)
|
||||
},
|
||||
)
|
||||
// the "from" card keeps the user's typed value, the receive card shows the reduced amount + its fiat
|
||||
assertThat(sendCard.amountField?.value).isEqualTo(initialAmountValue)
|
||||
assertThat(receiveCard.amountField?.value).isEqualTo(
|
||||
sendingAmount.parseBigDecimal(transferState.toTokenInfo.tokenAmount.decimals),
|
||||
)
|
||||
assertThat(receiveCard.amountEquivalent).isEqualTo(expectedFiat)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN sendingAmount loading WHEN createTransferState THEN receive card amount and fiat shimmer`() =
|
||||
runTest {
|
||||
// fee not loaded yet → reduced amount unknown → receive card shimmers (null amount + null fiat)
|
||||
val transferState = buildTransferState(
|
||||
fromAmount = BigDecimal("1.5"),
|
||||
toAmount = BigDecimal("1.5"),
|
||||
isAccountsMode = false,
|
||||
isSendingAmountLoading = true,
|
||||
)
|
||||
|
||||
val result = sut.createTransferState(
|
||||
actions = actions,
|
||||
transferState = transferState,
|
||||
uiStateHolder = baseStateHolder(),
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
)
|
||||
|
||||
val receiveCard = result.receiveCardData as SwapCardState.SwapCardData
|
||||
assertThat(receiveCard.amountField).isNull()
|
||||
assertThat(receiveCard.amountEquivalent).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN sendingAmount loading but insufficient balance WHEN createTransferState THEN receive card shows amount not shimmer`() =
|
||||
runTest {
|
||||
// Insufficient balance → fee coverage can't apply, so there is no reduction to wait for.
|
||||
// The receive card must show the amount instead of shimmering.
|
||||
val amount = BigDecimal("99")
|
||||
val transferState = buildTransferState(
|
||||
fromAmount = amount,
|
||||
toAmount = amount,
|
||||
isAccountsMode = false,
|
||||
isInsufficientBalance = true,
|
||||
isSendingAmountLoading = true,
|
||||
)
|
||||
|
||||
val result = sut.createTransferState(
|
||||
actions = actions,
|
||||
transferState = transferState,
|
||||
uiStateHolder = baseStateHolder(),
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = null,
|
||||
)
|
||||
|
||||
val receiveCard = result.receiveCardData as SwapCardState.SwapCardData
|
||||
assertThat(receiveCard.amountField?.value).isEqualTo(
|
||||
amount.parseBigDecimal(transferState.toTokenInfo.tokenAmount.decimals),
|
||||
)
|
||||
assertThat(receiveCard.amountEquivalent).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN reduced sendingAmount WHEN updateTransferButtonEnableState THEN receive card is rebuilt to reduced amount`() =
|
||||
runTest {
|
||||
// After the fee resolves, sendingAmount is reduced; the refresh path must rebuild the receive card
|
||||
// so it no longer shows the stale full amount.
|
||||
val sendingAmount = BigDecimal("1.3")
|
||||
val transferState = buildTransferState(
|
||||
fromAmount = BigDecimal("1.5"),
|
||||
toAmount = sendingAmount,
|
||||
isAccountsMode = false,
|
||||
isFeeCoverage = true,
|
||||
)
|
||||
|
||||
val result = sut.updateTransferButtonEnableState(
|
||||
dataState = SwapProcessDataState(),
|
||||
transferState = transferState,
|
||||
actions = actions,
|
||||
uiStateHolder = baseStateHolder(),
|
||||
feePaidCryptoCurrencyStatus = null,
|
||||
fee = mockk(relaxed = true),
|
||||
isTangemPayWithdrawal = false,
|
||||
)
|
||||
|
||||
val receiveCard = result.receiveCardData as SwapCardState.SwapCardData
|
||||
assertThat(receiveCard.amountField?.value).isEqualTo(
|
||||
sendingAmount.parseBigDecimal(transferState.toTokenInfo.tokenAmount.decimals),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN Tron fee WHEN updateTransferButtonEnableState THEN transferFooter uses Tron token fee sending text`() =
|
||||
runTest {
|
||||
|
|
@ -747,8 +865,12 @@ internal class SwapTransferStateBuilderTest {
|
|||
) {
|
||||
val sendCard = result.sendCardData as SwapCardState.SwapCardData
|
||||
val receiveCard = result.receiveCardData as SwapCardState.SwapCardData
|
||||
// The "from" card preserves the user's typed value; the receive card shows the (possibly
|
||||
// fee-reduced) sendingAmount formatted with the receive token's decimals.
|
||||
assertThat(sendCard.amountField?.value).isEqualTo(initialAmountValue)
|
||||
assertThat(receiveCard.amountField?.value).isEqualTo(initialAmountValue)
|
||||
assertThat(receiveCard.amountField?.value).isEqualTo(
|
||||
transferState.sendingAmount.parseBigDecimal(transferState.toTokenInfo.tokenAmount.decimals),
|
||||
)
|
||||
assertThat(sendCard.currencyIconState).isEqualTo(fromIcon)
|
||||
assertThat(receiveCard.currencyIconState).isEqualTo(toIcon)
|
||||
assertThat(sendCard.isBalanceHidden).isEqualTo(transferState.isBalanceHidden)
|
||||
|
|
@ -778,6 +900,8 @@ internal class SwapTransferStateBuilderTest {
|
|||
toAmount: BigDecimal,
|
||||
isAccountsMode: Boolean,
|
||||
isInsufficientBalance: Boolean = false,
|
||||
isFeeCoverage: Boolean = false,
|
||||
isSendingAmountLoading: Boolean = false,
|
||||
): SwapState.Transfer {
|
||||
val fromInfo = TokenSwapInfo(
|
||||
tokenAmount = SwapAmount(value = fromAmount, decimals = fromCurrencyStatus.currency.decimals),
|
||||
|
|
@ -798,8 +922,9 @@ internal class SwapTransferStateBuilderTest {
|
|||
appCurrency = AppCurrency.Default,
|
||||
isBalanceHidden = false,
|
||||
isAccountsMode = isAccountsMode,
|
||||
isFeeCoverage = false,
|
||||
isFeeCoverage = isFeeCoverage,
|
||||
sendingAmount = toAmount,
|
||||
isSendingAmountLoading = isSendingAmountLoading,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue