Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-04 00:14:29 -07:00
parent 92bf1fa521
commit 0adc20d9aa
3 changed files with 154 additions and 8 deletions

View file

@ -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<Test>().configureEach {
useJUnitPlatform()
}

View file

@ -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
}
}

View file

@ -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),
)
}