commit cde75246bb966f5e59cf44b7680550e242036e31
parent 7cc1cdd434a416da2d5a812cb7373d84b961b327
Author: mcarare <48995920+mcarare@users.noreply.github.com>
Date: Wed, 8 Oct 2025 18:23:46 +0000
Bug 1993170 - Improve TrackingProtectionBlockingFragment testability. r=android-reviewers,avirvara
This patch refactors the `TrackingProtectionBlockingFragment` to improve testability by allowing `Settings` to be mocked.
The key changes include:
- A new `updateCategoryVisibility` function adjusts the visibility of tracker categories based on the selected protection mode (`STANDARD`, `STRICT`, or `CUSTOM`).
- Testability is improved by introducing a `settingsProvider`, which allows for injecting a mocked `Settings` instance.
- New tests have been added to verify the correct visibility of categories for each protection mode.
- Existing tests for Total Cookie Protection text have been updated to use the new mocking mechanism and remove static mocking.
Differential Revision: https://phabricator.services.mozilla.com/D267953
Diffstat:
3 files changed, 153 insertions(+), 31 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt
@@ -14,12 +14,23 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentTrackingProtectionBlockingBinding
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
+import org.mozilla.fenix.utils.Settings
-class TrackingProtectionBlockingFragment :
- Fragment(R.layout.fragment_tracking_protection_blocking) {
+/**
+ * A screen that displays a read-only list of the types of trackers and scripts
+ * that are being blocked by a specific tracking protection mode (Standard, Strict, or Custom).
+ *
+ * This fragment receives the [TrackingProtectionMode] as a navigation argument and adjusts the
+ * visibility of the tracker categories based on the selected mode and its current settings.
+ * For example, in "Standard" mode, it shows a default set of blocked categories, while in
+ * "Custom" mode, it reflects the user's specific choices.
+ */
+class TrackingProtectionBlockingFragment : Fragment(R.layout.fragment_tracking_protection_blocking) {
private val args: TrackingProtectionBlockingFragmentArgs by navArgs()
- private val settings by lazy { requireContext().settings() }
+
+ internal var settingsProvider: () -> Settings = { requireContext().settings() }
+ private val settings: Settings by lazy { settingsProvider() }
@VisibleForTesting
internal lateinit var binding: FragmentTrackingProtectionBlockingBinding
@@ -29,20 +40,49 @@ class TrackingProtectionBlockingFragment :
binding = FragmentTrackingProtectionBlockingBinding.bind(view)
+ setTotalCookieProtectionText(settings.enabledTotalCookieProtection)
+
+ updateCategoryVisibility(args.protectionMode, settings)
+ }
+
+ /**
+ * Sets the title and description for the "Cookies" category based on whether the
+ * "Total Cookie Protection" feature is enabled.
+ *
+ * @param totalCookieProtectionEnabled A flag indicating if the
+ * `enabledTotalCookieProtection` feature flag is active.
+ */
+ @VisibleForTesting
+ internal fun setTotalCookieProtectionText(totalCookieProtectionEnabled: Boolean) {
// Text for the updated "Total cookie protection" option should be updated as part of a staged rollout
- if (requireContext().settings().enabledTotalCookieProtection) {
+ if (totalCookieProtectionEnabled) {
binding.categoryCookies.apply {
trackingProtectionCategoryTitle.text = getText(R.string.etp_cookies_title_2)
trackingProtectionCategoryItemDescription.text = getText(R.string.etp_cookies_description_2)
}
}
+ }
- when (args.protectionMode) {
+ /**
+ * Updates the visibility of the tracker category views on the screen based on the selected
+ * tracking protection mode and its current configuration.
+ *
+ * - In **Standard** mode, all categories are shown except for "Tracking content".
+ * - In **Strict** mode, all categories are shown.
+ * - In **Custom** mode, the visibility of each category is determined by user preferences.
+ *
+ * @param protectionMode The current tracking protection mode (`STANDARD`, `STRICT`, or `CUSTOM`).
+ * @param settings The app's settings object, used to check individual blocking preferences for `CUSTOM` mode.
+ */
+ private fun updateCategoryVisibility(protectionMode: TrackingProtectionMode, settings: Settings) {
+ when (protectionMode) {
TrackingProtectionMode.STANDARD -> {
binding.categoryTrackingContent.isVisible = false
}
- TrackingProtectionMode.STRICT -> {}
+ TrackingProtectionMode.STRICT -> {
+ // All categories are visible by default
+ }
TrackingProtectionMode.CUSTOM -> {
binding.categoryFingerprinters.isVisible =
diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragmentTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragmentTest.kt
@@ -4,60 +4,139 @@
package org.mozilla.fenix.trackingprotection
-import android.content.Context
+import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import io.mockk.every
import io.mockk.mockk
-import io.mockk.mockkStatic
+import junit.framework.TestCase.assertFalse
+import junit.framework.TestCase.assertTrue
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
-import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.trackingprotection.TrackingProtectionMode.CUSTOM
+import org.mozilla.fenix.trackingprotection.TrackingProtectionMode.STANDARD
+import org.mozilla.fenix.trackingprotection.TrackingProtectionMode.STRICT
+import org.mozilla.fenix.utils.Settings
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class TrackingProtectionBlockingFragmentTest {
+
@Test
- fun `GIVEN total cookie protection is enabled WHEN showing details about the protection options THEN show update details for tracking protection`() {
+ fun `GIVEN total cookie protection is enabled WHEN showing details THEN show the updated cookie protection text`() {
val expectedTitle = testContext.getString(R.string.etp_cookies_title_2)
val expectedDescription = testContext.getString(R.string.etp_cookies_description_2)
- mockkStatic("org.mozilla.fenix.ext.ContextKt") {
- every { any<Context>().settings() } returns mockk(relaxed = true) {
- every { enabledTotalCookieProtection } returns true
- }
+ val mockSettings = mockk<Settings>(relaxed = true) {
+ every { enabledTotalCookieProtection } returns true
+ }
- val fragment = createFragment()
+ val fragment = createFragment(mockSettings)
- val cookiesCategory = fragment.binding.categoryCookies
- assertEquals(expectedTitle, cookiesCategory.trackingProtectionCategoryTitle.text)
- assertEquals(expectedDescription, cookiesCategory.trackingProtectionCategoryItemDescription.text)
- }
+ val cookiesCategory = fragment.binding.categoryCookies
+ assertEquals(expectedTitle, cookiesCategory.trackingProtectionCategoryTitle.text.toString())
+ assertEquals(expectedDescription, cookiesCategory.trackingProtectionCategoryItemDescription.text.toString())
}
@Test
- fun `GIVEN total cookie protection is not enabled WHEN showing details about the protection options THEN show the default details for tracking protection`() {
+ fun `GIVEN total cookie protection is disabled WHEN showing details THEN show the default cookie protection text`() {
val expectedTitle = testContext.getString(R.string.etp_cookies_title)
val expectedDescription = testContext.getString(R.string.etp_cookies_description)
- mockkStatic("org.mozilla.fenix.ext.ContextKt") {
- every { any<Context>().settings() } returns mockk(relaxed = true) {
- every { enabledTotalCookieProtection } returns false
- }
+ val mockSettings = mockk<Settings>(relaxed = true) {
+ every { enabledTotalCookieProtection } returns false
+ }
+
+ val fragment = createFragment(mockSettings)
+
+ val cookiesCategory = fragment.binding.categoryCookies
+ assertEquals(expectedTitle, cookiesCategory.trackingProtectionCategoryTitle.text.toString())
+ assertEquals(expectedDescription, cookiesCategory.trackingProtectionCategoryItemDescription.text.toString())
+ }
+
+ @Test
+ fun `GIVEN standard mode WHEN creating fragment THEN only tracking content category is hidden`() {
+ val mockSettings = mockk<Settings>(relaxed = true)
+ val fragment = createFragment(mockSettings, STANDARD)
- val fragment = createFragment()
+ with(fragment.binding) {
+ assertTrue(categoryCookies.isVisible)
+ assertTrue(categoryCryptominers.isVisible)
+ assertTrue(categoryFingerprinters.isVisible)
+ assertTrue(categoryRedirectTrackers.isVisible)
+ assertTrue(categorySuspectedFingerprinters.isVisible)
+ assertFalse(categoryTrackingContent.isVisible)
+ }
+ }
- val cookiesCategory = fragment.binding.categoryCookies
- assertEquals(expectedTitle, cookiesCategory.trackingProtectionCategoryTitle.text)
- assertEquals(expectedDescription, cookiesCategory.trackingProtectionCategoryItemDescription.text)
+ @Test
+ fun `GIVEN strict mode WHEN creating fragment THEN all categories are visible`() {
+ val mockSettings = mockk<Settings>(relaxed = true)
+ val fragment = createFragment(mockSettings, STRICT)
+
+ with(fragment.binding) {
+ assertTrue(categoryCookies.isVisible)
+ assertTrue(categoryTrackingContent.isVisible)
+ assertTrue(categoryCryptominers.isVisible)
+ assertTrue(categoryFingerprinters.isVisible)
+ assertTrue(categoryRedirectTrackers.isVisible)
+ assertTrue(categorySuspectedFingerprinters.isVisible)
}
}
- private fun createFragment(): TrackingProtectionBlockingFragment {
+ @Test
+ fun `GIVEN custom mode WHEN all blocking settings are true THEN all categories are visible`() {
+ val mockSettings = mockk<Settings> {
+ every { enabledTotalCookieProtection } returns false
+ every { blockFingerprintersInCustomTrackingProtection } returns true
+ every { blockCryptominersInCustomTrackingProtection } returns true
+ every { blockCookiesInCustomTrackingProtection } returns true
+ every { blockTrackingContentInCustomTrackingProtection } returns true
+ every { blockRedirectTrackersInCustomTrackingProtection } returns true
+ every { blockSuspectedFingerprintersInCustomTrackingProtection } returns true
+ }
+ val fragment = createFragment(mockSettings, CUSTOM)
+
+ with(fragment.binding) {
+ assertTrue(categoryCookies.isVisible)
+ assertTrue(categoryTrackingContent.isVisible)
+ assertTrue(categoryCryptominers.isVisible)
+ assertTrue(categoryFingerprinters.isVisible)
+ assertTrue(categoryRedirectTrackers.isVisible)
+ assertTrue(categorySuspectedFingerprinters.isVisible)
+ }
+ }
+
+ @Test
+ fun `GIVEN custom mode WHEN all blocking settings are false THEN all categories are hidden`() {
+ val mockSettings = mockk<Settings> {
+ every { enabledTotalCookieProtection } returns false
+ every { blockFingerprintersInCustomTrackingProtection } returns false
+ every { blockCryptominersInCustomTrackingProtection } returns false
+ every { blockCookiesInCustomTrackingProtection } returns false
+ every { blockTrackingContentInCustomTrackingProtection } returns false
+ every { blockRedirectTrackersInCustomTrackingProtection } returns false
+ every { blockSuspectedFingerprintersInCustomTrackingProtection } returns false
+ }
+ val fragment = createFragment(mockSettings, CUSTOM)
+
+ with(fragment.binding) {
+ assertFalse(categoryCookies.isVisible)
+ assertFalse(categoryTrackingContent.isVisible)
+ assertFalse(categoryCryptominers.isVisible)
+ assertFalse(categoryFingerprinters.isVisible)
+ assertFalse(categoryRedirectTrackers.isVisible)
+ assertFalse(categorySuspectedFingerprinters.isVisible)
+ }
+ }
+
+ private fun createFragment(
+ mockedSettings: Settings,
+ protectionMode: TrackingProtectionMode = CUSTOM,
+ ): TrackingProtectionBlockingFragment {
// Create and attach the fragment ourself instead of using "createAddedTestFragment"
// to prevent having "onResume -> showToolbar" called.
@@ -65,11 +144,15 @@ class TrackingProtectionBlockingFragmentTest {
.create()
.start()
.get()
+
val fragment = TrackingProtectionBlockingFragment().apply {
arguments = TrackingProtectionBlockingFragmentArgs(
- protectionMode = CUSTOM,
+ protectionMode = protectionMode,
).toBundle()
+ // Set the provider to return our mock settings for this test instance
+ settingsProvider = { mockedSettings }
}
+
activity.supportFragmentManager.beginTransaction()
.add(fragment, "test")
.commitNow()
diff --git a/mobile/android/fenix/config/detekt-baseline.xml b/mobile/android/fenix/config/detekt-baseline.xml
@@ -579,7 +579,6 @@
<ID>UndocumentedPublicClass:TopSites.kt$TopSiteColors$Companion</ID>
<ID>UndocumentedPublicClass:TrackerBuckets.kt$TrackerBuckets$BucketedTrackerLog</ID>
<ID>UndocumentedPublicClass:TrackerBuckets.kt$TrackerBuckets$Companion</ID>
- <ID>UndocumentedPublicClass:TrackingProtectionBlockingFragment.kt$TrackingProtectionBlockingFragment : Fragment</ID>
<ID>UndocumentedPublicClass:TrackingProtectionCategoryItem.kt$TrackingProtectionCategoryItem : ConstraintLayout</ID>
<ID>UndocumentedPublicClass:TrackingProtectionExceptionsAdapter.kt$TrackingProtectionExceptionsAdapter$TrackingProtectionAdapterItem : Item</ID>
<ID>UndocumentedPublicClass:TrackingProtectionExceptionsInteractor.kt$DefaultTrackingProtectionExceptionsInteractor : TrackingProtectionExceptionsInteractor</ID>