chromium/chrome/browser/ui/android/google_bottom_bar/java/src/org/chromium/chrome/browser/ui/google_bottom_bar/GoogleBottomBarCoordinatorTest.java

// Copyright 2024 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.ui.google_bottom_bar;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

import static org.chromium.chrome.browser.preferences.ChromePreferenceKeys.IS_CHROME_DEFAULT_SEARCH_ENGINE_GOOGLE;
import static org.chromium.chrome.browser.ui.google_bottom_bar.BottomBarConfig.ButtonId.CUSTOM;
import static org.chromium.chrome.browser.ui.google_bottom_bar.BottomBarConfig.ButtonId.HOME;
import static org.chromium.chrome.browser.ui.google_bottom_bar.BottomBarConfig.ButtonId.PIH_BASIC;
import static org.chromium.chrome.browser.ui.google_bottom_bar.BottomBarConfig.ButtonId.SAVE;
import static org.chromium.chrome.browser.ui.google_bottom_bar.BottomBarConfig.ButtonId.SEARCH;
import static org.chromium.chrome.browser.ui.google_bottom_bar.BottomBarConfig.ButtonId.SHARE;
import static org.chromium.chrome.browser.ui.google_bottom_bar.BottomBarConfigCreator.CUSTOM_BUTTON_PARAM_ID_TO_BUTTON_ID_MAP;
import static org.chromium.chrome.browser.ui.google_bottom_bar.BottomBarConfigCreator.IS_GOOGLE_DEFAULT_SEARCH_ENGINE_CHECK_ENABLED;
import static org.chromium.chrome.browser.ui.google_bottom_bar.GoogleBottomBarLogger.BOTTOM_BAR_CREATED_HISTOGRAM;
import static org.chromium.chrome.browser.ui.google_bottom_bar.GoogleBottomBarLogger.BUTTON_SHOWN_HISTOGRAM;

import android.app.Activity;
import android.app.PendingIntent;
import android.graphics.drawable.Drawable;

import androidx.test.ext.junit.rules.ActivityScenarioRule;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;

