Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c02276b
Add handling of app links
joshliebe Jun 2, 2021
9f87e31
Fix ktLint issues
joshliebe Jun 2, 2021
51bab21
Update tests for API <24
joshliebe Jun 2, 2021
babaad1
Fix broken tests
joshliebe Jun 2, 2021
f436a59
Remove close tab button from app link dialog
joshliebe Jun 3, 2021
6be2222
Handle redirects after an app link has been triggered
joshliebe Jun 7, 2021
01d6534
Use existing copy for app link dialog title and message
joshliebe Jun 7, 2021
630cccc
Add app link SDK check
joshliebe Jun 7, 2021
603a96b
Update app link dialog title
joshliebe Jun 7, 2021
3bb6d6a
Refactor app link handling out of BrowserWebViewClient
joshliebe Jun 9, 2021
c11a097
Move untranslated strings to string-untranslated.xml
joshliebe Jun 10, 2021
cf27a02
Remove toast from app links dialog
joshliebe Jun 10, 2021
bf3c447
Extract URI flag
joshliebe Jun 10, 2021
f3ddea7
Split up tests for different SDK versions
joshliebe Jun 10, 2021
df479ae
Move app link handling to ViewModel
joshliebe Jun 11, 2021
ee8efe3
Extract constant for URI flag
joshliebe Jun 11, 2021
b4e6f1d
Inject PackageManager instead of Context
joshliebe Jun 11, 2021
75f575a
Remove context check when launching dialog
joshliebe Jun 11, 2021
d572547
Open app link in browser when entered by user
joshliebe Jun 11, 2021
31fc3a3
Open app link redirects in browser when entered by user
joshliebe Jun 14, 2021
15d81af
Rename URI_FLAG to URI_NO_FLAG
joshliebe Jun 15, 2021
5ba4a47
Replace link in non http app link test
joshliebe Jun 15, 2021
2235a6a
Refactor browser filtering into separate method
joshliebe Jun 15, 2021
3fb25a8
Rename url to uriString
joshliebe Jun 15, 2021
2aa4749
Fix KtLint issue
joshliebe Jun 15, 2021
2b74bef
Update function to take filter as parameter
joshliebe Jun 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.duckduckgo.app.browser

