For #25025 - Use the `PocketStory` supertype

This prepares extending support from just `PocketRecommendedStory` to
`PocketSponsoredStory` also.
pull/543/head
Mugurell 2 years ago committed by mergify[bot]
parent abff68a31b
commit 1c7e788bae

@ -8,7 +8,8 @@ import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.lib.crash.Crash.NativeCodeCrash
import mozilla.components.lib.state.Action
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.home.Mode
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory
@ -56,7 +57,7 @@ sealed class AppAction : Action {
data class DisbandSearchGroupAction(val searchTerm: String) : AppAction()
data class SelectPocketStoriesCategory(val categoryName: String) : AppAction()
data class DeselectPocketStoriesCategory(val categoryName: String) : AppAction()
data class PocketStoriesShown(val storiesShown: List<PocketRecommendedStory>) : AppAction()
data class PocketStoriesShown(val storiesShown: List<PocketStory>) : AppAction()
data class PocketStoriesChange(val pocketStories: List<PocketRecommendedStory>) : AppAction()
/**
* Adds a set of items marked for removal to the app state, to be hidden in the UI.

@ -9,7 +9,9 @@ import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.lib.crash.Crash.NativeCodeCrash
import mozilla.components.lib.state.State
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.home.Mode
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory
@ -39,6 +41,8 @@ import org.mozilla.fenix.gleanplumb.MessagingState
* @property recentHistory The list of [RecentlyVisitedItem]s.
* @property pocketStories The list of currently shown [PocketRecommendedStory]s.
* @property pocketStoriesCategories All [PocketRecommendedStory] categories.
* @property pocketStoriesCategoriesSelections Current Pocket recommended stories categories selected by the user.
* @property pocketSponsoredStories All [PocketSponsoredStory]s.
* @property messaging State related messages.
* @property pendingDeletionHistoryItems The set of History items marked for removal in the UI,
* awaiting to be removed once the Undo snackbar hides away.
@ -56,9 +60,10 @@ data class AppState(
val recentSyncedTabState: RecentSyncedTabState = RecentSyncedTabState.None,
val recentBookmarks: List<RecentBookmark> = emptyList(),
val recentHistory: List<RecentlyVisitedItem> = emptyList(),
val pocketStories: List<PocketRecommendedStory> = emptyList(),
val pocketStories: List<PocketStory> = emptyList(),
val pocketStoriesCategories: List<PocketRecommendedStoriesCategory> = emptyList(),
val pocketStoriesCategoriesSelections: List<PocketRecommendedStoriesSelectedCategory> = emptyList(),
val pocketSponsoredStories: List<PocketSponsoredStory> = emptyList(),
val messaging: MessagingState = MessagingState(),
val pendingDeletionHistoryItems: Set<PendingDeletionHistory> = emptySet(),
) : State

@ -5,6 +5,7 @@
package org.mozilla.fenix.components.appstate
import androidx.annotation.VisibleForTesting
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.ext.filterOutTab
import org.mozilla.fenix.ext.getFilteredStories
@ -141,10 +142,12 @@ internal object AppStoreReducer {
pocketStories = updatedCategoriesState.getFilteredStories()
)
}
is AppAction.PocketStoriesChange -> state.copy(pocketStories = action.pocketStories)
is AppAction.PocketStoriesChange -> state.copy(
pocketStories = action.pocketStories
)
is AppAction.PocketStoriesShown -> {
var updatedCategories = state.pocketStoriesCategories
action.storiesShown.forEach { shownStory ->
action.storiesShown.filterIsInstance<PocketRecommendedStory>().forEach { shownStory ->
updatedCategories = updatedCategories.map { category ->
when (category.name == shownStory.category) {
true -> {

@ -5,11 +5,14 @@
package org.mozilla.fenix.ext
import androidx.annotation.VisibleForTesting
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.home.blocklist.BlocklistHandler
import org.mozilla.fenix.home.pocket.POCKET_STORIES_DEFAULT_CATEGORY_NAME
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.pocket.PocketStory
import org.mozilla.fenix.home.recenttabs.RecentTab.SearchGroup
@VisibleForTesting
@ -18,16 +21,21 @@ internal const val POCKET_STORIES_TO_SHOW_COUNT = 8
/**
* Get the list of stories to be displayed based on the user selected categories.
*
* @return a list of [PocketRecommendedStory]es from the currently selected categories.
* @return a list of [PocketStory]es from the currently selected categories.
*/
fun AppState.getFilteredStories(): List<PocketRecommendedStory> {
fun AppState.getFilteredStories(): List<PocketStory> {
if (pocketStoriesCategoriesSelections.isEmpty()) {
return pocketStoriesCategories
val recommendedStories = pocketStoriesCategories
.find {
it.name == POCKET_STORIES_DEFAULT_CATEGORY_NAME
}?.stories
?.sortedBy { it.timesShown }
?.take(POCKET_STORIES_TO_SHOW_COUNT) ?: emptyList()
return combineRecommendedAndSponsoredStories(
recommendedStories = recommendedStories,
sponsoredStories = pocketSponsoredStories,
)
}
val oldestSortedCategories = pocketStoriesCategoriesSelections
@ -48,6 +56,18 @@ fun AppState.getFilteredStories(): List<PocketRecommendedStory> {
}.take(POCKET_STORIES_TO_SHOW_COUNT)
}
private fun combineRecommendedAndSponsoredStories(
recommendedStories: List<PocketRecommendedStory>,
sponsoredStories: List<PocketSponsoredStory>,
): List<PocketStory> {
val recommendedStoriesToShow = POCKET_STORIES_TO_SHOW_COUNT - sponsoredStories.size.coerceAtMost(2)
return recommendedStories.take(1) +
sponsoredStories.take(1) +
recommendedStories.take(recommendedStoriesToShow).drop(1) +
sponsoredStories.take(2).drop(1)
}
/**
* Get how many stories needs to be shown from each currently selected category.
*

@ -14,8 +14,8 @@ import mozilla.components.lib.state.Action
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import mozilla.components.lib.state.Store
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStoriesService
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppState
@ -68,9 +68,11 @@ class PocketUpdatesMiddleware(
persistStories(
coroutineScope = coroutineScope,
pocketStoriesService = pocketStoriesService,
updatedStories = action.storiesShown.map {
it.copy(timesShown = it.timesShown.inc())
}
updatedStories = action.storiesShown
.filterIsInstance<PocketRecommendedStory>()
.map {
it.copy(timesShown = it.timesShown.inc())
}
)
}
is AppAction.SelectPocketStoriesCategory,

@ -4,7 +4,7 @@
package org.mozilla.fenix.home.pocket
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
/**
* Category name of the default category from which stories are to be shown

@ -37,7 +37,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.ClickableSubstringLink
import org.mozilla.fenix.compose.EagerFlingBehavior
@ -57,7 +58,7 @@ private const val POCKET_STORIES_UTM_VALUE = "pocket-newtab-android"
private const val POCKET_FEATURE_UTM_KEY_VALUE = "utm_source=ff_android"
/**
* Placeholder [PocketRecommendedStory] allowing to combine other items in the same list that shows stories.
* Placeholder [PocketStory] allowing to combine other items in the same list that shows stories.
* It uses empty values for it's properties ensuring that no conflict is possible since real stories have
* mandatory values.
*/
@ -114,11 +115,11 @@ fun PocketStory(
}
/**
* Displays a list of [PocketRecommendedStory]es on 3 by 3 grid.
* Displays a list of [PocketStory]es on 3 by 3 grid.
* If there aren't enough stories to fill all columns placeholders containing an external link
* to go to Pocket for more recommendations are added.
*
* @param stories The list of [PocketRecommendedStory]ies to be displayed. Expect a list with 8 items.
* @param stories The list of [PocketStory]ies to be displayed. Expect a list with 8 items.
* @param contentPadding Dimension for padding the content after it has been clipped.
* This space will be used for shadows and also content rendering when the list is scrolled.
* @param onStoryClicked Callback for when the user taps on a recommended story.
@ -126,9 +127,9 @@ fun PocketStory(
*/
@Composable
fun PocketStories(
@PreviewParameter(PocketStoryProvider::class) stories: List<PocketRecommendedStory>,
@PreviewParameter(PocketStoryProvider::class) stories: List<PocketStory>,
contentPadding: Dp,
onStoryClicked: (PocketRecommendedStory, Pair<Int, Int>) -> Unit,
onStoryClicked: (PocketStory, Pair<Int, Int>) -> Unit,
onDiscoverMoreClicked: (String) -> Unit
) {
// Show stories in at most 3 rows but on any number of columns depending on the data received.
@ -151,7 +152,7 @@ fun PocketStories(
ListItemTabLargePlaceholder(stringResource(R.string.pocket_stories_placeholder_text)) {
onDiscoverMoreClicked("https://getpocket.com/explore?$POCKET_FEATURE_UTM_KEY_VALUE")
}
} else {
} else if (story is PocketRecommendedStory) {
PocketStory(story) {
val uri = Uri.parse(story.url)
.buildUpon()
@ -284,12 +285,12 @@ private fun PocketStoriesComposablesPreview() {
}
}
private class PocketStoryProvider : PreviewParameterProvider<PocketRecommendedStory> {
private class PocketStoryProvider : PreviewParameterProvider<PocketStory> {
override val values = getFakePocketStories(7).asSequence()
override val count = 8
}
internal fun getFakePocketStories(limit: Int = 1): List<PocketRecommendedStory> {
internal fun getFakePocketStories(limit: Int = 1): List<PocketStory> {
return mutableListOf<PocketRecommendedStory>().apply {
for (index in 0 until limit) {
add(

@ -7,7 +7,8 @@ package org.mozilla.fenix.home.pocket
import androidx.annotation.VisibleForTesting
import androidx.navigation.NavController
import mozilla.components.service.glean.private.NoExtras
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.GleanMetrics.Pocket
import org.mozilla.fenix.HomeActivity
@ -16,15 +17,15 @@ import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
/**
* Contract for how all user interactions with the Pocket recommended stories feature are to be handled.
* Contract for how all user interactions with the Pocket stories feature are to be handled.
*/
interface PocketStoriesController {
/**
* Callback to decide what should happen as an effect of a new list of stories being shown.
*
* @param storiesShown the new list of [PocketRecommendedStory]es shown to the user.
* @param storiesShown the new list of [PocketStory]es shown to the user.
*/
fun handleStoriesShown(storiesShown: List<PocketRecommendedStory>)
fun handleStoriesShown(storiesShown: List<PocketStory>)
/**
* Callback allowing to handle a specific [PocketRecommendedStoriesCategory] being clicked by the user.
@ -36,10 +37,10 @@ interface PocketStoriesController {
/**
* Callback for when the user clicks on a specific story.
*
* @param storyClicked The just clicked [PocketRecommendedStory] URL.
* @param storyClicked The just clicked [PocketStory].
* @param storyPosition `row x column` matrix representing the grid position of the clicked story.
*/
fun handleStoryClicked(storyClicked: PocketRecommendedStory, storyPosition: Pair<Int, Int>)
fun handleStoryClicked(storyClicked: PocketStory, storyPosition: Pair<Int, Int>)
/**
* Callback for when the "Learn more" link is clicked.
@ -68,7 +69,7 @@ internal class DefaultPocketStoriesController(
private val appStore: AppStore,
private val navController: NavController,
) : PocketStoriesController {
override fun handleStoriesShown(storiesShown: List<PocketRecommendedStory>) {
override fun handleStoriesShown(storiesShown: List<PocketStory>) {
appStore.dispatch(AppAction.PocketStoriesShown(storiesShown))
Pocket.homeRecsShown.record(NoExtras())
}
@ -114,17 +115,19 @@ internal class DefaultPocketStoriesController(
}
override fun handleStoryClicked(
storyClicked: PocketRecommendedStory,
storyClicked: PocketStory,
storyPosition: Pair<Int, Int>
) {
dismissSearchDialogIfDisplayed()
homeActivity.openToBrowserAndLoad(storyClicked.url, true, BrowserDirection.FromHome)
Pocket.homeRecsStoryClicked.record(
Pocket.HomeRecsStoryClickedExtra(
position = "${storyPosition.first}x${storyPosition.second}",
timesShown = storyClicked.timesShown.inc().toString()
if (storyClicked is PocketRecommendedStory) {
Pocket.homeRecsStoryClicked.record(
Pocket.HomeRecsStoryClickedExtra(
position = "${storyPosition.first}x${storyPosition.second}",
timesShown = storyClicked.timesShown.inc().toString()
)
)
)
}
}
override fun handleLearnMoreClicked(link: String) {

@ -4,7 +4,8 @@
package org.mozilla.fenix.home.pocket
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
/**
* Contract for all possible user interactions with the Pocket recommended stories feature.
@ -15,7 +16,7 @@ interface PocketStoriesInteractor {
*
* @param storiesShown The new list of [PocketRecommendedStory]es shown to the user.
*/
fun onStoriesShown(storiesShown: List<PocketRecommendedStory>)
fun onStoriesShown(storiesShown: List<PocketStory>)
/**
* Callback for when the user clicks a specific category.
@ -27,10 +28,10 @@ interface PocketStoriesInteractor {
/**
* Callback for when the user clicks on a specific story.
*
* @param storyClicked The just clicked [PocketRecommendedStory] URL.
* @param storyClicked The just clicked [PocketStory].
* @param storyPosition `row x column` matrix representing the grid position of the clicked story.
*/
fun onStoryClicked(storyClicked: PocketRecommendedStory, storyPosition: Pair<Int, Int>)
fun onStoryClicked(storyClicked: PocketStory, storyPosition: Pair<Int, Int>)
/**
* Callback for when the user clicks the "Learn more" link.

@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.lib.state.ext.observeAsComposableState
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import org.mozilla.fenix.R
import org.mozilla.fenix.R.string
import org.mozilla.fenix.components.components

@ -7,7 +7,7 @@ package org.mozilla.fenix.home.sessioncontrol
import mozilla.components.feature.tab.collections.Tab
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.gleanplumb.Message
@ -18,11 +18,11 @@ import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
import org.mozilla.fenix.home.recentbookmarks.controller.RecentBookmarksController
import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTab
import org.mozilla.fenix.home.recentsyncedtabs.controller.RecentSyncedTabController
import org.mozilla.fenix.home.recentsyncedtabs.interactor.RecentSyncedTabInteractor
import org.mozilla.fenix.home.recenttabs.RecentTab
import org.mozilla.fenix.home.recenttabs.controller.RecentTabController
import org.mozilla.fenix.home.recentsyncedtabs.controller.RecentSyncedTabController
import org.mozilla.fenix.home.recenttabs.interactor.RecentTabInteractor
import org.mozilla.fenix.home.recentsyncedtabs.interactor.RecentSyncedTabInteractor
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem.RecentHistoryGroup
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem.RecentHistoryHighlight
import org.mozilla.fenix.home.recentvisits.controller.RecentVisitsController
@ -435,7 +435,7 @@ class SessionControlInteractor(
controller.handleCustomizeHomeTapped()
}
override fun onStoriesShown(storiesShown: List<PocketRecommendedStory>) {
override fun onStoriesShown(storiesShown: List<PocketStory>) {
pocketStoriesController.handleStoriesShown(storiesShown)
}
@ -443,7 +443,7 @@ class SessionControlInteractor(
pocketStoriesController.handleCategoryClick(categoryClicked)
}
override fun onStoryClicked(storyClicked: PocketRecommendedStory, storyPosition: Pair<Int, Int>) {
override fun onStoryClicked(storyClicked: PocketStory, storyPosition: Pair<Int, Int>) {
pocketStoriesController.handleStoryClicked(storyClicked, storyPosition)
}

@ -11,11 +11,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.gleanplumb.Message
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.gleanplumb.Message
import org.mozilla.fenix.home.Mode
import org.mozilla.fenix.home.OnboardingState
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
@ -38,7 +38,7 @@ internal fun normalModeAdapterItems(
nimbusMessageCard: Message? = null,
recentTabs: List<RecentTab>,
recentVisits: List<RecentlyVisitedItem>,
pocketStories: List<PocketRecommendedStory>
pocketStories: List<PocketStory>
): List<AdapterItem> {
val items = mutableListOf<AdapterItem>()
var shouldShowCustomizeHome = false

@ -14,7 +14,8 @@ import mozilla.components.concept.sync.DeviceType
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
@ -288,7 +289,7 @@ class AppStoreTest {
fun `Test selecting a Pocket recommendations category`() = runTest {
val otherStoriesCategory = PocketRecommendedStoriesCategory("other")
val anotherStoriesCategory = PocketRecommendedStoriesCategory("another")
val filteredStories = listOf(mockk<PocketRecommendedStory>())
val filteredStories = listOf(mockk<PocketStory>())
appStore = AppStore(
AppState(
pocketStoriesCategories = listOf(otherStoriesCategory, anotherStoriesCategory),
@ -316,7 +317,7 @@ class AppStoreTest {
fun `Test deselecting a Pocket recommendations category`() = runTest {
val otherStoriesCategory = PocketRecommendedStoriesCategory("other")
val anotherStoriesCategory = PocketRecommendedStoriesCategory("another")
val filteredStories = listOf(mockk<PocketRecommendedStory>())
val filteredStories = listOf(mockk<PocketStory>())
appStore = AppStore(
AppState(
pocketStoriesCategories = listOf(otherStoriesCategory, anotherStoriesCategory),
@ -363,7 +364,7 @@ class AppStoreTest {
appStore = AppStore(AppState())
mockkStatic("org.mozilla.fenix.ext.AppStateKt") {
val firstFilteredStories = listOf(mockk<PocketRecommendedStory>())
val firstFilteredStories = listOf(mockk<PocketStory>())
every { any<AppState>().getFilteredStories() } returns firstFilteredStories
appStore.dispatch(
@ -378,7 +379,7 @@ class AppStoreTest {
assertSame(firstFilteredStories, appStore.state.pocketStories)
val updatedCategories = listOf(PocketRecommendedStoriesCategory("yetAnother"))
val secondFilteredStories = listOf(mockk<PocketRecommendedStory>())
val secondFilteredStories = listOf(mockk<PocketStory>())
every { any<AppState>().getFilteredStories() } returns secondFilteredStories
appStore.dispatch(
AppAction.PocketStoriesCategoriesChange(
@ -399,7 +400,7 @@ class AppStoreTest {
appStore = AppStore(AppState())
mockkStatic("org.mozilla.fenix.ext.AppStateKt") {
val firstFilteredStories = listOf(mockk<PocketRecommendedStory>())
val firstFilteredStories = listOf(mockk<PocketStory>())
every { any<AppState>().getFilteredStories() } returns firstFilteredStories
appStore.dispatch(

@ -5,7 +5,9 @@
package org.mozilla.fenix.ext
import io.mockk.mockk
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory
import mozilla.components.service.pocket.PocketStory.PocketSponsoredStoryShim
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
@ -29,7 +31,7 @@ class AppStateTest {
)
@Test
fun `GIVEN no category is selected WHEN getFilteredStories is called THEN only Pocket stories from the default category are returned`() {
fun `GIVEN no category is selected and no sponsored stories are available WHEN getFilteredStories is called THEN only Pocket stories from the default category are returned`() {
val state = AppState(
pocketStoriesCategories = listOf(
otherStoriesCategory, anotherStoriesCategory, defaultStoriesCategory
@ -38,11 +40,15 @@ class AppStateTest {
val result = state.getFilteredStories()
assertNull(result.firstOrNull { it.category != POCKET_STORIES_DEFAULT_CATEGORY_NAME })
assertNull(
result.firstOrNull {
it is PocketRecommendedStory && it.category != POCKET_STORIES_DEFAULT_CATEGORY_NAME
}
)
}
@Test
fun `GIVEN no category is selected WHEN getFilteredStories is called THEN no more than the default stories number are returned from the default category`() {
fun `GIVEN no category is selected and no sponsored stories are available WHEN getFilteredStories is called THEN no more than the default stories number are returned from the default category`() {
val defaultStoriesCategoryWithManyStories = PocketRecommendedStoriesCategory(
POCKET_STORIES_DEFAULT_CATEGORY_NAME,
getFakePocketStories(POCKET_STORIES_TO_SHOW_COUNT + 2)
@ -58,6 +64,63 @@ class AppStateTest {
assertEquals(POCKET_STORIES_TO_SHOW_COUNT, result.size)
}
@Test
fun `GIVEN no category is selected and 1 sponsored story available WHEN getFilteredStories is called THEN get stories from the default category combined with the sponsored one`() {
val defaultStoriesCategoryWithManyStories = PocketRecommendedStoriesCategory(
POCKET_STORIES_DEFAULT_CATEGORY_NAME,
getFakePocketStories(POCKET_STORIES_TO_SHOW_COUNT)
)
val sponsoredStories = getFakeSponsoredStories(1)
val state = AppState(
pocketStoriesCategories = listOf(
otherStoriesCategory, anotherStoriesCategory, defaultStoriesCategoryWithManyStories
),
pocketSponsoredStories = sponsoredStories
)
val result = state.getFilteredStories().toMutableList()
assertEquals(POCKET_STORIES_TO_SHOW_COUNT, result.size)
assertEquals(sponsoredStories[0], result[1]) // second story should be a sponsored one
result.removeAt(1) // remove the sponsored story to hopefully only remain with general recommendations
assertNull(
result.firstOrNull {
it is PocketRecommendedStory && it.category != POCKET_STORIES_DEFAULT_CATEGORY_NAME
}
)
}
@Test
fun `GIVEN no category is selected and 2 sponsored stories available WHEN getFilteredStories is called THEN get stories from the default category combined with the sponsored stories`() {
val defaultStoriesCategoryWithManyStories = PocketRecommendedStoriesCategory(
POCKET_STORIES_DEFAULT_CATEGORY_NAME,
getFakePocketStories(POCKET_STORIES_TO_SHOW_COUNT)
)
val sponsoredStories = getFakeSponsoredStories(2)
val state = AppState(
pocketStoriesCategories = listOf(
otherStoriesCategory, anotherStoriesCategory, defaultStoriesCategoryWithManyStories
),
pocketSponsoredStories = sponsoredStories
)
val result = state.getFilteredStories().toMutableList()
assertEquals(POCKET_STORIES_TO_SHOW_COUNT, result.size)
// second story should be a sponsored one
assertEquals(sponsoredStories[0], result[1])
// last story should be a sponsored one
assertEquals(sponsoredStories[1], result[POCKET_STORIES_TO_SHOW_COUNT - 1])
// remove the sponsored stories to hopefully only remain with general recommendations
result.removeAt(7)
result.removeAt(1)
assertNull(
result.firstOrNull {
it is PocketRecommendedStory && it.category != POCKET_STORIES_DEFAULT_CATEGORY_NAME
}
)
}
@Test
fun `GIVEN a category is selected WHEN getFilteredStories is called THEN only stories from that category are returned`() {
val state = AppState(
@ -67,7 +130,11 @@ class AppStateTest {
val result = state.getFilteredStories()
assertNull(result.firstOrNull { it.category != otherStoriesCategory.name })
assertNull(
result.firstOrNull {
it is PocketRecommendedStory && it.category != otherStoriesCategory.name
}
)
}
@Test
@ -103,7 +170,9 @@ class AppStateTest {
assertEquals(6, result.size)
assertNull(
result.firstOrNull {
it.category != otherStoriesCategory.name && it.category != anotherStoriesCategory.name
it is PocketRecommendedStory &&
it.category != otherStoriesCategory.name &&
it.category != anotherStoriesCategory.name
}
)
}
@ -262,7 +331,11 @@ class AppStateTest {
val result = state.getFilteredStories()
assertEquals(3, result.size)
assertNull(result.firstOrNull { it.category != anotherStoriesCategory.name })
assertNull(
result.firstOrNull {
it is PocketRecommendedStory && it.category != anotherStoriesCategory.name
}
)
}
@Test
@ -306,3 +379,20 @@ private fun getFakePocketStories(
}
}
}
private fun getFakeSponsoredStories(limit: Int) = mutableListOf<PocketSponsoredStory>().apply {
for (index in 0 until limit) {
add(
PocketSponsoredStory(
title = "Story title $index",
url = "https://sponsored.story",
imageUrl = "https://sponsored.image",
sponsor = "Sponsor $index",
shim = PocketSponsoredStoryShim(
click = "Story title $index click shim",
impression = "Story title $index impression shim"
)
)
)
}
}

@ -13,8 +13,8 @@ import io.mockk.verify
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStoriesService
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.rule.MainCoroutineRule
import mozilla.components.support.test.rule.runTestOnMain
@ -34,7 +34,9 @@ class PocketUpdatesMiddlewareTest {
@Test
fun `WHEN PocketStoriesShown is dispatched THEN update PocketStoriesService`() = runTestOnMain {
val story1 = PocketRecommendedStory("title", "url1", "imageUrl", "publisher", "category", 0, timesShown = 0)
val story1 = PocketRecommendedStory(
"title", "url1", "imageUrl", "publisher", "category", 0, timesShown = 0
)
val story2 = story1.copy("title2", "url2")
val story3 = story1.copy("title3", "url3")
val pocketService: PocketStoriesService = mockk(relaxed = true)

@ -9,7 +9,7 @@ import io.mockk.mockk
import io.mockk.verify
import mozilla.components.feature.tab.collections.Tab
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
@ -239,7 +239,7 @@ class SessionControlInteractorTest {
@Test
fun `GIVEN a PocketStoriesInteractor WHEN stories are shown THEN handle it in a PocketStoriesController`() {
val shownStories: List<PocketRecommendedStory> = mockk()
val shownStories: List<PocketStory> = mockk()
interactor.onStoriesShown(shownStories)
@ -257,7 +257,7 @@ class SessionControlInteractorTest {
@Test
fun `GIVEN a PocketStoriesInteractor WHEN a story is clicked THEN handle it in a PocketStoriesController`() {
val clickedStory: PocketRecommendedStory = mockk()
val clickedStory: PocketStory = mockk()
val storyGridLocation = 1 to 2
interactor.onStoryClicked(clickedStory, storyGridLocation)

@ -10,7 +10,9 @@ import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verifyOrder
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory
import mozilla.components.support.test.robolectric.testContext
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
@ -146,7 +148,7 @@ class DefaultPocketStoriesControllerTest {
fun `WHEN new stories are shown THEN update the State and record telemetry`() {
val store = spyk(AppStore())
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
val storiesShown: List<PocketRecommendedStory> = mockk()
val storiesShown: List<PocketStory> = mockk()
assertFalse(Pocket.homeRecsShown.testHasValue())
controller.handleStoriesShown(storiesShown)
@ -158,7 +160,7 @@ class DefaultPocketStoriesControllerTest {
}
@Test
fun `WHEN a story is clicked then open that story's url using HomeActivity and record telemetry`() {
fun `WHEN a recommended story is clicked THEN open that story's url using HomeActivity and record telemetry`() {
val story = PocketRecommendedStory(
title = "",
url = "testLink",
@ -185,6 +187,25 @@ class DefaultPocketStoriesControllerTest {
assertEquals(story.timesShown.inc().toString(), event.single().extra!!["times_shown"])
}
@Test
fun `WHEN a sponsored story is clicked THEN open that story's url using HomeActivity and don't record telemetry`() {
val story = PocketSponsoredStory(
title = "",
url = "testLink",
imageUrl = "",
sponsor = "",
shim = mockk()
)
val homeActivity: HomeActivity = mockk(relaxed = true)
val controller = DefaultPocketStoriesController(homeActivity, mockk(), mockk(relaxed = true))
assertFalse(Pocket.homeRecsStoryClicked.testHasValue())
controller.handleStoryClicked(story, 1 to 2)
verify { homeActivity.openToBrowserAndLoad(story.url, true, BrowserDirection.FromHome) }
assertFalse(Pocket.homeRecsStoryClicked.testHasValue())
}
@Test
fun `WHEN discover more is clicked then open that using HomeActivity and record telemetry`() {
val link = "http://getpocket.com/explore"

@ -10,7 +10,8 @@ import io.mockk.mockk
import io.mockk.verify
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@ -67,24 +68,24 @@ class SessionControlViewTest {
@Test
fun `GIVEN pocketArticles WHEN calling shouldShowHomeOnboardingDialog THEN show the dialog `() {
val pocketArticles = listOf(PocketRecommendedStory("", "", "", "", "", 0, 0))
val pocketStories = listOf(PocketRecommendedStory("", "", "", "", "", 0, 0))
val settings: Settings = mockk()
every { settings.hasShownHomeOnboardingDialog } returns false
val state = AppState(pocketStories = pocketArticles)
val state = AppState(pocketStories = pocketStories)
assertTrue(state.shouldShowHomeOnboardingDialog(settings))
}
@Test
fun `GIVEN the home onboading dialog has been shown before WHEN calling shouldShowHomeOnboardingDialog THEN DO NOT showthe dialog `() {
val pocketArticles = listOf(PocketRecommendedStory("", "", "", "", "", 0, 0))
val pocketStories = listOf(PocketRecommendedStory("", "", "", "", "", 0, 0))
val settings: Settings = mockk()
every { settings.hasShownHomeOnboardingDialog } returns true
val state = AppState(pocketStories = pocketArticles)
val state = AppState(pocketStories = pocketStories)
assertFalse(state.shouldShowHomeOnboardingDialog(settings))
}
@ -139,7 +140,7 @@ class SessionControlViewTest {
val recentBookmarks = listOf(RecentBookmark())
val recentTabs = emptyList<RecentTab.Tab>()
val historyMetadata = emptyList<RecentHistoryGroup>()
val pocketArticles = emptyList<PocketRecommendedStory>()
val pocketStories = emptyList<PocketStory>()
every { settings.showTopSitesFeature } returns true
every { settings.showRecentTabsFeature } returns true
@ -157,7 +158,7 @@ class SessionControlViewTest {
null,
recentTabs,
historyMetadata,
pocketArticles
pocketStories
)
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
@ -175,7 +176,7 @@ class SessionControlViewTest {
val recentBookmarks = listOf(RecentBookmark())
val recentTabs = emptyList<RecentTab.Tab>()
val historyMetadata = emptyList<RecentHistoryGroup>()
val pocketArticles = emptyList<PocketRecommendedStory>()
val pocketStories = emptyList<PocketStory>()
val nimbusMessageCard: Message = mockk()
every { settings.showTopSitesFeature } returns true
@ -194,7 +195,7 @@ class SessionControlViewTest {
nimbusMessageCard,
recentTabs,
historyMetadata,
pocketArticles
pocketStories
)
assertTrue(results.contains(AdapterItem.NimbusMessageCard(nimbusMessageCard)))
@ -209,7 +210,7 @@ class SessionControlViewTest {
val recentBookmarks = listOf<RecentBookmark>()
val recentTabs = listOf<RecentTab.Tab>(mockk())
val historyMetadata = emptyList<RecentHistoryGroup>()
val pocketArticles = emptyList<PocketRecommendedStory>()
val pocketStories = emptyList<PocketStory>()
every { settings.showTopSitesFeature } returns true
every { settings.showRecentTabsFeature } returns true
@ -227,7 +228,7 @@ class SessionControlViewTest {
null,
recentTabs,
historyMetadata,
pocketArticles
pocketStories
)
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
@ -245,7 +246,7 @@ class SessionControlViewTest {
val recentBookmarks = listOf<RecentBookmark>()
val recentTabs = emptyList<RecentTab.Tab>()
val historyMetadata = listOf(RecentHistoryGroup("title", emptyList()))
val pocketArticles = emptyList<PocketRecommendedStory>()
val pocketStories = emptyList<PocketStory>()
every { settings.showTopSitesFeature } returns true
every { settings.showRecentTabsFeature } returns true
@ -263,7 +264,7 @@ class SessionControlViewTest {
null,
recentTabs,
historyMetadata,
pocketArticles
pocketStories
)
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
@ -281,7 +282,7 @@ class SessionControlViewTest {
val recentBookmarks = listOf<RecentBookmark>()
val recentTabs = emptyList<RecentTab.Tab>()
val historyMetadata = emptyList<RecentHistoryGroup>()
val pocketArticles = listOf(PocketRecommendedStory("", "", "", "", "", 1, 1))
val pocketStories = listOf(PocketRecommendedStory("", "", "", "", "", 1, 1))
every { settings.showTopSitesFeature } returns true
every { settings.showRecentTabsFeature } returns true
@ -299,7 +300,7 @@ class SessionControlViewTest {
null,
recentTabs,
historyMetadata,
pocketArticles
pocketStories
)
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
@ -318,7 +319,7 @@ class SessionControlViewTest {
val recentBookmarks = listOf<RecentBookmark>()
val recentTabs = emptyList<RecentTab.Tab>()
val historyMetadata = emptyList<RecentHistoryGroup>()
val pocketArticles = emptyList<PocketRecommendedStory>()
val pocketStories = emptyList<PocketStory>()
every { settings.showTopSitesFeature } returns true
every { settings.showRecentTabsFeature } returns true
@ -336,7 +337,7 @@ class SessionControlViewTest {
null,
recentTabs,
historyMetadata,
pocketArticles
pocketStories
)
assertEquals(results.size, 2)
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
@ -354,7 +355,7 @@ class SessionControlViewTest {
val recentBookmarks = listOf<RecentBookmark>(mockk())
val recentTabs = listOf<RecentTab.Tab>(mockk())
val historyMetadata = listOf<RecentHistoryGroup>(mockk())
val pocketArticles = listOf<PocketRecommendedStory>(mockk())
val pocketStories = listOf<PocketStory>(mockk())
every { settings.showTopSitesFeature } returns true
every { settings.showRecentTabsFeature } returns true
@ -372,7 +373,7 @@ class SessionControlViewTest {
null,
recentTabs,
historyMetadata,
pocketArticles
pocketStories
)
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)

Loading…
Cancel
Save