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 90057dfbc..b6745a48c 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -8,14 +8,18 @@ import android.content.Context import android.content.res.Configuration import android.os.Build 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.engine.gecko.GeckoEngine import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient import mozilla.components.browser.engine.gecko.permission.GeckoSitePermissionsStorage import mozilla.components.browser.icons.BrowserIcons import mozilla.components.browser.session.storage.SessionStorage import mozilla.components.browser.state.engine.EngineMiddleware +import mozilla.components.browser.state.search.SearchEngine import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.SearchState import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.storage.sync.PlacesBookmarksStorage import mozilla.components.browser.storage.sync.PlacesHistoryStorage @@ -39,6 +43,7 @@ import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.readerview.ReaderViewMiddleware import mozilla.components.feature.recentlyclosed.RecentlyClosedMiddleware import mozilla.components.feature.recentlyclosed.RecentlyClosedTabsStorage +import mozilla.components.feature.search.ext.createApplicationSearchEngine import mozilla.components.feature.search.middleware.AdsTelemetryMiddleware import mozilla.components.feature.search.middleware.SearchMiddleware import mozilla.components.feature.search.region.RegionMiddleware @@ -189,6 +194,17 @@ class Core( } } + val applicationSearchEngines: List by lazyMonitored { + listOf( + createApplicationSearchEngine( + id = HISTORY_SEARCH_ENGINE_ID, + name = context.getString(R.string.library_history), + url = "", + icon = getDrawable(context, R.drawable.ic_history_search)?.toBitmap()!!, + ) + ) + } + /** * The [BrowserStore] holds the global [BrowserState]. */ @@ -217,6 +233,15 @@ class Core( ) BrowserStore( + initialState = BrowserState( + search = SearchState( + applicationSearchEngines = if (context.settings().showUnifiedSearchFeature) { + applicationSearchEngines + } else { + emptyList() + }, + ) + ), middleware = middlewareList + EngineMiddleware.create( engine, // We are disabling automatic suspending of engine sessions under memory pressure. @@ -477,5 +502,11 @@ class Core( private const val RECENTLY_CLOSED_MAX = 10 const val HISTORY_METADATA_MAX_AGE_IN_MS = 14 * 24 * 60 * 60 * 1000 // 14 days private const val CONTILE_MAX_CACHE_AGE = 60L // 60 minutes + const val HISTORY_SEARCH_ENGINE_ID = "history_search_engine_id" + + // Maximum number of suggestions returned from the history search engine source. + const val METADATA_HISTORY_SUGGESTION_LIMIT = 100 + // Maximum number of suggestions returned from shortcut search engine. + const val METADATA_SHORTCUT_SUGGESTION_LIMIT = 20 } } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/awesomebar/AwesomeBarView.kt b/app/src/main/java/org/mozilla/fenix/library/history/awesomebar/AwesomeBarView.kt index 4dd213874..4960aa810 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/awesomebar/AwesomeBarView.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/awesomebar/AwesomeBarView.kt @@ -9,6 +9,7 @@ import mozilla.components.feature.awesomebar.provider.CombinedHistorySuggestionP import mozilla.components.feature.session.SessionUseCases import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.components.Core.Companion.METADATA_SHORTCUT_SUGGESTION_LIMIT import org.mozilla.fenix.ext.components import org.mozilla.fenix.library.history.HistorySearchFragmentState @@ -47,7 +48,7 @@ class AwesomeBarView( loadUrlUseCase = loadUrlUseCase, icons = components.core.icons, engine = engineForSpeculativeConnects, - maxNumberOfSuggestions = METADATA_SUGGESTION_LIMIT, + maxNumberOfSuggestions = METADATA_SHORTCUT_SUGGESTION_LIMIT, showEditSuggestion = false, ) @@ -57,9 +58,4 @@ class AwesomeBarView( fun update(state: HistorySearchFragmentState) { view.onInputChanged(state.query) } - - companion object { - // Maximum number of suggestions returned from the history metadata storage. - const val METADATA_SUGGESTION_LIMIT = 100 - } } diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt index 8c2f945e9..027281599 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt @@ -23,6 +23,7 @@ import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.GleanMetrics.SearchShortcuts import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R +import org.mozilla.fenix.components.Core import org.mozilla.fenix.components.metrics.MetricsUtils import org.mozilla.fenix.crashes.CrashListActivity import org.mozilla.fenix.ext.navigateSafe @@ -69,6 +70,11 @@ class SearchDialogController( ) : SearchController { override fun handleUrlCommitted(url: String, fromHomeScreen: Boolean) { + // Do not load URL if application search engine is selected. + if (fragmentStore.state.searchEngineSource.searchEngine?.type == SearchEngine.Type.APPLICATION) { + return + } + when (url) { "about:crashes" -> { // The list of past crashes can be accessed via "settings > about", but desktop and @@ -190,9 +196,22 @@ class SearchDialogController( override fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) { focusToolbar() - fragmentStore.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine)) + + when { + searchEngine.type == SearchEngine.Type.APPLICATION && searchEngine.id == Core.HISTORY_SEARCH_ENGINE_ID -> { + fragmentStore.dispatch(SearchFragmentAction.SearchHistoryEngineSelected(searchEngine)) + } + searchEngine == store.state.search.selectedOrDefaultSearchEngine -> { + fragmentStore.dispatch(SearchFragmentAction.SearchDefaultEngineSelected(searchEngine, settings)) + } + else -> { + fragmentStore.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine, settings)) + } + } + val engine = when (searchEngine.type) { SearchEngine.Type.CUSTOM -> "custom" + SearchEngine.Type.APPLICATION -> "application" else -> searchEngine.name } SearchShortcuts.selected.record(SearchShortcuts.SelectedExtra(engine)) diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt index 27fdf5c6e..62dc90caf 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt @@ -16,6 +16,7 @@ import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.metrics.MetricsUtils +import org.mozilla.fenix.utils.Settings /** * The [Store] for holding the [SearchFragmentState] and applying [SearchFragmentAction]s. @@ -33,12 +34,28 @@ class SearchFragmentStore( sealed class SearchEngineSource { abstract val searchEngine: SearchEngine? + /** + * No search engine + */ object None : SearchEngineSource() { override val searchEngine: SearchEngine? = null } + /** + * Search engine set as default + */ data class Default(override val searchEngine: SearchEngine) : SearchEngineSource() + + /** + * Search engine for quick search + * This is for any search engine that is not the user selected default. + */ data class Shortcut(override val searchEngine: SearchEngine) : SearchEngineSource() + + /** + * Search engine for history + */ + data class History(override val searchEngine: SearchEngine) : SearchEngineSource() } /** @@ -60,6 +77,8 @@ sealed class SearchEngineSource { * @property showClipboardSuggestions Whether or not to show clipboard suggestion in the AwesomeBar * @property showHistorySuggestions Whether or not to show history suggestions in the AwesomeBar * @property showBookmarkSuggestions Whether or not to show the bookmark suggestion in the AwesomeBar + * @property showSyncedTabsSuggestions Whether or not to show the synced tabs suggestion in the AwesomeBar + * @property showSessionSuggestions Whether or not to show the session suggestion in the AwesomeBar * @property pastedText The text pasted from the long press toolbar menu * @property clipboardHasUrl Indicates if the clipboard contains an URL. */ @@ -78,6 +97,7 @@ data class SearchFragmentState( val showHistorySuggestions: Boolean, val showBookmarkSuggestions: Boolean, val showSyncedTabsSuggestions: Boolean, + val showSessionSuggestions: Boolean, val tabId: String?, val pastedText: String? = null, val searchAccessPoint: MetricsUtils.Source, @@ -119,6 +139,7 @@ fun createInitialSearchFragmentState( showHistorySuggestions = settings.shouldShowHistorySuggestions, showBookmarkSuggestions = settings.shouldShowBookmarkSuggestions, showSyncedTabsSuggestions = settings.shouldShowSyncedTabsSuggestions, + showSessionSuggestions = true, tabId = tabId, pastedText = pastedText, searchAccessPoint = searchAccessPoint @@ -129,11 +150,44 @@ fun createInitialSearchFragmentState( * Actions to dispatch through the `SearchStore` to modify `SearchState` through the reducer. */ sealed class SearchFragmentAction : Action { + /** + * Action to enable or disable search suggestions. + */ data class SetShowSearchSuggestions(val show: Boolean) : SearchFragmentAction() - data class SearchShortcutEngineSelected(val engine: SearchEngine) : SearchFragmentAction() + + /** + * Action when default search engine is selected. + */ + data class SearchDefaultEngineSelected(val engine: SearchEngine, val settings: Settings) : SearchFragmentAction() + + /** + * Action when shortcut search engine is selected. + */ + data class SearchShortcutEngineSelected(val engine: SearchEngine, val settings: Settings) : SearchFragmentAction() + + /** + * Action when history search engine is selected. + */ + data class SearchHistoryEngineSelected(val engine: SearchEngine) : SearchFragmentAction() + + /** + * Action when search engine picker is selected. + */ data class ShowSearchShortcutEnginePicker(val show: Boolean) : SearchFragmentAction() + + /** + * Action when allow search suggestion in private mode hint is tapped. + */ data class AllowSearchSuggestionsInPrivateModePrompt(val show: Boolean) : SearchFragmentAction() + + /** + * Action when query is updated. + */ data class UpdateQuery(val query: String) : SearchFragmentAction() + + /** + * Action when updating clipboard URL. + */ data class UpdateClipboardHasUrl(val hasUrl: Boolean) : SearchFragmentAction() /** @@ -145,12 +199,56 @@ sealed class SearchFragmentAction : Action { /** * The SearchState Reducer. */ +@Suppress("LongMethod") private fun searchStateReducer(state: SearchFragmentState, action: SearchFragmentAction): SearchFragmentState { return when (action) { + is SearchFragmentAction.SearchDefaultEngineSelected -> + state.copy( + searchEngineSource = SearchEngineSource.Default(action.engine), + showSearchSuggestions = true, + showSearchShortcuts = action.settings.shouldShowSearchShortcuts, + showClipboardSuggestions = action.settings.shouldShowClipboardSuggestions, + showHistorySuggestions = action.settings.shouldShowHistorySuggestions, + showBookmarkSuggestions = action.settings.shouldShowBookmarkSuggestions, + showSyncedTabsSuggestions = action.settings.shouldShowSyncedTabsSuggestions, + showSessionSuggestions = true, + ) is SearchFragmentAction.SearchShortcutEngineSelected -> state.copy( searchEngineSource = SearchEngineSource.Shortcut(action.engine), - showSearchShortcuts = false + showSearchSuggestions = true, + showSearchShortcuts = when (action.settings.showUnifiedSearchFeature) { + true -> false + false -> action.settings.shouldShowSearchShortcuts + }, + showClipboardSuggestions = action.settings.shouldShowClipboardSuggestions, + showHistorySuggestions = when (action.settings.showUnifiedSearchFeature) { + true -> false + false -> action.settings.shouldShowHistorySuggestions + }, + showBookmarkSuggestions = when (action.settings.showUnifiedSearchFeature) { + true -> false + false -> action.settings.shouldShowBookmarkSuggestions + }, + showSyncedTabsSuggestions = when (action.settings.showUnifiedSearchFeature) { + true -> false + false -> action.settings.shouldShowSyncedTabsSuggestions + }, + showSessionSuggestions = when (action.settings.showUnifiedSearchFeature) { + true -> false + false -> true + }, + ) + is SearchFragmentAction.SearchHistoryEngineSelected -> + state.copy( + searchEngineSource = SearchEngineSource.History(action.engine), + showSearchSuggestions = false, + showSearchShortcuts = false, + showClipboardSuggestions = false, + showHistorySuggestions = true, + showBookmarkSuggestions = false, + showSyncedTabsSuggestions = false, + showSessionSuggestions = false, ) is SearchFragmentAction.ShowSearchShortcutEnginePicker -> state.copy(showSearchShortcuts = action.show && state.areShortcutsAvailable) @@ -167,11 +265,14 @@ private fun searchStateReducer(state: SearchFragmentState, action: SearchFragmen showSearchShortcuts = state.url.isEmpty() && state.showSearchShortcutsSetting && action.search.searchEngines.size > 1, - searchEngineSource = if (state.searchEngineSource !is SearchEngineSource.Shortcut) { - action.search.selectedOrDefaultSearchEngine?.let { SearchEngineSource.Default(it) } - ?: SearchEngineSource.None - } else { - state.searchEngineSource + searchEngineSource = when (state.searchEngineSource) { + is SearchEngineSource.Shortcut, is SearchEngineSource.History -> { + state.searchEngineSource + } + else -> { + action.search.selectedOrDefaultSearchEngine?.let { SearchEngineSource.Default(it) } + ?: SearchEngineSource.None + } } ) } diff --git a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt index cf1df415b..2c116f4c5 100644 --- a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt @@ -28,6 +28,8 @@ import mozilla.components.support.ktx.android.content.getColorFromAttr import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.components.Core.Companion.METADATA_HISTORY_SUGGESTION_LIMIT +import org.mozilla.fenix.components.Core.Companion.METADATA_SHORTCUT_SUGGESTION_LIMIT import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings import org.mozilla.fenix.search.SearchEngineSource @@ -53,7 +55,6 @@ class AwesomeBarView( private val defaultSearchActionProvider: SearchActionProvider private val searchEngineSuggestionProvider: SearchEngineSuggestionProvider private val searchSuggestionProviderMap: MutableMap> - private var providersInUse = mutableSetOf() private val loadUrlUseCase = object : SessionUseCases.LoadUrlUseCase { override fun invoke( @@ -213,33 +214,15 @@ class AwesomeBarView( fun updateSuggestionProvidersVisibility( state: SearchProviderState ) { + view.removeAllProviders() + if (state.showSearchShortcuts) { handleDisplayShortcutsProviders() return } - val providersToAdd = getProvidersToAdd(state) - val providersToRemove = getProvidersToRemove(state) - - performProviderListChanges(providersToAdd, providersToRemove) - } - - private fun performProviderListChanges( - providersToAdd: MutableSet, - providersToRemove: MutableSet - ) { - for (provider in providersToAdd) { - if (providersInUse.find { it.id == provider.id } == null) { - providersInUse.add(provider) - view.addProviders(provider) - } - } - - for (provider in providersToRemove) { - if (providersInUse.find { it.id == provider.id } != null) { - providersInUse.remove(provider) - view.removeProviders(provider) - } + for (provider in getProvidersToAdd(state)) { + view.addProviders(provider) } } @@ -249,6 +232,17 @@ class AwesomeBarView( ): MutableSet { val providersToAdd = mutableSetOf() + when (state.searchEngineSource) { + is SearchEngineSource.History -> { + combinedHistoryProvider.setMaxNumberOfSuggestions(METADATA_HISTORY_SUGGESTION_LIMIT) + historyStorageProvider.setMaxNumberOfSuggestions(METADATA_HISTORY_SUGGESTION_LIMIT) + } + else -> { + combinedHistoryProvider.setMaxNumberOfSuggestions(METADATA_SUGGESTION_LIMIT) + historyStorageProvider.setMaxNumberOfSuggestions(METADATA_SUGGESTION_LIMIT) + } + } + if (state.showHistorySuggestions) { if (activity.settings().historyMetadataUIFeature) { providersToAdd.add(combinedHistoryProvider) @@ -269,45 +263,15 @@ class AwesomeBarView( providersToAdd.add(syncedTabsStorageSuggestionProvider) } - if (activity.browsingModeManager.mode == BrowsingMode.Normal) { + if (activity.browsingModeManager.mode == BrowsingMode.Normal && state.showSessionSuggestions) { providersToAdd.add(sessionProvider) } - providersToAdd.add(searchEngineSuggestionProvider) - - return providersToAdd - } - - private fun getProvidersToRemove(state: SearchProviderState): MutableSet { - val providersToRemove = mutableSetOf() - - providersToRemove.add(shortcutsEnginePickerProvider) - - if (!state.showHistorySuggestions) { - if (activity.settings().historyMetadataUIFeature) { - providersToRemove.add(combinedHistoryProvider) - } else { - providersToRemove.add(historyStorageProvider) - } - } - - if (!state.showBookmarkSuggestions) { - providersToRemove.add(bookmarksStorageSuggestionProvider) + if (!activity.settings().showUnifiedSearchFeature) { + providersToAdd.add(searchEngineSuggestionProvider) } - if (!state.showSearchSuggestions) { - providersToRemove.addAll(getSelectedSearchSuggestionProvider(state)) - } - - if (!state.showSyncedTabsSuggestions) { - providersToRemove.add(syncedTabsStorageSuggestionProvider) - } - - if (activity.browsingModeManager.mode == BrowsingMode.Private) { - providersToRemove.add(sessionProvider) - } - - return providersToRemove + return providersToAdd } private fun getSelectedSearchSuggestionProvider(state: SearchProviderState): List { @@ -319,14 +283,12 @@ class AwesomeBarView( is SearchEngineSource.Shortcut -> getSuggestionProviderForEngine( state.searchEngineSource.searchEngine ) + is SearchEngineSource.History -> emptyList() is SearchEngineSource.None -> emptyList() } } private fun handleDisplayShortcutsProviders() { - view.removeAllProviders() - providersInUse.clear() - providersInUse.add(shortcutsEnginePickerProvider) view.addProviders(shortcutsEnginePickerProvider) } @@ -355,7 +317,11 @@ class AwesomeBarView( engine, shortcutSearchUseCase, components.core.client, - limit = 3, + limit = if (activity.settings().showUnifiedSearchFeature) { + METADATA_SHORTCUT_SUGGESTION_LIMIT + } else { + METADATA_SUGGESTION_LIMIT + }, mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS, icon = searchBitmap, engine = engineForSpeculativeConnects, @@ -375,11 +341,12 @@ class AwesomeBarView( val showBookmarkSuggestions: Boolean, val showSearchSuggestions: Boolean, val showSyncedTabsSuggestions: Boolean, + val showSessionSuggestions: Boolean, val searchEngineSource: SearchEngineSource ) companion object { - // Maximum number of suggestions returned from the history metadata storage. + // Maximum number of suggestions returned. const val METADATA_SUGGESTION_LIMIT = 3 } } @@ -390,5 +357,6 @@ fun SearchFragmentState.toSearchProviderState() = AwesomeBarView.SearchProviderS showBookmarkSuggestions, showSearchSuggestions, showSyncedTabsSuggestions, + showSessionSuggestions, searchEngineSource ) diff --git a/app/src/main/res/drawable/ic_history_search.xml b/app/src/main/res/drawable/ic_history_search.xml new file mode 100644 index 000000000..5896fb27e --- /dev/null +++ b/app/src/main/res/drawable/ic_history_search.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/layout/search_selector.xml b/app/src/main/res/layout/search_selector.xml index 9101fce4f..f250b4863 100644 --- a/app/src/main/res/layout/search_selector.xml +++ b/app/src/main/res/layout/search_selector.xml @@ -14,7 +14,7 @@ android:layout_height="28dp" android:layout_marginTop="14dp" android:layout_marginHorizontal="8dp" - app:cardBackgroundColor="?attr/layer2" + app:cardBackgroundColor="@color/photonWhite" app:cardCornerRadius="4dp" app:cardElevation="0dp"> @@ -43,6 +43,7 @@ android:layout_height="6dp" android:importantForAccessibility="no" app:srcCompat="@drawable/ic_chevron_down_6" + app:tint="@color/photonBlack" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/app/src/main/res/values/static_strings.xml b/app/src/main/res/values/static_strings.xml index d1c0ad2ae..79e50b07b 100644 --- a/app/src/main/res/values/static_strings.xml +++ b/app/src/main/res/values/static_strings.xml @@ -44,7 +44,7 @@ Enable Task Continuity - Enable Unified Search + Enable Unified Search (requires restart) Make inactive diff --git a/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt index bd633ba8d..f5243d641 100644 --- a/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt @@ -43,6 +43,7 @@ import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.GleanMetrics.SearchShortcuts import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R +import org.mozilla.fenix.components.Core import org.mozilla.fenix.components.metrics.MetricsUtils import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.search.SearchDialogFragmentDirections.Companion.actionGlobalAddonsManagementFragment @@ -76,6 +77,7 @@ class SearchDialogControllerTest { ) every { store.state.tabId } returns "test-tab-id" every { store.state.searchEngineSource.searchEngine } returns searchEngine + every { searchEngine.type } returns SearchEngine.Type.BUNDLED every { navController.currentDestination } returns mockk { every { id } returns R.id.searchDialogFragment } @@ -139,6 +141,31 @@ class SearchDialogControllerTest { } } + @Test + fun `WHEN the search engine is added by the application THEN do not load URL`() { + every { searchEngine.type } returns SearchEngine.Type.APPLICATION + + val searchTerm = "Firefox" + var dismissDialogInvoked = false + + createController( + dismissDialog = { + dismissDialogInvoked = true + } + ).handleUrlCommitted(searchTerm) + + verify(exactly = 0) { + activity.openToBrowserAndLoad( + searchTermOrURL = any(), + newTab = any(), + from = any(), + engine = any() + ) + } + + assertFalse(dismissDialogInvoked) + } + @Test fun handleCrashesUrlCommitted() { val url = "about:crashes" @@ -319,7 +346,7 @@ class SearchDialogControllerTest { ).handleSearchShortcutEngineSelected(searchEngine) assertTrue(focusToolbarInvoked) - verify { store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine)) } + verify { store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine, settings)) } assertTrue(SearchShortcuts.selected.testHasValue()) val recordedEvents = SearchShortcuts.selected.testGetValue() @@ -330,6 +357,31 @@ class SearchDialogControllerTest { assertEquals(searchEngine.name, eventExtra["engine"]) } + @Test + fun `WHEN history search engine is selected THEN dispatch correct action`() { + val searchEngine: SearchEngine = mockk(relaxed = true) + every { searchEngine.type } returns SearchEngine.Type.APPLICATION + every { searchEngine.id } returns Core.HISTORY_SEARCH_ENGINE_ID + + var focusToolbarInvoked = false + createController( + focusToolbar = { + focusToolbarInvoked = true + } + ).handleSearchShortcutEngineSelected(searchEngine) + + assertTrue(focusToolbarInvoked) + verify { store.dispatch(SearchFragmentAction.SearchHistoryEngineSelected(searchEngine)) } + + assertTrue(SearchShortcuts.selected.testHasValue()) + val recordedEvents = SearchShortcuts.selected.testGetValue() + assertEquals(1, recordedEvents.size) + val eventExtra = recordedEvents.single().extra + assertNotNull(eventExtra) + assertTrue(eventExtra!!.containsKey("engine")) + assertEquals("application", eventExtra["engine"]) + } + @Test fun handleClickSearchEngineSettings() { val directions: NavDirections = actionGlobalSearchEngineFragment() diff --git a/app/src/test/java/org/mozilla/fenix/search/SearchFragmentStoreTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchFragmentStoreTest.kt index 4e8b433e6..589c07717 100644 --- a/app/src/test/java/org/mozilla/fenix/search/SearchFragmentStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchFragmentStoreTest.kt @@ -23,6 +23,7 @@ import org.junit.Assert.assertNotSame import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.browser.browsingmode.BrowsingMode @@ -68,6 +69,7 @@ class SearchFragmentStoreTest { showHistorySuggestions = false, showBookmarkSuggestions = false, showSyncedTabsSuggestions = false, + showSessionSuggestions = true, tabId = null, pastedText = "pastedText", searchAccessPoint = MetricsUtils.Source.ACTION @@ -126,6 +128,7 @@ class SearchFragmentStoreTest { showHistorySuggestions = false, showBookmarkSuggestions = false, showSyncedTabsSuggestions = false, + showSessionSuggestions = true, tabId = "tabId", pastedText = "", searchAccessPoint = MetricsUtils.Source.SHORTCUT @@ -156,12 +159,22 @@ class SearchFragmentStoreTest { val initialState = emptyDefaultState() val store = SearchFragmentStore(initialState) - store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine)).join() + store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine, settings)).join() assertNotSame(initialState, store.state) assertEquals(SearchEngineSource.Shortcut(searchEngine), store.state.searchEngineSource) assertEquals(false, store.state.showSearchShortcuts) } + @Test + fun `WHEN history engine selected action dispatched THEN update search engine source`() = runTest { + val initialState = emptyDefaultState() + val store = SearchFragmentStore(initialState) + + store.dispatch(SearchFragmentAction.SearchHistoryEngineSelected(searchEngine)).join() + assertNotSame(initialState, store.state) + assertEquals(SearchEngineSource.History(searchEngine), store.state.searchEngineSource) + } + @Test fun showSearchShortcutEnginePicker() = runTest { val initialState = emptyDefaultState() @@ -213,6 +226,7 @@ class SearchFragmentStoreTest { } @Test + @Ignore("Flaky, needs investigation: https://github.com/mozilla-mobile/fenix/issues/25170") fun `Updating SearchFragmentState from SearchState`() = runTest { val store = SearchFragmentStore( emptyDefaultState( @@ -270,6 +284,7 @@ class SearchFragmentStoreTest { } @Test + @Ignore("Flaky, needs investigation: https://github.com/mozilla-mobile/fenix/issues/25170") fun `Updating SearchFragmentState from SearchState - shortcuts disabled`() = runTest { val store = SearchFragmentStore( emptyDefaultState( @@ -347,6 +362,7 @@ class SearchFragmentStoreTest { showHistorySuggestions = false, showBookmarkSuggestions = false, showSyncedTabsSuggestions = false, + showSessionSuggestions = false, searchAccessPoint = MetricsUtils.Source.NONE ) } 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 5c75ea34d..7f5119f8a 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 @@ -57,6 +57,7 @@ class ToolbarViewTest { showHistorySuggestions = false, showBookmarkSuggestions = false, showSyncedTabsSuggestions = false, + showSessionSuggestions = false, searchAccessPoint = MetricsUtils.Source.NONE ) diff --git a/detekt-baseline.xml b/detekt-baseline.xml index d676235bf..3e3eb92ad 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -361,15 +361,6 @@ UndocumentedPublicClass:SearchEngineMenu.kt$SearchEngineMenu$Item UndocumentedPublicClass:SearchEngineMenu.kt$SearchEngineMenu.Item$Delete : Item UndocumentedPublicClass:SearchEngineMenu.kt$SearchEngineMenu.Item$Edit : Item - UndocumentedPublicClass:SearchFragmentStore.kt$SearchEngineSource$Default : SearchEngineSource - UndocumentedPublicClass:SearchFragmentStore.kt$SearchEngineSource$None : SearchEngineSource - UndocumentedPublicClass:SearchFragmentStore.kt$SearchEngineSource$Shortcut : SearchEngineSource - UndocumentedPublicClass:SearchFragmentStore.kt$SearchFragmentAction$AllowSearchSuggestionsInPrivateModePrompt : SearchFragmentAction - UndocumentedPublicClass:SearchFragmentStore.kt$SearchFragmentAction$SearchShortcutEngineSelected : SearchFragmentAction - UndocumentedPublicClass:SearchFragmentStore.kt$SearchFragmentAction$SetShowSearchSuggestions : SearchFragmentAction - UndocumentedPublicClass:SearchFragmentStore.kt$SearchFragmentAction$ShowSearchShortcutEnginePicker : SearchFragmentAction - UndocumentedPublicClass:SearchFragmentStore.kt$SearchFragmentAction$UpdateClipboardHasUrl : SearchFragmentAction - UndocumentedPublicClass:SearchFragmentStore.kt$SearchFragmentAction$UpdateQuery : SearchFragmentAction UndocumentedPublicClass:SearchStringValidator.kt$SearchStringValidator UndocumentedPublicClass:SearchStringValidator.kt$SearchStringValidator$Result UndocumentedPublicClass:SearchWidgetProvider.kt$SearchWidgetProvider : AppWidgetProvider