chromium/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerActivityTest.java

// Copyright 2021 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.toolbar.adaptive;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.Pair;

import androidx.test.core.app.ActivityScenario;
import androidx.test.filters.MediumTest;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;

import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.chrome.browser.ChromeRobolectricTestRunner;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.MockTab;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.toolbar.ButtonDataProvider;
import org.chromium.chrome.browser.toolbar.top.OptionalBrowsingModeButtonController;
import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator;
import org.chromium.chrome.test.util.browser.tabmodel.MockTabCreatorManager;
import org.chromium.chrome.test.util.browser.tabmodel.MockTabModelSelector;
import org.chromium.content_public.browser.WebContents;
import org.chromium.url.JUnitTestGURLs;

import java.util.List;
import java.util.NoSuchElementException;

/**
 * Robolectric tests running {@link OptionalNewTabButtonController} in a {@link
 * ChromeTabbedActivity}.
 */
@Config(shadows = {OptionalNewTabButtonControllerActivityTest.ShadowDelegate.class})
@RunWith(ChromeRobolectricTestRunner.class)
@EnableFeatures({ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2})
@CommandLineFlags.Add({
    ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
    ChromeSwitches.DISABLE_NATIVE_INITIALIZATION,
    "enable-features="
            + ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2
            + "<FakeStudyName",
    "force-fieldtrials=FakeStudyName/Enabled",
    "force-fieldtrial-params=FakeStudyName.Enabled:min_version_adaptive/0"
})
public class OptionalNewTabButtonControllerActivityTest {

    /**
     * Shadow of {@link OptionalNewTabButtonController.Delegate}. Injects testing values into every
     * instance of {@link OptionalNewTabButtonController}.
     */
    @Implements(OptionalNewTabButtonController.Delegate.class)
    public static class ShadowDelegate {
        private static MockTabCreatorManager sTabCreatorManager;
        private static MockTabModelSelector sTabModelSelector;

        protected static void reset() {
            sTabModelSelector = null;
            sTabCreatorManager = null;
        }

        @Implementation
        protected TabCreatorManager getTabCreatorManager() {
            return sTabCreatorManager;
        }

        @Implementation
        protected TabModelSelector getTabModelSelector() {
            return sTabModelSelector;
        }
    }

    private ActivityScenario<ChromeTabbedActivity> mActivityScenario;
    private AdaptiveToolbarButtonController mAdaptiveButtonController;
    private MockTab mTab;

    @Before
    public void setUp() {
        Profile originalProfile = Mockito.mock(Profile.class);
        Profile incognitoProfile = Mockito.mock(Profile.class);
        when(originalProfile.getOriginalProfile()).thenReturn(originalProfile);
        when(incognitoProfile.isOffTheRecord()).thenReturn(true);

        PriceTrackingFeatures.setPriceTrackingEnabledForTesting(false);

        // Avoid leaking state from the previous test.
        AdaptiveToolbarStatePredictor.setToolbarStateForTesting(
                AdaptiveToolbarButtonVariant.NEW_TAB);
        // To bypass a direct call to AdaptiveToolbarStatePredictor#readFromSegmentationPlatform for
        // UMA.
        AdaptiveToolbarStatePredictor.setSegmentationResultsForTesting(
                new Pair<>(true, List.of(AdaptiveToolbarButtonVariant.NEW_TAB)));
        MockTabModelSelector tabModelSelector =
                new MockTabModelSelector(
                        originalProfile,
                        incognitoProfile,
                        /* tabCount= */ 1,
                        /* incognitoTabCount= */ 0,
                        (id, incognito) -> {
                            Profile profile = incognito ? incognitoProfile : originalProfile;
                            MockTab tab = spy(MockTab.createAndInitialize(id, profile));
                            doReturn(Mockito.mock(WebContents.class)).when(tab).getWebContents();
                            return tab;
                        });
        assertNull(ShadowDelegate.sTabModelSelector);
        assertNull(ShadowDelegate.sTabCreatorManager);
        ShadowDelegate.sTabModelSelector = tabModelSelector;
        ShadowDelegate.sTabCreatorManager = new MockTabCreatorManager(tabModelSelector);
        mTab = tabModelSelector.getCurrentTab();
        mTab.setGurlOverrideForTesting(JUnitTestGURLs.EXAMPLE_URL);

        mActivityScenario = ActivityScenario.launch(ChromeTabbedActivity.class);
        mActivityScenario.onActivity(
                activity -> {
                    mAdaptiveButtonController =
                            getAdaptiveButton(getOptionalButtonController(activity));
                    mAdaptiveButtonController.setProfile(originalProfile);
                });
    }

    @After
    public void tearDown() {
        mActivityScenario.close();
        ShadowDelegate.reset();
    }

    @Test
    @MediumTest
    @Config(qualifiers = "w390dp-h820dp-land")
    public void testAlwaysShownOnPhone() {
        mActivityScenario.onActivity(
                activity -> {
                    assertTrue(mAdaptiveButtonController.get(mTab).canShow());

                    applyQualifiers(activity, "+port");

                    assertTrue(mAdaptiveButtonController.get(mTab).canShow());
                });
    }

    @Test
    @MediumTest
    @Config(qualifiers = "w600dp-h820dp")
    public void testNeverShownOnTablet() {
        mActivityScenario.onActivity(
                activity -> {
                    assertFalse(mAdaptiveButtonController.get(mTab).canShow());

                    // Rotating a tablet should not change canShow.
                    applyQualifiers(activity, "+land");

                    assertFalse(mAdaptiveButtonController.get(mTab).canShow());
                });
    }

    @Test
    @MediumTest
    @Config(qualifiers = "w400dp-h600dp")
    public void testNightMode() {
        mActivityScenario.onActivity(
                activity -> {
                    assertTrue(mAdaptiveButtonController.get(mTab).canShow());

                    // Unrelated qualifiers should not change canShow. This covers an early return
                    // from onConfigurationChanged.
                    applyQualifiers(activity, "+night");

                    assertTrue(mAdaptiveButtonController.get(mTab).canShow());
                });
    }

    @Test
    @MediumTest
    @Config(qualifiers = "w400dp-h600dp")
    public void testNtp() {
        mActivityScenario.onActivity(
                activity -> {
                    assertTrue(mAdaptiveButtonController.get(mTab).canShow());

                    mTab.setGurlOverrideForTesting(JUnitTestGURLs.NTP_URL);
                    assertFalse(mAdaptiveButtonController.get(mTab).canShow());

                    mTab.setGurlOverrideForTesting(JUnitTestGURLs.EXAMPLE_URL);
                    assertTrue(mAdaptiveButtonController.get(mTab).canShow());
                });
    }

    private static OptionalBrowsingModeButtonController getOptionalButtonController(
            ChromeTabbedActivity activity) {
        TopToolbarCoordinator toolbar =
                (TopToolbarCoordinator) activity.getToolbarManager().getToolbar();
        return toolbar.getOptionalButtonControllerForTesting();
    }

    private static AdaptiveToolbarButtonController getAdaptiveButton(
            OptionalBrowsingModeButtonController optionalButtonController) {
        List<ButtonDataProvider> buttonDataProviders =
                optionalButtonController.getButtonDataProvidersForTesting();
        for (ButtonDataProvider buttonDataProvider : buttonDataProviders) {
            if (!(buttonDataProvider instanceof AdaptiveToolbarButtonController)) {
                continue;
            }
            return (AdaptiveToolbarButtonController) buttonDataProvider;
        }
        throw new NoSuchElementException();
    }

    /** Sets device qualifiers and notifies the activity about configuration change. */
    private static void applyQualifiers(ChromeTabbedActivity activity, String qualifiers) {
        RuntimeEnvironment.setQualifiers(qualifiers);
        Configuration configuration = Resources.getSystem().getConfiguration();
        activity.onConfigurationChanged(configuration);
    }
}