Skip to content

Commit 412d70b

Browse files
authored
Add interstitial search (#719)
* Add new interstitial search screen * Remove duplicate data and presentation logic from AutoCompleteApi * Fix autocomplete issue where "no suggestions" briefly flashed up on the screen * Update to latest tooling
1 parent acd30fa commit 412d70b

34 files changed

+1258
-129
lines changed

app/src/androidTest/java/com/duckduckgo/app/autocomplete/api/AutoCompleteApiTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.duckduckgo.app.autocomplete.api
1818

19+
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteResult
1920
import com.duckduckgo.app.bookmarks.db.BookmarkEntity
2021
import com.duckduckgo.app.bookmarks.db.BookmarksDao
2122
import com.nhaarman.mockitokotlin2.verify
@@ -59,7 +60,7 @@ class AutoCompleteApiTest {
5960
@Test
6061
fun whenQueryIsBlankThenReturnAnEmptyList() {
6162
val result = testee.autoComplete("").test()
62-
val value = result.values()[0] as AutoCompleteApi.AutoCompleteResult
63+
val value = result.values()[0] as AutoCompleteResult
6364

6465
assertTrue(value.suggestions.isEmpty())
6566
}
@@ -70,7 +71,7 @@ class AutoCompleteApiTest {
7071
whenever(mockBookmarksDao.bookmarksByQuery(anyString())).thenReturn(Single.just(listOf(BookmarkEntity(0, "title", "https://example.com"))))
7172

7273
val result = testee.autoComplete("foo").test()
73-
val value = result.values()[0] as AutoCompleteApi.AutoCompleteResult
74+
val value = result.values()[0] as AutoCompleteResult
7475

7576
assertSame("https://example.com", value.suggestions[0].phrase)
7677
}

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
3030
import com.duckduckgo.app.CoroutineTestRule
3131
import com.duckduckgo.app.InstantSchedulersRule
3232
import com.duckduckgo.app.ValueCaptorObserver
33+
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteResult
34+
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion.AutoCompleteBookmarkSuggestion
35+
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion.AutoCompleteSearchSuggestion
3336
import com.duckduckgo.app.autocomplete.api.AutoCompleteApi
34-
import com.duckduckgo.app.autocomplete.api.AutoCompleteApi.AutoCompleteSuggestion.AutoCompleteBookmarkSuggestion
35-
import com.duckduckgo.app.autocomplete.api.AutoCompleteApi.AutoCompleteSuggestion.AutoCompleteSearchSuggestion
3637
import com.duckduckgo.app.autocomplete.api.AutoCompleteService
3738
import com.duckduckgo.app.bookmarks.db.BookmarkEntity
3839
import com.duckduckgo.app.bookmarks.db.BookmarksDao
@@ -227,7 +228,7 @@ class BrowserTabViewModelTest {
227228
siteFactory = siteFactory,
228229
tabRepository = mockTabsRepository,
229230
networkLeaderboardDao = mockNetworkLeaderboardDao,
230-
autoCompleteApi = mockAutoCompleteApi,
231+
autoComplete = mockAutoCompleteApi,
231232
appSettingsPreferencesStore = mockSettingsStore,
232233
bookmarksDao = mockBookmarksDao,
233234
longPressHandler = mockLongPressHandler,
@@ -1263,16 +1264,17 @@ class BrowserTabViewModelTest {
12631264
@Test
12641265
fun whenBookmarkSuggestionSubmittedThenAutoCompleteBookmarkSelectionPixelSent() = runBlocking {
12651266
whenever(mockBookmarksDao.hasBookmarks()).thenReturn(true)
1266-
testee.autoCompleteViewState.value = autoCompleteViewState().copy(searchResults = AutoCompleteApi.AutoCompleteResult("", emptyList(), true))
1267-
testee.fireAutocompletePixel(AutoCompleteBookmarkSuggestion("example", "Example", "https://example.com"))
1268-
1267+
val suggestion = AutoCompleteBookmarkSuggestion("example", "Example", "https://example.com")
1268+
testee.autoCompleteViewState.value = autoCompleteViewState().copy(searchResults = AutoCompleteResult("", listOf(suggestion)))
1269+
testee.fireAutocompletePixel(suggestion)
12691270
verify(mockPixel).fire(Pixel.PixelName.AUTOCOMPLETE_BOOKMARK_SELECTION, pixelParams(showedBookmarks = true, bookmarkCapable = true))
12701271
}
12711272

12721273
@Test
12731274
fun whenSearchSuggestionSubmittedWithBookmarksThenAutoCompleteSearchSelectionPixelSent() = runBlocking {
12741275
whenever(mockBookmarksDao.hasBookmarks()).thenReturn(true)
1275-
testee.autoCompleteViewState.value = autoCompleteViewState().copy(searchResults = AutoCompleteApi.AutoCompleteResult("", emptyList(), true))
1276+
val suggestions = listOf(AutoCompleteSearchSuggestion("", false), AutoCompleteBookmarkSuggestion("", "", ""))
1277+
testee.autoCompleteViewState.value = autoCompleteViewState().copy(searchResults = AutoCompleteResult("", suggestions))
12761278
testee.fireAutocompletePixel(AutoCompleteSearchSuggestion("example", false))
12771279

12781280
verify(mockPixel).fire(Pixel.PixelName.AUTOCOMPLETE_SEARCH_SELECTION, pixelParams(showedBookmarks = true, bookmarkCapable = true))
@@ -1281,7 +1283,7 @@ class BrowserTabViewModelTest {
12811283
@Test
12821284
fun whenSearchSuggestionSubmittedWithoutBookmarksThenAutoCompleteSearchSelectionPixelSent() = runBlocking {
12831285
whenever(mockBookmarksDao.hasBookmarks()).thenReturn(false)
1284-
testee.autoCompleteViewState.value = autoCompleteViewState().copy(searchResults = AutoCompleteApi.AutoCompleteResult("", emptyList(), false))
1286+
testee.autoCompleteViewState.value = autoCompleteViewState().copy(searchResults = AutoCompleteResult("", emptyList()))
12851287
testee.fireAutocompletePixel(AutoCompleteSearchSuggestion("example", false))
12861288

12871289
verify(mockPixel).fire(Pixel.PixelName.AUTOCOMPLETE_SEARCH_SELECTION, pixelParams(showedBookmarks = false, bookmarkCapable = false))

app/src/androidTest/java/com/duckduckgo/app/di/TestAppComponent.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import javax.inject.Singleton
5353
NetworkModule::class,
5454
StoreModule::class,
5555
JsonModule::class,
56+
SystemComponentsModule::class,
5657
BrowserModule::class,
5758
BrowserAutoCompleteModule::class,
5859
HttpsUpgraderModule::class,

app/src/androidTest/java/com/duckduckgo/app/privacy/ui/TrackerNetworksViewModelTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class TrackerNetworksViewModelTest {
108108
assertEquals(1, result[Entity.MINOR_ENTITY_B]?.count())
109109
}
110110

111-
private fun site(url: String = "", networkCount: Int = 0, trackingEvents: List<TrackingEvent> = emptyList()): Site {
111+
private fun site(url: String = "", trackingEvents: List<TrackingEvent> = emptyList()): Site {
112112
val site: Site = mock()
113113
whenever(site.url).thenReturn(url)
114114
whenever(site.uri).thenReturn(Uri.parse(url))

app/src/androidTest/java/com/duckduckgo/app/statistics/VariantManagerTest.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class VariantManagerTest {
5353
fun conceptTestNoCtaVariantIsInactiveAndHasSuppressCtaFeatures() {
5454
val variant = variants.firstOrNull { it.key == "md" }
5555
assertEqualsDouble(0.0, variant!!.weight)
56-
assertEquals(2, variant!!.features.size)
56+
assertEquals(2, variant.features.size)
5757
assertTrue(variant.hasFeature(SuppressWidgetCta))
5858
assertTrue(variant.hasFeature(SuppressDefaultBrowserCta))
5959
}
@@ -62,12 +62,13 @@ class VariantManagerTest {
6262
fun conceptTestExperimentalVariantIsInactiveAndHasConceptTestAndSuppressCtaFeatures() {
6363
val variant = variants.firstOrNull { it.key == "me" }
6464
assertEqualsDouble(0.0, variant!!.weight)
65-
assertEquals(3, variant!!.features.size)
65+
assertEquals(3, variant.features.size)
6666
assertTrue(variant.hasFeature(ConceptTest))
6767
assertTrue(variant.hasFeature(SuppressWidgetCta))
6868
assertTrue(variant.hasFeature(SuppressDefaultBrowserCta))
6969
}
7070

71+
7172
// CTA on Concept Test experiments
7273

7374
@Test
@@ -81,7 +82,7 @@ class VariantManagerTest {
8182
fun insertCtaConceptTestVariantIsActiveAndHasConceptTestAndSuppressCtaFeatures() {
8283
val variant = variants.firstOrNull { it.key == "mv" }
8384
assertEqualsDouble(1.0, variant!!.weight)
84-
assertEquals(3, variant!!.features.size)
85+
assertEquals(3, variant.features.size)
8586
assertTrue(variant.hasFeature(ConceptTest))
8687
assertTrue(variant.hasFeature(SuppressWidgetCta))
8788
assertTrue(variant.hasFeature(SuppressDefaultBrowserCta))
@@ -91,7 +92,7 @@ class VariantManagerTest {
9192
fun insertCtaConceptTestWithAllCtaExperimentalVariantIsActiveAndHasConceptTestAndSuppressCtaFeatures() {
9293
val variant = variants.firstOrNull { it.key == "mz" }
9394
assertEqualsDouble(1.0, variant!!.weight)
94-
assertEquals(2, variant!!.features.size)
95+
assertEquals(2, variant.features.size)
9596
assertTrue(variant.hasFeature(ConceptTest))
9697
}
9798

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright (c) 2020 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.systemsearch
18+
19+
import android.content.Intent
20+
import com.duckduckgo.app.systemsearch.DeviceAppLookupTest.AppName.DDG_MOVIES
21+
import com.duckduckgo.app.systemsearch.DeviceAppLookupTest.AppName.DDG_MUSIC
22+
import com.duckduckgo.app.systemsearch.DeviceAppLookupTest.AppName.FILES
23+
import com.duckduckgo.app.systemsearch.DeviceAppLookupTest.AppName.LIVE_DDG
24+
import com.nhaarman.mockitokotlin2.mock
25+
import com.nhaarman.mockitokotlin2.whenever
26+
import org.junit.Assert.assertEquals
27+
import org.junit.Assert.assertTrue
28+
import org.junit.Test
29+
30+
class DeviceAppLookupTest {
31+
32+
private val mockAppProvider: DeviceAppListProvider = mock()
33+
34+
private val testee = InstalledDeviceAppLookup(mockAppProvider)
35+
36+
@Test
37+
fun whenQueryMatchesWordInShortNameThenMatchesAreReturned() {
38+
whenever(mockAppProvider.get()).thenReturn(apps)
39+
val result = testee.query("DDG")
40+
assertEquals(3, result.size)
41+
assertEquals(DDG_MOVIES, result[0].shortName)
42+
assertEquals(DDG_MUSIC, result[1].shortName)
43+
assertEquals(LIVE_DDG, result[2].shortName)
44+
}
45+
46+
@Test
47+
fun whenQueryMatchesWordPrefixInShortNameThenMatchesAreReturned() {
48+
whenever(mockAppProvider.get()).thenReturn(apps)
49+
val result = testee.query("DDG")
50+
assertEquals(3, result.size)
51+
assertEquals(DDG_MOVIES, result[0].shortName)
52+
assertEquals(DDG_MUSIC, result[1].shortName)
53+
assertEquals(LIVE_DDG, result[2].shortName)
54+
}
55+
56+
@Test
57+
fun whenQueryMatchesPastShortNameWordBoundaryToNextPrefixThenMatchesAreReturned() {
58+
whenever(mockAppProvider.get()).thenReturn(apps)
59+
val result = testee.query("DDG M")
60+
assertEquals(2, result.size)
61+
assertEquals(DDG_MOVIES, result[0].shortName)
62+
assertEquals(DDG_MUSIC, result[1].shortName)
63+
}
64+
65+
@Test
66+
fun whenQueryMatchesWordPrefixInShortNameWithDifferentCaseThenMatchesAreReturned() {
67+
whenever(mockAppProvider.get()).thenReturn(apps)
68+
val result = testee.query("ddg")
69+
assertEquals(3, result.size)
70+
assertEquals(DDG_MOVIES, result[0].shortName)
71+
assertEquals(DDG_MUSIC, result[1].shortName)
72+
assertEquals(LIVE_DDG, result[2].shortName)
73+
}
74+
75+
@Test
76+
fun whenQueryMatchesMiddleOrSuffixOfAppNameWordThenNoAppsReturned() {
77+
whenever(mockAppProvider.get()).thenReturn(apps)
78+
val result = testee.query("DG")
79+
assertTrue(result.isEmpty())
80+
}
81+
82+
@Test
83+
fun whenQueryDoesNotMatchAnyPartOfAppNameThenNoAppsReturned() {
84+
whenever(mockAppProvider.get()).thenReturn(apps)
85+
val result = testee.query("nonmatching")
86+
assertTrue(result.isEmpty())
87+
}
88+
89+
@Test
90+
fun whenQueryIsEmptyThenNoAppsReturned() {
91+
whenever(mockAppProvider.get()).thenReturn(apps)
92+
val result = testee.query("")
93+
assertTrue(result.isEmpty())
94+
}
95+
96+
@Test
97+
fun whenAppsListIsEmptyThenNoAppsReturned() {
98+
whenever(mockAppProvider.get()).thenReturn(noApps)
99+
val result = testee.query("DDG")
100+
assertTrue(result.isEmpty())
101+
}
102+
103+
object AppName {
104+
const val DDG_MOVIES = "DDG Movies"
105+
const val DDG_MUSIC = "DDG Music"
106+
const val LIVE_DDG = "Live DDG"
107+
const val FILES = "Files"
108+
}
109+
110+
companion object {
111+
val noApps = emptyList<DeviceApp>()
112+
113+
val apps = listOf(
114+
DeviceApp(DDG_MOVIES, "", Intent()),
115+
DeviceApp(DDG_MUSIC, "", Intent()),
116+
DeviceApp(FILES, "", Intent()),
117+
DeviceApp(LIVE_DDG, "", Intent())
118+
)
119+
}
120+
}

0 commit comments

Comments
 (0)