diff --git a/features/tangempay/details/impl/build.gradle.kts b/features/tangempay/details/impl/build.gradle.kts index 0aee66703c..84b5260423 100644 --- a/features/tangempay/details/impl/build.gradle.kts +++ b/features/tangempay/details/impl/build.gradle.kts @@ -61,4 +61,15 @@ dependencies { /** Other */ implementation(deps.kotlin.immutable.collections) + + /** Test */ + testRuntimeOnly(deps.test.junit5.engine) + testImplementation(deps.test.junit5) + testImplementation(deps.test.mockk) + testImplementation(deps.test.truth) + testImplementation(deps.test.coroutine) +} + +tasks.withType().configureEach { + useJUnitPlatform() } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModel.kt index 5f3941838b..7672c8911c 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModel.kt @@ -112,6 +112,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor( } private fun onAmountChange(newValue: String) { + if (newValue.toBigDecimalOrNull() == null) return uiState.update { state -> state.copy( amountFieldModel = state.amountFieldModel.copy(value = newValue), @@ -121,9 +122,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor( } private fun onPresetClick(preset: BigDecimal) { - val amount = uiState.value.amountFieldModel.value.toBigDecimalOrNull() ?: return - val newAmount = if (preset == BigDecimal.ZERO) BigDecimal.ZERO else amount + preset - onAmountChange(newAmount.stripTrailingZeros().toPlainString()) + onAmountChange(preset.stripTrailingZeros().toPlainString()) } private fun onSubmitClick() { @@ -154,7 +153,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor( private fun isValid(value: String): Boolean { val amount = value.toBigDecimalOrNull() ?: return false - return amount >= BigDecimal.ZERO + return amount >= MIN_LIMIT } private fun buildSubtitle(maxLimit: BigDecimal?, currency: Currency): TextReference { @@ -163,7 +162,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor( id = R.string.tangempay_card_limit_setup_amount_subtitle, formatArgs = WrappedList( listOf( - BigDecimal.ZERO.format { fiat(currency.currencyCode, currency.symbol).anyDecimals(0) }, + MIN_LIMIT.format { fiat(currency.currencyCode, currency.symbol).anyDecimals(0) }, ), ), ) @@ -172,7 +171,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor( id = R.string.tangempay_daily_limit_hint, formatArgs = WrappedList( listOf( - BigDecimal.ZERO.format { fiat(currency.currencyCode, currency.symbol).anyDecimals(0) }, + MIN_LIMIT.format { fiat(currency.currencyCode, currency.symbol).anyDecimals(0) }, maxLimit.format { fiat(currency.currencyCode, currency.symbol).anyDecimals(0) }, ), ), @@ -181,15 +180,19 @@ internal class TangemPayCardLimitSetupModel @Inject constructor( } private fun buildPresets(currency: Currency) = listOf( - BigDecimal.ZERO, + MIN_LIMIT, BigDecimal("5000"), BigDecimal("10000"), BigDecimal("25000"), ).map { preset -> val label = preset.format { fiat(currency.currencyCode, currency.symbol).anyDecimals(0) } TangemPayCardLimitSetupUM.LimitPresetUM( - label = if (preset == BigDecimal.ZERO) "0" else "+$label", + label = label, onClick = { onPresetClick(preset) }, ) }.toPersistentList() + + companion object { + private val MIN_LIMIT = BigDecimal.ONE + } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModelTest.kt b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModelTest.kt new file mode 100644 index 0000000000..3213e42482 --- /dev/null +++ b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModelTest.kt @@ -0,0 +1,132 @@ +package com.tangem.features.tangempay.limit.setup + +import com.google.common.truth.Truth.assertThat +import com.tangem.core.decompose.model.MutableParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.account.PaymentAccountStatusValue +import com.tangem.domain.models.pay.TangemPayCard +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayDetailsConfig +import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier +import com.tangem.domain.pay.usecase.SetTangemPayCardLimitUseCase +import com.tangem.domain.visa.model.TangemPayCardFrozenState +import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.flowOf +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class TangemPayCardLimitSetupModelTest { + + private val cardId = "test_card_id" + private val userWalletId = UserWalletId("123") + + private val router: Router = mockk(relaxed = true) + private val uiMessageSender: UiMessageSender = mockk(relaxed = true) + private val setLimitUseCase: SetTangemPayCardLimitUseCase = mockk(relaxed = true) + private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier = mockk() + + private val params = TangemPayDetailsContainerComponent.Params( + userWalletId = userWalletId, + config = TangemPayDetailsConfig( + customerId = "customer1", + cardId = cardId, + isPinSet = false, + cardFrozenState = TangemPayCardFrozenState.Unfrozen, + cardNumberEnd = "1234", + chainId = 1, + isTangemPayDeactivated = false, + displayName = null, + ), + ) + + private val testCard = TangemPayCard( + id = cardId, + hasPinCode = false, + displayName = null, + limit = null, + isFrozen = false, + lastDigits = "1234", + ) + + private val loadedStatus: PaymentAccountStatusValue.Loaded = mockk(relaxed = true) { + every { source } returns StatusSource.ACTUAL + every { cards } returns listOf(testCard) + every { currencyCode } returns "USD" + } + + private val paymentStatus: AccountStatus.Payment = mockk(relaxed = true) { + every { value } returns loadedStatus + } + + private fun createModel(): TangemPayCardLimitSetupModel { + every { paymentAccountStatusSupplier.invoke(userWalletId) } returns flowOf(paymentStatus) + return TangemPayCardLimitSetupModel( + paramsContainer = MutableParamsContainer(params), + dispatchers = TestingCoroutineDispatcherProvider(), + router = router, + paymentAccountStatusSupplier = paymentAccountStatusSupplier, + setTangemPayCardLimitUseCase = setLimitUseCase, + uiMessageSender = uiMessageSender, + ) + } + + @ParameterizedTest + @MethodSource("provideTestCases") + fun `GIVEN amount WHEN changed THEN submit button reflects validity`( + amount: String, + expectedEnabled: Boolean, + ) { + val model = createModel() + + model.uiState.value.amountFieldModel.onValueChange(amount) + + assertThat(model.uiState.value.isSubmitButtonEnabled).isEqualTo(expectedEnabled) + model.onDestroy() + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class Presets { + + @Test + fun `WHEN first preset clicked THEN amount set to MIN_LIMIT`() { + val model = createModel() + + model.uiState.value.presets.first().onClick() + + assertThat(model.uiState.value.amountFieldModel.value).isEqualTo("1") + model.onDestroy() + } + + @Test + fun `WHEN last preset clicked THEN amount set to 5000`() { + val model = createModel() + + model.uiState.value.presets.last().onClick() + + assertThat(model.uiState.value.amountFieldModel.value).isEqualTo("25000") + model.onDestroy() + } + } + + private fun provideTestCases() = listOf( + Arguments.of("0", false), + Arguments.of("0.99", false), + Arguments.of("1", true), + Arguments.of("100", true), + Arguments.of("-1", false), + Arguments.of("", false), + Arguments.of("abc", false), + ) +} \ No newline at end of file