From 1ff64cab6755054e46dd1f21a98dd17ebb00a416 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Tue, 14 Jul 2020 10:32:04 -0700 Subject: [PATCH] Add tests for search (#12437) --- .../fenix/search/SearchFragmentStore.kt | 4 +- .../awesomebar/ShortcutsSuggestionProvider.kt | 22 ++-- .../fenix/search/toolbar/ToolbarView.kt | 14 +-- .../fenix/search/SearchFragmentStoreTest.kt | 41 ++++++ .../ShortcutsSuggestionProviderTest.kt | 119 ++++++++++++++++++ .../fenix/search/toolbar/ToolbarViewTest.kt | 78 ++++++++++++ 6 files changed, 257 insertions(+), 21 deletions(-) create mode 100644 app/src/test/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProviderTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/search/toolbar/ToolbarViewTest.kt 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 7ef4aa16a..7268f45eb 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt @@ -96,9 +96,7 @@ private fun searchStateReducer(state: SearchFragmentState, action: SearchFragmen is SearchFragmentAction.UpdateQuery -> state.copy(query = action.query) is SearchFragmentAction.SelectNewDefaultSearchEngine -> - state.copy( - searchEngineSource = SearchEngineSource.Default(action.engine) - ) + state.copy(searchEngineSource = SearchEngineSource.Default(action.engine)) is SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt -> state.copy(showSearchSuggestionsHint = action.show) is SearchFragmentAction.SetShowSearchSuggestions -> diff --git a/app/src/main/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProvider.kt b/app/src/main/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProvider.kt index a81f8bd82..9bba4db2b 100644 --- a/app/src/main/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProvider.kt +++ b/app/src/main/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProvider.kt @@ -34,16 +34,15 @@ class ShortcutsSuggestionProvider( override suspend fun onInputChanged(text: String): List { val suggestions = mutableListOf() - searchEngineProvider.installedSearchEngines(context).list.forEach { - suggestions.add( - AwesomeBar.Suggestion( - provider = this, - id = it.identifier, - icon = it.icon, - title = it.name, - onSuggestionClicked = { - selectShortcutEngine(it) - }) + searchEngineProvider.installedSearchEngines(context).list.mapTo(suggestions) { + AwesomeBar.Suggestion( + provider = this, + id = it.identifier, + icon = it.icon, + title = it.name, + onSuggestionClicked = { + selectShortcutEngine(it) + } ) } @@ -55,7 +54,8 @@ class ShortcutsSuggestionProvider( title = context.getString(R.string.search_shortcuts_engine_settings), onSuggestionClicked = { selectShortcutEngineSettings() - }) + } + ) ) return suggestions } 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 4eb97f40b..34ee23dc1 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 @@ -22,24 +22,24 @@ import org.mozilla.fenix.theme.ThemeManager /** * Interface for the Toolbar Interactor. This interface is implemented by objects that want - * to respond to user interaction on the [BrowserToolbarView] + * to respond to user interaction on the [ToolbarView] */ interface ToolbarInteractor { /** - * Called when a user hits the return key while [BrowserToolbarView] has focus. - * @param url the text inside the [BrowserToolbarView] when committed + * Called when a user hits the return key while [ToolbarView] has focus. + * @param url the text inside the [ToolbarView] when committed */ fun onUrlCommitted(url: String) /** - * Called when a user removes focus from the [BrowserToolbarView] + * Called when a user removes focus from the [ToolbarView] */ fun onEditingCanceled() /** - * Called whenever the text inside the [BrowserToolbarView] changes - * @param text the current text displayed by [BrowserToolbarView] + * Called whenever the text inside the [ToolbarView] changes + * @param text the current text displayed by [ToolbarView] */ fun onTextChanged(text: String) } @@ -103,7 +103,7 @@ class ToolbarView( override fun onTextChanged(text: String) { url = text - this@ToolbarView.interactor.onTextChanged(text) + interactor.onTextChanged(text) } }) } 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 66bc1f589..00b50a0a0 100644 --- a/app/src/test/java/org/mozilla/fenix/search/SearchFragmentStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchFragmentStoreTest.kt @@ -5,13 +5,17 @@ package org.mozilla.fenix.search import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import mozilla.components.browser.search.SearchEngine import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertNotSame +import org.junit.Assert.assertTrue import org.junit.Test import org.mozilla.fenix.components.metrics.Event +@ExperimentalCoroutinesApi class SearchFragmentStoreTest { @Test @@ -57,6 +61,43 @@ class SearchFragmentStoreTest { assertEquals(false, store.state.showSearchShortcuts) } + @Test + fun showSearchSuggestions() = runBlocking { + val initialState = emptyDefaultState() + val store = SearchFragmentStore(initialState) + + store.dispatch(SearchFragmentAction.SetShowSearchSuggestions(true)).join() + assertNotSame(initialState, store.state) + assertTrue(store.state.showSearchSuggestions) + + store.dispatch(SearchFragmentAction.SetShowSearchSuggestions(false)).join() + assertFalse(store.state.showSearchSuggestions) + } + + @Test + fun allowSearchInPrivateMode() = runBlocking { + val initialState = emptyDefaultState() + val store = SearchFragmentStore(initialState) + + store.dispatch(SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt(true)).join() + assertNotSame(initialState, store.state) + assertTrue(store.state.showSearchSuggestionsHint) + + store.dispatch(SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt(false)).join() + assertFalse(store.state.showSearchSuggestionsHint) + } + + @Test + fun selectNewDefaultEngine() = runBlocking { + val initialState = emptyDefaultState() + val store = SearchFragmentStore(initialState) + val engine = mockk() + + store.dispatch(SearchFragmentAction.SelectNewDefaultSearchEngine(engine)).join() + assertNotSame(initialState, store.state) + assertEquals(SearchEngineSource.Default(engine), store.state.searchEngineSource) + } + private fun emptyDefaultState(): SearchFragmentState = SearchFragmentState( tabId = null, url = "", diff --git a/app/src/test/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProviderTest.kt b/app/src/test/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProviderTest.kt new file mode 100644 index 000000000..d5ad43485 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProviderTest.kt @@ -0,0 +1,119 @@ +/* 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.awesomebar + +import android.content.Context +import androidx.appcompat.content.res.AppCompatResources +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verifySequence +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import mozilla.components.browser.search.SearchEngine +import mozilla.components.browser.search.provider.SearchEngineList +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Test +import org.mozilla.fenix.R +import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider + +@ExperimentalCoroutinesApi +class ShortcutsSuggestionProviderTest { + + private lateinit var context: Context + + @Before + fun setup() { + mockkStatic(AppCompatResources::class) + context = mockk { + every { getString(R.string.search_shortcuts_engine_settings) } returns "Search engine settings" + } + + every { AppCompatResources.getDrawable(context, R.drawable.ic_settings) } returns null + } + + @After + fun teardown() { + unmockkStatic(AppCompatResources::class) + } + + @Test + fun `should clear is always false`() { + val provider = ShortcutsSuggestionProvider(mockk(), mockk(), mockk(), mockk()) + assertFalse(provider.shouldClearSuggestions) + } + + @Test + fun `returns suggestions from search engine provider`() = runBlockingTest { + val engineOne = mockk { + every { identifier } returns "1" + every { name } returns "EngineOne" + every { icon } returns mockk() + } + val engineTwo = mockk { + every { identifier } returns "2" + every { name } returns "EngineTwo" + every { icon } returns mockk() + } + val searchEngineProvider = mockk { + every { installedSearchEngines(context) } returns SearchEngineList( + list = listOf(engineOne, engineTwo), + default = null + ) + } + val provider = ShortcutsSuggestionProvider(searchEngineProvider, context, mockk(), mockk()) + + val suggestions = provider.onInputChanged("") + assertEquals(3, suggestions.size) + + assertEquals(provider, suggestions[0].provider) + assertEquals(engineOne.identifier, suggestions[0].id) + assertEquals(engineOne.icon, suggestions[0].icon) + assertEquals(engineOne.name, suggestions[0].title) + + assertEquals(provider, suggestions[1].provider) + assertEquals(engineTwo.identifier, suggestions[1].id) + assertEquals(engineTwo.icon, suggestions[1].icon) + assertEquals(engineTwo.name, suggestions[1].title) + + assertEquals(provider, suggestions[2].provider) + assertEquals("Search engine settings", suggestions[2].id) + assertEquals("Search engine settings", suggestions[2].title) + } + + @Test + fun `callbacks are triggered when suggestions are clicked`() = runBlockingTest { + val engineOne = mockk(relaxed = true) + val searchEngineProvider = mockk { + every { installedSearchEngines(context) } returns SearchEngineList( + list = listOf(engineOne), + default = null + ) + } + val selectShortcutEngine = mockk<(SearchEngine) -> Unit>(relaxed = true) + val selectShortcutEngineSettings = mockk<() -> Unit>(relaxed = true) + val provider = ShortcutsSuggestionProvider( + searchEngineProvider, + context, + selectShortcutEngine, + selectShortcutEngineSettings + ) + + val suggestions = provider.onInputChanged("") + assertEquals(2, suggestions.size) + + suggestions[0].onSuggestionClicked?.invoke() + suggestions[1].onSuggestionClicked?.invoke() + + verifySequence { + selectShortcutEngine(engineOne) + selectShortcutEngineSettings() + } + } +} 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 new file mode 100644 index 000000000..f5fc0b5ad --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/search/toolbar/ToolbarViewTest.kt @@ -0,0 +1,78 @@ +/* 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.toolbar + +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.slot +import io.mockk.spyk +import io.mockk.verify +import mozilla.components.browser.toolbar.BrowserToolbar +import mozilla.components.concept.engine.Engine +import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class ToolbarViewTest { + + @MockK(relaxed = true) private lateinit var interactor: ToolbarInteractor + @MockK private lateinit var engine: Engine + private lateinit var toolbar: BrowserToolbar + + @Before + fun setup() { + MockKAnnotations.init(this) + toolbar = spyk(BrowserToolbar(testContext)) + } + + @Test + fun `sets up interactor listeners`() { + val urlCommitListener = slot<(String) -> Boolean>() + val editListener = slot() + every { toolbar.setOnUrlCommitListener(capture(urlCommitListener)) } just Runs + every { toolbar.setOnEditListener(capture(editListener)) } just Runs + + buildToolbarView(isPrivate = false) + + assertFalse(urlCommitListener.captured("test")) + verify { interactor.onUrlCommitted("test") } + + assertFalse(editListener.captured.onCancelEditing()) + verify { interactor.onEditingCanceled() } + + editListener.captured.onTextChanged("https://example.com") + verify { interactor.onTextChanged("https://example.com") } + } + + @Test + fun `sets toolbar to normal mode`() { + buildToolbarView(isPrivate = false) + assertFalse(toolbar.private) + } + + @Test + fun `sets toolbar to private mode`() { + buildToolbarView(isPrivate = true) + assertTrue(toolbar.private) + } + + private fun buildToolbarView(isPrivate: Boolean) = ToolbarView( + testContext, + interactor, + historyStorage = null, + isPrivate = isPrivate, + view = toolbar, + engine = engine + ) +}