[fenix] For https://github.com/mozilla-mobile/fenix/issues/4399: Create LibState Controller for Search (https://github.com/mozilla-mobile/fenix/pull/4673)
* For https://github.com/mozilla-mobile/fenix/issues/4399: Create LibState Controller for Search * fix code format * add unit tests for DefaultSearchController * add more test * fix unit testspull/600/head
parent
2634b6dee4
commit
0e376bb266
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.Context
|
||||
import androidx.navigation.NavController
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.session.Session
|
||||
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.ext.components
|
||||
import org.mozilla.fenix.ext.metrics
|
||||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.ext.searchEngineManager
|
||||
|
||||
/**
|
||||
* An interface that handles the view manipulation of the Search, triggered by the Interactor
|
||||
*/
|
||||
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 handleTurnOnStartedTyping()
|
||||
fun handleExistingSessionSelected(session: Session)
|
||||
}
|
||||
|
||||
class DefaultSearchController(
|
||||
private val context: Context,
|
||||
private val store: SearchStore,
|
||||
private val navController: NavController
|
||||
) : SearchController {
|
||||
|
||||
data class UserTypingCheck(var ranOnTextChanged: Boolean, var userHasTyped: Boolean)
|
||||
|
||||
internal val userTypingCheck = UserTypingCheck(false, !store.state.showShortcutEnginePicker)
|
||||
|
||||
override fun handleUrlCommitted(url: String) {
|
||||
if (url.isNotBlank()) {
|
||||
(context as HomeActivity).openToBrowserAndLoad(
|
||||
searchTermOrURL = url,
|
||||
newTab = store.state.session == null,
|
||||
from = BrowserDirection.FromSearch,
|
||||
engine = store.state.searchEngineSource.searchEngine
|
||||
)
|
||||
|
||||
val event = if (url.isUrl()) {
|
||||
Event.EnteredUrl(false)
|
||||
} else {
|
||||
createSearchEvent(store.state.searchEngineSource.searchEngine, false)
|
||||
}
|
||||
|
||||
context.metrics.track(event)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleEditingCancelled() {
|
||||
navController.navigateUp()
|
||||
}
|
||||
|
||||
override fun handleTextChanged(text: String) {
|
||||
store.dispatch(SearchAction.UpdateQuery(text))
|
||||
|
||||
if (userTypingCheck.ranOnTextChanged && !userTypingCheck.userHasTyped) {
|
||||
store.dispatch(SearchAction.ShowSearchShortcutEnginePicker(false))
|
||||
handleTurnOnStartedTyping()
|
||||
}
|
||||
|
||||
userTypingCheck.ranOnTextChanged = true
|
||||
}
|
||||
|
||||
override fun handleUrlTapped(url: String) {
|
||||
(context as HomeActivity).openToBrowserAndLoad(
|
||||
searchTermOrURL = url,
|
||||
newTab = store.state.session == null,
|
||||
from = BrowserDirection.FromSearch
|
||||
)
|
||||
|
||||
context.metrics.track(Event.EnteredUrl(false))
|
||||
}
|
||||
|
||||
override fun handleSearchTermsTapped(searchTerms: String) {
|
||||
(context as HomeActivity).openToBrowserAndLoad(
|
||||
searchTermOrURL = searchTerms,
|
||||
newTab = store.state.session == null,
|
||||
from = BrowserDirection.FromSearch,
|
||||
engine = store.state.searchEngineSource.searchEngine,
|
||||
forceSearch = true
|
||||
)
|
||||
|
||||
val event = createSearchEvent(store.state.searchEngineSource.searchEngine, true)
|
||||
context.metrics.track(event)
|
||||
}
|
||||
|
||||
override fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) {
|
||||
store.dispatch(SearchAction.SearchShortcutEngineSelected(searchEngine))
|
||||
context.metrics.track(Event.SearchShortcutSelected(searchEngine.name))
|
||||
}
|
||||
|
||||
override fun handleClickSearchEngineSettings() {
|
||||
val directions = SearchFragmentDirections.actionSearchFragmentToSearchEngineFragment()
|
||||
navController.navigate(directions)
|
||||
}
|
||||
|
||||
override fun handleTurnOnStartedTyping() {
|
||||
userTypingCheck.ranOnTextChanged = true
|
||||
userTypingCheck.userHasTyped = true
|
||||
}
|
||||
|
||||
override fun handleExistingSessionSelected(session: Session) {
|
||||
val directions = SearchFragmentDirections.actionSearchFragmentToBrowserFragment(null)
|
||||
navController.nav(R.id.searchFragment, directions)
|
||||
context.components.core.sessionManager.select(session)
|
||||
}
|
||||
|
||||
private fun createSearchEvent(
|
||||
engine: SearchEngine,
|
||||
isSuggestion: Boolean
|
||||
): Event.PerformedSearch {
|
||||
val isShortcut = engine != context.searchEngineManager.defaultSearchEngine
|
||||
|
||||
val engineSource =
|
||||
if (isShortcut) Event.PerformedSearch.EngineSource.Shortcut(engine)
|
||||
else Event.PerformedSearch.EngineSource.Default(engine)
|
||||
|
||||
val source =
|
||||
if (isSuggestion) Event.PerformedSearch.EventSource.Suggestion(engineSource)
|
||||
else Event.PerformedSearch.EventSource.Action(engineSource)
|
||||
|
||||
return Event.PerformedSearch(source)
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
/* 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.navigation.NavController
|
||||
import androidx.navigation.NavDirections
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import org.junit.Assert.assertTrue
|
||||
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.ext.components
|
||||
import org.mozilla.fenix.ext.metrics
|
||||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.ext.searchEngineManager
|
||||
|
||||
class DefaultSearchControllerTest {
|
||||
|
||||
private val context: HomeActivity = mockk(relaxed = true)
|
||||
private val store: SearchStore = mockk(relaxed = true)
|
||||
private val navController: NavController = mockk(relaxed = true)
|
||||
private val defaultSearchEngine: SearchEngine? = mockk(relaxed = true)
|
||||
private val session: Session? = mockk(relaxed = true)
|
||||
private val searchEngine: SearchEngine = mockk(relaxed = true)
|
||||
private val metrics: MetricController = mockk(relaxed = true)
|
||||
private val sessionManager: SessionManager = mockk(relaxed = true)
|
||||
|
||||
private lateinit var controller: DefaultSearchController
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
every { store.state.showShortcutEnginePicker } returns false
|
||||
every { context.searchEngineManager.defaultSearchEngine } returns defaultSearchEngine
|
||||
every { store.state.session } returns session
|
||||
every { store.state.searchEngineSource.searchEngine } returns searchEngine
|
||||
every { context.metrics } returns metrics
|
||||
every { context.components.core.sessionManager } returns sessionManager
|
||||
|
||||
controller = DefaultSearchController(
|
||||
context = context,
|
||||
store = store,
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleUrlCommitted() {
|
||||
val url = "https://www.google.com/"
|
||||
|
||||
controller.handleUrlCommitted(url)
|
||||
|
||||
verify { context.openToBrowserAndLoad(
|
||||
searchTermOrURL = url,
|
||||
newTab = session == null,
|
||||
from = BrowserDirection.FromSearch,
|
||||
engine = searchEngine
|
||||
) }
|
||||
verify { metrics.track(Event.EnteredUrl(false)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleEditingCancelled() {
|
||||
controller.handleEditingCancelled()
|
||||
|
||||
verify { navController.navigateUp() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleTextChanged() {
|
||||
val text = "fenix"
|
||||
|
||||
controller.handleTextChanged(text)
|
||||
|
||||
verify { store.dispatch(SearchAction.UpdateQuery(text)) }
|
||||
verify(inverse = true) {
|
||||
store.dispatch(SearchAction.ShowSearchShortcutEnginePicker(false))
|
||||
}
|
||||
assertTrue(controller.userTypingCheck.ranOnTextChanged)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleUrlTapped() {
|
||||
val url = "https://www.google.com/"
|
||||
|
||||
controller.handleUrlTapped(url)
|
||||
|
||||
verify { context.openToBrowserAndLoad(
|
||||
searchTermOrURL = url,
|
||||
newTab = session == null,
|
||||
from = BrowserDirection.FromSearch
|
||||
) }
|
||||
verify { metrics.track(Event.EnteredUrl(false)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleSearchTermsTapped() {
|
||||
val searchTerms = "fenix"
|
||||
|
||||
controller.handleSearchTermsTapped(searchTerms)
|
||||
|
||||
verify { context.openToBrowserAndLoad(
|
||||
searchTermOrURL = searchTerms,
|
||||
newTab = session == null,
|
||||
from = BrowserDirection.FromSearch,
|
||||
engine = searchEngine,
|
||||
forceSearch = true
|
||||
) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleSearchShortcutEngineSelected() {
|
||||
val searchEngine: SearchEngine = mockk(relaxed = true)
|
||||
|
||||
controller.handleSearchShortcutEngineSelected(searchEngine)
|
||||
|
||||
verify { store.dispatch(SearchAction.SearchShortcutEngineSelected(searchEngine)) }
|
||||
verify { metrics.track(Event.SearchShortcutSelected(searchEngine.name)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleClickSearchEngineSettings() {
|
||||
val directions: NavDirections = SearchFragmentDirections.actionSearchFragmentToSearchEngineFragment()
|
||||
|
||||
controller.handleClickSearchEngineSettings()
|
||||
|
||||
verify { navController.navigate(directions) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleTurnOnStartedTyping() {
|
||||
controller.handleTurnOnStartedTyping()
|
||||
|
||||
assertTrue(controller.userTypingCheck.ranOnTextChanged)
|
||||
assertTrue(controller.userTypingCheck.userHasTyped)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleExistingSessionSelected() {
|
||||
val session: Session = mockk(relaxed = true)
|
||||
val directions = SearchFragmentDirections.actionSearchFragmentToBrowserFragment(null)
|
||||
|
||||
controller.handleExistingSessionSelected(session)
|
||||
|
||||
verify { navController.nav(R.id.searchFragment, directions) }
|
||||
verify { sessionManager.select(session) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue