Commit 14a6a7ed authored by MozLando's avatar MozLando
Browse files

Merge #6068



6068: Closes #4562: Migrate feature-awesomebar to use browser-state. r=csadilek a=pocmo
Co-authored-by: default avatarSebastian Kaspari <s.kaspari@gmail.com>
parents 6d614c68 01d19bc9
......@@ -27,6 +27,7 @@ dependencies {
implementation Dependencies.androidx_lifecycle_extensions
implementation project(':concept-engine')
implementation project(":browser-session")
implementation project(':feature-tabs')
implementation project(':service-firefox-accounts')
implementation project(':support-webextensions')
......
......@@ -28,8 +28,8 @@ dependencies {
implementation project(':concept-toolbar')
implementation project(':concept-storage')
implementation project(':browser-session')
implementation project(':browser-search')
implementation project(':browser-state')
implementation project(':browser-icons')
implementation project(':feature-tabs')
......
......@@ -10,7 +10,7 @@ import android.view.View
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.search.SearchEngineManager
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.awesomebar.AwesomeBar
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineView
......@@ -54,10 +54,10 @@ class AwesomeBarFeature(
*/
fun addSessionProvider(
resources: Resources,
sessionManager: SessionManager,
store: BrowserStore,
selectTabUseCase: TabsUseCases.SelectTabUseCase
): AwesomeBarFeature {
val provider = SessionSuggestionProvider(resources, sessionManager, selectTabUseCase, icons)
val provider = SessionSuggestionProvider(resources, store, selectTabUseCase, icons)
awesomeBar.addProviders(provider)
return this
}
......
......@@ -9,8 +9,9 @@ import kotlinx.coroutines.Deferred
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.browser.icons.Icon
import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.awesomebar.AwesomeBar
import mozilla.components.feature.awesomebar.R
import mozilla.components.feature.tabs.TabsUseCases
......@@ -22,7 +23,7 @@ import java.util.UUID
*/
class SessionSuggestionProvider(
private val resources: Resources,
private val sessionManager: SessionManager,
private val store: BrowserStore,
private val selectTabUseCase: TabsUseCases.SelectTabUseCase,
private val icons: BrowserIcons? = null,
private val excludeSelectedSession: Boolean = false
......@@ -34,20 +35,26 @@ class SessionSuggestionProvider(
return emptyList()
}
val state = store.state
val tabs = state.tabs
val suggestions = mutableListOf<AwesomeBar.Suggestion>()
val iconRequests: List<Deferred<Icon>?> = sessionManager.sessions.map { icons?.loadIcon(IconRequest(it.url)) }
val iconRequests: List<Deferred<Icon>?> = tabs.map { icons?.loadIcon(IconRequest(it.content.url)) }
sessionManager.sessions.zip(iconRequests) { result, icon ->
if (result.contains(text) && !result.private && shouldIncludeSelectedSession(result)
tabs.zip(iconRequests) { result, icon ->
if (
result.contains(text) &&
!result.content.private &&
shouldIncludeSelectedTab(state, result)
) {
suggestions.add(
AwesomeBar.Suggestion(
provider = this,
id = result.id,
title = result.title,
title = result.content.title,
description = resources.getString(R.string.switch_to_tab_description),
icon = icon?.await()?.bitmap,
onSuggestionClicked = { selectTabUseCase(result) }
onSuggestionClicked = { selectTabUseCase(result.id) }
)
)
}
......@@ -55,16 +62,14 @@ class SessionSuggestionProvider(
return suggestions
}
private fun Session.contains(text: String) =
(url.contains(text, ignoreCase = true) || title.contains(text, ignoreCase = true))
private fun TabSessionState.contains(text: String) =
(content.url.contains(text, ignoreCase = true) || content.title.contains(text, ignoreCase = true))
private fun shouldIncludeSelectedSession(session: Session): Boolean {
private fun shouldIncludeSelectedTab(state: BrowserState, tab: TabSessionState): Boolean {
return if (excludeSelectedSession) {
!isSelectedSession(session)
tab.id != state.selectedTabId
} else {
true
}
}
private fun isSelectedSession(session: Session) = sessionManager.selectedSession?.equals(session) ?: false
}
......@@ -10,11 +10,8 @@ import android.content.Context
import android.graphics.Bitmap
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.awesomebar.AwesomeBar
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.support.test.any
import mozilla.components.support.test.eq
......@@ -28,9 +25,7 @@ import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
......@@ -162,13 +157,7 @@ class ClipboardSuggestionProviderTest {
)
)
val selectedEngineSession: EngineSession = mock()
val selectedSession: Session = mock()
val sessionManager: SessionManager = mock()
`when`(sessionManager.selectedSession).thenReturn(selectedSession)
`when`(sessionManager.getOrCreateEngineSession(selectedSession)).thenReturn(selectedEngineSession)
val useCase = spy(SessionUseCases(sessionManager).loadUrl)
val useCase: SessionUseCases.LoadUrlUseCase = mock()
val provider = ClipboardSuggestionProvider(testContext, useCase, requireEmptyText = false)
......@@ -178,12 +167,12 @@ class ClipboardSuggestionProviderTest {
val suggestion = suggestions.first()
verify(useCase, never()).invoke(any(), any(), any())
verify(useCase, never()).invoke(any(), any())
assertNotNull(suggestion.onSuggestionClicked)
suggestion.onSuggestionClicked!!.invoke()
verify(useCase).invoke(eq("https://www.mozilla.org"), any(), any())
verify(useCase).invoke(eq("https://www.mozilla.org"), any())
}
@Test
......
......@@ -9,8 +9,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.search.SearchEngineManager
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.Engine
import mozilla.components.feature.awesomebar.R
import mozilla.components.feature.search.SearchUseCases
......@@ -23,15 +21,13 @@ import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
......@@ -40,7 +36,6 @@ private const val GOOGLE_MOCK_RESPONSE_WITH_DUPLICATES = "[\"firefox\",[\"firefo
@RunWith(AndroidJUnit4::class)
class SearchSuggestionProviderTest {
@Test
fun `Provider returns suggestion with chips based on search engine suggestion`() {
runBlocking {
......@@ -57,12 +52,7 @@ class SearchSuggestionProviderTest {
val searchEngineManager: SearchEngineManager = mock()
doReturn(searchEngine).`when`(searchEngineManager).getDefaultSearchEngineAsync(any(), any())
val useCase = spy(SearchUseCases(
testContext,
searchEngineManager,
SessionManager(mock()).apply { add(Session("https://www.mozilla.org")) }
).defaultSearch)
doNothing().`when`(useCase).invoke(anyString(), any<Session>(), any<SearchEngine>())
val useCase: SearchUseCases.SearchUseCase = mock()
val provider =
SearchSuggestionProvider(searchEngine, useCase, HttpURLConnectionClient())
......@@ -86,11 +76,11 @@ class SearchSuggestionProviderTest {
assertEquals("firefox nightly", suggestion.chips[9].title)
assertEquals("firefox clear cache", suggestion.chips[10].title)
verify(useCase, never()).invoke(anyString(), any<Session>(), any<SearchEngine>())
verify(useCase, never()).invoke(anyString(), any())
suggestion.onChipClicked!!.invoke(suggestion.chips[6])
verify(useCase).invoke(eq("firefox focus"), any<Session>(), any<SearchEngine>())
verify(useCase).invoke(eq("firefox focus"), any())
} finally {
server.shutdown()
}
......@@ -113,12 +103,7 @@ class SearchSuggestionProviderTest {
val searchEngineManager: SearchEngineManager = mock()
doReturn(searchEngine).`when`(searchEngineManager).getDefaultSearchEngineAsync(any(), any())
val useCase = spy(SearchUseCases(
testContext,
searchEngineManager,
SessionManager(mock()).apply { add(Session("https://www.mozilla.org")) }
).defaultSearch)
doNothing().`when`(useCase).invoke(anyString(), any<Session>(), any<SearchEngine>())
val useCase: SearchUseCases.SearchUseCase = mock()
val provider = SearchSuggestionProvider(
searchEngine,
......@@ -146,11 +131,11 @@ class SearchSuggestionProviderTest {
assertEquals("firefox nightly", suggestions[9].title)
assertEquals("firefox clear cache", suggestions[10].title)
verify(useCase, never()).invoke(anyString(), any<Session>(), any<SearchEngine>())
verify(useCase, never()).invoke(anyString(), any())
suggestions[6].onSuggestionClicked!!.invoke()
verify(useCase).invoke(eq("firefox focus"), any<Session>(), any<SearchEngine>())
verify(useCase).invoke(eq("firefox focus"), any())
} finally {
server.shutdown()
}
......@@ -173,12 +158,7 @@ class SearchSuggestionProviderTest {
val searchEngineManager: SearchEngineManager = mock()
doReturn(searchEngine).`when`(searchEngineManager).getDefaultSearchEngineAsync(any(), any())
val useCase = spy(SearchUseCases(
testContext,
searchEngineManager,
SessionManager(mock()).apply { add(Session("https://www.mozilla.org")) }
).defaultSearch)
doNothing().`when`(useCase).invoke(anyString(), any<Session>(), any<SearchEngine>())
val useCase: SearchUseCases.SearchUseCase = mock()
val provider = SearchSuggestionProvider(
searchEngine,
......@@ -222,12 +202,7 @@ class SearchSuggestionProviderTest {
val searchEngineManager: SearchEngineManager = mock()
doReturn(searchEngine).`when`(searchEngineManager).getDefaultSearchEngineAsync(any(), any())
val useCase = spy(SearchUseCases(
testContext,
searchEngineManager,
SessionManager(mock()).apply { add(Session("https://www.mozilla.org")) }
).defaultSearch)
doNothing().`when`(useCase).invoke(anyString(), any<Session>(), any<SearchEngine>())
val useCase: SearchUseCases.SearchUseCase = mock()
val provider =
SearchSuggestionProvider(searchEngine, useCase, HttpURLConnectionClient(), limit = 5)
......@@ -353,12 +328,7 @@ class SearchSuggestionProviderTest {
val searchEngineManager: SearchEngineManager = mock()
doReturn(searchEngine).`when`(searchEngineManager).getDefaultSearchEngineAsync(any(), any())
val useCase = spy(SearchUseCases(
testContext,
searchEngineManager,
SessionManager(mock()).apply { add(Session("https://www.mozilla.org")) }
).defaultSearch)
doNothing().`when`(useCase).invoke(anyString(), any<Session>(), any<SearchEngine>())
val useCase: SearchUseCases.SearchUseCase = mock()
val provider =
SearchSuggestionProvider(searchEngine, useCase, HttpURLConnectionClient())
......@@ -387,12 +357,7 @@ class SearchSuggestionProviderTest {
val searchEngineManager: SearchEngineManager = mock()
doReturn(searchEngine).`when`(searchEngineManager).getDefaultSearchEngineAsync(any(), any())
val useCase = spy(SearchUseCases(
testContext,
searchEngineManager,
SessionManager(mock()).apply { add(Session("https://www.mozilla.org")) }
).defaultSearch)
doNothing().`when`(useCase).invoke(anyString(), any<Session>(), any<SearchEngine>())
val useCase: SearchUseCases.SearchUseCase = mock()
val provider =
SearchSuggestionProvider(searchEngine, useCase, HttpURLConnectionClient())
......@@ -426,12 +391,7 @@ class SearchSuggestionProviderTest {
val searchEngineManager: SearchEngineManager = mock()
doReturn(searchEngine).`when`(searchEngineManager).getDefaultSearchEngineAsync(any(), any())
val useCase = spy(SearchUseCases(
testContext,
searchEngineManager,
SessionManager(mock()).apply { add(Session("https://www.mozilla.org")) }
).defaultSearch)
doNothing().`when`(useCase).invoke(anyString(), any<Session>(), any<SearchEngine>())
val useCase: SearchUseCases.SearchUseCase = mock()
val provider = SearchSuggestionProvider(
searchEngine,
......@@ -476,12 +436,7 @@ class SearchSuggestionProviderTest {
val searchEngineManager: SearchEngineManager = mock()
doReturn(searchEngine).`when`(searchEngineManager).getDefaultSearchEngineAsync(any(), any())
val useCase = spy(SearchUseCases(
testContext,
searchEngineManager,
SessionManager(mock()).apply { add(Session("https://www.mozilla.org")) }
).defaultSearch)
doNothing().`when`(useCase).invoke(anyString(), any<Session>(), any<SearchEngine>())
val useCase: SearchUseCases.SearchUseCase = mock()
val provider = SearchSuggestionProvider(
searchEngine,
......
......@@ -6,8 +6,10 @@ package mozilla.components.feature.awesomebar.provider
import android.content.res.Resources
import kotlinx.coroutines.runBlocking
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.support.test.mock
import org.junit.Assert.assertEquals
......@@ -32,42 +34,43 @@ class SessionSuggestionProviderTest {
@Test
fun `Provider returns Sessions with matching URLs`() = runBlocking {
val sessionManager = SessionManager(mock())
val resources: Resources = mock()
val session1 = Session("https://www.mozilla.org")
val session2 = Session("https://example.com")
val session3 = Session("https://firefox.com")
val session4 = Session("https://example.org/")
val store = BrowserStore()
val tab1 = createTab("https://www.mozilla.org")
val tab2 = createTab("https://example.com")
val tab3 = createTab("https://firefox.com")
val tab4 = createTab("https://example.org/")
val resources: Resources = mock()
`when`(resources.getString(anyInt())).thenReturn("Switch to tab")
val provider = SessionSuggestionProvider(resources, sessionManager, mock())
val provider = SessionSuggestionProvider(resources, store, mock())
run {
val suggestions = provider.onInputChanged("Example")
assertTrue(suggestions.isEmpty())
}
sessionManager.add(session1)
sessionManager.add(session2)
sessionManager.add(session3)
store.dispatch(TabListAction.AddTabAction(tab1)).join()
store.dispatch(TabListAction.AddTabAction(tab2)).join()
store.dispatch(TabListAction.AddTabAction(tab3)).join()
run {
val suggestions = provider.onInputChanged("Example")
assertEquals(1, suggestions.size)
assertEquals(session2.id, suggestions[0].id)
assertEquals(tab2.id, suggestions[0].id)
assertEquals("Switch to tab", suggestions[0].description)
}
sessionManager.add(session4)
store.dispatch(TabListAction.AddTabAction(tab4)).join()
run {
val suggestions = provider.onInputChanged("Example")
assertEquals(2, suggestions.size)
assertEquals(session2.id, suggestions[0].id)
assertEquals(session4.id, suggestions[1].id)
assertEquals(tab2.id, suggestions[0].id)
assertEquals(tab4.id, suggestions[1].id)
assertEquals("Switch to tab", suggestions[0].description)
assertEquals("Switch to tab", suggestions[1].description)
}
......@@ -75,28 +78,24 @@ class SessionSuggestionProviderTest {
@Test
fun `Provider returns Sessions with matching titles`() = runBlocking {
val sessionManager = SessionManager(mock())
val resources: Resources = mock()
val session1 = Session("https://allizom.org").apply {
title = "Internet for people, not profit — Mozilla" }
val session2 = Session("https://getpocket.com").apply {
title = "Pocket: My List" }
val session3 = Session("https://firefox.com").apply {
title = "Download Firefox — Free Web Browser" }
val tab1 = createTab("https://allizom.org", title = "Internet for people, not profit — Mozilla")
val tab2 = createTab("https://getpocket.com", title = "Pocket: My List")
val tab3 = createTab("https://firefox.com", title = "Download Firefox — Free Web Browser")
`when`(resources.getString(anyInt())).thenReturn("Switch to tab")
val store = BrowserStore(BrowserState(
tabs = listOf(tab1, tab2, tab3)
))
sessionManager.add(session1)
sessionManager.add(session2)
sessionManager.add(session3)
val resources: Resources = mock()
`when`(resources.getString(anyInt())).thenReturn("Switch to tab")
val provider = SessionSuggestionProvider(resources, sessionManager, mock())
val provider = SessionSuggestionProvider(resources, store, mock())
run {
val suggestions = provider.onInputChanged("Browser")
assertEquals(1, suggestions.size)
assertEquals(suggestions.first().id, session3.id)
assertEquals(suggestions.first().id, tab3.id)
assertEquals("Switch to tab", suggestions.first().description)
assertEquals("Download Firefox — Free Web Browser", suggestions[0].title)
}
......@@ -105,7 +104,7 @@ class SessionSuggestionProviderTest {
val suggestions = provider.onInputChanged("Mozilla")
assertEquals(1, suggestions.size)
assertEquals(session1.id, suggestions.first().id)
assertEquals(tab1.id, suggestions.first().id)
assertEquals("Switch to tab", suggestions.first().description)
assertEquals("Internet for people, not profit — Mozilla", suggestions[0].title)
}
......@@ -113,19 +112,20 @@ class SessionSuggestionProviderTest {
@Test
fun `Provider only returns non-private Sessions`() = runBlocking {
val sessionManager = SessionManager(mock())
val session = Session("https://www.mozilla.org")
val privateSession1 = Session("https://mozilla.org/firefox", true)
val privateSession2 = Session("https://mozilla.org/projects", true)
val tab = createTab("https://www.mozilla.org")
val privateTab1 = createTab("https://mozilla.org/firefox", private = true)
val privateTab2 = createTab("https://mozilla.org/projects", private = true)
val store = BrowserStore(BrowserState(
tabs = listOf(tab, privateTab1, privateTab2)
))
val resources: Resources = mock()
`when`(resources.getString(anyInt())).thenReturn("Switch to tab")
sessionManager.add(privateSession1)
sessionManager.add(session)
sessionManager.add(privateSession2)
val useCase: TabsUseCases.SelectTabUseCase = mock()
val provider = SessionSuggestionProvider(resources, sessionManager, useCase)
val provider = SessionSuggestionProvider(resources, store, useCase)
val suggestions = provider.onInputChanged("mozilla")
assertEquals(1, suggestions.size)
......@@ -133,25 +133,28 @@ class SessionSuggestionProviderTest {
@Test
fun `Clicking suggestion invokes SelectTabUseCase`() = runBlocking {
val sessionManager = SessionManager(mock())
val session = Session("https://www.mozilla.org")
val resources: Resources = mock()
`when`(resources.getString(anyInt())).thenReturn("Switch to tab")
sessionManager.add(session)
val tab = createTab("https://www.mozilla.org")
val store = BrowserStore(BrowserState(
tabs = listOf(tab)
))
val useCase: TabsUseCases.SelectTabUseCase = mock()
val provider = SessionSuggestionProvider(resources, sessionManager, useCase)
val provider = SessionSuggestionProvider(resources, store, useCase)
val suggestions = provider.onInputChanged("mozilla")
assertEquals(1, suggestions.size)
val suggestion = suggestions[0]
verify(useCase, never()).invoke(session)
verify(useCase, never()).invoke(tab.id)
suggestion.onSuggestionClicked!!.invoke()
verify(useCase).invoke(session)
verify(useCase).invoke(tab.id)
}
@Test
......@@ -166,45 +169,47 @@ class SessionSuggestionProviderTest {
@Test
fun `When excludeSelectedSession is true provider should not include the selected session`() = runBlocking {
val sessionManager = SessionManager(mock())
val session = Session("https://wikipedia.org")
val selectedSession = Session("https://www.mozilla.org")
val store = BrowserStore(BrowserState(
tabs = listOf(
createTab(id = "a", url = "https://wikipedia.org"),
createTab(id = "b", url = "https://www.mozilla.org")
),
selectedTabId = "b"
))
val resources: Resources = mock()
`when`(resources.getString(anyInt())).thenReturn("Switch to tab")
sessionManager.add(selectedSession)
sessionManager.add(session)
sessionManager.select(selectedSession)
val useCase: TabsUseCases.SelectTabUseCase = mock()