Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Privacy Pro onboarding dialog copy test #5222

Merged
merged 10 commits into from
Nov 22, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ import com.duckduckgo.app.onboarding.store.AppStage.ESTABLISHED
import com.duckduckgo.app.onboarding.store.OnboardingStore
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingFeatureToggles
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingPixelsPlugin
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.HighlightsOnboardingExperimentManager
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_BANNER_SHOWN
Expand Down Expand Up @@ -213,6 +214,8 @@ import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Disabled
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Enabled
import com.duckduckgo.feature.toggles.api.FakeFeatureToggleFactory
import com.duckduckgo.feature.toggles.api.FakeToggleStore
import com.duckduckgo.feature.toggles.api.FeatureToggles
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.feature.toggles.api.Toggle.State
import com.duckduckgo.history.api.HistoryEntry.VisitedPage
Expand Down Expand Up @@ -467,7 +470,10 @@ class BrowserTabViewModelTest {

private val mockEnabledToggle: Toggle = mock { on { it.isEnabled() } doReturn true }

private val mockDisabledToggle: Toggle = mock { on { it.isEnabled() } doReturn false }
private val mockDisabledToggle: Toggle = mock {
on { it.isEnabled() } doReturn false
on { it.isEnabled(any()) } doReturn false
}

private val mockPrivacyProtectionsPopupManager: PrivacyProtectionsPopupManager = mock()

Expand Down Expand Up @@ -496,6 +502,9 @@ class BrowserTabViewModelTest {
private val fakeCustomHeadersPlugin = FakeCustomHeadersProvider(emptyMap())
private val mockBrokenSitePrompt: BrokenSitePrompt = mock()
private val mockTabStatsBucketing: TabStatsBucketing = mock()
private val extendedOnboardingFeatureToggles = FeatureToggles.Builder(FakeToggleStore(), featureName = "extendedOnboarding").build()
.create(ExtendedOnboardingFeatureToggles::class.java)
private val extendedOnboardingPixelsPlugin = ExtendedOnboardingPixelsPlugin(extendedOnboardingFeatureToggles)

@Before
fun before() = runTest {
Expand Down Expand Up @@ -523,6 +532,7 @@ class BrowserTabViewModelTest {
lazyFaviconManager,
)

whenever(mockExtendedOnboardingFeatureToggles.testPrivacyProOnboardingCopyNov24()).thenReturn(mockDisabledToggle)
whenever(mockHighlightsOnboardingExperimentManager.isHighlightsEnabled()).thenReturn(false)
whenever(mockDuckPlayer.observeUserPreferences()).thenReturn(flowOf(UserPreferences(false, Disabled)))
whenever(mockDismissedCtaDao.dismissedCtas()).thenReturn(dismissedCtaDaoChannel.consumeAsFlow())
Expand Down Expand Up @@ -564,6 +574,7 @@ class BrowserTabViewModelTest {
duckPlayer = mockDuckPlayer,
highlightsOnboardingExperimentManager = mockHighlightsOnboardingExperimentManager,
brokenSitePrompt = mockBrokenSitePrompt,
extendedOnboardingPixelsPlugin = extendedOnboardingPixelsPlugin,
)

val siteFactory = SiteFactoryImpl(
Expand Down Expand Up @@ -2580,10 +2591,15 @@ class BrowserTabViewModelTest {

@Test
fun whenUserClickedLearnMoreExperimentBubbleCtaButtonThenLaunchPrivacyPro() {
val cta = DaxBubbleCta.DaxPrivacyProCta(mockOnboardingStore, mockAppInstallStore)
val cta = DaxBubbleCta.DaxPrivacyProCta(
mockOnboardingStore,
mockAppInstallStore,
R.string.onboardingPrivacyProDaxDialogTitle,
R.string.onboardingPrivacyProDaxDialogDescription,
)
setCta(cta)
testee.onUserClickCtaOkButton(cta)
assertCommandIssued<Command.LaunchPrivacyPro>()
assertCommandIssued<LaunchPrivacyPro>()
}

@Test
Expand Down Expand Up @@ -6060,6 +6076,7 @@ class BrowserTabViewModelTest {
override suspend fun onToggleOff(origin: PrivacyToggleOrigin) {
toggleOff++
}

override suspend fun onToggleOn(origin: PrivacyToggleOrigin) {
toggleOn++
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import com.duckduckgo.app.onboarding.store.AppStage
import com.duckduckgo.app.onboarding.store.OnboardingStore
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingFeatureToggles
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingFeatureToggles.Cohorts
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingPixelsPlugin
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.HighlightsOnboardingExperimentManager
import com.duckduckgo.app.pixels.AppPixelName.*
import com.duckduckgo.app.privacy.db.UserAllowListRepository
Expand All @@ -45,6 +47,7 @@ import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Count
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Unique
import com.duckduckgo.app.tabs.model.TabEntity
import com.duckduckgo.app.tabs.model.TabRepository
import com.duckduckgo.app.trackerdetection.blocklist.BlockList.Cohorts.CONTROL
import com.duckduckgo.app.trackerdetection.model.Entity
import com.duckduckgo.app.trackerdetection.model.TrackerStatus
import com.duckduckgo.app.trackerdetection.model.TrackerType
Expand All @@ -58,7 +61,10 @@ import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.DISABLED
import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.ENABLED
import com.duckduckgo.duckplayer.api.DuckPlayer.UserPreferences
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk
import com.duckduckgo.feature.toggles.api.FakeToggleStore
import com.duckduckgo.feature.toggles.api.FeatureToggles
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.feature.toggles.api.Toggle.State.Cohort
import com.duckduckgo.subscriptions.api.Subscriptions
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.FlowPreview
Expand Down Expand Up @@ -127,6 +133,10 @@ class CtaViewModelTest {
CtaId.DAX_END,
)

private val extendedOnboardingFeatureToggles = FeatureToggles.Builder(FakeToggleStore(), featureName = "extendedOnboarding").build()
.create(ExtendedOnboardingFeatureToggles::class.java)
private val extendedOnboardingPixelsPlugin = ExtendedOnboardingPixelsPlugin(extendedOnboardingFeatureToggles)

private lateinit var testee: CtaViewModel

val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
Expand All @@ -141,7 +151,7 @@ class CtaViewModelTest {

val mockDisabledToggle: Toggle = mock { on { it.isEnabled() } doReturn false }
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockDisabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.privacyProCta()).thenReturn(mockDisabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.testPrivacyProOnboardingCopyNov24()).thenReturn(mockDisabledToggle)
whenever(mockAppInstallStore.installTimestamp).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1))
whenever(mockUserAllowListRepository.isDomainInUserAllowList(any())).thenReturn(false)
whenever(mockDismissedCtaDao.dismissedCtas()).thenReturn(db.dismissedCtaDao().dismissedCtas())
Expand All @@ -153,6 +163,7 @@ class CtaViewModelTest {
whenever(mockDuckPlayer.isSimulatedYoutubeNoCookie(any())).thenReturn(false)
whenever(mockBrokenSitePrompt.shouldShowBrokenSitePrompt(any())).thenReturn(false)
whenever(mockBrokenSitePrompt.isFeatureEnabled()).thenReturn(false)
whenever(mockSubscriptions.isEligible()).thenReturn(false)

testee = CtaViewModel(
appInstallStore = mockAppInstallStore,
Expand All @@ -171,6 +182,7 @@ class CtaViewModelTest {
duckPlayer = mockDuckPlayer,
highlightsOnboardingExperimentManager = mockHighlightsOnboardingExperimentManager,
brokenSitePrompt = mockBrokenSitePrompt,
extendedOnboardingPixelsPlugin = extendedOnboardingPixelsPlugin,
)
}

Expand Down Expand Up @@ -744,8 +756,9 @@ class CtaViewModelTest {
@Test
fun givenPrivacyProCtaExperimentWhenRefreshCtaOnHomeTabThenReturnPrivacyProCta() = runTest {
givenDaxOnboardingActive()
whenever(mockSubscriptions.isEligible()).thenReturn(true)
whenever(mockExtendedOnboardingFeatureToggles.noBrowserCtas()).thenReturn(mockEnabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.privacyProCta()).thenReturn(mockEnabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.testPrivacyProOnboardingCopyNov24()).thenReturn(mockEnabledToggle)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(true)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO_VISIT_SITE)).thenReturn(true)
whenever(mockDismissedCtaDao.exists(CtaId.DAX_END)).thenReturn(true)
Expand Down Expand Up @@ -847,6 +860,19 @@ class CtaViewModelTest {
assertTrue(value is OnboardingDaxDialogCta.DaxExperimentFireButtonCta)
}

@Test
fun givenPrivacyProExperimentWhenControlCohortThenAppendItToOriginForPrivacyProSubscriptionURL() = runTest {
val controlCohort = Cohort(Cohorts.CONTROL.cohortName, 1)
whenever(mockExtendedOnboardingFeatureToggles.testPrivacyProOnboardingCopyNov24().getCohort()).thenReturn(controlCohort)

assertEquals(testee.getCohortOrigin(), "_control")
}

@Test
fun whenPrivacyProExperimentIsDisabledThenCohortIsNotAppendToPrivacyProSubscriptionURL() = runTest {
assertEquals(testee.getCohortOrigin(), "")
}

private suspend fun givenDaxOnboardingActive() {
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3853,7 +3853,7 @@ class BrowserTabFragment :
when (configuration) {
is HomePanelCta -> showHomeCta(configuration)
is DaxBubbleCta.DaxExperimentIntroSearchOptionsCta, is DaxBubbleCta.DaxExperimentIntroVisitSiteOptionsCta,
is DaxBubbleCta.DaxExperimentEndCta,
is DaxBubbleCta.DaxExperimentEndCta, is DaxBubbleCta.DaxExperimentPrivacyProCta,
-> showDaxExperimentOnboardingBubbleCta(configuration as DaxBubbleCta)
is DaxBubbleCta -> showDaxOnboardingBubbleCta(configuration)
is OnboardingDaxDialogCta -> showOnboardingDialogCta(configuration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2657,7 +2657,7 @@ class BrowserTabViewModel @Inject constructor(
}

private fun showOrHideKeyboard(cta: Cta?) {
val shouldHideKeyboard = cta is HomePanelCta || cta is DaxBubbleCta.DaxPrivacyProCta
val shouldHideKeyboard = cta is HomePanelCta || cta is DaxBubbleCta.DaxPrivacyProCta || cta is DaxBubbleCta.DaxExperimentPrivacyProCta
command.value = if (shouldHideKeyboard) HideKeyboard else ShowKeyboard
}

Expand Down Expand Up @@ -2688,7 +2688,8 @@ class BrowserTabViewModel @Inject constructor(
fun onUserClickCtaSecondaryButton(cta: Cta) {
viewModelScope.launch {
ctaViewModel.onUserDismissedCta(cta)
if (cta is DaxBubbleCta.DaxPrivacyProCta) {
ctaViewModel.onUserClickCtaSkipButton(cta)
if (cta is DaxBubbleCta.DaxPrivacyProCta || cta is DaxBubbleCta.DaxExperimentPrivacyProCta) {
val updatedCta = refreshCta()
ctaViewState.value = currentCtaViewState().copy(cta = updatedCta)
} else if (cta is BrokenSitePromptDialogCta) {
Expand Down Expand Up @@ -3510,7 +3511,12 @@ class BrowserTabViewModel @Inject constructor(
private fun onDaxBubbleCtaOkButtonClicked(cta: DaxBubbleCta): Command? {
onUserDismissedCta(cta)
return when (cta) {
is DaxBubbleCta.DaxPrivacyProCta -> LaunchPrivacyPro("https://duckduckgo.com/pro?origin=funnel_pro_android_onboarding".toUri())
is DaxBubbleCta.DaxPrivacyProCta, is DaxBubbleCta.DaxExperimentPrivacyProCta -> {
val cohortOrigin = ctaViewModel.getCohortOrigin()
LaunchPrivacyPro(
"https://duckduckgo.com/pro?origin=funnel_pro_android_onboarding$cohortOrigin".toUri(),
)
}
is DaxBubbleCta.DaxEndCta, is DaxBubbleCta.DaxExperimentEndCta -> {
viewModelScope.launch {
val updatedCta = refreshCta()
Expand Down
26 changes: 24 additions & 2 deletions app/src/main/java/com/duckduckgo/app/cta/ui/Cta.kt
Original file line number Diff line number Diff line change
Expand Up @@ -970,10 +970,12 @@ sealed class DaxBubbleCta(
data class DaxPrivacyProCta(
override val onboardingStore: OnboardingStore,
override val appInstallStore: AppInstallStore,
val titleRes: Int,
val descriptionRes: Int,
) : DaxBubbleCta(
ctaId = CtaId.DAX_INTRO_PRIVACY_PRO,
title = R.string.onboardingPrivacyProDaxDialogTitle,
description = R.string.onboardingPrivacyProDaxDialogDescription,
title = titleRes,
description = descriptionRes,
placeholder = com.duckduckgo.mobile.android.R.drawable.ic_privacy_pro_128,
primaryCta = R.string.onboardingPrivacyProDaxDialogOkButton,
secondaryCta = R.string.onboardingPrivacyProDaxDialogCancelButton,
Expand Down Expand Up @@ -1032,6 +1034,26 @@ sealed class DaxBubbleCta(
appInstallStore = appInstallStore,
)

data class DaxExperimentPrivacyProCta(
override val onboardingStore: OnboardingStore,
override val appInstallStore: AppInstallStore,
val titleRes: Int,
val descriptionRes: Int,
) : DaxBubbleCta(
ctaId = CtaId.DAX_INTRO_PRIVACY_PRO,
title = titleRes,
description = descriptionRes,
placeholder = com.duckduckgo.mobile.android.R.drawable.ic_privacy_pro_128,
primaryCta = R.string.onboardingPrivacyProDaxDialogOkButton,
secondaryCta = R.string.onboardingPrivacyProDaxDialogCancelButton,
shownPixel = AppPixelName.ONBOARDING_DAX_CTA_SHOWN,
okPixel = AppPixelName.ONBOARDING_DAX_CTA_OK_BUTTON,
cancelPixel = AppPixelName.ONBOARDING_DAX_CTA_CANCEL_BUTTON,
ctaPixelParam = Pixel.PixelValues.DAX_PRIVACY_PRO,
onboardingStore = onboardingStore,
appInstallStore = appInstallStore,
)

data class DaxDialogIntroOption(
val optionText: String,
@DrawableRes val iconRes: Int,
Expand Down
Loading
Loading