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.
pull/159/head^2
Elise Richards 4 years ago committed by GitHub
parent add60611b4
commit 1adf467248
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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),

@ -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
*/

@ -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 ->

@ -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()
}
}

@ -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() {

@ -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()))
}

@ -46,7 +46,7 @@ class StartSearchIntentProcessor(
out.removeExtra(HomeActivity.OPEN_TO_SEARCH)
val directions = source?.let {
NavGraphDirections.actionGlobalSearch(
NavGraphDirections.actionGlobalSearchDialog(
sessionId = null,
searchAccessPoint = it
)

@ -428,7 +428,7 @@ class DefaultSessionControlController(
}
override fun handlePaste(clipboardText: String) {
val directions = HomeFragmentDirections.actionGlobalSearch(
val directions = HomeFragmentDirections.actionGlobalSearchDialog(
sessionId = null,
pastedText = clipboardText
)

@ -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()
}
}
}

@ -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,

@ -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 {

@ -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) {

@ -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<QrFeature>()
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<SearchFragmentArgs>()
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<InlineAutocompleteEditText>(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<InlineAutocompleteEditText>(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<String>,
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<View>(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
}
}

@ -23,10 +23,6 @@
app:popUpTo="@id/homeFragment"
app:popUpToInclusive="false" />
<action
android:id="@+id/action_global_search"
app:destination="@id/searchFragment" />
<action
android:id="@+id/action_global_search_dialog"
app:destination="@id/searchDialogFragment" />
@ -145,7 +141,7 @@
<dialog
android:id="@+id/searchDialogFragment"
android:name="org.mozilla.fenix.searchdialog.SearchDialogFragment"
android:name="org.mozilla.fenix.search.SearchDialogFragment"
tools:layout="@layout/fragment_search_dialog">
<argument
android:name="session_id"
@ -162,25 +158,6 @@
app:argType="org.mozilla.fenix.components.metrics.Event$PerformedSearch$SearchAccessPoint" />
</dialog>
<fragment
android:id="@+id/searchFragment"
android:name="org.mozilla.fenix.search.SearchFragment"
tools:layout="@layout/fragment_search">
<argument
android:name="session_id"
app:argType="string"
app:nullable="true" />
<argument
android:name="pastedText"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="search_access_point"
android:defaultValue="NONE"
app:argType="org.mozilla.fenix.components.metrics.Event$PerformedSearch$SearchAccessPoint" />
</fragment>
<fragment
android:id="@+id/recentlyClosedFragment"
android:name="org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragment"
@ -200,11 +177,6 @@
android:name="org.mozilla.fenix.browser.BrowserFragment"
app:exitAnim="@anim/fade_out"
tools:layout="@layout/fragment_browser">
<action
android:id="@+id/action_browserFragment_to_searchFragment"
app:destination="@id/searchFragment"
app:enterAnim="@anim/fade_in_up"
app:popExitAnim="@anim/fade_out_down" />
<argument
android:name="activeSessionId"
app:argType="string"

@ -78,10 +78,10 @@ class DefaultBrowserToolbarControllerTest {
@Test
fun handleBrowserToolbarPaste() {
val pastedText = "Mozilla"
val controller = createController(useNewSearchExperience = false)
val controller = createController()
controller.handleToolbarPaste(pastedText)
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
val directions = BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = "1",
pastedText = pastedText
)
@ -92,7 +92,7 @@ class DefaultBrowserToolbarControllerTest {
@Test
fun handleBrowserToolbarPaste_useNewSearchExperience() {
val pastedText = "Mozilla"
val controller = createController(useNewSearchExperience = true)
val controller = createController()
controller.handleToolbarPaste(pastedText)
val directions = BrowserFragmentDirections.actionGlobalSearchDialog(
@ -153,10 +153,10 @@ class DefaultBrowserToolbarControllerTest {
@Test
fun handleToolbarClick() {
val controller = createController(useNewSearchExperience = false)
val controller = createController()
controller.handleToolbarClick()
val expected = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
val expected = BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = "1"
)
@ -166,7 +166,7 @@ class DefaultBrowserToolbarControllerTest {
@Test
fun handleToolbarClick_useNewSearchExperience() {
val controller = createController(useNewSearchExperience = true)
val controller = createController()
controller.handleToolbarClick()
val expected = BrowserFragmentDirections.actionGlobalSearchDialog(
@ -256,8 +256,7 @@ class DefaultBrowserToolbarControllerTest {
private fun createController(
activity: HomeActivity = this.activity,
customTabSession: Session? = null,
useNewSearchExperience: Boolean = false
customTabSession: Session? = null
) = DefaultBrowserToolbarController(
activity = activity,
navController = navController,
@ -267,7 +266,6 @@ class DefaultBrowserToolbarControllerTest {
customTabSession = customTabSession,
readerModeController = readerModeController,
sessionManager = sessionManager,
useNewSearchExperience = useNewSearchExperience,
onTabCounterClicked = onTabCounterClicked,
onCloseTab = onCloseTab
)

@ -410,7 +410,7 @@ class DefaultSessionControlControllerTest {
verify {
navController.navigate(
match<NavDirections> { it.actionId == R.id.action_global_search },
match<NavDirections> { it.actionId == R.id.action_global_search_dialog },
null
)
}

@ -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
),

@ -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<Session>()
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<Session>()
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() }
}
}

@ -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

@ -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")

@ -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

Loading…
Cancel
Save