From 244dc7e2af80c23a0a19e4fd75b01177b9507816 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Wed, 16 Nov 2022 17:11:15 +0200 Subject: [PATCH] [fenix] For https://github.com/mozilla-mobile/fenix/issues/25816: Support changing autocomplete providers. Everywhere the toolbar which needs to show autocomplete suggestions is used we show a `ToolbarView`. So instead of having this configurable in 2 or more places as it happened before the autocomplete functionality is configured only from the `ToolbarView` class. It will contain a `ToolbarAutocompleteFeature` that will be immediately updated with the appropriate autocomplete providers or remove all such providers to immediately update the current autocomplete or remove it entirely depending on the new search engine selected. --- .../fenix/components/BackgroundServices.kt | 4 + .../java/org/mozilla/fenix/components/Core.kt | 15 + .../components/toolbar/BrowserToolbarView.kt | 4 - .../components/toolbar/ToolbarIntegration.kt | 27 +- .../fenix/search/SearchDialogFragment.kt | 33 +- .../fenix/search/toolbar/ToolbarView.kt | 80 ++++ .../toolbar/DefaultToolbarIntegrationTest.kt | 3 - .../fenix/search/toolbar/ToolbarViewTest.kt | 392 +++++++++++++++++- 8 files changed, 491 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt index 1d849614f4..250a380ca4 100644 --- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -21,6 +21,7 @@ import mozilla.components.concept.sync.DeviceType import mozilla.components.concept.sync.OAuthAccount import mozilla.components.feature.accounts.push.FxaPushSupportFeature import mozilla.components.feature.accounts.push.SendTabFeature +import mozilla.components.feature.syncedtabs.SyncedTabsAutocompleteProvider import mozilla.components.feature.syncedtabs.storage.SyncedTabsStorage import mozilla.components.lib.crash.CrashReporter import mozilla.components.service.fxa.PeriodicSyncConfig @@ -144,6 +145,9 @@ class BackgroundServices( val syncedTabsStorage by lazyMonitored { SyncedTabsStorage(accountManager, context.components.core.store, remoteTabsStorage.value) } + val syncedTabsAutocompleteProvider by lazyMonitored { + SyncedTabsAutocompleteProvider(syncedTabsStorage) + } @VisibleForTesting(otherwise = PRIVATE) fun makeAccountManager( diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index a360f739fd..20115723a4 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -11,6 +11,8 @@ import android.os.StrictMode import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap +import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider +import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider import mozilla.components.browser.engine.gecko.GeckoEngine import mozilla.components.browser.engine.gecko.cookiebanners.GeckoCookieBannersStorage import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient @@ -33,6 +35,7 @@ import mozilla.components.concept.engine.DefaultSettings import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.mediaquery.PreferredColorScheme import mozilla.components.concept.fetch.Client +import mozilla.components.feature.awesomebar.provider.SessionAutocompleteProvider import mozilla.components.feature.customtabs.store.CustomTabsServiceStore import mozilla.components.feature.downloads.DownloadMiddleware import mozilla.components.feature.logins.exceptions.LoginExceptionStorage @@ -358,6 +361,16 @@ class Core( val lazyPasswordsStorage = lazyMonitored { SyncableLoginsStorage(context, lazySecurePrefs) } val lazyAutofillStorage = lazyMonitored { AutofillCreditCardsAddressesStorage(context, lazySecurePrefs) } + val lazyDomainsAutocompleteProvider = lazyMonitored { + // Assume this is used together with other autocomplete providers (like history) which have priority 0 + // and set priority 1 for the domains provider to ensure other providers' results are shown first. + ShippedDomainsProvider(1).also { shippedDomainsProvider -> + shippedDomainsProvider.initialize(context) + } + } + val lazySessionAutocompleteProvider = lazyMonitored { + SessionAutocompleteProvider(store) + } /** * The storage component to sync and persist tabs in a Firefox Sync account. @@ -372,6 +385,8 @@ class Core( val bookmarksStorage: PlacesBookmarksStorage get() = lazyBookmarksStorage.value val passwordsStorage: SyncableLoginsStorage get() = lazyPasswordsStorage.value val autofillStorage: AutofillCreditCardsAddressesStorage get() = lazyAutofillStorage.value + val domainsAutocompleteProvider: BaseDomainAutocompleteProvider get() = lazyDomainsAutocompleteProvider.value + val sessionAutocompleteProvider: SessionAutocompleteProvider get() = lazySessionAutocompleteProvider.value val tabCollectionStorage by lazyMonitored { TabCollectionStorage( diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt index 244a6239ba..66a2263647 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt @@ -15,7 +15,6 @@ import androidx.annotation.VisibleForTesting import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner -import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.state.CustomTabSessionState import mozilla.components.browser.state.state.ExternalAppType @@ -175,13 +174,10 @@ class BrowserToolbarView( this, view, menuToolbar, - ShippedDomainsProvider().also { it.initialize(this) }, - components.core.historyStorage, lifecycleOwner, sessionId = null, isPrivate = components.core.store.state.selectedTab?.content?.private ?: false, interactor = interactor, - engine = components.core.engine, ) } } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt index a2371f9feb..5f35eafdfb 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt @@ -8,15 +8,11 @@ import android.content.Context import androidx.annotation.VisibleForTesting import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner -import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.privateTabs import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.browser.toolbar.display.DisplayToolbar -import mozilla.components.concept.engine.Engine -import mozilla.components.concept.toolbar.AutocompleteProvider import mozilla.components.feature.tabs.toolbar.TabCounterToolbarButton -import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature import mozilla.components.feature.toolbar.ToolbarBehaviorController import mozilla.components.feature.toolbar.ToolbarFeature import mozilla.components.feature.toolbar.ToolbarPresenter @@ -28,6 +24,9 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings import org.mozilla.fenix.theme.ThemeManager +/** + * Feature configuring the toolbar when in display mode. + */ abstract class ToolbarIntegration( context: Context, toolbar: BrowserToolbar, @@ -77,18 +76,14 @@ abstract class ToolbarIntegration( } } -@Suppress("LongParameterList") class DefaultToolbarIntegration( context: Context, toolbar: BrowserToolbar, toolbarMenu: ToolbarMenu, - domainAutocompleteProvider: BaseDomainAutocompleteProvider, - historyStorage: AutocompleteProvider, lifecycleOwner: LifecycleOwner, sessionId: String? = null, isPrivate: Boolean, interactor: BrowserToolbarInteractor, - engine: Engine, ) : ToolbarIntegration( context = context, toolbar = toolbar, @@ -150,22 +145,6 @@ class DefaultToolbarIntegration( tabsAction.updateCount(tabCount) toolbar.addBrowserAction(tabsAction) - - val engineForSpeculativeConnects = if (!isPrivate) engine else null - ToolbarAutocompleteFeature( - toolbar, - engineForSpeculativeConnects, - ).apply { - updateAutocompleteProviders( - listOfNotNull( - when (context.settings().shouldShowHistorySuggestions) { - true -> historyStorage - false -> null - }, - domainAutocompleteProvider, - ), - ) - } } override fun start() { diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt index eac3ae10a4..f319460c9b 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt @@ -45,7 +45,6 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider import mozilla.components.browser.state.search.SearchEngine import mozilla.components.browser.state.state.searchEngines import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine @@ -53,10 +52,8 @@ import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.menu.candidate.DrawableMenuIcon import mozilla.components.concept.menu.candidate.TextMenuCandidate -import mozilla.components.concept.toolbar.AutocompleteProvider import mozilla.components.concept.toolbar.Toolbar import mozilla.components.feature.qr.QrFeature -import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature import mozilla.components.lib.state.ext.consumeFlow import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.service.glean.private.NoExtras @@ -231,6 +228,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { toolbarView = ToolbarView( requireContext(), requireContext().settings(), + requireComponents, interactor, isPrivate, binding.toolbar, @@ -239,27 +237,6 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { inlineAutocompleteEditText = it.view.findViewById(R.id.mozac_browser_toolbar_edit_url_view) } - if (requireContext().settings().shouldAutocompleteInAwesomebar) { - val engineForSpeculativeConnects = if (!isPrivate) requireComponents.core.engine else null - - ToolbarAutocompleteFeature( - binding.toolbar, - engineForSpeculativeConnects, - { store.state.searchEngineSource.searchEngine?.type != SearchEngine.Type.APPLICATION }, - ).apply { - updateAutocompleteProviders( - listOfNotNull( - historyStorageProvider(), - // Assume the history provider has priority 0 and set priority 1 for the domains provider - // to ensure the first source checked for autocomplete suggestions is history. - ShippedDomainsProvider(1).also { shippedDomainsProvider -> - shippedDomainsProvider.initialize(requireContext()) - }, - ), - ) - } - } - val awesomeBar = binding.awesomeBar awesomeBarView = AwesomeBarView( @@ -663,14 +640,6 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { dismissAllowingStateLoss() } - private fun historyStorageProvider(): AutocompleteProvider? { - return if (requireContext().settings().shouldShowHistorySuggestions) { - requireComponents.core.historyStorage - } else { - null - } - } - @Suppress("DEPRECATION") // https://github.com/mozilla-mobile/fenix/issues/19920 private fun createQrFeature(): QrFeature { diff --git a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt index 9ec51c0c1f..21f7c4e8b0 100644 --- a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt @@ -13,11 +13,14 @@ import androidx.core.content.ContextCompat import mozilla.components.browser.state.search.SearchEngine import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.android.content.res.resolveAttribute import mozilla.components.support.ktx.android.view.hideKeyboard import org.mozilla.fenix.R +import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.Core +import org.mozilla.fenix.search.SearchEngineSource import org.mozilla.fenix.search.SearchFragmentState import org.mozilla.fenix.utils.Settings @@ -55,6 +58,7 @@ interface ToolbarInteractor : SearchSelectorInteractor { class ToolbarView( private val context: Context, private val settings: Settings, + private val components: Components, private val interactor: ToolbarInteractor, private val isPrivate: Boolean, val view: BrowserToolbar, @@ -64,6 +68,13 @@ class ToolbarView( @VisibleForTesting internal var isInitialized = false + @VisibleForTesting + internal val autocompleteFeature = ToolbarAutocompleteFeature( + toolbar = view, + engine = if (!isPrivate) components.core.engine else null, + shouldAutocomplete = { settings.shouldAutocompleteInAwesomebar }, + ) + init { view.apply { editMode() @@ -145,6 +156,8 @@ class ToolbarView( isInitialized = true } + configureAutocomplete(searchState.searchEngineSource) + val searchEngine = searchState.searchEngineSource.searchEngine view.edit.hint = when (searchEngine?.type) { @@ -182,4 +195,71 @@ class ToolbarView( view.edit.setIcon(icon, searchEngine.name) } } + + private fun configureAutocomplete(searchEngineSource: SearchEngineSource) { + when (settings.showUnifiedSearchFeature) { + true -> configureAutocompleteWithUnifiedSearch(searchEngineSource) + else -> configureAutocompleteWithoutUnifiedSearch(searchEngineSource) + } + } + + private fun configureAutocompleteWithoutUnifiedSearch(searchEngineSource: SearchEngineSource) { + when (searchEngineSource) { + is SearchEngineSource.Default -> { + autocompleteFeature.updateAutocompleteProviders( + listOfNotNull( + when (settings.shouldShowHistorySuggestions) { + true -> components.core.historyStorage + false -> null + }, + components.core.domainsAutocompleteProvider, + ), + ) + } + else -> { + autocompleteFeature.updateAutocompleteProviders(emptyList()) + } + } + } + + private fun configureAutocompleteWithUnifiedSearch(searchEngineSource: SearchEngineSource) { + when (searchEngineSource) { + is SearchEngineSource.Default -> { + autocompleteFeature.updateAutocompleteProviders( + listOfNotNull( + when (settings.shouldShowHistorySuggestions) { + true -> components.core.historyStorage + false -> null + }, + components.core.domainsAutocompleteProvider, + ), + ) + } + is SearchEngineSource.Tabs -> { + autocompleteFeature.updateAutocompleteProviders( + listOf( + components.core.sessionAutocompleteProvider, + components.backgroundServices.syncedTabsAutocompleteProvider, + ), + ) + } + is SearchEngineSource.Bookmarks -> { + autocompleteFeature.updateAutocompleteProviders( + listOf( + components.core.bookmarksStorage, + ), + ) + } + is SearchEngineSource.History -> { + autocompleteFeature.updateAutocompleteProviders( + listOf( + components.core.historyStorage, + ), + ) + } + else -> { + autocompleteFeature.updateAutocompleteProviders(emptyList()) + } + } + } } diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultToolbarIntegrationTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultToolbarIntegrationTest.kt index 21c2bbf604..298cf64ae6 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultToolbarIntegrationTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultToolbarIntegrationTest.kt @@ -38,13 +38,10 @@ class DefaultToolbarIntegrationTest { context = testContext, toolbar = mockk(relaxed = true), toolbarMenu = mockk(relaxed = true), - domainAutocompleteProvider = mockk(relaxed = true), - historyStorage = mockk(), lifecycleOwner = mockk(), sessionId = null, isPrivate = false, interactor = mockk(), - engine = mockk(), ) } diff --git a/app/src/test/java/org/mozilla/fenix/search/toolbar/ToolbarViewTest.kt b/app/src/test/java/org/mozilla/fenix/search/toolbar/ToolbarViewTest.kt index 1b4dee21b1..3ff5beaea6 100644 --- a/app/src/test/java/org/mozilla/fenix/search/toolbar/ToolbarViewTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/toolbar/ToolbarViewTest.kt @@ -5,34 +5,46 @@ package org.mozilla.fenix.search.toolbar import android.content.Context +import android.graphics.Bitmap import androidx.appcompat.view.ContextThemeWrapper import androidx.core.graphics.drawable.toBitmap import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk +import io.mockk.mockkConstructor import io.mockk.mockkObject import io.mockk.spyk import io.mockk.verify +import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.storage.sync.PlacesBookmarksStorage +import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.browser.toolbar.edit.EditToolbar import mozilla.components.concept.engine.Engine import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.feature.awesomebar.provider.SessionAutocompleteProvider +import mozilla.components.feature.syncedtabs.SyncedTabsAutocompleteProvider +import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R +import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.metrics.MetricsUtils import org.mozilla.fenix.ext.settings import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.search.SearchEngineSource import org.mozilla.fenix.search.SearchFragmentState +import org.mozilla.fenix.utils.Settings import java.util.UUID @RunWith(FenixRobolectricTestRunner::class) @@ -127,6 +139,7 @@ class ToolbarViewTest { @Test fun `GIVEN search term is set WHEN switching to edit mode THEN the cursor is set at the end of the search term`() { every { context.settings().showUnifiedSearchFeature } returns true + every { context.settings().shouldShowHistorySuggestions } returns true val view = buildToolbarView(false) mockkObject(FeatureFlags) @@ -140,6 +153,7 @@ class ToolbarViewTest { @Test fun `GIVEN no search term is set WHEN switching to edit mode THEN the cursor is set at the end of the search term`() { every { context.settings().showUnifiedSearchFeature } returns true + every { context.settings().shouldShowHistorySuggestions } returns true val view = buildToolbarView(false) mockkObject(FeatureFlags) @@ -255,10 +269,369 @@ class ToolbarViewTest { assertEquals(context.getString(R.string.search_hint), toolbarView.view.edit.hint) } - private fun buildToolbarView(isPrivate: Boolean) = ToolbarView( - context, - context.settings(), - interactor, + @Test + fun `GIVEN normal browsing mode WHEN the toolbar view is initialized THEN create an autocomplete feature with valid engine`() { + val toolbarView = buildToolbarView(false) + + val autocompleteFeature = toolbarView.autocompleteFeature + + assertNotNull(autocompleteFeature.engine) + } + + @Test + fun `GIVEN normal private mode WHEN the toolbar view is initialized THEN create an autocomplete feature with null engine`() { + val toolbarView = buildToolbarView(true) + + val autocompleteFeature = toolbarView.autocompleteFeature + + assertNull(autocompleteFeature.engine) + } + + @Test + fun `GIVEN autocomplete disabled WHEN the toolbar view is initialized THEN create an autocomplete with disabled functionality`() { + val settings: Settings = mockk { + every { shouldAutocompleteInAwesomebar } returns false + } + val toolbarView = buildToolbarView(true, settings) + + val autocompleteFeature = toolbarView.autocompleteFeature + + assertFalse(autocompleteFeature.shouldAutocomplete()) + } + + @Test + fun `GIVEN autocomplete enabled WHEN the toolbar view is initialized THEN create an autocomplete with enabled functionality`() { + val settings: Settings = mockk { + every { shouldAutocompleteInAwesomebar } returns true + } + val toolbarView = buildToolbarView(true, settings) + + val autocompleteFeature = toolbarView.autocompleteFeature + + assertTrue(autocompleteFeature.shouldAutocomplete()) + } + + @Test + fun `GIVEN unified search is disabled and history suggestions enabled a new search state with the default search engine source selected WHEN updating the toolbar THEN reconfigure autocomplete suggestions`() { + mockkConstructor(ToolbarAutocompleteFeature::class) { + val historyProvider: PlacesHistoryStorage = mockk(relaxed = true) + val domainsProvider: BaseDomainAutocompleteProvider = mockk(relaxed = true) + val components: Components = mockk(relaxed = true) { + every { core.historyStorage } returns historyProvider + every { core.domainsAutocompleteProvider } returns domainsProvider + } + + val settings: Settings = mockk(relaxed = true) { + every { showUnifiedSearchFeature } returns false + every { shouldShowHistorySuggestions } returns true + } + val toolbarView = buildToolbarView( + isPrivate = false, + settings = settings, + components = components, + ) + + toolbarView.update(defaultState) + + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = listOf(historyProvider, domainsProvider), + refreshAutocomplete = true, + ) + } + } + } + + @Test + fun `GIVEN unified search is disabled, history suggestions disabled and a new search state with the default search engine source selected WHEN updating the toolbar THEN reconfigure autocomplete suggestions`() { + mockkConstructor(ToolbarAutocompleteFeature::class) { + val historyProvider: PlacesHistoryStorage = mockk(relaxed = true) + val domainsProvider: BaseDomainAutocompleteProvider = mockk(relaxed = true) + val components: Components = mockk(relaxed = true) { + every { core.historyStorage } returns historyProvider + every { core.domainsAutocompleteProvider } returns domainsProvider + } + + val settings: Settings = mockk(relaxed = true) { + every { showUnifiedSearchFeature } returns false + every { shouldShowHistorySuggestions } returns false + } + val toolbarView = buildToolbarView( + isPrivate = false, + settings = settings, + components = components, + ) + + toolbarView.update(defaultState) + + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = listOf(domainsProvider), + refreshAutocomplete = true, + ) + } + } + } + + @Test + fun `GIVEN unified search is disabled and a new search state with other than the default search engine source selected WHEN updating the toolbar THEN reconfigure autocomplete suggestions`() { + mockkConstructor(ToolbarAutocompleteFeature::class) { + val historyProvider: PlacesHistoryStorage = mockk(relaxed = true) + val domainsProvider: BaseDomainAutocompleteProvider = mockk(relaxed = true) + val components: Components = mockk(relaxed = true) { + every { core.historyStorage } returns historyProvider + every { core.domainsAutocompleteProvider } returns domainsProvider + } + + val settings: Settings = mockk(relaxed = true) { + every { showUnifiedSearchFeature } returns false + every { shouldShowHistorySuggestions } returns true + } + val toolbarView = buildToolbarView( + isPrivate = false, + settings = settings, + components = components, + ) + + toolbarView.update(defaultState.copy(searchEngineSource = SearchEngineSource.Tabs(fakeSearchEngine))) + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = emptyList(), + refreshAutocomplete = true, + ) + } + + toolbarView.update(defaultState.copy(searchEngineSource = SearchEngineSource.Bookmarks(fakeSearchEngine))) + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = emptyList(), + refreshAutocomplete = true, + ) + } + + toolbarView.update(defaultState.copy(searchEngineSource = SearchEngineSource.History(fakeSearchEngine))) + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = emptyList(), + refreshAutocomplete = true, + ) + } + + toolbarView.update(defaultState.copy(searchEngineSource = SearchEngineSource.Shortcut(fakeSearchEngine))) + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = emptyList(), + refreshAutocomplete = true, + ) + } + + toolbarView.update(defaultState.copy(searchEngineSource = SearchEngineSource.None)) + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = emptyList(), + refreshAutocomplete = true, + ) + } + } + } + + @Test + fun `GIVEN history suggestions enabled and a new search state with the default search engine source selected WHEN updating the toolbar THEN reconfigure autocomplete suggestions`() { + mockkConstructor(ToolbarAutocompleteFeature::class) { + val historyProvider: PlacesHistoryStorage = mockk(relaxed = true) + val domainsProvider: BaseDomainAutocompleteProvider = mockk(relaxed = true) + val components: Components = mockk(relaxed = true) { + every { core.historyStorage } returns historyProvider + every { core.domainsAutocompleteProvider } returns domainsProvider + } + + val settings: Settings = mockk(relaxed = true) { + every { showUnifiedSearchFeature } returns true + every { shouldShowHistorySuggestions } returns true + } + val toolbarView = buildToolbarView( + isPrivate = false, + settings = settings, + components = components, + ) + + toolbarView.update(defaultState) + + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = listOf(historyProvider, domainsProvider), + refreshAutocomplete = true, + ) + } + } + } + + @Test + fun `GIVEN history suggestions disabled and a new search state with the default search engine source selected WHEN updating the toolbar THEN reconfigure autocomplete suggestions`() { + mockkConstructor(ToolbarAutocompleteFeature::class) { + val historyProvider: PlacesHistoryStorage = mockk(relaxed = true) + val domainsProvider: BaseDomainAutocompleteProvider = mockk(relaxed = true) + val components: Components = mockk(relaxed = true) { + every { core.historyStorage } returns historyProvider + every { core.domainsAutocompleteProvider } returns domainsProvider + } + val settings: Settings = mockk(relaxed = true) { + every { showUnifiedSearchFeature } returns true + every { shouldShowHistorySuggestions } returns false + } + val toolbarView = buildToolbarView( + isPrivate = false, + settings = settings, + components = components, + ) + + toolbarView.update(defaultState) + + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = listOf(domainsProvider), + refreshAutocomplete = true, + ) + } + } + } + + @Test + fun `GIVEN a new search state with the tabs engine source selected WHEN updating the toolbar THEN reconfigure autocomplete suggestions`() { + mockkConstructor(ToolbarAutocompleteFeature::class) { + val localSessionProvider: SessionAutocompleteProvider = mockk(relaxed = true) + val syncedSessionsProvider: SyncedTabsAutocompleteProvider = mockk(relaxed = true) + val components: Components = mockk(relaxed = true) { + every { core.sessionAutocompleteProvider } returns localSessionProvider + every { backgroundServices.syncedTabsAutocompleteProvider } returns syncedSessionsProvider + } + val settings: Settings = mockk(relaxed = true) { + every { showUnifiedSearchFeature } returns true + } + val toolbarView = buildToolbarView( + isPrivate = false, + settings = settings, + components = components, + ) + + toolbarView.update(defaultState.copy(searchEngineSource = SearchEngineSource.Tabs(fakeSearchEngine))) + + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = listOf(localSessionProvider, syncedSessionsProvider), + refreshAutocomplete = true, + ) + } + } + } + + @Test + fun `GIVEN a new search state with the bookmarks engine source selected WHEN updating the toolbar THEN reconfigure autocomplete suggestions`() { + mockkConstructor(ToolbarAutocompleteFeature::class) { + val bookmarksProvider: PlacesBookmarksStorage = mockk(relaxed = true) + val components: Components = mockk(relaxed = true) { + every { core.bookmarksStorage } returns bookmarksProvider + } + val settings: Settings = mockk(relaxed = true) { + every { showUnifiedSearchFeature } returns true + } + val toolbarView = buildToolbarView( + isPrivate = false, + settings = settings, + components = components, + ) + + toolbarView.update(defaultState.copy(searchEngineSource = SearchEngineSource.Bookmarks(fakeSearchEngine))) + + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = listOf(bookmarksProvider), + refreshAutocomplete = true, + ) + } + } + } + + @Test + fun `GIVEN a new search state with the history engine source selected WHEN updating the toolbar THEN reconfigure autocomplete suggestions`() { + mockkConstructor(ToolbarAutocompleteFeature::class) { + val historyProvider: PlacesHistoryStorage = mockk(relaxed = true) + val components: Components = mockk(relaxed = true) { + every { core.historyStorage } returns historyProvider + } + val settings: Settings = mockk(relaxed = true) { + every { showUnifiedSearchFeature } returns true + } + val toolbarView = buildToolbarView( + isPrivate = false, + settings = settings, + components = components, + ) + + toolbarView.update(defaultState.copy(searchEngineSource = SearchEngineSource.History(fakeSearchEngine))) + + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = listOf(historyProvider), + refreshAutocomplete = true, + ) + } + } + } + + @Test + fun `GIVEN a new search state with no engine source selected WHEN updating the toolbar THEN reconfigure autocomplete suggestions`() { + mockkConstructor(ToolbarAutocompleteFeature::class) { + val settings: Settings = mockk(relaxed = true) { + every { showUnifiedSearchFeature } returns true + } + val toolbarView = buildToolbarView( + false, + settings = settings, + ) + + toolbarView.update(defaultState.copy(searchEngineSource = SearchEngineSource.None)) + + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = emptyList(), + refreshAutocomplete = true, + ) + } + } + } + + @Test + fun `GIVEN a new search state with a shortcut engine source selected WHEN updating the toolbar THEN reconfigure autocomplete suggestions`() { + mockkConstructor(ToolbarAutocompleteFeature::class) { + val settings: Settings = mockk(relaxed = true) { + every { showUnifiedSearchFeature } returns true + } + val toolbarView = buildToolbarView( + isPrivate = false, + settings = settings, + ) + + toolbarView.update(defaultState.copy(searchEngineSource = SearchEngineSource.Shortcut(fakeSearchEngine))) + + verify { + toolbarView.autocompleteFeature.updateAutocompleteProviders( + providers = emptyList(), + refreshAutocomplete = true, + ) + } + } + } + + private fun buildToolbarView( + isPrivate: Boolean, + settings: Settings = context.settings(), + components: Components = mockk(relaxed = true), + ) = ToolbarView( + context = context, + settings = settings, + components = components, + interactor = interactor, isPrivate = isPrivate, view = toolbar, fromHomeFragment = false, @@ -272,3 +645,14 @@ class ToolbarViewTest { isGeneral = isGeneral, ) } + +/** + * Get a fake [SearchEngine] to use where a simple mock won't suffice. + */ +private val fakeSearchEngine = SearchEngine( + id = "fakeId", + name = "fakeName", + icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8), + type = SearchEngine.Type.CUSTOM, + resultUrls = emptyList(), +)