@@ -52,13 +52,28 @@ import com.duckduckgo.app.browser.session.WebViewSessionStorage
5252import com.duckduckgo.app.cta.db.DismissedCtaDao
5353import com.duckduckgo.app.cta.model.CtaId
5454import com.duckduckgo.app.cta.model.DismissedCta
55- import com.duckduckgo.app.cta.ui.*
5655import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteDao
5756import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteEntity
5857import 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
5966import com.duckduckgo.app.global.db.AppDatabase
6067import com.duckduckgo.app.global.install.AppInstallStore
68+ import com.duckduckgo.app.global.model.Site
6169import 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
6277import com.duckduckgo.app.onboarding.store.OnboardingStore
6378import com.duckduckgo.app.onboarding.store.UserStageStore
6479import 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