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