diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index f1ae8a80a..74a7122c1 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -18,6 +18,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.component_search.* import kotlinx.android.synthetic.main.fragment_browser.* @@ -46,6 +47,7 @@ import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.view.exitImmersiveModeIfNeeded import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.R import org.mozilla.fenix.ThemeManager import org.mozilla.fenix.collections.CreateCollectionViewModel @@ -65,6 +67,8 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.enterToImmersiveMode import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.ext.toTab +import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.utils.Settings /** @@ -142,7 +146,14 @@ abstract class BaseBrowserFragment : Fragment(), BackHandler { nestedScrollQuickActionView = nestedScrollQuickAction, engineView = engineView, currentSession = session, - viewModel = viewModel + viewModel = viewModel, + getSupportUrl = { SupportUtils.getSumoURLForTopic(context!!, SupportUtils.SumoTopic.HELP) }, + openInFenixIntent = Intent(context, IntentReceiverActivity::class.java).also { + it.action = Intent.ACTION_VIEW + it.flags = Intent.FLAG_ACTIVITY_NEW_TASK + }, + currentSessionAsTab = session.toTab(context!!), + bottomSheetBehavior = BottomSheetBehavior.from(nestedScrollQuickAction) ) browserInteractor = createBrowserToolbarViewInteractor(browserToolbarController, session) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 8800bcf11..9493010de 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -19,6 +19,7 @@ import androidx.navigation.fragment.findNavController import androidx.transition.TransitionInflater import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.component_search.* +import kotlinx.android.synthetic.main.fragment_browser.* import kotlinx.android.synthetic.main.fragment_browser.view.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt index ec47345cf..553ba4b2b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt @@ -14,7 +14,6 @@ import mozilla.components.browser.session.Session import mozilla.components.concept.engine.EngineView import org.mozilla.fenix.BrowsingModeManager import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserFragment import org.mozilla.fenix.browser.BrowserFragmentDirections @@ -23,10 +22,8 @@ import org.mozilla.fenix.collections.getStepForCollectionsSize import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav -import org.mozilla.fenix.ext.toTab +import org.mozilla.fenix.home.sessioncontrol.Tab import org.mozilla.fenix.lib.Do -import org.mozilla.fenix.quickactionsheet.QuickActionSheetBehavior -import org.mozilla.fenix.settings.SupportUtils /** * An interface that handles the view manipulation of the BrowserToolbar, triggered by the Interactor @@ -43,7 +40,11 @@ class DefaultBrowserToolbarController( private val nestedScrollQuickActionView: NestedScrollView, private val engineView: EngineView, private val currentSession: Session, - private val viewModel: CreateCollectionViewModel + private val viewModel: CreateCollectionViewModel, + private val getSupportUrl: () -> String, + private val openInFenixIntent: Intent, + private val currentSessionAsTab: Tab, + private val bottomSheetBehavior: BottomSheetBehavior ) : BrowserToolbarController { override fun handleToolbarClick() { @@ -93,9 +94,7 @@ class DefaultBrowserToolbarController( (context as HomeActivity).browsingModeManager.mode = BrowsingModeManager.Mode.Private } ToolbarMenu.Item.FindInPage -> { - (BottomSheetBehavior.from(nestedScrollQuickActionView) as QuickActionSheetBehavior).apply { - state = BottomSheetBehavior.STATE_COLLAPSED - } + bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED findInPageLauncher() context.components.analytics.metrics.track(Event.FindInPageOpened) } @@ -106,12 +105,7 @@ class DefaultBrowserToolbarController( } } ToolbarMenu.Item.Help -> { - context.components.useCases.tabsUseCases.addTab.invoke( - SupportUtils.getSumoURLForTopic( - context, - SupportUtils.SumoTopic.HELP - ) - ) + context.components.useCases.tabsUseCases.addTab.invoke(getSupportUrl()) } ToolbarMenu.Item.NewTab -> { val directions = BrowserFragmentDirections @@ -123,10 +117,9 @@ class DefaultBrowserToolbarController( ToolbarMenu.Item.SaveToCollection -> { context.components.analytics.metrics .track(Event.CollectionSaveButtonPressed(TELEMETRY_BROWSER_IDENTIFIER)) - currentSession.let { - val tab = it.toTab(context) - viewModel.tabs = listOf(tab) - val selectedSet = mutableSetOf(tab) + + viewModel.tabs = listOf(currentSessionAsTab) + val selectedSet = mutableSetOf(currentSessionAsTab) viewModel.selectedTabs = selectedSet viewModel.tabCollections = context.components.core.tabCollectionStorage.cachedTabCollections.reversed() @@ -136,7 +129,6 @@ class DefaultBrowserToolbarController( val directions = BrowserFragmentDirections.actionBrowserFragmentToCreateCollectionFragment() navController.nav(R.id.browserFragment, directions) - } } ToolbarMenu.Item.OpenInFenix -> { // Release the session from this view so that it can immediately be rendered by a different view @@ -147,10 +139,7 @@ class DefaultBrowserToolbarController( context.components.core.sessionManager.select(currentSession) // Switch to the actual browser which should now display our new selected session - context.startActivity(Intent(context, IntentReceiverActivity::class.java).also { - it.action = Intent.ACTION_VIEW - it.flags = Intent.FLAG_ACTIVITY_NEW_TASK - }) + context.startActivity(openInFenixIntent) // Close this activity since it is no longer displaying any session (context as Activity).finish() @@ -188,6 +177,6 @@ class DefaultBrowserToolbarController( } companion object { - private const val TELEMETRY_BROWSER_IDENTIFIER = "browserMenu" + internal const val TELEMETRY_BROWSER_IDENTIFIER = "browserMenu" } } diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt new file mode 100644 index 000000000..bda7388b6 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt @@ -0,0 +1,338 @@ +/* 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.components.toolbar + +import android.content.Intent +import androidx.core.widget.NestedScrollView +import androidx.navigation.NavController +import com.google.android.material.bottomsheet.BottomSheetBehavior +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.verify +import mozilla.components.browser.session.Session +import mozilla.components.browser.session.SessionManager +import mozilla.components.concept.engine.EngineView +import mozilla.components.feature.session.SessionUseCases +import mozilla.components.feature.tabs.TabsUseCases +import org.junit.Before +import org.junit.Test +import org.mozilla.fenix.BrowsingModeManager +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.BrowserFragment +import org.mozilla.fenix.browser.BrowserFragmentDirections +import org.mozilla.fenix.collections.CreateCollectionViewModel +import org.mozilla.fenix.collections.SaveCollectionStep +import org.mozilla.fenix.components.Analytics +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.metrics +import org.mozilla.fenix.ext.nav +import org.mozilla.fenix.home.sessioncontrol.Tab +import org.mozilla.fenix.home.sessioncontrol.TabCollection + +class DefaultBrowserToolbarControllerTest { + + private var context: HomeActivity = mockk(relaxed = true) + private var analytics: Analytics = mockk(relaxed = true) + private var navController: NavController = mockk(relaxed = true) + private var findInPageLauncher: () -> Unit = mockk(relaxed = true) + private val nestedScrollQuickActionView: NestedScrollView = mockk(relaxed = true) + private val engineView: EngineView = mockk(relaxed = true) + private val currentSession: Session = mockk(relaxed = true) + private val viewModel: CreateCollectionViewModel = mockk(relaxed = true) + private val getSupportUrl: () -> String = { "https://supportUrl.org" } + private val openInFenixIntent: Intent = mockk(relaxed = true) + private val currentSessionAsTab: Tab = mockk(relaxed = true) + private val bottomSheetBehavior: BottomSheetBehavior = mockk(relaxed = true) + private val metrics: MetricController = mockk(relaxed = true) + private val sessionUseCases: SessionUseCases = mockk(relaxed = true) + + private lateinit var controller: DefaultBrowserToolbarController + + @Before + fun setUp() { + controller = DefaultBrowserToolbarController( + context = context, + navController = navController, + findInPageLauncher = findInPageLauncher, + nestedScrollQuickActionView = nestedScrollQuickActionView, + engineView = engineView, + currentSession = currentSession, + viewModel = viewModel, + getSupportUrl = getSupportUrl, + openInFenixIntent = openInFenixIntent, + currentSessionAsTab = currentSessionAsTab, + bottomSheetBehavior = bottomSheetBehavior + ) + + every { context.components.analytics } returns analytics + every { analytics.metrics } returns metrics + every { context.components.useCases.sessionUseCases } returns sessionUseCases + } + + @Test + fun handleToolbarClick() { + every { currentSession.id } returns "1" + + controller.handleToolbarClick() + + verify { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) } + verify { navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(currentSession.id) + ) } + } + + @Test + fun handleToolbarBackPress() { + val item = ToolbarMenu.Item.Back + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BACK)) } + verify { sessionUseCases.goBack } + } + + @Test + fun handleToolbarForwardPress() { + val item = ToolbarMenu.Item.Forward + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) } + verify { sessionUseCases.goForward } + } + + @Test + fun handleToolbarReloadPress() { + val item = ToolbarMenu.Item.Reload + + every { context.components.useCases.sessionUseCases } returns sessionUseCases + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.RELOAD)) } + verify { sessionUseCases.reload } + } + + @Test + fun handleToolbarStopPress() { + val item = ToolbarMenu.Item.Stop + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.STOP)) } + verify { sessionUseCases.stopLoading } + } + + @Test + fun handleToolbarSettingsPress() { + val item = ToolbarMenu.Item.Settings + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SETTINGS)) } + verify { navController.nav( + R.id.settingsFragment, + BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment() + ) } + } + @Test + fun handleToolbarLibraryPress() { + val item = ToolbarMenu.Item.Library + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.LIBRARY)) } + verify { navController.nav( + R.id.libraryFragment, + BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment() + ) } + } + + @Test + fun handleToolbarRequestDesktopOnPress() { + val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = mockk(relaxed = true) + val item = ToolbarMenu.Item.RequestDesktop(true) + + every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON)) } + verify { + requestDesktopSiteUseCase.invoke( + true, + currentSession + ) + } + } + + @Test + fun handleToolbarRequestDesktopOffPress() { + val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = mockk(relaxed = true) + val item = ToolbarMenu.Item.RequestDesktop(false) + + every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF)) } + verify { + requestDesktopSiteUseCase.invoke( + false, + currentSession + ) + } + } + + @Test + fun handleToolbarSharePress() { + val item = ToolbarMenu.Item.Share + + every { currentSession.url } returns "https://mozilla.org" + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SHARE)) } + verify { + val directions = BrowserFragmentDirections.actionBrowserFragmentToShareFragment(currentSession.url) + navController.nav(R.id.browserFragment, directions) + } + } + + @Test + fun handleToolbarNewPrivateTabPress() { + val browsingModeManager: BrowsingModeManager = mockk(relaxed = true) + val item = ToolbarMenu.Item.NewPrivateTab + + every { context.browsingModeManager } returns browsingModeManager + every { browsingModeManager.mode } returns BrowsingModeManager.Mode.Normal + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.NEW_PRIVATE_TAB)) } + verify { + val directions = BrowserFragmentDirections + .actionBrowserFragmentToSearchFragment(null) + navController.nav(R.id.browserFragment, directions) + } + verify { browsingModeManager.mode = BrowsingModeManager.Mode.Private } + } + + @Test + fun handleToolbarFindInPagePress() { + val item = ToolbarMenu.Item.FindInPage + + controller.handleToolbarItemInteraction(item) + + verify { bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED } + verify { findInPageLauncher() } + verify { metrics.track(Event.FindInPageOpened) } + } + + @Test + fun handleToolbarReportIssuePress() { + val tabsUseCases: TabsUseCases = mockk(relaxed = true) + val addTabUseCase: TabsUseCases.AddNewTabUseCase = mockk(relaxed = true) + + val item = ToolbarMenu.Item.ReportIssue + + every { currentSession.url } returns "https://mozilla.org" + every { context.components.useCases.tabsUseCases } returns tabsUseCases + every { tabsUseCases.addTab } returns addTabUseCase + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.REPORT_SITE_ISSUE)) } + verify { + // Hardcoded URL because this function modifies the URL with an apply + addTabUseCase.invoke(String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, "https://mozilla.org")) + } + } + + @Test + fun handleToolbarHelpPress() { + val tabsUseCases: TabsUseCases = mockk(relaxed = true) + val addTabUseCase: TabsUseCases.AddNewTabUseCase = mockk(relaxed = true) + + val item = ToolbarMenu.Item.Help + + every { context.components.useCases.tabsUseCases } returns tabsUseCases + every { tabsUseCases.addTab } returns addTabUseCase + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.HELP)) } + verify { + addTabUseCase.invoke(getSupportUrl()) + } + } + + @Test + fun handleToolbarNewTabPress() { + val browsingModeManager: BrowsingModeManager = mockk(relaxed = true) + val item = ToolbarMenu.Item.NewTab + + every { context.browsingModeManager } returns browsingModeManager + every { browsingModeManager.mode } returns BrowsingModeManager.Mode.Private + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.NEW_TAB)) } + verify { + val directions = BrowserFragmentDirections + .actionBrowserFragmentToSearchFragment(null) + navController.nav(R.id.browserFragment, directions) + } + verify { browsingModeManager.mode = BrowsingModeManager.Mode.Normal } + } + + @Test + fun handleToolbarSaveToCollectionPress() { + val item = ToolbarMenu.Item.SaveToCollection + val cachedTabCollections: List = mockk(relaxed = true) + every { context.components.useCases.sessionUseCases } returns sessionUseCases + every { context.components.core.tabCollectionStorage.cachedTabCollections } returns cachedTabCollections + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION)) } + verify { metrics.track(Event.CollectionSaveButtonPressed(DefaultBrowserToolbarController.TELEMETRY_BROWSER_IDENTIFIER)) } + verify { viewModel.tabs = listOf(currentSessionAsTab) } + verify { viewModel.selectedTabs = mutableSetOf(currentSessionAsTab) } + verify { viewModel.tabCollections = cachedTabCollections.reversed() } + verify { viewModel.saveCollectionStep = SaveCollectionStep.SelectCollection } + verify { viewModel.snackbarAnchorView = nestedScrollQuickActionView } + verify { viewModel.previousFragmentId = R.id.browserFragment } + verify { + val directions = BrowserFragmentDirections + .actionBrowserFragmentToSearchFragment(null) + navController.nav(R.id.browserFragment, directions) + } + } + + @Test + fun handleToolbarOpenInFenixPress() { + val sessionManager: SessionManager = mockk(relaxed = true) + val item = ToolbarMenu.Item.OpenInFenix + + every { context.components.core.sessionManager } returns sessionManager + every { currentSession.customTabConfig } returns mockk() + every { context.startActivity(any()) } just Runs + + controller.handleToolbarItemInteraction(item) + + verify { engineView.release() } + verify { currentSession.customTabConfig = null } + verify { sessionManager.select(currentSession) } + verify { context.startActivity(openInFenixIntent) } + verify { context.finish() } + } +}