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/DefaultSessionControlContro...

1366 lines
50 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
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.state.action.SearchAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.search.RegionState
import mozilla.components.browser.state.search.SearchEngine
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.ReaderState
import mozilla.components.browser.state.state.SearchState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.state.recover.RecoverableTab
import mozilla.components.browser.state.state.recover.TabState
import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.robolectric.testContext
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.GleanMetrics.Collections
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.HomeScreen
import org.mozilla.fenix.GleanMetrics.Pings
import org.mozilla.fenix.GleanMetrics.RecentBookmarks
import org.mozilla.fenix.GleanMetrics.RecentTabs
import org.mozilla.fenix.GleanMetrics.TopSites
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.Analytics
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.gleanplumb.Message
import org.mozilla.fenix.gleanplumb.MessageController
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
import org.mozilla.fenix.home.recenttabs.RecentTab
import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController
import org.mozilla.fenix.onboarding.WallpaperOnboardingDialogFragment.Companion.THUMBNAILS_SELECTION_COUNT
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.wallpapers.Wallpaper
import org.mozilla.fenix.wallpapers.WallpaperState
import mozilla.components.feature.tab.collections.Tab as ComponentTab
@RunWith(FenixRobolectricTestRunner::class) // For gleanTestRule
@OptIn(ExperimentalCoroutinesApi::class)
class DefaultSessionControlControllerTest {
@get:Rule
val coroutinesTestRule = MainCoroutineRule()
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
private val activity: HomeActivity = mockk(relaxed = true)
private val appStore: AppStore = mockk(relaxed = true)
private val navController: NavController = mockk(relaxed = true)
private val messageController: MessageController = mockk(relaxed = true)
private val engine: Engine = mockk(relaxed = true)
private val tabCollectionStorage: TabCollectionStorage = mockk(relaxed = true)
private val tabsUseCases: TabsUseCases = mockk(relaxed = true)
private val reloadUrlUseCase: SessionUseCases = mockk(relaxed = true)
private val selectTabUseCase: TabsUseCases = mockk(relaxed = true)
private val settings: Settings = mockk(relaxed = true)
private val analytics: Analytics = mockk(relaxed = true)
private val scope = coroutinesTestRule.scope
private val searchEngine = SearchEngine(
id = "test",
name = "Test Engine",
icon = mockk(relaxed = true),
type = SearchEngine.Type.BUNDLED,
resultUrls = listOf("https://example.org/?q={searchTerms}"),
)
private val googleSearchEngine = SearchEngine(
id = "googleTest",
name = "Google Test Engine",
icon = mockk(relaxed = true),
type = SearchEngine.Type.BUNDLED,
resultUrls = listOf("https://www.google.com/?q={searchTerms}"),
suggestUrl = "https://www.google.com/",
)
private val duckDuckGoSearchEngine = SearchEngine(
id = "ddgTest",
name = "DuckDuckGo Test Engine",
icon = mockk(relaxed = true),
type = SearchEngine.Type.BUNDLED,
resultUrls = listOf("https://duckduckgo.com/?q=%7BsearchTerms%7D&t=fpas"),
suggestUrl = "https://ac.duckduckgo.com/ac/?q=%7BsearchTerms%7D&type=list",
)
private lateinit var store: BrowserStore
private val appState: AppState = mockk(relaxed = true)
@Before
fun setup() {
store = BrowserStore(
BrowserState(
search = SearchState(
regionSearchEngines = listOf(searchEngine),
),
),
)
every { appStore.state } returns AppState(
collections = emptyList(),
expandedCollections = emptySet(),
mode = Mode.Normal,
topSites = emptyList(),
showCollectionPlaceholder = true,
recentTabs = emptyList(),
recentBookmarks = emptyList(),
)
every { navController.currentDestination } returns mockk {
every { id } returns R.id.homeFragment
}
every { activity.components.settings } returns settings
every { activity.settings() } returns settings
every { activity.components.analytics } returns analytics
}
@Test
fun handleCollectionAddTabTapped() {
val collection = mockk<TabCollection> {
every { id } returns 12L
}
createController().handleCollectionAddTabTapped(collection)
assertNotNull(Collections.addTabButton.testGetValue())
val recordedEvents = Collections.addTabButton.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals(null, recordedEvents.single().extra)
verify {
navController.navigate(
match<NavDirections> {
it.actionId == R.id.action_global_collectionCreationFragment
},
null,
)
}
}
@Test
fun handleCustomizeHomeTapped() {
assertNull(HomeScreen.customizeHomeClicked.testGetValue())
createController().handleCustomizeHomeTapped()
assertNotNull(HomeScreen.customizeHomeClicked.testGetValue())
verify {
navController.navigate(
match<NavDirections> {
it.actionId == R.id.action_global_homeSettingsFragment
},
null,
)
}
}
@Test
fun `handleCollectionOpenTabClicked onFailure`() {
val tab = mockk<ComponentTab> {
every { url } returns "https://mozilla.org"
every { restore(activity, engine, restoreSessionId = false) } returns null
}
createController().handleCollectionOpenTabClicked(tab)
assertNotNull(Collections.tabRestored.testGetValue())
val recordedEvents = Collections.tabRestored.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals(null, recordedEvents.single().extra)
verify {
activity.openToBrowserAndLoad(
searchTermOrURL = "https://mozilla.org",
newTab = true,
from = BrowserDirection.FromHome,
)
}
}
@Test
fun `handleCollectionOpenTabClicked with existing selected tab`() {
val recoverableTab = RecoverableTab(
engineSessionState = null,
state = TabState(
id = "test",
parentId = null,
url = "https://www.mozilla.org",
title = "Mozilla",
contextId = null,
readerState = ReaderState(),
lastAccess = 0,
private = false,
),
)
val tab = mockk<ComponentTab> {
every { restore(activity, engine, restoreSessionId = false) } returns recoverableTab
}
val restoredTab = createTab(id = recoverableTab.state.id, url = recoverableTab.state.url)
val otherTab = createTab(id = "otherTab", url = "https://mozilla.org")
store.dispatch(TabListAction.AddTabAction(otherTab)).joinBlocking()
store.dispatch(TabListAction.SelectTabAction(otherTab.id)).joinBlocking()
store.dispatch(TabListAction.AddTabAction(restoredTab)).joinBlocking()
createController().handleCollectionOpenTabClicked(tab)
assertNotNull(Collections.tabRestored.testGetValue())
val recordedEvents = Collections.tabRestored.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals(null, recordedEvents.single().extra)
verify { activity.openToBrowser(BrowserDirection.FromHome) }
verify { selectTabUseCase.selectTab.invoke(restoredTab.id) }
verify { reloadUrlUseCase.reload.invoke(restoredTab.id) }
}
@Test
fun `handleCollectionOpenTabClicked without existing selected tab`() {
val recoverableTab = RecoverableTab(
engineSessionState = null,
state = TabState(
id = "test",
parentId = null,
url = "https://www.mozilla.org",
title = "Mozilla",
contextId = null,
readerState = ReaderState(),
lastAccess = 0,
private = false,
),
)
val tab = mockk<ComponentTab> {
every { restore(activity, engine, restoreSessionId = false) } returns recoverableTab
}
val restoredTab = createTab(id = recoverableTab.state.id, url = recoverableTab.state.url)
store.dispatch(TabListAction.AddTabAction(restoredTab)).joinBlocking()
createController().handleCollectionOpenTabClicked(tab)
assertNotNull(Collections.tabRestored.testGetValue())
val recordedEvents = Collections.tabRestored.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals(null, recordedEvents.single().extra)
verify { activity.openToBrowser(BrowserDirection.FromHome) }
verify { selectTabUseCase.selectTab.invoke(restoredTab.id) }
verify { reloadUrlUseCase.reload.invoke(restoredTab.id) }
}
@Test
fun handleCollectionOpenTabsTapped() {
val collection = mockk<TabCollection> {
every { tabs } returns emptyList()
}
createController().handleCollectionOpenTabsTapped(collection)
assertNotNull(Collections.allTabsRestored.testGetValue())
val recordedEvents = Collections.allTabsRestored.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals(null, recordedEvents.single().extra)
}
@Test
fun `handleCollectionRemoveTab one tab`() {
val expectedCollection = mockk<TabCollection> {
every { tabs } returns listOf(mockk())
every { title } returns "Collection"
}
val tab = mockk<ComponentTab>()
every {
activity.resources.getString(
R.string.delete_tab_and_collection_dialog_title,
"Collection",
)
} returns "Delete Collection?"
every {
activity.resources.getString(R.string.delete_tab_and_collection_dialog_message)
} returns "Deleting this tab will delete everything."
var actualCollection: TabCollection? = null
createController(
removeCollectionWithUndo = { collection ->
actualCollection = collection
},
).handleCollectionRemoveTab(expectedCollection, tab, false)
assertNotNull(Collections.tabRemoved.testGetValue())
val recordedEvents = Collections.tabRemoved.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals(null, recordedEvents.single().extra)
assertEquals(expectedCollection, actualCollection)
}
@Test
fun `handleCollectionRemoveTab multiple tabs`() {
val collection: TabCollection = mockk(relaxed = true)
val tab: ComponentTab = mockk(relaxed = true)
createController().handleCollectionRemoveTab(collection, tab, false)
assertNotNull(Collections.tabRemoved.testGetValue())
val recordedEvents = Collections.tabRemoved.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals(null, recordedEvents.single().extra)
}
@Test
fun handleCollectionShareTabsClicked() {
val collection = mockk<TabCollection> {
every { tabs } returns emptyList()
every { title } returns ""
}
createController().handleCollectionShareTabsClicked(collection)
assertNotNull(Collections.shared.testGetValue())
val recordedEvents = Collections.shared.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals(null, recordedEvents.single().extra)
verify {
navController.navigate(
match<NavDirections> { it.actionId == R.id.action_global_shareFragment },
null,
)
}
}
@Test
fun handleDeleteCollectionTapped() {
val expectedCollection = mockk<TabCollection> {
every { title } returns "Collection"
}
every {
activity.resources.getString(R.string.tab_collection_dialog_message, "Collection")
} returns "Are you sure you want to delete Collection?"
var actualCollection: TabCollection? = null
createController(
removeCollectionWithUndo = { collection ->
actualCollection = collection
},
).handleDeleteCollectionTapped(expectedCollection)
assertEquals(expectedCollection, actualCollection)
assertNotNull(Collections.removed.testGetValue())
val recordedEvents = Collections.removed.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals(null, recordedEvents.single().extra)
}
@Test
fun handlePrivateBrowsingLearnMoreClicked() {
createController().handlePrivateBrowsingLearnMoreClicked()
verify {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
(SupportUtils.SumoTopic.PRIVATE_BROWSING_MYTHS),
newTab = true,
from = BrowserDirection.FromHome,
)
}
}
@Test
fun handleRenameCollectionTapped() {
val collection = mockk<TabCollection> {
every { id } returns 3L
}
createController().handleRenameCollectionTapped(collection)
assertNotNull(Collections.renameButton.testGetValue())
val recordedEvents = Collections.renameButton.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals(null, recordedEvents.single().extra)
verify {
navController.navigate(
match<NavDirections> { it.actionId == R.id.action_global_collectionCreationFragment },
null,
)
}
}
@Test
fun handleSelectDefaultTopSite() {
val topSite = TopSite.Default(
id = 1L,
title = "Mozilla",
url = "mozilla.org",
createdAt = 0,
)
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(searchEngine)
controller.handleSelectTopSite(topSite, position = 0)
assertNotNull(TopSites.openInNewTab.testGetValue())
assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size)
assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra)
assertNotNull(TopSites.openDefault.testGetValue())
assertEquals(1, TopSites.openDefault.testGetValue()!!.size)
assertNull(TopSites.openDefault.testGetValue()!!.single().extra)
verify {
tabsUseCases.addTab.invoke(
url = topSite.url,
selectTab = true,
startLoading = true,
)
}
verify { activity.openToBrowser(BrowserDirection.FromHome) }
}
@Test
fun handleSelectNonDefaultTopSite() {
val topSite = TopSite.Frecent(
id = 1L,
title = "Mozilla",
url = "mozilla.org",
createdAt = 0,
)
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(searchEngine)
controller.handleSelectTopSite(topSite, position = 0)
assertNotNull(TopSites.openInNewTab.testGetValue())
assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size)
assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra)
verify {
tabsUseCases.addTab.invoke(
url = topSite.url,
selectTab = true,
startLoading = true,
)
}
verify { activity.openToBrowser(BrowserDirection.FromHome) }
}
@Test
fun handleSelectGoogleDefaultTopSiteUS() {
val topSite = TopSite.Default(
id = 1L,
title = "Google",
url = SupportUtils.GOOGLE_URL,
createdAt = 0,
)
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(searchEngine)
store.dispatch(SearchAction.SetRegionAction(RegionState("US", "US"))).joinBlocking()
controller.handleSelectTopSite(topSite, position = 0)
assertNotNull(TopSites.openInNewTab.testGetValue())
assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size)
assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra)
assertNotNull(TopSites.openDefault.testGetValue())
assertEquals(1, TopSites.openDefault.testGetValue()!!.size)
assertNull(TopSites.openDefault.testGetValue()!!.single().extra)
assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue())
assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size)
assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra)
verify {
tabsUseCases.addTab.invoke(
url = SupportUtils.GOOGLE_US_URL,
selectTab = true,
startLoading = true,
)
}
verify { activity.openToBrowser(BrowserDirection.FromHome) }
}
@Test
fun handleSelectGoogleDefaultTopSiteXX() {
val topSite = TopSite.Default(
id = 1L,
title = "Google",
url = SupportUtils.GOOGLE_URL,
createdAt = 0,
)
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(searchEngine)
store.dispatch(SearchAction.SetRegionAction(RegionState("DE", "FR"))).joinBlocking()
controller.handleSelectTopSite(topSite, position = 0)
assertNotNull(TopSites.openInNewTab.testGetValue())
assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size)
assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra)
assertNotNull(TopSites.openDefault.testGetValue())
assertEquals(1, TopSites.openDefault.testGetValue()!!.size)
assertNull(TopSites.openDefault.testGetValue()!!.single().extra)
assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue())
assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size)
assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra)
verify {
tabsUseCases.addTab.invoke(
SupportUtils.GOOGLE_XX_URL,
selectTab = true,
startLoading = true,
)
}
verify { activity.openToBrowser(BrowserDirection.FromHome) }
}
@Test
fun handleSelectGoogleDefaultTopSite_EventPerformedSearchTopSite() {
assertNull(Events.performedSearch.testGetValue())
val topSite = TopSite.Default(
id = 1L,
title = "Google",
url = SupportUtils.GOOGLE_URL,
createdAt = 0,
)
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(googleSearchEngine)
try {
mockkStatic("mozilla.components.browser.state.state.SearchStateKt")
every { any<SearchState>().selectedOrDefaultSearchEngine } returns googleSearchEngine
controller.handleSelectTopSite(topSite, position = 0)
assertNotNull(Events.performedSearch.testGetValue())
assertNotNull(TopSites.openDefault.testGetValue())
assertEquals(1, TopSites.openDefault.testGetValue()!!.size)
assertNull(TopSites.openDefault.testGetValue()!!.single().extra)
assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue())
assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size)
assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra)
} finally {
unmockkStatic("mozilla.components.browser.state.state.SearchStateKt")
}
}
@Test
fun handleSelectDuckDuckGoTopSite_EventPerformedSearchTopSite() {
assertNull(Events.performedSearch.testGetValue())
val topSite = TopSite.Pinned(
id = 1L,
title = "DuckDuckGo",
url = "https://duckduckgo.com",
createdAt = 0,
)
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(googleSearchEngine, duckDuckGoSearchEngine)
try {
mockkStatic("mozilla.components.browser.state.state.SearchStateKt")
every { any<SearchState>().selectedOrDefaultSearchEngine } returns googleSearchEngine
controller.handleSelectTopSite(topSite, position = 0)
assertNotNull(Events.performedSearch.testGetValue())
} finally {
unmockkStatic("mozilla.components.browser.state.state.SearchStateKt")
}
}
@Test
fun handleSelectGooglePinnedTopSiteUS() {
val topSite = TopSite.Pinned(
id = 1L,
title = "Google",
url = SupportUtils.GOOGLE_URL,
createdAt = 0,
)
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(searchEngine)
store.dispatch(SearchAction.SetRegionAction(RegionState("US", "US"))).joinBlocking()
controller.handleSelectTopSite(topSite, position = 0)
assertNotNull(TopSites.openInNewTab.testGetValue())
assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size)
assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra)
assertNotNull(TopSites.openPinned.testGetValue())
assertEquals(1, TopSites.openPinned.testGetValue()!!.size)
assertNull(TopSites.openPinned.testGetValue()!!.single().extra)
assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue())
assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size)
assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra)
verify {
tabsUseCases.addTab.invoke(
SupportUtils.GOOGLE_US_URL,
selectTab = true,
startLoading = true,
)
}
verify { activity.openToBrowser(BrowserDirection.FromHome) }
}
@Test
fun handleSelectGooglePinnedTopSiteXX() {
val topSite = TopSite.Pinned(
id = 1L,
title = "Google",
url = SupportUtils.GOOGLE_URL,
createdAt = 0,
)
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(searchEngine)
store.dispatch(SearchAction.SetRegionAction(RegionState("DE", "FR"))).joinBlocking()
controller.handleSelectTopSite(topSite, position = 0)
assertNotNull(TopSites.openInNewTab.testGetValue())
assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size)
assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra)
assertNotNull(TopSites.openPinned.testGetValue())
assertEquals(1, TopSites.openPinned.testGetValue()!!.size)
assertNull(TopSites.openPinned.testGetValue()!!.single().extra)
assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue())
assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size)
assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra)
verify {
tabsUseCases.addTab.invoke(
SupportUtils.GOOGLE_XX_URL,
selectTab = true,
startLoading = true,
)
}
verify { activity.openToBrowser(BrowserDirection.FromHome) }
}
@Test
fun handleSelectGoogleFrecentTopSiteUS() {
val topSite = TopSite.Frecent(
id = 1L,
title = "Google",
url = SupportUtils.GOOGLE_URL,
createdAt = 0,
)
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(searchEngine)
store.dispatch(SearchAction.SetRegionAction(RegionState("US", "US"))).joinBlocking()
controller.handleSelectTopSite(topSite, position = 0)
assertNotNull(TopSites.openInNewTab.testGetValue())
assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size)
assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra)
assertNotNull(TopSites.openFrecency.testGetValue())
assertEquals(1, TopSites.openFrecency.testGetValue()!!.size)
assertNull(TopSites.openFrecency.testGetValue()!!.single().extra)
assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue())
assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size)
assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra)
verify {
tabsUseCases.addTab.invoke(
SupportUtils.GOOGLE_US_URL,
selectTab = true,
startLoading = true,
)
}
verify { activity.openToBrowser(BrowserDirection.FromHome) }
}
@Test
fun handleSelectGoogleFrecentTopSiteXX() {
val topSite = TopSite.Frecent(
id = 1L,
title = "Google",
url = SupportUtils.GOOGLE_URL,
createdAt = 0,
)
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(searchEngine)
store.dispatch(SearchAction.SetRegionAction(RegionState("DE", "FR"))).joinBlocking()
controller.handleSelectTopSite(topSite, position = 0)
assertNotNull(TopSites.openInNewTab.testGetValue())
assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size)
assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra)
assertNotNull(TopSites.openFrecency.testGetValue())
assertEquals(1, TopSites.openFrecency.testGetValue()!!.size)
assertNull(TopSites.openFrecency.testGetValue()!!.single().extra)
assertNotNull(TopSites.openGoogleSearchAttribution.testGetValue())
assertEquals(1, TopSites.openGoogleSearchAttribution.testGetValue()!!.size)
assertNull(TopSites.openGoogleSearchAttribution.testGetValue()!!.single().extra)
verify {
tabsUseCases.addTab.invoke(
SupportUtils.GOOGLE_XX_URL,
selectTab = true,
startLoading = true,
)
}
verify { activity.openToBrowser(BrowserDirection.FromHome) }
}
@Test
fun handleSelectProvidedTopSite() {
val topSite = TopSite.Provided(
id = 1L,
title = "Mozilla",
url = "mozilla.org",
clickUrl = "",
imageUrl = "",
impressionUrl = "",
createdAt = 0,
)
val position = 0
val controller = spyk(createController())
every { controller.getAvailableSearchEngines() } returns listOf(searchEngine)
controller.handleSelectTopSite(topSite, position)
assertNotNull(TopSites.openInNewTab.testGetValue())
assertEquals(1, TopSites.openInNewTab.testGetValue()!!.size)
assertNull(TopSites.openInNewTab.testGetValue()!!.single().extra)
assertNotNull(TopSites.openContileTopSite.testGetValue())
assertEquals(1, TopSites.openContileTopSite.testGetValue()!!.size)
assertNull(TopSites.openContileTopSite.testGetValue()!!.single().extra)
verify {
tabsUseCases.addTab.invoke(
url = topSite.url,
selectTab = true,
startLoading = true,
)
}
verify { controller.submitTopSitesImpressionPing(topSite, position) }
verify { activity.openToBrowser(BrowserDirection.FromHome) }
}
@Test
fun `GIVEN a provided top site WHEN the provided top site is clicked THEN submit a top site impression ping`() {
val controller = spyk(createController())
val topSite = TopSite.Provided(
id = 3,
title = "Mozilla",
url = "https://mozilla.com",
clickUrl = "https://mozilla.com/click",
imageUrl = "https://test.com/image2.jpg",
impressionUrl = "https://example.com",
createdAt = 3,
)
val position = 0
assertNull(TopSites.contileImpression.testGetValue())
var topSiteImpressionPinged = false
Pings.topsitesImpression.testBeforeNextSubmit {
assertNotNull(TopSites.contileTileId.testGetValue())
assertEquals(3L, TopSites.contileTileId.testGetValue())
assertNotNull(TopSites.contileAdvertiser.testGetValue())
assertEquals("mozilla", TopSites.contileAdvertiser.testGetValue())
assertNotNull(TopSites.contileReportingUrl.testGetValue())
assertEquals(topSite.clickUrl, TopSites.contileReportingUrl.testGetValue())
topSiteImpressionPinged = true
}
controller.submitTopSitesImpressionPing(topSite, position)
assertNotNull(TopSites.contileClick.testGetValue())
val event = TopSites.contileClick.testGetValue()!!
assertEquals(1, event.size)
assertEquals("top_sites", event[0].category)
assertEquals("contile_click", event[0].name)
assertEquals("1", event[0].extra!!["position"])
assertEquals("newtab", event[0].extra!!["source"])
assertTrue(topSiteImpressionPinged)
}
@Test
fun `WHEN the default Google top site is removed THEN the correct metric is recorded`() {
val controller = spyk(createController())
val topSite = TopSite.Default(
id = 1L,
title = "Google",
url = SupportUtils.GOOGLE_URL,
createdAt = 0,
)
assertNull(TopSites.remove.testGetValue())
assertNull(TopSites.googleTopSiteRemoved.testGetValue())
controller.handleRemoveTopSiteClicked(topSite)
assertNotNull(TopSites.googleTopSiteRemoved.testGetValue())
assertEquals(1, TopSites.googleTopSiteRemoved.testGetValue()!!.size)
assertNull(TopSites.googleTopSiteRemoved.testGetValue()!!.single().extra)
assertNotNull(TopSites.remove.testGetValue())
assertEquals(1, TopSites.remove.testGetValue()!!.size)
assertNull(TopSites.remove.testGetValue()!!.single().extra)
}
@Test
fun `GIVEN exactly the required amount of downloaded thumbnails with no errors WHEN handling wallpaper dialog THEN dialog is shown`() {
val wallpaperState = WallpaperState.default.copy(
availableWallpapers = makeFakeRemoteWallpapers(
THUMBNAILS_SELECTION_COUNT,
false,
),
)
assertTrue(createController().handleShowWallpapersOnboardingDialog(wallpaperState))
}
@Test
fun `GIVEN more than required amount of downloaded thumbnails with no errors WHEN handling wallpaper dialog THEN dialog is shown`() {
val wallpaperState = WallpaperState.default.copy(
availableWallpapers = makeFakeRemoteWallpapers(
THUMBNAILS_SELECTION_COUNT,
false,
),
)
assertTrue(createController().handleShowWallpapersOnboardingDialog(wallpaperState))
}
@Test
fun `GIVEN more than required amount of downloaded thumbnails with some errors WHEN handling wallpaper dialog THEN dialog is shown`() {
val wallpaperState = WallpaperState.default.copy(
availableWallpapers = makeFakeRemoteWallpapers(
THUMBNAILS_SELECTION_COUNT + 2,
true,
),
)
assertTrue(createController().handleShowWallpapersOnboardingDialog(wallpaperState))
}
@Test
fun `GIVEN fewer than the required amount of downloaded thumbnails WHEN handling wallpaper dialog THEN the dialog is not shown`() {
val wallpaperState = WallpaperState.default.copy(
availableWallpapers = makeFakeRemoteWallpapers(
THUMBNAILS_SELECTION_COUNT - 1,
false,
),
)
assertFalse(createController().handleShowWallpapersOnboardingDialog(wallpaperState))
}
@Test
fun `GIVEN exactly the required amount of downloaded thumbnails with errors WHEN handling wallpaper dialog THEN the dialog is not shown`() {
val wallpaperState = WallpaperState.default.copy(
availableWallpapers = makeFakeRemoteWallpapers(
THUMBNAILS_SELECTION_COUNT,
true,
),
)
assertFalse(createController().handleShowWallpapersOnboardingDialog(wallpaperState))
}
@Test
fun `GIVEN app is in private browsing mode WHEN handling wallpaper dialog THEN the dialog is not shown`() {
every { activity.browsingModeManager } returns mockk {
every { mode } returns mockk {
every { isPrivate } returns true
}
}
val wallpaperState = WallpaperState.default.copy(
availableWallpapers = makeFakeRemoteWallpapers(
THUMBNAILS_SELECTION_COUNT,
true,
),
)
assertFalse(createController().handleShowWallpapersOnboardingDialog(wallpaperState))
}
@Test
fun handleStartBrowsingClicked() {
var hideOnboardingInvoked = false
createController(hideOnboarding = { hideOnboardingInvoked = true }).handleStartBrowsingClicked()
assertTrue(hideOnboardingInvoked)
}
@Test
fun handleReadPrivacyNoticeClicked() {
createController().handleReadPrivacyNoticeClicked()
verify {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),
newTab = true,
from = BrowserDirection.FromHome,
)
}
}
@Test
fun handleToggleCollectionExpanded() {
val collection = mockk<TabCollection>()
createController().handleToggleCollectionExpanded(collection, true)
verify { appStore.dispatch(AppAction.CollectionExpanded(collection, true)) }
}
@Test
fun handleCreateCollection() {
createController().handleCreateCollection()
verify {
navController.navigate(
match<NavDirections> { it.actionId == R.id.action_global_tabsTrayFragment },
null,
)
}
}
@Test
fun handlePasteAndGo() {
assertNull(Events.enteredUrl.testGetValue())
assertNull(Events.performedSearch.testGetValue())
createController().handlePasteAndGo("text")
verify {
activity.openToBrowserAndLoad(
searchTermOrURL = "text",
newTab = true,
from = BrowserDirection.FromHome,
engine = searchEngine,
)
}
assertNotNull(Events.performedSearch.testGetValue())
createController().handlePasteAndGo("https://mozilla.org")
verify {
activity.openToBrowserAndLoad(
searchTermOrURL = "https://mozilla.org",
newTab = true,
from = BrowserDirection.FromHome,
engine = searchEngine,
)
}
assertNotNull(Events.enteredUrl.testGetValue())
}
@Test
fun handlePaste() {
createController().handlePaste("text")
verify {
navController.navigate(
match<NavDirections> { it.actionId == R.id.action_global_search_dialog },
null,
)
}
}
@Test
fun handleRemoveCollectionsPlaceholder() {
createController().handleRemoveCollectionsPlaceholder()
verify {
settings.showCollectionsPlaceholderOnHome = false
appStore.dispatch(AppAction.RemoveCollectionsPlaceholder)
}
}
@Test
@Ignore("Can't instantiate proxy for class kotlin.Function0")
fun handleMenuOpenedWhileSearchShowing() {
every { navController.currentDestination } returns mockk {
every { id } returns R.id.searchDialogFragment
}
createController().handleMenuOpened()
verify {
navController.navigateUp()
}
}
@Test
fun handleMenuOpenedWhileSearchNotShowing() {
every { navController.currentDestination } returns mockk {
every { id } returns R.id.homeFragment
}
createController().handleMenuOpened()
verify(exactly = 0) {
navController.navigateUp()
}
}
@Test
fun `WHEN private mode button is selected from home THEN handle mode change`() {
every { navController.currentDestination } returns mockk {
every { id } returns R.id.homeFragment
}
every { settings.incrementNumTimesPrivateModeOpened() } just Runs
val newMode = BrowsingMode.Private
val hasBeenOnboarded = true
createController().handlePrivateModeButtonClicked(newMode, hasBeenOnboarded)
verify {
settings.incrementNumTimesPrivateModeOpened()
AppAction.ModeChange(Mode.fromBrowsingMode(newMode))
}
}
@Test
fun `WHEN private mode is selected on home from behind search THEN handle mode change`() {
every { navController.currentDestination } returns mockk {
every { id } returns R.id.searchDialogFragment
}
every { settings.incrementNumTimesPrivateModeOpened() } just Runs
val url = "https://mozilla.org"
val tab = createTab(
id = "otherTab",
url = url,
private = false,
engineSession = mockk(relaxed = true),
)
store.dispatch(TabListAction.AddTabAction(tab, select = true)).joinBlocking()
val newMode = BrowsingMode.Private
val hasBeenOnboarded = true
createController().handlePrivateModeButtonClicked(newMode, hasBeenOnboarded)
verify {
settings.incrementNumTimesPrivateModeOpened()
AppAction.ModeChange(Mode.fromBrowsingMode(newMode))
navController.navigate(
BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = null,
),
)
}
}
@Test
fun `WHEN private mode is deselected on home from behind search THEN handle mode change`() {
every { navController.currentDestination } returns mockk {
every { id } returns R.id.searchDialogFragment
}
val url = "https://mozilla.org"
val tab = createTab(
id = "otherTab",
url = url,
private = true,
engineSession = mockk(relaxed = true),
)
store.dispatch(TabListAction.AddTabAction(tab, select = true)).joinBlocking()
val newMode = BrowsingMode.Normal
val hasBeenOnboarded = true
createController().handlePrivateModeButtonClicked(newMode, hasBeenOnboarded)
verify(exactly = 0) {
settings.incrementNumTimesPrivateModeOpened()
}
verify {
AppAction.ModeChange(Mode.fromBrowsingMode(newMode))
navController.navigate(
BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = null,
),
)
}
}
@Test
fun `WHEN handleReportSessionMetrics is called AND there are zero recent tabs THEN report Event#RecentTabsSectionIsNotVisible`() {
assertNull(RecentTabs.sectionVisible.testGetValue())
every { appState.recentTabs } returns emptyList()
createController().handleReportSessionMetrics(appState)
assertNotNull(RecentTabs.sectionVisible.testGetValue())
assertFalse(RecentTabs.sectionVisible.testGetValue()!!)
}
@Test
fun `WHEN handleReportSessionMetrics is called AND there is at least one recent tab THEN report Event#RecentTabsSectionIsVisible`() {
assertNull(RecentTabs.sectionVisible.testGetValue())
val recentTab: RecentTab = mockk(relaxed = true)
every { appState.recentTabs } returns listOf(recentTab)
createController().handleReportSessionMetrics(appState)
assertNotNull(RecentTabs.sectionVisible.testGetValue())
assertTrue(RecentTabs.sectionVisible.testGetValue()!!)
}
@Test
fun `WHEN handleReportSessionMetrics is called AND there are zero recent bookmarks THEN report Event#RecentBookmarkCount(0)`() {
every { appState.recentBookmarks } returns emptyList()
every { appState.recentTabs } returns emptyList()
assertNull(RecentBookmarks.recentBookmarksCount.testGetValue())
createController().handleReportSessionMetrics(appState)
assertNotNull(RecentBookmarks.recentBookmarksCount.testGetValue())
assertEquals(0L, RecentBookmarks.recentBookmarksCount.testGetValue())
}
@Test
fun `WHEN handleReportSessionMetrics is called AND there is at least one recent bookmark THEN report Event#RecentBookmarkCount(1)`() {
val recentBookmark: RecentBookmark = mockk(relaxed = true)
every { appState.recentBookmarks } returns listOf(recentBookmark)
every { appState.recentTabs } returns emptyList()
assertNull(RecentBookmarks.recentBookmarksCount.testGetValue())
createController().handleReportSessionMetrics(appState)
assertNotNull(RecentBookmarks.recentBookmarksCount.testGetValue())
assertEquals(1L, RecentBookmarks.recentBookmarksCount.testGetValue())
}
@Test
fun `WHEN handleTopSiteSettingsClicked is called THEN navigate to the HomeSettingsFragment AND report the interaction`() {
createController().handleTopSiteSettingsClicked()
assertNotNull(TopSites.contileSettings.testGetValue())
assertEquals(1, TopSites.contileSettings.testGetValue()!!.size)
assertNull(TopSites.contileSettings.testGetValue()!!.single().extra)
verify {
navController.navigate(
match<NavDirections> {
it.actionId == R.id.action_global_homeSettingsFragment
},
null,
)
}
}
@Test
fun `WHEN handleSponsorPrivacyClicked is called THEN navigate to the privacy webpage AND report the interaction`() {
createController().handleSponsorPrivacyClicked()
assertNotNull(TopSites.contileSponsorsAndPrivacy.testGetValue())
assertEquals(1, TopSites.contileSponsorsAndPrivacy.testGetValue()!!.size)
assertNull(TopSites.contileSponsorsAndPrivacy.testGetValue()!!.single().extra)
verify {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic(SupportUtils.SumoTopic.SPONSOR_PRIVACY),
newTab = true,
from = BrowserDirection.FromHome,
)
}
}
@Test
fun `WHEN handleOpenInPrivateTabClicked is called with a TopSite#Provided site THEN Event#TopSiteOpenContileInPrivateTab is reported`() {
val topSite = TopSite.Provided(
id = 1L,
title = "Mozilla",
url = "mozilla.org",
clickUrl = "",
imageUrl = "",
impressionUrl = "",
createdAt = 0,
)
createController().handleOpenInPrivateTabClicked(topSite)
assertNotNull(TopSites.openContileInPrivateTab.testGetValue())
assertEquals(1, TopSites.openContileInPrivateTab.testGetValue()!!.size)
assertNull(TopSites.openContileInPrivateTab.testGetValue()!!.single().extra)
}
@Test
fun `WHEN handleOpenInPrivateTabClicked is called with a Default, Pinned, or Frecent top site THEN openInPrivateTab event is recorded`() {
val controller = createController()
val topSite1 = TopSite.Default(
id = 1L,
title = "Mozilla",
url = "mozilla.org",
createdAt = 0,
)
val topSite2 = TopSite.Pinned(
id = 1L,
title = "Mozilla",
url = "mozilla.org",
createdAt = 0,
)
val topSite3 = TopSite.Frecent(
id = 1L,
title = "Mozilla",
url = "mozilla.org",
createdAt = 0,
)
assertNull(TopSites.openInPrivateTab.testGetValue())
controller.handleOpenInPrivateTabClicked(topSite1)
controller.handleOpenInPrivateTabClicked(topSite2)
controller.handleOpenInPrivateTabClicked(topSite3)
assertNotNull(TopSites.openInPrivateTab.testGetValue())
assertEquals(3, TopSites.openInPrivateTab.testGetValue()!!.size)
for (event in TopSites.openInPrivateTab.testGetValue()!!) {
assertNull(event.extra)
}
}
@Test
fun `WHEN handleMessageClicked and handleMessageClosed are called THEN delegate to messageController`() {
val controller = createController()
val message = mockk<Message>()
controller.handleMessageClicked(message)
controller.handleMessageClosed(message)
verify {
messageController.onMessagePressed(message)
}
verify {
messageController.onMessageDismissed(message)
}
}
private fun createController(
hideOnboarding: () -> Unit = { },
registerCollectionStorageObserver: () -> Unit = { },
showTabTray: () -> Unit = { },
removeCollectionWithUndo: (tabCollection: TabCollection) -> Unit = { },
): DefaultSessionControlController {
return DefaultSessionControlController(
activity = activity,
settings = settings,
engine = engine,
store = store,
messageController = messageController,
tabCollectionStorage = tabCollectionStorage,
addTabUseCase = tabsUseCases.addTab,
restoreUseCase = mockk(relaxed = true),
reloadUrlUseCase = reloadUrlUseCase.reload,
selectTabUseCase = selectTabUseCase.selectTab,
appStore = appStore,
navController = navController,
viewLifecycleScope = scope,
hideOnboarding = hideOnboarding,
registerCollectionStorageObserver = registerCollectionStorageObserver,
removeCollectionWithUndo = removeCollectionWithUndo,
showTabTray = showTabTray,
)
}
private fun makeFakeRemoteWallpapers(size: Int, hasError: Boolean): List<Wallpaper> {
val list = mutableListOf<Wallpaper>()
for (i in 0 until size) {
if (hasError && i == 0) {
list.add(makeFakeRemoteWallpaper(Wallpaper.ImageFileState.Error))
} else {
list.add(makeFakeRemoteWallpaper(Wallpaper.ImageFileState.Downloaded))
}
}
return list
}
private fun makeFakeRemoteWallpaper(
thumbnailFileState: Wallpaper.ImageFileState = Wallpaper.ImageFileState.Unavailable,
) = Wallpaper(
name = "name",
collection = Wallpaper.Collection(
name = Wallpaper.firefoxCollectionName,
heading = null,
description = null,
availableLocales = null,
startDate = null,
endDate = null,
learnMoreUrl = null,
),
textColor = null,
cardColorLight = null,
cardColorDark = null,
thumbnailFileState = thumbnailFileState,
assetsFileState = Wallpaper.ImageFileState.Unavailable,
)
}