Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-06 12:44:09 +04:00
parent 84f35ac892
commit e7edce0cd8
5 changed files with 205 additions and 0 deletions

View file

@ -0,0 +1,14 @@
package com.tangem.feature.wallet.child.managetokens.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
internal sealed class PortfolioAnalyticsEvent(
event: String,
) : AnalyticsEvent(category = "Portfolio", event = event) {
class ButtonAddManage : PortfolioAnalyticsEvent(event = "Button - Add Manage")
class ButtonAddTokens : PortfolioAnalyticsEvent(event = "Button - Add tokens")
class ButtonOrganizeTokens : PortfolioAnalyticsEvent(event = "Button - Organize Tokens")
}

View file

@ -3,11 +3,13 @@ package com.tangem.feature.wallet.child.managetokens.model
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.core.analytics.api.AnalyticsEventHandler
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.AccountId
import com.tangem.feature.wallet.child.managetokens.AddAndManageBottomSheetComponent
import com.tangem.feature.wallet.child.managetokens.analytics.PortfolioAnalyticsEvent
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioFetcher
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorController
@ -21,6 +23,7 @@ internal class AddAndManageModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val portfolioFetcherFactory: PortfolioFetcher.Factory,
private val analyticsEventHandler: AnalyticsEventHandler,
val portfolioSelectorController: PortfolioSelectorController,
) : Model() {
@ -45,6 +48,7 @@ internal class AddAndManageModel @Inject constructor(
}
fun onAddTokensClick() {
analyticsEventHandler.send(PortfolioAnalyticsEvent.ButtonAddTokens())
modelScope.launch {
val data = portfolioFetcher.data.first()
val isSingleAccount = data.isSingleChoice(params.userWalletId)
@ -65,6 +69,7 @@ internal class AddAndManageModel @Inject constructor(
}
fun onOrganizeTokensClick() {
analyticsEventHandler.send(PortfolioAnalyticsEvent.ButtonOrganizeTokens())
params.onDismiss()
params.onOrganizeTokensClick()
}

View file

@ -23,6 +23,7 @@ import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.managetokens.analytics.PortfolioAnalyticsEvent
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
@ -123,6 +124,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
override fun onOrganizeTokensClick() {
val userWalletId = stateHolder.getSelectedWalletId()
if (walletFeatureToggles.isAddAndManageTokensEnabled) {
analyticsEventHandler.send(PortfolioAnalyticsEvent.ButtonAddManage())
router.openAddAndManageBottomSheet(userWalletId = userWalletId)
} else {
router.openOrganizeTokensScreen(userWalletId = userWalletId)

View file

@ -0,0 +1,101 @@
package com.tangem.feature.wallet.child.managetokens.model
import com.google.common.truth.Truth.assertThat
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.decompose.model.MutableParamsContainer
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.managetokens.AddAndManageBottomSheetComponent
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioFetcher
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorController
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
internal class AddAndManageModelTest {
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
private val portfolioFetcher: PortfolioFetcher = mockk(relaxed = true) {
every { data } returns flowOf(
PortfolioFetcher.Data(
appCurrency = mockk(relaxed = true),
isBalanceHidden = false,
balances = emptyMap(),
),
)
}
private val portfolioFetcherFactory: PortfolioFetcher.Factory = mockk(relaxed = true) {
every { create(any(), any()) } returns portfolioFetcher
}
private val portfolioSelectorController: PortfolioSelectorController = mockk(relaxed = true) {
every { selectedAccount } returns flowOf(null)
}
private val onDismiss: () -> Unit = mockk(relaxed = true)
private val onOrganizeTokensClick: () -> Unit = mockk(relaxed = true)
private val onManageTokensClick: (AccountId) -> Unit = mockk(relaxed = true)
private val userWalletId = UserWalletId(stringValue = "0123456789ABCDEF")
private val params = AddAndManageBottomSheetComponent.Params(
userWalletId = userWalletId,
onDismiss = onDismiss,
onOrganizeTokensClick = onOrganizeTokensClick,
onManageTokensClick = onManageTokensClick,
)
private fun createModel(): AddAndManageModel = AddAndManageModel(
paramsContainer = MutableParamsContainer(params),
dispatchers = TestingCoroutineDispatcherProvider(),
portfolioFetcherFactory = portfolioFetcherFactory,
analyticsEventHandler = analyticsEventHandler,
portfolioSelectorController = portfolioSelectorController,
)
@Test
fun `GIVEN bottom sheet model WHEN onAddTokensClick THEN sends ButtonAddTokens event with correct payload`() =
runTest {
val model = createModel()
val captured = slot<AnalyticsEvent>()
model.onAddTokensClick()
verify(exactly = 1) { analyticsEventHandler.send(capture(captured)) }
assertThat(captured.captured.category).isEqualTo("Portfolio")
assertThat(captured.captured.event).isEqualTo("Button - Add tokens")
assertThat(captured.captured.params).isEmpty()
}
@Test
fun `GIVEN bottom sheet model WHEN onOrganizeTokensClick THEN sends ButtonOrganizeTokens event with correct payload`() =
runTest {
val model = createModel()
val captured = slot<AnalyticsEvent>()
model.onOrganizeTokensClick()
verify(exactly = 1) { analyticsEventHandler.send(capture(captured)) }
assertThat(captured.captured.category).isEqualTo("Portfolio")
assertThat(captured.captured.event).isEqualTo("Button - Organize Tokens")
assertThat(captured.captured.params).isEmpty()
}
@Test
fun `GIVEN bottom sheet model WHEN onOrganizeTokensClick THEN dismisses bottom sheet and forwards to params callback`() =
runTest {
val model = createModel()
model.onOrganizeTokensClick()
verify(exactly = 1) { onDismiss() }
verify(exactly = 1) { onOrganizeTokensClick() }
}
}

View file

@ -0,0 +1,83 @@
package com.tangem.feature.wallet.child.wallet.model.intents
import com.google.common.truth.Truth.assertThat
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
internal class WalletContentClickIntentsAnalyticsTest {
private val stateHolder: WalletStateController = mockk(relaxed = true)
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
private val walletFeatureToggles: WalletFeatureToggles = mockk(relaxed = true)
private val router: InnerWalletRouter = mockk(relaxed = true)
private val userWalletId = UserWalletId(stringValue = "0123456789ABCDEF")
private fun createImplementor(): WalletContentClickIntentsImplementor {
every { stateHolder.getSelectedWalletId() } returns userWalletId
val implementor = WalletContentClickIntentsImplementor(
stateHolder = stateHolder,
currencyActionsClickIntents = mockk(relaxed = true),
onrampStatusFactory = mockk(relaxed = true),
getUserWalletUseCase = mockk(relaxed = true),
singleAccountStatusListSupplier = mockk(relaxed = true),
getCryptoCurrencyActionsUseCase = mockk(relaxed = true),
getExplorerTransactionUrlUseCase = mockk(relaxed = true),
shouldShowMarketsTooltipUseCase = mockk(relaxed = true),
dispatchers = mockk(relaxed = true),
walletEventSender = mockk(relaxed = true),
analyticsEventHandler = analyticsEventHandler,
accountDependencies = mockk(relaxed = true),
yieldSupplySetShouldShowMainPromoUseCase = mockk(relaxed = true),
tokenListAnalyticsSender = mockk(relaxed = true),
uiMessageSender = mockk(relaxed = true),
walletFeatureToggles = walletFeatureToggles,
)
implementor.initialize(router = router, coroutineScope = TestScope())
return implementor
}
@Test
fun `GIVEN add and manage toggle enabled WHEN onOrganizeTokensClick THEN sends ButtonAddManage event and opens bottom sheet`() =
runTest {
every { walletFeatureToggles.isAddAndManageTokensEnabled } returns true
val implementor = createImplementor()
val captured = slot<AnalyticsEvent>()
implementor.onOrganizeTokensClick()
verify(exactly = 1) { analyticsEventHandler.send(capture(captured)) }
assertThat(captured.captured.category).isEqualTo("Portfolio")
assertThat(captured.captured.event).isEqualTo("Button - Add Manage")
assertThat(captured.captured.params).isEmpty()
verify(exactly = 1) { router.openAddAndManageBottomSheet(userWalletId = userWalletId) }
verify(exactly = 0) { router.openOrganizeTokensScreen(any()) }
}
@Test
fun `GIVEN add and manage toggle disabled WHEN onOrganizeTokensClick THEN does not send analytics and opens organize screen`() =
runTest {
every { walletFeatureToggles.isAddAndManageTokensEnabled } returns false
val implementor = createImplementor()
implementor.onOrganizeTokensClick()
verify(exactly = 0) { analyticsEventHandler.send(any<AnalyticsEvent>()) }
verify(exactly = 1) { router.openOrganizeTokensScreen(userWalletId = userWalletId) }
verify(exactly = 0) { router.openAddAndManageBottomSheet(any()) }
}
}