import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.view.MenuItem
Expand Down Expand Up @@ -47,6 +48,7 @@ import com.duckduckgo.app.browser.BrowserTabViewModel.FireButton
import com.duckduckgo.app.browser.LongPressHandler.RequiredAction.DownloadFile
import com.duckduckgo.app.browser.LongPressHandler.RequiredAction.OpenInNewTab
import com.duckduckgo.app.browser.addtohome.AddToHomeCapabilityDetector
import com.duckduckgo.app.browser.applinks.AppLinksHandler
import com.duckduckgo.app.browser.downloader.FileDownloader
import com.duckduckgo.app.browser.favicon.FaviconManager
import com.duckduckgo.app.browser.favicon.FaviconSource
Expand Down Expand Up @@ -112,15 +114,11 @@ import com.duckduckgo.app.trackerdetection.EntityLookup
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
import com.duckduckgo.app.usage.search.SearchCountDao
import com.duckduckgo.app.widget.ui.WidgetCapabilities
import com.nhaarman.mockitokotlin2.*
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.atLeastOnce
import com.nhaarman.mockitokotlin2.doAnswer
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.firstValue
import com.nhaarman.mockitokotlin2.lastValue
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import dagger.Lazy
import io.reactivex.Observable
Expand All @@ -143,11 +141,13 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.*
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.internal.util.DefaultMockingDetails
import java.io.File
Expand Down Expand Up @@ -264,6 +264,12 @@ class BrowserTabViewModelTest {
@Mock
private lateinit var mockFavoritesRepository: FavoritesRepository

@Mock
private lateinit var mockSpecialUrlDetector: SpecialUrlDetector

@Mock
private lateinit var mockAppLinksHandler: AppLinksHandler

private val lazyFaviconManager = Lazy { mockFaviconManager }

private lateinit var mockAutoCompleteApi: AutoCompleteApi
Expand All @@ -273,6 +279,9 @@ class BrowserTabViewModelTest {
@Captor
private lateinit var commandCaptor: ArgumentCaptor<Command>

@Captor
private lateinit var appLinkCaptor: ArgumentCaptor<() -> Unit>

private lateinit var db: AppDatabase

private lateinit var testee: BrowserTabViewModel
Expand Down Expand Up @@ -352,7 +361,7 @@ class BrowserTabViewModelTest {
bookmarksDao = mockBookmarksDao,
longPressHandler = mockLongPressHandler,
webViewSessionStorage = webViewSessionStorage,
specialUrlDetector = SpecialUrlDetectorImpl(),
specialUrlDetector = mockSpecialUrlDetector,
faviconManager = mockFaviconManager,
addToHomeCapabilityDetector = mockAddToHomeCapabilityDetector,
ctaViewModel = ctaViewModel,
Expand All @@ -375,7 +384,8 @@ class BrowserTabViewModelTest {
fireproofDialogsEventHandler = fireproofDialogsEventHandler,
emailManager = mockEmailManager,
favoritesRepository = mockFavoritesRepository,
appCoroutineScope = TestCoroutineScope()
appCoroutineScope = TestCoroutineScope(),
appLinksHandler = mockAppLinksHandler
)

testee.loadData("abc", null, false)
Expand Down Expand Up @@ -1125,6 +1135,14 @@ class BrowserTabViewModelTest {
assertTrue(commandCaptor.allValues.any { it == Command.HideKeyboard })
}

@Test
fun whenEnteringAppLinkQueryThenNavigateInBrowser() {
whenever(mockOmnibarConverter.convertQueryToUrl("foo", null)).thenReturn("foo.com")
testee.onUserSubmittedQuery("foo")
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
assertTrue(commandCaptor.allValues.any { it == Command.HideKeyboard })
}

@Test
fun whenNotifiedEnteringFullScreenThenViewStateUpdatedWithFullScreenFlag() {
val stubView = View(getInstrumentation().targetContext)
Expand Down Expand Up @@ -3011,24 +3029,24 @@ class BrowserTabViewModelTest {
@Test
fun whenExternalAppLinkClickedIfGpcIsEnabledThenAddHeaderToUrl() {
whenever(mockSettingsStore.globalPrivacyControlEnabled).thenReturn(true)
val intentType = SpecialUrlDetector.UrlType.IntentType("query", mock(), null)
val intentType = SpecialUrlDetector.UrlType.NonHttpAppLink("query", mock(), null)

testee.externalAppLinkClicked(intentType)
testee.nonHttpAppLinkClicked(intentType)
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())

val command = commandCaptor.lastValue as Command.HandleExternalAppLink
val command = commandCaptor.lastValue as Command.HandleNonHttpAppLink
assertEquals(GPC_HEADER_VALUE, command.headers[GPC_HEADER])
}

@Test
fun whenExternalAppLinkClickedIfGpcIsDisabledThenDoNotAddHeaderToUrl() {
whenever(mockSettingsStore.globalPrivacyControlEnabled).thenReturn(false)
val intentType = SpecialUrlDetector.UrlType.IntentType("query", mock(), null)
val intentType = SpecialUrlDetector.UrlType.NonHttpAppLink("query", mock(), null)

testee.externalAppLinkClicked(intentType)
testee.nonHttpAppLinkClicked(intentType)
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())

val command = commandCaptor.lastValue as Command.HandleExternalAppLink
val command = commandCaptor.lastValue as Command.HandleNonHttpAppLink
assertTrue(command.headers.isEmpty())
}

Expand Down Expand Up @@ -3197,6 +3215,39 @@ class BrowserTabViewModelTest {
assertCommandNotIssued<Command.ShowEmailTooltip>()
}

@Test
fun whenHandleAppLinkCalledThenHandleAppLink() {
val urlType = SpecialUrlDetector.UrlType.AppLink(uriString = "http://example.com")
testee.handleAppLink(urlType, isRedirect = false, isForMainFrame = true)
verify(mockAppLinksHandler).handleAppLink(isRedirect = eq(false), isForMainFrame = eq(true), capture(appLinkCaptor))
appLinkCaptor.value.invoke()
assertCommandIssued<Command.HandleAppLink>()
}

@Test
fun whenHandleNonHttpAppLinkCalledThenHandleNonHttpAppLink() {
val urlType = SpecialUrlDetector.UrlType.NonHttpAppLink("market://details?id=com.example", Intent(), "http://example.com")
testee.handleNonHttpAppLink(urlType, false)
verify(mockAppLinksHandler).handleNonHttpAppLink(isRedirect = eq(false), capture(appLinkCaptor))
appLinkCaptor.value.invoke()
assertCommandIssued<Command.HandleNonHttpAppLink>()
}

@Test
fun whenResetAppLinkStateCalledThenResetAppLinkState() {
testee.resetAppLinkState()
verify(mockAppLinksHandler).reset()
}

@Test
fun whenUserSubmittedQueryIsAppLinkThenOpenAppLinkInBrowser() {
whenever(mockOmnibarConverter.convertQueryToUrl("foo", null)).thenReturn("foo.com")
whenever(mockSpecialUrlDetector.determineType(anyString())).thenReturn(SpecialUrlDetector.UrlType.AppLink(uriString = "http://foo.com"))
testee.onUserSubmittedQuery("foo")
verify(mockAppLinksHandler).enterBrowserState()
assertCommandIssued<Navigate>()
}

private suspend fun givenFireButtonPulsing() {
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
dismissedCtaDaoChannel.send(listOf(DismissedCta(CtaId.DAX_DIALOG_TRACKERS_FOUND)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.duckduckgo.app.browser

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.webkit.*
Expand All @@ -38,12 +39,13 @@ import com.duckduckgo.app.globalprivacycontrol.GlobalPrivacyControl
import com.duckduckgo.app.runBlocking
import com.duckduckgo.app.statistics.store.OfflinePixelCountDataStore
import com.nhaarman.mockitokotlin2.*
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScope
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.lang.RuntimeException

@ExperimentalCoroutinesApi
class BrowserWebViewClientTest {
Expand All @@ -68,6 +70,7 @@ class BrowserWebViewClientTest {
private val webViewHttpAuthStore: WebViewHttpAuthStore = mock()
private val thirdPartyCookieManager: ThirdPartyCookieManager = mock()
private val emailInjector: EmailInjector = mock()
private val webResourceRequest: WebResourceRequest = mock()

@UiThreadTest
@Before
Expand All @@ -91,6 +94,7 @@ class BrowserWebViewClientTest {
emailInjector
)
testee.webViewClientListener = listener
whenever(webResourceRequest.url).thenReturn(Uri.EMPTY)
}

@UiThreadTest
Expand Down Expand Up @@ -261,6 +265,115 @@ class BrowserWebViewClientTest {
verify(uncaughtExceptionRepository).recordUncaughtException(exception, UncaughtExceptionSource.SHOULD_OVERRIDE_REQUEST)
}

@Test
fun whenAppLinkDetectedAndIsHandledThenReturnTrue() = coroutinesTestRule.runBlocking {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val urlType = SpecialUrlDetector.UrlType.AppLink(uriString = EXAMPLE_URL)
whenever(specialUrlDetector.determineType(any<Uri>())).thenReturn(urlType)
whenever(webResourceRequest.isRedirect).thenReturn(false)
whenever(webResourceRequest.isForMainFrame).thenReturn(true)
whenever(listener.handleAppLink(any(), any(), any())).thenReturn(true)
assertTrue(testee.shouldOverrideUrlLoading(webView, webResourceRequest))
verify(listener).handleAppLink(urlType, isRedirect = false, isForMainFrame = true)
}
}

@Test
fun whenAppLinkDetectedAndIsNotHandledThenReturnFalse() = coroutinesTestRule.runBlocking {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val urlType = SpecialUrlDetector.UrlType.AppLink(uriString = EXAMPLE_URL)
whenever(specialUrlDetector.determineType(any<Uri>())).thenReturn(urlType)
whenever(webResourceRequest.isRedirect).thenReturn(false)
whenever(webResourceRequest.isForMainFrame).thenReturn(true)
whenever(listener.handleAppLink(any(), any(), any())).thenReturn(false)
assertFalse(testee.shouldOverrideUrlLoading(webView, webResourceRequest))
verify(listener).handleAppLink(urlType, isRedirect = false, isForMainFrame = true)
}
}

@Test
fun whenAppLinkDetectedAndListenerIsNullThenReturnFalse() = coroutinesTestRule.runBlocking {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
whenever(specialUrlDetector.determineType(any<Uri>())).thenReturn(SpecialUrlDetector.UrlType.AppLink(uriString = EXAMPLE_URL))
testee.webViewClientListener = null
assertFalse(testee.shouldOverrideUrlLoading(webView, webResourceRequest))
verify(listener, never()).handleAppLink(any(), any(), any())
}
}

@Test
fun whenNonHttpAppLinkDetectedAndIsHandledThenReturnTrue() = coroutinesTestRule.runBlocking {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val urlType = SpecialUrlDetector.UrlType.NonHttpAppLink(EXAMPLE_URL, Intent(), EXAMPLE_URL)
whenever(specialUrlDetector.determineType(any<Uri>())).thenReturn(urlType)
whenever(webResourceRequest.isRedirect).thenReturn(false)
whenever(listener.handleNonHttpAppLink(any(), any())).thenReturn(true)
assertTrue(testee.shouldOverrideUrlLoading(webView, webResourceRequest))
verify(listener).handleNonHttpAppLink(urlType, isRedirect = false)
}
}

@Test
fun whenNonHttpAppLinkDetectedAndIsHandledOnApiLessThan24ThenReturnTrue() = coroutinesTestRule.runBlocking {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
val urlType = SpecialUrlDetector.UrlType.NonHttpAppLink(EXAMPLE_URL, Intent(), EXAMPLE_URL)
whenever(specialUrlDetector.determineType(any<Uri>())).thenReturn(urlType)
whenever(listener.handleNonHttpAppLink(any(), any())).thenReturn(true)
assertTrue(testee.shouldOverrideUrlLoading(webView, EXAMPLE_URL))
verify(listener).handleNonHttpAppLink(urlType, isRedirect = false)
}
}

@Test
fun whenNonHttpAppLinkDetectedAndIsNotHandledThenReturnFalse() = coroutinesTestRule.runBlocking {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val urlType = SpecialUrlDetector.UrlType.NonHttpAppLink(EXAMPLE_URL, Intent(), EXAMPLE_URL)
whenever(specialUrlDetector.determineType(any<Uri>())).thenReturn(urlType)
whenever(webResourceRequest.isRedirect).thenReturn(false)
whenever(listener.handleNonHttpAppLink(any(), any())).thenReturn(false)
assertFalse(testee.shouldOverrideUrlLoading(webView, webResourceRequest))
verify(listener).handleNonHttpAppLink(urlType, isRedirect = false)
}
}

@Test
fun whenNonHttpAppLinkDetectedAndIsNotHandledOnApiLessThan24ThenReturnFalse() = coroutinesTestRule.runBlocking {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
val urlType = SpecialUrlDetector.UrlType.NonHttpAppLink(EXAMPLE_URL, Intent(), EXAMPLE_URL)
whenever(specialUrlDetector.determineType(any<Uri>())).thenReturn(urlType)
whenever(listener.handleNonHttpAppLink(any(), any())).thenReturn(false)
assertFalse(testee.shouldOverrideUrlLoading(webView, EXAMPLE_URL))
verify(listener).handleNonHttpAppLink(urlType, isRedirect = false)
}
}

