Bug 1797577 - Add cookie banner handling panel to the toolbar.
parent
e7a7712a6b
commit
cc666c8887
@ -1,79 +0,0 @@
|
||||
/* 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.settings.quicksettings
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.view.isVisible
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.databinding.QuicksettingsTrackingProtectionBinding
|
||||
import org.mozilla.fenix.trackingprotection.TrackingProtectionState
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
/**
|
||||
* Contract declaring all possible user interactions with [TrackingProtectionView].
|
||||
*/
|
||||
interface TrackingProtectionInteractor {
|
||||
|
||||
/**
|
||||
* Called whenever the tracking protection toggle for this site is toggled.
|
||||
*
|
||||
* @param isEnabled Whether or not tracking protection is enabled.
|
||||
*/
|
||||
fun onTrackingProtectionToggled(isEnabled: Boolean)
|
||||
|
||||
/**
|
||||
* Navigates to the tracking protection preferences. Called when a user clicks on the
|
||||
* "Details" button.
|
||||
*/
|
||||
fun onDetailsClicked()
|
||||
}
|
||||
|
||||
/**
|
||||
* MVI View that displays the tracking protection toggle and navigation to additional tracking
|
||||
* protection details.
|
||||
*
|
||||
* @param containerView [ViewGroup] in which this View will inflate itself.
|
||||
* @param interactor [TrackingProtectionInteractor] which will have delegated to all user
|
||||
* @param settings [Settings] application settings.
|
||||
* interactions.
|
||||
*/
|
||||
class TrackingProtectionView(
|
||||
val containerView: ViewGroup,
|
||||
val interactor: TrackingProtectionInteractor,
|
||||
val settings: Settings,
|
||||
) {
|
||||
private val context = containerView.context
|
||||
|
||||
@VisibleForTesting
|
||||
internal val binding = QuicksettingsTrackingProtectionBinding.inflate(
|
||||
LayoutInflater.from(containerView.context),
|
||||
containerView,
|
||||
true,
|
||||
)
|
||||
fun update(state: TrackingProtectionState) {
|
||||
bindTrackingProtectionInfo(state.isTrackingProtectionEnabled)
|
||||
binding.root.isVisible = settings.shouldUseTrackingProtection
|
||||
binding.trackingProtectionDetails.setOnClickListener {
|
||||
interactor.onDetailsClicked()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateDetailsSection(show: Boolean) {
|
||||
binding.trackingProtectionDetails.isVisible = show
|
||||
}
|
||||
|
||||
private fun bindTrackingProtectionInfo(isTrackingProtectionEnabled: Boolean) {
|
||||
binding.trackingProtectionSwitch.trackingProtectionCategoryItemDescription.text =
|
||||
context.getString(if (isTrackingProtectionEnabled) R.string.etp_panel_on else R.string.etp_panel_off)
|
||||
binding.trackingProtectionSwitch.switchWidget.isChecked = isTrackingProtectionEnabled
|
||||
binding.trackingProtectionSwitch.switchWidget.jumpDrawablesToCurrentState()
|
||||
|
||||
binding.trackingProtectionSwitch.switchWidget.setOnCheckedChangeListener { _, isChecked ->
|
||||
interactor.onTrackingProtectionToggled(isChecked)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/* 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.settings.quicksettings.protections
|
||||
|
||||
/**
|
||||
* Contract declaring all possible user interactions with [ProtectionsView].
|
||||
*/
|
||||
interface ProtectionsInteractor {
|
||||
|
||||
/**
|
||||
* Called whenever the tracking protection toggle for this site is toggled.
|
||||
*
|
||||
* @param isEnabled Whether or not tracking protection is enabled.
|
||||
*/
|
||||
fun onTrackingProtectionToggled(isEnabled: Boolean)
|
||||
|
||||
/**
|
||||
* Navigates to the tracking protection details panel.
|
||||
*/
|
||||
fun onCookieBannerHandlingDetailsClicked()
|
||||
|
||||
/**
|
||||
* Navigates to the tracking protection preferences. Called when a user clicks on the
|
||||
* "Details" button.
|
||||
*/
|
||||
fun onTrackingProtectionDetailsClicked()
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
/* 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.settings.quicksettings.protections
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.isVisible
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.databinding.QuicksettingsProtectionsPanelBinding
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
import org.mozilla.fenix.trackingprotection.ProtectionsState
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
/**
|
||||
* MVI View that displays the tracking protection, cookie banner handling toggles and the navigation
|
||||
* to additional tracking protection details.
|
||||
*
|
||||
* @param containerView [ViewGroup] in which this View will inflate itself.
|
||||
* @param interactor [ProtectionsInteractor] which will have delegated to all user
|
||||
* @param settings [Settings] application settings.
|
||||
* interactions.
|
||||
*/
|
||||
class ProtectionsView(
|
||||
val containerView: ViewGroup,
|
||||
val interactor: ProtectionsInteractor,
|
||||
val settings: Settings,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Allows changing what this View displays.
|
||||
*/
|
||||
fun update(state: ProtectionsState) {
|
||||
bindTrackingProtectionInfo(state.isTrackingProtectionEnabled)
|
||||
bindCookieBannerProtection(state.isCookieBannerHandlingEnabled)
|
||||
binding.trackingProtectionSwitch.isVisible = settings.shouldUseTrackingProtection
|
||||
binding.cookieBannerItem.isVisible = shouldShowCookieBanner
|
||||
|
||||
binding.trackingProtectionDetails.setOnClickListener {
|
||||
interactor.onTrackingProtectionDetailsClicked()
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun updateDetailsSection(show: Boolean) {
|
||||
binding.trackingProtectionDetails.isVisible = show
|
||||
}
|
||||
|
||||
private fun bindTrackingProtectionInfo(isTrackingProtectionEnabled: Boolean) {
|
||||
binding.trackingProtectionSwitch.isChecked = isTrackingProtectionEnabled
|
||||
binding.trackingProtectionSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||
interactor.onTrackingProtectionToggled(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal val binding = QuicksettingsProtectionsPanelBinding.inflate(
|
||||
LayoutInflater.from(containerView.context),
|
||||
containerView,
|
||||
true,
|
||||
)
|
||||
|
||||
private val shouldShowCookieBanner: Boolean
|
||||
get() = settings.shouldShowCookieBannerUI && settings.shouldUseCookieBanner
|
||||
|
||||
private fun bindCookieBannerProtection(isCookieBannerHandlingEnabled: Boolean) {
|
||||
val context = binding.cookieBannerItem.context
|
||||
val label = context.getString(R.string.preferences_cookie_banner_reduction)
|
||||
val description = context.getString(
|
||||
if (isCookieBannerHandlingEnabled) {
|
||||
R.string.reduce_cookie_banner_on_for_site
|
||||
} else {
|
||||
R.string.reduce_cookie_banner_off_for_site
|
||||
},
|
||||
)
|
||||
val icon = if (isCookieBannerHandlingEnabled) {
|
||||
R.drawable.ic_cookies_enabled
|
||||
} else {
|
||||
R.drawable.ic_cookies_disabled
|
||||
}
|
||||
|
||||
binding.cookieBannerItem.apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
FirefoxTheme {
|
||||
CookieBannerItem(
|
||||
label = label,
|
||||
description = description,
|
||||
startIconPainter = painterResource(icon),
|
||||
endIconPainter = painterResource(R.drawable.ic_arrowhead_right),
|
||||
onClick = { interactor.onCookieBannerHandlingDetailsClicked() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CookieBannerItem(
|
||||
label: String,
|
||||
description: String,
|
||||
startIconPainter: Painter,
|
||||
endIconPainter: Painter,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable { onClick() }
|
||||
.defaultMinSize(minHeight = 48.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = startIconPainter,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(horizontal = 0.dp),
|
||||
tint = FirefoxTheme.colors.iconPrimary,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp)
|
||||
.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
style = FirefoxTheme.typography.subtitle1,
|
||||
maxLines = 1,
|
||||
)
|
||||
Text(
|
||||
text = description,
|
||||
color = FirefoxTheme.colors.textSecondary,
|
||||
style = FirefoxTheme.typography.body2,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(end = 0.dp)
|
||||
.size(24.dp),
|
||||
painter = endIconPainter,
|
||||
contentDescription = null,
|
||||
tint = FirefoxTheme.colors.iconPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
private fun CookieBannerItemPreview() {
|
||||
FirefoxTheme {
|
||||
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
|
||||
CookieBannerItem(
|
||||
label = "Cookie Banner Reduction",
|
||||
description = "On for this site",
|
||||
startIconPainter = painterResource(R.drawable.ic_cookies_enabled),
|
||||
endIconPainter = painterResource(R.drawable.ic_arrowhead_right),
|
||||
onClick = { println("list item click") },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/* 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.settings.quicksettings.protections.cookiebanners
|
||||
|
||||
import android.content.Context
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.NavController
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.browser.state.selector.findTabOrCustomTab
|
||||
import mozilla.components.browser.state.state.SessionState
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.cookiehandling.CookieBannersStorage
|
||||
import mozilla.components.concept.engine.permission.SitePermissions
|
||||
import mozilla.components.feature.session.SessionUseCases
|
||||
import mozilla.components.service.glean.private.NoExtras
|
||||
import org.mozilla.fenix.GleanMetrics.CookieBanners
|
||||
import org.mozilla.fenix.browser.BrowserFragmentDirections
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.runIfFragmentIsAttached
|
||||
import org.mozilla.fenix.trackingprotection.ProtectionsAction
|
||||
import org.mozilla.fenix.trackingprotection.ProtectionsStore
|
||||
|
||||
/**
|
||||
* [CookieBannerDetailsController] controller.
|
||||
*
|
||||
* Delegated by View Interactors, handles container business logic and operates changes on it,
|
||||
* complex Android interactions or communication with other features.
|
||||
*/
|
||||
interface CookieBannerDetailsController {
|
||||
/**
|
||||
* @see [CookieBannerDetailsInteractor.onBackPressed]
|
||||
*/
|
||||
fun handleBackPressed()
|
||||
|
||||
/**
|
||||
* @see [CookieBannerDetailsInteractor.onTogglePressed]
|
||||
*/
|
||||
fun handleTogglePressed(isEnabled: Boolean)
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behavior of [CookieBannerDetailsController].
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class DefaultCookieBannerDetailsController(
|
||||
private val context: Context,
|
||||
private val fragment: Fragment,
|
||||
private val ioScope: CoroutineScope,
|
||||
internal val sessionId: String,
|
||||
private val browserStore: BrowserStore,
|
||||
internal val protectionsStore: ProtectionsStore,
|
||||
private val cookieBannersStorage: CookieBannersStorage,
|
||||
private val navController: () -> NavController,
|
||||
internal var sitePermissions: SitePermissions?,
|
||||
private val gravity: Int,
|
||||
private val getCurrentTab: () -> SessionState?,
|
||||
private val reload: SessionUseCases.ReloadUrlUseCase,
|
||||
) : CookieBannerDetailsController {
|
||||
|
||||
override fun handleBackPressed() {
|
||||
getCurrentTab()?.let { tab ->
|
||||
context.components.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
|
||||
ioScope.launch {
|
||||
val hasException =
|
||||
cookieBannersStorage.hasException(tab.content.url, tab.content.private)
|
||||
withContext(Dispatchers.Main) {
|
||||
fragment.runIfFragmentIsAttached {
|
||||
navController().popBackStack()
|
||||
val isTrackingProtectionEnabled = tab.trackingProtection.enabled && !contains
|
||||
val directions =
|
||||
BrowserFragmentDirections.actionGlobalQuickSettingsSheetDialogFragment(
|
||||
sessionId = tab.id,
|
||||
url = tab.content.url,
|
||||
title = tab.content.title,
|
||||
isSecured = tab.content.securityInfo.secure,
|
||||
sitePermissions = sitePermissions,
|
||||
gravity = gravity,
|
||||
certificateName = tab.content.securityInfo.issuer,
|
||||
permissionHighlights = tab.content.permissionHighlights,
|
||||
isTrackingProtectionEnabled = isTrackingProtectionEnabled,
|
||||
isCookieHandlingEnabled = !hasException,
|
||||
)
|
||||
navController().navigate(directions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleTogglePressed(isEnabled: Boolean) {
|
||||
val tab = requireNotNull(browserStore.state.findTabOrCustomTab(sessionId)) {
|
||||
"A session is required to update the cookie banner mode"
|
||||
}
|
||||
ioScope.launch {
|
||||
if (isEnabled) {
|
||||
cookieBannersStorage.removeException(
|
||||
uri = tab.content.url,
|
||||
privateBrowsing = tab.content.private,
|
||||
)
|
||||
CookieBanners.exceptionRemoved.record(NoExtras())
|
||||
} else {
|
||||
cookieBannersStorage.addException(uri = tab.content.url, privateBrowsing = tab.content.private)
|
||||
CookieBanners.exceptionAdded.record(NoExtras())
|
||||
}
|
||||
protectionsStore.dispatch(
|
||||
ProtectionsAction.ToggleCookieBannerHandlingProtectionEnabled(
|
||||
isEnabled,
|
||||
),
|
||||
)
|
||||
reload(tab.id)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/* 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.settings.quicksettings.protections.cookiebanners
|
||||
|
||||
/**
|
||||
* Contract declaring all possible user interactions with [CookieBannerHandlingDetailsView].
|
||||
*/
|
||||
interface CookieBannerDetailsInteractor {
|
||||
/**
|
||||
* Called whenever back is pressed.
|
||||
*/
|
||||
fun onBackPressed() = Unit
|
||||
|
||||
/**
|
||||
* Called whenever the user press the toggle widget.
|
||||
*/
|
||||
fun onTogglePressed(vale: Boolean) = Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* [CookieBannerPanelDialogFragment] interactor.
|
||||
*
|
||||
* Implements callbacks for each of [CookieBannerPanelDialogFragment]'s Views declared possible user interactions,
|
||||
* delegates all such user events to the [CookieBannerDetailsController].
|
||||
*
|
||||
* @param controller [CookieBannerDetailsController] which will be delegated for all users interactions,
|
||||
* it expected to contain all business logic for how to act in response.
|
||||
*/
|
||||
class DefaultCookieBannerDetailsInteractor(
|
||||
private val controller: CookieBannerDetailsController,
|
||||
) : CookieBannerDetailsInteractor {
|
||||
|
||||
override fun onBackPressed() {
|
||||
controller.handleBackPressed()
|
||||
}
|
||||
|
||||
override fun onTogglePressed(vale: Boolean) {
|
||||
controller.handleTogglePressed(vale)
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/* 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.settings.quicksettings.protections.cookiebanners
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||
import mozilla.components.support.ktx.kotlin.toShortUrl
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.databinding.ComponentCookieBannerDetailsPanelBinding
|
||||
import org.mozilla.fenix.trackingprotection.ProtectionsState
|
||||
|
||||
/**
|
||||
* MVI View that knows how to display cookie banner handling details for a site.
|
||||
*
|
||||
* @param container [ViewGroup] in which this View will inflate itself.
|
||||
* @param publicSuffixList To show short url.
|
||||
* @param interactor [CookieBannerDetailsInteractor] which will have delegated to all user interactions.
|
||||
*/
|
||||
class CookieBannerHandlingDetailsView(
|
||||
container: ViewGroup,
|
||||
private val context: Context,
|
||||
private val publicSuffixList: PublicSuffixList,
|
||||
val interactor: CookieBannerDetailsInteractor,
|
||||
) {
|
||||
val binding = ComponentCookieBannerDetailsPanelBinding.inflate(
|
||||
LayoutInflater.from(container.context),
|
||||
container,
|
||||
true,
|
||||
)
|
||||
|
||||
/**
|
||||
* Allows changing what this View displays.
|
||||
*/
|
||||
fun update(state: ProtectionsState) {
|
||||
bindTitle(state.url, state.isCookieBannerHandlingEnabled)
|
||||
bindBackButtonListener()
|
||||
bindDescription(state.isCookieBannerHandlingEnabled)
|
||||
bindSwitch(state.isCookieBannerHandlingEnabled)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun bindTitle(url: String, isCookieBannerHandlingEnabled: Boolean) {
|
||||
val stringID =
|
||||
if (isCookieBannerHandlingEnabled) {
|
||||
R.string.reduce_cookie_banner_details_panel_title_off_for_site
|
||||
} else {
|
||||
R.string.reduce_cookie_banner_details_panel_title_on_for_site
|
||||
}
|
||||
val shortUrl = url.toShortUrl(publicSuffixList)
|
||||
binding.title.text = context.getString(stringID, shortUrl)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun bindDescription(isCookieBannerHandlingEnabled: Boolean) {
|
||||
val stringID =
|
||||
if (isCookieBannerHandlingEnabled) {
|
||||
R.string.reduce_cookie_banner_details_panel_description_off_for_site
|
||||
} else {
|
||||
R.string.reduce_cookie_banner_details_panel_description_on_for_site
|
||||
}
|
||||
binding.details.text = context.getString(stringID, context.getString(R.string.app_name))
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun bindBackButtonListener() {
|
||||
binding.navigateBack.setOnClickListener {
|
||||
interactor.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun bindSwitch(isCookieBannerHandlingEnabled: Boolean) {
|
||||
binding.cookieBannerSwitch.isChecked = isCookieBannerHandlingEnabled
|
||||
binding.cookieBannerSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||
interactor.onTogglePressed(isChecked)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
/* 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.settings.quicksettings.protections.cookiebanners
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.plus
|
||||
import mozilla.components.browser.state.selector.findTabOrCustomTab
|
||||
import mozilla.components.browser.state.state.SessionState
|
||||
import mozilla.components.lib.state.ext.consumeFrom
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.android.FenixDialogFragment
|
||||
import org.mozilla.fenix.components.StoreProvider
|
||||
import org.mozilla.fenix.databinding.FragmentCookieBannerHandlingDetailsDialogBinding
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.trackingprotection.ProtectionsState
|
||||
import org.mozilla.fenix.trackingprotection.ProtectionsStore
|
||||
|
||||
/**
|
||||
* A [FenixDialogFragment] that contains all the cookie banner details for a given tab.
|
||||
*/
|
||||
class CookieBannerPanelDialogFragment : FenixDialogFragment() {
|
||||
@VisibleForTesting
|
||||
private lateinit var cookieBannersView: CookieBannerHandlingDetailsView
|
||||
private val args by navArgs<CookieBannerPanelDialogFragmentArgs>()
|
||||
private var _binding: FragmentCookieBannerHandlingDetailsDialogBinding? = null
|
||||
|
||||
override val gravity: Int get() = args.gravity
|
||||
override val layoutId: Int = R.layout.fragment_cookie_banner_handling_details_dialog
|
||||
|
||||
@VisibleForTesting
|
||||
internal lateinit var protectionsStore: ProtectionsStore
|
||||
|
||||
// This property is only valid between onCreateView and onDestroyView.
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
val store = requireComponents.core.store
|
||||
val rootView = inflateRootView(container)
|
||||
val tab = store.state.findTabOrCustomTab(provideCurrentTabId())
|
||||
|
||||
protectionsStore = StoreProvider.get(this) {
|
||||
ProtectionsStore(
|
||||
ProtectionsState(
|
||||
tab = tab,
|
||||
url = args.url,
|
||||
isTrackingProtectionEnabled = args.trackingProtectionEnabled,
|
||||
isCookieBannerHandlingEnabled = args.cookieBannerHandlingEnabled,
|
||||
listTrackers = listOf(),
|
||||
mode = ProtectionsState.Mode.Normal,
|
||||
lastAccessedCategory = "",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val controller = DefaultCookieBannerDetailsController(
|
||||
context = requireContext(),
|
||||
ioScope = viewLifecycleOwner.lifecycleScope + Dispatchers.IO,
|
||||
cookieBannersStorage = requireComponents.core.cookieBannersStorage,
|
||||
protectionsStore = protectionsStore,
|
||||
browserStore = requireComponents.core.store,
|
||||
fragment = this,
|
||||
sessionId = args.sessionId,
|
||||
reload = requireComponents.useCases.sessionUseCases.reload,
|
||||
navController = { findNavController() },
|
||||
sitePermissions = args.sitePermissions,
|
||||
gravity = args.gravity,
|
||||
getCurrentTab = ::getCurrentTab,
|
||||
)
|
||||
|
||||
_binding = FragmentCookieBannerHandlingDetailsDialogBinding.bind(rootView)
|
||||
|
||||
cookieBannersView = CookieBannerHandlingDetailsView(
|
||||
context = requireContext(),
|
||||
container = binding.cookieBannerDetailsInfoLayout,
|
||||
publicSuffixList = requireComponents.publicSuffixList,
|
||||
interactor = DefaultCookieBannerDetailsInteractor(controller),
|
||||
)
|
||||
|
||||
return rootView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
consumeFrom(protectionsStore) { state ->
|
||||
cookieBannersView.update(state)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun provideCurrentTabId(): String = args.sessionId
|
||||
|
||||
private fun getCurrentTab(): SessionState? {
|
||||
return requireComponents.core.store.state.findTabOrCustomTab(args.sessionId)
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/enabled"
|
||||
android:drawable="@drawable/ic_tracking_protection_enabled"
|
||||
android:state_checked="true" />
|
||||
<item
|
||||
android:id="@+id/disabled"
|
||||
android:drawable="@drawable/ic_tracking_protection_disabled" />
|
||||
|
||||
</animated-selector>
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/quick_settings_sheet"
|
||||
android:fillViewport="true">
|
||||
<FrameLayout
|
||||
android:id="@+id/cookieBannerDetailsInfoLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</androidx.core.widget.NestedScrollView>
|
@ -0,0 +1,141 @@
|
||||
/* 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.settings.quicksettings
|
||||
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.isVisible
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.databinding.QuicksettingsProtectionsPanelBinding
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.settings.quicksettings.protections.ProtectionsInteractor
|
||||
import org.mozilla.fenix.settings.quicksettings.protections.ProtectionsView
|
||||
import org.mozilla.fenix.trackingprotection.ProtectionsState
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class ProtectionsViewTest {
|
||||
|
||||
private lateinit var view: ProtectionsView
|
||||
private lateinit var binding: QuicksettingsProtectionsPanelBinding
|
||||
private lateinit var interactor: ProtectionsInteractor
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var settings: Settings
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
interactor = mockk(relaxed = true)
|
||||
view = spyk(ProtectionsView(FrameLayout(testContext), interactor, settings))
|
||||
binding = view.binding
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN updating THEN bind checkbox`() {
|
||||
val websiteUrl = "https://mozilla.org"
|
||||
val state = ProtectionsState(
|
||||
tab = createTab(url = websiteUrl),
|
||||
url = websiteUrl,
|
||||
isTrackingProtectionEnabled = true,
|
||||
isCookieBannerHandlingEnabled = true,
|
||||
listTrackers = listOf(),
|
||||
mode = ProtectionsState.Mode.Normal,
|
||||
lastAccessedCategory = "",
|
||||
)
|
||||
|
||||
every { settings.shouldUseTrackingProtection } returns true
|
||||
|
||||
view.update(state)
|
||||
|
||||
assertTrue(binding.root.isVisible)
|
||||
assertTrue(binding.trackingProtectionSwitch.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN TP is globally off WHEN updating THEN hide the TP section`() {
|
||||
val websiteUrl = "https://mozilla.org"
|
||||
val state = ProtectionsState(
|
||||
tab = createTab(url = websiteUrl),
|
||||
url = websiteUrl,
|
||||
isTrackingProtectionEnabled = true,
|
||||
isCookieBannerHandlingEnabled = true,
|
||||
listTrackers = listOf(),
|
||||
mode = ProtectionsState.Mode.Normal,
|
||||
lastAccessedCategory = "",
|
||||
)
|
||||
|
||||
every { settings.shouldUseTrackingProtection } returns false
|
||||
|
||||
view.update(state)
|
||||
|
||||
assertFalse(binding.trackingProtectionSwitch.isVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cookie banners handling is globally off WHEN updating THEN hide the cookie banner section`() {
|
||||
val websiteUrl = "https://mozilla.org"
|
||||
val state = ProtectionsState(
|
||||
tab = createTab(url = websiteUrl),
|
||||
url = websiteUrl,
|
||||
isTrackingProtectionEnabled = true,
|
||||
isCookieBannerHandlingEnabled = true,
|
||||
listTrackers = listOf(),
|
||||
mode = ProtectionsState.Mode.Normal,
|
||||
lastAccessedCategory = "",
|
||||
)
|
||||
|
||||
every { settings.shouldShowCookieBannerUI } returns true
|
||||
every { settings.shouldUseCookieBanner } returns false
|
||||
|
||||
view.update(state)
|
||||
|
||||
assertFalse(binding.cookieBannerItem.isVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cookie banners handling UI feature flag is off WHEN updating THEN hide the cookie banner section`() {
|
||||
val websiteUrl = "https://mozilla.org"
|
||||
val state = ProtectionsState(
|
||||
tab = createTab(url = websiteUrl),
|
||||
url = websiteUrl,
|
||||
isTrackingProtectionEnabled = true,
|
||||
isCookieBannerHandlingEnabled = true,
|
||||
listTrackers = listOf(),
|
||||
mode = ProtectionsState.Mode.Normal,
|
||||
lastAccessedCategory = "",
|
||||
)
|
||||
|
||||
every { settings.shouldShowCookieBannerUI } returns false
|
||||
every { settings.shouldUseCookieBanner } returns false
|
||||
|
||||
view.update(state)
|
||||
|
||||
assertFalse(binding.cookieBannerItem.isVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN updateDetailsSection is called THEN update the visibility of the section`() {
|
||||
every { settings.shouldUseTrackingProtection } returns false
|
||||
|
||||
view.updateDetailsSection(false)
|
||||
|
||||
assertFalse(binding.trackingProtectionDetails.isVisible)
|
||||
|
||||
view.updateDetailsSection(true)
|
||||
|
||||
assertTrue(binding.trackingProtectionDetails.isVisible)
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
/* 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.settings.quicksettings
|
||||
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.isVisible
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.databinding.QuicksettingsTrackingProtectionBinding
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.trackingprotection.TrackingProtectionState
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class TrackingProtectionViewTest {
|
||||
|
||||
private lateinit var view: TrackingProtectionView
|
||||
private lateinit var binding: QuicksettingsTrackingProtectionBinding
|
||||
private lateinit var interactor: TrackingProtectionInteractor
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var settings: Settings
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
interactor = mockk(relaxed = true)
|
||||
view = spyk(TrackingProtectionView(FrameLayout(testContext), interactor, settings))
|
||||
binding = view.binding
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN updating THEN bind checkbox`() {
|
||||
val websiteUrl = "https://mozilla.org"
|
||||
val state = TrackingProtectionState(
|
||||
tab = createTab(url = websiteUrl),
|
||||
url = websiteUrl,
|
||||
isTrackingProtectionEnabled = true,
|
||||
listTrackers = listOf(),
|
||||
mode = TrackingProtectionState.Mode.Normal,
|
||||
lastAccessedCategory = "",
|
||||
)
|
||||
|
||||
every { settings.shouldUseTrackingProtection } returns true
|
||||
|
||||
view.update(state)
|
||||
|
||||
assertTrue(binding.root.isVisible)
|
||||
assertTrue(binding.trackingProtectionSwitch.switchWidget.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN TP is globally off WHEN updating THEN hide the TP section`() {
|
||||
val websiteUrl = "https://mozilla.org"
|
||||
val state = TrackingProtectionState(
|
||||
tab = createTab(url = websiteUrl),
|
||||
url = websiteUrl,
|
||||
isTrackingProtectionEnabled = true,
|
||||
listTrackers = listOf(),
|
||||
mode = TrackingProtectionState.Mode.Normal,
|
||||
lastAccessedCategory = "",
|
||||
)
|
||||
|
||||
every { settings.shouldUseTrackingProtection } returns false
|
||||
|
||||
view.update(state)
|
||||
|
||||
assertFalse(binding.root.isVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN updateDetailsSection is called THEN update the visibility of the section`() {
|
||||
every { settings.shouldUseTrackingProtection } returns false
|
||||
|
||||
view.updateDetailsSection(false)
|
||||
|
||||
assertFalse(binding.trackingProtectionDetails.isVisible)
|
||||
|
||||
view.updateDetailsSection(true)
|
||||
|
||||
assertTrue(binding.trackingProtectionDetails.isVisible)
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
/* 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.settings.quicksettings.protections.cookiebanners
|
||||
|
||||
import android.widget.FrameLayout
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||
import mozilla.components.support.ktx.kotlin.toShortUrl
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.databinding.ComponentCookieBannerDetailsPanelBinding
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.trackingprotection.ProtectionsState
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class CookieBannerHandlingDetailsViewTest {
|
||||
|
||||
private lateinit var view: CookieBannerHandlingDetailsView
|
||||
private lateinit var binding: ComponentCookieBannerDetailsPanelBinding
|
||||
private lateinit var interactor: CookieBannerDetailsInteractor
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var publicSuffixList: PublicSuffixList
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
interactor = mockk(relaxed = true)
|
||||
view = spyk(
|
||||
CookieBannerHandlingDetailsView(
|
||||
container = FrameLayout(testContext),
|
||||
context = testContext,
|
||||
publicSuffixList = publicSuffixList,
|
||||
interactor = interactor,
|
||||
),
|
||||
)
|
||||
binding = view.binding
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN updating THEN bind title,back button, description and switch`() {
|
||||
val websiteUrl = "https://mozilla.org"
|
||||
val state = ProtectionsState(
|
||||
tab = createTab(url = websiteUrl),
|
||||
url = websiteUrl,
|
||||
isTrackingProtectionEnabled = true,
|
||||
isCookieBannerHandlingEnabled = true,
|
||||
listTrackers = listOf(),
|
||||
mode = ProtectionsState.Mode.Normal,
|
||||
lastAccessedCategory = "",
|
||||
)
|
||||
|
||||
view.update(state)
|
||||
|
||||
verify {
|
||||
view.bindTitle(state.url, state.isCookieBannerHandlingEnabled)
|
||||
view.bindBackButtonListener()
|
||||
view.bindDescription(state.isCookieBannerHandlingEnabled)
|
||||
view.bindSwitch(state.isCookieBannerHandlingEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cookie banner handling is enabled WHEN biding title THEN title view must have the expected string`() {
|
||||
val websiteUrl = "https://mozilla.org"
|
||||
|
||||
view.bindTitle(url = websiteUrl, isCookieBannerHandlingEnabled = true)
|
||||
|
||||
val expectedText =
|
||||
testContext.getString(
|
||||
R.string.reduce_cookie_banner_details_panel_title_off_for_site,
|
||||
websiteUrl.toShortUrl(publicSuffixList),
|
||||
)
|
||||
|
||||
assertEquals(expectedText, view.binding.title.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cookie banner handling is disabled WHEN biding title THEN title view must have the expected string`() {
|
||||
val websiteUrl = "https://mozilla.org"
|
||||
|
||||
view.bindTitle(url = websiteUrl, isCookieBannerHandlingEnabled = false)
|
||||
|
||||
val expectedText =
|
||||
testContext.getString(
|
||||
R.string.reduce_cookie_banner_details_panel_title_on_for_site,
|
||||
websiteUrl.toShortUrl(publicSuffixList),
|
||||
)
|
||||
|
||||
assertEquals(expectedText, view.binding.title.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN clicking the back button THEN view must delegate to the interactor#onBackPressed()`() {
|
||||
view.bindBackButtonListener()
|
||||
|
||||
view.binding.navigateBack.performClick()
|
||||
|
||||
verify {
|
||||
interactor.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cookie banner handling is enabled WHEN biding description THEN description view must have the expected string`() {
|
||||
view.bindDescription(isCookieBannerHandlingEnabled = true)
|
||||
|
||||
val expectedText =
|
||||
testContext.getString(
|
||||
R.string.reduce_cookie_banner_details_panel_description_off_for_site,
|
||||
testContext.getString(R.string.app_name),
|
||||
)
|
||||
|
||||
assertEquals(expectedText, view.binding.details.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cookie banner handling is disabled WHEN biding description THEN description view must have the expected string`() {
|
||||
view.bindDescription(isCookieBannerHandlingEnabled = false)
|
||||
|
||||
val expectedText =
|
||||
testContext.getString(
|
||||
R.string.reduce_cookie_banner_details_panel_description_on_for_site,
|
||||
)
|
||||
|
||||
assertEquals(expectedText, view.binding.details.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cookie banner handling is disabled WHEN biding switch THEN switch view must have the expected isChecked status`() {
|
||||
view.bindSwitch(isCookieBannerHandlingEnabled = false)
|
||||
|
||||
assertFalse(view.binding.cookieBannerSwitch.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cookie banner handling is enabled WHEN biding switch THEN switch view must have the expected isChecked status`() {
|
||||
view.bindSwitch(isCookieBannerHandlingEnabled = true)
|
||||
|
||||
assertTrue(view.binding.cookieBannerSwitch.isChecked)
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
/* 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.settings.quicksettings.protections.cookiebanners
|
||||
|
||||
import android.content.Context
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDirections
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.cookiehandling.CookieBannersStorage
|
||||
import mozilla.components.concept.engine.permission.SitePermissions
|
||||
import mozilla.components.feature.session.SessionUseCases
|
||||
import mozilla.components.feature.session.TrackingProtectionUseCases
|
||||
import mozilla.components.service.glean.testing.GleanTestRule
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import mozilla.components.support.test.rule.runTestOnMain
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.GleanMetrics.CookieBanners
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.trackingprotection.ProtectionsAction
|
||||
import org.mozilla.fenix.trackingprotection.ProtectionsStore
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
internal class DefaultCookieBannerDetailsControllerTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var navController: NavController
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var fragment: Fragment
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var sitePermissions: SitePermissions
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var cookieBannersStorage: CookieBannersStorage
|
||||
|
||||
private lateinit var controller: DefaultCookieBannerDetailsController
|
||||
|
||||
private lateinit var tab: TabSessionState
|
||||
|
||||
private lateinit var browserStore: BrowserStore
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var protectionsStore: ProtectionsStore
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var reload: SessionUseCases.ReloadUrlUseCase
|
||||
|
||||
private var gravity = 54
|
||||
|
||||
@get:Rule
|
||||
val coroutinesTestRule = MainCoroutineRule()
|
||||
private val scope = coroutinesTestRule.scope
|
||||
|
||||
@get:Rule
|
||||
val gleanRule = GleanTestRule(testContext)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
val trackingProtectionUseCases: TrackingProtectionUseCases = mockk(relaxed = true)
|
||||
context = spyk(testContext)
|
||||
tab = createTab("https://mozilla.org")
|
||||
browserStore = BrowserStore(BrowserState(tabs = listOf(tab)))
|
||||
controller = DefaultCookieBannerDetailsController(
|
||||
fragment = fragment,
|
||||
context = context,
|
||||
ioScope = scope,
|
||||
cookieBannersStorage = cookieBannersStorage,
|
||||
navController = { navController },
|
||||
sitePermissions = sitePermissions,
|
||||
gravity = gravity,
|
||||
getCurrentTab = { tab },
|
||||
sessionId = tab.id,
|
||||
browserStore = browserStore,
|
||||
protectionsStore = protectionsStore,
|
||||
reload = reload,
|
||||
)
|
||||
|
||||
every { fragment.context } returns context
|
||||
every { context.components.useCases.trackingProtectionUseCases } returns trackingProtectionUseCases
|
||||
|
||||
val onComplete = slot<(Boolean) -> Unit>()
|
||||
every {
|
||||
trackingProtectionUseCases.containsException.invoke(
|
||||
any(),
|
||||
capture(onComplete),
|
||||
)
|
||||
}.answers { onComplete.captured.invoke(true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN handleBackPressed is called THEN should call popBackStack and navigate`() {
|
||||
controller.handleBackPressed()
|
||||
|
||||
verify {
|
||||
navController.popBackStack()
|
||||
|
||||
navController.navigate(any<NavDirections>())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cookie banner is enabled WHEN handleTogglePressed THEN remove from the storage, send telemetry and reload the tab`() =
|
||||
runTestOnMain {
|
||||
val isEnabled = true
|
||||
|
||||
assertNull(CookieBanners.exceptionRemoved.testGetValue())
|
||||
every { protectionsStore.dispatch(any()) } returns mockk()
|
||||
|
||||
controller.handleTogglePressed(isEnabled)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerifyOrder {
|
||||
cookieBannersStorage.removeException(
|
||||
uri = tab.content.url,
|
||||
privateBrowsing = tab.content.private,
|
||||
)
|
||||
protectionsStore.dispatch(
|
||||
ProtectionsAction.ToggleCookieBannerHandlingProtectionEnabled(
|
||||
isEnabled,
|
||||
),
|
||||
)
|
||||
reload(tab.id)
|
||||
}
|
||||
|
||||
assertNotNull(CookieBanners.exceptionRemoved.testGetValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cookie banner is disabled WHEN handleTogglePressed THEN remove from the storage, send telemetry and reload the tab`() =
|
||||
runTestOnMain {
|
||||
val isEnabled = false
|
||||
|
||||
assertNull(CookieBanners.exceptionRemoved.testGetValue())
|
||||
every { protectionsStore.dispatch(any()) } returns mockk()
|
||||
|
||||
controller.handleTogglePressed(isEnabled)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerifyOrder {
|
||||
cookieBannersStorage.addException(
|
||||
uri = tab.content.url,
|
||||
privateBrowsing = tab.content.private,
|
||||
)
|
||||
protectionsStore.dispatch(
|
||||
ProtectionsAction.ToggleCookieBannerHandlingProtectionEnabled(
|
||||
isEnabled,
|
||||
),
|
||||
)
|
||||
reload(tab.id)
|
||||
}
|
||||
|
||||
assertNotNull(CookieBanners.exceptionAdded.testGetValue())
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/* 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.settings.quicksettings.protections.cookiebanners
|
||||
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class DefaultCookieBannerDetailsInteractorTest {
|
||||
private lateinit var controller: CookieBannerDetailsController
|
||||
private lateinit var interactor: DefaultCookieBannerDetailsInteractor
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
controller = mockk(relaxed = true)
|
||||
interactor = DefaultCookieBannerDetailsInteractor(controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN onBackPressed is called THEN delegate the controller`() {
|
||||
interactor.onBackPressed()
|
||||
|
||||
verify {
|
||||
controller.handleBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN onTogglePressed is called THEN delegate the controller`() {
|
||||
interactor.onTogglePressed(true)
|
||||
|
||||
verify {
|
||||
controller.handleTogglePressed(true)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue