Updated on 2026-08-14
This commit is contained in:
commit
c21ef23449
5 changed files with 138 additions and 15 deletions
|
|
@ -43,7 +43,7 @@ sealed interface SwapState {
|
|||
val userWallet: UserWallet,
|
||||
val fromTokenInfo: TokenSwapInfo,
|
||||
val toTokenInfo: TokenSwapInfo,
|
||||
val txFee: TxFeeState,
|
||||
val isInsufficientBalance: Boolean,
|
||||
val appCurrency: AppCurrency,
|
||||
val isBalanceHidden: Boolean,
|
||||
val isAccountsMode: Boolean,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.swap.models.SwapCurrencyStatus
|
|||
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.feature.swap.domain.models.ui.TxFeeState
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
|
@ -40,6 +39,7 @@ class SwapTransferInteractorImpl @Inject constructor(
|
|||
val isAccountsMode = isAccountsModeEnabledUseCase.invokeSync()
|
||||
val fromTokenAmountValue = fromTokenAmount.parseBigDecimalOrNull() ?: return createEmptyAmountState(appCurrency)
|
||||
val fromTokenAmountFiat = fromSwapCurrencyStatus.status.value.fiatRate.orZero() * fromTokenAmountValue
|
||||
val fromTokenBalance = fromSwapCurrencyStatus.status.value.amount.orZero()
|
||||
|
||||
val fromTokenInfo = TokenSwapInfo(
|
||||
tokenAmount = SwapAmount(fromTokenAmountValue, fromToken.decimals),
|
||||
|
|
@ -56,7 +56,7 @@ class SwapTransferInteractorImpl @Inject constructor(
|
|||
userWallet = toSwapCurrencyStatus.userWallet,
|
||||
fromTokenInfo = fromTokenInfo,
|
||||
toTokenInfo = toTokenInfo,
|
||||
txFee = TxFeeState.Empty, // todo Will be implemented in [REDACTED_TASK_KEY]
|
||||
isInsufficientBalance = fromTokenAmountValue > fromTokenBalance,
|
||||
appCurrency = appCurrency,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
isAccountsMode = isAccountsMode,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.swap.models.SwapCurrencyStatus
|
|||
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.feature.swap.domain.models.ui.TxFeeState
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -85,6 +84,7 @@ internal class SwapTransferInteractorImplTest {
|
|||
rawCurrencyId = FROM_RAW_CURRENCY_ID,
|
||||
decimals = FROM_DECIMALS,
|
||||
fiatRate = BigDecimal.TEN,
|
||||
amount = BigDecimal("1.6"),
|
||||
)
|
||||
val toCurrencyStatus = buildCurrencyStatus(
|
||||
rawCurrencyId = TO_RAW_CURRENCY_ID,
|
||||
|
|
@ -115,7 +115,7 @@ internal class SwapTransferInteractorImplTest {
|
|||
swapCurrencyStatus = toCurrencyStatus,
|
||||
amountFiat = expectedFiat,
|
||||
),
|
||||
txFee = TxFeeState.Empty,
|
||||
isInsufficientBalance = false,
|
||||
appCurrency = appCurrency,
|
||||
isBalanceHidden = true,
|
||||
isAccountsMode = true,
|
||||
|
|
@ -125,6 +125,55 @@ internal class SwapTransferInteractorImplTest {
|
|||
verify { getBalanceHidingSettingsUseCase.isBalanceHidden() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN insufficient amount WHEN updateTransfer THEN return state with insufficient amount`() = runTest {
|
||||
val appCurrency = AppCurrency(code = "USD", name = "US Dollar", symbol = "$")
|
||||
val userWallet: UserWallet = mockk()
|
||||
val fromCurrencyStatus = buildCurrencyStatus(
|
||||
rawCurrencyId = FROM_RAW_CURRENCY_ID,
|
||||
decimals = FROM_DECIMALS,
|
||||
fiatRate = BigDecimal.TEN,
|
||||
amount = BigDecimal("1.4"),
|
||||
)
|
||||
val toCurrencyStatus = buildCurrencyStatus(
|
||||
rawCurrencyId = TO_RAW_CURRENCY_ID,
|
||||
decimals = TO_DECIMALS,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
every { getSelectedAppCurrencyUseCase() } returns flowOf(appCurrency.right())
|
||||
every { getBalanceHidingSettingsUseCase.isBalanceHidden() } returns flowOf(true)
|
||||
coEvery { isAccountsModeEnabledUseCase.invokeSync() } returns true
|
||||
|
||||
val result = sut.updateTransfer(
|
||||
fromSwapCurrencyStatus = fromCurrencyStatus,
|
||||
toSwapCurrencyStatus = toCurrencyStatus,
|
||||
fromTokenAmount = "1,5",
|
||||
)
|
||||
|
||||
val expectedAmount = BigDecimal("1.5")
|
||||
val expectedFiat = BigDecimal("15.0")
|
||||
val expected = SwapState.Transfer(
|
||||
userWallet = userWallet,
|
||||
fromTokenInfo = TokenSwapInfo(
|
||||
tokenAmount = SwapAmount(expectedAmount, FROM_DECIMALS),
|
||||
swapCurrencyStatus = fromCurrencyStatus,
|
||||
amountFiat = expectedFiat,
|
||||
),
|
||||
toTokenInfo = TokenSwapInfo(
|
||||
tokenAmount = SwapAmount(expectedAmount, TO_DECIMALS),
|
||||
swapCurrencyStatus = toCurrencyStatus,
|
||||
amountFiat = expectedFiat,
|
||||
),
|
||||
isInsufficientBalance = true,
|
||||
appCurrency = appCurrency,
|
||||
isBalanceHidden = true,
|
||||
isAccountsMode = true,
|
||||
)
|
||||
assertThat(result).isEqualTo(expected)
|
||||
coVerify { isAccountsModeEnabledUseCase.invokeSync() }
|
||||
verify { getBalanceHidingSettingsUseCase.isBalanceHidden() }
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region shouldTransferInsteadOfSwap
|
||||
|
|
@ -249,6 +298,7 @@ internal class SwapTransferInteractorImplTest {
|
|||
rawCurrencyId: CryptoCurrency.RawID?,
|
||||
decimals: Int,
|
||||
fiatRate: BigDecimal = BigDecimal.ZERO,
|
||||
amount: BigDecimal = BigDecimal.ZERO,
|
||||
userWallet: UserWallet = mockk(),
|
||||
): SwapCurrencyStatus {
|
||||
val currencyId: CryptoCurrency.ID = mockk {
|
||||
|
|
@ -260,6 +310,7 @@ internal class SwapTransferInteractorImplTest {
|
|||
}
|
||||
val currencyValue: CryptoCurrencyStatus.Value = mockk {
|
||||
every { this@mockk.fiatRate } returns fiatRate
|
||||
every { this@mockk.amount } returns amount
|
||||
}
|
||||
val status: CryptoCurrencyStatus = mockk {
|
||||
every { this@mockk.value } returns currencyValue
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ internal class SwapTransferStateBuilder @Inject constructor() {
|
|||
): SwapStateHolder {
|
||||
val fromTokenSwapInfo = transferState.fromTokenInfo
|
||||
val toTokenSwapInfo = transferState.toTokenInfo
|
||||
val isInsufficientBalance = transferState.isInsufficientBalance
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = createSendSwapCardState(
|
||||
actions = actions,
|
||||
|
|
@ -46,6 +47,7 @@ internal class SwapTransferStateBuilder @Inject constructor() {
|
|||
isAccountsMode = transferState.isAccountsMode,
|
||||
isFromCard = true,
|
||||
isBalanceHidden = transferState.isBalanceHidden,
|
||||
isInsufficientBalance = isInsufficientBalance,
|
||||
),
|
||||
receiveCardData = createSendSwapCardState(
|
||||
actions = actions,
|
||||
|
|
@ -54,10 +56,12 @@ internal class SwapTransferStateBuilder @Inject constructor() {
|
|||
isAccountsMode = transferState.isAccountsMode,
|
||||
isFromCard = false,
|
||||
isBalanceHidden = transferState.isBalanceHidden,
|
||||
isInsufficientBalance = isInsufficientBalance,
|
||||
),
|
||||
isInsufficientFunds = isInsufficientBalance,
|
||||
swapButton = SwapButton(
|
||||
walletInteractionIcon = walletInterationIcon(transferState.userWallet),
|
||||
isEnabled = false,
|
||||
isEnabled = !isInsufficientBalance,
|
||||
mode = SwapButton.Mode.TRANSFER,
|
||||
onClick = actions.onTransferClick,
|
||||
),
|
||||
|
|
@ -72,6 +76,7 @@ internal class SwapTransferStateBuilder @Inject constructor() {
|
|||
isAccountsMode: Boolean,
|
||||
isFromCard: Boolean,
|
||||
isBalanceHidden: Boolean,
|
||||
isInsufficientBalance: Boolean,
|
||||
): SwapCardState {
|
||||
val swapCurrencyStatus = tokenSwapInfo.swapCurrencyStatus
|
||||
val formattedSwapAmount = tokenSwapInfo.tokenAmount.formatToUIRepresentation()
|
||||
|
|
@ -82,6 +87,7 @@ internal class SwapTransferStateBuilder @Inject constructor() {
|
|||
swapCurrencyStatus = tokenSwapInfo.swapCurrencyStatus,
|
||||
isAccountsMode = isAccountsMode,
|
||||
isFromCard = isFromCard,
|
||||
isInsufficientBalance = isInsufficientBalance,
|
||||
),
|
||||
currencyIconState = iconConverter.convert(
|
||||
value = swapCurrencyStatus.status,
|
||||
|
|
@ -105,17 +111,27 @@ internal class SwapTransferStateBuilder @Inject constructor() {
|
|||
swapCurrencyStatus: SwapCurrencyStatus,
|
||||
isAccountsMode: Boolean,
|
||||
isFromCard: Boolean,
|
||||
isInsufficientBalance: Boolean,
|
||||
): TransactionCardType {
|
||||
val type = if (isFromCard) {
|
||||
TransactionCardType.Inputtable(
|
||||
onAmountChanged = actions.onAmountChanged,
|
||||
onFocusChanged = actions.onAmountSelected,
|
||||
inputError = TransactionCardType.InputError.Empty,
|
||||
accountTitleUM = getCardAccountTitle(
|
||||
val accountTitleUM = if (isInsufficientBalance) {
|
||||
AccountTitleUM.Text(resourceReference(R.string.swapping_insufficient_funds))
|
||||
} else {
|
||||
getCardAccountTitle(
|
||||
account = swapCurrencyStatus.account,
|
||||
isAccountsMode = isAccountsMode,
|
||||
isFromCard = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
TransactionCardType.Inputtable(
|
||||
onAmountChanged = actions.onAmountChanged,
|
||||
onFocusChanged = actions.onAmountSelected,
|
||||
inputError = if (isInsufficientBalance) {
|
||||
TransactionCardType.InputError.InsufficientFunds
|
||||
} else {
|
||||
TransactionCardType.InputError.Empty
|
||||
},
|
||||
accountTitleUM = accountTitleUM,
|
||||
isEnabled = true,
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import com.tangem.feature.swap.domain.models.SwapAmount
|
|||
import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
||||
import com.tangem.feature.swap.domain.models.ui.SwapState
|
||||
import com.tangem.feature.swap.domain.models.ui.TokenSwapInfo
|
||||
import com.tangem.feature.swap.domain.models.ui.TxFeeState
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
|
|
@ -105,6 +104,62 @@ internal class SwapTransferStateBuilderTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN insufficient balance and accounts mode disabled WHEN createTransferState THEN from card shows insufficient funds title and error and swap is disabled`() {
|
||||
val transferState = buildTransferState(
|
||||
fromAmount = BigDecimal("10"),
|
||||
toAmount = BigDecimal("10"),
|
||||
isAccountsMode = false,
|
||||
isInsufficientBalance = true,
|
||||
)
|
||||
|
||||
val result = sut.createTransferState(actions, transferState, baseStateHolder())
|
||||
|
||||
val sendType = (result.sendCardData as SwapCardState.SwapCardData).type as TransactionCardType.Inputtable
|
||||
val receiveType = (result.receiveCardData as SwapCardState.SwapCardData).type as TransactionCardType.ReadOnly
|
||||
assertThat(sendType.accountTitleUM).isEqualTo(
|
||||
AccountTitleUM.Text(resourceReference(R.string.swapping_insufficient_funds)),
|
||||
)
|
||||
assertThat(sendType.inputError).isEqualTo(TransactionCardType.InputError.InsufficientFunds)
|
||||
assertThat(receiveType.accountTitleUM).isEqualTo(
|
||||
AccountTitleUM.Text(resourceReference(R.string.swapping_to_title)),
|
||||
)
|
||||
assertThat(result.isInsufficientFunds).isTrue()
|
||||
assertThat(result.swapButton.isEnabled).isFalse()
|
||||
assertThat(result.swapButton.mode).isEqualTo(SwapButton.Mode.TRANSFER)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN insufficient balance and accounts mode enabled WHEN createTransferState THEN from card overrides Account title with insufficient funds text`() {
|
||||
val transferState = buildTransferState(
|
||||
fromAmount = BigDecimal("10"),
|
||||
toAmount = BigDecimal("10"),
|
||||
isAccountsMode = true,
|
||||
isInsufficientBalance = true,
|
||||
)
|
||||
|
||||
val result = sut.createTransferState(actions, transferState, baseStateHolder())
|
||||
|
||||
val portfolioAccount = toCurrencyStatus.account as Account.CryptoPortfolio
|
||||
val expectedAccountIcon = CryptoPortfolioIconConverter.convert(portfolioAccount.icon)
|
||||
val expectedAccountName = portfolioAccount.accountName.toUM().value
|
||||
val sendType = (result.sendCardData as SwapCardState.SwapCardData).type as TransactionCardType.Inputtable
|
||||
val receiveType = (result.receiveCardData as SwapCardState.SwapCardData).type as TransactionCardType.ReadOnly
|
||||
assertThat(sendType.accountTitleUM).isEqualTo(
|
||||
AccountTitleUM.Text(resourceReference(R.string.swapping_insufficient_funds)),
|
||||
)
|
||||
assertThat(sendType.inputError).isEqualTo(TransactionCardType.InputError.InsufficientFunds)
|
||||
assertThat(receiveType.accountTitleUM).isEqualTo(
|
||||
AccountTitleUM.Account(
|
||||
prefixText = resourceReference(R.string.swapping_to_account_title),
|
||||
name = expectedAccountName,
|
||||
icon = expectedAccountIcon,
|
||||
),
|
||||
)
|
||||
assertThat(result.isInsufficientFunds).isTrue()
|
||||
assertThat(result.swapButton.isEnabled).isFalse()
|
||||
}
|
||||
|
||||
private fun assertSharedCardShape(
|
||||
result: SwapStateHolder,
|
||||
transferState: SwapState.Transfer,
|
||||
|
|
@ -128,7 +183,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
assertThat(result.swapButton).isEqualTo(
|
||||
SwapButton(
|
||||
walletInteractionIcon = walletInterationIcon(transferState.userWallet),
|
||||
isEnabled = false,
|
||||
isEnabled = !transferState.isInsufficientBalance,
|
||||
mode = SwapButton.Mode.TRANSFER,
|
||||
onClick = actions.onTransferClick,
|
||||
),
|
||||
|
|
@ -139,6 +194,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
fromAmount: BigDecimal,
|
||||
toAmount: BigDecimal,
|
||||
isAccountsMode: Boolean,
|
||||
isInsufficientBalance: Boolean = false,
|
||||
): SwapState.Transfer {
|
||||
val fromInfo = TokenSwapInfo(
|
||||
tokenAmount = SwapAmount(value = fromAmount, decimals = fromCurrencyStatus.currency.decimals),
|
||||
|
|
@ -154,7 +210,7 @@ internal class SwapTransferStateBuilderTest {
|
|||
userWallet = coldWallet,
|
||||
fromTokenInfo = fromInfo,
|
||||
toTokenInfo = toInfo,
|
||||
txFee = TxFeeState.Empty,
|
||||
isInsufficientBalance = isInsufficientBalance,
|
||||
appCurrency = AppCurrency.Default,
|
||||
isBalanceHidden = false,
|
||||
isAccountsMode = isAccountsMode,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue