/* 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.searchdialog 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.search.AlertDialogBuilder import org.mozilla.fenix.search.SearchFragmentAction import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.utils.Settings @ExperimentalCoroutinesApi class SearchDialogControllerTest { @MockK(relaxed = true) private lateinit var activity: HomeActivity @MockK(relaxed = true) private lateinit var store: SearchDialogFragmentStore @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 @MockK(relaxed = true) private lateinit var dismissDialog: () -> Unit private lateinit var controller: SearchDialogController @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.searchDialogFragment } every { MetricsUtils.createSearchEvent(searchEngine, activity, any()) } returns null controller = SearchDialogController( activity = activity, sessionManager = sessionManager, store = store, navController = navController, settings = settings, metrics = metrics, dismissDialog = dismissDialog, 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.FromSearchDialog, engine = searchEngine ) } verify { metrics.track(Event.EnteredUrl(false)) } } @Test fun handleBlankUrlCommitted() { val url = "" controller.handleUrlCommitted(url) verify { dismissDialog() } } @Test fun handleSearchCommitted() { val searchTerm = "Firefox" controller.handleUrlCommitted(searchTerm) verify { activity.openToBrowserAndLoad( searchTermOrURL = searchTerm, newTab = false, from = BrowserDirection.FromSearchDialog, 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 = SearchDialogFragmentDirections.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.FromSearchDialog, 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.FromSearchDialog ) } verify { metrics.track(Event.EnteredUrl(false)) } } @Test fun handleSearchTermsTapped() { val searchTerms = "fenix" controller.handleSearchTermsTapped(searchTerms) verify { activity.openToBrowserAndLoad( searchTermOrURL = searchTerms, newTab = false, from = BrowserDirection.FromSearchDialog, 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 = SearchDialogFragmentDirections.actionGlobalSearchEngineFragment() controller.handleClickSearchEngineSettings() verify { navController.navigate(directions) } } @Test fun handleSearchShortcutsButtonClicked_alreadyOpen() { every { store.state.showSearchShortcuts } returns true controller.handleSearchShortcutsButtonClicked() verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(false)) } } @Test fun handleSearchShortcutsButtonClicked_notYetOpen() { every { store.state.showSearchShortcuts } returns false controller.handleSearchShortcutsButtonClicked() verify { store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(true)) } } @Test fun handleExistingSessionSelected() { val session = mockk() controller.handleExistingSessionSelected(session) verify { sessionManager.select(session) } verify { activity.openToBrowser(from = BrowserDirection.FromSearchDialog) } } @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.FromSearchDialog) } } @Test fun handleExistingSessionSelected_tabId() { val session = mockk() every { sessionManager.findSessionById("tab-id") } returns session controller.handleExistingSessionSelected("tab-id") verify { sessionManager.select(any()) } verify { activity.openToBrowser(from = BrowserDirection.FromSearchDialog) } } @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() } } }