From b1ad36df71a240fc45c00f88575d25c3aab0fdbe Mon Sep 17 00:00:00 2001 From: Gabriel Luong Date: Thu, 16 Mar 2023 01:58:29 -0400 Subject: [PATCH] Bug 1822744 - Part 2: Add ToolbarController for handling home toolbar related functionalities --- .../org/mozilla/fenix/home/HomeFragment.kt | 6 + .../SessionControlController.kt | 62 -------- .../SessionControlInteractor.kt | 8 +- .../fenix/home/toolbar/ToolbarController.kt | 91 +++++++++++ .../DefaultSessionControlControllerTest.kt | 61 -------- .../home/SessionControlInteractorTest.kt | 9 +- .../interactor/RecentVisitsInteractorTest.kt | 3 + .../toolbar/DefaultToolbarControllerTest.kt | 141 ++++++++++++++++++ 8 files changed, 252 insertions(+), 129 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/home/toolbar/ToolbarController.kt create mode 100644 app/src/test/java/org/mozilla/fenix/home/toolbar/DefaultToolbarControllerTest.kt 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 087155450..77a2a9392 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -119,6 +119,7 @@ import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor import org.mozilla.fenix.home.sessioncontrol.SessionControlView import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHolder +import org.mozilla.fenix.home.toolbar.DefaultToolbarController import org.mozilla.fenix.home.topsites.DefaultTopSitesView import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.onboarding.FenixOnboarding @@ -427,6 +428,11 @@ class HomeFragment : Fragment() { activity = activity, hideOnboarding = ::hideOnboardingAndOpenSearch, ), + toolbarController = DefaultToolbarController( + activity = activity, + store = components.core.store, + navController = findNavController(), + ), ) updateLayout(binding.root) 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 4858109da..05d45c199 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 @@ -27,11 +27,9 @@ import mozilla.components.feature.tab.collections.ext.invoke import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.top.sites.TopSite import mozilla.components.support.ktx.android.view.showKeyboard -import mozilla.components.support.ktx.kotlin.isUrl import mozilla.telemetry.glean.private.NoExtras 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.Pocket @@ -148,21 +146,6 @@ interface SessionControlController { */ fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) - /** - * @see [ToolbarInteractor.onPasteAndGo] - */ - fun handlePasteAndGo(clipboardText: String) - - /** - * @see [ToolbarInteractor.onPaste] - */ - fun handlePaste(clipboardText: String) - - /** - * @see [ToolbarInteractor.onNavigateSearch] - */ - fun handleNavigateSearch() - /** * @see [CollectionInteractor.onAddTabsToCollectionTapped] */ @@ -559,51 +542,6 @@ class DefaultSessionControlController( navController.nav(R.id.homeFragment, directions) } - override fun handlePasteAndGo(clipboardText: String) { - val searchEngine = store.state.search.selectedOrDefaultSearchEngine - - activity.openToBrowserAndLoad( - searchTermOrURL = clipboardText, - newTab = true, - from = BrowserDirection.FromHome, - engine = searchEngine, - ) - - if (clipboardText.isUrl() || searchEngine == null) { - Events.enteredUrl.record(Events.EnteredUrlExtra(autocomplete = false)) - } else { - val searchAccessPoint = MetricsUtils.Source.ACTION - MetricsUtils.recordSearchMetrics( - searchEngine, - searchEngine == store.state.search.selectedOrDefaultSearchEngine, - searchAccessPoint, - ) - } - } - - override fun handlePaste(clipboardText: String) { - val directions = HomeFragmentDirections.actionGlobalSearchDialog( - sessionId = null, - pastedText = clipboardText, - ) - navController.nav(R.id.homeFragment, directions) - } - - override fun handleNavigateSearch() { - val directions = - HomeFragmentDirections.actionGlobalSearchDialog( - sessionId = null, - ) - - navController.nav( - R.id.homeFragment, - directions, - BrowserAnimator.getToolbarNavOptions(activity), - ) - - Events.searchBarTapped.record(Events.SearchBarTappedExtra("HOME")) - } - override fun handleMessageClicked(message: Message) { messageController.onMessagePressed(message) } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt index 9e86647c4..190a842fe 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt @@ -27,6 +27,7 @@ import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem.RecentHistoryGrou import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem.RecentHistoryHighlight import org.mozilla.fenix.home.recentvisits.controller.RecentVisitsController import org.mozilla.fenix.home.recentvisits.interactor.RecentVisitsInteractor +import org.mozilla.fenix.home.toolbar.ToolbarController import org.mozilla.fenix.home.toolbar.ToolbarInteractor import org.mozilla.fenix.onboarding.controller.OnboardingController import org.mozilla.fenix.onboarding.interactor.OnboardingInteractor @@ -233,6 +234,7 @@ class SessionControlInteractor( private val recentVisitsController: RecentVisitsController, private val pocketStoriesController: PocketStoriesController, private val onboardingController: OnboardingController, + private val toolbarController: ToolbarController, ) : CollectionInteractor, OnboardingInteractor, TopSiteInteractor, @@ -329,15 +331,15 @@ class SessionControlInteractor( } override fun onPasteAndGo(clipboardText: String) { - controller.handlePasteAndGo(clipboardText) + toolbarController.handlePasteAndGo(clipboardText) } override fun onPaste(clipboardText: String) { - controller.handlePaste(clipboardText) + toolbarController.handlePaste(clipboardText) } override fun onNavigateSearch() { - controller.handleNavigateSearch() + toolbarController.handleNavigateSearch() } override fun onRemoveCollectionsPlaceholder() { diff --git a/app/src/main/java/org/mozilla/fenix/home/toolbar/ToolbarController.kt b/app/src/main/java/org/mozilla/fenix/home/toolbar/ToolbarController.kt new file mode 100644 index 000000000..e3ae3e68a --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/toolbar/ToolbarController.kt @@ -0,0 +1,91 @@ +/* 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.toolbar + +import androidx.navigation.NavController +import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.support.ktx.kotlin.isUrl +import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.GleanMetrics.Events +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.BrowserAnimator +import org.mozilla.fenix.components.metrics.MetricsUtils +import org.mozilla.fenix.ext.nav +import org.mozilla.fenix.home.HomeFragmentDirections + +/** + * An interface that handles the view manipulation of the home screen toolbar. + */ +interface ToolbarController { + /** + * @see [ToolbarInteractor.onPasteAndGo] + */ + fun handlePasteAndGo(clipboardText: String) + + /** + * @see [ToolbarInteractor.onPaste] + */ + fun handlePaste(clipboardText: String) + + /** + * @see [ToolbarInteractor.onNavigateSearch] + */ + fun handleNavigateSearch() +} + +/** + * The default implementation of [ToolbarController]. + */ +class DefaultToolbarController( + private val activity: HomeActivity, + private val store: BrowserStore, + private val navController: NavController, +) : ToolbarController { + override fun handlePasteAndGo(clipboardText: String) { + val searchEngine = store.state.search.selectedOrDefaultSearchEngine + + activity.openToBrowserAndLoad( + searchTermOrURL = clipboardText, + newTab = true, + from = BrowserDirection.FromHome, + engine = searchEngine, + ) + + if (clipboardText.isUrl() || searchEngine == null) { + Events.enteredUrl.record(Events.EnteredUrlExtra(autocomplete = false)) + } else { + val searchAccessPoint = MetricsUtils.Source.ACTION + MetricsUtils.recordSearchMetrics( + searchEngine, + searchEngine == store.state.search.selectedOrDefaultSearchEngine, + searchAccessPoint, + ) + } + } + override fun handlePaste(clipboardText: String) { + val directions = HomeFragmentDirections.actionGlobalSearchDialog( + sessionId = null, + pastedText = clipboardText, + ) + navController.nav(R.id.homeFragment, directions) + } + + override fun handleNavigateSearch() { + val directions = + HomeFragmentDirections.actionGlobalSearchDialog( + sessionId = null, + ) + + navController.nav( + R.id.homeFragment, + directions, + BrowserAnimator.getToolbarNavOptions(activity), + ) + + Events.searchBarTapped.record(Events.SearchBarTappedExtra("HOME")) + } +} 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 6d4523011..7e7f6537a 100644 --- a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt @@ -973,67 +973,6 @@ class DefaultSessionControlControllerTest { } } - @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 { it.actionId == R.id.action_global_search_dialog }, - null, - ) - } - } - - @Test - fun handleNavigateSearch() { - assertNull(Events.searchBarTapped.testGetValue()) - - createController().handleNavigateSearch() - - assertNotNull(Events.searchBarTapped.testGetValue()) - val recordedEvents = Events.searchBarTapped.testGetValue()!! - assertEquals(1, recordedEvents.size) - assertEquals("HOME", recordedEvents.single().extra?.getValue("source")) - - verify { - navController.navigate( - match { it.actionId == R.id.action_global_search_dialog }, - any(), - ) - } - } - @Test fun handleRemoveCollectionsPlaceholder() { createController().handleRemoveCollectionsPlaceholder() diff --git a/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt index 9095e6689..f6959da5b 100644 --- a/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt @@ -24,6 +24,7 @@ import org.mozilla.fenix.home.recenttabs.controller.RecentTabController import org.mozilla.fenix.home.recentvisits.controller.RecentVisitsController import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor +import org.mozilla.fenix.home.toolbar.ToolbarController import org.mozilla.fenix.onboarding.controller.OnboardingController class SessionControlInteractorTest { @@ -34,6 +35,7 @@ class SessionControlInteractorTest { private val recentBookmarksController: RecentBookmarksController = mockk(relaxed = true) private val pocketStoriesController: PocketStoriesController = mockk(relaxed = true) private val onboardingController: OnboardingController = mockk(relaxed = true) + private val toolbarController: ToolbarController = mockk(relaxed = true) // Note: the recent visits tests are handled in [RecentVisitsInteractorTest] and [RecentVisitsControllerTest] private val recentVisitsController: RecentVisitsController = mockk(relaxed = true) @@ -50,6 +52,7 @@ class SessionControlInteractorTest { recentVisitsController, pocketStoriesController, onboardingController, + toolbarController, ) } @@ -137,19 +140,19 @@ class SessionControlInteractorTest { @Test fun onPaste() { interactor.onPaste("text") - verify { controller.handlePaste("text") } + verify { toolbarController.handlePaste("text") } } @Test fun onPasteAndGo() { interactor.onPasteAndGo("text") - verify { controller.handlePasteAndGo("text") } + verify { toolbarController.handlePasteAndGo("text") } } @Test fun onNavigateSearch() { interactor.onNavigateSearch() - verify { controller.handleNavigateSearch() } + verify { toolbarController.handleNavigateSearch() } } @Test diff --git a/app/src/test/java/org/mozilla/fenix/home/recentvisits/interactor/RecentVisitsInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/home/recentvisits/interactor/RecentVisitsInteractorTest.kt index 5ecbaaec5..7988a1c4b 100644 --- a/app/src/test/java/org/mozilla/fenix/home/recentvisits/interactor/RecentVisitsInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/recentvisits/interactor/RecentVisitsInteractorTest.kt @@ -20,6 +20,7 @@ import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem.RecentHistoryHigh import org.mozilla.fenix.home.recentvisits.controller.RecentVisitsController import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor +import org.mozilla.fenix.home.toolbar.ToolbarController import org.mozilla.fenix.onboarding.controller.OnboardingController class RecentVisitsInteractorTest { @@ -31,6 +32,7 @@ class RecentVisitsInteractorTest { private val pocketStoriesController: PocketStoriesController = mockk(relaxed = true) private val recentVisitsController: RecentVisitsController = mockk(relaxed = true) private val onboardingController: OnboardingController = mockk(relaxed = true) + private val toolbarController: ToolbarController = mockk(relaxed = true) private lateinit var interactor: SessionControlInteractor @@ -44,6 +46,7 @@ class RecentVisitsInteractorTest { recentVisitsController, pocketStoriesController, onboardingController, + toolbarController, ) } diff --git a/app/src/test/java/org/mozilla/fenix/home/toolbar/DefaultToolbarControllerTest.kt b/app/src/test/java/org/mozilla/fenix/home/toolbar/DefaultToolbarControllerTest.kt new file mode 100644 index 000000000..191be9071 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/toolbar/DefaultToolbarControllerTest.kt @@ -0,0 +1,141 @@ +/* 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.toolbar + +import androidx.navigation.NavController +import androidx.navigation.NavDirections +import androidx.navigation.NavOptions +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.SearchState +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.service.glean.testing.GleanTestRule +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.GleanMetrics.Events +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.utils.Settings + +@RunWith(FenixRobolectricTestRunner::class) // For gleanTestRule +class DefaultToolbarControllerTest { + + @get:Rule + val gleanTestRule = GleanTestRule(testContext) + + private val activity: HomeActivity = mockk(relaxed = true) + private val navController: NavController = mockk(relaxed = true) + private val settings: Settings = mockk(relaxed = true) + + 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 lateinit var store: BrowserStore + + @Before + fun setup() { + store = BrowserStore( + initialState = BrowserState( + search = SearchState( + regionSearchEngines = listOf(searchEngine), + ), + ), + ) + + every { navController.currentDestination } returns mockk { + every { id } returns R.id.homeFragment + } + every { activity.settings() } returns settings + } + + @Test + fun `WHEN Paste & Go toolbar menu is clicked THEN open the browser with the clipboard text as the search term`() { + assertNull(Events.enteredUrl.testGetValue()) + assertNull(Events.performedSearch.testGetValue()) + + var clipboardText = "text" + createController().handlePasteAndGo(clipboardText) + + verify { + activity.openToBrowserAndLoad( + searchTermOrURL = clipboardText, + newTab = true, + from = BrowserDirection.FromHome, + engine = searchEngine, + ) + } + + assertNotNull(Events.performedSearch.testGetValue()) + + clipboardText = "https://mozilla.org" + createController().handlePasteAndGo(clipboardText) + + verify { + activity.openToBrowserAndLoad( + searchTermOrURL = clipboardText, + newTab = true, + from = BrowserDirection.FromHome, + engine = searchEngine, + ) + } + + assertNotNull(Events.enteredUrl.testGetValue()) + } + + @Test + fun `WHEN Paste toolbar menu is clicked THEN navigate to the search dialog`() { + createController().handlePaste("text") + + verify { + navController.navigate( + match { it.actionId == R.id.action_global_search_dialog }, + null, + ) + } + } + + @Test + fun `WHEN the toolbar is tapped THEN navigate to the search dialog`() { + assertNull(Events.searchBarTapped.testGetValue()) + + createController().handleNavigateSearch() + + assertNotNull(Events.searchBarTapped.testGetValue()) + + val recordedEvents = Events.searchBarTapped.testGetValue()!! + assertEquals(1, recordedEvents.size) + assertEquals("HOME", recordedEvents.single().extra?.getValue("source")) + + verify { + navController.navigate( + match { it.actionId == R.id.action_global_search_dialog }, + any(), + ) + } + } + + private fun createController() = DefaultToolbarController( + activity = activity, + store = store, + navController = navController, + ) +}