For #21623 - Pocket recommended stories telemetry

upstream-sync
Mugurell 3 years ago committed by mergify[bot]
parent 2c528391da
commit 507801e5d5

@ -3704,6 +3704,91 @@ pocket:
notification_emails:
- android-probes@mozilla.com
expires: "2022-02-01"
home_recs_shown:
type: event
description: |
The Pocket recommended stories are shown on the home screen.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/21593
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/21625#issuecomment-936745506
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-10-01"
home_recs_story_clicked:
type: event
description: |
User tapped a Pocket recommended story to be opened.
extra_keys:
times_shown:
description: |
How many times was this story shown, including current.
position:
description: |
Position of the clicked story in the list shown.
Uses the [row x column] matrix notation.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/21593
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/21625#issuecomment-936745506
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-10-01"
home_recs_category_clicked:
type: event
description: |
User tapped a Pocket stories category to filter stories.
extra_keys:
category_name:
description: |
Pocket set topic name representing the just clicked category.
selected_total:
description: |
How many categories were selected before this being tapped.
new_state:
description: |
Category's new state after being tapped.
Possible values: [selected], [deselected].
bugs:
- https://github.com/mozilla-mobile/fenix/issues/21593
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/21625#issuecomment-936745506
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-10-01"
home_recs_discover_clicked:
type: event
description: |
User tapped the "Discover more" tile to open a new tab
for more Pocket stories.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/21593
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/21625#issuecomment-936745506
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-10-01"
home_recs_learn_more_clicked:
type: event
description: |
User tapped "Learn more" to open a new tab for Pocket.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/21593
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/21625#issuecomment-936745506
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-10-01"
first_session:
campaign:

