From 1adf4672489067c60ee5e369c169cecd33006ff8 Mon Sep 17 00:00:00 2001 From: Elise Richards Date: Thu, 24 Sep 2020 15:46:42 -0500 Subject: [PATCH] For #14280, #14743: Remove old search fragment (#15169) * Remove search fragment * Use new folder to search dialog * Rebase and lint * Update tests with search dialog nav directions * Rename interactor to match naming convention. Remove old controller and point everything to the dialog controller. --- .../org/mozilla/fenix/BrowserDirection.kt | 1 - .../java/org/mozilla/fenix/FeatureFlags.kt | 5 - .../java/org/mozilla/fenix/HomeActivity.kt | 5 +- .../fenix/browser/BaseBrowserFragment.kt | 2 +- .../toolbar/BrowserToolbarController.kt | 58 +-- .../org/mozilla/fenix/home/HomeFragment.kt | 13 +- .../home/intent/StartSearchIntentProcessor.kt | 2 +- .../SessionControlController.kt | 2 +- .../mozilla/fenix/search/SearchController.kt | 252 ---------- .../SearchDialogController.kt | 23 +- .../SearchDialogFragment.kt | 8 +- ...nteractor.kt => SearchDialogInteractor.kt} | 4 +- .../mozilla/fenix/search/SearchFragment.kt | 462 ------------------ app/src/main/res/navigation/nav_graph.xml | 30 +- .../DefaultBrowserToolbarControllerTest.kt | 16 +- .../DefaultSessionControlControllerTest.kt | 2 +- .../intent/StartSearchIntentProcessorTest.kt | 2 +- .../search/DefaultSearchControllerTest.kt | 346 ------------- .../SearchDialogControllerTest.kt | 14 +- ...rTest.kt => SearchDialogInteractorTest.kt} | 10 +- .../account/DefaultSyncControllerTest.kt | 4 +- 21 files changed, 68 insertions(+), 1193 deletions(-) delete mode 100644 app/src/main/java/org/mozilla/fenix/search/SearchController.kt rename app/src/main/java/org/mozilla/fenix/{searchdialog => search}/SearchDialogController.kt (92%) rename app/src/main/java/org/mozilla/fenix/{searchdialog => search}/SearchDialogFragment.kt (98%) rename app/src/main/java/org/mozilla/fenix/search/{SearchInteractor.kt => SearchDialogInteractor.kt} (95%) delete mode 100644 app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt delete mode 100644 app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt rename app/src/test/java/org/mozilla/fenix/{searchdialog => search}/SearchDialogControllerTest.kt (95%) rename app/src/test/java/org/mozilla/fenix/search/{SearchInteractorTest.kt => SearchDialogInteractorTest.kt} (91%) diff --git a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt index 84da2f843..e14c6c048 100644 --- a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt +++ b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt @@ -16,7 +16,6 @@ import androidx.annotation.IdRes enum class BrowserDirection(@IdRes val fragmentId: Int) { FromGlobal(0), FromHome(R.id.homeFragment), - FromSearch(R.id.searchFragment), FromSearchDialog(R.id.searchDialogFragment), FromSettings(R.id.settingsFragment), FromSyncedTabs(R.id.syncedTabsFragment), diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index 01fd6d64c..b68d5f35f 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -21,11 +21,6 @@ object FeatureFlags { */ val syncedTabsInTabsTray = Config.channel.isNightlyOrDebug - /** - * Enables the new search experience - */ - const val newSearchExperience = true - /** * Enables showing the top frequently visited sites */ diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 7402463dd..7523c5ab4 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -86,8 +86,7 @@ import org.mozilla.fenix.library.history.HistoryFragmentDirections import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections import org.mozilla.fenix.perf.Performance import org.mozilla.fenix.perf.StartupTimeline -import org.mozilla.fenix.search.SearchFragmentDirections -import org.mozilla.fenix.searchdialog.SearchDialogFragmentDirections +import org.mozilla.fenix.search.SearchDialogFragmentDirections import org.mozilla.fenix.session.PrivateNotificationService import org.mozilla.fenix.settings.SettingsFragmentDirections import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections @@ -670,8 +669,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { NavGraphDirections.actionGlobalBrowser(customTabSessionId) BrowserDirection.FromHome -> HomeFragmentDirections.actionGlobalBrowser(customTabSessionId) - BrowserDirection.FromSearch -> - SearchFragmentDirections.actionGlobalBrowser(customTabSessionId) BrowserDirection.FromSearchDialog -> SearchDialogFragmentDirections.actionGlobalBrowser(customTabSessionId) BrowserDirection.FromSettings -> 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 b8921157d..7b224d6c7 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -848,7 +848,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session @CallSuper override fun onPause() { super.onPause() - if (findNavController().currentDestination?.id != R.id.searchFragment) { + if (findNavController().currentDestination?.id != R.id.searchDialogFragment) { view?.hideKeyboard() } } 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 56d2c3711..fbc5eddb1 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 @@ -9,7 +9,6 @@ import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.concept.engine.EngineView import mozilla.components.support.ktx.kotlin.isUrl -import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserAnimator @@ -46,7 +45,6 @@ class DefaultBrowserToolbarController( private val engineView: EngineView, private val browserAnimator: BrowserAnimator, private val customTabSession: Session?, - private val useNewSearchExperience: Boolean = FeatureFlags.newSearchExperience, private val onTabCounterClicked: () -> Unit, private val onCloseTab: (Session) -> Unit ) : BrowserToolbarController { @@ -55,27 +53,14 @@ class DefaultBrowserToolbarController( get() = customTabSession ?: sessionManager.selectedSession override fun handleToolbarPaste(text: String) { - if (useNewSearchExperience) { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionGlobalSearchDialog( - sessionId = currentSession?.id, - pastedText = text - ), - getToolbarNavOptions(activity) - ) - } else { - browserAnimator.captureEngineViewAndDrawStatically { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( - sessionId = currentSession?.id, - pastedText = text - ), - getToolbarNavOptions(activity) - ) - } - } + navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalSearchDialog( + sessionId = currentSession?.id, + pastedText = text + ), + getToolbarNavOptions(activity) + ) } override fun handleToolbarPasteAndGo(text: String) { @@ -94,26 +79,13 @@ class DefaultBrowserToolbarController( override fun handleToolbarClick() { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) - - if (useNewSearchExperience) { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionGlobalSearchDialog( - currentSession?.id - ), - getToolbarNavOptions(activity) - ) - } else { - browserAnimator.captureEngineViewAndDrawStatically { - navController.nav( - R.id.browserFragment, - BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( - currentSession?.id - ), - getToolbarNavOptions(activity) - ) - } - } + navController.nav( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalSearchDialog( + currentSession?.id + ), + getToolbarNavOptions(activity) + ) } override fun handleTabCounterClick() { 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 12a926898..708e5e19b 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -72,7 +72,6 @@ import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.content.res.resolveAttribute import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions @@ -431,8 +430,7 @@ class HomeFragment : Fragment() { // We call this onLayout so that the bottom bar width is correctly set for us to center // the CFR in. view.toolbar_wrapper.doOnLayout { - val willNavigateToSearch = - !bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR) && FeatureFlags.newSearchExperience + val willNavigateToSearch = !bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR) if (!browsingModeManager.mode.isPrivate && !willNavigateToSearch) { SearchWidgetCFR( context = view.context, @@ -464,7 +462,7 @@ class HomeFragment : Fragment() { updateTabCounter(requireComponents.core.store.state) - if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR) && FeatureFlags.newSearchExperience) { + if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR)) { navigateToSearch() } } @@ -739,15 +737,10 @@ class HomeFragment : Fragment() { } private fun navigateToSearch() { - val directions = if (FeatureFlags.newSearchExperience) { + val directions = HomeFragmentDirections.actionGlobalSearchDialog( sessionId = null ) - } else { - HomeFragmentDirections.actionGlobalSearch( - sessionId = null - ) - } nav(R.id.homeFragment, directions, getToolbarNavOptions(requireContext())) } diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt index 6b5be6a51..e6f079c8b 100644 --- a/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt @@ -46,7 +46,7 @@ class StartSearchIntentProcessor( out.removeExtra(HomeActivity.OPEN_TO_SEARCH) val directions = source?.let { - NavGraphDirections.actionGlobalSearch( + NavGraphDirections.actionGlobalSearchDialog( sessionId = null, searchAccessPoint = it ) 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 3ad615f26..c2a35df94 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 @@ -428,7 +428,7 @@ class DefaultSessionControlController( } override fun handlePaste(clipboardText: String) { - val directions = HomeFragmentDirections.actionGlobalSearch( + val directions = HomeFragmentDirections.actionGlobalSearchDialog( sessionId = null, pastedText = clipboardText ) diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchController.kt b/app/src/main/java/org/mozilla/fenix/search/SearchController.kt deleted file mode 100644 index 058bf715b..000000000 --- a/app/src/main/java/org/mozilla/fenix/search/SearchController.kt +++ /dev/null @@ -1,252 +0,0 @@ -/* 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.search - -import android.content.DialogInterface -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.text.SpannableString -import androidx.annotation.VisibleForTesting -import androidx.appcompat.app.AlertDialog -import androidx.navigation.NavController -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.session.Session -import mozilla.components.browser.session.SessionManager -import mozilla.components.support.ktx.kotlin.isUrl -import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.R -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.ACTION -import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.NONE -import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint.SUGGESTION -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.components.metrics.MetricsUtils -import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore -import org.mozilla.fenix.crashes.CrashListActivity -import org.mozilla.fenix.ext.navigateSafe -import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.settings.SupportUtils.MozillaPage.MANIFESTO -import org.mozilla.fenix.utils.Settings - -/** - * An interface that handles the view manipulation of the Search, triggered by the Interactor - */ -@Suppress("TooManyFunctions") -interface SearchController { - fun handleUrlCommitted(url: String) - fun handleEditingCancelled() - fun handleTextChanged(text: String) - fun handleUrlTapped(url: String) - fun handleSearchTermsTapped(searchTerms: String) - fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) - fun handleClickSearchEngineSettings() - fun handleExistingSessionSelected(session: Session) - fun handleExistingSessionSelected(tabId: String) - fun handleSearchShortcutsButtonClicked() - fun handleCameraPermissionsNeeded() -} - -@Suppress("TooManyFunctions", "LongParameterList") -class DefaultSearchController( - private val activity: HomeActivity, - private val sessionManager: SessionManager, - private val store: SearchFragmentStore, - private val navController: NavController, - private val settings: Settings, - private val metrics: MetricController, - private val clearToolbarFocus: () -> Unit -) : SearchController { - - override fun handleUrlCommitted(url: String) { - when (url) { - "about:crashes" -> { - // The list of past crashes can be accessed via "settings > about", but desktop and - // fennec users may be used to navigating to "about:crashes". So we intercept this here - // and open the crash list activity instead. - activity.startActivity(Intent(activity, CrashListActivity::class.java)) - } - "about:addons" -> { - val directions = SearchFragmentDirections.actionGlobalAddonsManagementFragment() - navController.navigateSafe(R.id.searchFragment, directions) - } - "moz://a" -> openSearchOrUrl(SupportUtils.getMozillaPageUrl(MANIFESTO)) - else -> if (url.isNotBlank()) { - openSearchOrUrl(url) - } - } - } - - private fun openSearchOrUrl(url: String) { - activity.openToBrowserAndLoad( - searchTermOrURL = url, - newTab = store.state.tabId == null, - from = BrowserDirection.FromSearch, - engine = store.state.searchEngineSource.searchEngine - ) - - val event = if (url.isUrl()) { - Event.EnteredUrl(false) - } else { - settings.incrementActiveSearchCount() - - val searchAccessPoint = when (store.state.searchAccessPoint) { - NONE -> ACTION - else -> store.state.searchAccessPoint - } - - searchAccessPoint?.let { sap -> - MetricsUtils.createSearchEvent( - store.state.searchEngineSource.searchEngine, - activity, - sap - ) - } - } - - event?.let { metrics.track(it) } - } - - override fun handleEditingCancelled() { - clearToolbarFocus() - } - - override fun handleTextChanged(text: String) { - // Display the search shortcuts on each entry of the search fragment (see #5308) - val textMatchesCurrentUrl = store.state.url == text - val textMatchesCurrentSearch = store.state.searchTerms == text - - store.dispatch(SearchFragmentAction.UpdateQuery(text)) - store.dispatch( - SearchFragmentAction.ShowSearchShortcutEnginePicker( - (textMatchesCurrentUrl || textMatchesCurrentSearch || text.isEmpty()) && - settings.shouldShowSearchShortcuts - ) - ) - store.dispatch( - SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt( - text.isNotEmpty() && - activity.browsingModeManager.mode.isPrivate && - !settings.shouldShowSearchSuggestionsInPrivate && - !settings.showSearchSuggestionsInPrivateOnboardingFinished - ) - ) - } - - override fun handleUrlTapped(url: String) { - activity.openToBrowserAndLoad( - searchTermOrURL = url, - newTab = store.state.tabId == null, - from = BrowserDirection.FromSearch - ) - - metrics.track(Event.EnteredUrl(false)) - } - - override fun handleSearchTermsTapped(searchTerms: String) { - settings.incrementActiveSearchCount() - - activity.openToBrowserAndLoad( - searchTermOrURL = searchTerms, - newTab = store.state.tabId == null, - from = BrowserDirection.FromSearch, - engine = store.state.searchEngineSource.searchEngine, - forceSearch = true - ) - - val searchAccessPoint = when (store.state.searchAccessPoint) { - NONE -> SUGGESTION - else -> store.state.searchAccessPoint - } - - val event = searchAccessPoint?.let { sap -> - MetricsUtils.createSearchEvent( - store.state.searchEngineSource.searchEngine, - activity, - sap - ) - } - event?.let { metrics.track(it) } - } - - override fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) { - store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine)) - val isCustom = - CustomSearchEngineStore.isCustomSearchEngine(activity, searchEngine.identifier) - metrics.track(Event.SearchShortcutSelected(searchEngine, isCustom)) - } - - override fun handleSearchShortcutsButtonClicked() { - val isOpen = store.state.showSearchShortcuts - store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(!isOpen)) - } - - override fun handleClickSearchEngineSettings() { - val directions = SearchFragmentDirections.actionGlobalSearchEngineFragment() - navController.navigateSafe(R.id.searchFragment, directions) - } - - override fun handleExistingSessionSelected(session: Session) { - sessionManager.select(session) - activity.openToBrowser( - from = BrowserDirection.FromSearch - ) - } - - override fun handleExistingSessionSelected(tabId: String) { - val session = sessionManager.findSessionById(tabId) - if (session != null) { - handleExistingSessionSelected(session) - } - } - - /** - * Creates and shows an [AlertDialog] when camera permissions are needed. - * - * In versions above M, [AlertDialog.BUTTON_POSITIVE] takes the user to the app settings. This - * intent only exists in M and above. Below M, [AlertDialog.BUTTON_POSITIVE] routes to a SUMO - * help page to find the app settings. - * - * [AlertDialog.BUTTON_NEGATIVE] dismisses the dialog. - */ - override fun handleCameraPermissionsNeeded() { - val dialog = buildDialog() - dialog.show() - } - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun buildDialog(): AlertDialog.Builder { - return AlertDialog.Builder(activity).apply { - val spannableText = SpannableString( - activity.resources.getString(R.string.camera_permissions_needed_message) - ) - setMessage(spannableText) - setNegativeButton(R.string.camera_permissions_needed_negative_button_text) { - dialog: DialogInterface, _ -> - dialog.cancel() - } - setPositiveButton(R.string.camera_permissions_needed_positive_button_text) { - dialog: DialogInterface, _ -> - val intent: Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - } else { - SupportUtils.createCustomTabIntent( - activity, - SupportUtils.getSumoURLForTopic( - activity, - SupportUtils.SumoTopic.QR_CAMERA_ACCESS - ) - ) - } - val uri = Uri.fromParts("package", activity.packageName, null) - intent.data = uri - dialog.cancel() - activity.startActivity(intent) - } - create() - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt similarity index 92% rename from app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt rename to app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt index 476a7d615..742f7fab1 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogController.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt @@ -2,7 +2,7 @@ * 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.searchdialog +package org.mozilla.fenix.search import android.content.DialogInterface import android.content.Intent @@ -25,12 +25,27 @@ import org.mozilla.fenix.components.metrics.MetricsUtils import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore import org.mozilla.fenix.crashes.CrashListActivity import org.mozilla.fenix.ext.navigateSafe -import org.mozilla.fenix.search.SearchController -import org.mozilla.fenix.search.SearchFragmentAction -import org.mozilla.fenix.search.SearchFragmentStore import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.utils.Settings +/** + * An interface that handles the view manipulation of the Search, triggered by the Interactor + */ +@Suppress("TooManyFunctions") +interface SearchController { + fun handleUrlCommitted(url: String) + fun handleEditingCancelled() + fun handleTextChanged(text: String) + fun handleUrlTapped(url: String) + fun handleSearchTermsTapped(searchTerms: String) + fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) + fun handleClickSearchEngineSettings() + fun handleExistingSessionSelected(session: Session) + fun handleExistingSessionSelected(tabId: String) + fun handleSearchShortcutsButtonClicked() + fun handleCameraPermissionsNeeded() +} + @Suppress("TooManyFunctions", "LongParameterList") class SearchDialogController( private val activity: HomeActivity, diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt similarity index 98% rename from app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt rename to app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt index ef15773d5..f290638ba 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt @@ -2,7 +2,7 @@ * 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.searchdialog +package org.mozilla.fenix.search import android.Manifest import android.app.Activity @@ -56,19 +56,13 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.isKeyboardVisible import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.search.SearchFragmentAction -import org.mozilla.fenix.search.SearchFragmentState -import org.mozilla.fenix.search.SearchFragmentStore -import org.mozilla.fenix.search.SearchInteractor import org.mozilla.fenix.search.awesomebar.AwesomeBarView -import org.mozilla.fenix.search.createInitialSearchFragmentState import org.mozilla.fenix.search.toolbar.ToolbarView import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener import org.mozilla.fenix.widget.VoiceSearchActivity typealias SearchDialogFragmentStore = SearchFragmentStore -typealias SearchDialogInteractor = SearchInteractor @SuppressWarnings("LargeClass", "TooManyFunctions") class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt similarity index 95% rename from app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt rename to app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt index 823945453..cd35bdbce 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt @@ -14,8 +14,8 @@ import org.mozilla.fenix.search.toolbar.ToolbarInteractor * Provides implementations for the AwesomeBarView and ToolbarView */ @Suppress("TooManyFunctions") -class SearchInteractor( - private val searchController: SearchController +class SearchDialogInteractor( + private val searchController: SearchDialogController ) : AwesomeBarInteractor, ToolbarInteractor { override fun onUrlCommitted(url: String) { diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt deleted file mode 100644 index 7076dbf70..000000000 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt +++ /dev/null @@ -1,462 +0,0 @@ -/* 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.search - -import android.Manifest -import android.app.Activity.RESULT_OK -import android.content.Context -import android.content.DialogInterface -import android.content.Intent -import android.graphics.Typeface.BOLD -import android.graphics.Typeface.ITALIC -import android.os.Bundle -import android.speech.RecognizerIntent -import android.speech.RecognizerIntent.EXTRA_RESULTS -import android.text.style.StyleSpan -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.ViewStub -import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat -import androidx.core.view.isVisible -import androidx.core.widget.NestedScrollView -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import kotlinx.android.synthetic.main.fragment_search.* -import kotlinx.android.synthetic.main.fragment_search.view.* -import kotlinx.android.synthetic.main.search_suggestions_hint.view.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import mozilla.components.browser.toolbar.BrowserToolbar -import mozilla.components.concept.storage.HistoryStorage -import mozilla.components.feature.qr.QrFeature -import mozilla.components.lib.state.ext.consumeFrom -import mozilla.components.support.base.feature.UserInteractionHandler -import mozilla.components.support.base.feature.ViewBoundFeatureWrapper -import mozilla.components.support.ktx.android.content.getColorFromAttr -import mozilla.components.support.ktx.android.content.hasCamera -import mozilla.components.support.ktx.android.content.isPermissionGranted -import mozilla.components.support.ktx.android.content.res.getSpanned -import mozilla.components.support.ktx.android.view.hideKeyboard -import mozilla.components.ui.autocomplete.InlineAutocompleteEditText -import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.R -import org.mozilla.fenix.components.StoreProvider -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore -import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.hideToolbar -import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.search.awesomebar.AwesomeBarView -import org.mozilla.fenix.search.ext.areShortcutsAvailable -import org.mozilla.fenix.search.toolbar.ToolbarView -import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener -import org.mozilla.fenix.widget.VoiceSearchActivity.Companion.SPEECH_REQUEST_CODE - -@Suppress("TooManyFunctions", "LargeClass") -class SearchFragment : Fragment(), UserInteractionHandler { - private lateinit var toolbarView: ToolbarView - private lateinit var awesomeBarView: AwesomeBarView - private val qrFeature = ViewBoundFeatureWrapper() - private lateinit var searchStore: SearchFragmentStore - private lateinit var searchInteractor: SearchInteractor - - private val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) - - @Suppress("LongMethod") - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val activity = activity as HomeActivity - val settings = activity.settings() - val args by navArgs() - - val view = inflater.inflate(R.layout.fragment_search, container, false) - - val isPrivate = activity.browsingModeManager.mode.isPrivate - - requireComponents.analytics.metrics.track(Event.InteractWithSearchURLArea) - - searchStore = StoreProvider.get(this) { - SearchFragmentStore( - createInitialSearchFragmentState( - activity, - requireComponents, - tabId = args.sessionId, - pastedText = args.pastedText, - searchAccessPoint = args.searchAccessPoint - ) - ) - } - - val searchController = DefaultSearchController( - activity = activity, - sessionManager = requireComponents.core.sessionManager, - store = searchStore, - navController = findNavController(), - settings = settings, - metrics = requireComponents.analytics.metrics, - clearToolbarFocus = ::clearToolbarFocus - ) - - searchInteractor = SearchInteractor( - searchController - ) - - awesomeBarView = AwesomeBarView( - activity, - searchInteractor, - view.findViewById(R.id.awesomeBar) - ) - setShortcutsChangedListener(CustomSearchEngineStore.PREF_FILE_SEARCH_ENGINES) - setShortcutsChangedListener(FenixSearchEngineProvider.PREF_FILE_SEARCH_ENGINES) - - view.scrollView.setOnScrollChangeListener { - _: NestedScrollView, _: Int, _: Int, _: Int, _: Int -> - view.hideKeyboard() - } - - toolbarView = ToolbarView( - requireContext(), - searchInteractor, - historyStorageProvider(), - isPrivate, - view.toolbar, - requireComponents.core.engine - ) - - toolbarView.view.addEditAction( - BrowserToolbar.Button( - ContextCompat.getDrawable(requireContext(), R.drawable.ic_microphone)!!, - requireContext().getString(R.string.voice_search_content_description), - visible = { - searchStore.state.searchEngineSource.searchEngine.identifier.contains("google") && - speechIsAvailable() && - settings.shouldShowVoiceSearch - }, - listener = ::launchVoiceSearch - ) - ) - - awesomeBarView.view.setOnEditSuggestionListener(toolbarView.view::setSearchTerms) - - val urlView = toolbarView.view - .findViewById(R.id.mozac_browser_toolbar_edit_url_view) - urlView?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - - requireComponents.core.engine.speculativeCreateSession(isPrivate) - startPostponedEnterTransition() - return view - } - - private fun speechIsAvailable(): Boolean { - return (speechIntent.resolveActivity(requireContext().packageManager) != null) - } - - private fun setShortcutsChangedListener(preferenceFileName: String) { - requireContext().getSharedPreferences( - preferenceFileName, - Context.MODE_PRIVATE - ).registerOnSharedPreferenceChangeListener(viewLifecycleOwner) { _, _ -> - awesomeBarView.update(searchStore.state) - } - } - - private fun launchVoiceSearch() { - // Note if a user disables speech while the app is on the search fragment - // the voice button will still be available and *will* cause a crash if tapped, - // since the `visible` call is only checked on create. In order to avoid extra complexity - // around such a small edge case, we make the button have no functionality in this case. - if (!speechIsAvailable()) { return } - - requireComponents.analytics.metrics.track(Event.VoiceSearchTapped) - speechIntent.apply { - putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) - putExtra(RecognizerIntent.EXTRA_PROMPT, requireContext().getString(R.string.voice_search_explainer)) - } - startActivityForResult(speechIntent, SPEECH_REQUEST_CODE) - } - - private fun clearToolbarFocus() { - toolbarView.view.hideKeyboard() - toolbarView.view.clearFocus() - } - - @ExperimentalCoroutinesApi - @SuppressWarnings("LongMethod") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - search_scan_button.visibility = if (context?.hasCamera() == true) View.VISIBLE else View.GONE - - qrFeature.set( - createQrFeature(), - owner = this, - view = view - ) - - view.search_scan_button.setOnClickListener { - if (requireContext().settings().shouldShowCameraPermissionPrompt) { - requireComponents.analytics.metrics.track(Event.QRScannerOpened) - qrFeature.get()?.scan(R.id.container) - } else { - if (requireContext().isPermissionGranted(Manifest.permission.CAMERA)) { - requireComponents.analytics.metrics.track(Event.QRScannerOpened) - qrFeature.get()?.scan(R.id.container) - } else { - searchInteractor.onCameraPermissionsNeeded() - } - } - view.hideKeyboard() - search_scan_button.isChecked = false - requireContext().settings().setCameraPermissionNeededState = false - } - - view.search_engines_shortcut_button.setOnClickListener { - searchInteractor.onSearchShortcutsButtonClicked() - } - - val stubListener = ViewStub.OnInflateListener { _, inflated -> - inflated.learn_more.setOnClickListener { - (activity as HomeActivity) - .openToBrowserAndLoad( - searchTermOrURL = SupportUtils.getGenericSumoURLForTopic( - SupportUtils.SumoTopic.SEARCH_SUGGESTION - ), - newTab = searchStore.state.tabId == null, - from = BrowserDirection.FromSearch - ) - } - - inflated.allow.setOnClickListener { - inflated.visibility = View.GONE - context?.settings()?.shouldShowSearchSuggestionsInPrivate = true - context?.settings()?.showSearchSuggestionsInPrivateOnboardingFinished = true - searchStore.dispatch(SearchFragmentAction.SetShowSearchSuggestions(true)) - searchStore.dispatch(SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt(false)) - requireComponents.analytics.metrics.track(Event.PrivateBrowsingShowSearchSuggestions) - } - - inflated.dismiss.setOnClickListener { - inflated.visibility = View.GONE - context?.settings()?.shouldShowSearchSuggestionsInPrivate = false - context?.settings()?.showSearchSuggestionsInPrivateOnboardingFinished = true - } - - inflated.text.text = - getString(R.string.search_suggestions_onboarding_text, getString(R.string.app_name)) - - inflated.title.text = - getString(R.string.search_suggestions_onboarding_title) - } - - view.search_suggestions_onboarding.setOnInflateListener((stubListener)) - - fill_link_from_clipboard.setOnClickListener { - (activity as HomeActivity) - .openToBrowserAndLoad( - searchTermOrURL = requireContext().components.clipboardHandler.url ?: "", - newTab = searchStore.state.tabId == null, - from = BrowserDirection.FromSearch - ) - } - - consumeFrom(searchStore) { - awesomeBarView.update(it) - updateSearchShortcutsIcon(it) - toolbarView.update(it) - updateSearchWithLabel(it) - updateClipboardSuggestion(it, requireContext().components.clipboardHandler.url) - updateSearchSuggestionsHintVisibility(it) - updateToolbarContentDescription(it) - } - - startPostponedEnterTransition() - } - - private fun createQrFeature(): QrFeature { - return QrFeature( - requireContext(), - fragmentManager = parentFragmentManager, - onNeedToRequestPermissions = { permissions -> - requestPermissions(permissions, REQUEST_CODE_CAMERA_PERMISSIONS) - }, - onScanResult = { result -> - search_scan_button.isChecked = false - activity?.let { - AlertDialog.Builder(it).apply { - val spannable = resources.getSpanned( - R.string.qr_scanner_confirmation_dialog_message, - getString(R.string.app_name) to StyleSpan(BOLD), - result to StyleSpan(ITALIC) - ) - setMessage(spannable) - setNegativeButton(R.string.qr_scanner_dialog_negative) { dialog: DialogInterface, _ -> - requireComponents.analytics.metrics.track(Event.QRScannerNavigationDenied) - dialog.cancel() - resetFocus() - } - setPositiveButton(R.string.qr_scanner_dialog_positive) { dialog: DialogInterface, _ -> - requireComponents.analytics.metrics.track(Event.QRScannerNavigationAllowed) - (activity as HomeActivity) - .openToBrowserAndLoad( - searchTermOrURL = result, - newTab = searchStore.state.tabId == null, - from = BrowserDirection.FromSearch - ) - dialog.dismiss() - resetFocus() - } - create() - }.show() - requireComponents.analytics.metrics.track(Event.QRScannerPromptDisplayed) - } - } - ) - } - - private fun updateToolbarContentDescription(searchState: SearchFragmentState) { - val urlView = toolbarView.view - .findViewById(R.id.mozac_browser_toolbar_edit_url_view) - toolbarView.view.contentDescription = - searchState.searchEngineSource.searchEngine.name + ", " + urlView.hint - urlView?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - } - - override fun onResume() { - super.onResume() - - val provider = requireComponents.search.provider - - // The user has the option to go to 'Shortcuts' -> 'Search engine settings' to modify the default search engine. - // When returning from that settings screen we need to update it to account for any changes. - val currentDefaultEngine = provider.getDefaultEngine(requireContext()) - - if (searchStore.state.defaultEngineSource.searchEngine != currentDefaultEngine) { - searchStore.dispatch( - SearchFragmentAction.SelectNewDefaultSearchEngine - (currentDefaultEngine) - ) - } - - // Users can from this fragment go to install/uninstall search engines and then return. - val areShortcutsAvailable = provider.areShortcutsAvailable(requireContext()) - if (searchStore.state.areShortcutsAvailable != areShortcutsAvailable) { - searchStore.dispatch(SearchFragmentAction.UpdateShortcutsAvailability(areShortcutsAvailable)) - } - - updateClipboardSuggestion( - searchStore.state, - requireComponents.clipboardHandler.url - ) - - hideToolbar() - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { - if (requestCode == SPEECH_REQUEST_CODE && resultCode == RESULT_OK) { - intent?.getStringArrayListExtra(EXTRA_RESULTS)?.first()?.also { - toolbarView.view.edit.updateUrl(url = it, shouldHighlight = true) - searchInteractor.onTextChanged(it) - toolbarView.view.edit.focus() - } - } - } - - override fun onPause() { - super.onPause() - toolbarView.view.clearFocus() - } - - override fun onBackPressed(): Boolean { - return when { - qrFeature.onBackPressed() -> { - resetFocus() - true - } - else -> false - } - } - - private fun resetFocus() { - search_scan_button.isChecked = false - toolbarView.view.edit.focus() - toolbarView.view.requestFocus() - } - - private fun updateSearchWithLabel(searchState: SearchFragmentState) { - search_engine_shortcut.visibility = - if (searchState.showSearchShortcuts) View.VISIBLE else View.GONE - } - - private fun updateClipboardSuggestion(searchState: SearchFragmentState, clipboardUrl: String?) { - val visibility = - if (searchState.showClipboardSuggestions && searchState.query.isEmpty() && !clipboardUrl.isNullOrEmpty()) - View.VISIBLE else View.GONE - - fill_link_from_clipboard.visibility = visibility - divider_line.visibility = visibility - clipboard_url.text = clipboardUrl - - if (clipboardUrl != null && !((activity as HomeActivity).browsingModeManager.mode.isPrivate)) { - requireComponents.core.engine.speculativeConnect(clipboardUrl) - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - when (requestCode) { - REQUEST_CODE_CAMERA_PERMISSIONS -> qrFeature.withFeature { - it.onPermissionsResult(permissions, grantResults) - resetFocus() - requireContext().settings().setCameraPermissionNeededState = false - } - else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - } - - private fun historyStorageProvider(): HistoryStorage? { - return if (requireContext().settings().shouldShowHistorySuggestions) { - requireComponents.core.historyStorage - } else null - } - - private fun updateSearchSuggestionsHintVisibility(state: SearchFragmentState) { - view?.apply { - findViewById(R.id.search_suggestions_onboarding)?.isVisible = state.showSearchSuggestionsHint - - search_suggestions_onboarding_divider?.isVisible = - search_engine_shortcut.isVisible && state.showSearchSuggestionsHint - } - } - - private fun updateSearchShortcutsIcon(searchState: SearchFragmentState) { - view?.apply { - search_engines_shortcut_button.isVisible = searchState.areShortcutsAvailable - - val showShortcuts = searchState.showSearchShortcuts - search_engines_shortcut_button.isChecked = showShortcuts - - val color = if (showShortcuts) R.attr.contrastText else R.attr.primaryText - search_engines_shortcut_button.compoundDrawables[0]?.setTint( - requireContext().getColorFromAttr(color) - ) - } - } - - companion object { - private const val REQUEST_CODE_CAMERA_PERMISSIONS = 1 - } -} diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 1d3033460..5bf3c9190 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -23,10 +23,6 @@ app:popUpTo="@id/homeFragment" app:popUpToInclusive="false" /> - - @@ -145,7 +141,7 @@ - - - - - - - { it.actionId == R.id.action_global_search }, + match { it.actionId == R.id.action_global_search_dialog }, null ) } diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt index 2f7908383..0bc87d407 100644 --- a/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt @@ -55,7 +55,7 @@ class StartSearchIntentProcessorTest { verify { metrics.track(Event.SearchWidgetNewTabPressed) } verify { navController.navigate( - NavGraphDirections.actionGlobalSearch( + NavGraphDirections.actionGlobalSearchDialog( sessionId = null, searchAccessPoint = Event.PerformedSearch.SearchAccessPoint.WIDGET ), diff --git a/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt b/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt deleted file mode 100644 index 52b8dbbf0..000000000 --- a/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt +++ /dev/null @@ -1,346 +0,0 @@ -/* 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.search - -import androidx.appcompat.app.AlertDialog -import androidx.navigation.NavController -import androidx.navigation.NavDirections -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.just -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.spyk -import io.mockk.unmockkObject -import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.session.Session -import mozilla.components.browser.session.SessionManager -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.R -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.components.metrics.MetricsUtils -import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.utils.Settings - -typealias AlertDialogBuilder = AlertDialog.Builder - -@ExperimentalCoroutinesApi -class DefaultSearchControllerTest { - - @MockK(relaxed = true) private lateinit var activity: HomeActivity - @MockK(relaxed = true) private lateinit var store: SearchFragmentStore - @MockK(relaxed = true) private lateinit var navController: NavController - @MockK private lateinit var searchEngine: SearchEngine - @MockK(relaxed = true) private lateinit var metrics: MetricController - @MockK(relaxed = true) private lateinit var settings: Settings - @MockK private lateinit var sessionManager: SessionManager - @MockK(relaxed = true) private lateinit var clearToolbarFocus: () -> Unit - - private lateinit var controller: DefaultSearchController - - @Before - fun setUp() { - MockKAnnotations.init(this) - mockkObject(MetricsUtils) - - every { store.state.tabId } returns "test-tab-id" - every { store.state.searchEngineSource.searchEngine } returns searchEngine - every { sessionManager.select(any()) } just Runs - every { navController.currentDestination } returns mockk { - every { id } returns R.id.searchFragment - } - every { MetricsUtils.createSearchEvent(searchEngine, activity, any()) } returns null - controller = DefaultSearchController( - activity = activity, - sessionManager = sessionManager, - store = store, - navController = navController, - settings = settings, - metrics = metrics, - clearToolbarFocus = clearToolbarFocus - ) - } - - @After - fun teardown() { - unmockkObject(MetricsUtils) - } - - @Test - fun handleUrlCommitted() { - val url = "https://www.google.com/" - - controller.handleUrlCommitted(url) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = url, - newTab = false, - from = BrowserDirection.FromSearch, - engine = searchEngine - ) - } - verify { metrics.track(Event.EnteredUrl(false)) } - } - - @Test - fun handleSearchCommitted() { - val searchTerm = "Firefox" - - controller.handleUrlCommitted(searchTerm) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = searchTerm, - newTab = false, - from = BrowserDirection.FromSearch, - engine = searchEngine - ) - } - verify { settings.incrementActiveSearchCount() } - } - - @Test - fun handleCrashesUrlCommitted() { - val url = "about:crashes" - every { activity.packageName } returns "org.mozilla.fenix" - - controller.handleUrlCommitted(url) - - verify { - activity.startActivity(any()) - } - } - - @Test - fun handleAddonsUrlCommitted() { - val url = "about:addons" - val directions = SearchFragmentDirections.actionGlobalAddonsManagementFragment() - - controller.handleUrlCommitted(url) - - verify { navController.navigate(directions) } - } - - @Test - fun handleMozillaUrlCommitted() { - val url = "moz://a" - - controller.handleUrlCommitted(url) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.MANIFESTO), - newTab = false, - from = BrowserDirection.FromSearch, - engine = searchEngine - ) - } - verify { metrics.track(Event.EnteredUrl(false)) } - } - - @Test - fun handleEditingCancelled() = runBlockingTest { - controller.handleEditingCancelled() - - verify { - clearToolbarFocus() - } - } - - @Test - fun handleTextChangedNonEmpty() { - val text = "fenix" - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.UpdateQuery(text)) } - } - - @Test - fun handleTextChangedEmpty() { - val text = "" - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.UpdateQuery(text)) } - } - - @Test - fun `show search shortcuts when setting enabled AND query empty`() { - val text = "" - every { settings.shouldShowSearchShortcuts } returns true - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(true)) } - } - - @Test - fun `show search shortcuts when setting enabled AND query equals url`() { - val text = "mozilla.org" - every { store.state.url } returns "mozilla.org" - every { settings.shouldShowSearchShortcuts } returns true - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(true)) } - } - - @Test - fun `do not show search shortcuts when setting enabled AND query non-empty`() { - val text = "mozilla" - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(false)) } - } - - @Test - fun `do not show search shortcuts when setting disabled AND query empty AND url not matching query`() { - every { settings.shouldShowSearchShortcuts } returns false - - val text = "" - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(false)) } - } - - @Test - fun `do not show search shortcuts when setting disabled AND query non-empty`() { - every { settings.shouldShowSearchShortcuts } returns false - - val text = "mozilla" - - controller.handleTextChanged(text) - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(false)) } - } - - @Test - fun handleUrlTapped() { - val url = "https://www.google.com/" - - controller.handleUrlTapped(url) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = url, - newTab = false, - from = BrowserDirection.FromSearch - ) - } - verify { metrics.track(Event.EnteredUrl(false)) } - } - - @Test - fun handleSearchTermsTapped() { - val searchTerms = "fenix" - - controller.handleSearchTermsTapped(searchTerms) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = searchTerms, - newTab = false, - from = BrowserDirection.FromSearch, - engine = searchEngine, - forceSearch = true - ) - } - } - - @Test - fun handleSearchShortcutEngineSelected() { - val searchEngine: SearchEngine = mockk(relaxed = true) - - controller.handleSearchShortcutEngineSelected(searchEngine) - - verify { store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine)) } - verify { metrics.track(Event.SearchShortcutSelected(searchEngine, false)) } - } - - @Test - fun handleClickSearchEngineSettings() { - val directions: NavDirections = - SearchFragmentDirections.actionGlobalSearchEngineFragment() - - controller.handleClickSearchEngineSettings() - - verify { navController.navigate(directions) } - } - - @Test - fun handleSearchShortcutsButtonClicked_alreadyOpen() { - every { store.state.showSearchShortcuts } returns true - - controller.handleSearchShortcutsButtonClicked() - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(false)) } - } - - @Test - fun handleSearchShortcutsButtonClicked_notYetOpen() { - every { store.state.showSearchShortcuts } returns false - - controller.handleSearchShortcutsButtonClicked() - - verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(true)) } - } - - @Test - fun handleExistingSessionSelected() { - val session = mockk() - - controller.handleExistingSessionSelected(session) - - verify { sessionManager.select(session) } - verify { activity.openToBrowser(from = BrowserDirection.FromSearch) } - } - - @Test - fun handleExistingSessionSelected_tabId_nullSession() { - every { sessionManager.findSessionById("tab-id") } returns null - - controller.handleExistingSessionSelected("tab-id") - - verify(inverse = true) { sessionManager.select(any()) } - verify(inverse = true) { activity.openToBrowser(from = BrowserDirection.FromSearch) } - } - - @Test - fun handleExistingSessionSelected_tabId() { - val session = mockk() - every { sessionManager.findSessionById("tab-id") } returns session - - controller.handleExistingSessionSelected("tab-id") - - verify { sessionManager.select(any()) } - verify { activity.openToBrowser(from = BrowserDirection.FromSearch) } - } - - @Test - fun `show camera permissions needed dialog`() { - val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true) - - val spyController = spyk(controller) - every { spyController.buildDialog() } returns dialogBuilder - - spyController.handleCameraPermissionsNeeded() - - verify { dialogBuilder.show() } - } -} diff --git a/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt similarity index 95% rename from app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt rename to app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt index d30fde735..bbfed3309 100644 --- a/app/src/test/java/org/mozilla/fenix/searchdialog/SearchDialogControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt @@ -2,8 +2,9 @@ * 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.searchdialog +package org.mozilla.fenix.search +import androidx.appcompat.app.AlertDialog import androidx.navigation.NavController import androidx.navigation.NavDirections import io.mockk.MockKAnnotations @@ -30,8 +31,8 @@ import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricsUtils -import org.mozilla.fenix.search.AlertDialogBuilder -import org.mozilla.fenix.search.SearchFragmentAction +import org.mozilla.fenix.search.SearchDialogFragmentDirections.Companion.actionGlobalAddonsManagementFragment +import org.mozilla.fenix.search.SearchDialogFragmentDirections.Companion.actionGlobalSearchEngineFragment import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.utils.Settings @@ -140,7 +141,7 @@ class SearchDialogControllerTest { @Test fun handleAddonsUrlCommitted() { val url = "about:addons" - val directions = SearchDialogFragmentDirections.actionGlobalAddonsManagementFragment() + val directions = actionGlobalAddonsManagementFragment() controller.handleUrlCommitted(url) @@ -288,8 +289,7 @@ class SearchDialogControllerTest { @Test fun handleClickSearchEngineSettings() { - val directions: NavDirections = - SearchDialogFragmentDirections.actionGlobalSearchEngineFragment() + val directions: NavDirections = actionGlobalSearchEngineFragment() controller.handleClickSearchEngineSettings() @@ -347,7 +347,7 @@ class SearchDialogControllerTest { @Test fun `show camera permissions needed dialog`() { - val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true) + val dialogBuilder: AlertDialog.Builder = mockk(relaxed = true) val spyController = spyk(controller) every { spyController.buildDialog() } returns dialogBuilder diff --git a/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchDialogInteractorTest.kt similarity index 91% rename from app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt rename to app/src/test/java/org/mozilla/fenix/search/SearchDialogInteractorTest.kt index 7a3af7ef0..7fc3761d1 100644 --- a/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchDialogInteractorTest.kt @@ -14,15 +14,15 @@ import org.junit.Before import org.junit.Test @ExperimentalCoroutinesApi -class SearchInteractorTest { +class SearchDialogInteractorTest { - lateinit var searchController: DefaultSearchController - lateinit var interactor: SearchInteractor + lateinit var searchController: SearchDialogController + lateinit var interactor: SearchDialogInteractor @Before fun setup() { searchController = mockk(relaxed = true) - interactor = SearchInteractor( + interactor = SearchDialogInteractor( searchController ) } @@ -47,7 +47,7 @@ class SearchInteractorTest { @Test fun onTextChanged() { - val interactor = SearchInteractor(searchController) + val interactor = SearchDialogInteractor(searchController) interactor.onTextChanged("test") diff --git a/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt b/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt index 1d1424d01..1866d9e44 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.settings.account +import androidx.appcompat.app.AlertDialog import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK @@ -13,7 +14,6 @@ import io.mockk.verify import org.junit.Before import org.junit.Test import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.search.AlertDialogBuilder class DefaultSyncControllerTest { @@ -28,7 +28,7 @@ class DefaultSyncControllerTest { @Test fun `show camera permissions needed dialog`() { - val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true) + val dialogBuilder: AlertDialog.Builder = mockk(relaxed = true) val spyController = spyk(syncController) every { spyController.buildDialog() } returns dialogBuilder