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.
396 lines
19 KiB
Kotlin
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() }
|
|
}
|
|
}
|