@ -19,6 +19,7 @@ import org.mozilla.fenix.GleanMetrics.ErrorPage
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.Logins
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.GleanMetrics.Pocket
import org.mozilla.fenix.GleanMetrics.ProgressiveWebApp
import org.mozilla.fenix.GleanMetrics.SearchShortcuts
import org.mozilla.fenix.GleanMetrics.TabsTray
@ -127,6 +128,35 @@ sealed class Event {
object WhatsNewTapped : Event()
object PocketTopSiteClicked : Event()
object PocketTopSiteRemoved : Event()
object PocketHomeRecsShown : Event()
object PocketHomeRecsDiscoverMoreClicked : Event()
object PocketHomeRecsLearnMoreClicked : Event()
data class PocketHomeRecsStoryClicked(
val timesShown: Long,
val storyPosition: Pair<Int, Int>,
) : Event() {
override val extras: Map<Pocket.homeRecsStoryClickedKeys, String>
get() = mapOf(
Pocket.homeRecsStoryClickedKeys.timesShown to timesShown.toString(),
Pocket.homeRecsStoryClickedKeys.position to "${storyPosition.first}x${storyPosition.second}"
)
}
data class PocketHomeRecsCategoryClicked(
val categoryname: String,
val previousSelectedCategoriesTotal: Int,
val isSelectedNextState: Boolean
) : Event() {
override val extras: Map<Pocket.homeRecsCategoryClickedKeys, String>
get() = mapOf(
Pocket.homeRecsCategoryClickedKeys.categoryName to categoryname,
Pocket.homeRecsCategoryClickedKeys.selectedTotal to previousSelectedCategoriesTotal.toString(),
Pocket.homeRecsCategoryClickedKeys.newState to when (isSelectedNextState) {
true -> "selected"
false -> "deselected"
}
)
}
object FennecToFenixMigrated : Event()
object AddonsOpenInSettings : Event()
object VoiceSearchTapped : Event()

@ -493,6 +493,23 @@ private val Event.wrapper: EventWrapper<*>?
is Event.PocketTopSiteRemoved -> EventWrapper<NoExtraKeys>(
{ Pocket.pocketTopSiteRemoved.record(it) }
)
is Event.PocketHomeRecsShown -> EventWrapper<NoExtraKeys>(
{ Pocket.homeRecsShown.record(it) }
)
is Event.PocketHomeRecsLearnMoreClicked -> EventWrapper<NoExtraKeys>(
{ Pocket.homeRecsLearnMoreClicked.record(it) }
)
is Event.PocketHomeRecsDiscoverMoreClicked -> EventWrapper<NoExtraKeys>(
{ Pocket.homeRecsDiscoverClicked.record(it) }
)
is Event.PocketHomeRecsStoryClicked -> EventWrapper(
{ Pocket.homeRecsStoryClicked.record(it) },
{ Pocket.homeRecsStoryClickedKeys.valueOf(it) }
)
is Event.PocketHomeRecsCategoryClicked -> EventWrapper(
{ Pocket.homeRecsCategoryClicked.record(it) },
{ Pocket.homeRecsCategoryClickedKeys.valueOf(it) }
)
is Event.DarkThemeSelected -> EventWrapper(
{ AppTheme.darkThemeSelected.record(it) },
{ AppTheme.darkThemeSelectedKeys.valueOf(it) }

@ -355,7 +355,8 @@ class HomeFragment : Fragment() {
pocketStoriesController = DefaultPocketStoriesController(
homeActivity = activity,
homeStore = homeFragmentStore,
navController = findNavController()
navController = findNavController(),
metrics = requireComponents.analytics.metrics
)
)

@ -391,15 +391,23 @@ class SessionControlInteractor(
controller.handleCustomizeHomeTapped()
}
override fun onCategoryClick(categoryClicked: PocketRecommendedStoriesCategory) {
override fun onStoriesShown(storiesShown: List<PocketRecommendedStory>) {
pocketStoriesController.handleStoriesShown(storiesShown)
}
override fun onCategoryClicked(categoryClicked: PocketRecommendedStoriesCategory) {
pocketStoriesController.handleCategoryClick(categoryClicked)
}
override fun onStoriesShown(storiesShown: List<PocketRecommendedStory>) {
pocketStoriesController.handleStoriesShown(storiesShown)
override fun onStoryClicked(storyClicked: PocketRecommendedStory, storyPosition: Pair<Int, Int>) {
pocketStoriesController.handleStoryClicked(storyClicked, storyPosition)
}
override fun onLearnMoreClicked(link: String) {
pocketStoriesController.handleLearnMoreClicked(link)
}
override fun onExternalLinkClicked(link: String) {
pocketStoriesController.handleExternalLinkClick(link)
override fun onDiscoverMoreClicked(link: String) {
pocketStoriesController.handleDiscoverMoreClicked(link)
}
}

@ -19,7 +19,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Icon
import androidx.compose.material.Text
@ -107,14 +107,15 @@ fun PocketStory(
* @param stories The list of [PocketRecommendedStory]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 onExternalLinkClicked Callback for when the user taps an element which contains an
* external link for where user can go for more recommendations.
* @param onStoryClicked Callback for when the user taps on a recommended story.
* @param onDiscoverMoreClicked Callback for when the user taps an element which contains an
*/
@Composable
fun PocketStories(
@PreviewParameter(PocketStoryProvider::class) stories: List<PocketRecommendedStory>,
contentPadding: Dp,
onExternalLinkClicked: (String) -> Unit
onStoryClicked: (PocketRecommendedStory, 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.
val maxRowsNo = 3
@ -129,20 +130,20 @@ fun PocketStories(
flingBehavior = flingBehavior,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(storiesToShow) { columnItems ->
itemsIndexed(storiesToShow) { columnIndex, columnItems ->
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
columnItems.forEach { story ->
columnItems.forEachIndexed { rowIndex, story ->
if (story == placeholderStory) {
ListItemTabLargePlaceholder(stringResource(R.string.pocket_stories_placeholder_text)) {
onExternalLinkClicked("https://getpocket.com/explore?$POCKET_FEATURE_UTM_KEY_VALUE")
onDiscoverMoreClicked("https://getpocket.com/explore?$POCKET_FEATURE_UTM_KEY_VALUE")
}
} else {
val uri = Uri.parse(story.url)
.buildUpon()
.appendQueryParameter(URI_PARAM_UTM_KEY, POCKET_STORIES_UTM_VALUE)
.build().toString()
PocketStory(story) {
onExternalLinkClicked(uri)
val uri = Uri.parse(story.url)
.buildUpon()
.appendQueryParameter(URI_PARAM_UTM_KEY, POCKET_STORIES_UTM_VALUE)
.build().toString()
onStoryClicked(it.copy(url = uri), rowIndex to columnIndex)
}
}
}
@ -184,13 +185,13 @@ fun PocketStoriesCategories(
* Pocket feature section title.
* Shows a default text about Pocket and offers a external link to learn more.
*
* @param onExternalLinkClicked Callback invoked when the user clicks the "Learn more" link.
* @param onLearnMoreClicked Callback invoked when the user clicks the "Learn more" link.
* Contains the full URL for where the user should be navigated to.
* @param modifier [Modifier] to be applied to the layout.
*/
@Composable
fun PoweredByPocketHeader(
onExternalLinkClicked: (String) -> Unit,
onLearnMoreClicked: (String) -> Unit,
modifier: Modifier = Modifier
) {
val color = when (isSystemInDarkTheme()) {
@ -231,7 +232,7 @@ fun PoweredByPocketHeader(
)
ClickableSubstringLink(text, color, linkStartIndex, linkEndIndex) {
onExternalLinkClicked("https://www.mozilla.org/en-US/firefox/pocket/?$POCKET_FEATURE_UTM_KEY_VALUE")
onLearnMoreClicked("https://www.mozilla.org/en-US/firefox/pocket/?$POCKET_FEATURE_UTM_KEY_VALUE")
}
}
}
@ -247,7 +248,8 @@ private fun PocketStoriesComposablesPreview() {
PocketStories(
stories = getFakePocketStories(8),
contentPadding = 0.dp,
onExternalLinkClicked = { }
onStoryClicked = { _, _ -> },
onDiscoverMoreClicked = { }
)
Spacer(Modifier.height(10.dp))

@ -13,11 +13,20 @@ import mozilla.components.service.pocket.PocketRecommendedStory
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
/**
* Contract for how all user interactions with the Pocket recommended 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.
*/
fun handleStoriesShown(storiesShown: List<PocketRecommendedStory>)
/**
* Callback allowing to handle a specific [PocketRecommendedStoriesCategory] being clicked by the user.
*
@ -26,18 +35,26 @@ interface PocketStoriesController {
fun handleCategoryClick(categoryClicked: PocketRecommendedStoriesCategory): Unit
/**
* Callback to decide what should happen as an effect of a new list of stories being shown.
* Callback for when the user clicks on a specific story.
*
* @param storiesShown the new list of [PocketRecommendedStory]es shown to the user.
* @param storyClicked The just clicked [PocketRecommendedStory] URL.
* @param storyPosition `row x column` matrix representing the grid position of the clicked story.
*/
fun handleStoriesShown(storiesShown: List<PocketRecommendedStory>)
fun handleStoryClicked(storyClicked: PocketRecommendedStory, storyPosition: Pair<Int, Int>)
/**
* Callback for when the an external link is clicked.
* Callback for when the "Learn more" link is clicked.
*
* @param link URL clicked.
*/
fun handleExternalLinkClick(link: String)
fun handleLearnMoreClicked(link: String)
/**
* Callback for when the "Discover more" link is clicked.
*
* @param link URL clicked.
*/
fun handleDiscoverMoreClicked(link: String)
}
/**
@ -50,14 +67,27 @@ interface PocketStoriesController {
internal class DefaultPocketStoriesController(
private val homeActivity: HomeActivity,
private val homeStore: HomeFragmentStore,
private val navController: NavController
private val navController: NavController,
private val metrics: MetricController
) : PocketStoriesController {
override fun handleStoriesShown(storiesShown: List<PocketRecommendedStory>) {
homeStore.dispatch(HomeFragmentAction.PocketStoriesShown(storiesShown))
metrics.track(Event.PocketHomeRecsShown)
}
override fun handleCategoryClick(categoryClicked: PocketRecommendedStoriesCategory) {
val initialCategoriesSelections = homeStore.state.pocketStoriesCategoriesSelections
// First check whether the category is clicked to be deselected.
if (initialCategoriesSelections.map { it.name }.contains(categoryClicked.name)) {
homeStore.dispatch(HomeFragmentAction.DeselectPocketStoriesCategory(categoryClicked.name))
metrics.track(
Event.PocketHomeRecsCategoryClicked(
categoryClicked.name,
initialCategoriesSelections.size,
false
)
)
return
}
@ -75,19 +105,36 @@ internal class DefaultPocketStoriesController(
// Finally update the selection.
homeStore.dispatch(HomeFragmentAction.SelectPocketStoriesCategory(categoryClicked.name))
metrics.track(
Event.PocketHomeRecsCategoryClicked(
categoryClicked.name,
initialCategoriesSelections.size,
true
)
)
}
override fun handleStoriesShown(storiesShown: List<PocketRecommendedStory>) {
homeStore.dispatch(HomeFragmentAction.PocketStoriesShown(storiesShown))
override fun handleStoryClicked(storyClicked: PocketRecommendedStory, storyPosition: Pair<Int, Int>) {
dismissSearchDialogIfDisplayed()
homeActivity.openToBrowserAndLoad(storyClicked.url, true, BrowserDirection.FromHome)
metrics.track(Event.PocketHomeRecsStoryClicked(storyClicked.timesShown.inc(), storyPosition))
}
override fun handleLearnMoreClicked(link: String) {
dismissSearchDialogIfDisplayed()
homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome)
metrics.track(Event.PocketHomeRecsLearnMoreClicked)
}
override fun handleExternalLinkClick(link: String) {
override fun handleDiscoverMoreClicked(link: String) {
dismissSearchDialogIfDisplayed()
homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome)
metrics.track(Event.PocketHomeRecsDiscoverMoreClicked)
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun dismissSearchDialogIfDisplayed() {
internal fun dismissSearchDialogIfDisplayed() {
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigateUp()
}

@ -11,23 +11,38 @@ import mozilla.components.service.pocket.PocketRecommendedStory
*/
interface PocketStoriesInteractor {
/**
* Callback for when the user clicked a specific category.
* Callback for then new stories are shown to the user.
*
* @param categoryClicked the just clicked [PocketRecommendedStoriesCategory].
* @param storiesShown The new list of [PocketRecommendedStory]es shown to the user.
*/
fun onCategoryClick(categoryClicked: PocketRecommendedStoriesCategory)
fun onStoriesShown(storiesShown: List<PocketRecommendedStory>)
/**
* Callback for then new stories are shown to the user.
* Callback for when the user clicks a specific category.
*
* @param storiesShown the new list of [PocketRecommendedStory]es shown to the user.
* @param categoryClicked The just clicked [PocketRecommendedStoriesCategory].
*/
fun onStoriesShown(storiesShown: List<PocketRecommendedStory>)
fun onCategoryClicked(categoryClicked: PocketRecommendedStoriesCategory)
/**
* Callback for when the user clicks on a specific story.
*
* @param storyClicked The just clicked [PocketRecommendedStory] URL.
* @param storyPosition `row x column` matrix representing the grid position of the clicked story.
*/
fun onStoryClicked(storyClicked: PocketRecommendedStory, storyPosition: Pair<Int, Int>)
/**
* Callback for when the user clicks the "Learn more" link.
*
* @param link URL clicked.
*/
fun onLearnMoreClicked(link: String)
/**
* Callback for when the user clicks an external link.
* Callback for when the user clicks the "Discover more" link.
*
* @param link URL clicked.
*/
fun onExternalLinkClicked(link: String)
fun onDiscoverMoreClicked(link: String)
}

@ -52,8 +52,10 @@ class PocketStoriesViewHolder(
PocketStories(
store,
interactor::onStoriesShown,
interactor::onCategoryClick,
interactor::onExternalLinkClicked,
interactor::onStoryClicked,
interactor::onCategoryClicked,
interactor::onDiscoverMoreClicked,
interactor::onLearnMoreClicked,
with(composeView.resources) {
getDimensionPixelSize(R.dimen.home_item_horizontal_margin) / displayMetrics.density
}
@ -68,11 +70,14 @@ class PocketStoriesViewHolder(
}
@Composable
@Suppress("LongParameterList")
fun PocketStories(
store: HomeFragmentStore,
onStoriesShown: (List<PocketRecommendedStory>) -> Unit,
onCategoryClick: (PocketRecommendedStoriesCategory) -> Unit,
onExternalLinkClicked: (String) -> Unit,
onStoryClicked: (PocketRecommendedStory, Pair<Int, Int>) -> Unit,
onCategoryClicked: (PocketRecommendedStoriesCategory) -> Unit,
onDiscoverMoreClicked: (String) -> Unit,
onLearnMoreClicked: (String) -> Unit,
@Dimension horizontalPadding: Float = 0f
) {
val stories = store
@ -102,7 +107,7 @@ fun PocketStories(
Spacer(Modifier.height(17.dp))
PocketStories(stories ?: emptyList(), horizontalPadding.dp, onExternalLinkClicked)
PocketStories(stories ?: emptyList(), horizontalPadding.dp, onStoryClicked, onDiscoverMoreClicked)
Spacer(Modifier.height(24.dp))
@ -118,7 +123,7 @@ fun PocketStories(
PocketStoriesCategories(
categories = categories ?: emptyList(),
selections = categoriesSelections ?: emptyList(),
onCategoryClick = onCategoryClick,
onCategoryClick = onCategoryClicked,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = horizontalPadding.dp)
@ -127,7 +132,7 @@ fun PocketStories(
Spacer(Modifier.height(24.dp))
PoweredByPocketHeader(
onExternalLinkClicked,
onLearnMoreClicked,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = horizontalPadding.dp)

@ -10,6 +10,7 @@ import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.feature.tab.collections.Tab
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.service.pocket.PocketRecommendedStory
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
@ -213,21 +214,49 @@ class SessionControlInteractorTest {
verify { controller.handlePrivateModeButtonClicked(newMode, hasBeenOnboarded) }
}
@Test
fun `GIVEN a PocketStoriesInteractor WHEN stories are shown THEN handle it in a PocketStoriesController`() {
val shownStories: List<PocketRecommendedStory> = mockk()
interactor.onStoriesShown(shownStories)
verify { pocketStoriesController.handleStoriesShown(shownStories) }
}
@Test
fun `GIVEN a PocketStoriesInteractor WHEN a category is clicked THEN handle it in a PocketStoriesController`() {
val clickedCategory: PocketRecommendedStoriesCategory = mockk()
interactor.onCategoryClick(clickedCategory)
interactor.onCategoryClicked(clickedCategory)
verify { pocketStoriesController.handleCategoryClick(clickedCategory) }
}
@Test
fun `GIVEN a PocketStoriesInteractor WHEN an external link is clicked THEN handle it in a PocketStoriesController`() {
fun `GIVEN a PocketStoriesInteractor WHEN a story is clicked THEN handle it in a PocketStoriesController`() {
val clickedStory: PocketRecommendedStory = mockk()
val storyGridLocation = 1 to 2
interactor.onStoryClicked(clickedStory, storyGridLocation)
verify { pocketStoriesController.handleStoryClicked(clickedStory, storyGridLocation) }
}
@Test
fun `GIVEN a PocketStoriesInteractor WHEN discover more clicked THEN handle it in a PocketStoriesController`() {
val link = "http://getpocket.com/explore"
interactor.onDiscoverMoreClicked(link)
verify { pocketStoriesController.handleDiscoverMoreClicked(link) }
}
@Test
fun `GIVEN a PocketStoriesInteractor WHEN learn more clicked THEN handle it in a PocketStoriesController`() {
val link = "https://www.mozilla.org/en-US/firefox/pocket/"
interactor.onExternalLinkClicked(link)
interactor.onLearnMoreClicked(link)
verify { pocketStoriesController.handleExternalLinkClick(link) }
verify { pocketStoriesController.handleLearnMoreClicked(link) }
}
}

@ -9,18 +9,23 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verifyOrder
import mozilla.components.service.pocket.PocketRecommendedStory
import org.junit.Test
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.home.HomeFragmentAction
import org.mozilla.fenix.home.HomeFragmentState
import org.mozilla.fenix.home.HomeFragmentStore
class DefaultPocketStoriesControllerTest {
val metrics: MetricController = mockk(relaxed = true)
@Test
fun `GIVEN a category is selected WHEN that same category is clicked THEN deselect it`() {
fun `GIVEN a category is selected WHEN that same category is clicked THEN deselect it and record telemetry`() {
val category1 = PocketRecommendedStoriesCategory("cat1", emptyList())
val category2 = PocketRecommendedStoriesCategory("cat2", emptyList())
val selections = listOf(PocketRecommendedStoriesSelectedCategory(category2.name))
@ -32,17 +37,21 @@ class DefaultPocketStoriesControllerTest {
)
)
)
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
val controller = DefaultPocketStoriesController(mockk(), store, mockk(), metrics)
controller.handleCategoryClick(category1)
verify(exactly = 0) { store.dispatch(HomeFragmentAction.DeselectPocketStoriesCategory(category1.name)) }
verify { store.dispatch(HomeFragmentAction.SelectPocketStoriesCategory(category1.name)) }
verify { metrics.track(Event.PocketHomeRecsCategoryClicked(category1.name, 1, true)) }
controller.handleCategoryClick(category2)
verify(exactly = 0) { store.dispatch(HomeFragmentAction.SelectPocketStoriesCategory(category2.name)) }
verify { store.dispatch(HomeFragmentAction.DeselectPocketStoriesCategory(category2.name)) }
verify { metrics.track(Event.PocketHomeRecsCategoryClicked(category2.name, 2, false)) }
}
@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`() {
fun `GIVEN 8 categories are selected WHEN when a new one is clicked THEN the oldest selected is deselected before selecting the new one and record telemetry`() {
val category1 = PocketRecommendedStoriesSelectedCategory(name = "cat1", selectionTimestamp = 111)
val category2 = PocketRecommendedStoriesSelectedCategory(name = "cat2", selectionTimestamp = 222)
val category3 = PocketRecommendedStoriesSelectedCategory(name = "cat3", selectionTimestamp = 333)
@ -61,16 +70,17 @@ class DefaultPocketStoriesControllerTest {
)
)
)
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
val controller = DefaultPocketStoriesController(mockk(), store, mockk(), metrics)
controller.handleCategoryClick(PocketRecommendedStoriesCategory(newSelectedCategory.name))
verify { store.dispatch(HomeFragmentAction.DeselectPocketStoriesCategory(oldestSelectedCategory.name)) }
verify { store.dispatch(HomeFragmentAction.SelectPocketStoriesCategory(newSelectedCategory.name)) }
verify { metrics.track(Event.PocketHomeRecsCategoryClicked(newSelectedCategory.name, 8, true)) }
}
@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`() {
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 and record telemetry`() {
val category1 = PocketRecommendedStoriesSelectedCategory(name = "cat1", selectionTimestamp = 111)
val category2 = PocketRecommendedStoriesSelectedCategory(name = "cat2", selectionTimestamp = 222)
val category3 = PocketRecommendedStoriesSelectedCategory(name = "cat3", selectionTimestamp = 333)
@ -87,50 +97,146 @@ class DefaultPocketStoriesControllerTest {
)
)
)
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
val newSelectedCategoryName = "newSelectedCategory"
val controller = DefaultPocketStoriesController(mockk(), store, mockk(), metrics)
controller.handleCategoryClick(PocketRecommendedStoriesCategory("newSelectedCategory"))
controller.handleCategoryClick(PocketRecommendedStoriesCategory(newSelectedCategoryName))
verify(exactly = 0) { store.dispatch(HomeFragmentAction.DeselectPocketStoriesCategory(oldestSelectedCategory.name)) }
verify { store.dispatch(HomeFragmentAction.SelectPocketStoriesCategory("newSelectedCategory")) }
verify { store.dispatch(HomeFragmentAction.SelectPocketStoriesCategory(newSelectedCategoryName)) }
verify { metrics.track(Event.PocketHomeRecsCategoryClicked(newSelectedCategoryName, 7, true)) }
}
@Test
fun `WHEN new stories are shown THEN update the State`() {
fun `WHEN new stories are shown THEN update the State and record telemetry`() {
val store = spyk(HomeFragmentStore())
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
val controller = DefaultPocketStoriesController(mockk(), store, mockk(), metrics)
val storiesShown: List<PocketRecommendedStory> = mockk()
controller.handleStoriesShown(storiesShown)
verify { store.dispatch(HomeFragmentAction.PocketStoriesShown(storiesShown)) }
verify { metrics.track(Event.PocketHomeRecsShown) }
}
@Test
fun `WHEN an external link is clicked THEN link is opened`() {
val link = "https://www.mozilla.org/en-US/firefox/pocket/"
fun `WHEN a story is clicked then open that story's url using HomeActivity and record telemetry`() {
val story = PocketRecommendedStory(
title = "",
url = "testLink",
imageUrl = "",
publisher = "",
category = "",
timeToRead = 0,
timesShown = 123
)
val homeActivity: HomeActivity = mockk(relaxed = true)
val controller = DefaultPocketStoriesController(homeActivity, mockk(), mockk(relaxed = true), metrics)
controller.handleStoryClicked(story, 1 to 2)
verify { homeActivity.openToBrowserAndLoad(story.url, true, BrowserDirection.FromHome) }
metrics.track(Event.PocketHomeRecsStoryClicked(story.timesShown, 1 to 2))
}
@Test
fun `WHEN discover more is clicked then open that using HomeActivity and record telemetry`() {
val link = "http://getpocket.com/explore"
val homeActivity: HomeActivity = mockk(relaxed = true)
val controller = DefaultPocketStoriesController(homeActivity, mockk(), mockk(relaxed = true))
val controller = DefaultPocketStoriesController(homeActivity, mockk(), mockk(relaxed = true), metrics)
controller.handleExternalLinkClick(link)
controller.handleDiscoverMoreClicked(link)
verify { homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome) }
metrics.track(Event.PocketHomeRecsDiscoverMoreClicked)
}
@Test
fun `WHEN an external link is clicked THEN link is opened and search dismissed`() {
fun `WHEN learn more is clicked then open that using HomeActivity and record telemetry`() {
val link = "https://www.mozilla.org/en-US/firefox/pocket/"
val homeActivity: HomeActivity = mockk(relaxed = true)
val controller = DefaultPocketStoriesController(homeActivity, mockk(), mockk(relaxed = true), metrics)
controller.handleLearnMoreClicked(link)
verify { homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome) }
metrics.track(Event.PocketHomeRecsLearnMoreClicked)
}
@Test
fun `WHEN a story is clicked THEN search is dismissed and then its link opened`() {
val story = PocketRecommendedStory("", "url", "", "", "", 0, 0)
val homeActivity: HomeActivity = mockk(relaxed = true)
val navController: NavController = mockk(relaxed = true)
every { navController.currentDestination } returns mockk {
every { id } returns R.id.searchDialogFragment
}
val controller = DefaultPocketStoriesController(homeActivity, mockk(), navController, metrics)
controller.handleStoryClicked(story, 1 to 2)
verifyOrder {
navController.navigateUp()
homeActivity.openToBrowserAndLoad(story.url, true, BrowserDirection.FromHome)
}
}
@Test
fun `WHEN discover more is clicked THEN search is dismissed and then its link opened`() {
val link = "https://discoverMore.link"
val homeActivity: HomeActivity = mockk(relaxed = true)
val navController: NavController = mockk(relaxed = true)
every { navController.currentDestination } returns mockk {
every { id } returns R.id.searchDialogFragment
}
val controller = DefaultPocketStoriesController(homeActivity, mockk(), navController, metrics)
val controller = DefaultPocketStoriesController(homeActivity, mockk(), navController)
controller.handleExternalLinkClick(link)
controller.handleDiscoverMoreClicked(link)
verifyOrder {
navController.navigateUp()
homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome)
}
}
@Test
fun `WHEN learn more link is clicked THEN search is dismissed and then that link is opened`() {
val link = "https://learnMore.link"
val homeActivity: HomeActivity = mockk(relaxed = true)
val navController: NavController = mockk(relaxed = true)
every { navController.currentDestination } returns mockk {
every { id } returns R.id.searchDialogFragment
}
val controller = DefaultPocketStoriesController(homeActivity, mockk(), navController, metrics)
controller.handleLearnMoreClicked(link)
verifyOrder {
navController.navigateUp()
homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome)
}
}
@Test
fun `GIVEN search dialog is currently focused WHEN dismissSearchDialogIfDisplayed is called THEN close the search dialog`() {
val navController: NavController = mockk(relaxed = true)
every { navController.currentDestination } returns mockk {
every { id } returns R.id.searchDialogFragment
}
val controller = DefaultPocketStoriesController(mockk(), mockk(), navController, mockk())
controller.dismissSearchDialogIfDisplayed()
verify { homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome) }
verify { navController.navigateUp() }
}
@Test
fun `GIVEN search dialog is not currently focused WHEN dismissSearchDialogIfDisplayed is called THEN do nothing`() {
val navController: NavController = mockk(relaxed = true)
val controller = DefaultPocketStoriesController(mockk(), mockk(), navController, mockk())
controller.dismissSearchDialogIfDisplayed()
verify(exactly = 0) { navController.navigateUp() }
}
}

Loading…
Cancel
Save