From ebc435091997920178c0317cfa121805ef0a0a08 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 14 Jul 2025 14:27:34 +0500 Subject: [PATCH] Updated on 2026-08-14 --- features/send-v2/api/build.gradle.kts | 14 + .../feeSelector/utils/FeeCalculationUtils.kt | 99 ++ .../utils/FeeCalculationUtilsTest.kt | 188 ++++ features/send-v2/impl/build.gradle.kts | 13 + .../v2/send/confirm/model/SendConfirmModel.kt | 28 +- ...dConfirmationNotificationsTransformerV2.kt | 110 +++ ...onfirmationNotificationsTransformerTest.kt | 439 +++++++++ ...firmationNotificationsTransformerV2Test.kt | 488 ++++++++++ .../TransformersComparisonTest.kt | 847 ++++++++++++++++++ 9 files changed, 2218 insertions(+), 8 deletions(-) create mode 100644 features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/utils/FeeCalculationUtils.kt create mode 100644 features/send-v2/api/src/test/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/utils/FeeCalculationUtilsTest.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2.kt create mode 100644 features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerTest.kt create mode 100644 features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2Test.kt create mode 100644 features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/TransformersComparisonTest.kt diff --git a/features/send-v2/api/build.gradle.kts b/features/send-v2/api/build.gradle.kts index 6a1ce98877..c1a6e00fc9 100644 --- a/features/send-v2/api/build.gradle.kts +++ b/features/send-v2/api/build.gradle.kts @@ -8,6 +8,10 @@ android { namespace = "com.tangem.features.send.v2.api" } +tasks.withType().configureEach { + useJUnitPlatform() +} + dependencies { /** Core */ implementation(projects.core.decompose) @@ -34,4 +38,14 @@ dependencies { /** Other */ implementation(deps.arrow.core) implementation(deps.kotlin.immutable.collections) + + // region Tests + testImplementation(deps.test.coroutine) + testImplementation(deps.test.junit5) + testRuntimeOnly(deps.test.junit5.engine) + testImplementation(deps.test.mockk) + testImplementation(deps.test.truth) + testImplementation(projects.common.test) + testImplementation(projects.domain.staking.models) + // endregion } \ No newline at end of file diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/utils/FeeCalculationUtils.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/utils/FeeCalculationUtils.kt new file mode 100644 index 0000000000..181267b68a --- /dev/null +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/utils/FeeCalculationUtils.kt @@ -0,0 +1,99 @@ +package com.tangem.features.send.v2.api.subcomponents.feeSelector.utils + +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.v2.api.entity.FeeItem +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.utils.extensions.isZero +import java.math.BigDecimal +import java.math.RoundingMode + +object FeeCalculationUtils { + + private val FEE_MAX_DIFF = BigDecimal("5") + + /** + * Check and calculates subtracted amount + */ + fun checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable: Boolean, + cryptoCurrencyStatus: CryptoCurrencyStatus, + amountValue: BigDecimal, + feeValue: BigDecimal, + reduceAmountBy: BigDecimal, + ): BigDecimal { + val balance = cryptoCurrencyStatus.value.amount ?: return amountValue + val isFeeCoverage = checkFeeCoverage( + isSubtractAvailable = isAmountSubtractAvailable, + balance = balance, + amountValue = amountValue, + feeValue = feeValue, + reduceAmountBy = reduceAmountBy, + ) + return if (isFeeCoverage) { + balance.minus(reduceAmountBy).minus(feeValue) + } else { + amountValue + } + } + + /** + * Check if custom fee is too high + */ + fun checkIfCustomFeeTooHigh(feeSelectorUM: FeeSelectorUM.Content): Pair { + val defaultResult = false to "" + + if (feeSelectorUM.selectedFeeItem !is FeeItem.Custom) return defaultResult + + val customAmount = feeSelectorUM.selectedFeeItem.customValues.firstOrNull() ?: return defaultResult + val multipleFees = feeSelectorUM.fees as? TransactionFee.Choosable ?: return defaultResult + val highValue = multipleFees.priority.amount.value ?: return defaultResult + + val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) + val diff = if (highValue > BigDecimal.ZERO) { + customValue / highValue + } else { + BigDecimal.ZERO + } + val isFeeTooHigh = diff > FEE_MAX_DIFF + return isFeeTooHigh to diff.parseBigDecimal(0, RoundingMode.HALF_UP) + } + + /** + * Check if custom fee is too low + */ + fun checkIfCustomFeeTooLow(feeSelectorUM: FeeSelectorUM.Content): Boolean { + if (feeSelectorUM.selectedFeeItem !is FeeItem.Custom) return false + + val multipleFees = feeSelectorUM.fees as? TransactionFee.Choosable ?: return false + val minimumValue = multipleFees.minimum.amount.value ?: return false + val customAmount = feeSelectorUM.selectedFeeItem.customValues.firstOrNull() ?: return false + val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) + + return minimumValue > customValue + } + + /** + * Checks if sending amount with fee is greater than balance + */ + fun checkFeeCoverage( + isSubtractAvailable: Boolean, + balance: BigDecimal, + amountValue: BigDecimal, + feeValue: BigDecimal, + reduceAmountBy: BigDecimal?, + ): Boolean { + if (!isSubtractAvailable) return false + val reducedBy = balance - (reduceAmountBy ?: BigDecimal.ZERO) + return reducedBy < amountValue + feeValue && reducedBy > feeValue && reducedBy >= amountValue + } + + /** + * Checks if fee exceeds fee paid currency balance + */ + fun checkExceedBalance(feeBalance: BigDecimal?, feeAmount: BigDecimal?): Boolean { + return feeAmount == null || feeBalance == null || feeAmount.isZero() || feeAmount > feeBalance + } +} \ No newline at end of file diff --git a/features/send-v2/api/src/test/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/utils/FeeCalculationUtilsTest.kt b/features/send-v2/api/src/test/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/utils/FeeCalculationUtilsTest.kt new file mode 100644 index 0000000000..59ea19d75c --- /dev/null +++ b/features/send-v2/api/src/test/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/utils/FeeCalculationUtilsTest.kt @@ -0,0 +1,188 @@ +package com.tangem.features.send.v2.api.subcomponents.feeSelector.utils + +import com.google.common.truth.Truth.assertThat +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import io.mockk.mockk +import org.junit.jupiter.api.Test +import java.math.BigDecimal + +class FeeCalculationUtilsTest { + + @Test + fun `GIVEN amount subtract available and fee coverage needed WHEN checkAndCalculateSubtractedAmount THEN returns subtracted amount`() { + // GIVEN + val isAmountSubtractAvailable = true + val cryptoCurrencyStatus = createCryptoCurrencyStatus(BigDecimal("6")) + val amountValue = BigDecimal("5") + val feeValue = BigDecimal("2") + val reduceAmountBy = BigDecimal("1") + + // WHEN + val result = FeeCalculationUtils.checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable = isAmountSubtractAvailable, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amountValue = amountValue, + feeValue = feeValue, + reduceAmountBy = reduceAmountBy, + ) + + // THEN + assertThat(result).isEqualTo(BigDecimal("3")) + } + + @Test + fun `GIVEN amount subtract not available WHEN checkAndCalculateSubtractedAmount THEN returns original amount`() { + // GIVEN + val isAmountSubtractAvailable = false + val cryptoCurrencyStatus = createCryptoCurrencyStatus(BigDecimal("10")) + val amountValue = BigDecimal("5") + val feeValue = BigDecimal("2") + val reduceAmountBy = BigDecimal("1") + + // WHEN + val result = FeeCalculationUtils.checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable = isAmountSubtractAvailable, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amountValue = amountValue, + feeValue = feeValue, + reduceAmountBy = reduceAmountBy, + ) + + // THEN + assertThat(result).isEqualTo(amountValue) + } + + @Test + fun `GIVEN sufficient balance for amount and fee WHEN checkAndCalculateSubtractedAmount THEN returns original amount`() { + // GIVEN + val isAmountSubtractAvailable = true + val cryptoCurrencyStatus = createCryptoCurrencyStatus(BigDecimal("10")) + val amountValue = BigDecimal("5") + val feeValue = BigDecimal("2") + val reduceAmountBy = BigDecimal("1") + + // WHEN + val result = FeeCalculationUtils.checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable = isAmountSubtractAvailable, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amountValue = amountValue, + feeValue = feeValue, + reduceAmountBy = reduceAmountBy, + ) + + // THEN + assertThat(result).isEqualTo(amountValue) + } + + @Test + fun `GIVEN no balance WHEN checkAndCalculateSubtractedAmount THEN returns original amount`() { + // GIVEN + val isAmountSubtractAvailable = true + val cryptoCurrencyStatus = createCryptoCurrencyStatus(null) + val amountValue = BigDecimal("5") + val feeValue = BigDecimal("2") + val reduceAmountBy = BigDecimal("1") + + // WHEN + val result = FeeCalculationUtils.checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable = isAmountSubtractAvailable, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amountValue = amountValue, + feeValue = feeValue, + reduceAmountBy = reduceAmountBy, + ) + + // THEN + assertThat(result).isEqualTo(amountValue) + } + + @Test + fun `GIVEN fee exceeds balance WHEN checkExceedBalance THEN returns true`() { + // GIVEN + val feeBalance = BigDecimal("5") + val feeAmount = BigDecimal("10") + + // WHEN + val result = FeeCalculationUtils.checkExceedBalance(feeBalance, feeAmount) + + // THEN + assertThat(result).isTrue() + } + + @Test + fun `GIVEN fee within balance WHEN checkExceedBalance THEN returns false`() { + // GIVEN + val feeBalance = BigDecimal("10") + val feeAmount = BigDecimal("5") + + // WHEN + val result = FeeCalculationUtils.checkExceedBalance(feeBalance, feeAmount) + + // THEN + assertThat(result).isFalse() + } + + @Test + fun `GIVEN null fee amount WHEN checkExceedBalance THEN returns true`() { + // GIVEN + val feeBalance = BigDecimal("10") + val feeAmount: BigDecimal? = null + + // WHEN + val result = FeeCalculationUtils.checkExceedBalance(feeBalance, feeAmount) + + // THEN + assertThat(result).isTrue() + } + + @Test + fun `GIVEN null fee balance WHEN checkExceedBalance THEN returns true`() { + // GIVEN + val feeBalance: BigDecimal? = null + val feeAmount = BigDecimal("5") + + // WHEN + val result = FeeCalculationUtils.checkExceedBalance(feeBalance, feeAmount) + + // THEN + assertThat(result).isTrue() + } + + @Test + fun `GIVEN zero fee amount WHEN checkExceedBalance THEN returns true`() { + // GIVEN + val feeBalance = BigDecimal("10") + val feeAmount = BigDecimal.ZERO + + // WHEN + val result = FeeCalculationUtils.checkExceedBalance(feeBalance, feeAmount) + + // THEN + assertThat(result).isTrue() + } + + private fun createCryptoCurrencyStatus(amount: BigDecimal?): CryptoCurrencyStatus { + val value = if (amount != null) { + CryptoCurrencyStatus.Loaded( + amount = amount, + fiatAmount = BigDecimal.ZERO, + fiatRate = BigDecimal.ZERO, + priceChange = BigDecimal.ZERO, + yieldBalance = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = mockk(relaxed = true), + sources = CryptoCurrencyStatus.Sources(), + ) + } else { + CryptoCurrencyStatus.NoAmount( + priceChange = BigDecimal.ZERO, + fiatRate = BigDecimal.ZERO, + ) + } + return CryptoCurrencyStatus( + currency = mockk(relaxed = true), + value = value, + ) + } +} \ No newline at end of file diff --git a/features/send-v2/impl/build.gradle.kts b/features/send-v2/impl/build.gradle.kts index dc97603af5..519db0736a 100644 --- a/features/send-v2/impl/build.gradle.kts +++ b/features/send-v2/impl/build.gradle.kts @@ -11,6 +11,10 @@ android { namespace = "com.tangem.features.send.v2.impl" } +tasks.withType().configureEach { + useJUnitPlatform() +} + dependencies { /** Api */ implementation(projects.features.sendV2.api) @@ -79,4 +83,13 @@ dependencies { /** DI */ implementation(deps.hilt.android) kapt(deps.hilt.kapt) + + // region Tests + testImplementation(deps.test.coroutine) + testImplementation(deps.test.junit5) + testRuntimeOnly(deps.test.junit5.engine) + testImplementation(deps.test.mockk) + testImplementation(deps.test.truth) + testImplementation(projects.common.test) + // endregion } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt index b079ab6810..f5bb31ace0 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt @@ -54,6 +54,7 @@ import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmIn import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmSendingStateTransformer import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmSentStateTransformer import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmationNotificationsTransformer +import com.tangem.features.send.v2.send.confirm.model.transformers.SendConfirmationNotificationsTransformerV2 import com.tangem.features.send.v2.send.ui.state.SendUM import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadListener import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadTrigger @@ -474,14 +475,25 @@ internal class SendConfirmModel @Inject constructor( ) _uiState.update { it.copy( - confirmUM = SendConfirmationNotificationsTransformer( - feeUM = uiState.value.feeUM, - amountUM = uiState.value.amountUM, - analyticsEventHandler = analyticsEventHandler, - cryptoCurrency = cryptoCurrencyStatus.currency, - appCurrency = appCurrency, - analyticsCategoryName = params.analyticsCategoryName, - ).transform(uiState.value.confirmUM), + confirmUM = if (uiState.value.isRedesignEnabled) { + SendConfirmationNotificationsTransformerV2( + feeSelectorUM = uiState.value.feeSelectorUM, + amountUM = uiState.value.amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrencyStatus.currency, + appCurrency = appCurrency, + analyticsCategoryName = params.analyticsCategoryName, + ).transform(uiState.value.confirmUM) + } else { + SendConfirmationNotificationsTransformer( + feeUM = uiState.value.feeUM, + amountUM = uiState.value.amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrencyStatus.currency, + appCurrency = appCurrency, + analyticsCategoryName = params.analyticsCategoryName, + ).transform(uiState.value.confirmUM) + }, ) } } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2.kt new file mode 100644 index 0000000000..1e432effb6 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2.kt @@ -0,0 +1,110 @@ +package com.tangem.features.send.v2.send.confirm.model.transformers + +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils +import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents +import com.tangem.features.send.v2.common.ui.state.ConfirmUM +import com.tangem.features.send.v2.common.utils.formatFooterFiatFee +import com.tangem.features.send.v2.common.utils.getTronTokenFeeSendingText +import com.tangem.features.send.v2.impl.R +import com.tangem.utils.transformer.Transformer +import kotlinx.collections.immutable.toPersistentList + +internal class SendConfirmationNotificationsTransformerV2( + private val feeSelectorUM: FeeSelectorUM, + private val amountUM: AmountState, + private val analyticsEventHandler: AnalyticsEventHandler, + private val cryptoCurrency: CryptoCurrency, + private val appCurrency: AppCurrency, + private val analyticsCategoryName: String, +) : Transformer { + override fun transform(prevState: ConfirmUM): ConfirmUM { + val state = prevState as? ConfirmUM.Content ?: return prevState + val feeSelectorUM = feeSelectorUM as? FeeSelectorUM.Content ?: return prevState + return state.copy( + sendingFooter = getSendingFooterText(), + notifications = buildList { + addTooHighNotification(feeSelectorUM) + addTooLowNotification(feeSelectorUM) + }.toPersistentList(), + ) + } + + private fun MutableList.addTooLowNotification(feeSelectorUM: FeeSelectorUM.Content) { + if (FeeCalculationUtils.checkIfCustomFeeTooLow(feeSelectorUM)) { + add(NotificationUM.Warning.FeeTooLow) + analyticsEventHandler.send( + CommonSendAnalyticEvents.NoticeTransactionDelays( + categoryName = analyticsCategoryName, + token = cryptoCurrency.symbol, + ), + ) + } + } + + private fun MutableList.addTooHighNotification(feeSelectorUM: FeeSelectorUM.Content) { + val (isFeeTooHigh, diff) = FeeCalculationUtils.checkIfCustomFeeTooHigh(feeSelectorUM) + if (isFeeTooHigh) { + add(NotificationUM.Warning.TooHigh(diff)) + } + } + + private fun getSendingFooterText(): TextReference { + val feeSelectorUM = feeSelectorUM as? FeeSelectorUM.Content + val amountUM = amountUM as? AmountState.Data + val fee = feeSelectorUM?.selectedFeeItem?.fee + + if (fee == null || amountUM == null) return TextReference.EMPTY + + val fiatAmountValue = amountUM.amountTextField.fiatAmount.value + val fiatFeeValue = feeSelectorUM.feeFiatRateUM?.rate?.let { fee.amount.value?.multiply(it) } + + val fiatSendingValue = if (feeSelectorUM.feeFiatRateUM != null) { + fiatFeeValue?.let { fiatAmountValue?.plus(it) } + } else { + fiatAmountValue + } + + val fiatSending = fiatSendingValue.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + } + val fiatFee = formatFooterFiatFee( + amount = fee.amount.copy(value = fiatFeeValue), + isFeeConvertibleToFiat = feeSelectorUM.feeFiatRateUM != null, + isFeeApproximate = feeSelectorUM.isFeeApproximate, + appCurrency = appCurrency, + ) + + return if (fee is Fee.Tron) { + getTronTokenFeeSendingText( + fee = fee, + fiatFee = fiatFee, + fiatSending = stringReference(fiatSending), + ) + } else { + resourceReference( + id = if (feeSelectorUM.feeFiatRateUM != null) { + R.string.send_summary_transaction_description + } else { + R.string.send_summary_transaction_description_no_fiat_fee + }, + formatArgs = wrappedList(fiatSending, fiatFee), + ) + } + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerTest.kt b/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerTest.kt new file mode 100644 index 0000000000..6eece69628 --- /dev/null +++ b/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerTest.kt @@ -0,0 +1,439 @@ +package com.tangem.features.send.v2.send.confirm.model.transformers + +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.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.AmountFieldModel +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.send.v2.common.ui.state.ConfirmUM +import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM +import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM +import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType +import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal +import java.math.BigInteger +import java.util.Locale +import org.junit.jupiter.api.BeforeAll + +class SendConfirmationNotificationsTransformerTest { + + private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true) + private val cryptoCurrency: CryptoCurrency = mockk(relaxed = true) + private val appCurrency = AppCurrency(name = "US Dollar", code = "USD", symbol = "$") + private val analyticsCategoryName = "test_category" + + @Test + fun `GIVEN non content state WHEN transform THEN returns original state`() = runTest { + // GIVEN + val feeUM: FeeUM = mockk(relaxed = true) + val amountUM: AmountState = mockk(relaxed = true) + val transformer = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState: ConfirmUM = ConfirmUM.Empty + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isEqualTo(initialState) + } + + @Test + fun `GIVEN fee UM not content WHEN transform THEN returns original state`() = runTest { + // GIVEN + val feeUM: FeeUM = FeeUM.Empty() + val amountUM: AmountState = mockk(relaxed = true) + val transformer = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState: ConfirmUM.Content = createTestConfirmUM() + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isEqualTo(initialState) + } + + @Test + fun `GIVEN normal fee WHEN transform THEN returns state with footer and no notifications`() = runTest { + // GIVEN + val feeUM = createNormalFeeUM() + val amountUM = createTestAmountUM() + val transformer = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState = createTestConfirmUM() + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isInstanceOf(ConfirmUM.Content::class.java) + val content = result as ConfirmUM.Content + assertThat(content.notifications).isEmpty() + assertThat(content.sendingFooter).isNotEqualTo(initialState.sendingFooter) + } + + @Test + fun `GIVEN fee too high WHEN transform THEN returns state with too high notification`() = runTest { + // GIVEN + val feeUM = createFeeTooHighUM() + val amountUM = createTestAmountUM() + val transformer = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState = createTestConfirmUM() + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isInstanceOf(ConfirmUM.Content::class.java) + val content = result as ConfirmUM.Content + assertThat(content.notifications).hasSize(1) + assertThat(content.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java) + } + + @Test + fun `GIVEN fee too low WHEN transform THEN returns state with too low notification`() = runTest { + // GIVEN + val feeUM = createFeeTooLowUM() + val amountUM = createTestAmountUM() + val transformer = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState = createTestConfirmUM() + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isInstanceOf(ConfirmUM.Content::class.java) + val content = result as ConfirmUM.Content + assertThat(content.notifications).hasSize(1) + assertThat(content.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java) + verify { analyticsEventHandler.send(any()) } + } + + @Test + fun `GIVEN both fee too high and too low WHEN transform THEN returns state with both notifications`() = runTest { + // GIVEN + val feeUM = createFeeTooHighAndTooLowUM() + val amountUM = createTestAmountUM() + val transformer = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState = createTestConfirmUM() + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isInstanceOf(ConfirmUM.Content::class.java) + val content = result as ConfirmUM.Content + assertThat(content.notifications).hasSize(2) + assertThat(content.notifications.any { it is NotificationUM.Warning.TooHigh }).isTrue() + assertThat(content.notifications.any { it is NotificationUM.Warning.FeeTooLow }).isTrue() + } + + private fun createTestConfirmUM(): ConfirmUM.Content { + return ConfirmUM.Content( + isPrimaryButtonEnabled = true, + walletName = mockk(relaxed = true), + isSending = false, + showTapHelp = false, + sendingFooter = mockk(relaxed = true), + notifications = persistentListOf(), + ) + } + + private fun createTestAmountUM(): AmountState.Data { + val cryptoAmount = com.tangem.domain.tokens.model.Amount( + currencySymbol = "SOL", + value = BigDecimal("1.5"), + decimals = 8, + ) + val fiatAmount = com.tangem.domain.tokens.model.Amount( + currencySymbol = "USD", + value = BigDecimal("50.00"), + decimals = 2, + ) + + return AmountState.Data( + isPrimaryButtonEnabled = true, + isRedesignEnabled = false, + title = mockk(relaxed = true), + availableBalance = mockk(relaxed = true), + tokenName = mockk(relaxed = true), + tokenIconState = mockk(relaxed = true), + segmentedButtonConfig = persistentListOf(), + selectedButton = 0, + isSegmentedButtonsEnabled = false, + amountTextField = AmountFieldModel( + value = "1.5", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + cryptoAmount = cryptoAmount, + fiatAmount = fiatAmount, + isFiatValue = false, + fiatValue = "50.00", + isFiatUnavailable = false, + isValuePasted = false, + onValuePastedTriggerDismiss = {}, + isError = false, + isWarning = false, + error = mockk(relaxed = true), + ), + appCurrency = appCurrency, + isEditingDisabled = false, + reduceAmountBy = BigDecimal.ZERO, + isIgnoreReduce = false, + ) + } + + private fun createNormalFeeUM(): FeeUM.Content { + val fee = Fee.Common( + amount = com.tangem.blockchain.common.Amount( + currencySymbol = "SOL", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Single(fee) + return FeeUM.Content( + feeSelectorUM = FeeSelectorUM.Content( + fees = transactionFee, + selectedType = FeeType.Market, + selectedFee = fee, + customValues = persistentListOf(), + nonce = BigInteger.ZERO, + ), + rate = BigDecimal("50000"), + isFeeConvertibleToFiat = true, + isFeeApproximate = false, + isTronToken = false, + isEditingDisabled = false, + isPrimaryButtonEnabled = true, + appCurrency = AppCurrency.Default, + isCustomSelected = false, + notifications = persistentListOf(), + ) + } + + private fun createFeeTooHighUM(): FeeUM.Content { + val priorityFee = Fee.Common( + amount = com.tangem.blockchain.common.Amount( + currencySymbol = "SOL", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = com.tangem.blockchain.common.Amount( + currencySymbol = "SOL", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val customFee = Fee.Common( + amount = com.tangem.blockchain.common.Amount( + currencySymbol = "SOL", + value = BigDecimal("0.01"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = minimumFee, + priority = priorityFee, + ) + return FeeUM.Content( + feeSelectorUM = FeeSelectorUM.Content( + fees = transactionFee, + selectedType = FeeType.Custom, + selectedFee = customFee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.01", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "SOL", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + nonce = BigInteger.ZERO, + ), + rate = BigDecimal("50000"), + isFeeConvertibleToFiat = true, + isFeeApproximate = false, + isTronToken = false, + isEditingDisabled = false, + isPrimaryButtonEnabled = true, + appCurrency = AppCurrency.Default, + isCustomSelected = false, + notifications = persistentListOf(), + ) + } + + private fun createFeeTooLowUM(): FeeUM.Content { + val fee = Fee.Common( + amount = com.tangem.blockchain.common.Amount( + currencySymbol = "SOL", + value = BigDecimal("0.0001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = com.tangem.blockchain.common.Amount( + currencySymbol = "SOL", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = fee, + priority = fee, + ) + return FeeUM.Content( + feeSelectorUM = FeeSelectorUM.Content( + fees = transactionFee, + selectedType = FeeType.Custom, + selectedFee = fee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.0001", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "SOL", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + nonce = BigInteger.ZERO, + ), + rate = BigDecimal("50000"), + isFeeConvertibleToFiat = true, + isFeeApproximate = false, + isTronToken = false, + isEditingDisabled = false, + isPrimaryButtonEnabled = true, + appCurrency = AppCurrency.Default, + isCustomSelected = false, + notifications = persistentListOf(), + ) + } + + private fun createFeeTooHighAndTooLowUM(): FeeUM.Content { + val priorityFee = Fee.Common( + amount = com.tangem.blockchain.common.Amount( + currencySymbol = "SOL", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = com.tangem.blockchain.common.Amount( + currencySymbol = "SOL", + value = BigDecimal("0.01"), + decimals = 8, + ), + ) + val customFee = Fee.Common( + amount = com.tangem.blockchain.common.Amount( + currencySymbol = "SOL", + value = BigDecimal("0.008"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = minimumFee, + priority = priorityFee, + ) + return FeeUM.Content( + feeSelectorUM = FeeSelectorUM.Content( + fees = transactionFee, + selectedType = FeeType.Custom, + selectedFee = customFee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.008", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "SOL", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + nonce = BigInteger.ZERO, + ), + rate = BigDecimal("50000"), + isFeeConvertibleToFiat = true, + isFeeApproximate = false, + isTronToken = false, + isEditingDisabled = false, + isPrimaryButtonEnabled = true, + appCurrency = AppCurrency.Default, + isCustomSelected = false, + notifications = persistentListOf(), + ) + } + + companion object { + @JvmStatic + @BeforeAll + fun setUpLocale() { + Locale.setDefault(Locale.US) + } + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2Test.kt b/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2Test.kt new file mode 100644 index 0000000000..55627b93e4 --- /dev/null +++ b/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/SendConfirmationNotificationsTransformerV2Test.kt @@ -0,0 +1,488 @@ +package com.tangem.features.send.v2.send.confirm.model.transformers + +import com.google.common.truth.Truth.assertThat +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.AmountFieldModel +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.tokens.model.Amount as DomainAmount +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.send.v2.api.entity.FeeItem +import com.tangem.features.send.v2.api.entity.FeeFiatRateUM +import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM +import com.tangem.features.send.v2.common.ui.state.ConfirmUM +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal +import java.math.BigInteger +import java.util.Locale +import org.junit.jupiter.api.BeforeAll + +class SendConfirmationNotificationsTransformerV2Test { + + private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true) + private val cryptoCurrency: CryptoCurrency = mockk(relaxed = true) + private val appCurrency = AppCurrency(name = "US Dollar", code = "USD", symbol = "$") + private val analyticsCategoryName = "test_category" + + @Test + fun `GIVEN non content state WHEN transform THEN returns original state`() = runTest { + // GIVEN + val feeSelectorUM: FeeSelectorUM = mockk(relaxed = true) + val amountUM: AmountState = mockk(relaxed = true) + val transformer = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState: ConfirmUM = ConfirmUM.Empty + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isEqualTo(initialState) + } + + @Test + fun `GIVEN fee selector not content WHEN transform THEN returns original state`() = runTest { + // GIVEN + val feeSelectorUM: FeeSelectorUM = FeeSelectorUM.Loading + val amountUM: AmountState = mockk(relaxed = true) + val transformer = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState = createTestConfirmUM() + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isEqualTo(initialState) + } + + @Test + fun `GIVEN normal fee WHEN transform THEN returns state with footer and no notifications`() = runTest { + // GIVEN + val feeSelectorUM = createNormalFeeSelectorUM() + val amountUM = createTestAmountUM() + val transformer = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState = createTestConfirmUM() + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isInstanceOf(ConfirmUM.Content::class.java) + val content = result as ConfirmUM.Content + assertThat(content.notifications).isEmpty() + assertThat(content.sendingFooter).isNotEqualTo(initialState.sendingFooter) + } + + @Test + fun `GIVEN fee too high WHEN transform THEN returns state with too high notification`() = runTest { + // GIVEN + val feeSelectorUM = createFeeTooHighUM() + val amountUM = createTestAmountUM() + val transformer = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState = createTestConfirmUM() + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isInstanceOf(ConfirmUM.Content::class.java) + val content = result as ConfirmUM.Content + assertThat(content.notifications).hasSize(1) + assertThat(content.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java) + } + + @Test + fun `GIVEN fee too low WHEN transform THEN returns state with too low notification`() = runTest { + // GIVEN + val feeSelectorUM = createFeeTooLowUM() + val amountUM = createTestAmountUM() + val transformer = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState = createTestConfirmUM() + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isInstanceOf(ConfirmUM.Content::class.java) + val content = result as ConfirmUM.Content + assertThat(content.notifications).hasSize(1) + assertThat(content.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java) + verify { analyticsEventHandler.send(any()) } + } + + @Test + fun `GIVEN both fee too high and too low WHEN transform THEN returns state with both notifications`() = runTest { + // GIVEN + val feeSelectorUM = createFeeTooHighAndTooLowUM() + val amountUM = createTestAmountUM() + val transformer = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + val initialState = createTestConfirmUM() + + // WHEN + val result = transformer.transform(initialState) + + // THEN + assertThat(result).isInstanceOf(ConfirmUM.Content::class.java) + val content = result as ConfirmUM.Content + assertThat(content.notifications).hasSize(2) + assertThat(content.notifications.any { it is NotificationUM.Warning.TooHigh }).isTrue() + assertThat(content.notifications.any { it is NotificationUM.Warning.FeeTooLow }).isTrue() + } + + private fun createTestConfirmUM(): ConfirmUM.Content { + return ConfirmUM.Content( + isPrimaryButtonEnabled = true, + walletName = mockk(relaxed = true), + isSending = false, + showTapHelp = false, + sendingFooter = mockk(relaxed = true), + notifications = persistentListOf(), + ) + } + + private fun createTestAmountUM(): AmountState.Data { + val cryptoAmount = DomainAmount( + currencySymbol = "SOL", + value = BigDecimal("1.5"), + decimals = 8, + ) + val fiatAmount = DomainAmount( + currencySymbol = "USD", + value = BigDecimal("50.00"), + decimals = 2, + ) + + return AmountState.Data( + isPrimaryButtonEnabled = true, + isRedesignEnabled = false, + title = mockk(relaxed = true), + availableBalance = mockk(relaxed = true), + tokenName = mockk(relaxed = true), + tokenIconState = mockk(relaxed = true), + segmentedButtonConfig = persistentListOf(), + selectedButton = 0, + isSegmentedButtonsEnabled = false, + amountTextField = AmountFieldModel( + value = "1.5", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + cryptoAmount = cryptoAmount, + fiatAmount = fiatAmount, + isFiatValue = false, + fiatValue = "50.00", + isFiatUnavailable = false, + isValuePasted = false, + onValuePastedTriggerDismiss = {}, + isError = false, + isWarning = false, + error = mockk(relaxed = true), + ), + appCurrency = appCurrency, + isEditingDisabled = false, + reduceAmountBy = BigDecimal.ZERO, + isIgnoreReduce = false, + ) + } + + private fun createNormalFeeSelectorUM(): FeeSelectorUM.Content { + val fee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Single(fee) + return FeeSelectorUM.Content( + fees = transactionFee, + feeItems = persistentListOf(FeeItem.Market(fee)), + selectedFeeItem = FeeItem.Market(fee), + isFeeApproximate = false, + feeFiatRateUM = FeeFiatRateUM( + rate = BigDecimal("50000"), + appCurrency = appCurrency, + ), + displayNonceInput = false, + nonce = BigInteger.ZERO, + onNonceChange = {}, + ) + } + + private fun createFeeTooHighUM(): FeeSelectorUM.Content { + val priorityFee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = minimumFee, + priority = priorityFee, + ) + return FeeSelectorUM.Content( + fees = transactionFee, + feeItems = persistentListOf( + FeeItem.Custom( + fee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.01"), + decimals = 8, + ), + ), + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.01", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "SOL", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + ), + selectedFeeItem = FeeItem.Custom( + fee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.01"), + decimals = 8, + ), + ), + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.01", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "SOL", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + isFeeApproximate = false, + feeFiatRateUM = FeeFiatRateUM( + rate = BigDecimal("50000"), + appCurrency = appCurrency, + ), + displayNonceInput = false, + nonce = BigInteger.ZERO, + onNonceChange = {}, + ) + } + + private fun createFeeTooLowUM(): FeeSelectorUM.Content { + val fee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.0001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = fee, + priority = fee, + ) + return FeeSelectorUM.Content( + fees = transactionFee, + feeItems = persistentListOf( + FeeItem.Custom( + fee = fee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.0001", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "SOL", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + ), + selectedFeeItem = FeeItem.Custom( + fee = fee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.0001", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "SOL", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + isFeeApproximate = false, + feeFiatRateUM = FeeFiatRateUM( + rate = BigDecimal("50000"), + appCurrency = appCurrency, + ), + displayNonceInput = false, + nonce = BigInteger.ZERO, + onNonceChange = {}, + ) + } + + private fun createFeeTooHighAndTooLowUM(): FeeSelectorUM.Content { + val priorityFee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.01"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = minimumFee, + priority = priorityFee, + ) + return FeeSelectorUM.Content( + fees = transactionFee, + feeItems = persistentListOf( + FeeItem.Custom( + fee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.008"), + decimals = 8, + ), + ), + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.008", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "SOL", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + ), + selectedFeeItem = FeeItem.Custom( + fee = Fee.Common( + amount = Amount( + currencySymbol = "SOL", + value = BigDecimal("0.008"), + decimals = 8, + ), + ), + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.008", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "SOL", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + isFeeApproximate = false, + feeFiatRateUM = FeeFiatRateUM( + rate = BigDecimal("50000"), + appCurrency = appCurrency, + ), + displayNonceInput = false, + nonce = BigInteger.ZERO, + onNonceChange = {}, + ) + } + + companion object { + @JvmStatic + @BeforeAll + fun setUpLocale() { + Locale.setDefault(Locale.US) + } + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/TransformersComparisonTest.kt b/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/TransformersComparisonTest.kt new file mode 100644 index 0000000000..706a11ca37 --- /dev/null +++ b/features/send-v2/impl/src/test/java/com/tangem/features/send/v2/send/confirm/model/transformers/TransformersComparisonTest.kt @@ -0,0 +1,847 @@ +package com.tangem.features.send.v2.send.confirm.model.transformers + +import com.google.common.truth.Truth.assertThat +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.AmountFieldModel +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.send.v2.api.entity.FeeSelectorUM as FeeSelectorUMV2 +import com.tangem.features.send.v2.common.ui.state.ConfirmUM +import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM +import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM +import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType +import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM +import com.tangem.features.send.v2.api.entity.FeeItem +import com.tangem.features.send.v2.api.entity.FeeFiatRateUM +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import kotlinx.collections.immutable.persistentListOf +import org.junit.jupiter.api.BeforeAll +import java.math.BigDecimal +import java.math.BigInteger +import java.util.Locale +import com.tangem.domain.tokens.model.Amount as DomainAmount + +class TransformersComparisonTest { + + private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true) + private val cryptoCurrency: CryptoCurrency = mockk(relaxed = true) + private val appCurrency = AppCurrency(name = "US Dollar", code = "USD", symbol = "$") + private val analyticsCategoryName = "test_category" + + @Test + fun `GIVEN equivalent input data WHEN both transformers transform THEN they produce equal ConfirmUM`() = runTest { + // GIVEN + val initialConfirmUM = createTestConfirmUM() + val amountUM = createTestAmountUM() + + val feeUM = createTestFeeUM() + val feeSelectorUMV2 = createTestFeeSelectorUMV2() + + // WHEN + val transformerV1 = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + + val transformerV2 = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUMV2, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + + val resultV1 = transformerV1.transform(initialConfirmUM) + val resultV2 = transformerV2.transform(initialConfirmUM) + + // THEN + assertThat(resultV1).isInstanceOf(ConfirmUM.Content::class.java) + assertThat(resultV2).isInstanceOf(ConfirmUM.Content::class.java) + + val contentV1 = resultV1 as ConfirmUM.Content + val contentV2 = resultV2 as ConfirmUM.Content + + assertThat(contentV1.notifications.size).isEqualTo(contentV2.notifications.size) + assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter) + assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled) + assertThat(contentV1.isSending).isEqualTo(contentV2.isSending) + assertThat(contentV1.showTapHelp).isEqualTo(contentV2.showTapHelp) + } + + @Test + fun `GIVEN fee too low WHEN both transformers transform THEN they produce equal notifications`() = runTest { + // GIVEN + val initialConfirmUM = createTestConfirmUM() + val amountUM = createTestAmountUM() + + val feeUM = createFeeTooLowUM() + val feeSelectorUMV2 = createFeeTooLowUMV2() + + // WHEN + val transformerV1 = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + + val transformerV2 = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUMV2, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + + val resultV1 = transformerV1.transform(initialConfirmUM) + val resultV2 = transformerV2.transform(initialConfirmUM) + + // THEN + assertThat(resultV1).isInstanceOf(ConfirmUM.Content::class.java) + assertThat(resultV2).isInstanceOf(ConfirmUM.Content::class.java) + + val contentV1 = resultV1 as ConfirmUM.Content + val contentV2 = resultV2 as ConfirmUM.Content + + assertThat(contentV1.notifications).hasSize(1) + assertThat(contentV2.notifications).hasSize(1) + assertThat(contentV1.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java) + assertThat(contentV2.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java) + assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter) + + // Verify analytics event was sent for both transformers + verify(exactly = 2) { analyticsEventHandler.send(any()) } + } + + @Test + fun `GIVEN fee too high WHEN both transformers transform THEN they produce equal notifications`() = runTest { + // GIVEN + val initialConfirmUM = createTestConfirmUM() + val amountUM = createTestAmountUM() + + val feeUM = createFeeTooHighUM() + val feeSelectorUMV2 = createFeeTooHighUMV2() + + // WHEN + val transformerV1 = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + + val transformerV2 = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUMV2, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + + val resultV1 = transformerV1.transform(initialConfirmUM) + val resultV2 = transformerV2.transform(initialConfirmUM) + + // THEN + assertThat(resultV1).isInstanceOf(ConfirmUM.Content::class.java) + assertThat(resultV2).isInstanceOf(ConfirmUM.Content::class.java) + + val contentV1 = resultV1 as ConfirmUM.Content + val contentV2 = resultV2 as ConfirmUM.Content + + assertThat(contentV1.notifications).hasSize(1) + assertThat(contentV2.notifications).hasSize(1) + assertThat(contentV1.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java) + assertThat(contentV2.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java) + + val tooHighV1 = contentV1.notifications.first() as NotificationUM.Warning.TooHigh + val tooHighV2 = contentV2.notifications.first() as NotificationUM.Warning.TooHigh + assertThat(tooHighV1.value).isEqualTo(tooHighV2.value) + assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter) + } + + @Test + fun `GIVEN fee both too high and too low WHEN both transformers transform THEN they produce equal notifications`() = + runTest { + // GIVEN + val initialConfirmUM = createTestConfirmUM() + val amountUM = createTestAmountUM() + + val feeUM = createFeeTooHighAndTooLowUM() + val feeSelectorUMV2 = createFeeTooHighAndTooLowUMV2() + + // WHEN + val transformerV1 = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + + val transformerV2 = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUMV2, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + + val resultV1 = transformerV1.transform(initialConfirmUM) + val resultV2 = transformerV2.transform(initialConfirmUM) + + // THEN + assertThat(resultV1).isInstanceOf(ConfirmUM.Content::class.java) + assertThat(resultV2).isInstanceOf(ConfirmUM.Content::class.java) + + val contentV1 = resultV1 as ConfirmUM.Content + val contentV2 = resultV2 as ConfirmUM.Content + + assertThat(contentV1.notifications).hasSize(2) + assertThat(contentV2.notifications).hasSize(2) + + assertThat(contentV1.notifications.any { it is NotificationUM.Warning.FeeTooLow }).isTrue() + assertThat(contentV1.notifications.any { it is NotificationUM.Warning.TooHigh }).isTrue() + assertThat(contentV2.notifications.any { it is NotificationUM.Warning.FeeTooLow }).isTrue() + assertThat(contentV2.notifications.any { it is NotificationUM.Warning.TooHigh }).isTrue() + + assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter) + + verify(exactly = 2) { analyticsEventHandler.send(any()) } + } + + @Test + fun `GIVEN normal fee WHEN both transformers transform THEN they produce equal notifications`() = runTest { + // GIVEN + val initialConfirmUM = createTestConfirmUM() + val amountUM = createTestAmountUM() + + val feeUM = createNormalFeeUM() + val feeSelectorUMV2 = createNormalFeeSelectorUMV2() + + // WHEN + val transformerV1 = SendConfirmationNotificationsTransformer( + feeUM = feeUM, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + + val transformerV2 = SendConfirmationNotificationsTransformerV2( + feeSelectorUM = feeSelectorUMV2, + amountUM = amountUM, + analyticsEventHandler = analyticsEventHandler, + cryptoCurrency = cryptoCurrency, + appCurrency = appCurrency, + analyticsCategoryName = analyticsCategoryName, + ) + + val resultV1 = transformerV1.transform(initialConfirmUM) + val resultV2 = transformerV2.transform(initialConfirmUM) + + // THEN + assertThat(resultV1).isInstanceOf(ConfirmUM.Content::class.java) + assertThat(resultV2).isInstanceOf(ConfirmUM.Content::class.java) + + val contentV1 = resultV1 as ConfirmUM.Content + val contentV2 = resultV2 as ConfirmUM.Content + + assertThat(contentV1.notifications).isEmpty() + assertThat(contentV2.notifications).isEmpty() + assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter) + } + + private fun createTestConfirmUM(): ConfirmUM.Content { + return ConfirmUM.Content( + isPrimaryButtonEnabled = true, + walletName = mockk(relaxed = true), + isSending = false, + showTapHelp = false, + sendingFooter = mockk(relaxed = true), + notifications = persistentListOf(), + ) + } + + private fun createTestAmountUM(): AmountState.Data { + val cryptoAmount = DomainAmount( + currencySymbol = "TST", + value = BigDecimal("1.5"), + decimals = 8, + ) + val fiatAmount = DomainAmount( + currencySymbol = "USD", + value = BigDecimal("50.00"), + decimals = 2, + ) + + return AmountState.Data( + isPrimaryButtonEnabled = true, + isRedesignEnabled = false, + title = mockk(relaxed = true), + availableBalance = mockk(relaxed = true), + tokenName = mockk(relaxed = true), + tokenIconState = mockk(relaxed = true), + segmentedButtonConfig = persistentListOf(), + selectedButton = 0, + isSegmentedButtonsEnabled = false, + amountTextField = AmountFieldModel( + value = "1.5", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + cryptoAmount = cryptoAmount, + fiatAmount = fiatAmount, + isFiatValue = false, + fiatValue = "50.00", + isFiatUnavailable = false, + isValuePasted = false, + onValuePastedTriggerDismiss = {}, + isError = false, + isWarning = false, + error = mockk(relaxed = true), + ), + appCurrency = appCurrency, + isEditingDisabled = false, + reduceAmountBy = BigDecimal.ZERO, + isIgnoreReduce = false, + ) + } + + private fun createTestFeeUM(): FeeUM.Content { + val fee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Single(fee) + return FeeUM.Content( + feeSelectorUM = FeeSelectorUM.Content( + fees = transactionFee, + selectedType = FeeType.Market, + selectedFee = fee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.001", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + nonce = BigInteger.ZERO, + ), + rate = BigDecimal("50000"), + isFeeConvertibleToFiat = true, + isFeeApproximate = false, + isTronToken = false, + isEditingDisabled = false, + isPrimaryButtonEnabled = true, + appCurrency = AppCurrency.Default, + isCustomSelected = false, + notifications = persistentListOf(), + ) + } + + private fun createTestFeeSelectorUMV2(): FeeSelectorUMV2.Content { + val fee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Single(fee) + return FeeSelectorUMV2.Content( + fees = transactionFee, + feeItems = persistentListOf( + FeeItem.Market(fee), + ), + selectedFeeItem = FeeItem.Market(fee), + isFeeApproximate = false, + feeFiatRateUM = FeeFiatRateUM( + rate = BigDecimal("50000"), + appCurrency = appCurrency, + ), + displayNonceInput = false, + nonce = BigInteger.ZERO, + onNonceChange = {}, + ) + } + + private fun createFeeTooLowUM(): FeeUM.Content { + val fee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.0001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = fee, + priority = fee, + ) + return FeeUM.Content( + feeSelectorUM = FeeSelectorUM.Content( + fees = transactionFee, + selectedType = FeeType.Custom, + selectedFee = fee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.0001", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + nonce = BigInteger.ZERO, + ), + rate = BigDecimal("50000"), + isFeeConvertibleToFiat = true, + isFeeApproximate = false, + isTronToken = false, + isEditingDisabled = false, + isPrimaryButtonEnabled = true, + appCurrency = AppCurrency.Default, + isCustomSelected = true, + notifications = persistentListOf(), + ) + } + + private fun createFeeTooLowUMV2(): FeeSelectorUMV2.Content { + val fee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.0001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = fee, + priority = fee, + ) + return FeeSelectorUMV2.Content( + fees = transactionFee, + feeItems = persistentListOf( + FeeItem.Custom( + fee = fee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.0001", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + ), + selectedFeeItem = FeeItem.Custom( + fee = fee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.0001", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + isFeeApproximate = false, + feeFiatRateUM = FeeFiatRateUM( + rate = BigDecimal("50000"), + appCurrency = appCurrency, + ), + displayNonceInput = false, + nonce = BigInteger.ZERO, + onNonceChange = {}, + ) + } + + private fun createFeeTooHighUM(): FeeUM.Content { + val priorityFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val customFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.01"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = minimumFee, + priority = priorityFee, + ) + return FeeUM.Content( + feeSelectorUM = FeeSelectorUM.Content( + fees = transactionFee, + selectedType = FeeType.Custom, + selectedFee = customFee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.01", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + nonce = BigInteger.ZERO, + ), + rate = BigDecimal("50000"), + isFeeConvertibleToFiat = true, + isFeeApproximate = false, + isTronToken = false, + isEditingDisabled = false, + isPrimaryButtonEnabled = true, + appCurrency = AppCurrency.Default, + isCustomSelected = true, + notifications = persistentListOf(), + ) + } + + private fun createFeeTooHighUMV2(): FeeSelectorUMV2.Content { + val priorityFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val customFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.01"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = minimumFee, + priority = priorityFee, + ) + return FeeSelectorUMV2.Content( + fees = transactionFee, + feeItems = persistentListOf( + FeeItem.Custom( + fee = customFee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.01", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + ), + selectedFeeItem = FeeItem.Custom( + fee = customFee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.01", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + isFeeApproximate = false, + feeFiatRateUM = FeeFiatRateUM( + rate = BigDecimal("50000"), + appCurrency = appCurrency, + ), + displayNonceInput = false, + nonce = BigInteger.ZERO, + onNonceChange = {}, + ) + } + + private fun createFeeTooHighAndTooLowUM(): FeeUM.Content { + val priorityFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.01"), + decimals = 8, + ), + ) + val customFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.008"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = minimumFee, + priority = priorityFee, + ) + return FeeUM.Content( + feeSelectorUM = FeeSelectorUM.Content( + fees = transactionFee, + selectedType = FeeType.Custom, + selectedFee = customFee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.008", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + nonce = BigInteger.ZERO, + ), + rate = BigDecimal("50000"), + isFeeConvertibleToFiat = true, + isFeeApproximate = false, + isTronToken = false, + isEditingDisabled = false, + isPrimaryButtonEnabled = true, + appCurrency = AppCurrency.Default, + isCustomSelected = true, + notifications = persistentListOf(), + ) + } + + private fun createFeeTooHighAndTooLowUMV2(): FeeSelectorUMV2.Content { + val priorityFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val minimumFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.01"), + decimals = 8, + ), + ) + val customFee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.008"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Choosable( + minimum = minimumFee, + normal = minimumFee, + priority = priorityFee, + ) + return FeeSelectorUMV2.Content( + fees = transactionFee, + feeItems = persistentListOf( + FeeItem.Custom( + fee = customFee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.008", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + ), + selectedFeeItem = FeeItem.Custom( + fee = customFee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.008", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + ), + isFeeApproximate = false, + feeFiatRateUM = FeeFiatRateUM( + rate = BigDecimal("50000"), + appCurrency = appCurrency, + ), + displayNonceInput = false, + nonce = BigInteger.ZERO, + onNonceChange = {}, + ) + } + + private fun createNormalFeeUM(): FeeUM.Content { + val fee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Single(fee) + return FeeUM.Content( + feeSelectorUM = FeeSelectorUM.Content( + fees = transactionFee, + selectedType = FeeType.Market, + selectedFee = fee, + customValues = persistentListOf( + CustomFeeFieldUM( + value = "0.001", + onValueChange = {}, + keyboardOptions = mockk(relaxed = true), + keyboardActions = mockk(relaxed = true), + symbol = "TST", + decimals = 8, + title = mockk(relaxed = true), + footer = mockk(relaxed = true), + ), + ), + nonce = BigInteger.ZERO, + ), + rate = BigDecimal("50000"), + isFeeConvertibleToFiat = true, + isFeeApproximate = false, + isTronToken = false, + isEditingDisabled = false, + isPrimaryButtonEnabled = true, + appCurrency = AppCurrency.Default, + isCustomSelected = false, + notifications = persistentListOf(), + ) + } + + private fun createNormalFeeSelectorUMV2(): FeeSelectorUMV2.Content { + val fee = Fee.Common( + amount = Amount( + currencySymbol = "TST", + value = BigDecimal("0.001"), + decimals = 8, + ), + ) + val transactionFee = TransactionFee.Single(fee) + return FeeSelectorUMV2.Content( + fees = transactionFee, + feeItems = persistentListOf( + FeeItem.Market(fee), + ), + selectedFeeItem = FeeItem.Market(fee), + isFeeApproximate = false, + feeFiatRateUM = FeeFiatRateUM( + rate = BigDecimal("50000"), + appCurrency = appCurrency, + ), + displayNonceInput = false, + nonce = BigInteger.ZERO, + onNonceChange = {}, + ) + } + + companion object { + @JvmStatic + @BeforeAll + fun setUpLocale() { + Locale.setDefault(Locale.US) + } + } +} \ No newline at end of file