Updated on 2026-08-14
This commit is contained in:
parent
d718cf557d
commit
970312afd3
19 changed files with 260 additions and 10 deletions
|
|
@ -296,6 +296,16 @@
|
|||
android:host="onboard-visa"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="news"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
|
|
|
|||
|
|
@ -700,6 +700,15 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = feedEntryComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.News -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = FeedEntryRoute.NewsList(
|
||||
preselectedCategoryId = route.categoryId,
|
||||
),
|
||||
componentFactory = feedEntryComponentFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
|
|||
import com.tangem.features.feed.entry.deeplink.MarketsDeepLinkHandler
|
||||
import com.tangem.features.feed.entry.deeplink.MarketsTokenDetailDeepLinkHandler
|
||||
import com.tangem.features.feed.entry.deeplink.MarketsTokenExchangesDeepLinkHandler
|
||||
import com.tangem.features.feed.entry.deeplink.NewsDeepLinkHandler
|
||||
import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
|
||||
|
|
@ -54,6 +55,7 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
private val onboardVisaDeepLink: OnboardVisaDeepLinkHandler.Factory,
|
||||
private val marketsTokenExchangesDeepLink: MarketsTokenExchangesDeepLinkHandler.Factory,
|
||||
private val newsDetailsDeepLink: NewsDetailsDeepLinkHandler.Factory,
|
||||
private val newsDeepLink: NewsDeepLinkHandler.Factory,
|
||||
) {
|
||||
private val permittedAppRoute = MutableStateFlow(false)
|
||||
|
||||
|
|
@ -159,6 +161,7 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
DeepLinkRoute.WalletConnect.host -> walletConnectDeepLink.create(deeplinkUri)
|
||||
DeepLinkRoute.Promo.host -> promoDeepLink.create(coroutineScope, queryParams)
|
||||
DeepLinkRoute.OnboardVisa.host -> onboardVisaDeepLink.create(deeplinkUri)
|
||||
DeepLinkRoute.News.host -> newsDeepLink.create(queryParams)
|
||||
else -> {
|
||||
TangemLogger.i(
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
|
|||
import com.tangem.features.feed.entry.deeplink.MarketsDeepLinkHandler
|
||||
import com.tangem.features.feed.entry.deeplink.MarketsTokenDetailDeepLinkHandler
|
||||
import com.tangem.features.feed.entry.deeplink.MarketsTokenExchangesDeepLinkHandler
|
||||
import com.tangem.features.feed.entry.deeplink.NewsDeepLinkHandler
|
||||
import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
|
||||
|
|
@ -87,6 +88,10 @@ class DeepLinkFactoryTest {
|
|||
every { create(any(), any()) } returns mockk()
|
||||
}
|
||||
|
||||
private val newsDeepLinkFactory = mockk<NewsDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any()) } returns mockk()
|
||||
}
|
||||
|
||||
private val marketsTokenExchangesDeepLinkFactory =
|
||||
mockk<MarketsTokenExchangesDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any(), any()) } returns mockk()
|
||||
|
|
@ -116,6 +121,7 @@ class DeepLinkFactoryTest {
|
|||
promoDeepLink = promoDeepLinkFactory,
|
||||
onboardVisaDeepLink = onboardVisaDeepLink,
|
||||
newsDetailsDeepLink = newsDeeplink,
|
||||
newsDeepLink = newsDeepLinkFactory,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
|
@ -432,6 +438,21 @@ class DeepLinkFactoryTest {
|
|||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleTangemDeepLinks routes news host to dedicated handler`() = runTest {
|
||||
every { mockedUri.scheme } returns "tangem"
|
||||
every { mockedUri.host } returns "news"
|
||||
every { mockedUri.query } returns null
|
||||
every { mockedUri.queryParameterNames } returns emptySet()
|
||||
every { mockedUri.getQueryParameter(any()) } returns null
|
||||
|
||||
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
advanceUntilIdle()
|
||||
|
||||
verify { newsDeepLinkFactory.create(eq(emptyMap())) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleTangemDeepLinks routes to promo handler`() = runTest {
|
||||
every { mockedUri.scheme } returns "tangem"
|
||||
|
|
|
|||
|
|
@ -489,4 +489,9 @@ sealed class AppRoute(val path: String) : Route {
|
|||
|
||||
@Serializable
|
||||
data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId")
|
||||
|
||||
@Serializable
|
||||
data class News(
|
||||
val categoryId: Int? = null,
|
||||
) : AppRoute(path = "/news")
|
||||
}
|
||||
|
|
@ -71,6 +71,10 @@ sealed class DeepLinkRoute {
|
|||
data object PayApp : DeepLinkRoute() {
|
||||
override val host: String = "tangem.com"
|
||||
}
|
||||
|
||||
data object News : DeepLinkRoute() {
|
||||
override val host: String = "news"
|
||||
}
|
||||
}
|
||||
|
||||
enum class DeepLinkScheme(val scheme: String) {
|
||||
|
|
|
|||
|
|
@ -18,4 +18,6 @@ object DeeplinkConst {
|
|||
const val ORDER_KEY = "order"
|
||||
const val INTERVAL_KEY = "interval"
|
||||
const val SECTION_KEY = "section"
|
||||
const val CATEGORY_ID_KEY = "category_id"
|
||||
const val NEWS_ID_KEY = "news_id"
|
||||
}
|
||||
|
|
@ -88,6 +88,26 @@ internal class DeepLinkBuilderTest {
|
|||
assertThat(result).isEqualTo("${DeeplinkConst.TANGEM_SCHEME}://$action?$key1=$value1&$key2=$value2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `news host with categoryId produces expected uri`() {
|
||||
val result = deepLinkBuilder
|
||||
.setAction("news")
|
||||
.addQueryParam(DeeplinkConst.CATEGORY_ID_KEY, "5")
|
||||
.build()
|
||||
|
||||
assertThat(result).isEqualTo("${DeeplinkConst.TANGEM_SCHEME}://news?${DeeplinkConst.CATEGORY_ID_KEY}=5")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `news host with newsId produces expected uri`() {
|
||||
val result = deepLinkBuilder
|
||||
.setAction("news")
|
||||
.addQueryParam(DeeplinkConst.NEWS_ID_KEY, "20533")
|
||||
.build()
|
||||
|
||||
assertThat(result).isEqualTo("${DeeplinkConst.TANGEM_SCHEME}://news?${DeeplinkConst.NEWS_ID_KEY}=20533")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN complex deep link WHEN build THEN should construct correct URI`() {
|
||||
// GIVEN
|
||||
|
|
|
|||
|
|
@ -36,4 +36,7 @@ sealed interface FeedEntryRoute {
|
|||
|
||||
@Serializable
|
||||
data class NewsDetail(val articleId: Int, val preselectedArticlesId: List<Int>) : FeedEntryRoute
|
||||
|
||||
@Serializable
|
||||
data class NewsList(val preselectedCategoryId: Int? = null) : FeedEntryRoute
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.feed.entry.deeplink
|
||||
|
||||
interface NewsDeepLinkHandler {
|
||||
|
||||
interface Factory {
|
||||
fun create(queryParams: Map<String, String>): NewsDeepLinkHandler
|
||||
}
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun onOpenAllNews() {
|
||||
innerRouter.push(FeedEntryChildFactory.Child.NewsList)
|
||||
innerRouter.push(FeedEntryChildFactory.Child.NewsList())
|
||||
}
|
||||
|
||||
override fun onOpenEarnPage() {
|
||||
|
|
@ -263,6 +263,9 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
|||
},
|
||||
),
|
||||
)
|
||||
is FeedEntryRoute.NewsList -> FeedEntryChildFactory.Child.NewsList(
|
||||
preselectedCategoryId = entryRoute.preselectedCategoryId,
|
||||
)
|
||||
null -> FeedEntryChildFactory.Child.Feed
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
|
||||
@Serializable
|
||||
@Immutable
|
||||
data object NewsList : Child
|
||||
data class NewsList(val preselectedCategoryId: Int? = null) : Child
|
||||
|
||||
@Serializable
|
||||
@Immutable
|
||||
|
|
@ -110,7 +110,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
params = child.params,
|
||||
)
|
||||
}
|
||||
Child.NewsList -> {
|
||||
is Child.NewsList -> {
|
||||
DefaultNewsListComponent(
|
||||
appComponentContext = appComponentContext,
|
||||
params = Params(
|
||||
|
|
@ -123,6 +123,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
)
|
||||
},
|
||||
onBackClick = onBackClicked,
|
||||
preselectedCategoryId = child.preselectedCategoryId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,5 +112,6 @@ internal class DefaultNewsListComponent(
|
|||
paginationConfig: NewsListConfig?,
|
||||
) -> Unit,
|
||||
val onBackClick: () -> Unit,
|
||||
val preselectedCategoryId: Int? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.features.feed.deeplink
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.CATEGORY_ID_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.NEWS_ID_KEY
|
||||
import com.tangem.features.feed.entry.deeplink.NewsDeepLinkHandler
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultNewsDeepLinkHandler @AssistedInject constructor(
|
||||
@Assisted private val queryParams: Map<String, String>,
|
||||
private val appRouter: AppRouter,
|
||||
) : NewsDeepLinkHandler {
|
||||
|
||||
init {
|
||||
handleDeepLink()
|
||||
}
|
||||
|
||||
private fun handleDeepLink() {
|
||||
val newsId = queryParams[NEWS_ID_KEY]?.toIntOrNull()
|
||||
if (newsId != null) {
|
||||
appRouter.push(AppRoute.NewsDetails(newsId = newsId))
|
||||
return
|
||||
}
|
||||
|
||||
appRouter.push(AppRoute.News(categoryId = queryParams[CATEGORY_ID_KEY]?.toIntOrNull()))
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : NewsDeepLinkHandler.Factory {
|
||||
override fun create(queryParams: Map<String, String>): DefaultNewsDeepLinkHandler
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.features.feed.deeplink.di
|
||||
|
||||
import com.tangem.features.feed.deeplink.DefaultNewsDeepLinkHandler
|
||||
import com.tangem.features.feed.deeplink.DefaultNewsDetailsDeepLinkHandler
|
||||
import com.tangem.features.feed.entry.deeplink.NewsDeepLinkHandler
|
||||
import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -17,4 +19,8 @@ internal interface FeedDeepLinkModule {
|
|||
fun bindNewsDetailsDeepLinkHandlerFactory(
|
||||
impl: DefaultNewsDetailsDeepLinkHandler.Factory,
|
||||
): NewsDetailsDeepLinkHandler.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindNewsDeepLinkHandlerFactory(impl: DefaultNewsDeepLinkHandler.Factory): NewsDeepLinkHandler.Factory
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.core.ui.components.chip.entity.ChipUM
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.domain.news.model.NewsListConfig
|
||||
import com.tangem.domain.news.usecase.GetNewsCategoriesUseCase
|
||||
import com.tangem.domain.news.usecase.GetNewsListBatchFlowUseCase
|
||||
|
|
@ -39,7 +41,7 @@ internal class NewsListModel @Inject constructor(
|
|||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<DefaultNewsListComponent.Params>()
|
||||
private val selectedCategoryId = MutableStateFlow<Int?>(null)
|
||||
private val selectedCategoryId = MutableStateFlow(params.preselectedCategoryId)
|
||||
|
||||
private val categoriesLoader by lazy {
|
||||
NewsCategoriesLoader(
|
||||
|
|
@ -67,7 +69,7 @@ internal class NewsListModel @Inject constructor(
|
|||
|
||||
private val _state = MutableStateFlow(
|
||||
NewsListUM(
|
||||
selectedCategoryId = DEFAULT_ALL_NEWS_CATEGORIES_ID,
|
||||
selectedCategoryId = params.preselectedCategoryId ?: DEFAULT_ALL_NEWS_CATEGORIES_ID,
|
||||
filters = persistentListOf(),
|
||||
newsListState = NewsListState.Loading,
|
||||
listOfArticles = persistentListOf(),
|
||||
|
|
@ -84,20 +86,38 @@ internal class NewsListModel @Inject constructor(
|
|||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
loadCategories()
|
||||
observeNewsList()
|
||||
batchFlowManager.reload()
|
||||
loadCategories()
|
||||
}
|
||||
|
||||
private fun loadCategories() {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
val filterChips = categoriesLoader.load()
|
||||
val validIds = filterChips.mapTo(mutableSetOf()) { it.id }
|
||||
selectedCategoryId.value = selectedCategoryId.value?.takeIf { it in validIds }
|
||||
val effectiveId = selectedCategoryId.value ?: DEFAULT_ALL_NEWS_CATEGORIES_ID
|
||||
val scrollIndex = filterChips.indexOfFirst { it.id == effectiveId }.takeIf { it > 0 }
|
||||
_state.update { currentState ->
|
||||
currentState.copy(filters = filterChips)
|
||||
currentState.copy(
|
||||
selectedCategoryId = effectiveId,
|
||||
filters = filterChips.map { chip ->
|
||||
chip.copy(isSelected = chip.id == effectiveId)
|
||||
}.toImmutableList(),
|
||||
scrollToCategoryEvent = if (scrollIndex != null) {
|
||||
triggeredEvent(data = scrollIndex, onConsume = ::onScrollToCategoryConsumed)
|
||||
} else {
|
||||
currentState.scrollToCategoryEvent
|
||||
},
|
||||
)
|
||||
}
|
||||
batchFlowManager.reload()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onScrollToCategoryConsumed() {
|
||||
_state.update { it.copy(scrollToCategoryEvent = consumedEvent()) }
|
||||
}
|
||||
|
||||
private fun observeNewsList() {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
combine(
|
||||
|
|
@ -111,7 +131,6 @@ internal class NewsListModel @Inject constructor(
|
|||
paginationStatus = paginationStatus,
|
||||
onRetryClick = {
|
||||
loadCategories()
|
||||
batchFlowManager.reload()
|
||||
},
|
||||
onLoadMore = { batchFlowManager.loadMore() },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.feed.ui.news.list
|
|||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
|
|
@ -20,6 +21,7 @@ import com.tangem.core.ui.components.haze.hazeEffectTangem
|
|||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.ds.tabs.TangemTab
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
|
||||
|
|
@ -50,6 +52,9 @@ internal fun NewsListContent(contentPadding: PaddingValues, state: NewsListUM, m
|
|||
internal fun NewsListContentV1(contentPadding: PaddingValues, state: NewsListUM, modifier: Modifier = Modifier) {
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val lazyListState = rememberLazyListState()
|
||||
val chipsListState = rememberLazyListState()
|
||||
|
||||
ScrollChipsToSelected(state = state, chipsListState = chipsListState)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
|
|
@ -58,6 +63,7 @@ internal fun NewsListContentV1(contentPadding: PaddingValues, state: NewsListUM,
|
|||
) {
|
||||
SpacerH(contentPadding.calculateTopPadding())
|
||||
LazyRow(
|
||||
state = chipsListState,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
|
|
@ -84,9 +90,12 @@ internal fun NewsListContentV1(contentPadding: PaddingValues, state: NewsListUM,
|
|||
internal fun NewsListContentV2(contentPadding: PaddingValues, state: NewsListUM) {
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val lazyListState = rememberLazyListState()
|
||||
val chipsListState = rememberLazyListState()
|
||||
var chipsHeight by remember { mutableStateOf(0.dp) }
|
||||
val density = LocalDensity.current
|
||||
|
||||
ScrollChipsToSelected(state = state, chipsListState = chipsListState)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -103,6 +112,7 @@ internal fun NewsListContentV2(contentPadding: PaddingValues, state: NewsListUM)
|
|||
onArticleClick = state.onArticleClick,
|
||||
)
|
||||
LazyRow(
|
||||
state = chipsListState,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.padding(top = contentPadding.calculateTopPadding(), bottom = TangemTheme.dimens2.x4)
|
||||
|
|
@ -141,6 +151,13 @@ internal fun NewsListContentV2(contentPadding: PaddingValues, state: NewsListUM)
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScrollChipsToSelected(state: NewsListUM, chipsListState: LazyListState) {
|
||||
EventEffect(event = state.scrollToCategoryEvent) { index ->
|
||||
chipsListState.animateScrollToItem(index)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.features.feed.ui.news.list.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
|
||||
import com.tangem.core.ui.components.chip.entity.ChipUM
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
|
|
@ -13,6 +15,7 @@ data class NewsListUM(
|
|||
val newsListState: NewsListState,
|
||||
val onArticleClick: (Int) -> Unit,
|
||||
val onBackClick: () -> Unit,
|
||||
val scrollToCategoryEvent: StateEvent<Int> = consumedEvent(),
|
||||
)
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.features.feed.deeplink
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.CATEGORY_ID_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.NEWS_ID_KEY
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class DefaultNewsDeepLinkHandlerTest {
|
||||
|
||||
private val appRouter: AppRouter = mockk()
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
every { appRouter.push(any(), any()) } just Runs
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no params opens news list with null category`() {
|
||||
DefaultNewsDeepLinkHandler(queryParams = emptyMap(), appRouter = appRouter)
|
||||
|
||||
verify { appRouter.push(AppRoute.News(categoryId = null), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `valid categoryId opens news list with that category`() {
|
||||
DefaultNewsDeepLinkHandler(
|
||||
queryParams = mapOf(CATEGORY_ID_KEY to "5"),
|
||||
appRouter = appRouter,
|
||||
)
|
||||
|
||||
verify { appRouter.push(AppRoute.News(categoryId = 5), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unknown categoryId is forwarded — sanitized in NewsListModel against loaded chips`() {
|
||||
DefaultNewsDeepLinkHandler(
|
||||
queryParams = mapOf(CATEGORY_ID_KEY to "42"),
|
||||
appRouter = appRouter,
|
||||
)
|
||||
|
||||
verify { appRouter.push(AppRoute.News(categoryId = 42), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `non-integer categoryId is silently dropped`() {
|
||||
DefaultNewsDeepLinkHandler(
|
||||
queryParams = mapOf(CATEGORY_ID_KEY to "not-a-number"),
|
||||
appRouter = appRouter,
|
||||
)
|
||||
|
||||
verify { appRouter.push(AppRoute.News(categoryId = null), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `newsId opens news details`() {
|
||||
DefaultNewsDeepLinkHandler(
|
||||
queryParams = mapOf(NEWS_ID_KEY to "20533"),
|
||||
appRouter = appRouter,
|
||||
)
|
||||
|
||||
verify { appRouter.push(AppRoute.NewsDetails(newsId = 20533), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `newsId takes priority over categoryId`() {
|
||||
DefaultNewsDeepLinkHandler(
|
||||
queryParams = mapOf(NEWS_ID_KEY to "20533", CATEGORY_ID_KEY to "5"),
|
||||
appRouter = appRouter,
|
||||
)
|
||||
|
||||
verify { appRouter.push(AppRoute.NewsDetails(newsId = 20533), any()) }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue