chromium/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/FledgeFragmentTest.java

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.privacy_sandbox;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.Espresso.pressBack;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import static org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxTestUtils.clickImageButtonNextToText;
import static org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxTestUtils.getRootViewSanitized;
import static org.chromium.ui.test.util.ViewUtils.clickOnClickableSpan;
import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;

import android.view.View;

import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.filters.SmallTest;

import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.base.test.util.UserActionTester;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
import org.chromium.chrome.test.ChromeBrowserTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.ChromeRenderTestRule;
import org.chromium.components.policy.test.annotations.Policies;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.ui.test.util.RenderTestRule;
import org.chromium.ui.test.util.ViewUtils;

import java.io.IOException;

/** Tests {@link FledgeFragment} */
@RunWith(ChromeJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public final class FledgeFragmentTest {
    private static final String SITE_NAME_1 = "first.com";
    private static final String SITE_NAME_2 = "second.com";

    @Rule public ChromeBrowserTestRule mChromeBrowserTestRule = new ChromeBrowserTestRule();

    @Rule
    public ChromeRenderTestRule mRenderTestRule =
            ChromeRenderTestRule.Builder.withPublicCorpus()
                    .setBugComponent(RenderTestRule.Component.UI_SETTINGS_PRIVACY)
                    .setRevision(1)
                    .build();

    @Rule
    public SettingsActivityTestRule<FledgeFragment> mSettingsActivityTestRule =
            new SettingsActivityTestRule<>(FledgeFragment.class);

    @Rule public JniMocker mocker = new JniMocker();

    private FakePrivacySandboxBridge mFakePrivacySandboxBridge;
    private UserActionTester mUserActionTester;

    @Before
    public void setUp() {
        mFakePrivacySandboxBridge = new FakePrivacySandboxBridge();
        mocker.mock(PrivacySandboxBridgeJni.TEST_HOOKS, mFakePrivacySandboxBridge);

        mUserActionTester = new UserActionTester();
    }

    @After
    public void tearDown() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    PrefService prefService =
                            UserPrefs.get(ProfileManager.getLastUsedRegularProfile());
                    prefService.clearPref(Pref.PRIVACY_SANDBOX_M1_FLEDGE_ENABLED);
                });

        mUserActionTester.tearDown();
    }

    private void startFledgeSettings() {
        mSettingsActivityTestRule.startSettingsActivity();
        ViewUtils.onViewWaiting(
                allOf(
                        withText(R.string.settings_fledge_page_title),
                        withParent(withId(R.id.action_bar))));
    }

    private Matcher<View> getFledgeToggleMatcher() {
        return allOf(
                withId(R.id.switchWidget),
                withParent(
                        withParent(
                                hasDescendant(
                                        withText(R.string.settings_fledge_page_toggle_label)))));
    }

    private View getFledgeRootView() {
        return getRootViewSanitized(R.string.settings_fledge_page_toggle_sub_label);
    }

    private View getAllSitesPageRootView() {
        return getRootViewSanitized(R.string.settings_fledge_all_sites_sub_page_title);
    }

    private View getBlockedSitesPageRootView() {
        return getRootViewSanitized(R.string.settings_fledge_page_blocked_sites_sub_page_title);
    }

    private View getLearnMoreRootView() {
        return getRootViewSanitized(R.string.settings_fledge_page_learn_more_heading);
    }

    private void setFledgePrefEnabled(boolean isEnabled) {
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        FledgeFragment.setFledgePrefEnabled(
                                ProfileManager.getLastUsedRegularProfile(), isEnabled));
    }

    private boolean isFledgePrefEnabled() {
        return ThreadUtils.runOnUiThreadBlocking(
                () ->
                        FledgeFragment.isFledgePrefEnabled(
                                ProfileManager.getLastUsedRegularProfile()));
    }

    private void scrollToSetting(Matcher<View> matcher) {
        onView(withId(R.id.recycler_view))
                .perform(RecyclerViewActions.scrollTo(hasDescendant(matcher)));
    }

    private String generateSiteFromNr(int nr) {
        return "site-" + nr + ".com";
    }

    @Test
    @SmallTest
    @Feature({"RenderTest"})
    @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_PROACTIVE_TOPICS_BLOCKING)
    public void testRenderFledgeOff() throws IOException {
        setFledgePrefEnabled(false);
        startFledgeSettings();
        mRenderTestRule.render(getFledgeRootView(), "fledge_page_off");
    }

    @Test
    @SmallTest
    @Feature({"RenderTest"})
    @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_PROACTIVE_TOPICS_BLOCKING)
    public void testRenderFledgeEmpty() throws IOException {
        setFledgePrefEnabled(true);
        startFledgeSettings();
        mRenderTestRule.render(getFledgeRootView(), "fledge_page_empty");
    }

    @Test
    @SmallTest
    @Feature({"RenderTest"})
    @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_PROACTIVE_TOPICS_BLOCKING)
    public void testRenderFledgePopulated() throws IOException {
        setFledgePrefEnabled(true);
        mFakePrivacySandboxBridge.setCurrentFledgeSites(SITE_NAME_1, SITE_NAME_2);
        startFledgeSettings();
        mRenderTestRule.render(getFledgeRootView(), "fledge_page_populated");
    }

    @Test
    @SmallTest
    @Feature({"RenderTest"})
    public void testRenderAllSitesPage() throws IOException {
        setFledgePrefEnabled(true);
        for (int i = 0; i < FledgeFragment.MAX_DISPLAYED_SITES + 1; i++) {
            mFakePrivacySandboxBridge.setFledgeJoiningAllowed(generateSiteFromNr(i), true);
        }
        startFledgeSettings();
        scrollToSetting(withText(R.string.settings_fledge_page_see_all_sites_label));
        onView(withText(R.string.settings_fledge_page_see_all_sites_label)).perform(click());
        mRenderTestRule.render(getAllSitesPageRootView(), "fledge_all_sites_page");
    }

    @Test
    @SmallTest
    @Feature({"RenderTest"})
    public void testRenderBlockedSitesPageEmpty() throws IOException {
        setFledgePrefEnabled(false);
        startFledgeSettings();
        onView(withText(R.string.settings_fledge_page_blocked_sites_heading)).perform(click());
        mRenderTestRule.render(getBlockedSitesPageRootView(), "fledge_blocked_sites_empty");
    }

    @Test
    @SmallTest
    @Feature({"RenderTest"})
    public void testRenderBlockedSitesPagePopulated() throws IOException {
        setFledgePrefEnabled(true);
        mFakePrivacySandboxBridge.setBlockedFledgeSites(SITE_NAME_1, SITE_NAME_2);
        startFledgeSettings();
        onView(withText(R.string.settings_fledge_page_blocked_sites_heading)).perform(click());
        mRenderTestRule.render(getBlockedSitesPageRootView(), "fledge_blocked_sites_populated");
    }

    @Test
    @SmallTest
    @Feature({"RenderTest"})
    public void testRenderLearnMore() throws IOException {
        setFledgePrefEnabled(true);
        mFakePrivacySandboxBridge.setCurrentFledgeSites(SITE_NAME_1, SITE_NAME_2);
        startFledgeSettings();
        onView(withText(containsString("30 days. Learn more"))).perform(clickOnClickableSpan(0));
        mRenderTestRule.render(getLearnMoreRootView(), "fledge_learn_more");
    }

    @Test
    @SmallTest
    public void testToggleUncheckedWhenFledgeOff() {
        setFledgePrefEnabled(false);
        startFledgeSettings();
        onView(getFledgeToggleMatcher()).check(matches(not(isChecked())));
    }

    @Test
    @SmallTest
    public void testToggleCheckedWhenFledgeOn() {
        setFledgePrefEnabled(true);
        startFledgeSettings();
        onView(getFledgeToggleMatcher()).check(matches(isChecked()));
    }

    @Test
    @SmallTest
    public void testTurnFledgeOnWhenSitesListEmpty() {
        setFledgePrefEnabled(false);
        startFledgeSettings();
        onView(getFledgeToggleMatcher()).perform(click());

        assertTrue(isFledgePrefEnabled());
        onViewWaiting(withText(R.string.settings_fledge_page_current_sites_description_empty))
                .check(matches(isDisplayed()));
        onView(withText(R.string.settings_fledge_page_current_sites_description_disabled))
                .check(doesNotExist());

        assertThat(
                mUserActionTester.getActions(), hasItems("Settings.PrivacySandbox.Fledge.Enabled"));
    }

    @Test
    @SmallTest
    public void testTurnFledgeOnWhenSitesListPopulated() {
        setFledgePrefEnabled(false);
        mFakePrivacySandboxBridge.setCurrentFledgeSites(SITE_NAME_1, SITE_NAME_2);
        startFledgeSettings();

        // Check that the sites list is not displayed when Fledge is disabled.
        onView(withText(SITE_NAME_1)).check(doesNotExist());
        onView(withText(SITE_NAME_2)).check(doesNotExist());

        // Click on the toggle.
        onView(getFledgeToggleMatcher()).perform(click());

        // Check that the sites list is displayed when Fledge is enabled.
        onView(withText(SITE_NAME_1)).check(matches(isDisplayed()));
        onView(withText(SITE_NAME_2)).check(matches(isDisplayed()));

        // Check that actions are reported
        assertThat(
                mUserActionTester.getActions(), hasItems("Settings.PrivacySandbox.Fledge.Enabled"));
    }

    @Test
    @SmallTest
    public void testTurnFledgeOff() {
        setFledgePrefEnabled(true);
        startFledgeSettings();
        onView(getFledgeToggleMatcher()).perform(click());

        assertFalse(isFledgePrefEnabled());
        onViewWaiting(withText(R.string.settings_fledge_page_current_sites_description_disabled))
                .check(matches(isDisplayed()));
        onView(withText(R.string.settings_fledge_page_current_sites_description_empty))
                .check(doesNotExist());

        assertThat(
                mUserActionTester.getActions(),
                hasItems("Settings.PrivacySandbox.Fledge.Disabled"));
    }

    @Test
    @SmallTest
    public void testPopulateSitesList() {
        setFledgePrefEnabled(true);
        mFakePrivacySandboxBridge.setCurrentFledgeSites(SITE_NAME_1, SITE_NAME_2);
        startFledgeSettings();

        // Check that the all sites pref is not displayed
        onView(withText(R.string.settings_fledge_page_see_all_sites_label)).check(doesNotExist());

        // Check that the sites are displayed.
        onView(withText(SITE_NAME_1)).check(matches(isDisplayed()));
        onView(withText(SITE_NAME_2)).check(matches(isDisplayed()));
    }

    @Test
    @SmallTest
    public void testMaxDisplayedSites() {
        setFledgePrefEnabled(true);
        for (int i = 0; i < FledgeFragment.MAX_DISPLAYED_SITES + 1; i++) {
            mFakePrivacySandboxBridge.setFledgeJoiningAllowed(generateSiteFromNr(i), true);
        }
        startFledgeSettings();

        // Scroll to pref below last displayed site.
        scrollToSetting(withText(R.string.settings_fledge_page_see_all_sites_label));

        String lastDisplayedSite = generateSiteFromNr(FledgeFragment.MAX_DISPLAYED_SITES - 1);
        String firstNotDisplayedSite = generateSiteFromNr(FledgeFragment.MAX_DISPLAYED_SITES);

        // Verify that only MAX_DISPLAY_SITES are shown.
        onView(withText(lastDisplayedSite)).check(matches(isDisplayed()));
        onView(withText(firstNotDisplayedSite)).check(doesNotExist());

        // Navigate to All Sites page.
        onView(withText(R.string.settings_fledge_page_see_all_sites_label)).perform(click());

        // Verify that all sites are displayed
        scrollToSetting(withText(firstNotDisplayedSite));
        onView(withText(firstNotDisplayedSite)).check(matches(isDisplayed()));

        // Verify actions are reported
        assertThat(
                mUserActionTester.getActions(),
                hasItems("Settings.PrivacySandbox.Fledge.AllSitesOpened"));
    }

    @Test
    @SmallTest
    public void testBlockSites() {
        setFledgePrefEnabled(true);
        mFakePrivacySandboxBridge.setCurrentFledgeSites(SITE_NAME_1, SITE_NAME_2);
        startFledgeSettings();

        // Remove the first site from the list.
        clickImageButtonNextToText(SITE_NAME_1);
        onView(withText(SITE_NAME_1)).check(doesNotExist());
        onView(withText(R.string.settings_fledge_page_block_site_snackbar))
                .check(matches(isDisplayed()));

        // Remove the second site from the list.
        clickImageButtonNextToText(SITE_NAME_2);
        onView(withText(SITE_NAME_2)).check(doesNotExist());
        onView(withText(R.string.settings_fledge_page_block_site_snackbar))
                .check(matches(isDisplayed()));

        // Check that the empty state UI is displayed when the sites list is empty.
        onView(withText(R.string.settings_fledge_page_current_sites_description_empty))
                .check(matches(isDisplayed()));

        // Open the blocked sites sub-page.
        onView(withText(R.string.settings_fledge_page_blocked_sites_heading)).perform(click());
        onViewWaiting(withText(R.string.settings_fledge_page_blocked_sites_sub_page_title));

        // Verify that the sites are blocked.
        onView(withText(SITE_NAME_1)).check(matches(isDisplayed()));
        onView(withText(SITE_NAME_2)).check(matches(isDisplayed()));

        // Verify that actions are reported
        assertThat(
                mUserActionTester.getActions(),
                hasItems(
                        "Settings.PrivacySandbox.Fledge.BlockedSitesOpened",
                        "Settings.PrivacySandbox.Fledge.SiteRemoved"));
    }

    @Test
    @SmallTest
    public void testUnblockSites() {
        setFledgePrefEnabled(true);
        mFakePrivacySandboxBridge.setBlockedFledgeSites(SITE_NAME_1, SITE_NAME_2);
        startFledgeSettings();

        // Open the blocked sites sub-page.
        onView(withText(R.string.settings_fledge_page_blocked_sites_heading)).perform(click());
        onViewWaiting(withText(R.string.settings_fledge_page_blocked_sites_sub_page_title));

        // Unblock the first site.
        clickImageButtonNextToText(SITE_NAME_1);
        onView(withText(SITE_NAME_1)).check(doesNotExist());
        onView(withText(R.string.settings_fledge_page_add_site_snackbar))
                .check(matches(isDisplayed()));

        // Unblock the second site.
        clickImageButtonNextToText(SITE_NAME_2);
        onView(withText(SITE_NAME_2)).check(doesNotExist());
        onView(withText(R.string.settings_fledge_page_add_site_snackbar))
                .check(matches(isDisplayed()));

        // Check that the empty state UI is displayed when the site list is empty.
        onView(withText(R.string.settings_fledge_page_blocked_sites_description_empty))
                .check(matches(isDisplayed()));

        // Go back to the main Fledge fragment.
        pressBack();
        onViewWaiting(withText(R.string.settings_fledge_page_toggle_sub_label));

        // Verify that the sites are unblocked.
        onView(withText(SITE_NAME_1)).check(matches(isDisplayed()));
        onView(withText(SITE_NAME_2)).check(matches(isDisplayed()));

        // Verify that actions are reported
        assertThat(
                mUserActionTester.getActions(),
                hasItems(
                        "Settings.PrivacySandbox.Fledge.BlockedSitesOpened",
                        "Settings.PrivacySandbox.Fledge.SiteAdded"));
    }

    @Test
    @SmallTest
    public void testBlockedSitesAppearWhenFledgeOff() {
        setFledgePrefEnabled(false);
        mFakePrivacySandboxBridge.setBlockedFledgeSites(SITE_NAME_1, SITE_NAME_2);
        startFledgeSettings();
        onView(withText(R.string.settings_fledge_page_blocked_sites_heading)).perform(click());

        onViewWaiting(withText(R.string.settings_fledge_page_blocked_sites_sub_page_title));
        onView(withText(SITE_NAME_1)).check(matches(isDisplayed()));
        onView(withText(SITE_NAME_2)).check(matches(isDisplayed()));

        assertThat(
                mUserActionTester.getActions(),
                hasItems("Settings.PrivacySandbox.Fledge.BlockedSitesOpened"));
    }

    @Test
    @SmallTest
    public void testBlockedSitesAppearWhenFledgeOn() {
        setFledgePrefEnabled(true);
        mFakePrivacySandboxBridge.setBlockedFledgeSites(SITE_NAME_1, SITE_NAME_2);
        startFledgeSettings();
        onView(withText(R.string.settings_fledge_page_blocked_sites_heading)).perform(click());

        onViewWaiting(withText(R.string.settings_fledge_page_blocked_sites_sub_page_title));
        onView(withText(SITE_NAME_1)).check(matches(isDisplayed()));
        onView(withText(SITE_NAME_2)).check(matches(isDisplayed()));

        assertThat(
                mUserActionTester.getActions(),
                hasItems("Settings.PrivacySandbox.Fledge.BlockedSitesOpened"));
    }

    @Test
    @SmallTest
    @Policies.Add({
        @Policies.Item(key = "PrivacySandboxSiteEnabledAdsEnabled", string = "false"),
        @Policies.Item(key = "PrivacySandboxPromptEnabled", string = "false")
    })
    public void testFledgeManaged() {
        startFledgeSettings();

        // Check default state and try to press the toggle.
        assertFalse(isFledgePrefEnabled());
        onView(getFledgeToggleMatcher()).check(matches(not(isChecked())));
        onView(getFledgeToggleMatcher()).perform(click());

        // Check that the state of the pref and the toggle did not change.
        assertFalse(isFledgePrefEnabled());
        onView(getFledgeToggleMatcher()).check(matches(not(isChecked())));
    }

    @Test
    @SmallTest
    public void testLearnMoreLink() {
        startFledgeSettings();
        // Open the Fledge learn more activity
        onView(withText(containsString("30 days. Learn more"))).perform(clickOnClickableSpan(0));
        onViewWaiting(withText(R.string.settings_fledge_page_learn_more_heading))
                .check(matches(isDisplayed()));
        // Close the additional activity
        pressBack();
        assertThat(
                mUserActionTester.getActions(),
                hasItems("Settings.PrivacySandbox.Fledge.LearnMoreClicked"));
    }

    @Test
    @SmallTest
    @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_PROACTIVE_TOPICS_BLOCKING)
    public void testFooterTopicsLink() throws IOException {
        setFledgePrefEnabled(true);
        startFledgeSettings();
        // Open a Topics settings activity.
        onView(withText(containsString("Ad topics"))).perform(clickOnClickableSpan(0));
        onViewWaiting(withText(R.string.settings_topics_page_toggle_sub_label))
                .check(matches(isDisplayed()));
        // Close the additional activity by navigating back.
        pressBack();
    }

    @Test
    @SmallTest
    @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_PROACTIVE_TOPICS_BLOCKING)
    public void testFooterTopicsLinkV2() throws IOException {
        setFledgePrefEnabled(true);
        startFledgeSettings();
        // Open a Topics settings activity.
        onView(withText(containsString("ad topics"))).perform(clickOnClickableSpan(0));
        onViewWaiting(withText(R.string.settings_topics_page_toggle_sub_label_v2))
                .check(matches(isDisplayed()));
        // Close the additional activity by navigating back.
        pressBack();
    }

    @Test
    @SmallTest
    public void testFooterCookieSettingsLink() throws IOException {
        setFledgePrefEnabled(true);
        startFledgeSettings();
        // Open a CookieSettings activity.
        onView(withText(containsString("cookie settings"))).perform(clickOnClickableSpan(1));
        onViewWaiting(withText(R.string.third_party_cookies_page_title))
                .check(matches(isDisplayed()));
        // Close the additional activity by navigating back.
        pressBack();
    }
}