For #21593 - Refactor out "isSelected" from PocketRecommendedStoriesCategory

Having the list of categories and the list of selected categories separate in
State allows updating them independently.
upstream-sync
Mugurell 3 years ago committed by mergify[bot]
parent 84c61e24a7
commit 565beb88c9

@ -8,7 +8,7 @@ import androidx.annotation.VisibleForTesting
import mozilla.components.service.pocket.PocketRecommendedStory
import org.mozilla.fenix.home.HomeFragmentState
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.POCKET_STORIES_DEFAULT_CATEGORY_NAME
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoryCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoriesCategory
/**
* Get the list of stories to be displayed based on the user selected categories.
@ -21,9 +21,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommende
fun HomeFragmentState.getFilteredStories(
neededStoriesCount: Int
): List<PocketRecommendedStory> {
val currentlySelectedCategories = pocketStoriesCategories.filter { it.isSelected }
if (currentlySelectedCategories.isEmpty()) {
if (pocketStoriesCategoriesSelections.isEmpty()) {
return pocketStoriesCategories
.find {
it.name == POCKET_STORIES_DEFAULT_CATEGORY_NAME
@ -32,8 +30,13 @@ fun HomeFragmentState.getFilteredStories(
?.take(neededStoriesCount) ?: emptyList()
}
val oldestSortedCategories = currentlySelectedCategories
.sortedByDescending { it.lastInteractedWithTimestamp }
val oldestSortedCategories = pocketStoriesCategoriesSelections
.sortedByDescending { it.selectionTimestamp }
.map { selectedCategory ->
pocketStoriesCategories.first {
it.name == selectedCategory.name
}
}
val filteredStoriesCount = getFilteredStoriesCount(
oldestSortedCategories, neededStoriesCount
@ -57,7 +60,7 @@ fun HomeFragmentState.getFilteredStories(
@VisibleForTesting
@Suppress("ReturnCount", "NestedBlockDepth")
internal fun getFilteredStoriesCount(
selectedCategories: List<PocketRecommendedStoryCategory>,
selectedCategories: List<PocketRecommendedStoriesCategory>,
neededStoriesCount: Int
): Map<String, Int> {
val totalStoriesInFilteredCategories = selectedCategories.fold(0) { availableStories, category ->

@ -116,7 +116,7 @@ import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
import org.mozilla.fenix.home.sessioncontrol.SessionControlView
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.DefaultPocketStoriesController
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoryCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.topsites.DefaultTopSitesView
import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.settings.SupportUtils
@ -258,7 +258,7 @@ class HomeFragment : Fragment() {
if (requireContext().settings().showPocketRecommendationsFeature) {
val categories = components.core.pocketStoriesService.getStories()
.groupBy { story -> story.category }
.map { (category, stories) -> PocketRecommendedStoryCategory(category, stories) }
.map { (category, stories) -> PocketRecommendedStoriesCategory(category, stories) }
homeFragmentStore.dispatch(HomeFragmentAction.PocketStoriesCategoriesChange(categories))
} else {

@ -18,7 +18,8 @@ import org.mozilla.fenix.ext.getFilteredStories
import org.mozilla.fenix.historymetadata.HistoryMetadataGroup
import org.mozilla.fenix.home.recenttabs.RecentTab
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.POCKET_STORIES_TO_SHOW_COUNT
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoryCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoriesSelectedCategory
/**
* The [Store] for holding the [HomeFragmentState] and applying [HomeFragmentAction]s.
@ -69,7 +70,8 @@ data class HomeFragmentState(
val recentBookmarks: List<BookmarkNode> = emptyList(),
val historyMetadata: List<HistoryMetadataGroup> = emptyList(),
val pocketStories: List<PocketRecommendedStory> = emptyList(),
val pocketStoriesCategories: List<PocketRecommendedStoryCategory> = emptyList()
val pocketStoriesCategories: List<PocketRecommendedStoriesCategory> = emptyList(),
val pocketStoriesCategoriesSelections: List<PocketRecommendedStoriesSelectedCategory> = emptyList()
) : State
sealed class HomeFragmentAction : Action {
@ -99,7 +101,7 @@ sealed class HomeFragmentAction : Action {
data class DeselectPocketStoriesCategory(val categoryName: String) : HomeFragmentAction()
data class PocketStoriesShown(val storiesShown: List<PocketRecommendedStory>) : HomeFragmentAction()
data class PocketStoriesChange(val pocketStories: List<PocketRecommendedStory>) : HomeFragmentAction()
data class PocketStoriesCategoriesChange(val storiesCategories: List<PocketRecommendedStoryCategory>) :
data class PocketStoriesCategoriesChange(val storiesCategories: List<PocketRecommendedStoriesCategory>) :
HomeFragmentAction()
object RemoveCollectionsPlaceholder : HomeFragmentAction()
object RemoveSetDefaultBrowserCard : HomeFragmentAction()
@ -145,29 +147,26 @@ private fun homeFragmentStateReducer(
is HomeFragmentAction.RecentBookmarksChange -> state.copy(recentBookmarks = action.recentBookmarks)
is HomeFragmentAction.HistoryMetadataChange -> state.copy(historyMetadata = action.historyMetadata)
is HomeFragmentAction.SelectPocketStoriesCategory -> {
// Selecting a category means the stories to be displayed needs to also be changed.
val updatedCategoriesState = state.copy(
pocketStoriesCategories = state.pocketStoriesCategories.map {
when (it.name == action.categoryName) {
true -> it.copy(isSelected = true, lastInteractedWithTimestamp = System.currentTimeMillis())
false -> it
}
}
pocketStoriesCategoriesSelections =
state.pocketStoriesCategoriesSelections + PocketRecommendedStoriesSelectedCategory(
name = action.categoryName
)
)
// Selecting a category means the stories to be displayed needs to also be changed.
return updatedCategoriesState.copy(
pocketStories = updatedCategoriesState.getFilteredStories(POCKET_STORIES_TO_SHOW_COUNT)
)
}
is HomeFragmentAction.DeselectPocketStoriesCategory -> {
val updatedCategoriesState = state.copy(
// Deselecting a category means the stories to be displayed needs to also be changed.
pocketStoriesCategories = state.pocketStoriesCategories.map {
when (it.name == action.categoryName) {
true -> it.copy(isSelected = false, lastInteractedWithTimestamp = System.currentTimeMillis())
false -> it
}
pocketStoriesCategoriesSelections = state.pocketStoriesCategoriesSelections.filterNot {
it.name == action.categoryName
}
)
// Deselecting a category means the stories to be displayed needs to also be changed.
return updatedCategoriesState.copy(
pocketStories = updatedCategoriesState.getFilteredStories(POCKET_STORIES_TO_SHOW_COUNT)
)

@ -18,7 +18,7 @@ import org.mozilla.fenix.home.recentbookmarks.controller.RecentBookmarksControll
import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor
import org.mozilla.fenix.home.recenttabs.controller.RecentTabController
import org.mozilla.fenix.home.recenttabs.interactor.RecentTabInteractor
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoryCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketStoriesController
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketStoriesInteractor
@ -391,7 +391,7 @@ class SessionControlInteractor(
controller.handleCustomizeHomeTapped()
}
override fun onCategoryClick(categoryClicked: PocketRecommendedStoryCategory) {
override fun onCategoryClick(categoryClicked: PocketRecommendedStoriesCategory) {
pocketStoriesController.handleCategoryClick(categoryClicked)
}

@ -13,18 +13,15 @@ import mozilla.components.service.pocket.PocketRecommendedStory
const val POCKET_STORIES_DEFAULT_CATEGORY_NAME = "general"
/**
* Pocket assigned topic of interest for each story.
* In memory cache of Pocket assigned topic of interest for recommended stories.
* Avoids multiple stories mappings for each time we are interested in their categories.
*
* One to many relationship with [PocketRecommendedStory]es.
*
* @property name The exact name of each category. Case sensitive.
* @property stories All [PocketRecommendedStory]es with this category.
* @property isSelected Whether this category is currently selected by the user.
* @property lastInteractedWithTimestamp Last time the user selected or deselected this category.
* @property stories All [PocketRecommendedStory]s with this category.
*/
data class PocketRecommendedStoryCategory(
data class PocketRecommendedStoriesCategory(
val name: String,
val stories: List<PocketRecommendedStory> = emptyList(),
val isSelected: Boolean = false,
val lastInteractedWithTimestamp: Long = 0L
val stories: List<PocketRecommendedStory> = emptyList()
)

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.home.sessioncontrol.viewholders.pocket
/**
* Details about a selected Pocket recommended stories category.
*
* @property name The exact name of a selected category. Case sensitive.
* @property selectionTimestamp The exact time at which a category was selected. Defaults to [System.currentTimeMillis].
*/
data class PocketRecommendedStoriesSelectedCategory(
val name: String,
val selectionTimestamp: Long = System.currentTimeMillis()
)

@ -150,21 +150,22 @@ fun PocketStories(
}
/**
* Displays a list of [PocketRecommendedStoryCategory].
* Displays a list of [PocketRecommendedStoriesCategory]s.
*
* @param categories The categories needed to be displayed.
* @param onCategoryClick Callback for when the user taps a category.
*/
@Composable
fun PocketStoriesCategories(
categories: List<PocketRecommendedStoryCategory>,
onCategoryClick: (PocketRecommendedStoryCategory) -> Unit
categories: List<PocketRecommendedStoriesCategory>,
selections: List<PocketRecommendedStoriesSelectedCategory>,
onCategoryClick: (PocketRecommendedStoriesCategory) -> Unit
) {
StaggeredHorizontalGrid(
horizontalItemsSpacing = 16.dp
) {
categories.filter { it.name != POCKET_STORIES_DEFAULT_CATEGORY_NAME }.forEach { category ->
SelectableChip(category.name, category.isSelected) {
SelectableChip(category.name, selections.map { it.name }.contains(category.name)) {
onCategoryClick(category)
}
}
@ -241,8 +242,9 @@ private fun PocketStoriesComposablesPreview() {
PocketStoriesCategories(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor".split(" ").map {
PocketRecommendedStoryCategory(it)
}
PocketRecommendedStoriesCategory(it)
},
emptyList()
) { }
Spacer(Modifier.height(10.dp))

@ -19,11 +19,11 @@ import org.mozilla.fenix.R
*/
interface PocketStoriesController {
/**
* Callback allowing to handle a specific [PocketRecommendedStoryCategory] being clicked by the user.
* Callback allowing to handle a specific [PocketRecommendedStoriesCategory] being clicked by the user.
*
* @param categoryClicked the just clicked [PocketRecommendedStoryCategory].
* @param categoryClicked the just clicked [PocketRecommendedStoriesCategory].
*/
fun handleCategoryClick(categoryClicked: PocketRecommendedStoryCategory): Unit
fun handleCategoryClick(categoryClicked: PocketRecommendedStoriesCategory): Unit
/**
* Callback to decide what should happen as an effect of a new list of stories being shown.
@ -52,30 +52,20 @@ internal class DefaultPocketStoriesController(
private val homeStore: HomeFragmentStore,
private val navController: NavController
) : PocketStoriesController {
override fun handleCategoryClick(categoryClicked: PocketRecommendedStoryCategory) {
val allCategories = homeStore.state.pocketStoriesCategories
override fun handleCategoryClick(categoryClicked: PocketRecommendedStoriesCategory) {
val initialCategoriesSelections = homeStore.state.pocketStoriesCategoriesSelections
// First check whether the category is clicked to be deselected.
if (categoryClicked.isSelected) {
if (initialCategoriesSelections.map { it.name }.contains(categoryClicked.name)) {
homeStore.dispatch(HomeFragmentAction.DeselectPocketStoriesCategory(categoryClicked.name))
return
}
// If a new category is clicked to be selected:
// Ensure the number of categories selected at a time is capped.
val currentlySelectedCategoriesCount = allCategories.fold(0) { count, category ->
if (category.isSelected) count + 1 else count
}
val oldestCategoryToDeselect =
if (currentlySelectedCategoriesCount == POCKET_CATEGORIES_SELECTED_AT_A_TIME_COUNT) {
allCategories
.filter { it.isSelected }
.reduce { oldestSelected, category ->
when (oldestSelected.lastInteractedWithTimestamp <= category.lastInteractedWithTimestamp) {
true -> oldestSelected
false -> category
}
}
if (initialCategoriesSelections.size == POCKET_CATEGORIES_SELECTED_AT_A_TIME_COUNT) {
initialCategoriesSelections.minByOrNull { it.selectionTimestamp }
} else {
null
}

@ -13,9 +13,9 @@ interface PocketStoriesInteractor {
/**
* Callback for when the user clicked a specific category.
*
* @param categoryClicked the just clicked [PocketRecommendedStoryCategory].
* @param categoryClicked the just clicked [PocketRecommendedStoriesCategory].
*/
fun onCategoryClick(categoryClicked: PocketRecommendedStoryCategory)
fun onCategoryClick(categoryClicked: PocketRecommendedStoriesCategory)
/**
* Callback for then new stories are shown to the user.

@ -72,7 +72,7 @@ fun PocketStories(
store: HomeFragmentStore,
client: Client,
onStoriesShown: (List<PocketRecommendedStory>) -> Unit,
onCategoryClick: (PocketRecommendedStoryCategory) -> Unit,
onCategoryClick: (PocketRecommendedStoriesCategory) -> Unit,
onExternalLinkClicked: (String) -> Unit
) {
val stories = store
@ -81,6 +81,9 @@ fun PocketStories(
val categories = store
.observeAsComposableState { state -> state.pocketStoriesCategories }.value
val categoriesSelections = store
.observeAsComposableState { state -> state.pocketStoriesCategoriesSelections }.value
LaunchedEffect(stories) {
// We should report back when a certain story is actually being displayed.
// Cannot do it reliably so for now we'll just mass report everything as being displayed.
@ -109,7 +112,10 @@ fun PocketStories(
Spacer(Modifier.height(17.dp))
PocketStoriesCategories(categories ?: emptyList()) {
PocketStoriesCategories(
categories = categories ?: emptyList(),
selections = categoriesSelections ?: emptyList()
) {
onCategoryClick(it)
}

@ -12,15 +12,16 @@ import org.junit.Assert.assertTrue
import org.junit.Test
import org.mozilla.fenix.home.HomeFragmentState
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.POCKET_STORIES_DEFAULT_CATEGORY_NAME
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoryCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoriesSelectedCategory
import kotlin.random.Random
class HomeFragmentStateTest {
private val otherStoriesCategory =
PocketRecommendedStoryCategory("other", getFakePocketStories(3, "other"))
PocketRecommendedStoriesCategory("other", getFakePocketStories(3, "other"))
private val anotherStoriesCategory =
PocketRecommendedStoryCategory("another", getFakePocketStories(3, "another"))
private val defaultStoriesCategory = PocketRecommendedStoryCategory(
PocketRecommendedStoriesCategory("another", getFakePocketStories(3, "another"))
private val defaultStoriesCategory = PocketRecommendedStoriesCategory(
POCKET_STORIES_DEFAULT_CATEGORY_NAME,
getFakePocketStories(3)
)
@ -60,9 +61,8 @@ class HomeFragmentStateTest {
@Test
fun `GIVEN a category is selected WHEN getFilteredStories is called for fewer than in the category THEN only stories from that category are returned`() {
val homeState = HomeFragmentState(
pocketStoriesCategories = listOf(
otherStoriesCategory.copy(isSelected = true), anotherStoriesCategory, defaultStoriesCategory
)
pocketStoriesCategories = listOf(otherStoriesCategory, anotherStoriesCategory, defaultStoriesCategory),
pocketStoriesCategoriesSelections = listOf(PocketRecommendedStoriesSelectedCategory(otherStoriesCategory.name))
)
var result = homeState.getFilteredStories(2)
@ -77,10 +77,10 @@ class HomeFragmentStateTest {
@Test
fun `GIVEN two categories are selected WHEN getFilteredStories is called for fewer than in both THEN only stories from those categories are returned`() {
val homeState = HomeFragmentState(
pocketStoriesCategories = listOf(
otherStoriesCategory.copy(isSelected = true),
anotherStoriesCategory.copy(isSelected = true),
defaultStoriesCategory
pocketStoriesCategories = listOf(otherStoriesCategory, anotherStoriesCategory, defaultStoriesCategory),
pocketStoriesCategoriesSelections = listOf(
PocketRecommendedStoriesSelectedCategory(otherStoriesCategory.name),
PocketRecommendedStoriesSelectedCategory(anotherStoriesCategory.name)
)
)
@ -103,19 +103,19 @@ class HomeFragmentStateTest {
@Test
fun `GIVEN two categories are selected WHEN getFilteredStories is called for an odd number of stories THEN there are more by one stories from the newest category`() {
val firstSelectedCategory = otherStoriesCategory.copy(lastInteractedWithTimestamp = 0, isSelected = true)
val lastSelectedCategory = anotherStoriesCategory.copy(lastInteractedWithTimestamp = 1, isSelected = true)
val homeState = HomeFragmentState(
pocketStoriesCategories = listOf(
firstSelectedCategory, lastSelectedCategory, defaultStoriesCategory
pocketStoriesCategories = listOf(otherStoriesCategory, anotherStoriesCategory, defaultStoriesCategory),
pocketStoriesCategoriesSelections = listOf(
PocketRecommendedStoriesSelectedCategory(otherStoriesCategory.name, selectionTimestamp = 0),
PocketRecommendedStoriesSelectedCategory(anotherStoriesCategory.name, selectionTimestamp = 1)
)
)
val result = homeState.getFilteredStories(5)
assertEquals(5, result.size)
assertEquals(2, result.filter { it.category == firstSelectedCategory.name }.size)
assertEquals(3, result.filter { it.category == lastSelectedCategory.name }.size)
assertEquals(2, result.filter { it.category == otherStoriesCategory.name }.size)
assertEquals(3, result.filter { it.category == anotherStoriesCategory.name }.size)
}
@Test
@ -209,8 +209,8 @@ class HomeFragmentStateTest {
@Test
fun `GIVEN two categories selected with more than needed stories WHEN getFilteredStories is called THEN the results are sorted in the order of least shown`() {
val firstCategory = PocketRecommendedStoryCategory(
"first", getFakePocketStories(3, "first"), true, 0
val firstCategory = PocketRecommendedStoriesCategory(
"first", getFakePocketStories(3, "first")
).run {
// Avoid the first item also being the oldest to eliminate a potential bug in code
// that would still get the expected result.
@ -224,8 +224,8 @@ class HomeFragmentStateTest {
}
)
}
val secondCategory = PocketRecommendedStoryCategory(
"second", getFakePocketStories(3, "second"), true, 222
val secondCategory = PocketRecommendedStoriesCategory(
"second", getFakePocketStories(3, "second")
).run {
// Avoid the first item also being the oldest to eliminate a potential bug in code
// that would still get the expected result.
@ -240,7 +240,13 @@ class HomeFragmentStateTest {
)
}
val homeState = HomeFragmentState(pocketStoriesCategories = listOf(firstCategory, secondCategory))
val homeState = HomeFragmentState(
pocketStoriesCategories = listOf(firstCategory, secondCategory),
pocketStoriesCategoriesSelections = listOf(
PocketRecommendedStoriesSelectedCategory(firstCategory.name, selectionTimestamp = 0),
PocketRecommendedStoriesSelectedCategory(secondCategory.name, selectionTimestamp = 222)
)
)
val result = homeState.getFilteredStories(6)

@ -28,7 +28,8 @@ import org.mozilla.fenix.ext.getFilteredStories
import org.mozilla.fenix.historymetadata.HistoryMetadataGroup
import org.mozilla.fenix.home.recenttabs.RecentTab
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.POCKET_STORIES_TO_SHOW_COUNT
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoryCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoriesSelectedCategory
import org.mozilla.fenix.onboarding.FenixOnboarding
class HomeFragmentStoreTest {
@ -189,8 +190,8 @@ class HomeFragmentStoreTest {
@Test
fun `Test selecting a Pocket recommendations category`() = runBlocking {
val otherStoriesCategory = PocketRecommendedStoryCategory("other")
val anotherStoriesCategory = PocketRecommendedStoryCategory("another")
val otherStoriesCategory = PocketRecommendedStoriesCategory("other")
val anotherStoriesCategory = PocketRecommendedStoriesCategory("another")
val filteredStories = listOf(mockk<PocketRecommendedStory>())
homeFragmentStore = HomeFragmentStore(
HomeFragmentState(
@ -208,7 +209,7 @@ class HomeFragmentStoreTest {
verify { any<HomeFragmentState>().getFilteredStories(POCKET_STORIES_TO_SHOW_COUNT) }
}
val selectedCategories = homeFragmentStore.state.pocketStoriesCategories.filter { it.isSelected }
val selectedCategories = homeFragmentStore.state.pocketStoriesCategoriesSelections
assertEquals(1, selectedCategories.size)
assertTrue(otherStoriesCategory.name === selectedCategories[0].name)
assertSame(filteredStories, homeFragmentStore.state.pocketStories)
@ -216,13 +217,15 @@ class HomeFragmentStoreTest {
@Test
fun `Test deselecting a Pocket recommendations category`() = runBlocking {
val otherStoriesCategory = PocketRecommendedStoryCategory("other", isSelected = true)
val anotherStoriesCategory = PocketRecommendedStoryCategory("another", isSelected = true)
val otherStoriesCategory = PocketRecommendedStoriesCategory("other")
val anotherStoriesCategory = PocketRecommendedStoriesCategory("another")
val filteredStories = listOf(mockk<PocketRecommendedStory>())
homeFragmentStore = HomeFragmentStore(
HomeFragmentState(
pocketStoriesCategories = listOf(
otherStoriesCategory, anotherStoriesCategory
pocketStoriesCategories = listOf(otherStoriesCategory, anotherStoriesCategory),
pocketStoriesCategoriesSelections = listOf(
PocketRecommendedStoriesSelectedCategory(otherStoriesCategory.name),
PocketRecommendedStoriesSelectedCategory(anotherStoriesCategory.name)
)
)
)
@ -235,10 +238,9 @@ class HomeFragmentStoreTest {
verify { any<HomeFragmentState>().getFilteredStories(POCKET_STORIES_TO_SHOW_COUNT) }
}
assertTrue(
listOf(anotherStoriesCategory)
.containsAll(homeFragmentStore.state.pocketStoriesCategories.filter { it.isSelected })
)
val selectedCategories = homeFragmentStore.state.pocketStoriesCategoriesSelections
assertEquals(1, selectedCategories.size)
assertTrue(anotherStoriesCategory.name === selectedCategories[0].name)
assertSame(filteredStories, homeFragmentStore.state.pocketStories)
}
@ -259,8 +261,8 @@ class HomeFragmentStoreTest {
@Test
fun `Test updating the list of Pocket recommendations categories`() = runBlocking {
val otherStoriesCategory = PocketRecommendedStoryCategory("other")
val anotherStoriesCategory = PocketRecommendedStoryCategory("another", isSelected = true)
val otherStoriesCategory = PocketRecommendedStoriesCategory("other")
val anotherStoriesCategory = PocketRecommendedStoriesCategory("another")
homeFragmentStore = HomeFragmentStore(HomeFragmentState())
mockkStatic("org.mozilla.fenix.ext.HomeFragmentStateKt") {
@ -268,9 +270,7 @@ class HomeFragmentStoreTest {
every { any<HomeFragmentState>().getFilteredStories(any()) } returns firstFilteredStories
homeFragmentStore.dispatch(
HomeFragmentAction.PocketStoriesCategoriesChange(
listOf(otherStoriesCategory, anotherStoriesCategory)
)
HomeFragmentAction.PocketStoriesCategoriesChange(listOf(otherStoriesCategory, anotherStoriesCategory))
).join()
verify { any<HomeFragmentState>().getFilteredStories(POCKET_STORIES_TO_SHOW_COUNT) }
assertTrue(
@ -280,7 +280,7 @@ class HomeFragmentStoreTest {
)
assertSame(firstFilteredStories, homeFragmentStore.state.pocketStories)
val updatedCategories = listOf(PocketRecommendedStoryCategory("yetAnother"))
val updatedCategories = listOf(PocketRecommendedStoriesCategory("yetAnother"))
val secondFilteredStories = listOf(mockk<PocketRecommendedStory>())
every { any<HomeFragmentState>().getFilteredStories(any()) } returns secondFilteredStories
homeFragmentStore.dispatch(

@ -18,7 +18,7 @@ import org.mozilla.fenix.home.recentbookmarks.controller.RecentBookmarksControll
import org.mozilla.fenix.home.recenttabs.controller.RecentTabController
import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController
import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoryCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketStoriesController
class SessionControlInteractorTest {
@ -215,7 +215,7 @@ class SessionControlInteractorTest {
@Test
fun `GIVEN a PocketStoriesInteractor WHEN a category is clicked THEN handle it in a PocketStoriesController`() {
val clickedCategory: PocketRecommendedStoryCategory = mockk()
val clickedCategory: PocketRecommendedStoriesCategory = mockk()
interactor.onCategoryClick(clickedCategory)

@ -21,11 +21,15 @@ import org.mozilla.fenix.home.HomeFragmentStore
class DefaultPocketStoriesControllerTest {
@Test
fun `GIVEN a category is selected WHEN that same category is clicked THEN deselect it`() {
val category1 = PocketRecommendedStoryCategory("cat1", emptyList(), isSelected = false)
val category2 = PocketRecommendedStoryCategory("cat2", emptyList(), isSelected = true)
val category1 = PocketRecommendedStoriesCategory("cat1", emptyList())
val category2 = PocketRecommendedStoriesCategory("cat2", emptyList())
val selections = listOf(PocketRecommendedStoriesSelectedCategory(category2.name))
val store = spyk(
HomeFragmentStore(
HomeFragmentState(pocketStoriesCategories = listOf(category1, category2))
HomeFragmentState(
pocketStoriesCategories = listOf(category1, category2),
pocketStoriesCategoriesSelections = selections
)
)
)
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
@ -39,23 +43,19 @@ class DefaultPocketStoriesControllerTest {
@Test
fun `GIVEN 8 categories are selected WHEN when a new one is clicked THEN the oldest selected is deselected before selecting the new one`() {
val category1 = PocketRecommendedStoryCategory(
"cat1", emptyList(), isSelected = true, lastInteractedWithTimestamp = 111
)
val category2 = category1.copy("cat2", lastInteractedWithTimestamp = 222)
val category3 = category1.copy("cat3", lastInteractedWithTimestamp = 333)
val oldestSelectedCategory = category1.copy("oldestSelectedCategory", lastInteractedWithTimestamp = 0)
val category4 = category1.copy("cat4", lastInteractedWithTimestamp = 444)
val category5 = category1.copy("cat5", lastInteractedWithTimestamp = 555)
val category6 = category1.copy("cat6", lastInteractedWithTimestamp = 678)
val category7 = category1.copy("cat6", lastInteractedWithTimestamp = 890)
val newSelectedCategory = category1.copy(
"newSelectedCategory", isSelected = false, lastInteractedWithTimestamp = 654321
)
val category1 = PocketRecommendedStoriesSelectedCategory(name = "cat1", selectionTimestamp = 111)
val category2 = PocketRecommendedStoriesSelectedCategory(name = "cat2", selectionTimestamp = 222)
val category3 = PocketRecommendedStoriesSelectedCategory(name = "cat3", selectionTimestamp = 333)
val oldestSelectedCategory = PocketRecommendedStoriesSelectedCategory(name = "oldestSelectedCategory", selectionTimestamp = 0)
val category4 = PocketRecommendedStoriesSelectedCategory(name = "cat4", selectionTimestamp = 444)
val category5 = PocketRecommendedStoriesSelectedCategory(name = "cat5", selectionTimestamp = 555)
val category6 = PocketRecommendedStoriesSelectedCategory(name = "cat6", selectionTimestamp = 678)
val category7 = PocketRecommendedStoriesSelectedCategory(name = "cat7", selectionTimestamp = 890)
val newSelectedCategory = PocketRecommendedStoriesSelectedCategory(name = "newSelectedCategory", selectionTimestamp = 654321)
val store = spyk(
HomeFragmentStore(
HomeFragmentState(
pocketStoriesCategories = listOf(
pocketStoriesCategoriesSelections = listOf(
category1, category2, category3, category4, category5, category6, category7, oldestSelectedCategory
)
)
@ -63,7 +63,7 @@ class DefaultPocketStoriesControllerTest {
)
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
controller.handleCategoryClick(newSelectedCategory)
controller.handleCategoryClick(PocketRecommendedStoriesCategory(newSelectedCategory.name))
verify { store.dispatch(HomeFragmentAction.DeselectPocketStoriesCategory(oldestSelectedCategory.name)) }
verify { store.dispatch(HomeFragmentAction.SelectPocketStoriesCategory(newSelectedCategory.name)) }
@ -71,22 +71,17 @@ class DefaultPocketStoriesControllerTest {
@Test
fun `GIVEN fewer than 8 categories are selected WHEN when a new one is clicked THEN don't deselect anything but select the newly clicked category`() {
val category1 = PocketRecommendedStoryCategory(
"cat1", emptyList(), isSelected = true, lastInteractedWithTimestamp = 111
)
val category2 = category1.copy("cat2", lastInteractedWithTimestamp = 222)
val category3 = category1.copy("cat3", lastInteractedWithTimestamp = 333)
val oldestSelectedCategory = category1.copy("oldestSelectedCategory", lastInteractedWithTimestamp = 0)
val category4 = category1.copy("cat4", lastInteractedWithTimestamp = 444)
val category5 = category1.copy("cat5", lastInteractedWithTimestamp = 555)
val category6 = category1.copy("cat6", lastInteractedWithTimestamp = 678)
val newSelectedCategory = category1.copy(
"newSelectedCategory", isSelected = false, lastInteractedWithTimestamp = 654321
)
val category1 = PocketRecommendedStoriesSelectedCategory(name = "cat1", selectionTimestamp = 111)
val category2 = PocketRecommendedStoriesSelectedCategory(name = "cat2", selectionTimestamp = 222)
val category3 = PocketRecommendedStoriesSelectedCategory(name = "cat3", selectionTimestamp = 333)
val oldestSelectedCategory = PocketRecommendedStoriesSelectedCategory(name = "oldestSelectedCategory", selectionTimestamp = 0)
val category4 = PocketRecommendedStoriesSelectedCategory(name = "cat4", selectionTimestamp = 444)
val category5 = PocketRecommendedStoriesSelectedCategory(name = "cat5", selectionTimestamp = 555)
val category6 = PocketRecommendedStoriesSelectedCategory(name = "cat6", selectionTimestamp = 678)
val store = spyk(
HomeFragmentStore(
HomeFragmentState(
pocketStoriesCategories = listOf(
pocketStoriesCategoriesSelections = listOf(
category1, category2, category3, category4, category5, category6, oldestSelectedCategory
)
)
@ -94,10 +89,10 @@ class DefaultPocketStoriesControllerTest {
)
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
controller.handleCategoryClick(newSelectedCategory)
controller.handleCategoryClick(PocketRecommendedStoriesCategory("newSelectedCategory"))
verify(exactly = 0) { store.dispatch(HomeFragmentAction.DeselectPocketStoriesCategory(oldestSelectedCategory.name)) }
verify { store.dispatch(HomeFragmentAction.SelectPocketStoriesCategory(newSelectedCategory.name)) }
verify { store.dispatch(HomeFragmentAction.SelectPocketStoriesCategory("newSelectedCategory")) }
}
@Test

Loading…
Cancel
Save