mirror of
https://github.com/fork-maintainers/iceraven-browser
synced 2024-11-19 09:25:34 +00:00
[fenix] For https://github.com/mozilla-mobile/fenix/issues/26224 - New TCP CFR popup
If Total Cookie Protection is enabled when first accessing a normal tab (not a custom tab) a new Contextual Feature Recommendation popup will be shown informing about the added protection and allowing the user to open a support page with more data about the new option for privacy protection.
This commit is contained in:
parent
38d0eb7a9a
commit
d6967d5ef7
@ -0,0 +1,129 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.components.toolbar
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
|
import kotlinx.coroutines.flow.transformWhile
|
||||||
|
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||||
|
import mozilla.components.lib.state.ext.flowScoped
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.compose.cfr.CFRPopup
|
||||||
|
import org.mozilla.fenix.compose.cfr.CFRPopup.PopupAlignment.INDICATOR_CENTERED_IN_ANCHOR
|
||||||
|
import org.mozilla.fenix.compose.cfr.CFRPopupProperties
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.settings.SupportUtils
|
||||||
|
import org.mozilla.fenix.settings.SupportUtils.SumoTopic.TOTAL_COOKIE_PROTECTION
|
||||||
|
import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
|
import org.mozilla.fenix.utils.Settings
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertical padding needed to improve the visual alignment of the popup and respect the UX design.
|
||||||
|
*/
|
||||||
|
private const val CFR_TO_ANCHOR_VERTICAL_PADDING = -6
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate for handling all the business logic for showing CFRs in the toolbar.
|
||||||
|
*
|
||||||
|
* @param context used for various Android interactions.
|
||||||
|
* @param browserStore will be observed for tabs updates
|
||||||
|
* @param settings used to read and write persistent user settings
|
||||||
|
* @param toolbar will serve as anchor for the CFRs
|
||||||
|
* @param sessionId optional custom tab id used to identify the custom tab in which to show a CFR.
|
||||||
|
*/
|
||||||
|
class BrowserToolbarCFRPresenter(
|
||||||
|
private val context: Context,
|
||||||
|
private val browserStore: BrowserStore,
|
||||||
|
private val settings: Settings,
|
||||||
|
private val toolbar: BrowserToolbar,
|
||||||
|
private val sessionId: String? = null
|
||||||
|
) {
|
||||||
|
@VisibleForTesting
|
||||||
|
internal var tcpCfrScope: CoroutineScope? = null
|
||||||
|
@VisibleForTesting
|
||||||
|
internal var tcpCfrPopup: CFRPopup? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start observing [browserStore] for updates which may trigger showing a CFR.
|
||||||
|
*/
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
fun start() {
|
||||||
|
if (settings.shouldShowTotalCookieProtectionCFR) {
|
||||||
|
tcpCfrScope = browserStore.flowScoped { flow ->
|
||||||
|
flow
|
||||||
|
.mapNotNull { it.findCustomTabOrSelectedTab(sessionId)?.content?.progress }
|
||||||
|
// The "transformWhile" below ensures that the 100% progress is only collected once.
|
||||||
|
.transformWhile { progress ->
|
||||||
|
emit(progress)
|
||||||
|
progress != 100
|
||||||
|
}
|
||||||
|
.filter { it == 100 }
|
||||||
|
.collect {
|
||||||
|
tcpCfrScope?.cancel()
|
||||||
|
showTcpCfr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop listening for [browserStore] updates.
|
||||||
|
* CFRs already shown are not automatically dismissed.
|
||||||
|
*/
|
||||||
|
fun stop() {
|
||||||
|
tcpCfrScope?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
internal fun showTcpCfr() {
|
||||||
|
CFRPopup(
|
||||||
|
text = context.getString(R.string.tcp_cfr_message),
|
||||||
|
anchor = toolbar.findViewById(
|
||||||
|
R.id.mozac_browser_toolbar_security_indicator
|
||||||
|
),
|
||||||
|
properties = CFRPopupProperties(
|
||||||
|
popupAlignment = INDICATOR_CENTERED_IN_ANCHOR,
|
||||||
|
indicatorDirection = if (settings.toolbarPosition == ToolbarPosition.TOP) {
|
||||||
|
CFRPopup.IndicatorDirection.UP
|
||||||
|
} else {
|
||||||
|
CFRPopup.IndicatorDirection.DOWN
|
||||||
|
},
|
||||||
|
popupVerticalOffset = CFR_TO_ANCHOR_VERTICAL_PADDING.dp
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = context.getString(R.string.tcp_cfr_learn_more),
|
||||||
|
color = FirefoxTheme.colors.textOnColorPrimary,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
context.components.useCases.tabsUseCases.selectOrAddTab.invoke(
|
||||||
|
SupportUtils.getSumoURLForTopic(
|
||||||
|
context,
|
||||||
|
TOTAL_COOKIE_PROTECTION
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tcpCfrPopup?.dismiss()
|
||||||
|
},
|
||||||
|
style = FirefoxTheme.typography.body2.copy(
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}.run {
|
||||||
|
settings.shouldShowTotalCookieProtectionCFR = false
|
||||||
|
tcpCfrPopup = this
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@
|
|||||||
package org.mozilla.fenix.components.toolbar
|
package org.mozilla.fenix.components.toolbar
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider
|
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider
|
||||||
@ -95,6 +96,15 @@ class DefaultToolbarIntegration(
|
|||||||
renderStyle = ToolbarFeature.RenderStyle.UncoloredUrl
|
renderStyle = ToolbarFeature.RenderStyle.UncoloredUrl
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
internal var cfrPresenter = BrowserToolbarCFRPresenter(
|
||||||
|
context = context,
|
||||||
|
browserStore = context.components.core.store,
|
||||||
|
settings = context.settings(),
|
||||||
|
toolbar = toolbar,
|
||||||
|
sessionId = sessionId
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
toolbar.display.menuBuilder = toolbarMenu.menuBuilder
|
toolbar.display.menuBuilder = toolbarMenu.menuBuilder
|
||||||
toolbar.private = isPrivate
|
toolbar.private = isPrivate
|
||||||
@ -150,4 +160,14 @@ class DefaultToolbarIntegration(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun start() {
|
||||||
|
super.start()
|
||||||
|
cfrPresenter.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
cfrPresenter.stop()
|
||||||
|
super.stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,11 +54,11 @@ data class CFRPopupProperties(
|
|||||||
* @param action Optional other composable to show just below the popup text.
|
* @param action Optional other composable to show just below the popup text.
|
||||||
*/
|
*/
|
||||||
class CFRPopup(
|
class CFRPopup(
|
||||||
private val text: String,
|
@get:VisibleForTesting internal val text: String,
|
||||||
private val anchor: View,
|
@get:VisibleForTesting internal val anchor: View,
|
||||||
private val properties: CFRPopupProperties = CFRPopupProperties(),
|
@get:VisibleForTesting internal val properties: CFRPopupProperties = CFRPopupProperties(),
|
||||||
private val onDismiss: (Boolean) -> Unit = {},
|
@get:VisibleForTesting internal val onDismiss: (Boolean) -> Unit = {},
|
||||||
private val action: @Composable (() -> Unit) = {}
|
@get:VisibleForTesting internal val action: @Composable (() -> Unit) = {}
|
||||||
) {
|
) {
|
||||||
// This is just a facade for the CFRPopupFullScreenLayout composable offering a cleaner API.
|
// This is just a facade for the CFRPopupFullScreenLayout composable offering a cleaner API.
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ object SupportUtils {
|
|||||||
PRIVATE_BROWSING_MYTHS("common-myths-about-private-browsing"),
|
PRIVATE_BROWSING_MYTHS("common-myths-about-private-browsing"),
|
||||||
YOUR_RIGHTS("your-rights"),
|
YOUR_RIGHTS("your-rights"),
|
||||||
TRACKING_PROTECTION("tracking-protection-firefox-android"),
|
TRACKING_PROTECTION("tracking-protection-firefox-android"),
|
||||||
|
TOTAL_COOKIE_PROTECTION("enhanced-tracking-protection-android"),
|
||||||
WHATS_NEW("whats-new-firefox-preview"),
|
WHATS_NEW("whats-new-firefox-preview"),
|
||||||
OPT_OUT_STUDIES("how-opt-out-studies-firefox-android"),
|
OPT_OUT_STUDIES("how-opt-out-studies-firefox-android"),
|
||||||
SEND_TABS("send-tab-preview"),
|
SEND_TABS("send-tab-preview"),
|
||||||
|
@ -574,6 +574,14 @@ class Settings(private val appContext: Context) : PreferencesHolder {
|
|||||||
val enabledTotalCookieProtectionSetting: Boolean
|
val enabledTotalCookieProtectionSetting: Boolean
|
||||||
get() = mr2022Sections[Mr2022Section.TCP_CFR] == true
|
get() = mr2022Sections[Mr2022Section.TCP_CFR] == true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the total cookie protection CRF should be shown.
|
||||||
|
*/
|
||||||
|
var shouldShowTotalCookieProtectionCFR by booleanPreference(
|
||||||
|
appContext.getPreferenceKey(R.string.pref_key_should_show_total_cookie_protection_popup),
|
||||||
|
default = FxNimbus.features.engineSettings.value().totalCookieProtectionEnabled
|
||||||
|
)
|
||||||
|
|
||||||
val blockCookiesSelectionInCustomTrackingProtection by stringPreference(
|
val blockCookiesSelectionInCustomTrackingProtection by stringPreference(
|
||||||
key = appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies_select),
|
key = appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies_select),
|
||||||
default = if (enabledTotalCookieProtectionSetting) {
|
default = if (enabledTotalCookieProtectionSetting) {
|
||||||
|
@ -241,6 +241,8 @@
|
|||||||
<string name="pref_key_should_show_home_onboarding_dialog" translatable="false">pref_key_should_show_home_onboarding_dialog</string>
|
<string name="pref_key_should_show_home_onboarding_dialog" translatable="false">pref_key_should_show_home_onboarding_dialog</string>
|
||||||
<!-- A value of `true` means the first run onboarding updates should be shown -->
|
<!-- A value of `true` means the first run onboarding updates should be shown -->
|
||||||
<string name="pref_key_show_first_run_onboarding_update" translatable="false">pref_key_show_first_run_onboarding_update</string>
|
<string name="pref_key_show_first_run_onboarding_update" translatable="false">pref_key_show_first_run_onboarding_update</string>
|
||||||
|
<!-- A value of `true` means the total cookie protection popup has not been shown yet -->
|
||||||
|
<string name="pref_key_should_show_total_cookie_protection_popup" translatable="false">pref_key_should_show_total_cookie_protection_popup</string>
|
||||||
|
|
||||||
<string name="pref_key_debug_settings" translatable="false">pref_key_debug_settings</string>
|
<string name="pref_key_debug_settings" translatable="false">pref_key_debug_settings</string>
|
||||||
|
|
||||||
|
@ -76,6 +76,12 @@
|
|||||||
<!-- Content description for close button used in "contextual feature recommendation" (CFR) popups -->
|
<!-- Content description for close button used in "contextual feature recommendation" (CFR) popups -->
|
||||||
<string name="cfr_dismiss_button_default_content_description">Dismiss</string>
|
<string name="cfr_dismiss_button_default_content_description">Dismiss</string>
|
||||||
|
|
||||||
|
<!-- Total cookie protection "contextual feature recommendation" (CFR) -->
|
||||||
|
<!-- Text for the message displayed in the contextual feature recommendation popup promoting the total cookie protection feature. -->
|
||||||
|
<string name="tcp_cfr_message">Our most powerful privacy feature yet isolates cross-site trackers.</string>
|
||||||
|
<!-- Text displayed that links to website containing documentation about the "Total cookie protection" feature. -->
|
||||||
|
<string name="tcp_cfr_learn_more">Learn about Total Cookie Protection</string>
|
||||||
|
|
||||||
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
|
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
|
||||||
<string name="camera_permissions_needed_message">Camera access needed. Go to Android settings, tap permissions, and tap allow.</string>
|
<string name="camera_permissions_needed_message">Camera access needed. Go to Android settings, tap permissions, and tap allow.</string>
|
||||||
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
|
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
|
||||||
|
@ -0,0 +1,243 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.components.toolbar
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.mockk.Runs
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.spyk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import mozilla.components.browser.state.action.ContentAction
|
||||||
|
import mozilla.components.browser.state.state.BrowserState
|
||||||
|
import mozilla.components.browser.state.state.CustomTabSessionState
|
||||||
|
import mozilla.components.browser.state.state.TabSessionState
|
||||||
|
import mozilla.components.browser.state.state.createCustomTab
|
||||||
|
import mozilla.components.browser.state.state.createTab
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||||
|
import mozilla.components.support.test.ext.joinBlocking
|
||||||
|
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.compose.cfr.CFRPopup
|
||||||
|
import org.mozilla.fenix.utils.Settings
|
||||||
|
|
||||||
|
class BrowserToolbarCFRPresenterTest {
|
||||||
|
@get:Rule
|
||||||
|
val coroutinesTestRule = MainCoroutineRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN the TCP CFR should be shown for a custom tab WHEN the custom tab is fully loaded THEN the TCP CFR is shown`() {
|
||||||
|
val customTab = createCustomTab(url = "")
|
||||||
|
val browserStore = createBrowserStore(customTab = customTab)
|
||||||
|
val presenter = createPresenterThatShowsCFRs(
|
||||||
|
browserStore = browserStore,
|
||||||
|
sessionId = customTab.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
presenter.start()
|
||||||
|
|
||||||
|
assertNotNull(presenter.tcpCfrScope)
|
||||||
|
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(customTab.id, 0)).joinBlocking()
|
||||||
|
verify(exactly = 0) { presenter.showTcpCfr() }
|
||||||
|
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(customTab.id, 33)).joinBlocking()
|
||||||
|
verify(exactly = 0) { presenter.showTcpCfr() }
|
||||||
|
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(customTab.id, 100)).joinBlocking()
|
||||||
|
verify { presenter.showTcpCfr() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN the TCP CFR should be shown WHEN the current normal tab is fully loaded THEN the TCP CFR is shown`() {
|
||||||
|
val normalTab = createTab(url = "", private = false)
|
||||||
|
val browserStore = createBrowserStore(
|
||||||
|
tab = normalTab,
|
||||||
|
selectedTabId = normalTab.id,
|
||||||
|
)
|
||||||
|
val presenter = createPresenterThatShowsCFRs(browserStore = browserStore)
|
||||||
|
|
||||||
|
presenter.start()
|
||||||
|
|
||||||
|
assertNotNull(presenter.tcpCfrScope)
|
||||||
|
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(normalTab.id, 1)).joinBlocking()
|
||||||
|
verify(exactly = 0) { presenter.showTcpCfr() }
|
||||||
|
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(normalTab.id, 98)).joinBlocking()
|
||||||
|
verify(exactly = 0) { presenter.showTcpCfr() }
|
||||||
|
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(normalTab.id, 100)).joinBlocking()
|
||||||
|
verify { presenter.showTcpCfr() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN the TCP CFR should be shown WHEN the current private tab is fully loaded THEN the TCP CFR is shown`() {
|
||||||
|
val privateTab = createTab(url = "", private = true)
|
||||||
|
val browserStore = createBrowserStore(
|
||||||
|
tab = privateTab,
|
||||||
|
selectedTabId = privateTab.id,
|
||||||
|
)
|
||||||
|
val presenter = createPresenterThatShowsCFRs(browserStore = browserStore)
|
||||||
|
|
||||||
|
presenter.start()
|
||||||
|
|
||||||
|
assertNotNull(presenter.tcpCfrScope)
|
||||||
|
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(privateTab.id, 14)).joinBlocking()
|
||||||
|
verify(exactly = 0) { presenter.showTcpCfr() }
|
||||||
|
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(privateTab.id, 99)).joinBlocking()
|
||||||
|
verify(exactly = 0) { presenter.showTcpCfr() }
|
||||||
|
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(privateTab.id, 100)).joinBlocking()
|
||||||
|
verify { presenter.showTcpCfr() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN the TCP CFR should be shown WHEN the current tab is fully loaded THEN the TCP CFR is only shown once`() {
|
||||||
|
val tab = createTab(url = "")
|
||||||
|
val browserStore = createBrowserStore(
|
||||||
|
tab = tab,
|
||||||
|
selectedTabId = tab.id,
|
||||||
|
)
|
||||||
|
val presenter = createPresenterThatShowsCFRs(browserStore = browserStore)
|
||||||
|
|
||||||
|
presenter.start()
|
||||||
|
|
||||||
|
assertNotNull(presenter.tcpCfrScope)
|
||||||
|
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(tab.id, 99)).joinBlocking()
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(tab.id, 100)).joinBlocking()
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(tab.id, 100)).joinBlocking()
|
||||||
|
browserStore.dispatch(ContentAction.UpdateProgressAction(tab.id, 100)).joinBlocking()
|
||||||
|
verify(exactly = 1) { presenter.showTcpCfr() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN the TCP CFR should not be shown WHEN the feature starts THEN don't observe the store for updates`() {
|
||||||
|
val presenter = createPresenter(
|
||||||
|
settings = mockk {
|
||||||
|
every { shouldShowTotalCookieProtectionCFR } returns false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
presenter.start()
|
||||||
|
|
||||||
|
assertNull(presenter.tcpCfrScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN the store is observed for updates WHEN the presenter is stopped THEN stop observing the store`() {
|
||||||
|
val tcpScope: CoroutineScope = mockk {
|
||||||
|
every { cancel() } just Runs
|
||||||
|
}
|
||||||
|
val presenter = createPresenter()
|
||||||
|
presenter.tcpCfrScope = tcpScope
|
||||||
|
|
||||||
|
presenter.stop()
|
||||||
|
|
||||||
|
verify { tcpScope.cancel() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN the TCP CFR is to be shown THEN instantiate a new one and remember to not show it again`() {
|
||||||
|
val settings: Settings = mockk(relaxed = true)
|
||||||
|
val presenter = createPresenter(
|
||||||
|
anchor = mockk(relaxed = true),
|
||||||
|
settings = settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
presenter.showTcpCfr()
|
||||||
|
|
||||||
|
verify { settings.shouldShowTotalCookieProtectionCFR = false }
|
||||||
|
assertNotNull(presenter.tcpCfrPopup)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN the TCP CFR is instantiated THEN set the intended properties`() {
|
||||||
|
val anchor: View = mockk(relaxed = true)
|
||||||
|
val settings: Settings = mockk(relaxed = true)
|
||||||
|
val presenter = createPresenter(
|
||||||
|
anchor = anchor,
|
||||||
|
settings = settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
presenter.showTcpCfr()
|
||||||
|
|
||||||
|
verify { settings.shouldShowTotalCookieProtectionCFR = false }
|
||||||
|
assertNotNull(presenter.tcpCfrPopup)
|
||||||
|
presenter.tcpCfrPopup?.let {
|
||||||
|
assertEquals("Test", it.text)
|
||||||
|
assertEquals(anchor, it.anchor)
|
||||||
|
assertEquals(CFRPopup.DEFAULT_WIDTH.dp, it.properties.popupWidth)
|
||||||
|
assertEquals(CFRPopup.IndicatorDirection.DOWN, it.properties.indicatorDirection)
|
||||||
|
assertTrue(it.properties.dismissOnBackPress)
|
||||||
|
assertTrue(it.properties.dismissOnClickOutside)
|
||||||
|
assertFalse(it.properties.overlapAnchor)
|
||||||
|
assertEquals(CFRPopup.DEFAULT_INDICATOR_START_OFFSET.dp, it.properties.indicatorArrowStartOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and return a [spyk] of a [BrowserToolbarCFRPresenter] that can handle actually showing CFRs.
|
||||||
|
*/
|
||||||
|
private fun createPresenterThatShowsCFRs(
|
||||||
|
context: Context = mockk(),
|
||||||
|
anchor: View = mockk(),
|
||||||
|
browserStore: BrowserStore = mockk(),
|
||||||
|
settings: Settings = mockk { every { shouldShowTotalCookieProtectionCFR } returns true },
|
||||||
|
toolbar: BrowserToolbar = mockk(),
|
||||||
|
sessionId: String? = null
|
||||||
|
) = spyk(createPresenter(context, anchor, browserStore, settings, toolbar, sessionId)) {
|
||||||
|
every { showTcpCfr() } just Runs
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and return a [BrowserToolbarCFRPresenter] with all constructor properties mocked by default.
|
||||||
|
* Calls to show a CFR will fail. If this behavior is needed to work use [createPresenterThatShowsCFRs].
|
||||||
|
*/
|
||||||
|
private fun createPresenter(
|
||||||
|
context: Context = mockk { every { getString(R.string.tcp_cfr_message) } returns "Test" },
|
||||||
|
anchor: View = mockk(),
|
||||||
|
browserStore: BrowserStore = mockk(),
|
||||||
|
settings: Settings = mockk(relaxed = true) { every { shouldShowTotalCookieProtectionCFR } returns true },
|
||||||
|
toolbar: BrowserToolbar = mockk {
|
||||||
|
every { findViewById<View>(R.id.mozac_browser_toolbar_security_indicator) } returns anchor
|
||||||
|
},
|
||||||
|
sessionId: String? = null
|
||||||
|
) = BrowserToolbarCFRPresenter(
|
||||||
|
context = context,
|
||||||
|
browserStore = browserStore,
|
||||||
|
settings = settings,
|
||||||
|
toolbar = toolbar,
|
||||||
|
sessionId = sessionId
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createBrowserStore(
|
||||||
|
tab: TabSessionState? = null,
|
||||||
|
customTab: CustomTabSessionState? = null,
|
||||||
|
selectedTabId: String? = null
|
||||||
|
) = BrowserStore(
|
||||||
|
initialState = BrowserState(
|
||||||
|
tabs = if (tab != null) listOf(tab) else listOf(),
|
||||||
|
customTabs = if (customTab != null) listOf(customTab) else listOf(),
|
||||||
|
selectedTabId = selectedTabId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.components.toolbar
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkStatic
|
||||||
|
import io.mockk.verify
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.support.test.robolectric.testContext
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
|
||||||
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
|
class DefaultToolbarIntegrationTest {
|
||||||
|
private lateinit var feature: DefaultToolbarIntegration
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mockkStatic("org.mozilla.fenix.ext.ContextKt")
|
||||||
|
every { any<Context>().components } returns mockk {
|
||||||
|
every { core } returns mockk {
|
||||||
|
every { store } returns BrowserStore()
|
||||||
|
}
|
||||||
|
every { publicSuffixList } returns mockk()
|
||||||
|
every { settings } returns mockk(relaxed = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
feature = DefaultToolbarIntegration(
|
||||||
|
context = testContext,
|
||||||
|
toolbar = mockk(relaxed = true),
|
||||||
|
toolbarMenu = mockk(relaxed = true),
|
||||||
|
domainAutocompleteProvider = mockk(),
|
||||||
|
historyStorage = mockk(),
|
||||||
|
lifecycleOwner = mockk(),
|
||||||
|
sessionId = null,
|
||||||
|
isPrivate = false,
|
||||||
|
interactor = mockk(),
|
||||||
|
engine = mockk(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun teardown() {
|
||||||
|
unmockkStatic("org.mozilla.fenix.ext.ContextKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN the feature starts THEN start the cfr presenter`() {
|
||||||
|
feature.cfrPresenter = mockk(relaxed = true)
|
||||||
|
|
||||||
|
feature.start()
|
||||||
|
|
||||||
|
verify { feature.cfrPresenter.start() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN the feature stops THEN stop the cfr presenter`() {
|
||||||
|
feature.cfrPresenter = mockk(relaxed = true)
|
||||||
|
|
||||||
|
feature.stop()
|
||||||
|
|
||||||
|
verify { feature.cfrPresenter.stop() }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user