Skip to content

Commit 38045a5

Browse files
committed
Tests
1 parent a8dab6c commit 38045a5

File tree

16 files changed

+758
-133
lines changed

16 files changed

+758
-133
lines changed

content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/messaging/ContentScopeScriptsJsMessaging.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,6 @@ class ContentScopeScriptsJsMessaging @Inject constructor(
6565
message: String,
6666
secret: String,
6767
) {
68-
logcat {
69-
"Marcos is $message"
70-
}
7168
try {
7269
val adapter = moshi.adapter(JsMessage::class.java)
7370
val jsMessage = adapter.fromJson(message)

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/RealDuckChat.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,9 @@ interface DuckChatInternal : DuckChat {
215215

216216
/**
217217
* This method takes a [url] and returns `true` or `false`.
218-
* @return `true` if the given [url] belongs to the duck.ai domain (apex or subdomain) or if it's the revoke url and `false` otherwise.
218+
* @return `true` if the given [url] can be handled in the duck ai webview and `false` otherwise.
219219
*/
220-
fun isDuckAiUrl(url: String): Boolean
220+
fun canHandleOnAiWebView(url: String): Boolean
221221

222222
/**
223223
* Indicates whether Input Screen will present the input box at the bottom, if user has the omnibar also set to the bottom position.
@@ -437,7 +437,7 @@ class RealDuckChat @Inject constructor(
437437
}
438438
}
439439

440-
override fun isDuckAiUrl(url: String): Boolean {
440+
override fun canHandleOnAiWebView(url: String): Boolean {
441441
return runCatching { HOST_DUCK_AI == url.toHttpUrl().topPrivateDomain() || url == REVOKE_URL }.getOrElse { false }
442442
}
443443

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/helper/DuckChatJSHelper.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class RealDuckChatJSHelper @Inject constructor(
116116
}
117117
null
118118
}
119+
119120
else -> null
120121
}
121122

@@ -190,6 +191,7 @@ class RealDuckChatJSHelper @Inject constructor(
190191
}
191192
}
192193
}
194+
193195
companion object {
194196
const val DUCK_CHAT_FEATURE_NAME = "aiChat"
195197
private const val METHOD_GET_AI_CHAT_NATIVE_HANDOFF_DATA = "getAIChatNativeHandoffData"

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/messaging/DuckChatStandaloneJsMessageHandlers.kt

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,21 @@ class StoreMigrationDataHandler @Inject constructor(
7575
jsMessageCallback: JsMessageCallback?,
7676
) {
7777
if (jsMessage.id.isNullOrEmpty()) return
78-
7978
val item = jsMessage.params.optString(SERIALIZED_MIGRATION_FILE)
80-
val jsonPayload = JSONObject()
81-
if (item != null && item != JSONObject.NULL) {
79+
80+
val result = if (!item.isNullOrEmpty() && item != JSONObject.NULL.toString()) {
8281
standaloneDuckChatStore.storeMigrationItem(item)
83-
jsonPayload.put(OK, true)
82+
true
8483
} else {
85-
jsonPayload.put(OK, false)
86-
jsonPayload.put(REASON, "Missing or invalid serializedMigrationFile")
84+
false
8785
}
88-
jsMessaging.onResponse(JsCallbackData(jsonPayload, featureName, jsMessage.method, jsMessage.id!!))
86+
87+
val payload = JSONObject().apply {
88+
put(OK, result)
89+
if (!result) put(REASON, "Missing or invalid serializedMigrationFile")
90+
}
91+
92+
jsMessaging.onResponse(JsCallbackData(payload, featureName, jsMessage.method, jsMessage.id!!))
8993
}
9094

9195
override val allowedDomains: List<String> =
@@ -113,12 +117,12 @@ class GetMigrationInfoHandler @Inject constructor(
113117
if (jsMessage.id.isNullOrEmpty()) return
114118

115119
val count = standaloneDuckChatStore.getMigrationItemCount()
116-
val jsonPayload = JSONObject().apply {
120+
val payload = JSONObject().apply {
117121
put(OK, true)
118122
put(COUNT, count)
119123
}
120124

121-
jsMessaging.onResponse(JsCallbackData(jsonPayload, featureName, jsMessage.method, jsMessage.id!!))
125+
jsMessaging.onResponse(JsCallbackData(payload, featureName, jsMessage.method, jsMessage.id!!))
122126
}
123127

124128
override val allowedDomains: List<String> =
@@ -146,15 +150,17 @@ class GetMigrationDataByIndexHandler @Inject constructor(
146150
if (jsMessage.id.isNullOrEmpty()) return
147151
val index = jsMessage.params.optInt(INDEX, -1)
148152
val value = standaloneDuckChatStore.getMigrationItemByIndex(index)
149-
val jsonPayload = JSONObject()
150-
if (value == null) {
151-
jsonPayload.put(OK, false)
152-
jsonPayload.put(REASON, "nothing at index: $index")
153-
} else {
154-
jsonPayload.put(OK, true)
155-
jsonPayload.put(SERIALIZED_MIGRATION_FILE, value)
153+
154+
val payload = JSONObject().apply {
155+
if (value == null) {
156+
put(OK, false)
157+
put(REASON, "nothing at index: $index")
158+
} else {
159+
put(OK, true)
160+
put(SERIALIZED_MIGRATION_FILE, value)
161+
}
156162
}
157-
jsMessaging.onResponse(JsCallbackData(jsonPayload, featureName, jsMessage.method, jsMessage.id!!))
163+
jsMessaging.onResponse(JsCallbackData(payload, featureName, jsMessage.method, jsMessage.id!!))
158164
}
159165

160166
override val allowedDomains: List<String> =

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatWebViewClient.kt

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import android.webkit.WebViewClient
2222
import androidx.annotation.UiThread
2323
import com.duckduckgo.browser.api.JsInjectorPlugin
2424
import com.duckduckgo.common.utils.plugins.PluginPoint
25-
import logcat.logcat
2625
import javax.inject.Inject
2726

2827
class DuckChatWebViewClient @Inject constructor(
@@ -39,12 +38,4 @@ class DuckChatWebViewClient @Inject constructor(
3938
it.onPageStarted(webView, url, null)
4039
}
4140
}
42-
43-
override fun onPageFinished(
44-
view: WebView?,
45-
url: String?,
46-
) {
47-
logcat { "Marcos url is $url and ${view?.url}" }
48-
super.onPageFinished(view, url)
49-
}
5041
}

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatWebViewFragment.kt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -605,17 +605,25 @@ open class DuckChatWebViewFragment : DuckDuckGoFragment(R.layout.activity_duck_c
605605
}
606606

607607
fun onBackPressed(): Boolean {
608-
if (isVisible) {
609-
if (simpleWebview.canGoBack()) {
610-
simpleWebview.goBack()
611-
} else {
612-
hideSoftKeyboard()
613-
duckChat.closeDuckChat()
614-
}
608+
if (!isVisible) return false
609+
610+
if (!simpleWebview.canGoBack()) {
611+
exit()
615612
return true
613+
}
614+
615+
val history = simpleWebview.copyBackForwardList()
616+
if (viewModel.shouldCloseDuckChat(history)) {
617+
exit()
616618
} else {
617-
return false
619+
simpleWebview.goBack()
618620
}
621+
return true
622+
}
623+
624+
private fun exit() {
625+
hideSoftKeyboard()
626+
duckChat.closeDuckChat()
619627
}
620628

621629
override fun continueDownload(pendingFileDownload: PendingFileDownload) {

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatWebViewViewModel.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616

1717
package com.duckduckgo.duckchat.impl.ui
1818

19+
import android.webkit.WebBackForwardList
1920
import androidx.lifecycle.ViewModel
2021
import androidx.lifecycle.viewModelScope
2122
import com.duckduckgo.anvil.annotations.ContributesViewModel
23+
import com.duckduckgo.common.utils.AppUrl
2224
import com.duckduckgo.di.scopes.FragmentScope
25+
import com.duckduckgo.duckchat.impl.DuckChatConstants.HOST_DUCK_AI
2326
import com.duckduckgo.duckchat.impl.DuckChatInternal
24-
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.ViewState
2527
import com.duckduckgo.subscriptions.api.Subscriptions
2628
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
2729
import kotlinx.coroutines.channels.Channel
@@ -32,6 +34,7 @@ import kotlinx.coroutines.flow.launchIn
3234
import kotlinx.coroutines.flow.onEach
3335
import kotlinx.coroutines.flow.receiveAsFlow
3436
import kotlinx.coroutines.flow.stateIn
37+
import okhttp3.HttpUrl.Companion.toHttpUrl
3538
import javax.inject.Inject
3639

3740
@ContributesViewModel(FragmentScope::class)
@@ -67,7 +70,17 @@ class DuckChatWebViewViewModel @Inject constructor(
6770

6871
fun handleOnSameWebView(url: String): Boolean {
6972
// Allow Duck.ai links to load within the same WebView (in-sheet navigation)
70-
return duckChat.isDuckAiUrl(url)
73+
return duckChat.canHandleOnAiWebView(url)
74+
}
75+
76+
fun shouldCloseDuckChat(history: WebBackForwardList): Boolean {
77+
return runCatching {
78+
if (!duckChat.isStandaloneMigrationEnabled()) return false
79+
val currentItem = history.currentItem?.url
80+
val firstItem = history.getItemAtIndex(0).url
81+
currentItem?.toHttpUrl()?.topPrivateDomain() == HOST_DUCK_AI &&
82+
firstItem.toHttpUrl().topPrivateDomain() == AppUrl.Url.HOST
83+
}.getOrElse { false }
7184
}
7285

7386
private fun observeSubscriptionChanges() {
@@ -77,4 +90,7 @@ class DuckChatWebViewViewModel @Inject constructor(
7790
commandChannel.trySend(Command.SendSubscriptionAuthUpdateEvent)
7891
}.launchIn(viewModelScope)
7992
}
93+
94+
companion object {
95+
}
8096
}

duckchat/duckchat-impl/src/test/java/com/duckduckgo/duckchat/impl/ui/DuckChatWebViewViewModelTest.kt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.duckduckgo.duckchat.impl.ui
1818

19+
import android.webkit.WebBackForwardList
20+
import android.webkit.WebHistoryItem
1921
import androidx.test.ext.junit.runners.AndroidJUnit4
2022
import app.cash.turbine.test
2123
import com.duckduckgo.common.test.CoroutineTestRule
@@ -36,7 +38,9 @@ import org.junit.Before
3638
import org.junit.Rule
3739
import org.junit.Test
3840
import org.junit.runner.RunWith
41+
import org.mockito.kotlin.doReturn
3942
import org.mockito.kotlin.mock
43+
import org.mockito.kotlin.verify
4044
import org.mockito.kotlin.whenever
4145

4246
@RunWith(AndroidJUnit4::class)
@@ -188,4 +192,67 @@ class DuckChatWebViewViewModelTest {
188192
assertFalse(state.isFullScreenModeEnabled)
189193
}
190194
}
195+
196+
@Test
197+
fun `when handle on same webview then call duck chat`() {
198+
viewModel.handleOnSameWebView("https://duck.ai/somepath")
199+
verify(duckChat).canHandleOnAiWebView("https://duck.ai/somepath")
200+
}
201+
202+
@Test
203+
fun `when should close duck chat and current is duck ai and first is duckduckgo then return true`() {
204+
whenever(duckChat.isStandaloneMigrationEnabled()).thenReturn(true)
205+
val history = mock<WebBackForwardList>()
206+
207+
val currentItem = mock<WebHistoryItem> {
208+
on { url } doReturn "https://duck.ai/somepath"
209+
}
210+
val firstItem = mock<WebHistoryItem> {
211+
on { url } doReturn "https://duckduckgo.com/somepath"
212+
}
213+
whenever(history.currentItem).thenReturn(currentItem)
214+
whenever(history.getItemAtIndex(0)).thenReturn(firstItem)
215+
assertTrue(viewModel.shouldCloseDuckChat(history))
216+
}
217+
218+
@Test
219+
fun `when should close duck chat and current is not duck ai or first is no duckduckgo then return false`() {
220+
whenever(duckChat.isStandaloneMigrationEnabled()).thenReturn(true)
221+
val history = mock<WebBackForwardList>()
222+
223+
var currentItem = mock<WebHistoryItem> {
224+
on { url } doReturn "https://duckduckgo.com/somepath"
225+
}
226+
var firstItem = mock<WebHistoryItem> {
227+
on { url } doReturn "https://duckduckgo.com/somepath"
228+
}
229+
whenever(history.currentItem).thenReturn(currentItem)
230+
whenever(history.getItemAtIndex(0)).thenReturn(firstItem)
231+
assertFalse(viewModel.shouldCloseDuckChat(history))
232+
currentItem = mock<WebHistoryItem> {
233+
on { url } doReturn "https://duck.ai/somepath"
234+
}
235+
firstItem = mock<WebHistoryItem> {
236+
on { url } doReturn "https://somesite.com"
237+
}
238+
whenever(history.currentItem).thenReturn(currentItem)
239+
whenever(history.getItemAtIndex(0)).thenReturn(firstItem)
240+
assertFalse(viewModel.shouldCloseDuckChat(history))
241+
}
242+
243+
@Test
244+
fun `when should close duck chat and feature flag is disabled then return false`() {
245+
whenever(duckChat.isStandaloneMigrationEnabled()).thenReturn(false)
246+
val history = mock<WebBackForwardList>()
247+
248+
val currentItem = mock<WebHistoryItem> {
249+
on { url } doReturn "https://duck.ai/somepath"
250+
}
251+
val firstItem = mock<WebHistoryItem> {
252+
on { url } doReturn "https://duckduckgo.com/somepath"
253+
}
254+
whenever(history.currentItem).thenReturn(currentItem)
255+
whenever(history.getItemAtIndex(0)).thenReturn(firstItem)
256+
assertFalse(viewModel.shouldCloseDuckChat(history))
257+
}
191258
}

duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/RealDuckChatTest.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,18 @@ class RealDuckChatTest {
10901090
assertTrue(testee.showInputScreenOnSystemSearchLaunch.value)
10911091
}
10921092

1093+
@Test
1094+
fun `when url is from duck ai return true`() {
1095+
assertTrue(testee.isDuckChatUrl("https://duck.ai/somepath".toUri()))
1096+
assertTrue(testee.isDuckChatUrl("https://duck.ai/somepath/someotherpath?test=1".toUri()))
1097+
assertTrue(testee.isDuckChatUrl("https://duck.ai".toUri()))
1098+
}
1099+
1100+
@Test
1101+
fun `when url is revoke url return true`() {
1102+
assertTrue(testee.canHandleOnAiWebView("https://duckduckgo.com/revoke-duckai-access"))
1103+
}
1104+
10931105
companion object {
10941106
val SETTINGS_JSON = """
10951107
{

0 commit comments

Comments
 (0)