@Test
fun whenNonHttpAppLinkDetectedAndListenerIsNullThenReturnTrue() = coroutinesTestRule.runBlocking {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
whenever(specialUrlDetector.determineType(any<Uri>())).thenReturn(SpecialUrlDetector.UrlType.NonHttpAppLink(EXAMPLE_URL, Intent(), EXAMPLE_URL))
testee.webViewClientListener = null
assertTrue(testee.shouldOverrideUrlLoading(webView, webResourceRequest))
verify(listener, never()).handleNonHttpAppLink(any(), any())
}
}

@Test
fun whenNonHttpAppLinkDetectedAndListenerIsNullOnApiLessThan24ThenReturnTrue() = coroutinesTestRule.runBlocking {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
whenever(specialUrlDetector.determineType(any<Uri>())).thenReturn(SpecialUrlDetector.UrlType.NonHttpAppLink(EXAMPLE_URL, Intent(), EXAMPLE_URL))
testee.webViewClientListener = null
assertTrue(testee.shouldOverrideUrlLoading(webView, EXAMPLE_URL))
verify(listener, never()).handleNonHttpAppLink(any(), any())
}
}

@UiThreadTest
@Test
fun whenOnPageStartedCalledThenResetAppLinkState() {
testee.onPageStarted(webView, EXAMPLE_URL, null)
verify(listener).resetAppLinkState()
}

private class TestWebView(context: Context) : WebView(context)

companion object {
Expand Down
Loading