tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt | 52++++++++++++++++++++++++++++++++++++++++++++++------
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragmentTest.kt | 131++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mmobile/android/fenix/config/detekt-baseline.xml | 1-
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>