import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.chrome.browser.browserservices.intents.CustomButtonParams;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.chrome.browser.share.ShareDelegate;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.ui.google_bottom_bar.BottomBarConfig.ButtonId;
import org.chromium.chrome.browser.ui.google_bottom_bar.GoogleBottomBarLogger.GoogleBottomBarButtonEvent;
import org.chromium.chrome.browser.ui.google_bottom_bar.GoogleBottomBarLogger.GoogleBottomBarCreatedEvent;
import org.chromium.chrome.browser.ui.google_bottom_bar.proto.IntentParams.GoogleBottomBarIntentParams;
import org.chromium.chrome.browser.ui.google_bottom_bar.proto.IntentParams.GoogleBottomBarIntentParams.VariantLayoutType;
import org.chromium.components.search_engines.TemplateUrlService;
import org.chromium.ui.base.TestActivity;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/** Unit tests for {@link GoogleBottomBarCoordinator}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class GoogleBottomBarCoordinatorTest {

    private static final Map<Integer, Integer> BUTTON_ID_TO_CUSTOM_BUTTON_ID_MAP =
            Map.of(SAVE, 100, SHARE, 101, PIH_BASIC, 103, CUSTOM, 105, SEARCH, 106, HOME, 107);

    @Rule
    public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
            new ActivityScenarioRule<>(TestActivity.class);

    @Mock private Tab mTab;
    @Mock private Supplier<Tab> mTabSupplier;

    @Mock private ShareDelegate mShareDelegate;
    @Mock private Supplier<ShareDelegate> mShareDelegateSupplier;

    @Mock private TemplateUrlService mTemplateUrlService;
    @Mock private Profile mProfile;

    private Activity mActivity;
    private GoogleBottomBarCoordinator mGoogleBottomBarCoordinator;

    @Before
    public void setup() {
        mActivityScenarioRule.getScenario().onActivity(activity -> mActivity = activity);
        MockitoAnnotations.initMocks(this);

        GoogleBottomBarIntentParams googleBottomBarIntentParams =
                GoogleBottomBarIntentParams.newBuilder().build();
        List<CustomButtonParams> customButtonParamsList = new ArrayList<>();
        mGoogleBottomBarCoordinator =
                new GoogleBottomBarCoordinator(
                        mActivity,
                        mTabSupplier,
                        mShareDelegateSupplier,
                        googleBottomBarIntentParams,
                        customButtonParamsList);

        when(mTabSupplier.get()).thenReturn(mTab);
        when(mShareDelegateSupplier.get()).thenReturn(mShareDelegate);
        TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService);
    }

    @Test
    public void
            testCreateGoogleBottomBarView_evenLayout_logsGoogleBottomBarCreatedWithEvenLayout() {
        HistogramWatcher histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        BOTTOM_BAR_CREATED_HISTOGRAM, GoogleBottomBarCreatedEvent.EVEN_LAYOUT);

        mGoogleBottomBarCoordinator =
                createGoogleBottomBarCoordinator(
                        List.of(0, PIH_BASIC, SHARE, SAVE), new ArrayList<>());
        mGoogleBottomBarCoordinator.createGoogleBottomBarView();

        histogramWatcher.assertExpected();
        histogramWatcher.close();
    }

    @Test
    public void
            testCreateGoogleBottomBarView_spotlightLayout_logsGoogleBottomBarCreatedWithSpotlightLayout() {
        HistogramWatcher histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        BOTTOM_BAR_CREATED_HISTOGRAM, GoogleBottomBarCreatedEvent.SPOTLIGHT_LAYOUT);

        mGoogleBottomBarCoordinator =
                createGoogleBottomBarCoordinator(
                        List.of(PIH_BASIC, PIH_BASIC, SHARE, SAVE), new ArrayList<>());
        mGoogleBottomBarCoordinator.createGoogleBottomBarView();

        histogramWatcher.assertExpected();
        histogramWatcher.close();
    }

    @Test
    public void testLogButtons_beforeOnNativeInitialization_logsUnknownButton() {
        HistogramWatcher histogramWatcher =
                HistogramWatcher.newBuilder()
                        .expectIntRecords(
                                BUTTON_SHOWN_HISTOGRAM,
                                GoogleBottomBarButtonEvent.UNKNOWN,
                                GoogleBottomBarButtonEvent.SHARE_CHROME,
                                GoogleBottomBarButtonEvent.SAVE_DISABLED)
                        .build();
        mGoogleBottomBarCoordinator =
                createGoogleBottomBarCoordinator(
                        List.of(PIH_BASIC, PIH_BASIC, SHARE, SAVE), new ArrayList<>());
        mGoogleBottomBarCoordinator.createGoogleBottomBarView();

        mGoogleBottomBarCoordinator.getGoogleBottomBarViewCreatorForTesting().logButtons();

        histogramWatcher.assertExpected();
        histogramWatcher.close();
    }

    @Test
    public void
            testOnFinishNativeInitialization_pageInsightsSupplierIsNotPresent_logsPageInsightsEmbedder() {
        HistogramWatcher histogramWatcher =
                HistogramWatcher.newBuilder()
                        .expectIntRecords(
                                BUTTON_SHOWN_HISTOGRAM,
                                GoogleBottomBarButtonEvent.PIH_EMBEDDER,
                                GoogleBottomBarButtonEvent.SHARE_CHROME,
                                GoogleBottomBarButtonEvent.SAVE_DISABLED)
                        .build();
        mGoogleBottomBarCoordinator =
                createGoogleBottomBarCoordinator(
                        List.of(PIH_BASIC, PIH_BASIC, SHARE, SAVE),
                        List.of(getMockCustomButtonParams(PIH_BASIC)));
        mGoogleBottomBarCoordinator.createGoogleBottomBarView();

        mGoogleBottomBarCoordinator.onFinishNativeInitialization();

        histogramWatcher.assertExpected();
        histogramWatcher.close();
    }

    @Test
    @EnableFeatures(ChromeFeatureList.CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS)
    public void
            initDefaultSearchEngine_variantLayoutsEnabled_defaultSearchEngineCheckEnabled_doesNotCheckDefaultSearchEngine() {
        IS_GOOGLE_DEFAULT_SEARCH_ENGINE_CHECK_ENABLED.setForTesting(false);
        ChromeSharedPreferences.getInstance()
                .writeBoolean(IS_CHROME_DEFAULT_SEARCH_ENGINE_GOOGLE, false);
        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(true);

        mGoogleBottomBarCoordinator.initDefaultSearchEngine(mProfile);

        verifyNoInteractions(mTemplateUrlService);
        // Verify that true value is not written to Shared preferences
        // Use true as default value as we expect to get false
        assertFalse(
                ChromeSharedPreferences.getInstance()
                        .readBoolean(
                                IS_CHROME_DEFAULT_SEARCH_ENGINE_GOOGLE, /* defaultValue= */ true));
    }

    @Test
    @EnableFeatures(ChromeFeatureList.CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS)
    public void
            initDefaultSearchEngine_variantLayoutsEnabled_defaultSearchEngineCheckEnabledAndNotEqualToGoogle_writesFalse() {
        IS_GOOGLE_DEFAULT_SEARCH_ENGINE_CHECK_ENABLED.setForTesting(true);
        ChromeSharedPreferences.getInstance()
                .writeBoolean(IS_CHROME_DEFAULT_SEARCH_ENGINE_GOOGLE, true);
        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(false);

        mGoogleBottomBarCoordinator.initDefaultSearchEngine(mProfile);

        // Use true as default value as we expect to get false
        assertFalse(
                ChromeSharedPreferences.getInstance()
                        .readBoolean(
                                IS_CHROME_DEFAULT_SEARCH_ENGINE_GOOGLE, /* defaultValue= */ true));
    }

    @Test
    @EnableFeatures(ChromeFeatureList.CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS)
    public void
            initDefaultSearchEngine_variantLayoutsEnabled_defaultSearchEngineCheckEnabledAndEqualsToGoogle_writesTrue() {
        IS_GOOGLE_DEFAULT_SEARCH_ENGINE_CHECK_ENABLED.setForTesting(true);
        ChromeSharedPreferences.getInstance()
                .writeBoolean(IS_CHROME_DEFAULT_SEARCH_ENGINE_GOOGLE, false);
        when(mTemplateUrlService.isDefaultSearchEngineGoogle()).thenReturn(true);

        mGoogleBottomBarCoordinator.initDefaultSearchEngine(mProfile);

        // Use false as default value as we expect to get true
        assertTrue(
                ChromeSharedPreferences.getInstance()
                        .readBoolean(
                                IS_CHROME_DEFAULT_SEARCH_ENGINE_GOOGLE, /* defaultValue= */ false));
    }

    @Test
    @DisableFeatures(ChromeFeatureList.CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS)
    public void
            getSupportedCustomButtonParamIds_variantLayoutsDisabled_returnsSetWithAllSupportedCustomParams() {
        GoogleBottomBarIntentParams intentParams =
                GoogleBottomBarIntentParams.newBuilder()
                        .addAllEncodedButton(List.of(0, SAVE, SHARE, CUSTOM))
                        .setVariantLayoutType(VariantLayoutType.NO_VARIANT)
                        .build();

        Set<Integer> supportedGoogleBottomBarButtons =
                GoogleBottomBarCoordinator.getSupportedCustomButtonParamIds(intentParams);

        assertEquals(
                CUSTOM_BUTTON_PARAM_ID_TO_BUTTON_ID_MAP.keySet(), supportedGoogleBottomBarButtons);
    }

    @Test
    @EnableFeatures(ChromeFeatureList.CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS)
    public void
            getSupportedCustomButtonParamIds_variantLayoutsEnabled_noVariantLayout_returnsSetWithAllSupportedCustomParams() {
        GoogleBottomBarIntentParams intentParams =
                GoogleBottomBarIntentParams.newBuilder()
                        .addAllEncodedButton(List.of(0, SAVE, SHARE, CUSTOM))
                        .setVariantLayoutType(VariantLayoutType.NO_VARIANT)
                        .build();

        Set<Integer> supportedGoogleBottomBarButtons =
                GoogleBottomBarCoordinator.getSupportedCustomButtonParamIds(intentParams);

        assertEquals(
                CUSTOM_BUTTON_PARAM_ID_TO_BUTTON_ID_MAP.keySet(), supportedGoogleBottomBarButtons);
    }

    @Test
    @EnableFeatures(ChromeFeatureList.CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS)
    public void
            getSupportedCustomButtonParamIds_variantLayoutsEnabled_doubleDecker_returnsSetWithAllSupportedCustomParams() {
        GoogleBottomBarIntentParams intentParams =
                GoogleBottomBarIntentParams.newBuilder()
                        .addAllEncodedButton(List.of(0, SAVE, SHARE, CUSTOM))
                        .setVariantLayoutType(VariantLayoutType.DOUBLE_DECKER)
                        .build();

        Set<Integer> supportedGoogleBottomBarButtons =
                GoogleBottomBarCoordinator.getSupportedCustomButtonParamIds(intentParams);

        assertEquals(
                CUSTOM_BUTTON_PARAM_ID_TO_BUTTON_ID_MAP.keySet(), supportedGoogleBottomBarButtons);
    }

    @EnableFeatures(ChromeFeatureList.CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS)
    public void
            getSupportedCustomButtonParamIds_variantLayoutsEnabled_singleDecker_returnsEmptySet() {
        GoogleBottomBarIntentParams intentParams =
                GoogleBottomBarIntentParams.newBuilder()
                        .addAllEncodedButton(List.of(0, SAVE, SHARE, CUSTOM))
                        .setVariantLayoutType(VariantLayoutType.SINGLE_DECKER)
                        .build();

        Set<Integer> supportedGoogleBottomBarButtons =
                GoogleBottomBarCoordinator.getSupportedCustomButtonParamIds(intentParams);

        assertEquals(Set.of(), supportedGoogleBottomBarButtons);
    }

    @EnableFeatures(ChromeFeatureList.CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS)
    public void
            getSupportedCustomButtonParamIds_variantLayoutsEnabledButNotSupported_singleDecker_returnsSetWithAllSupportedCustomParams() {
        IS_GOOGLE_DEFAULT_SEARCH_ENGINE_CHECK_ENABLED.setForTesting(true);
        ChromeSharedPreferences.getInstance()
                .writeBoolean(IS_CHROME_DEFAULT_SEARCH_ENGINE_GOOGLE, false);
        GoogleBottomBarIntentParams intentParams =
                GoogleBottomBarIntentParams.newBuilder()
                        .addAllEncodedButton(List.of(0, SAVE, SHARE, CUSTOM))
                        .setVariantLayoutType(VariantLayoutType.SINGLE_DECKER)
                        .build();

        Set<Integer> supportedGoogleBottomBarButtons =
                GoogleBottomBarCoordinator.getSupportedCustomButtonParamIds(intentParams);

        assertEquals(
                CUSTOM_BUTTON_PARAM_ID_TO_BUTTON_ID_MAP.keySet(), supportedGoogleBottomBarButtons);
    }

    @Test
    @EnableFeatures(ChromeFeatureList.CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS)
    public void
            getSupportedCustomButtonParamIds_variantLayoutsEnabled_singleDeckerWithButtonsOnRight_returnsSetWithEncodedButtons() {
        GoogleBottomBarIntentParams intentParams =
                GoogleBottomBarIntentParams.newBuilder()
                        .addAllEncodedButton(List.of(0, SAVE, CUSTOM))
                        .setVariantLayoutType(VariantLayoutType.SINGLE_DECKER_WITH_RIGHT_BUTTONS)
                        .build();

        Set<Integer> supportedGoogleBottomBarButtons =
                GoogleBottomBarCoordinator.getSupportedCustomButtonParamIds(intentParams);

        assertEquals(Set.of(100, 105), supportedGoogleBottomBarButtons);
    }

    @Test
    @EnableFeatures(ChromeFeatureList.CCT_GOOGLE_BOTTOM_BAR_VARIANT_LAYOUTS)
    public void
            getSupportedCustomButtonParamIds_variantLayoutsEnabledButNotSupported_singleDeckerWithButtonsOnRight_returnsSetWithAllSupportedCustomParams() {
        IS_GOOGLE_DEFAULT_SEARCH_ENGINE_CHECK_ENABLED.setForTesting(true);
        ChromeSharedPreferences.getInstance()
                .writeBoolean(IS_CHROME_DEFAULT_SEARCH_ENGINE_GOOGLE, false);
        GoogleBottomBarIntentParams intentParams =
                GoogleBottomBarIntentParams.newBuilder()
                        .addAllEncodedButton(List.of(0, SAVE, CUSTOM))
                        .setVariantLayoutType(VariantLayoutType.SINGLE_DECKER_WITH_RIGHT_BUTTONS)
                        .build();

        Set<Integer> supportedGoogleBottomBarButtons =
                GoogleBottomBarCoordinator.getSupportedCustomButtonParamIds(intentParams);

        assertEquals(
                CUSTOM_BUTTON_PARAM_ID_TO_BUTTON_ID_MAP.keySet(), supportedGoogleBottomBarButtons);
    }

    private GoogleBottomBarCoordinator createGoogleBottomBarCoordinator(
            List<Integer> encodedLayoutList, List<CustomButtonParams> customButtonParamsList) {
        GoogleBottomBarIntentParams googleBottomBarIntentParams =
                GoogleBottomBarIntentParams.newBuilder()
                        .addAllEncodedButton(encodedLayoutList)
                        .build();

        return new GoogleBottomBarCoordinator(
                mActivity,
                mTabSupplier,
                mShareDelegateSupplier,
                googleBottomBarIntentParams,
                customButtonParamsList);
    }

    private CustomButtonParams getMockCustomButtonParams(@ButtonId int buttonId) {
        CustomButtonParams customButtonParams = mock(CustomButtonParams.class);
        Drawable drawable = mock(Drawable.class);
        when(drawable.mutate()).thenReturn(drawable);

        when(customButtonParams.getId())
                .thenReturn(BUTTON_ID_TO_CUSTOM_BUTTON_ID_MAP.get(buttonId));
        when(customButtonParams.getIcon(mActivity)).thenReturn(drawable);
        when(customButtonParams.getDescription()).thenReturn("Description");
        when(customButtonParams.getPendingIntent()).thenReturn(mock(PendingIntent.class));

        return customButtonParams;
    }
}