Skip to content

Commit 453f43e

Browse files
committed
Merge branch 'release/5.59.0' into main
2 parents bb7c0c0 + 72446f8 commit 453f43e

File tree

58 files changed

+2812
-153
lines changed

Some content is hidden

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

58 files changed

+2812
-153
lines changed

app/schemas/com.duckduckgo.app.global.db.AppDatabase/22.json

Lines changed: 746 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 238 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,28 @@ import com.duckduckgo.app.browser.session.WebViewSessionStorage
5252
import com.duckduckgo.app.cta.db.DismissedCtaDao
5353
import com.duckduckgo.app.cta.model.CtaId
5454
import com.duckduckgo.app.cta.model.DismissedCta
55-
import com.duckduckgo.app.cta.ui.*
5655
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteDao
5756
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteEntity
5857
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteRepository
58+
import com.duckduckgo.app.cta.ui.Cta
59+
import com.duckduckgo.app.cta.ui.CtaViewModel
60+
import com.duckduckgo.app.cta.ui.DaxBubbleCta
61+
import com.duckduckgo.app.cta.ui.DaxDialogCta
62+
import com.duckduckgo.app.cta.ui.HomePanelCta
63+
import com.duckduckgo.app.cta.ui.UseOurAppCta
64+
import com.duckduckgo.app.global.useourapp.UseOurAppDetector.Companion.USE_OUR_APP_DOMAIN
65+
import com.duckduckgo.app.global.useourapp.UseOurAppDetector.Companion.USE_OUR_APP_SHORTCUT_URL
5966
import com.duckduckgo.app.global.db.AppDatabase
6067
import com.duckduckgo.app.global.install.AppInstallStore
68+
import com.duckduckgo.app.global.model.Site
6169
import com.duckduckgo.app.global.model.SiteFactory
70+
import com.duckduckgo.app.global.events.db.UserEventKey
71+
import com.duckduckgo.app.notification.model.UseOurAppNotification
72+
import com.duckduckgo.app.global.events.db.UserEventEntity
73+
import com.duckduckgo.app.global.events.db.UserEventsStore
74+
import com.duckduckgo.app.global.useourapp.UseOurAppDetector
75+
import com.duckduckgo.app.notification.db.NotificationDao
76+
import com.duckduckgo.app.onboarding.store.AppStage
6277
import com.duckduckgo.app.onboarding.store.OnboardingStore
6378
import com.duckduckgo.app.onboarding.store.UserStageStore
6479
import com.duckduckgo.app.privacy.db.NetworkLeaderboardDao
@@ -188,6 +203,12 @@ class BrowserTabViewModelTest {
188203
@Mock
189204
private lateinit var mockNavigationAwareLoginDetector: NavigationAwareLoginDetector
190205

206+
@Mock
207+
private lateinit var mockUserEventsStore: UserEventsStore
208+
209+
@Mock
210+
private lateinit var mockNotificationDao: NotificationDao
211+
191212
private lateinit var mockAutoCompleteApi: AutoCompleteApi
192213

193214
private lateinit var ctaViewModel: CtaViewModel
@@ -227,6 +248,8 @@ class BrowserTabViewModelTest {
227248
mockSettingsStore,
228249
mockOnboardingStore,
229250
mockUserStageStore,
251+
mockUserEventsStore,
252+
UseOurAppDetector(mockUserEventsStore),
230253
coroutineRule.testDispatcherProvider
231254
)
232255

@@ -263,6 +286,9 @@ class BrowserTabViewModelTest {
263286
dispatchers = coroutineRule.testDispatcherProvider,
264287
fireproofWebsiteRepository = FireproofWebsiteRepository(fireproofWebsiteDao, coroutineRule.testDispatcherProvider),
265288
navigationAwareLoginDetector = mockNavigationAwareLoginDetector,
289+
userEventsStore = mockUserEventsStore,
290+
notificationDao = mockNotificationDao,
291+
useOurAppDetector = UseOurAppDetector(mockUserEventsStore),
266292
variantManager = mockVariantManager
267293
)
268294

@@ -313,19 +339,31 @@ class BrowserTabViewModelTest {
313339
}
314340

315341
@Test
316-
fun whenViewIsResumedAndBrowserShowingThenKeyboardHidden() {
317-
setBrowserShowing(true)
318-
testee.onViewResumed()
342+
fun whenViewBecomesVisibleAndHomeShowingAndUserIsNotInUseOurAppOnboardingStageThenKeyboardShown() = coroutineRule.runBlocking {
343+
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.ESTABLISHED)
344+
setBrowserShowing(false)
345+
346+
testee.onViewVisible()
319347
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
320-
assertTrue(commandCaptor.allValues.contains(Command.HideKeyboard))
348+
assertTrue(commandCaptor.allValues.contains(Command.ShowKeyboard))
321349
}
322350

323351
@Test
324-
fun whenViewIsResumedAndHomeShowingThenKeyboardShown() {
352+
fun whenViewBecomesVisibleAndHomeShowingAndUserIsInUseOurAppOnboardingStageThenKeyboardHidden() = coroutineRule.runBlocking {
353+
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.USE_OUR_APP_ONBOARDING)
325354
setBrowserShowing(false)
326-
testee.onViewResumed()
355+
356+
testee.onViewVisible()
327357
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
328-
assertTrue(commandCaptor.allValues.contains(Command.ShowKeyboard))
358+
assertTrue(commandCaptor.allValues.contains(Command.HideKeyboard))
359+
}
360+
361+
@Test
362+
fun whenViewBecomesVisibleAndBrowserShowingThenKeyboardHidden() {
363+
setBrowserShowing(true)
364+
testee.onViewVisible()
365+
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
366+
assertTrue(commandCaptor.allValues.contains(Command.HideKeyboard))
329367
}
330368

331369
@Test
@@ -1509,7 +1547,15 @@ class BrowserTabViewModelTest {
15091547
fun whenUserPressesBackAndNotSkippingHomeThenWebViewPreviewNotGenerated() {
15101548
setupNavigation(isBrowsing = true, canGoBack = false, skipHome = false)
15111549
testee.onUserPressedBack()
1512-
verify(mockCommandObserver, never()).onChanged(commandCaptor.capture())
1550+
assertFalse(commandCaptor.allValues.contains(Command.GenerateWebViewPreviewImage))
1551+
}
1552+
1553+
@Test
1554+
fun whenUserPressesBackAndGoesToHomeThenKeyboardShown() {
1555+
setupNavigation(isBrowsing = true, canGoBack = false, skipHome = false)
1556+
testee.onUserPressedBack()
1557+
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
1558+
assertTrue(commandCaptor.allValues.contains(Command.ShowKeyboard))
15131559
}
15141560

15151561
@Test
@@ -1650,6 +1696,16 @@ class BrowserTabViewModelTest {
16501696
assertCommandIssued<Command.LaunchLegacyAddWidget>()
16511697
}
16521698

1699+
@Test
1700+
fun whenUserClickedUseOurAppCtaOkButtonThenLaunchAddHomeShortcutAndNavigateCommand() {
1701+
whenever(mockOmnibarConverter.convertQueryToUrl(USE_OUR_APP_SHORTCUT_URL, null)).thenReturn(USE_OUR_APP_SHORTCUT_URL)
1702+
val cta = UseOurAppCta()
1703+
setCta(cta)
1704+
testee.onUserClickCtaOkButton()
1705+
assertCommandIssued<Command.AddHomeShortcut>()
1706+
assertCommandIssued<Navigate>()
1707+
}
1708+
16531709
@Test
16541710
fun whenSurveyCtaDismissedAndNoOtherCtaPossibleCtaIsNull() = coroutineRule.runBlocking {
16551711
givenShownCtas(CtaId.DAX_INTRO, CtaId.DAX_END)
@@ -1727,6 +1783,14 @@ class BrowserTabViewModelTest {
17271783
verify(mockSurveyDao).cancelScheduledSurveys()
17281784
}
17291785

1786+
@Test
1787+
fun whenUserClickedSecondaryCtaButtonInUseOurAppCtaThenLaunchShowKeyboardCommand() {
1788+
val cta = UseOurAppCta()
1789+
setCta(cta)
1790+
testee.onUserClickCtaSecondaryButton()
1791+
assertCommandIssued<Command.ShowKeyboard>()
1792+
}
1793+
17301794
@Test
17311795
fun whenSurrogateDetectedThenSiteUpdated() {
17321796
givenOneActiveTabSelected()
@@ -1915,6 +1979,30 @@ class BrowserTabViewModelTest {
19151979
}
19161980
}
19171981

1982+
@Test
1983+
fun whenLoginDetectedAndUrlIsUseOurAppThenRegisterUserEvent() = coroutineRule.runBlocking {
1984+
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)).thenReturn(null)
1985+
loginEventLiveData.value = givenLoginDetected(USE_OUR_APP_SHORTCUT_URL)
1986+
1987+
verify(mockUserEventsStore).registerUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)
1988+
}
1989+
1990+
@Test
1991+
fun whenLoginDetectedAndUrlIsNotUseOurAppThenDoNotRegisterUserEvent() = coroutineRule.runBlocking {
1992+
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)).thenReturn(null)
1993+
loginEventLiveData.value = givenLoginDetected("example.com")
1994+
1995+
verify(mockUserEventsStore, never()).registerUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)
1996+
}
1997+
1998+
@Test
1999+
fun whenLoginDetectedAndDialogAlreadySeenThenDoNotRegisterUserEvent() = coroutineRule.runBlocking {
2000+
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)).thenReturn(UserEventEntity(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN))
2001+
loginEventLiveData.value = givenLoginDetected(USE_OUR_APP_SHORTCUT_URL)
2002+
2003+
verify(mockUserEventsStore, never()).registerUserEvent(UserEventKey.USE_OUR_APP_FIREPROOF_DIALOG_SEEN)
2004+
}
2005+
19182006
@Test
19192007
fun whenUserBrowsingPressesBackThenCannotAddBookmark() {
19202008
setupNavigation(skipHome = false, isBrowsing = true, canGoBack = false)
@@ -2057,6 +2145,127 @@ class BrowserTabViewModelTest {
20572145
testee.onUserSubmittedQuery("about:blank")
20582146
}
20592147

2148+
@Test
2149+
fun whenViewReadyIfDomainSameAsUseOurAppAfterNotificationSeenThenPixelSent() = coroutineRule.runBlocking {
2150+
givenUseOurAppSiteSelected()
2151+
whenever(mockNotificationDao.exists(UseOurAppNotification.ID)).thenReturn(true)
2152+
2153+
testee.onViewReady()
2154+
2155+
verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_NOTIFICATION)
2156+
}
2157+
2158+
@Test
2159+
fun whenViewReadyIfDomainSameAsUseOurAppAfterShortcutAddedThenPixelSent() = coroutineRule.runBlocking {
2160+
givenUseOurAppSiteSelected()
2161+
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED)).thenReturn(UserEventEntity(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED))
2162+
2163+
testee.onViewReady()
2164+
2165+
verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_SHORTCUT)
2166+
}
2167+
2168+
@Test
2169+
fun whenViewReadyIfDomainSameAsUseOurAppAfterDeleteCtaShownThenPixelSent() = coroutineRule.runBlocking {
2170+
givenUseOurAppSiteSelected()
2171+
whenever(mockDismissedCtaDao.exists(CtaId.USE_OUR_APP_DELETION)).thenReturn(true)
2172+
2173+
testee.onViewReady()
2174+
2175+
verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_DELETE_CTA)
2176+
}
2177+
2178+
@Test
2179+
fun whenViewReadyIfDomainIsNotTheSameAsUseOurAppAfterNotificationSeenThenPixelNotSent() = coroutineRule.runBlocking {
2180+
givenUseOurAppSiteIsNotSelected()
2181+
whenever(mockNotificationDao.exists(UseOurAppNotification.ID)).thenReturn(true)
2182+
2183+
testee.onViewReady()
2184+
2185+
verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_NOTIFICATION)
2186+
}
2187+
2188+
@Test
2189+
fun whenViewReadyIfDomainIsNotTheSameAsUseOurAppAfterShortcutAddedThenPixelNotSent() = coroutineRule.runBlocking {
2190+
givenUseOurAppSiteIsNotSelected()
2191+
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED)).thenReturn(UserEventEntity(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED))
2192+
2193+
testee.onViewReady()
2194+
2195+
verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_SHORTCUT)
2196+
}
2197+
2198+
@Test
2199+
fun whenViewReadyIfDomainIsNotTheSameAsUseOurAppAfterDeleteCtaShownThenPixelNotSent() = coroutineRule.runBlocking {
2200+
givenUseOurAppSiteIsNotSelected()
2201+
whenever(mockDismissedCtaDao.exists(CtaId.USE_OUR_APP_DELETION)).thenReturn(true)
2202+
2203+
testee.onViewReady()
2204+
2205+
verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_DELETE_CTA)
2206+
}
2207+
2208+
@Test
2209+
fun whenPageChangedIfPreviousOneWasNotUseOurAppSiteAfterNotificationSeenThenPixelSent() = coroutineRule.runBlocking {
2210+
givenUseOurAppSiteIsNotSelected()
2211+
whenever(mockNotificationDao.exists(UseOurAppNotification.ID)).thenReturn(true)
2212+
2213+
loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)
2214+
2215+
verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_NOTIFICATION)
2216+
}
2217+
2218+
@Test
2219+
fun whenPageChangedIfPreviousOneWasNotUseOurAppSiteAfterShortcutAddedThenPixelSent() = coroutineRule.runBlocking {
2220+
givenUseOurAppSiteIsNotSelected()
2221+
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED)).thenReturn(UserEventEntity(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED))
2222+
2223+
loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)
2224+
2225+
verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_SHORTCUT)
2226+
}
2227+
2228+
@Test
2229+
fun whenPageChangedIfPreviousOneWasNotUseOurAppSiteAfterDeleteCtaShownThenPixelSent() = coroutineRule.runBlocking {
2230+
givenUseOurAppSiteIsNotSelected()
2231+
whenever(mockDismissedCtaDao.exists(CtaId.USE_OUR_APP_DELETION)).thenReturn(true)
2232+
2233+
loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)
2234+
2235+
verify(mockPixel).fire(Pixel.PixelName.UOA_VISITED_AFTER_DELETE_CTA)
2236+
}
2237+
2238+
@Test
2239+
fun whenPageChangedIfPreviousOneWasUseOurAppSiteAfterNotificationSeenThenPixelNotSent() = coroutineRule.runBlocking {
2240+
givenUseOurAppSiteSelected()
2241+
whenever(mockNotificationDao.exists(UseOurAppNotification.ID)).thenReturn(true)
2242+
2243+
loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)
2244+
2245+
verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_NOTIFICATION)
2246+
}
2247+
2248+
@Test
2249+
fun whenPageChangedIfPreviousOneWasUseOurAppSiteAfterShortcutAddedThenPixelNotSent() = coroutineRule.runBlocking {
2250+
givenUseOurAppSiteSelected()
2251+
val timestampEntity = UserEventEntity(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED)
2252+
whenever(mockUserEventsStore.getUserEvent(UserEventKey.USE_OUR_APP_SHORTCUT_ADDED)).thenReturn(timestampEntity)
2253+
2254+
loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)
2255+
2256+
verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_SHORTCUT)
2257+
}
2258+
2259+
@Test
2260+
fun whenPageChangedIfPreviousOneWasUseOurAppSiteThenAfterDeleteCtaShownPixelNotSent() = coroutineRule.runBlocking {
2261+
givenUseOurAppSiteSelected()
2262+
whenever(mockDismissedCtaDao.exists(CtaId.USE_OUR_APP_DELETION)).thenReturn(true)
2263+
2264+
loadUrl(USE_OUR_APP_DOMAIN, isBrowserShowing = true)
2265+
2266+
verify(mockPixel, never()).fire(Pixel.PixelName.UOA_VISITED_AFTER_DELETE_CTA)
2267+
}
2268+
20602269
private inline fun <reified T : Command> assertCommandIssued(instanceAssertions: T.() -> Unit = {}) {
20612270
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
20622271
val issuedCommand = commandCaptor.allValues.find { it is T }
@@ -2096,6 +2305,26 @@ class BrowserTabViewModelTest {
20962305
testee.loadData("TAB_ID", "https://example.com", false)
20972306
}
20982307

2308+
private fun givenUseOurAppSiteSelected() {
2309+
whenever(mockOmnibarConverter.convertQueryToUrl(USE_OUR_APP_DOMAIN, null)).thenReturn(USE_OUR_APP_DOMAIN)
2310+
val site: Site = mock()
2311+
whenever(site.url).thenReturn(USE_OUR_APP_DOMAIN)
2312+
val siteLiveData = MutableLiveData<Site>()
2313+
siteLiveData.value = site
2314+
whenever(mockTabsRepository.retrieveSiteData("TAB_ID")).thenReturn(siteLiveData)
2315+
testee.loadData("TAB_ID", USE_OUR_APP_DOMAIN, false)
2316+
}
2317+
2318+
private fun givenUseOurAppSiteIsNotSelected() {
2319+
whenever(mockOmnibarConverter.convertQueryToUrl("example.com", null)).thenReturn("example.com")
2320+
val site: Site = mock()
2321+
whenever(site.url).thenReturn("example.com")
2322+
val siteLiveData = MutableLiveData<Site>()
2323+
siteLiveData.value = site
2324+
whenever(mockTabsRepository.retrieveSiteData("TAB_ID")).thenReturn(siteLiveData)
2325+
testee.loadData("TAB_ID", "example.com", false)
2326+
}
2327+
20992328
private fun givenFireproofWebsiteDomain(vararg fireproofWebsitesDomain: String) {
21002329
fireproofWebsitesDomain.forEach {
21012330
fireproofWebsiteDao.insert(FireproofWebsiteEntity(domain = it))

0 commit comments

Comments
 (0)