Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-14 00:14:47 +05:00
parent 5f1a711c8c
commit bd13529d69
17 changed files with 550 additions and 77 deletions

View file

@ -130,6 +130,15 @@ internal class TangemPayCardPageScreenComponent(
onFieldCopied = model::onVaFieldCopied,
),
)
is TangemPayCardNavigation.VaBankingDetailsError -> TangemPayVaBankingDetailsErrorComponent(
appComponentContext = context,
params = TangemPayVaBankingDetailsErrorComponent.Params(
userWalletId = navigation.userWalletId,
onDismiss = model.bottomSheetNavigation::dismiss,
onContactSupport = model::onContactSupportClicked,
onResolved = model::onVaBankingDetailsResolved,
),
)
is TangemPayCardNavigation.Receive -> tokenReceiveComponentFactory.create(
context = context,
params = TokenReceiveComponent.Params(

View file

@ -159,6 +159,15 @@ internal class TangemPayDetailsComponent(
onFieldCopied = model::onVaFieldCopied,
),
)
is TangemPayDetailsNavigation.VaBankingDetailsError -> TangemPayVaBankingDetailsErrorComponent(
appComponentContext = context,
params = TangemPayVaBankingDetailsErrorComponent.Params(
userWalletId = navigation.userWalletId,
onDismiss = model.bottomSheetNavigation::dismiss,
onContactSupport = model::onContactSupportClicked,
onResolved = model::onVaBankingDetailsResolved,
),
)
is TangemPayDetailsNavigation.IssueAdditionalCard -> TangemPayIssueAdditionalCardComponent(
appComponentContext = context,
params = TangemPayIssueAdditionalCardComponent.Params(

View file

@ -0,0 +1,44 @@
package com.tangem.features.tangempay.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.account.VirtualAccountOnramp
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.tangempay.model.TangemPayVaBankingDetailsErrorModel
import com.tangem.features.tangempay.ui.TangemPayVaBankingDetailsErrorBottomSheet
/**
* Error bottom sheet shown when VA bank credentials fail to load ([VirtualAccountOnramp.BankCredentialsError]).
*
* "Try again" re-fetches the payment account status while showing a loader on the button; on success the
* resolved on-ramp is handed back via [Params.onResolved] (the parent opens the bank-transfer sheet), otherwise
* the error stays visible with the loader cleared.
*/
internal class TangemPayVaBankingDetailsErrorComponent(
appComponentContext: AppComponentContext,
params: Params,
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
private val model: TangemPayVaBankingDetailsErrorModel = getOrCreateModel(params = params)
override fun dismiss() {
model.onDismiss()
}
@Composable
override fun BottomSheet() {
val state by model.uiState.collectAsStateWithLifecycle()
TangemPayVaBankingDetailsErrorBottomSheet(state = state)
}
data class Params(
val userWalletId: UserWalletId,
val onDismiss: () -> Unit,
val onContactSupport: () -> Unit,
val onResolved: (VirtualAccountOnramp) -> Unit,
)
}

View file

@ -50,6 +50,11 @@ internal interface TangemPayModelModule {
@ClassKey(TangemPayVirtualAccountDepositModel::class)
fun bindTangemPayVirtualAccountDepositModel(model: TangemPayVirtualAccountDepositModel): Model
@Binds
@IntoMap
@ClassKey(TangemPayVaBankingDetailsErrorModel::class)
fun bindTangemPayVaBankingDetailsErrorModel(model: TangemPayVaBankingDetailsErrorModel): Model
@Binds
@IntoMap
@ClassKey(TangemPayViewPinModel::class)

View file

@ -48,6 +48,11 @@ internal sealed class TangemPayCardNavigation {
val bankCredentials: BankCredentials,
) : TangemPayCardNavigation()
@Serializable
data class VaBankingDetailsError(
val userWalletId: UserWalletId,
) : TangemPayCardNavigation()
@Serializable
data class Receive(val config: TokenReceiveConfig) : TangemPayCardNavigation()
}

View file

@ -39,6 +39,11 @@ internal sealed class TangemPayDetailsNavigation {
val bankCredentials: BankCredentials,
) : TangemPayDetailsNavigation()
@Serializable
data class VaBankingDetailsError(
val userWalletId: UserWalletId,
) : TangemPayDetailsNavigation()
@Serializable
data class TransactionDetails(
val transaction: TangemPayTxHistoryItem,

View file

@ -0,0 +1,17 @@
package com.tangem.features.tangempay.entity
import androidx.compose.runtime.Immutable
/**
* UI state for the "couldn't load banking details" bottom sheet (VA MVP0, TWI-1638).
*
* @property isRetryLoading whether the "Try again" button shows a loader while the payment account status
* is being re-fetched. While `true` both actions are disabled.
*/
@Immutable
internal data class TangemPayVaBankingDetailsErrorUM(
val isRetryLoading: Boolean,
val onRetryClick: () -> Unit,
val onContactSupportClick: () -> Unit,
val onDismiss: () -> Unit,
)

View file

@ -7,6 +7,7 @@ import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -24,6 +25,9 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_arrow_refresh_20
import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.feedback.models.WalletMetaInfo
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.account.AccountStatus
@ -72,11 +76,12 @@ import com.tangem.core.ui.R as CoreUiR
@ModelScoped
internal class TangemPayCardPageModel @Inject constructor(
paramsContainer: ParamsContainer,
paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val analytics: AnalyticsEventHandler,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val cardDetailsRepository: TangemPayCardDetailsRepository,
private val uiMessageSender: UiMessageSender,
private val changeCardFrozenStateUseCase: ChangeCardFrozenStateUseCase,
@ -488,7 +493,16 @@ internal class TangemPayCardPageModel @Inject constructor(
override fun onClickBankTransfer() {
val loaded = currentStatus.value.ifLoadedOrNull { it } ?: return
val onramp = loaded.virtualAccount ?: return
when (val onramp = loaded.virtualAccount) {
null -> return
is VirtualAccountOnramp.BankCredentialsError -> showVaBankingDetailsError()
is VirtualAccountOnramp.Available,
VirtualAccountOnramp.Eligible,
-> openVirtualAccountDeposit(onramp, loaded)
}
}
private fun openVirtualAccountDeposit(onramp: VirtualAccountOnramp, loaded: PaymentAccountStatusValue.Loaded) {
analytics.send(TangemPayAnalyticsEvents.VaTopupButtonClicked())
bottomSheetNavigation.dismiss()
bottomSheetNavigation.activate(
@ -500,6 +514,31 @@ internal class TangemPayCardPageModel @Inject constructor(
)
}
private fun showVaBankingDetailsError() {
bottomSheetNavigation.dismiss()
bottomSheetNavigation.activate(
TangemPayCardNavigation.VaBankingDetailsError(userWalletId = userWalletId),
)
}
fun onVaBankingDetailsResolved(onramp: VirtualAccountOnramp) {
val loaded = currentStatus.value.ifLoadedOrNull { it } ?: return
openVirtualAccountDeposit(onramp, loaded)
}
fun onContactSupportClicked() {
analytics.send(Basic.ButtonSupport(source = AnalyticsParam.ScreensSources.TangemPay))
val customerId = currentStatus.value.ifLoadedOrNull { it.customerId } ?: return
modelScope.launch {
sendFeedbackEmailUseCase.invoke(
type = FeedbackEmailType.Visa.FeatureIsBeta(
walletMetaInfo = WalletMetaInfo(userWalletId = userWalletId),
customerId = customerId,
),
)
}
}
fun onVirtualAccountOrderCreated() {
analytics.send(TangemPayAnalyticsEvents.VaSuccessScreenActivation())
bottomSheetNavigation.dismiss()

View file

@ -67,7 +67,7 @@ import javax.inject.Inject
@ModelScoped
internal class TangemPayDetailsModel @Inject constructor(
paramsContainer: ParamsContainer,
paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
override val dispatchers: CoroutineDispatcherProvider,
private val analytics: AnalyticsEventHandler,
private val router: Router,
@ -313,7 +313,16 @@ internal class TangemPayDetailsModel @Inject constructor(
override fun onClickBankTransfer() {
val loaded = currentStatus.value.ifLoadedOrNull { it } ?: return
val onramp = loaded.virtualAccount ?: return
when (val onramp = loaded.virtualAccount) {
null -> return
is VirtualAccountOnramp.BankCredentialsError -> showVaBankingDetailsError()
is VirtualAccountOnramp.Available,
VirtualAccountOnramp.Eligible,
-> openVirtualAccountDeposit(onramp, loaded)
}
}
private fun openVirtualAccountDeposit(onramp: VirtualAccountOnramp, loaded: PaymentAccountStatusValue.Loaded) {
analytics.send(TangemPayAnalyticsEvents.VaTopupButtonClicked())
bottomSheetNavigation.dismiss()
bottomSheetNavigation.activate(
@ -325,6 +334,18 @@ internal class TangemPayDetailsModel @Inject constructor(
)
}
private fun showVaBankingDetailsError() {
bottomSheetNavigation.dismiss()
bottomSheetNavigation.activate(
TangemPayDetailsNavigation.VaBankingDetailsError(userWalletId = userWalletId),
)
}
fun onVaBankingDetailsResolved(onramp: VirtualAccountOnramp) {
val loaded = currentStatus.value.ifLoadedOrNull { it } ?: return
openVirtualAccountDeposit(onramp, loaded)
}
fun onVirtualAccountOrderCreated() {
analytics.send(TangemPayAnalyticsEvents.VaSuccessScreenActivation())
bottomSheetNavigation.dismiss()

View file

@ -0,0 +1,63 @@
package com.tangem.features.tangempay.model
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.models.account.VirtualAccountOnramp
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.features.tangempay.components.TangemPayVaBankingDetailsErrorComponent
import com.tangem.features.tangempay.entity.TangemPayVaBankingDetailsErrorUM
import com.tangem.features.tangempay.utils.ifLoadedOrNull
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@Stable
@ModelScoped
internal class TangemPayVaBankingDetailsErrorModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
) : Model() {
private val params = paramsContainer.require<TangemPayVaBankingDetailsErrorComponent.Params>()
val uiState: StateFlow<TangemPayVaBankingDetailsErrorUM>
field = MutableStateFlow(
TangemPayVaBankingDetailsErrorUM(
isRetryLoading = false,
onRetryClick = ::onRetryClick,
onContactSupportClick = params.onContactSupport,
onDismiss = ::onDismiss,
),
)
fun onDismiss() {
params.onDismiss()
}
private fun onRetryClick() {
if (uiState.value.isRetryLoading) return
uiState.update { it.copy(isRetryLoading = true) }
modelScope.launch {
paymentAccountStatusFetcher.invoke(params.userWalletId)
val onramp = paymentAccountStatusSupplier.invoke(params.userWalletId)
.first()
.ifLoadedOrNull { it.virtualAccount }
when (onramp) {
is VirtualAccountOnramp.Available,
VirtualAccountOnramp.Eligible,
-> params.onResolved(onramp)
// Still failing (BankCredentialsError) or unavailable — keep the sheet, clear the loader.
else -> uiState.update { it.copy(isRetryLoading = false) }
}
}
}
}

View file

@ -81,6 +81,9 @@ internal class TangemPayVirtualAccountDepositModel @Inject constructor(
analytics.send(TangemPayAnalyticsEvents.VaShowDetailsFirstTimeClicked())
createVirtualAccountOrder()
}
// Error onramp is intercepted before this sheet opens (a dedicated error sheet is shown instead);
// the branch only keeps the `when` exhaustive.
VirtualAccountOnramp.BankCredentialsError -> onDismiss()
}
}

View file

@ -0,0 +1,171 @@
package com.tangem.features.tangempay.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.ds2.button.Close
import com.tangem.core.ui.ds2.button.TangemButton
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_error_28
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayVaBankingDetailsErrorUM
@Composable
internal fun TangemPayVaBankingDetailsErrorBottomSheet(state: TangemPayVaBankingDetailsErrorUM) {
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = state.onDismiss,
content = TangemBottomSheetConfigContent.Empty,
),
type = TangemBottomSheetType.Modal,
containerColor = TangemTheme.colors3.bg.secondary,
title = {
TangemTopBar(
type = TangemTopBarType.BottomSheet,
title = null,
endContent = { TangemButton.Close(onClick = state.onDismiss) },
)
},
content = { _ -> Content(state) },
)
}
@Composable
private fun Content(state: TangemPayVaBankingDetailsErrorUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(horizontal = TangemTheme.dimens2.x4)
.padding(bottom = TangemTheme.dimens2.x4),
horizontalAlignment = Alignment.CenterHorizontally,
) {
WarningIcon(modifier = Modifier.padding(top = TangemTheme.dimens2.x4))
TitleText(
text = resourceReference(R.string.tangempay_va_banking_details_error_title),
modifier = Modifier.padding(top = TangemTheme.dimens2.x8),
)
SubtitleText(
text = resourceReference(R.string.tangempay_va_banking_details_error_description),
modifier = Modifier.padding(top = TangemTheme.dimens2.x2),
)
TangemButton(
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens2.x8),
text = resourceReference(R.string.common_contact_support),
variant = TangemButton.Variant.Secondary,
size = TangemButton.Size.X12,
isEnabled = !state.isRetryLoading,
onClick = state.onContactSupportClick,
)
TangemButton(
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens2.x2),
text = resourceReference(R.string.alert_button_try_again),
variant = TangemButton.Variant.Primary,
size = TangemButton.Size.X12,
isLoading = state.isRetryLoading,
isEnabled = !state.isRetryLoading,
onClick = state.onRetryClick,
)
}
}
@Composable
private fun WarningIcon(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.size(TangemTheme.dimens2.x20)
.clip(CircleShape)
.background(TangemTheme.colors3.bg.status.warningSubtle),
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens2.x7),
imageVector = Icons.ic_error_28,
contentDescription = null,
tint = TangemTheme.colors3.icon.status.warning,
)
}
}
@Composable
private fun TitleText(text: TextReference, modifier: Modifier = Modifier) {
Text(
modifier = modifier.fillMaxWidth(),
text = text.resolveReference(),
style = TangemTheme.typography3.heading.small,
color = TangemTheme.colors3.text.primary,
textAlign = TextAlign.Center,
)
}
@Composable
private fun SubtitleText(text: TextReference, modifier: Modifier = Modifier) {
Text(
modifier = modifier.fillMaxWidth(),
text = text.resolveReference(),
style = TangemTheme.typography3.subheading.medium,
color = TangemTheme.colors3.text.secondary,
textAlign = TextAlign.Center,
)
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemPayVaBankingDetailsErrorPreview(
@PreviewParameter(VaBankingDetailsErrorPreviewProvider::class) state: TangemPayVaBankingDetailsErrorUM,
) {
TangemThemePreviewRedesign {
Content(
state = state,
modifier = Modifier.background(TangemTheme.colors3.bg.secondary),
)
}
}
private class VaBankingDetailsErrorPreviewProvider :
CollectionPreviewParameterProvider<TangemPayVaBankingDetailsErrorUM>(
collection = listOf(
TangemPayVaBankingDetailsErrorUM(
isRetryLoading = false,
onRetryClick = {},
onContactSupportClick = {},
onDismiss = {},
),
TangemPayVaBankingDetailsErrorUM(
isRetryLoading = true,
onRetryClick = {},
onContactSupportClick = {},
onDismiss = {},
),
),
)

View file

@ -0,0 +1,132 @@
package com.tangem.features.tangempay.model
import arrow.core.Either
import arrow.core.right
import com.google.common.truth.Truth.assertThat
import com.tangem.core.decompose.model.MutableParamsContainer
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.account.VirtualAccountOnramp
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.features.tangempay.components.TangemPayVaBankingDetailsErrorComponent
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.Called
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
internal class TangemPayVaBankingDetailsErrorModelTest {
private val userWalletId = UserWalletId("1234567890ABCDEF")
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher = mockk()
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier = mockk()
private val onDismiss: () -> Unit = mockk(relaxed = true)
private val onContactSupport: () -> Unit = mockk(relaxed = true)
private val onResolved: (VirtualAccountOnramp) -> Unit = mockk(relaxed = true)
@BeforeEach
fun resetMocks() {
clearMocks(paymentAccountStatusFetcher, paymentAccountStatusSupplier, onDismiss, onContactSupport, onResolved)
}
@Test
fun `GIVEN refetch resolves to available WHEN retry THEN onResolved called`() = runTest {
// Arrange
val onramp = VirtualAccountOnramp.Available(productInstanceId = "pi_1", bankCredentials = mockk())
coEvery { paymentAccountStatusFetcher.invoke(userWalletId) } returns Unit.right()
stubSupplier(onramp)
val model = createModel()
// Act
model.uiState.value.onRetryClick()
advanceUntilIdle()
// Assert
verify(exactly = 1) { onResolved(onramp) }
}
@Test
fun `GIVEN refetch still fails WHEN retry THEN onResolved not called and loading reset`() = runTest {
// Arrange
coEvery { paymentAccountStatusFetcher.invoke(userWalletId) } returns Unit.right()
stubSupplier(VirtualAccountOnramp.BankCredentialsError)
val model = createModel()
// Act
model.uiState.value.onRetryClick()
advanceUntilIdle()
// Assert
verify { onResolved wasNot Called }
assertThat(model.uiState.value.isRetryLoading).isFalse()
}
@Test
fun `GIVEN refetch in progress WHEN retry twice THEN fetch invoked once and loading shown`() = runTest {
// Arrange
val pending = CompletableDeferred<Either<Throwable, Unit>>()
coEvery { paymentAccountStatusFetcher.invoke(userWalletId) } coAnswers { pending.await() }
stubSupplier(VirtualAccountOnramp.BankCredentialsError)
val model = createModel()
// Act
model.uiState.value.onRetryClick() // starts loading, fetch suspends
advanceUntilIdle()
model.uiState.value.onRetryClick() // gated by isRetryLoading — must be ignored
advanceUntilIdle()
// Assert
assertThat(model.uiState.value.isRetryLoading).isTrue()
coVerify(exactly = 1) { paymentAccountStatusFetcher.invoke(userWalletId) }
pending.complete(Unit.right()) // let the in-flight call finish cleanly
advanceUntilIdle()
}
private fun stubSupplier(onramp: VirtualAccountOnramp) {
val loaded = mockk<PaymentAccountStatusValue.Loaded>()
every { loaded.virtualAccount } returns onramp
val status = mockk<AccountStatus.Payment>()
every { status.value } returns loaded
every { paymentAccountStatusSupplier.invoke(userWalletId) } returns flowOf(status)
}
private fun TestScope.createModel() = TangemPayVaBankingDetailsErrorModel(
paramsContainer = MutableParamsContainer(
TangemPayVaBankingDetailsErrorComponent.Params(
userWalletId = userWalletId,
onDismiss = onDismiss,
onContactSupport = onContactSupport,
onResolved = onResolved,
),
),
dispatchers = createTestingCoroutineDispatcherProvider(),
paymentAccountStatusFetcher = paymentAccountStatusFetcher,
paymentAccountStatusSupplier = paymentAccountStatusSupplier,
)
private fun TestScope.createTestingCoroutineDispatcherProvider(): TestingCoroutineDispatcherProvider {
val testDispatcher = StandardTestDispatcher(testScheduler)
return TestingCoroutineDispatcherProvider(
main = testDispatcher,
mainImmediate = testDispatcher,
io = testDispatcher,
default = testDispatcher,
single = testDispatcher,
)
}
}