Updated on 2026-08-14
This commit is contained in:
commit
92849d43c2
1407 changed files with 64092 additions and 11990 deletions
|
|
@ -11,19 +11,20 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(deps.compose.foundation)
|
||||
|
||||
/* Project - API */
|
||||
api(projects.features.account.api)
|
||||
/* Kotlin */
|
||||
api(deps.kotlin.coroutines)
|
||||
api(deps.kotlin.serialization.core)
|
||||
|
||||
/* Compose */
|
||||
api(deps.compose.foundation)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
api(projects.core.decompose)
|
||||
api(projects.core.ui)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.markets.models)
|
||||
api(projects.domain.appCurrency.models)
|
||||
api(projects.domain.markets.models)
|
||||
api(projects.domain.models)
|
||||
}
|
||||
|
|
@ -18,51 +18,48 @@ android {
|
|||
}
|
||||
dependencies {
|
||||
/* Project - API */
|
||||
api(projects.features.feed.api)
|
||||
api(projects.features.onramp.api)
|
||||
api(projects.features.send.api)
|
||||
api(projects.features.tokenRecieve.api)
|
||||
api(projects.features.wallet.api)
|
||||
api(projects.features.account.api)
|
||||
api(projects.features.commonFeatures.api)
|
||||
implementation(projects.features.promoBanners.api)
|
||||
api(projects.features.feed.api)
|
||||
api(projects.features.forYou.api)
|
||||
api(projects.features.promoBanners.api)
|
||||
api(projects.features.tokenRecieve.api)
|
||||
|
||||
/* Data */
|
||||
implementation(projects.data.common)
|
||||
|
||||
/* Domain */
|
||||
api(projects.domain.account.status)
|
||||
api(projects.domain.appCurrency)
|
||||
api(projects.domain.balanceHiding)
|
||||
api(projects.domain.common)
|
||||
api(projects.domain.earn)
|
||||
api(projects.domain.feedback)
|
||||
api(projects.domain.markets)
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.news)
|
||||
api(projects.domain.search)
|
||||
api(projects.domain.settings)
|
||||
api(projects.domain.tokens)
|
||||
api(projects.domain.transaction)
|
||||
api(projects.domain.wallets)
|
||||
api(projects.domain.yieldSupply)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.feedback.models)
|
||||
implementation(projects.domain.manageTokens)
|
||||
implementation(projects.domain.markets)
|
||||
implementation(projects.domain.offramp)
|
||||
implementation(projects.domain.onramp.models)
|
||||
implementation(projects.domain.staking.models)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.notifications.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.news)
|
||||
implementation(projects.domain.yieldSupply.models)
|
||||
implementation(projects.domain.yieldSupply)
|
||||
implementation(projects.domain.earn)
|
||||
implementation(projects.domain.search)
|
||||
runtimeOnly(projects.domain.manageTokens)
|
||||
runtimeOnly(projects.domain.offramp)
|
||||
|
||||
/* Domain models */
|
||||
api(projects.domain.markets.models)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.material3)
|
||||
api(deps.compose.coil)
|
||||
api(deps.compose.foundation)
|
||||
api(deps.compose.material3)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.ui.utils)
|
||||
|
|
@ -75,31 +72,38 @@ dependencies {
|
|||
kapt(deps.hilt.kapt)
|
||||
|
||||
/* Other */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
api(deps.decompose.ext.compose)
|
||||
api(deps.kotlin.coroutines)
|
||||
api(deps.kotlin.immutable.collections)
|
||||
api(deps.kotlin.serialization.core)
|
||||
implementation(deps.androidx.annotation)
|
||||
implementation(deps.androidx.appCompat)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.haze)
|
||||
implementation(deps.jodatime)
|
||||
|
||||
/* Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.analytics)
|
||||
api(projects.core.analytics)
|
||||
api(projects.core.decompose)
|
||||
api(projects.core.navigation)
|
||||
api(projects.core.ui)
|
||||
api(projects.core.utils)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.pagination)
|
||||
|
||||
/* Common */
|
||||
implementation(projects.common.ui)
|
||||
api(projects.common.routing)
|
||||
api(projects.common.ui)
|
||||
api(projects.common.uiMarkets)
|
||||
implementation(projects.common)
|
||||
implementation(projects.common.uiCharts)
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.common.uiMarkets)
|
||||
|
||||
/* Libs */
|
||||
api(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(tangemDeps.card.core)
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
/** Tests */
|
||||
|
|
@ -107,4 +111,5 @@ dependencies {
|
|||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(projects.domain.wallets.models)
|
||||
}
|
||||
|
|
@ -142,6 +142,10 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
|||
override fun openSearch(source: String) {
|
||||
stackNavigation.bringToFront(FeedEntryChildFactory.Child.Search(source))
|
||||
}
|
||||
|
||||
override fun openForYou() {
|
||||
stackNavigation.bringToFront(FeedEntryChildFactory.Child.ForYou)
|
||||
}
|
||||
}
|
||||
|
||||
private val stack: Value<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>> =
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.features.feed.components.news.details.DefaultNewsDetailsCompon
|
|||
import com.tangem.features.feed.components.news.list.DefaultNewsListComponent
|
||||
import com.tangem.features.feed.components.search.DefaultSearchComponent
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.foryou.ForYouComponent
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
||||
import kotlinx.serialization.Serializable
|
||||
import javax.inject.Inject
|
||||
|
|
@ -33,6 +34,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
private val manageFundsComponentFactory: ManageFundsComponent.Factory,
|
||||
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
private val forYouComponentFactory: ForYouComponent.Factory,
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
|
|
@ -66,6 +68,10 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
@Serializable
|
||||
@Immutable
|
||||
data class Search(val source: String) : Child
|
||||
|
||||
@Serializable
|
||||
@Immutable
|
||||
data object ForYou : Child
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
|
|
@ -147,6 +153,10 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
onSeeAllMarketsClick = { feedEntryClickIntents.onMarketOpenClick(SortByTypeUM.Rating) },
|
||||
),
|
||||
)
|
||||
Child.ForYou -> forYouComponentFactory.create(
|
||||
context = appComponentContext,
|
||||
params = Unit,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ import com.tangem.features.feed.model.feed.state.transformers.*
|
|||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.feed.state.*
|
||||
import com.tangem.features.foryou.ForYouFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -61,6 +62,7 @@ internal class FeedComponentModel @Inject constructor(
|
|||
private val appRouter: AppRouter,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
|
||||
private val forYouFeatureToggles: ForYouFeatureToggles,
|
||||
getTopFiveMarketTokenUseCase: GetTopFiveMarketTokenUseCase,
|
||||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
|
|
@ -273,6 +275,11 @@ internal class FeedComponentModel @Inject constructor(
|
|||
),
|
||||
globalState = GlobalFeedState.Loading,
|
||||
earnListUM = EarnListUM.Loading,
|
||||
forYouBannerUM = if (forYouFeatureToggles.isForYouEnabled) {
|
||||
ForYouBannerUM.Content(onClick = params.feedClickIntents::openForYou)
|
||||
} else {
|
||||
ForYouBannerUM.Empty
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,4 +31,6 @@ internal interface FeedModelClickIntents {
|
|||
fun onOpenEarnPage()
|
||||
|
||||
fun openSearch(source: String)
|
||||
|
||||
fun openForYou()
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ internal class FeedStateController @Inject constructor() {
|
|||
),
|
||||
globalState = GlobalFeedState.Loading,
|
||||
earnListUM = EarnListUM.Loading,
|
||||
forYouBannerUM = ForYouBannerUM.Empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,11 @@ import androidx.compose.animation.AnimatedContent
|
|||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -16,17 +18,20 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.ds2.messagebanner.TangemMessageBanner
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_pie_chart_20
|
||||
import com.tangem.core.ui.test.BaseSearchBarTestTags.SEARCH_BAR
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.ui.components.FeedSearchBar
|
||||
import com.tangem.features.feed.ui.feed.components.*
|
||||
import com.tangem.features.feed.ui.feed.preview.FeedListPreviewDataProvider.createFeedPreviewState
|
||||
import com.tangem.features.feed.ui.feed.state.FeedListSearchBar
|
||||
import com.tangem.features.feed.ui.feed.state.FeedListUM
|
||||
import com.tangem.features.feed.ui.feed.state.ForYouBannerUM
|
||||
import com.tangem.features.feed.ui.feed.state.GlobalFeedState
|
||||
|
||||
@Composable
|
||||
|
|
@ -105,6 +110,27 @@ private fun FeedListContent(
|
|||
SpacerH(contentPadding.calculateTopPadding())
|
||||
}
|
||||
DateBlock(state.currentDate)
|
||||
|
||||
SpacerH(16.dp)
|
||||
|
||||
if (state.forYouBannerUM is ForYouBannerUM.Content && LocalRedesignEnabled.current) {
|
||||
TangemMessageBanner(
|
||||
title = resourceReference(R.string.for_you_title),
|
||||
description = resourceReference(R.string.for_you_description),
|
||||
variant = TangemMessageBanner.Variant.Default,
|
||||
slotStart = {
|
||||
Icon(
|
||||
imageVector = Icons.ic_pie_chart_20,
|
||||
tint = TangemTheme.colors3.icon.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable(onClick = state.forYouBannerUM.onClick),
|
||||
)
|
||||
}
|
||||
|
||||
SpacerH(32.dp)
|
||||
|
||||
MarketBlock(
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ internal object FeedListPreviewDataProvider {
|
|||
earnListUM = EarnListUM.Content(
|
||||
items = createEarnListItemsUM(),
|
||||
),
|
||||
forYouBannerUM = createForYouItem(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -285,4 +286,10 @@ internal object FeedListPreviewDataProvider {
|
|||
)
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
private fun createForYouItem(): ForYouBannerUM {
|
||||
return ForYouBannerUM.Content(
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@ package com.tangem.features.feed.ui.feed.state
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -19,6 +19,7 @@ internal data class FeedListUM(
|
|||
val marketChartConfig: MarketChartConfig,
|
||||
val globalState: GlobalFeedState = GlobalFeedState.Content,
|
||||
val earnListUM: EarnListUM,
|
||||
val forYouBannerUM: ForYouBannerUM,
|
||||
)
|
||||
|
||||
internal data class FeedListCallbacks(
|
||||
|
|
@ -80,6 +81,16 @@ internal data class SortChartConfigUM(
|
|||
val isSelected: Boolean,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal sealed interface ForYouBannerUM {
|
||||
|
||||
data class Content(
|
||||
val onClick: () -> Unit,
|
||||
) : ForYouBannerUM
|
||||
|
||||
data object Empty : ForYouBannerUM
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal sealed interface GlobalFeedState {
|
||||
data object Loading : GlobalFeedState
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
package com.tangem.features.feed.model.feed
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import arrow.core.Either
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.model.MutableParamsContainer
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.earn.usecase.FetchTopEarnTokensUseCase
|
||||
import com.tangem.domain.earn.usecase.GetTopEarnTokensUseCase
|
||||
import com.tangem.domain.markets.GetTopFiveMarketTokenUseCase
|
||||
import com.tangem.domain.news.usecase.FetchTrendingNewsUseCase
|
||||
import com.tangem.domain.news.usecase.ManageTrendingNewsUseCase
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
|
||||
import com.tangem.features.feed.components.feed.DefaultFeedComponent.FeedParams
|
||||
import com.tangem.features.feed.model.feed.state.FeedStateController
|
||||
import com.tangem.features.feed.ui.feed.state.ForYouBannerUM
|
||||
import com.tangem.features.foryou.ForYouFeatureToggles
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
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.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class FeedComponentModelTest {
|
||||
|
||||
// --- shared mocks ---
|
||||
private val fetchTrendingNewsUseCase: FetchTrendingNewsUseCase = mockk(relaxed = true)
|
||||
private val manageTrendingNewsUseCase: ManageTrendingNewsUseCase = mockk()
|
||||
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
|
||||
private val fetchTopEarnTokensUseCase: FetchTopEarnTokensUseCase = mockk(relaxed = true)
|
||||
private val getTopEarnTokensUseCase: GetTopEarnTokensUseCase = mockk()
|
||||
private val appRouter: AppRouter = mockk(relaxed = true)
|
||||
private val designFeatureToggles: DesignFeatureToggles = mockk()
|
||||
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory = mockk(relaxed = true)
|
||||
private val getTopFiveMarketTokenUseCase: GetTopFiveMarketTokenUseCase = mockk(relaxed = true)
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase = mockk()
|
||||
private val feedClickIntents: FeedModelClickIntents = mockk(relaxed = true)
|
||||
|
||||
@BeforeEach
|
||||
fun setUpDateFormatMock() {
|
||||
// DateTimeFormatters.dateDMMM is a lazy val that calls android.text.format.DateFormat
|
||||
// .getBestDateTimePattern — an Android stub not available in JVM unit tests.
|
||||
// Mirror the pattern used in TxHistoryInfoToTxHistoryDetailsUMConverterTest.
|
||||
mockkStatic(DateFormat::class)
|
||||
every { DateFormat.getBestDateTimePattern(any(), any()) } answers { secondArg() }
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownDateFormatMock() {
|
||||
unmockkStatic(DateFormat::class)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a [FeedComponentModel] wired into the given [TestScope], using a real
|
||||
* [FeedStateController] so we can read the initialised state directly.
|
||||
*
|
||||
* All deps unrelated to [ForYouFeatureToggles] are relaxed or stubbed with empty flows so
|
||||
* the model's background coroutines don't throw.
|
||||
*/
|
||||
private fun TestScope.createModel(forYouFeatureToggles: ForYouFeatureToggles): FeedComponentModel {
|
||||
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
val dispatchers = TestingCoroutineDispatcherProvider(
|
||||
main = testDispatcher,
|
||||
mainImmediate = testDispatcher,
|
||||
io = testDispatcher,
|
||||
default = testDispatcher,
|
||||
single = testDispatcher,
|
||||
)
|
||||
|
||||
every { getSelectedAppCurrencyUseCase() } returns flowOf(Either.Right(AppCurrency.Default))
|
||||
every { manageTrendingNewsUseCase.observeTrendingNews() } returns emptyFlow()
|
||||
every { getTopEarnTokensUseCase() } returns emptyFlow()
|
||||
every { designFeatureToggles.isRedesignEnabled } returns false
|
||||
|
||||
val paramsContainer = MutableParamsContainer(FeedParams(feedClickIntents = feedClickIntents))
|
||||
|
||||
return FeedComponentModel(
|
||||
dispatchers = dispatchers,
|
||||
fetchTrendingNewsUseCase = fetchTrendingNewsUseCase,
|
||||
manageTrendingNewsUseCase = manageTrendingNewsUseCase,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
stateController = FeedStateController(),
|
||||
fetchTopEarnTokensUseCase = fetchTopEarnTokensUseCase,
|
||||
getTopEarnTokensUseCase = getTopEarnTokensUseCase,
|
||||
appRouter = appRouter,
|
||||
designFeatureToggles = designFeatureToggles,
|
||||
addToPortfolioManagerFactory = addToPortfolioManagerFactory,
|
||||
forYouFeatureToggles = forYouFeatureToggles,
|
||||
getTopFiveMarketTokenUseCase = getTopFiveMarketTokenUseCase,
|
||||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
paramsContainer = paramsContainer,
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class `initialState forYouBannerUM` {
|
||||
|
||||
@Test
|
||||
fun `GIVEN isForYouEnabled is true WHEN model initialises THEN forYouBannerUM is Content`() = runTest {
|
||||
// Arrange
|
||||
val toggles = mockk<ForYouFeatureToggles> { every { isForYouEnabled } returns true }
|
||||
|
||||
// Act
|
||||
val model = createModel(forYouFeatureToggles = toggles)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
assertThat(model.state.value.forYouBannerUM).isInstanceOf(ForYouBannerUM.Content::class.java)
|
||||
|
||||
model.onDestroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN isForYouEnabled is false WHEN model initialises THEN forYouBannerUM is Empty`() = runTest {
|
||||
// Arrange
|
||||
val toggles = mockk<ForYouFeatureToggles> { every { isForYouEnabled } returns false }
|
||||
|
||||
// Act
|
||||
val model = createModel(forYouFeatureToggles = toggles)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
assertThat(model.state.value.forYouBannerUM).isEqualTo(ForYouBannerUM.Empty)
|
||||
|
||||
model.onDestroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN isForYouEnabled is true WHEN Content banner clicked THEN openForYou invoked`() = runTest {
|
||||
// Arrange
|
||||
val toggles = mockk<ForYouFeatureToggles> { every { isForYouEnabled } returns true }
|
||||
|
||||
// Act
|
||||
val model = createModel(forYouFeatureToggles = toggles)
|
||||
advanceUntilIdle()
|
||||
val banner = model.state.value.forYouBannerUM
|
||||
(banner as? ForYouBannerUM.Content)?.onClick?.invoke()
|
||||
|
||||
// Assert – onClick must be wired to feedClickIntents::openForYou, not just any lambda
|
||||
assertThat(banner).isInstanceOf(ForYouBannerUM.Content::class.java)
|
||||
verify(exactly = 1) { feedClickIntents.openForYou() }
|
||||
|
||||
model.onDestroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue