chromium/chrome/browser/search_resumption/junit/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediatorUnitTest.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.search_resumption;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.view.View;
import android.view.ViewStub;

import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;

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

import org.chromium.base.FeatureList;
import org.chromium.base.UserDataHost;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.test.BaseRobolectricTestRunner;
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.omnibox.suggestions.AutocompleteController;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController.OnSuggestionsReceivedListener;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteControllerJni;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.chrome.browser.search_resumption.SearchResumptionModuleUtils.ModuleNotShownReason;
import org.chromium.chrome.browser.search_resumption.SearchResumptionModuleUtils.ModuleShowStatus;
import org.chromium.chrome.browser.search_resumption.SearchResumptionUserData.SuggestionResult;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.services.SigninManager;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.omnibox.AutocompleteMatch;
import org.chromium.components.omnibox.AutocompleteResult;
import org.chromium.components.omnibox.OmniboxSuggestionType;
import org.chromium.components.search_engines.TemplateUrlService;
import org.chromium.components.sync.SyncService;
import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;

import java.util.Arrays;
import java.util.List;

/** Unit tests for {@link SearchResumptionModuleMediator}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@SuppressWarnings("DoNotMock") // Mocking GURL
public class SearchResumptionModuleMediatorUnitTest {
    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private Tab mTabToTrack;
    @Mock private Tab mTab;
    @Mock private ViewStub mParent;
    @Mock private SearchResumptionModuleView mModuleLayoutView;
    @Mock private SearchResumptionTileContainerView mSuggestionTilesContainerView;
    @Mock private AutocompleteController mAutocompleteController;
    @Mock private AutocompleteController.Natives mControllerJniMock;
    @Mock SearchResumptionTileBuilder mTileBuilder;
    @Mock private Profile mProfile;
    @Mock private TemplateUrlService mTemplateUrlService;
    @Captor private ArgumentCaptor<OnSuggestionsReceivedListener> mListener;

    @Mock private AutocompleteMatch mSearchSuggest1;
    @Mock private AutocompleteMatch mSearchSuggest2;
    @Mock private AutocompleteMatch mNonSearchSuggest1;
    @Mock private AutocompleteResult mAutocompleteResult;
    @Mock private IdentityServicesProvider mIdentityServicesProvider;
    @Mock private SigninManager mSignInManager;
    @Mock private SyncService mSyncServiceMock;

    private GURL mUrlToTrack;
    private String[] mSuggestionTexts;
    private GURL[] mSuggestionUrls;
    private UserActionTester mActionTester;
    private UserDataHost mUserDataHost;
    private SearchResumptionModuleMediator mMediator;
    private FeatureList.TestValues mFeatureListValues;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mFeatureListValues = new FeatureList.TestValues();
        FeatureList.setTestValues(mFeatureListValues);
        mFeatureListValues.addFeatureFlagOverride(
                ChromeFeatureList.SEARCH_RESUMPTION_MODULE_ANDROID, false);

        mUserDataHost = new UserDataHost();
        mJniMocker.mock(AutocompleteControllerJni.TEST_HOOKS, mControllerJniMock);
        doReturn(mAutocompleteController).when(mControllerJniMock).getForProfile(any());
        mUrlToTrack = JUnitTestGURLs.EXAMPLE_URL;
        doReturn(mUrlToTrack).when(mTabToTrack).getUrl();
        GURL currentUrl = JUnitTestGURLs.NTP_URL;
        doReturn(currentUrl).when(mTab).getUrl();
        doReturn(false).when(mTab).canGoForward();
        doReturn(mUserDataHost).when(mTab).getUserDataHost();
        doReturn(mModuleLayoutView).when(mParent).inflate();
        doReturn(mSuggestionTilesContainerView)
                .when(mModuleLayoutView)
                .findViewById(R.id.search_resumption_module_tiles_container);
        TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService);
        when(mTemplateUrlService.isSearchResultsPageFromDefaultSearchProvider(any()))
                .thenReturn(false);

        doReturn(OmniboxSuggestionType.SEARCH_SUGGEST).when(mSearchSuggest1).getType();
        doReturn("suggestion 1").when(mSearchSuggest1).getDisplayText();
        doReturn(OmniboxSuggestionType.SEARCH_SUGGEST).when(mSearchSuggest2).getType();
        doReturn("suggestion 2").when(mSearchSuggest2).getDisplayText();
        doReturn(OmniboxSuggestionType.TILE_NAVSUGGEST).when(mNonSearchSuggest1).getType();
        doReturn("non search suggestion 1").when(mNonSearchSuggest1).getDisplayText();

        IdentityServicesProvider.setInstanceForTests(mIdentityServicesProvider);
        doReturn(mSignInManager).when(mIdentityServicesProvider).getSigninManager(any());

        SyncServiceFactory.setInstanceForTesting(mSyncServiceMock);

        mActionTester = new UserActionTester();
    }

    @After
    public void tearDown() {
        mActionTester.tearDown();
    }

    @Test
    @MediumTest
    public void testDoNotBuildModuleWithoutEnoughSuggestions() {
        createMediator(null, /* useNewServiceEnabled= */ false);
        List<AutocompleteMatch> list = Arrays.asList(mNonSearchSuggest1, mNonSearchSuggest1);
        doReturn(list).when(mAutocompleteResult).getSuggestionsList();

        mMediator.onSuggestionsReceived(mAutocompleteResult, true);
        verify(mParent, times(0)).inflate();
        Assert.assertEquals(
                0,
                RecordHistogram.getHistogramValueCountForTesting(
                        SearchResumptionModuleUtils.UMA_MODULE_SHOW, ModuleShowStatus.EXPANDED));
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        SearchResumptionModuleUtils.UMA_MODULE_NOT_SHOW,
                        ModuleNotShownReason.NOT_ENOUGH_RESULT));
    }

    @Test
    @MediumTest
    public void testShowModuleWithEnoughResults() {
        createMediator(null, /* useNewServiceEnabled= */ false);
        List<AutocompleteMatch> list =
                Arrays.asList(mNonSearchSuggest1, mSearchSuggest1, mSearchSuggest2);
        doReturn(list).when(mAutocompleteResult).getSuggestionsList();

        mMediator.onSuggestionsReceived(mAutocompleteResult, true);
        verify(mParent, times(1)).inflate();
        Assert.assertEquals(View.VISIBLE, mSuggestionTilesContainerView.getVisibility());
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        SearchResumptionModuleUtils.UMA_MODULE_SHOW, ModuleShowStatus.EXPANDED));
    }

    @Test
    @SmallTest
    public void testShowModuleWithCachedResults() {
        List<AutocompleteMatch> list =
                Arrays.asList(mNonSearchSuggest1, mSearchSuggest1, mSearchSuggest2);
        SuggestionResult suggestionResult = new SuggestionResult(mUrlToTrack, list);

        createMediator(suggestionResult, /* useNewServiceEnabled= */ false);
        verify(mParent, times(1)).inflate();
        Assert.assertEquals(View.VISIBLE, mSuggestionTilesContainerView.getVisibility());
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        SearchResumptionModuleUtils.UMA_MODULE_SHOW_CACHED,
                        ModuleShowStatus.EXPANDED));
    }

    @Test
    @MediumTest
    public void testDoNotBuildModuleWithoutEnoughSuggestions_newServiceAPI() {
        String[] texts = {"suggestion 1"};
        GURL[] gUrls = {JUnitTestGURLs.URL_1};

        createMediator(null, /* useNewServiceEnabled= */ true);
        mMediator.onSuggestionsAvailable(texts, gUrls);
        verify(mParent, times(0)).inflate();
        Assert.assertEquals(
                0,
                RecordHistogram.getHistogramValueCountForTesting(
                        SearchResumptionModuleUtils.UMA_MODULE_SHOW, ModuleShowStatus.EXPANDED));
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        SearchResumptionModuleUtils.UMA_MODULE_NOT_SHOW,
                        ModuleNotShownReason.NOT_ENOUGH_RESULT));
    }

    @Test
    @MediumTest
    public void testShowModuleWithEnoughResults_newServiceAPI() {
        initSuggestions();

        createMediator(null, /* useNewServiceEnabled= */ true);
        mMediator.onSuggestionsAvailable(mSuggestionTexts, mSuggestionUrls);
        verify(mParent, times(1)).inflate();
        Assert.assertEquals(View.VISIBLE, mSuggestionTilesContainerView.getVisibility());
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        SearchResumptionModuleUtils.UMA_MODULE_SHOW, ModuleShowStatus.EXPANDED));

        mMediator.onExpandedOrCollapsed(/* expand= */ true);
        Assert.assertTrue(
                mActionTester.getActions().contains(SearchResumptionModuleUtils.ACTION_EXPAND));

        mMediator.onExpandedOrCollapsed(/* expand= */ false);
        Assert.assertTrue(
                mActionTester.getActions().contains(SearchResumptionModuleUtils.ACTION_COLLAPSE));
    }

    @Test
    @SmallTest
    public void testShowModuleWithCachedResults_newServiceAPI() {
        initSuggestions();
        SuggestionResult suggestionResult = createCachedSuggestions();

        createMediator(suggestionResult, /* useNewServiceEnabled= */ true);
        verify(mParent, times(1)).inflate();
        Assert.assertEquals(View.VISIBLE, mSuggestionTilesContainerView.getVisibility());
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        SearchResumptionModuleUtils.UMA_MODULE_SHOW_CACHED,
                        ModuleShowStatus.EXPANDED));
    }

    @Test
    @MediumTest
    public void testModuleVisibility() {
        testShowModuleWithEnoughResults();
        mMediator.onSignedOut();
        verify(mModuleLayoutView, times(1)).setVisibility(View.GONE);

        mMediator.onSignedIn();
        verify(mModuleLayoutView, times(1)).setVisibility(View.VISIBLE);

        doReturn(false).when(mTemplateUrlService).isDefaultSearchEngineGoogle();
        mMediator.onTemplateURLServiceChanged();
        verify(mModuleLayoutView, times(2)).setVisibility(View.GONE);

        doReturn(true).when(mTemplateUrlService).isDefaultSearchEngineGoogle();
        mMediator.onTemplateURLServiceChanged();
        verify(mModuleLayoutView, times(2)).setVisibility(View.VISIBLE);

        when(mSyncServiceMock.hasKeepEverythingSynced()).thenReturn(false);
        mMediator.syncStateChanged();
        verify(mModuleLayoutView, times(3)).setVisibility(View.GONE);

        when(mSyncServiceMock.hasKeepEverythingSynced()).thenReturn(true);
        mMediator.syncStateChanged();
        verify(mModuleLayoutView, times(3)).setVisibility(View.VISIBLE);
    }

    @Test
    @SmallTest
    public void testDestroy() {
        testShowModuleWithEnoughResults();
        mMediator.destroy();

        verify(mModuleLayoutView, times(1)).destroy();
    }

    private void createMediator(SuggestionResult cachedSuggestions, boolean useNewServiceEnabled) {
        mMediator =
                new SearchResumptionModuleMediator(
                        mParent,
                        mTabToTrack,
                        mTab,
                        mProfile,
                        mTileBuilder,
                        cachedSuggestions);
        if (!useNewServiceEnabled && cachedSuggestions == null) {
            verify(mAutocompleteController).addOnSuggestionsReceivedListener(mListener.capture());
            verify(mAutocompleteController, times(1))
                    .startZeroSuggest(any(), eq(mUrlToTrack), anyInt(), any());
        }

        mFeatureListValues.addFeatureFlagOverride(
                ChromeFeatureList.SEARCH_RESUMPTION_MODULE_ANDROID, true);
        mFeatureListValues.addFieldTrialParamOverride(
                ChromeFeatureList.SEARCH_RESUMPTION_MODULE_ANDROID,
                SearchResumptionModuleUtils.USE_NEW_SERVICE_PARAM,
                String.valueOf(useNewServiceEnabled));
    }

    private void initSuggestions() {
        mSuggestionTexts = new String[] {"suggestion 1", "suggestion2"};
        mSuggestionUrls = new GURL[] {JUnitTestGURLs.URL_1, JUnitTestGURLs.URL_2};
    }

    private SuggestionResult createCachedSuggestions() {
        initSuggestions();
        return new SuggestionResult(mUrlToTrack, mSuggestionTexts, mSuggestionUrls);
    }
}