You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/test/java/org/mozilla/fenix/home/pocket/DefaultPocketStoriesControl...

396 lines
19 KiB
Kotlin

/* 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.pocket
import androidx.navigation.NavController
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verifyOrder
import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory
import mozilla.components.service.pocket.ext.getCurrentFlightImpressions
import mozilla.components.support.test.robolectric.testContext
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.GleanMetrics.Pings
import org.mozilla.fenix.GleanMetrics.Pocket
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class) // For gleanTestRule
class DefaultPocketStoriesControllerTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
@Test
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))
val store = spyk(
AppStore(
AppState(
pocketStoriesCategories = listOf(category1, category2),
pocketStoriesCategoriesSelections = selections,
),
),
)
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
assertNull(Pocket.homeRecsCategoryClicked.testGetValue())
controller.handleCategoryClick(category2)
verify(exactly = 0) { store.dispatch(AppAction.SelectPocketStoriesCategory(category2.name)) }
verify { store.dispatch(AppAction.DeselectPocketStoriesCategory(category2.name)) }
assertNotNull(Pocket.homeRecsCategoryClicked.testGetValue())
val event = Pocket.homeRecsCategoryClicked.testGetValue()!!
assertEquals(1, event.size)
assertTrue(event.single().extra!!.containsKey("category_name"))
assertEquals(category2.name, event.single().extra!!["category_name"])
assertTrue(event.single().extra!!.containsKey("new_state"))
assertEquals("deselected", event.single().extra!!["new_state"])
assertTrue(event.single().extra!!.containsKey("selected_total"))
assertEquals("1", event.single().extra!!["selected_total"])
}
@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 and record telemetry`() {
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(
AppStore(
AppState(
pocketStoriesCategoriesSelections = listOf(
category1,
category2,
category3,
category4,
category5,
category6,
category7,
oldestSelectedCategory,
),
),
),
)
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
assertNull(Pocket.homeRecsCategoryClicked.testGetValue())
controller.handleCategoryClick(PocketRecommendedStoriesCategory(newSelectedCategory.name))
verify { store.dispatch(AppAction.DeselectPocketStoriesCategory(oldestSelectedCategory.name)) }
verify { store.dispatch(AppAction.SelectPocketStoriesCategory(newSelectedCategory.name)) }
assertNotNull(Pocket.homeRecsCategoryClicked.testGetValue())
val event = Pocket.homeRecsCategoryClicked.testGetValue()!!
assertEquals(1, event.size)
assertTrue(event.single().extra!!.containsKey("category_name"))
assertEquals(newSelectedCategory.name, event.single().extra!!["category_name"])
assertTrue(event.single().extra!!.containsKey("new_state"))
assertEquals("selected", event.single().extra!!["new_state"])
assertTrue(event.single().extra!!.containsKey("selected_total"))
assertEquals("8", event.single().extra!!["selected_total"])
}
@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 and record telemetry`() {
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(
AppStore(
AppState(
pocketStoriesCategoriesSelections = listOf(
category1,
category2,
category3,
category4,
category5,
category6,
oldestSelectedCategory,
),
),
),
)
val newSelectedCategoryName = "newSelectedCategory"
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
controller.handleCategoryClick(PocketRecommendedStoriesCategory(newSelectedCategoryName))
verify(exactly = 0) { store.dispatch(AppAction.DeselectPocketStoriesCategory(oldestSelectedCategory.name)) }
verify { store.dispatch(AppAction.SelectPocketStoriesCategory(newSelectedCategoryName)) }
assertNotNull(Pocket.homeRecsCategoryClicked.testGetValue())
val event = Pocket.homeRecsCategoryClicked.testGetValue()!!
assertEquals(1, event.size)
assertTrue(event.single().extra!!.containsKey("category_name"))
assertEquals(newSelectedCategoryName, event.single().extra!!["category_name"])
assertTrue(event.single().extra!!.containsKey("new_state"))
assertEquals("selected", event.single().extra!!["new_state"])
assertTrue(event.single().extra!!.containsKey("selected_total"))
assertEquals("7", event.single().extra!!["selected_total"])
}
@Test
fun `WHEN a new recommended story is shown THEN update the State`() {
val store = spyk(AppStore())
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
val storyShown: PocketRecommendedStory = mockk()
val storyGridLocation = 1 to 2
controller.handleStoryShown(storyShown, storyGridLocation)
verify { store.dispatch(AppAction.PocketStoriesShown(listOf(storyShown))) }
}
@Test
fun `WHEN a new sponsored story is shown THEN update the State and record telemetry`() {
val store = spyk(AppStore())
val controller = DefaultPocketStoriesController(mockk(), store, mockk())
val storyShown: PocketSponsoredStory = mockk {
every { shim.click } returns "testClickShim"
every { shim.impression } returns "testImpressionShim"
}
var wasPingSent = false
mockkStatic("mozilla.components.service.pocket.ext.PocketStoryKt") {
// Simulate that the story was already shown 3 times.
every { storyShown.getCurrentFlightImpressions() } returns listOf(2L, 3L, 7L)
// Test that the spoc ping is immediately sent with the needed data.
Pings.spoc.testBeforeNextSubmit { reason ->
assertEquals(storyShown.shim.impression, Pocket.spocShim.testGetValue())
assertEquals(Pings.spocReasonCodes.impression.name, reason?.name)
wasPingSent = true
}
controller.handleStoryShown(storyShown, 1 to 2)
verify { store.dispatch(AppAction.PocketStoriesShown(listOf(storyShown))) }
assertNotNull(Pocket.homeRecsSpocShown.testGetValue())
assertEquals(1, Pocket.homeRecsSpocShown.testGetValue()!!.size)
val data = Pocket.homeRecsSpocShown.testGetValue()!!.single().extra
assertEquals("1x2", data?.entries?.first { it.key == "position" }?.value)
assertEquals("4", data?.entries?.first { it.key == "times_shown" }?.value)
assertTrue(wasPingSent)
}
}
@Test
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<PocketStory> = mockk()
assertNull(Pocket.homeRecsShown.testGetValue())
controller.handleStoriesShown(storiesShown)
verify { store.dispatch(AppAction.PocketStoriesShown(storiesShown)) }
assertNotNull(Pocket.homeRecsShown.testGetValue())
assertEquals(1, Pocket.homeRecsShown.testGetValue()!!.size)
assertNull(Pocket.homeRecsShown.testGetValue()!!.single().extra)
}
@Test
fun `WHEN a recommended 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))
assertNull(Pocket.homeRecsStoryClicked.testGetValue())
controller.handleStoryClicked(story, 1 to 2)
verify { homeActivity.openToBrowserAndLoad(story.url, true, BrowserDirection.FromHome) }
assertNotNull(Pocket.homeRecsStoryClicked.testGetValue())
val event = Pocket.homeRecsStoryClicked.testGetValue()!!
assertEquals(1, event.size)
assertTrue(event.single().extra!!.containsKey("position"))
assertEquals("1x2", event.single().extra!!["position"])
assertTrue(event.single().extra!!.containsKey("times_shown"))
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 record telemetry`() {
val storyClicked = PocketSponsoredStory(
id = 7,
title = "",
url = "testLink",
imageUrl = "",
sponsor = "",
shim = mockk {
every { click } returns "testClickShim"
every { impression } returns "testImpressionShim"
},
priority = 3,
caps = mockk(relaxed = true),
)
val homeActivity: HomeActivity = mockk(relaxed = true)
val controller = DefaultPocketStoriesController(homeActivity, mockk(), mockk(relaxed = true))
var wasPingSent = false
assertNull(Pocket.homeRecsSpocClicked.testGetValue())
mockkStatic("mozilla.components.service.pocket.ext.PocketStoryKt") {
// Simulate that the story was already shown 2 times.
every { storyClicked.getCurrentFlightImpressions() } returns listOf(2L, 3L)
// Test that the spoc ping is immediately sent with the needed data.
Pings.spoc.testBeforeNextSubmit { reason ->
assertEquals(storyClicked.shim.click, Pocket.spocShim.testGetValue())
assertEquals(Pings.spocReasonCodes.click.name, reason?.name)
wasPingSent = true
}
controller.handleStoryClicked(storyClicked, 2 to 3)
verify { homeActivity.openToBrowserAndLoad(storyClicked.url, true, BrowserDirection.FromHome) }
assertNotNull(Pocket.homeRecsSpocClicked.testGetValue())
assertEquals(1, Pocket.homeRecsSpocClicked.testGetValue()!!.size)
val data = Pocket.homeRecsSpocClicked.testGetValue()!!.single().extra
assertEquals("2x3", data?.entries?.first { it.key == "position" }?.value)
assertEquals("3", data?.entries?.first { it.key == "times_shown" }?.value)
assertTrue(wasPingSent)
}
}
@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))
assertNull(Pocket.homeRecsDiscoverClicked.testGetValue())
controller.handleDiscoverMoreClicked(link)
verify { homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome) }
assertNotNull(Pocket.homeRecsDiscoverClicked.testGetValue())
assertEquals(1, Pocket.homeRecsDiscoverClicked.testGetValue()!!.size)
assertNull(Pocket.homeRecsDiscoverClicked.testGetValue()!!.single().extra)
}
@Test
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))
assertNull(Pocket.homeRecsLearnMoreClicked.testGetValue())
controller.handleLearnMoreClicked(link)
verify { homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome) }
assertNotNull(Pocket.homeRecsLearnMoreClicked.testGetValue())
assertNull(Pocket.homeRecsLearnMoreClicked.testGetValue()!!.single().extra)
}
@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)
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)
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)
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)
controller.dismissSearchDialogIfDisplayed()
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)
controller.dismissSearchDialogIfDisplayed()
verify(exactly = 0) { navController.navigateUp() }
}
}