8000 Merge branch 'release/5.89.0' · codezwc/Android-1@3949745 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3949745

Browse files
committed
Merge branch 'release/5.89.0'
2 parents b2d8e7f + 3585f98 commit 3949745

File tree

106 files changed

+2363
-844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+2363
-844
lines changed

app/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ android {
4141
}
4242
}
4343
}
44+
buildFeatures {
45+
viewBinding = true
46+
}
4447
compileOptions {
4548
sourceCompatibility = JavaVersion.VERSION_1_8
4649
targetCompatibility = JavaVersion.VERSION_1_8
@@ -122,6 +125,7 @@ android {
122125
dependencies {
123126
implementation project(path: ':statistics')
124127
implementation project(path: ':common')
128+
implementation project(path: ':common-ui')
125129
implementation project(path: ':di')
126130

127131
implementation AndroidX.legacy.supportV4
@@ -164,6 +168,7 @@ dependencies {
164168
implementation AndroidX.fragmentKtx
165169

166170
// ViewModel and LiveData
171+
implementation AndroidX.lifecycle.runtimeKtx
167172
implementation AndroidX.lifecycle.viewModelKtx
168173
implementation AndroidX.lifecycle.liveDataKtx
169174
implementation AndroidX.lifecycle.process
@@ -213,6 +218,7 @@ dependencies {
213218
androidTestImplementation Testing.mockito.kotlin
214219
androidTestImplementation Square.okHttp3.mockWebServer
215220
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:_"
221+
androidTestImplementation 'app.cash.turbine:turbine:_'
216222
}
217223

218224
tasks.register('fastlaneVersionCode') {

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

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,45 @@ package com.duckduckgo.app.browser
2020

2121
import android.content.Context
2222
import android.graphics.Bitmap
23+
import android.net.Uri
2324
import android.os.Message
2425
import android.view.View
26+
import android.webkit.ValueCallback
2527
import android.webkit.WebChromeClient
2628
import android.webkit.WebView
2729
import androidx.test.annotation.UiThreadTest
2830
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
2931
import com.duckduckgo.app.global.exception.UncaughtExceptionRepository
32+
import com.duckduckgo.app.global.exception.UncaughtExceptionSource
3033
import com.nhaarman.mockitokotlin2.*
34+
import junit.framework.TestCase.assertTrue
35+
import kotlinx.coroutines.ExperimentalCoroutinesApi
36+
import kotlinx.coroutines.runBlocking
37+
import kotlinx.coroutines.test.TestCoroutineScope
3138
import org.junit.Before
3239
import org.junit.Test
40+
import org.mockito.ArgumentMatchers.anyInt
41+
import org.mockito.ArgumentMatchers.anyString
3342

3443
class BrowserChromeClientTest {
3544

3645
private lateinit var testee: BrowserChromeClient
3746
private lateinit var webView: TestWebView
3847
private lateinit var mockWebViewClientListener: WebViewClientListener
3948
private lateinit var mockUncaughtExceptionRepository: UncaughtExceptionRepository
49+
private lateinit var mockFilePathCallback: ValueCallback<Array<Uri>>
50+
private lateinit var mockFileChooserParams: WebChromeClient.FileChooserParams
4051
private val fakeView = View(getInstrumentation().targetContext)
4152

53+
@ExperimentalCoroutinesApi
4254
@UiThreadTest
4355
@Before
4456
fun setup() {
4557
mockUncaughtExceptionRepository = mock()
46-
testee = BrowserChromeClient(mockUncaughtExceptionRepository)
58+
testee = BrowserChromeClient(mockUncaughtExceptionRepository, TestCoroutineScope())
4759
mockWebViewClientListener = mock()
60+
mockFilePathCallback = mock()
61+
mockFileChooserParams = mock()
4862
testee.webViewClientListener = mockWebViewClientListener
4963
webView = TestWebView(getInstrumentation().targetContext)
5064
}
@@ -78,12 +92,28 @@ class BrowserChromeClientTest {
7892
verify(mockCustomViewCallback, times(2)).onCustomViewHidden()
7993
}
8094

95+
@Test
96+
fun whenCustomViewShownThrowsExceptionThenRecordException() = runBlocking {
97+
val exception = RuntimeException()
98+
whenever(mockWebViewClientListener.goFullScreen(any())).thenThrow(exception)
99+
testee.onShowCustomView(fakeView, null)
100+
verify(mockUncaughtExceptionRepository).recordUncaughtException(exception, UncaughtExceptionSource.SHOW_CUSTOM_VIEW)
101+
}
102+
81103
@Test
82104
fun whenHideCustomViewCalledThenListenerInstructedToExistFullScreen() {
83105
testee.onHideCustomView()
84106
verify(mockWebViewClientListener).exitFullScreen()
85107
}
86108

109+
@Test
110+
fun whenHideCustomViewThrowsExceptionThenRecordException() = runBlocking {
111+
val exception = RuntimeException()
112+
whenever(mockWebViewClientListener.exitFullScreen()).thenThrow(exception)
113+
testee.onHideCustomView()
114+
verify(mockUncaughtExceptionRepository).recordUncaughtException(exception, UncaughtExceptionSource.HIDE_CUSTOM_VIEW)
115+
}
116+
87117
@UiThreadTest
88118
@Test
89119
fun whenOnProgressChangedCalledThenListenerInstructedToUpdateProgress() {
@@ -98,6 +128,15 @@ class BrowserChromeClientTest {
98128
verify(mockWebViewClientListener).navigationStateChanged(any())
99129
}
100130

131+
@UiThreadTest
132+
@Test
133+
fun whenOnProgressChangedThrowsExceptionThenRecordException() = runBlocking {
134+
val exception = RuntimeException()
135+
whenever(mockWebViewClientListener.progressChanged(anyInt())).thenThrow(exception)
136+
testee.onProgressChanged(webView, 10)
137+
verify(mockUncaughtExceptionRepository).recordUncaughtException(exception, UncaughtExceptionSource.ON_PROGRESS_CHANGED)
138+
}
139+
101140
@UiThreadTest
102141
@Test
103142
fun whenOnCreateWindowWithUserGestureThenMessageOpenedInNewTab() {
@@ -120,6 +159,36 @@ class BrowserChromeClientTest {
120159
verify(mockWebViewClientListener).iconReceived(webView.url, bitmap)
121160
}
122161

162+
@Test
163+
fun whenOnReceivedTitleThenTitleReceived() {
164+
val title = "title"
165+
testee.onReceivedTitle(webView, title)
166+
verify(mockWebViewClientListener).titleReceived(title)
167+
}
168+
169+
@Test
170+
fun whenOnReceivedTitleThrowsExceptionThenRecordException() = runBlocking {
171+
val exception = RuntimeException()
172+
whenever(mockWebViewClientListener.titleReceived(anyString())).thenThrow(exception)
173+
testee.onReceivedTitle(webView, "")
174+
verify(mockUncaughtExceptionRepository).recordUncaughtException(exception, UncaughtExceptionSource.RECEIVED_PAGE_TITLE)
175+
}
176+
177+
@Test
178+
fun whenOnShowFileChooserCalledThenShowFileChooser() {
179+
assertTrue(testee.onShowFileChooser(webView, mockFilePathCallback, mockFileChooserParams))
180+
verify(mockWebViewClientListener).showFileChooser(mockFilePathCallback, mockFileChooserParams)
181+
}
182+
183+
@Test
184+
fun whenShowFileChooserThrowsExceptionThenRecordException() = runBlocking {
185+
val exception = RuntimeException()
186+
whenever(mockWebViewClientListener.showFileChooser(any(), any())).thenThrow(exception)
187+
assertTrue(testee.onShowFileChooser(webView, mockFilePathCallback, mockFileChooserParams))
188+
verify(mockUncaughtExceptionRepository).recordUncaughtException(exception, UncaughtExceptionSource.SHOW_FILE_CHOOSER)
189+
verify(mockFilePathCallback).onReceiveValue(null)
190+
}
191+
123192
private val mockMsg = Message().apply {
124193
target = mock()
125194
obj = mock<WebView.WebViewTransport>()

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

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

1717
package com.duckduckgo.app.browser
1818

19+
import android.content.Intent
1920
import android.graphics.Bitmap
2021
import android.net.Uri
2122
import android.view.MenuItem
@@ -47,6 +48,7 @@ import com.duckduckgo.app.browser.BrowserTabViewModel.FireButton
4748
import com.duckduckgo.app.browser.LongPressHandler.RequiredAction.DownloadFile
4849
import com.duckduckgo.app.browser.LongPressHandler.RequiredAction.OpenInNewTab
4950
import com.duckduckgo.app.browser.addtohome.AddToHomeCapabilityDetector
51+
import com.duckduckgo.app.browser.applinks.AppLinksHandler
5052
import com.duckduckgo.app.browser.downloader.FileDownloader
5153
import com.duckduckgo.app.browser.favicon.FaviconManager
5254
import com.duckduckgo.app.browser.favicon.FaviconSource
@@ -112,15 +114,11 @@ import com.duckduckgo.app.trackerdetection.EntityLookup
112114
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
113115
import com.duckduckgo.app.usage.search.SearchCountDao
114116
import com.duckduckgo.app.widget.ui.WidgetCapabilities
117+
import com.nhaarman.mockitokotlin2.*
115118
import com.nhaarman.mockitokotlin2.any
116-
import com.nhaarman.mockitokotlin2.anyOrNull
117119
import com.nhaarman.mockitokotlin2.atLeastOnce
118-
import com.nhaarman.mockitokotlin2.doAnswer
119120
import com.nhaarman.mockitokotlin2.doReturn
120121
import com.nhaarman.mockitokotlin2.eq
121-
import com.nhaarman.mockitokotlin2.firstValue
122-
import com.nhaarman.mockitokotlin2.lastValue
123-
import com.nhaarman.mockitokotlin2.mock
124122
import com.nhaarman.mockitokotlin2.whenever
125123
import dagger.Lazy
126124
import io.reactivex.Observable
@@ -135,18 +133,21 @@ import kotlinx.coroutines.flow.asStateFlow
135133
import kotlinx.coroutines.flow.consumeAsFlow
136134
import kotlinx.coroutines.flow.flowOf
137135
import kotlinx.coroutines.runBlocking
136+
import kotlinx.coroutines.test.TestCoroutineScope
138137
import kotlinx.coroutines.test.runBlockingTest
139138
import org.junit.After
140139
import org.junit.Assert.*
141140
import org.junit.Before
142141
import org.junit.Rule
143142
import org.junit.Test
144143
import org.mockito.ArgumentCaptor
145-
import org.mockito.ArgumentMatchers.anyString
146144
import org.mockito.Captor
147145
import org.mockito.Mock
148146
import org.mockito.Mockito
149147
import org.mockito.Mockito.*
148+
import org.mockito.Mockito.never
149+
import org.mockito.Mockito.times
150+
import org.mockito.Mockito.verify
150151
import org.mockito.MockitoAnnotations
151152
import org.mockito.internal.util.DefaultMockingDetails
152153
import java.io.File
@@ -263,6 +264,12 @@ class BrowserTabViewModelTest {
263264
@Mock
264265
private lateinit var mockFavoritesRepository: FavoritesRepository
265266

267+
@Mock
268+
private lateinit var mockSpecialUrlDetector: SpecialUrlDetector
269+
270+
@Mock
271+
private lateinit var mockAppLinksHandler: AppLinksHandler
272+
266273
private val lazyFaviconManager = Lazy { mockFaviconManager }
267274

268275
private lateinit var mockAutoCompleteApi: AutoCompleteApi
@@ -272,6 +279,9 @@ class BrowserTabViewModelTest {
272279
@Captor
273280
private lateinit var commandCaptor: ArgumentCaptor<Command>
274281

282+
@Captor
283+
private lateinit var appLinkCaptor: ArgumentCaptor<() -> Unit>
284+
275285
private lateinit var db: AppDatabase
276286

277287
private lateinit var testee: BrowserTabViewModel
@@ -351,7 +361,7 @@ class BrowserTabViewModelTest {
351361
bookmarksDao = mockBookmarksDao,
352362
longPressHandler = mockLongPressHandler,
353363
webViewSessionStorage = webViewSessionStorage,
354-
specialUrlDetector = SpecialUrlDetectorImpl(),
364+
specialUrlDetector = mockSpecialUrlDetector,
355365
faviconManager = mockFaviconManager,
356366
addToHomeCapabilityDetector = mockAddToHomeCapabilityDetector,
357367
ctaViewModel = ctaViewModel,
@@ -373,7 +383,9 @@ class BrowserTabViewModelTest {
373383
globalPrivacyControl = GlobalPrivacyControlManager(mockSettingsStore),
374384
fireproofDialogsEventHandler = fireproofDialogsEventHandler,
375385
emailManager = mockEmailManager,
376-
favoritesRepository = mockFavoritesRepository
386+
favoritesRepository = mockFavoritesRepository,
387+
appCoroutineScope = TestCoroutineScope(),
388+
appLinksHandler = mockAppLinksHandler
377389
)
378390

379391
testee.loadData("abc", null, false)
@@ -558,17 +570,6 @@ class BrowserTabViewModelTest {
558570
verify(mockFavoritesRepository, times(0)).insert(any(), any())
559571
}
560572

561-
@Test
562-
fun whenQuickAccessItemClickedThenSubmitNewQuery() {
563-
val savedSite = Favorite(1, "title", "http://example.com", 0)
564-
565-
testee.onQuickAccesItemClicked(savedSite)
566-
567-
assertCommandIssued<Command.SubmitQuery> {
568-
assertEquals("http://example.com", this.url)
569-
}
570-
}
571-
572573
@Test
573574
fun whenQuickAccessDeletedThenRepositoryUpdated() = coroutineRule.runBlocking {
574575
val savedSite = Favorite(1, "title", "http://example.com", 0)
@@ -1123,6 +1124,14 @@ class BrowserTabViewModelTest {
11231124
assertTrue(commandCaptor.allValues.any { it == Command.HideKeyboard })
11241125
}
11251126

1127+
@Test
1128+
fun whenEnteringAppLinkQueryThenNavigateInBrowser() {
1129+
whenever(mockOmnibarConverter.convertQueryToUrl("foo", null)).thenReturn("foo.com")
1130+
testee.onUserSubmittedQuery("foo")
1131+
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
1132+
assertTrue(commandCaptor.allValues.any { it == Command.HideKeyboard })
1133+
}
1134+
11261135
@Test
11271136
fun whenNotifiedEnteringFullScreenThenViewStateUpdatedWithFullScreenFlag() {
11281137
val stubView = View(getInstrumentation().targetContext)
@@ -3009,24 +3018,24 @@ class BrowserTabViewModelTest {
30093018
@Test
30103019
fun whenExternalAppLinkClickedIfGpcIsEnabledThenAddHeaderToUrl() {
30113020
whenever(mockSettingsStore.globalPrivacyControlEnabled).thenReturn(true)
3012-
val intentType = SpecialUrlDetector.UrlType.IntentType("query", mock(), null)
3021+
val intentType = SpecialUrlDetector.UrlType.NonHttpAppLink("query", mock(), null)
30133022

3014-
testee.externalAppLinkClicked(intentType)
3023+
testee.nonHttpAppLinkClicked(intentType)
30153024
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
30163025

3017-
val command = commandCaptor.lastValue as Command.HandleExternalAppLink
3026+
val command = commandCaptor.lastValue as Command.HandleNonHttpAppLink
30183027
assertEquals(GPC_HEADER_VALUE, command.headers[GPC_HEADER])
30193028
}
30203029

30213030
@Test
30223031
fun whenExternalAppLinkClickedIfGpcIsDisabledThenDoNotAddHeaderToUrl() {
30233032
whenever(mockSettingsStore.globalPrivacyControlEnabled).thenReturn(false)
3024-
val intentType = SpecialUrlDetector.UrlType.IntentType("query", mock(), null)
3033+
val intentType = SpecialUrlDetector.UrlType.NonHttpAppLink("query", mock(), null)
30253034

3026-
testee.externalAppLinkClicked(intentType)
3035+
testee.nonHttpAppLinkClicked(intentType)
30273036
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
30283037

3029-
val command = commandCaptor.lastValue as Command.HandleExternalAppLink
3038+
val command = commandCaptor.lastValue as Command.HandleNonHttpAppLink
30303039
assertTrue(command.headers.isEmpty())
30313040
}
30323041

@@ -3195,6 +3204,54 @@ class BrowserTabViewModelTest {
31953204
assertCommandNotIssued<Command.ShowEmailTooltip>()
31963205
}
31973206

3207+
@Test
3208+
fun whenHandleAppLinkCalledThenHandleAppLink() {
3209+
val urlType = SpecialUrlDetector.UrlType.AppLink(uriString = "http://example.com")
3210+
testee.handleAppLink(urlType, isRedirect = false, isForMainFrame = true)
3211+
verify(mockAppLinksHandler).handleAppLink(isRedirect = eq(false), isForMainFrame = eq(true), capture(appLinkCaptor))
3212+
appLinkCaptor.value.invoke()
3213+
assertCommandIssued<Command.HandleAppLink>()
3214+
}
3215+
3216+
@Test
3217+
fun whenHandleNonHttpAppLinkCalledThenHandleNonHttpAppLink() {
3218+
val urlType = SpecialUrlDetector.UrlType.NonHttpAppLink("market://details?id=com.example", Intent(), "http://example.com")
3219+
testee.handleNonHttpAppLink(urlType, false)
3220+
verify(mockAppLinksHandler).handleNonHttpAppLink(isRedirect = eq(false), capture(appLinkCaptor))
3221+
appLinkCaptor.value.invoke()
3222+
assertCommandIssued<Command.HandleNonHttpAppLink>()
3223+
}
3224+
3225+
@Test
3226+
fun whenResetAppLinkStateCalledThenResetAppLinkState() {
3227+
testee.resetAppLinkState()
3228+
verify(mockAppLinksHandler).reset()
3229+
}
3230+
3231+
@Test
3232+
fun whenUserSubmittedQueryIsAppLinkThenOpenAppLinkInBrowser() {
3233+
whenever(mockOmnibarConverter.convertQueryToUrl("foo", null)).thenReturn("foo.com")
3234+
whenever(mockSpecialUrlDetector.determineType(anyString())).thenReturn(SpecialUrlDetector.UrlType.AppLink(uriString = "http://foo.com"))
3235+
testee.onUserSubmittedQuery("foo")
3236+
verify(mockAppLinksHandler).enterBrowserState()
3237+
assertCommandIssued<Navigate>()
3238+
}
3239+
3240+
@Test
3241+
fun whenSubmittedQueryAndNavigationStateIsNullThenResetHistoryCommandSent() {
3242+
whenever(mockOmnibarConverter.convertQueryToUrl("nytimes.com", null)).thenReturn("nytimes.com")
3243+
testee.onUserSubmittedQuery("nytimes.com")
3244+
assertCommandIssued<Command.ResetHistory>()
3245+
}
3246+
3247+
@Test
3248+
fun whenSubmittedQueryAndNavigationStateIsNotNullThenResetHistoryCommandNotSent() {
3249+
setupNavigation(isBrowsing = true)
3250+
whenever(mockOmnibarConverter.convertQueryToUrl("nytimes.com", null)).thenReturn("nytimes.com")
3251+
testee.onUserSubmittedQuery("nytimes.com")
3252+
assertCommandNotIssued<Command.ResetHistory>()
3253+
}
3254+
31983255
private suspend fun givenFireButtonPulsing() {
31993256
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
32003257
dismissedCtaDaoChannel.send(listOf(DismissedCta(CtaId.DAX_DIALOG_TRACKERS_FOUND)))

0 commit comments

Comments
 (0)
0