From eed20b43b9fb5fba1224aec010c1b5634e95ff35 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Fri, 17 Jul 2020 13:07:01 -0700 Subject: [PATCH] Test session control controller (#12652) --- .../org/mozilla/fenix/home/HomeFragment.kt | 32 ++- .../SessionControlController.kt | 52 +++-- .../fenix/IntentReceiverActivityTest.kt | 9 +- .../DefaultSessionControlControllerTest.kt | 203 ++++++++++++++---- .../viewholders/AboutItemViewHolderTest.kt | 2 +- 5 files changed, 212 insertions(+), 86 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 60240cfd2..9ac274560 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -98,7 +98,6 @@ import org.mozilla.fenix.home.sessioncontrol.SessionControlView import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder import org.mozilla.fenix.onboarding.FenixOnboarding import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.settings.SupportUtils.MozillaPage.PRIVATE_NOTICE import org.mozilla.fenix.settings.SupportUtils.SumoTopic.HELP import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit import org.mozilla.fenix.tabtray.TabTrayDialogFragment @@ -175,6 +174,7 @@ class HomeFragment : Fragment() { ): View? { val view = inflater.inflate(R.layout.fragment_home, container, false) val activity = activity as HomeActivity + val components = requireComponents currentMode = CurrentMode( view.context, @@ -186,11 +186,11 @@ class HomeFragment : Fragment() { homeFragmentStore = StoreProvider.get(this) { HomeFragmentStore( HomeFragmentState( - collections = requireComponents.core.tabCollectionStorage.cachedTabCollections, + collections = components.core.tabCollectionStorage.cachedTabCollections, expandedCollections = emptySet(), mode = currentMode.getCurrentMode(), topSites = StrictMode.allowThreadDiskReads().resetPoliciesAfter { - requireComponents.core.topSiteStorage.cachedTopSites + components.core.topSiteStorage.cachedTopSites }, tip = FenixTipManager(listOf(MigrationTipProvider(requireContext()))).getTip() ) @@ -200,16 +200,18 @@ class HomeFragment : Fragment() { _sessionControlInteractor = SessionControlInteractor( DefaultSessionControlController( activity = activity, + engine = components.core.engine, + metrics = components.analytics.metrics, + sessionManager = sessionManager, + tabCollectionStorage = components.core.tabCollectionStorage, + topSiteStorage = components.core.topSiteStorage, + addTabUseCase = components.useCases.tabsUseCases.addTab, fragmentStore = homeFragmentStore, navController = findNavController(), viewLifecycleScope = viewLifecycleOwner.lifecycleScope, - getListOfTabs = ::getListOfTabs, hideOnboarding = ::hideOnboardingAndOpenSearch, registerCollectionStorageObserver = ::registerCollectionStorageObserver, showDeleteCollectionPrompt = ::showDeleteCollectionPrompt, - openSettingsScreen = ::openSettingsScreen, - openWhatsNewLink = { openInNormalTab(SupportUtils.getWhatsNewUrl(activity)) }, - openPrivacyNotice = { openInNormalTab(SupportUtils.getMozillaPageUrl(PRIVATE_NOTICE)) }, showTabTray = ::openTabTray ) ) @@ -611,11 +613,6 @@ class HomeFragment : Fragment() { nav(R.id.homeFragment, directions, getToolbarNavOptions(requireContext())) } - private fun openSettingsScreen() { - val directions = HomeFragmentDirections.actionGlobalPrivateBrowsingFragment() - nav(R.id.homeFragment, directions) - } - private fun openInNormalTab(url: String) { (activity as HomeActivity).openToBrowserAndLoad( searchTermOrURL = url, @@ -767,13 +764,8 @@ class HomeFragment : Fragment() { } } - private fun getListOfSessions(private: Boolean = browsingModeManager.mode.isPrivate): List { - return sessionManager.sessionsOfType(private = private) - .toList() - } - - private fun getListOfTabs(): List { - return getListOfSessions().toTabs() + private fun getNumberOfSessions(private: Boolean = browsingModeManager.mode.isPrivate): Int { + return sessionManager.sessionsOfType(private = private).count() } private fun registerCollectionStorageObserver() { @@ -787,7 +779,7 @@ class HomeFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launch { val recyclerView = sessionControlView!!.view delay(ANIM_SCROLL_DELAY) - val tabsSize = getListOfSessions().size + val tabsSize = getNumberOfSessions() var indexOfCollection = tabsSize + NON_TAB_ITEM_NUM changedCollection?.let { changedCollection -> diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt index 74b4e1bd0..2935fd075 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt @@ -9,9 +9,11 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import mozilla.components.browser.session.SessionManager +import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.ext.restore +import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.top.sites.TopSite import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity @@ -23,13 +25,12 @@ import org.mozilla.fenix.components.TopSiteStorage import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.tips.Tip -import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav +import org.mozilla.fenix.ext.sessionsOfType import org.mozilla.fenix.home.HomeFragment import org.mozilla.fenix.home.HomeFragmentAction import org.mozilla.fenix.home.HomeFragmentDirections import org.mozilla.fenix.home.HomeFragmentStore -import org.mozilla.fenix.home.Tab import org.mozilla.fenix.settings.SupportUtils import mozilla.components.feature.tab.collections.Tab as ComponentTab @@ -130,26 +131,20 @@ interface SessionControlController { @SuppressWarnings("TooManyFunctions", "LargeClass") class DefaultSessionControlController( private val activity: HomeActivity, + private val engine: Engine, + private val metrics: MetricController, + private val sessionManager: SessionManager, + private val tabCollectionStorage: TabCollectionStorage, + private val topSiteStorage: TopSiteStorage, + private val addTabUseCase: TabsUseCases.AddNewTabUseCase, private val fragmentStore: HomeFragmentStore, private val navController: NavController, private val viewLifecycleScope: CoroutineScope, - private val getListOfTabs: () -> List, private val hideOnboarding: () -> Unit, private val registerCollectionStorageObserver: () -> Unit, private val showDeleteCollectionPrompt: (tabCollection: TabCollection, title: String?, message: String) -> Unit, - private val openSettingsScreen: () -> Unit, - private val openWhatsNewLink: () -> Unit, - private val openPrivacyNotice: () -> Unit, private val showTabTray: () -> Unit ) : SessionControlController { - private val metrics: MetricController - get() = activity.components.analytics.metrics - private val sessionManager: SessionManager - get() = activity.components.core.sessionManager - private val tabCollectionStorage: TabCollectionStorage - get() = activity.components.core.tabCollectionStorage - private val topSiteStorage: TopSiteStorage - get() = activity.components.core.topSiteStorage override fun handleCollectionAddTabTapped(collection: TabCollection) { metrics.track(Event.CollectionAddTabPressed) @@ -162,7 +157,7 @@ class DefaultSessionControlController( override fun handleCollectionOpenTabClicked(tab: ComponentTab) { sessionManager.restore( activity, - activity.components.core.engine, + engine, tab, onTabRestored = { activity.openToBrowser(BrowserDirection.FromHome) @@ -182,10 +177,10 @@ class DefaultSessionControlController( override fun handleCollectionOpenTabsTapped(collection: TabCollection) { sessionManager.restore( activity, - activity.components.core.engine, + engine, collection, onFailure = { url -> - activity.components.useCases.tabsUseCases.addTab.invoke(url) + addTabUseCase.invoke(url) } ) @@ -261,7 +256,7 @@ class DefaultSessionControlController( metrics.track(Event.TopSiteOpenInNewTab) if (isDefault) { metrics.track(Event.TopSiteOpenDefault) } if (url == SupportUtils.POCKET_TRENDING_URL) { metrics.track(Event.PocketTopSiteClicked) } - activity.components.useCases.tabsUseCases.addTab.invoke( + addTabUseCase.invoke( url = url, selectTab = true, startLoading = true @@ -274,15 +269,24 @@ class DefaultSessionControlController( } override fun handleOpenSettingsClicked() { - openSettingsScreen() + val directions = HomeFragmentDirections.actionGlobalPrivateBrowsingFragment() + navController.nav(R.id.homeFragment, directions) } override fun handleWhatsNewGetAnswersClicked() { - openWhatsNewLink() + activity.openToBrowserAndLoad( + searchTermOrURL = SupportUtils.getWhatsNewUrl(activity), + newTab = true, + from = BrowserDirection.FromHome + ) } override fun handleReadPrivacyNoticeClicked() { - openPrivacyNotice() + activity.openToBrowserAndLoad( + searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE), + newTab = true, + from = BrowserDirection.FromHome + ) } override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) { @@ -303,7 +307,11 @@ class DefaultSessionControlController( // Only register the observer right before moving to collection creation registerCollectionStorageObserver() - val tabIds = getListOfTabs().map { it.sessionId }.toTypedArray() + val tabIds = sessionManager + .sessionsOfType(private = activity.browsingModeManager.mode.isPrivate) + .map { session -> session.id } + .toList() + .toTypedArray() val directions = HomeFragmentDirections.actionGlobalCollectionCreationFragment( tabIds = tabIds, saveCollectionStep = step, diff --git a/app/src/test/java/org/mozilla/fenix/IntentReceiverActivityTest.kt b/app/src/test/java/org/mozilla/fenix/IntentReceiverActivityTest.kt index 436f0feee..b66bc231c 100644 --- a/app/src/test/java/org/mozilla/fenix/IntentReceiverActivityTest.kt +++ b/app/src/test/java/org/mozilla/fenix/IntentReceiverActivityTest.kt @@ -12,9 +12,11 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic +import io.mockk.unmockkStatic import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runBlockingTest import mozilla.components.feature.intent.processing.IntentProcessor +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -39,6 +41,7 @@ class IntentReceiverActivityTest { @Before fun setup() { + mockkStatic("org.mozilla.fenix.ext.ContextKt") settings = mockk() intentProcessors = mockk() @@ -54,6 +57,11 @@ class IntentReceiverActivityTest { coEvery { intentProcessors.intentProcessor.process(any()) } returns true } + @After + fun teardown() { + unmockkStatic("org.mozilla.fenix.ext.ContextKt") + } + @Test fun `process intent with flag launched from history`() = runBlockingTest { val intent = Intent() @@ -185,7 +193,6 @@ class IntentReceiverActivityTest { } private fun attachMocks(activity: Activity) { - mockkStatic("org.mozilla.fenix.ext.ContextKt") every { activity.settings() } returns settings every { activity.components.analytics } returns mockk(relaxed = true) every { activity.components.intentProcessors } returns intentProcessors diff --git a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt index aa67649dc..f6fa9226d 100644 --- a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt @@ -5,13 +5,13 @@ package org.mozilla.fenix.home import androidx.navigation.NavController +import androidx.navigation.NavDirections import io.mockk.every import io.mockk.mockk -import io.mockk.mockkStatic import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.MainScope import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScope import mozilla.components.browser.session.SessionManager import mozilla.components.concept.engine.Engine import mozilla.components.feature.tab.collections.TabCollection @@ -23,12 +23,11 @@ import org.junit.Test import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R -import org.mozilla.fenix.collections.SaveCollectionStep import org.mozilla.fenix.components.TabCollectionStorage +import org.mozilla.fenix.components.TopSiteStorage import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.nav +import org.mozilla.fenix.components.tips.Tip import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController import org.mozilla.fenix.settings.SupportUtils import mozilla.components.feature.tab.collections.Tab as ComponentTab @@ -42,77 +41,138 @@ class DefaultSessionControlControllerTest { private val activity: HomeActivity = mockk(relaxed = true) private val fragmentStore: HomeFragmentStore = mockk(relaxed = true) private val navController: NavController = mockk(relaxed = true) - private val getListOfTabs: () -> List = { emptyList() } + private val metrics: MetricController = mockk(relaxed = true) + private val sessionManager: SessionManager = mockk(relaxed = true) + private val engine: Engine = mockk(relaxed = true) + private val tabCollectionStorage: TabCollectionStorage = mockk(relaxed = true) + private val topSiteStorage: TopSiteStorage = mockk(relaxed = true) + private val tabsUseCases: TabsUseCases = mockk(relaxed = true) + private val hideOnboarding: () -> Unit = mockk(relaxed = true) - private val openSettingsScreen: () -> Unit = mockk(relaxed = true) - private val openWhatsNewLink: () -> Unit = mockk(relaxed = true) - private val openPrivacyNotice: () -> Unit = mockk(relaxed = true) private val registerCollectionStorageObserver: () -> Unit = mockk(relaxed = true) private val showTabTray: () -> Unit = mockk(relaxed = true) private val showDeleteCollectionPrompt: (tabCollection: TabCollection, title: String?, message: String) -> Unit = mockk(relaxed = true) - private val metrics: MetricController = mockk(relaxed = true) - private val state: HomeFragmentState = mockk(relaxed = true) - private val sessionManager: SessionManager = 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 lateinit var controller: DefaultSessionControlController @Before fun setup() { - mockkStatic("org.mozilla.fenix.ext.ContextKt") - every { activity.components.core.engine } returns engine - every { activity.components.core.sessionManager } returns sessionManager - every { activity.components.core.tabCollectionStorage } returns tabCollectionStorage - every { activity.components.useCases.tabsUseCases } returns tabsUseCases - - every { fragmentStore.state } returns state - every { state.collections } returns emptyList() - every { state.expandedCollections } returns emptySet() - every { state.mode } returns Mode.Normal - every { activity.components.analytics.metrics } returns metrics + every { fragmentStore.state } returns HomeFragmentState( + collections = emptyList(), + expandedCollections = emptySet(), + mode = Mode.Normal, + topSites = emptyList() + ) + every { sessionManager.sessions } returns emptyList() + every { navController.currentDestination } returns mockk { + every { id } returns R.id.homeFragment + } controller = DefaultSessionControlController( activity = activity, + engine = engine, + metrics = metrics, + sessionManager = sessionManager, + tabCollectionStorage = tabCollectionStorage, + topSiteStorage = topSiteStorage, + addTabUseCase = tabsUseCases.addTab, fragmentStore = fragmentStore, navController = navController, - viewLifecycleScope = MainScope(), - getListOfTabs = getListOfTabs, + viewLifecycleScope = TestCoroutineScope(), hideOnboarding = hideOnboarding, registerCollectionStorageObserver = registerCollectionStorageObserver, showDeleteCollectionPrompt = showDeleteCollectionPrompt, - openSettingsScreen = openSettingsScreen, - openWhatsNewLink = openWhatsNewLink, - openPrivacyNotice = openPrivacyNotice, showTabTray = showTabTray ) } @Test fun handleCollectionAddTabTapped() { - val collection: TabCollection = mockk(relaxed = true) + val collection = mockk { + every { id } returns 12L + } controller.handleCollectionAddTabTapped(collection) + verify { metrics.track(Event.CollectionAddTabPressed) } + verify { + navController.navigate( + match { it.actionId == R.id.action_global_collectionCreationFragment }, + null + ) + } } @Test - fun handleCollectionOpenTabClicked() { - val tab: ComponentTab = mockk(relaxed = true) + fun `handleCollectionOpenTabClicked onFailure`() { + val tab = mockk { + every { url } returns "https://mozilla.org" + every { restore(activity, engine, restoreSessionId = false) } returns null + } controller.handleCollectionOpenTabClicked(tab) + verify { metrics.track(Event.CollectionTabRestored) } + verify { + activity.openToBrowserAndLoad( + searchTermOrURL = "https://mozilla.org", + newTab = true, + from = BrowserDirection.FromHome + ) + } + } + + @Test + fun `handleCollectionOpenTabClicked onTabRestored`() { + val tab = mockk { + every { restore(activity, engine, restoreSessionId = false) } returns mockk { + every { session } returns mockk() + every { engineSessionState } returns mockk() + } + } + controller.handleCollectionOpenTabClicked(tab) + + verify { metrics.track(Event.CollectionTabRestored) } + verify { activity.openToBrowser(BrowserDirection.FromHome) } } @Test fun handleCollectionOpenTabsTapped() { - val collection: TabCollection = mockk(relaxed = true) + val collection = mockk { + every { tabs } returns emptyList() + } controller.handleCollectionOpenTabsTapped(collection) + verify { metrics.track(Event.CollectionAllTabsRestored) } } @Test - fun handleCollectionRemoveTab() { + fun `handleCollectionRemoveTab one tab`() { + val collection = mockk { + every { tabs } returns listOf(mockk()) + every { title } returns "Collection" + } + val tab = mockk() + 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." + + controller.handleCollectionRemoveTab(collection, tab) + + verify { metrics.track(Event.CollectionTabRemoved) } + verify { + showDeleteCollectionPrompt( + collection, + "Delete Collection?", + "Deleting this tab will delete everything." + ) + } + } + + @Test + fun `handleCollectionRemoveTab multiple tabs`() { val collection: TabCollection = mockk(relaxed = true) val tab: ComponentTab = mockk(relaxed = true) controller.handleCollectionRemoveTab(collection, tab) @@ -121,9 +181,18 @@ class DefaultSessionControlControllerTest { @Test fun handleCollectionShareTabsClicked() { - val collection: TabCollection = mockk(relaxed = true) + val collection = mockk { + every { tabs } returns emptyList() + } controller.handleCollectionShareTabsClicked(collection) + verify { metrics.track(Event.CollectionShared) } + verify { + navController.navigate( + match { it.actionId == R.id.action_global_shareFragment }, + null + ) + } } @Test @@ -160,9 +229,18 @@ class DefaultSessionControlControllerTest { @Test fun handleRenameCollectionTapped() { - val collection: TabCollection = mockk(relaxed = true) + val collection = mockk { + every { id } returns 3L + } controller.handleRenameCollectionTapped(collection) + verify { metrics.track(Event.CollectionRenamePressed) } + verify { + navController.navigate( + match { it.actionId == R.id.action_global_collectionCreationFragment }, + null + ) + } } @Test @@ -203,20 +281,61 @@ class DefaultSessionControlControllerTest { @Test fun handleOpenSettingsClicked() { controller.handleOpenSettingsClicked() - verify { openSettingsScreen() } + verify { + navController.navigate( + match { it.actionId == R.id.action_global_privateBrowsingFragment }, + null + ) + } + } + + @Test + fun handleWhatsNewGetAnswersClicked() { + controller.handleWhatsNewGetAnswersClicked() + verify { + activity.openToBrowserAndLoad( + searchTermOrURL = SupportUtils.getWhatsNewUrl(activity), + newTab = true, + from = BrowserDirection.FromHome + ) + } + } + + @Test + fun handleReadPrivacyNoticeClicked() { + controller.handleReadPrivacyNoticeClicked() + verify { + activity.openToBrowserAndLoad( + searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE), + newTab = true, + from = BrowserDirection.FromHome + ) + } } @Test fun handleToggleCollectionExpanded() { - val collection: TabCollection = mockk(relaxed = true) + val collection = mockk() controller.handleToggleCollectionExpanded(collection, true) verify { fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, true)) } } + @Test + fun handleCloseTip() { + val tip = mockk() + controller.handleCloseTip(tip) + verify { fragmentStore.dispatch(HomeFragmentAction.RemoveTip(tip)) } + } + @Test fun handleCreateCollection() { controller.handleCreateCollection() - val directions = HomeFragmentDirections.actionGlobalCollectionCreationFragment(saveCollectionStep = SaveCollectionStep.SelectTabs) - verify { navController.nav(R.id.homeFragment, directions) } + + verify { + navController.navigate( + match { it.actionId == R.id.action_global_collectionCreationFragment }, + null + ) + } } } diff --git a/app/src/test/java/org/mozilla/fenix/settings/about/viewholders/AboutItemViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/settings/about/viewholders/AboutItemViewHolderTest.kt index 87b120dbf..ec2dd0e14 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/about/viewholders/AboutItemViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/about/viewholders/AboutItemViewHolderTest.kt @@ -44,7 +44,7 @@ class AboutItemViewHolderTest { fun `call listener on click`() { val holder = AboutItemViewHolder(view, listener) holder.bind(item) - view.performClick() + holder.itemView.performClick() verify { listener.onAboutItemClicked(AboutItem.Libraries) } }