Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-29 23:38:42 +05:00
parent d9bc010cd0
commit 07ee441996
7 changed files with 729 additions and 0 deletions

View file

@ -28,6 +28,7 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import kotlinx.coroutines.test.runTest
import org.junit.Ignore
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@ -1037,6 +1038,7 @@ internal class SwapInteractorImplFindBestQuoteTest : SwapInteractorImplTestBase(
assertThat(required.isResetApproval).isTrue()
}
@Ignore("Check in final integrated approve test")
@Test
fun `should fallback to no-permission state when yield-module proxy address is unresolvable`() = runTest {
// Given — yield store returns null (e.g. network unreachable on first resolve)

View file

@ -0,0 +1,70 @@
package com.tangem.feature.swap.model
import com.tangem.domain.models.currency.CryptoCurrency
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
/**
* Tests for [SwapModel.approvalFullCallback] (the full give-approval flow callback).
*
* Covers the synchronously-verifiable side effects. The quote-reload path
* ([SwapModel] `startLoadingQuotesFromLastState`) short-circuits because `dataState.amount` is null
* in these setups, keeping the callbacks side-effect-bounded.
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal class SwapModelApprovalFullCallbackTest : SwapModelTestBase() {
@BeforeEach
fun setUp() {
setUpBase()
}
@Test
fun `GIVEN token from-currency WHEN onApproveDone THEN adds contract address to in-progress`() = runTest {
val model = createModel()
val token = mockk<CryptoCurrency.Token>(relaxed = true) {
every { contractAddress } returns "0xContract"
}
model.dataState = model.dataState.copy(
fromSwapCurrencyStatus = swapCurrencyStatus(currency = token),
)
model.approvalFullCallback.onApproveDone()
verify(exactly = 1) { allowPermissionsHandler.addAddressToInProgress("0xContract") }
}
@Test
fun `GIVEN null from-currency WHEN onApproveDone THEN does not add address to in-progress`() = runTest {
val model = createModel()
model.dataState = model.dataState.copy(fromSwapCurrencyStatus = null)
model.approvalFullCallback.onApproveDone()
verify(exactly = 0) { allowPermissionsHandler.addAddressToInProgress(any()) }
}
@Test
fun `GIVEN failure WHEN onApproveFailed THEN sends alert message`() = runTest {
val model = createModel()
model.approvalFullCallback.onApproveFailed()
verify(exactly = 1) { messageSender.send(any()) }
}
@Test
fun `WHEN onApproveClick THEN no interactions`() = runTest {
val model = createModel()
// onApproveClick is intentionally a no-op; assert it does not crash.
model.approvalFullCallback.onApproveClick()
verify(exactly = 0) { allowPermissionsHandler.addAddressToInProgress(any()) }
}
}

View file

@ -0,0 +1,111 @@
package com.tangem.feature.swap.model
import com.google.common.truth.Truth.assertThat
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import io.mockk.coVerify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
/**
* Tests for [SwapModel.approvalSelectorCallback] (the [PermissionDataState.PermissionSettings]
* approval-type selector).
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal class SwapModelApprovalSelectorCallbackTest : SwapModelTestBase() {
@BeforeEach
fun setUp() {
setUpBase()
}
@Test
fun `GIVEN no current loaded state WHEN onApproveTypeSelected THEN no state change and no reload`() = runTest {
val model = createModel()
val before = model.dataState
model.approvalSelectorCallback.onApproveTypeSelected("0xSpender", ApproveType.UNLIMITED)
assertThat(model.dataState).isEqualTo(before)
coVerify(exactly = 0) { feeSelectorReloadTrigger.triggerLoadingState() }
coVerify(exactly = 0) { feeSelectorReloadTrigger.triggerUpdate() }
}
@Test
fun `GIVEN permission is not PermissionSettings WHEN onApproveTypeSelected THEN no reload`() = runTest {
val provider = swapProvider()
val model = createModel()
model.dataState = model.dataState.copy(
selectedProvider = provider,
lastLoadedSwapStates = mapOf(provider to quotesLoadedState(provider, PermissionDataState.Empty)),
)
model.approvalSelectorCallback.onApproveTypeSelected("0xSpender", ApproveType.UNLIMITED)
coVerify(exactly = 0) { feeSelectorReloadTrigger.triggerLoadingState() }
coVerify(exactly = 0) { feeSelectorReloadTrigger.triggerUpdate() }
}
@Test
fun `GIVEN same approve type WHEN onApproveTypeSelected THEN no state change and no reload`() = runTest {
val provider = swapProvider()
val model = createModel()
model.dataState = model.dataState.copy(
selectedProvider = provider,
lastLoadedSwapStates = mapOf(
provider to quotesLoadedState(provider, permissionSettings(type = ApproveType.LIMITED)),
),
)
val before = model.dataState
model.approvalSelectorCallback.onApproveTypeSelected("0xSpender", ApproveType.LIMITED)
assertThat(model.dataState).isEqualTo(before)
coVerify(exactly = 0) { feeSelectorReloadTrigger.triggerLoadingState() }
coVerify(exactly = 0) { feeSelectorReloadTrigger.triggerUpdate() }
}
@Test
fun `GIVEN different approve type WHEN onApproveTypeSelected THEN updates state and triggers reload`() = runTest {
val provider = swapProvider()
val model = createModel()
model.dataState = model.dataState.copy(
selectedProvider = provider,
lastLoadedSwapStates = mapOf(
provider to quotesLoadedState(provider, permissionSettings(type = ApproveType.LIMITED)),
),
)
model.approvalSelectorCallback.onApproveTypeSelected("0xSpender", ApproveType.UNLIMITED)
val updated = model.dataState.getCurrentLoadedSwapState()
val settings = updated?.permissionState as? PermissionDataState.PermissionSettings
assertThat(settings).isNotNull()
assertThat(settings!!.type).isEqualTo(ApproveType.UNLIMITED)
assertThat(model.feeSelectorRepository.state.value).isEqualTo(FeeSelectorUM.Loading)
coVerify(exactly = 1) { feeSelectorReloadTrigger.triggerLoadingState() }
coVerify(exactly = 1) { feeSelectorReloadTrigger.triggerUpdate() }
}
@Test
fun `GIVEN any state WHEN onCancelClick THEN no state change and no reload`() = runTest {
val provider = swapProvider()
val model = createModel()
model.dataState = model.dataState.copy(
selectedProvider = provider,
lastLoadedSwapStates = mapOf(
provider to quotesLoadedState(provider, permissionSettings()),
),
)
val before = model.dataState
model.approvalSelectorCallback.onCancelClick()
assertThat(model.dataState).isEqualTo(before)
coVerify(exactly = 0) { feeSelectorReloadTrigger.triggerLoadingState() }
coVerify(exactly = 0) { feeSelectorReloadTrigger.triggerUpdate() }
}
}

View file

@ -0,0 +1,142 @@
package com.tangem.feature.swap.model
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.swap.models.SwapPermissionUM
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
/**
* Tests for [SwapModel.getApprovalParams].
*
* Builds [com.tangem.features.approval.api.GiveApprovalComponent.Params] only when:
* - `uiState.permissionUM` is [SwapPermissionUM.PermissionRequired]
* - `fromSwapCurrencyStatus` is non-null
* - `feePaidCryptoCurrency` is non-null
*/
internal class SwapModelGetApprovalParamsTest : SwapModelTestBase() {
@BeforeEach
fun setUp() {
setUpBase()
}
@Test
fun `GIVEN permissionUM is not PermissionRequired THEN returns null`() {
val model = createModel()
// default uiState has permissionUM == Empty
model.dataState = model.dataState.copy(
fromSwapCurrencyStatus = swapCurrencyStatus(),
feePaidCryptoCurrency = mockk(relaxed = true),
)
assertThat(model.getApprovalParams()).isNull()
}
@Test
fun `GIVEN null fromSwapCurrencyStatus THEN returns null`() {
val model = createModel()
model.uiState = model.uiState.copy(
permissionUM = SwapPermissionUM.PermissionRequired(isResetApproval = false, spenderAddress = "0xSpender"),
)
model.dataState = model.dataState.copy(
fromSwapCurrencyStatus = null,
feePaidCryptoCurrency = mockk(relaxed = true),
)
assertThat(model.getApprovalParams()).isNull()
}
@Test
fun `GIVEN null feePaidCryptoCurrency THEN returns null`() {
val model = createModel()
model.uiState = model.uiState.copy(
permissionUM = SwapPermissionUM.PermissionRequired(isResetApproval = false, spenderAddress = "0xSpender"),
)
model.dataState = model.dataState.copy(
fromSwapCurrencyStatus = swapCurrencyStatus(),
feePaidCryptoCurrency = null,
)
assertThat(model.getApprovalParams()).isNull()
}
@Test
fun `GIVEN give-approval prerequisites met THEN builds params with give footer`() {
val model = createModel()
val coldWallet = mockk<UserWallet.Cold>(relaxed = true)
val currency = mockk<CryptoCurrency>(relaxed = true) { every { symbol } returns "DAI" }
val status = mockk<CryptoCurrencyStatus>(relaxed = true)
val from = swapCurrencyStatus(wallet = coldWallet, status = status, currency = currency)
val feeStatus: CryptoCurrencyStatus = mockk(relaxed = true)
val provider = swapProvider(name = "1inch")
model.uiState = model.uiState.copy(
permissionUM = SwapPermissionUM.PermissionRequired(isResetApproval = false, spenderAddress = "0xSpender"),
)
model.dataState = model.dataState.copy(
fromSwapCurrencyStatus = from,
feePaidCryptoCurrency = feeStatus,
selectedProvider = provider,
amount = "12.5",
)
val params = model.getApprovalParams()
assertThat(params).isNotNull()
assertThat(params!!.userWalletId).isEqualTo(userWalletId)
assertThat(params.cryptoCurrencyStatus).isEqualTo(status)
assertThat(params.feeCryptoCurrencyStatus).isEqualTo(feeStatus)
assertThat(params.amount).isEqualTo("12.5")
assertThat(params.spenderAddress).isEqualTo("0xSpender")
assertThat(params.isResetApproval).isFalse()
// Cold wallet -> not hold-to-confirm
assertThat(params.isHoldToConfirm).isFalse()
assertThat(params.callback).isSameInstanceAs(model.approvalFullCallback)
}
@Test
fun `GIVEN hot wallet THEN isHoldToConfirm is true`() {
val model = createModel()
val hotWallet = mockk<UserWallet.Hot>(relaxed = true)
val from = swapCurrencyStatus(wallet = hotWallet)
model.uiState = model.uiState.copy(
permissionUM = SwapPermissionUM.PermissionRequired(isResetApproval = false, spenderAddress = "0xSpender"),
)
model.dataState = model.dataState.copy(
fromSwapCurrencyStatus = from,
feePaidCryptoCurrency = mockk(relaxed = true),
amount = "1",
)
val params = model.getApprovalParams()
assertThat(params).isNotNull()
assertThat(params!!.isHoldToConfirm).isTrue()
}
@Test
fun `GIVEN reset approval THEN isResetApproval is true`() {
val model = createModel()
val from = swapCurrencyStatus()
model.uiState = model.uiState.copy(
permissionUM = SwapPermissionUM.PermissionRequired(isResetApproval = true, spenderAddress = "0xSpender"),
)
model.dataState = model.dataState.copy(
fromSwapCurrencyStatus = from,
feePaidCryptoCurrency = mockk(relaxed = true),
amount = "1",
)
val params = model.getApprovalParams()
assertThat(params).isNotNull()
assertThat(params!!.isResetApproval).isTrue()
}
}

View file

@ -0,0 +1,76 @@
package com.tangem.feature.swap.model
import com.google.common.truth.Truth.assertThat
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
import com.tangem.feature.swap.domain.models.ui.SwapState
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
/**
* Tests for [SwapModel.getSelectApprovalTypeParams].
*
* Builds [com.tangem.features.approval.api.SelectApprovalTypeComponent.Params] from the current
* `fromSwapCurrencyStatus` and the provider's loaded swap state. Returns `null` when prerequisites
* are missing. `initialApproveType` is taken from [PermissionDataState.PermissionSettings.type] or
* falls back to [ApproveType.LIMITED].
*/
internal class SwapModelGetSelectApprovalTypeParamsTest : SwapModelTestBase() {
@BeforeEach
fun setUp() {
setUpBase()
}
@Test
fun `GIVEN null fromSwapCurrencyStatus THEN returns null`() {
val provider = swapProvider()
val model = createModel()
model.dataState = model.dataState.copy(
fromSwapCurrencyStatus = null,
lastLoadedSwapStates = mapOf(provider to quotesLoadedState(provider, permissionSettings())),
)
assertThat(model.getSelectApprovalTypeParams(provider)).isNull()
}
@Test
fun `GIVEN provider state is not QuotesLoadedState THEN returns null`() {
val provider = swapProvider()
val notLoaded: SwapState.EmptyAmountState = mockk(relaxed = true)
val model = createModel()
model.dataState = model.dataState.copy(
fromSwapCurrencyStatus = swapCurrencyStatus(),
lastLoadedSwapStates = mapOf(provider to notLoaded),
)
assertThat(model.getSelectApprovalTypeParams(provider)).isNull()
}
@Test
fun `GIVEN PermissionSettings present THEN uses its type as initialApproveType`() {
val provider = swapProvider(name = "ParaSwap")
val currency = mockk<CryptoCurrency>(relaxed = true) { every { symbol } returns "USDT" }
val status = mockk<CryptoCurrencyStatus>(relaxed = true)
val from = swapCurrencyStatus(status = status, currency = currency)
val model = createModel()
model.dataState = model.dataState.copy(
fromSwapCurrencyStatus = from,
lastLoadedSwapStates = mapOf(
provider to quotesLoadedState(provider, permissionSettings(type = ApproveType.UNLIMITED)),
),
)
val params = model.getSelectApprovalTypeParams(provider)
assertThat(params).isNotNull()
assertThat(params!!.userWalletId).isEqualTo(userWalletId)
assertThat(params.cryptoCurrencyStatus).isEqualTo(status)
assertThat(params.initialApproveType).isEqualTo(ApproveType.UNLIMITED)
assertThat(params.callback).isSameInstanceAs(model.approvalSelectorCallback)
}
}

View file

@ -0,0 +1,92 @@
package com.tangem.feature.swap.model
import com.google.common.truth.Truth.assertThat
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
import io.mockk.every
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
/**
* Tests for [SwapModel.isPermissionNotNeeded].
*
* The getter is `true` when the current loaded swap state needs no approval:
* - always when [PermissionDataState.Empty]
* - additionally for [PermissionDataState.PermissionSettings] when the integrated-approve toggle is ON
*/
internal class SwapModelIsPermissionNotNeededTest : SwapModelTestBase() {
@BeforeEach
fun setUp() {
setUpBase()
}
private fun modelWithPermissionState(
permissionState: PermissionDataState,
isIntegratedApproveEnabled: Boolean,
): SwapModel {
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns isIntegratedApproveEnabled
val provider = swapProvider()
val model = createModel()
model.dataState = model.dataState.copy(
selectedProvider = provider,
lastLoadedSwapStates = mapOf(provider to quotesLoadedState(provider, permissionState)),
)
return model
}
@Test
fun `GIVEN toggle OFF and Empty permission THEN permission is not needed`() {
val model = modelWithPermissionState(PermissionDataState.Empty, isIntegratedApproveEnabled = false)
assertThat(model.isPermissionNotNeeded).isTrue()
}
@Test
fun `GIVEN toggle OFF and PermissionSettings THEN permission is needed`() {
val model = modelWithPermissionState(permissionSettings(), isIntegratedApproveEnabled = false)
assertThat(model.isPermissionNotNeeded).isFalse()
}
@Test
fun `GIVEN toggle OFF and PermissionRequired THEN permission is needed`() {
val model = modelWithPermissionState(
PermissionDataState.PermissionRequired(isResetApproval = false, spenderAddress = "0x"),
isIntegratedApproveEnabled = false,
)
assertThat(model.isPermissionNotNeeded).isFalse()
}
@Test
fun `GIVEN toggle ON and Empty permission THEN permission is not needed`() {
val model = modelWithPermissionState(PermissionDataState.Empty, isIntegratedApproveEnabled = true)
assertThat(model.isPermissionNotNeeded).isTrue()
}
@Test
fun `GIVEN toggle ON and PermissionSettings THEN permission is not needed`() {
val model = modelWithPermissionState(permissionSettings(), isIntegratedApproveEnabled = true)
assertThat(model.isPermissionNotNeeded).isTrue()
}
@Test
fun `GIVEN toggle ON and PermissionRequired THEN permission is needed`() {
val model = modelWithPermissionState(
PermissionDataState.PermissionRequired(isResetApproval = false, spenderAddress = "0x"),
isIntegratedApproveEnabled = true,
)
assertThat(model.isPermissionNotNeeded).isFalse()
}
@Test
fun `GIVEN no current loaded state THEN permission is needed`() {
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns true
val model = createModel()
assertThat(model.isPermissionNotNeeded).isFalse()
}
}

View file

@ -0,0 +1,236 @@
package com.tangem.feature.swap.model
import arrow.core.right
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.core.analytics.api.AnalyticsErrorHandler
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.model.MutableParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.common.routing.AppRouter
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.stories.ShouldShowStoriesUseCase
import com.tangem.domain.swap.models.SwapCurrencyStatus
import com.tangem.domain.swap.usecase.CalculateAmountUseCase
import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase
import com.tangem.domain.tangempay.TangemPayWithdrawUseCase
import com.tangem.domain.tangempay.TangemPayWithdrawWithSwapUseCase
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.feature.swap.domain.GetSwapUiModeUseCase
import com.tangem.feature.swap.domain.SetSwapUiModeUseCase
import com.tangem.feature.swap.domain.AllowPermissionsHandler
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType
import com.tangem.feature.swap.domain.models.domain.SwapProvider
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
import com.tangem.feature.swap.domain.models.ui.SwapState
import com.tangem.feature.swap.domain.transfer.SwapTransferInteractor
import com.tangem.feature.swap.ui.transfer.SwapTransferStateBuilder
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
import com.tangem.features.swap.SwapComponent
import com.tangem.features.swap.SwapFeatureToggles
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
/**
* Shared test infrastructure for [SwapModel] unit tests.
*
* Wires every constructor dependency as a relaxed MockK and stubs the init-block calls so that
* constructing the model has no observable side effects. See `swap-model-test-plan.md` for details.
*/
internal abstract class SwapModelTestBase {
protected val router: Router = mockk(relaxed = true)
protected val appRouter: AppRouter = mockk(relaxed = true)
protected val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
protected val analyticsErrorEventHandler: AnalyticsErrorHandler = mockk(relaxed = true)
protected val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase = mockk(relaxed = true)
protected val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase = mockk(relaxed = true)
protected val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase =
mockk(relaxed = true)
protected val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase = mockk(relaxed = true)
protected val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase = mockk(relaxed = true)
protected val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase = mockk(relaxed = true)
protected val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase =
mockk(relaxed = true)
protected val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase = mockk(relaxed = true)
protected val shouldShowStoriesUseCase: ShouldShowStoriesUseCase = mockk(relaxed = true)
protected val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase = mockk(relaxed = true)
protected val swapInteractor: SwapInteractor = mockk(relaxed = true)
protected val swapTransferInteractor: SwapTransferInteractor = mockk(relaxed = true)
protected val swapTransferStateBuilder: SwapTransferStateBuilder = mockk(relaxed = true)
protected val urlOpener: UrlOpener = mockk(relaxed = true)
protected val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase = mockk(relaxed = true)
protected val getPaymentAccountCryptoCurrencyStatusUseCase: GetPaymentAccountCryptoCurrencyStatusUseCase =
mockk(relaxed = true)
protected val tangemPayWithdrawWithSwapUseCase: TangemPayWithdrawWithSwapUseCase = mockk(relaxed = true)
protected val isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork = mockk(relaxed = true)
protected val feeSelectorReloadTrigger: FeeSelectorReloadTrigger = mockk(relaxed = true)
protected val getTangemPayCustomerIdUseCase: GetTangemPayCustomerIdUseCase = mockk(relaxed = true)
protected val appsFlyerStore: AppsFlyerStore = mockk(relaxed = true)
protected val messageSender: UiMessageSender = mockk(relaxed = true)
protected val initialCurrenciesResolver: InitialCurrenciesResolver = mockk(relaxed = true)
protected val allowPermissionsHandler: AllowPermissionsHandler = mockk(relaxed = true)
protected val swapFeatureToggles: SwapFeatureToggles = mockk(relaxed = true)
protected val getSwapUiModeUseCase: GetSwapUiModeUseCase = mockk(relaxed = true)
protected val setSwapUiModeUseCase: SetSwapUiModeUseCase = mockk(relaxed = true)
protected val calculateAmountUseCase: CalculateAmountUseCase = mockk(relaxed = true)
private val chooseTokenBridgeFactory: ChooseTokenBridge.Factory = mockk(relaxed = true)
private val getUserCountryUseCase: GetUserCountryUseCase = mockk(relaxed = true)
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase = mockk(relaxed = true)
protected val userWalletId = UserWalletId(stringValue = "0123456789ABCDEF")
/** Stubs init-block calls so [createModel] has no side effects. Call from `@BeforeEach`. */
protected fun setUpBase() {
val bridge = mockk<ChooseTokenBridge>(relaxed = true) {
every { onCurrencyChosen } returns Channel()
every { onClose } returns Channel()
}
every { chooseTokenBridgeFactory.create(any(), any(), any()) } returns bridge
every { getUserCountryUseCase.invokeSync() } returns UserCountry.Other("US").right()
every { getBalanceHidingSettingsUseCase.invoke() } returns emptyFlow()
coEvery { isAccountsModeEnabledUseCase.invokeSync() } returns false
coEvery { shouldShowStoriesUseCase.invokeSync(any()) } returns false
coEvery { initialCurrenciesResolver.invoke(any(), any(), any(), any()) } returns (null to null)
every { getSelectedAppCurrencyUseCase.invoke() } returns emptyFlow()
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns true
}
protected fun createParams(): SwapComponent.Params = SwapComponent.Params(
userWalletId = userWalletId,
cryptoCurrency = null,
screenSource = "Test",
)
@Suppress("LongMethod")
protected fun createModel(): SwapModel = SwapModel(
paramsContainer = MutableParamsContainer(createParams()),
getUserCountryUseCase = getUserCountryUseCase,
getBalanceHidingSettingsUseCase = getBalanceHidingSettingsUseCase,
chooseTokenBridgeFactory = chooseTokenBridgeFactory,
router = router,
appRouter = appRouter,
dispatchers = TestingCoroutineDispatcherProvider(),
analyticsEventHandler = analyticsEventHandler,
analyticsErrorEventHandler = analyticsErrorEventHandler,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
updateDelayedCurrencyStatusUseCase = updateDelayedCurrencyStatusUseCase,
getFeePaidCryptoCurrencyStatusSyncUseCase = getFeePaidCryptoCurrencyStatusSyncUseCase,
getWalletMetaInfoUseCase = getWalletMetaInfoUseCase,
saveBlockchainErrorUseCase = saveBlockchainErrorUseCase,
sendFeedbackEmailUseCase = sendFeedbackEmailUseCase,
getMinimumTransactionAmountSyncUseCase = getMinimumTransactionAmountSyncUseCase,
getExplorerTransactionUrlUseCase = getExplorerTransactionUrlUseCase,
shouldShowStoriesUseCase = shouldShowStoriesUseCase,
isAccountsModeEnabledUseCase = isAccountsModeEnabledUseCase,
swapInteractor = swapInteractor,
swapTransferInteractor = swapTransferInteractor,
swapTransferStateBuilder = swapTransferStateBuilder,
urlOpener = urlOpener,
getAccountCurrencyStatusUseCase = getAccountCurrencyStatusUseCase,
getPaymentAccountCryptoCurrencyStatusUseCase = getPaymentAccountCryptoCurrencyStatusUseCase,
tangemPayWithdrawWithSwapUseCase = tangemPayWithdrawWithSwapUseCase,
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
feeSelectorReloadTrigger = feeSelectorReloadTrigger,
getTangemPayCustomerIdUseCase = getTangemPayCustomerIdUseCase,
appsFlyerStore = appsFlyerStore,
messageSender = messageSender,
initialCurrenciesResolver = initialCurrenciesResolver,
allowPermissionsHandler = allowPermissionsHandler,
swapFeatureToggles = swapFeatureToggles,
getSwapUiModeUseCase = getSwapUiModeUseCase,
setSwapUiModeUseCase = setSwapUiModeUseCase,
calculateAmountUseCase = calculateAmountUseCase,
)
// region builders
protected fun swapProvider(
id: String = "provider-1",
name: String = "1inch",
type: ExchangeProviderType = ExchangeProviderType.DEX,
): SwapProvider = SwapProvider(
providerId = id,
name = name,
type = type,
imageLarge = "",
termsOfUse = null,
privacyPolicy = null,
slippage = null,
)
protected fun permissionSettings(
type: ApproveType = ApproveType.LIMITED,
spender: String = "0xSpender",
): PermissionDataState.PermissionSettings = PermissionDataState.PermissionSettings(
type = type,
spenderAddress = spender,
)
protected fun quotesLoadedState(
provider: SwapProvider,
permissionState: PermissionDataState = PermissionDataState.Empty,
): SwapState.QuotesLoadedState = mockk(relaxed = true) {
every { swapProvider } returns provider
every { this@mockk.permissionState } returns permissionState
every {
copy(
fromTokenInfo = any(),
toTokenInfo = any(),
priceImpact = any(),
preparedSwapConfigState = any(),
permissionState = any(),
swapDataModel = any(),
currencyCheck = any(),
validationResult = any(),
minAdaValue = any(),
swapProvider = any(),
)
} answers {
quotesLoadedState(
provider = provider,
permissionState = arg(4),
)
}
}
protected fun swapCurrencyStatus(
wallet: UserWallet = mockk(relaxed = true),
status: CryptoCurrencyStatus = mockk(relaxed = true),
currency: CryptoCurrency = mockk(relaxed = true),
): SwapCurrencyStatus = mockk(relaxed = true) {
every { userWallet } returns wallet
every { this@mockk.status } returns status
every { this@mockk.currency } returns currency
}
// endregion
}