chromium/chrome/android/junit/src/org/chromium/chrome/browser/search_engines/settings/SearchEngineAdapterTest.java

// Copyright 2019 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_engines.settings;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import static org.chromium.components.search_engines.TemplateUrlTestHelpers.buildMockTemplateUrl;

import android.content.Context;
import android.view.View;

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

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.R;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.components.favicon.LargeIconBridge;
import org.chromium.components.favicon.LargeIconBridgeJni;
import org.chromium.components.search_engines.TemplateUrl;
import org.chromium.components.search_engines.TemplateUrlService;
import org.chromium.ui.base.TestActivity;

import java.util.ArrayList;
import java.util.List;

/** Unit tests for {@link SearchEngineAdapter}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SearchEngineAdapterTest {
    public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule();
    public final @Rule JniMocker mJniMocker = new JniMocker();

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

    private @Mock Profile mProfile;
    private @Mock TemplateUrlService mTemplateUrlService;
    private @Mock LargeIconBridge.Natives mLargeIconBridgeNativeMock;
    private Context mContext;

    @Before
    public void setUp() {
        mJniMocker.mock(LargeIconBridgeJni.TEST_HOOKS, mLargeIconBridgeNativeMock);
        mActivityScenarioRule.getScenario().onActivity(activity -> mContext = activity);
    }

    @Test
    public void testSortAndFilterUnnecessaryTemplateUrl_PrepopulatedEnginesSorting() {
        String name = "prepopulated";
        long lastVisitedTime = System.currentTimeMillis();
        TemplateUrl p1 = buildMockTemplateUrl(name, 1, lastVisitedTime);
        TemplateUrl p2 = buildMockTemplateUrl(name, 2, lastVisitedTime);
        TemplateUrl p3 = buildMockTemplateUrl(name, 3, lastVisitedTime);
        TemplateUrl p4 = buildMockTemplateUrl(name, 4, lastVisitedTime);

        List<TemplateUrl> templateUrls = List.of(p2, p1, p4, p3);
        TemplateUrl[] expectedSortedUrls = new TemplateUrl[] {p1, p2, p3, p4};
        TemplateUrl[] expectedNonSortedUrls = new TemplateUrl[] {p2, p1, p4, p3};

        // When computing the list for the new settings in the EEA, don't re-sort prepopulated
        // engines.

        List<TemplateUrl> modifiedList = new ArrayList<>(templateUrls);
        SearchEngineAdapter.sortAndFilterUnnecessaryTemplateUrl(
                modifiedList,
                p3,
                /* isEeaChoiceCountry= */ true,
                /* shouldShowUpdatedSettings= */ true);
        assertThat(modifiedList, contains(expectedNonSortedUrls));

        // In all the other cases (old settings or out of EEA), keep sorting by ID.

        modifiedList = new ArrayList<>(templateUrls);
        SearchEngineAdapter.sortAndFilterUnnecessaryTemplateUrl(
                modifiedList,
                p3,
                /* isEeaChoiceCountry= */ false,
                /* shouldShowUpdatedSettings= */ true);
        assertThat(modifiedList, contains(expectedSortedUrls));

        modifiedList = new ArrayList<>(templateUrls);
        SearchEngineAdapter.sortAndFilterUnnecessaryTemplateUrl(
                modifiedList,
                p3,
                /* isEeaChoiceCountry= */ true,
                /* shouldShowUpdatedSettings= */ false);
        assertThat(modifiedList, contains(expectedSortedUrls));

        modifiedList = new ArrayList<>(templateUrls);
        SearchEngineAdapter.sortAndFilterUnnecessaryTemplateUrl(
                modifiedList,
                p3,
                /* isEeaChoiceCountry= */ false,
                /* shouldShowUpdatedSettings= */ false);
        assertThat(modifiedList, contains(expectedSortedUrls));
    }

    @Test
    public void testSortAndFilterUnnecessaryTemplateUrl_PrePopBeforeCustom() {
        long lastVisitedTime = System.currentTimeMillis();
        TemplateUrl p1 = buildMockTemplateUrl("prepopulated1", 1, lastVisitedTime);
        TemplateUrl p2 = buildMockTemplateUrl("prepopulated2", 2, lastVisitedTime);
        TemplateUrl c1 = buildMockTemplateUrl("custom1", 0, lastVisitedTime);

        List<TemplateUrl> templateUrls = new ArrayList<>(List.of(p1, c1, p2));
        checkSortAndFilterOutput(templateUrls, p1, List.of(p1, p2, c1));
    }

    @Test
    public void testSortAndFilterUnnecessaryTemplateUrl_CustomSortedByRecency() {
        long lastVisitedTime = System.currentTimeMillis();
        TemplateUrl c1 = buildMockTemplateUrl("custom1", 0, lastVisitedTime);
        TemplateUrl c2 = buildMockTemplateUrl("custom2", 0, lastVisitedTime - 1);
        TemplateUrl c3 = buildMockTemplateUrl("custom3", 0, lastVisitedTime - 2);

        List<TemplateUrl> templateUrls = new ArrayList<>(List.of(c3, c1, c2));
        checkSortAndFilterOutput(
                templateUrls, buildMockTemplateUrl("default", 0, 0), List.of(c1, c2, c3));
    }

    @Test
    public void testSortAndFilterUnnecessaryTemplateUrl_DefaultCustomSortedUp() {
        long lastVisitedTime = System.currentTimeMillis();
        TemplateUrl p1 = buildMockTemplateUrl("prepopulated", 1, lastVisitedTime - 5);
        TemplateUrl c1 = buildMockTemplateUrl("custom1", 0, lastVisitedTime);
        TemplateUrl c2 = buildMockTemplateUrl("custom2", 0, lastVisitedTime - 1);
        TemplateUrl c3 = buildMockTemplateUrl("custom3", 0, lastVisitedTime - 2);

        List<TemplateUrl> templateUrls = new ArrayList<>(List.of(c3, c1, c2, p1));
        checkSortAndFilterOutput(templateUrls, c2, List.of(p1, c2, c1, c3));
    }

    @Test
    public void testSortAndFilterUnnecessaryTemplateUrl_equalInstancesNotReordered() {
        String name = "prepopulated";
        long lastVisitedTime = System.currentTimeMillis();
        TemplateUrl p1 = buildMockTemplateUrl(name, 0, lastVisitedTime, 42);
        TemplateUrl p2 = buildMockTemplateUrl(name, 0, lastVisitedTime, 42);
        TemplateUrl p3 = buildMockTemplateUrl(name, 0, lastVisitedTime, 42);

        List<TemplateUrl> templateUrls = new ArrayList<>(List.of(p2, p1, p3));
        checkSortAndFilterOutput(templateUrls, p3, List.of(p2, p1, p3));

        // Instead of using the test helper, call the method directly and explicitly compare
        // identity for the output instead of equality here, as all instances are equal.
        SearchEngineAdapter.sortAndFilterUnnecessaryTemplateUrl(
                templateUrls,
                p3,
                /* isInEeaChoiceCountry= */ true,
                /* shouldShowUpdatedSettings= */ true);

        Assert.assertSame(templateUrls.get(0), p2);
        Assert.assertSame(templateUrls.get(1), p1);
        Assert.assertSame(templateUrls.get(2), p3);
    }

    @Test
    public void testSortAndFilterUnnecessaryTemplateUrl_LimitsCustomCount() {
        long lastVisitedTime = System.currentTimeMillis();
        TemplateUrl p1 = buildMockTemplateUrl("prepopulated", 1, lastVisitedTime);
        TemplateUrl c1 = buildMockTemplateUrl("custom1", 0, lastVisitedTime);
        TemplateUrl c2 = buildMockTemplateUrl("custom2", 0, lastVisitedTime);
        TemplateUrl c3 = buildMockTemplateUrl("custom3", 0, lastVisitedTime);
        TemplateUrl c4 = buildMockTemplateUrl("custom4", 0, lastVisitedTime);
        TemplateUrl c5 = buildMockTemplateUrl("custom5", 0, lastVisitedTime);

        List<TemplateUrl> templateUrls = new ArrayList<>(List.of(p1, c1, c2, c3, c4, c5));
        checkSortAndFilterOutput(templateUrls, p1, List.of(p1, c1, c2, c3));
    }

    @Test
    public void testSortAndFilterUnnecessaryTemplateUrl_LimitsCustomCountDseNotCounting() {
        long lastVisitedTime = System.currentTimeMillis();
        TemplateUrl p1 = buildMockTemplateUrl("prepopulated", 1, lastVisitedTime);
        TemplateUrl c1 = buildMockTemplateUrl("custom1", 0, lastVisitedTime);
        TemplateUrl c2 = buildMockTemplateUrl("custom2", 0, lastVisitedTime);
        TemplateUrl c3 = buildMockTemplateUrl("custom3", 0, lastVisitedTime);
        TemplateUrl c4 = buildMockTemplateUrl("custom4", 0, lastVisitedTime);
        TemplateUrl c5 = buildMockTemplateUrl("custom5", 0, lastVisitedTime);

        List<TemplateUrl> templateUrls = new ArrayList<>(List.of(p1, c1, c2, c3, c4, c5));
        checkSortAndFilterOutput(templateUrls, c1, List.of(p1, c1, c2, c3, c4));
    }

    @Test
    public void testSortAndFilterUnnecessaryTemplateUrl_RemovesOldCustom() {
        long recentTime = System.currentTimeMillis();
        long pastCutoffTime =
                System.currentTimeMillis() - SearchEngineAdapter.MAX_DISPLAY_TIME_SPAN_MS - 1;
        TemplateUrl p1 = buildMockTemplateUrl("prepopulated", 1, pastCutoffTime);
        TemplateUrl c1 = buildMockTemplateUrl("custom1", 0, recentTime);
        TemplateUrl c2 = buildMockTemplateUrl("custom2", 0, pastCutoffTime);
        TemplateUrl c3 = buildMockTemplateUrl("custom3", 0, pastCutoffTime);

        List<TemplateUrl> templateUrls = new ArrayList<>(List.of(p1, c1, c2, c3));
        checkSortAndFilterOutput(templateUrls, c3, List.of(p1, c3, c1));
    }

    /**
     * Calls {@link SearchEngineAdapter#sortAndFilterUnnecessaryTemplateUrl} twice to verify that
     * the outputs are consistent. The first time it indicates that the user is in the EEA, and the
     * second that they are not. Other inputs are kept the same.
     */
    private void checkSortAndFilterOutput(
            List<TemplateUrl> input,
            TemplateUrl defaultSearchEngine,
            List<TemplateUrl> expectedOutput) {
        List<TemplateUrl> modifiedList = new ArrayList<>(input);
        SearchEngineAdapter.sortAndFilterUnnecessaryTemplateUrl(
                modifiedList,
                defaultSearchEngine,
                /* isEeaChoiceCountry= */ true,
                /* shouldShowUpdatedSettings= */ true);
        assertThat(modifiedList, contains(expectedOutput.toArray()));

        modifiedList = new ArrayList<>(input);
        SearchEngineAdapter.sortAndFilterUnnecessaryTemplateUrl(
                modifiedList,
                defaultSearchEngine,
                /* isEeaChoiceCountry= */ false,
                /* shouldShowUpdatedSettings= */ true);
        assertThat(modifiedList, contains(expectedOutput.toArray()));

        modifiedList = new ArrayList<>(input);
        SearchEngineAdapter.sortAndFilterUnnecessaryTemplateUrl(
                modifiedList,
                defaultSearchEngine,
                /* isEeaChoiceCountry= */ true,
                /* shouldShowUpdatedSettings= */ false);
        assertThat(modifiedList, contains(expectedOutput.toArray()));

        modifiedList = new ArrayList<>(input);
        SearchEngineAdapter.sortAndFilterUnnecessaryTemplateUrl(
                modifiedList,
                defaultSearchEngine,
                /* isEeaChoiceCountry= */ false,
                /* shouldShowUpdatedSettings= */ false);
        assertThat(modifiedList, contains(expectedOutput.toArray()));
    }

    @Test
    public void testGetView() {
        baseTestGetView(/* shouldShowUpdatedSettings= */ false, /* expectLogos= */ false);
    }

    @Test
    public void testGetView_WithSecFeature() {
        baseTestGetView(/* shouldShowUpdatedSettings= */ true, /* expectLogos= */ true);
    }

    private void baseTestGetView(boolean shouldShowUpdatedSettings, boolean expectLogos) {
        TemplateUrl p1 = buildMockTemplateUrl("prepopulated1", 1);
        TemplateUrl p2 = buildMockTemplateUrl("", 2);
        TemplateUrl c1 = buildMockTemplateUrl("custom1", 0);

        doReturn(true).when(mTemplateUrlService).isLoaded();
        doReturn(new ArrayList<>(List.of(p1, p2, c1))).when(mTemplateUrlService).getTemplateUrls();
        doReturn(p2).when(mTemplateUrlService).getDefaultSearchEngineTemplateUrl();
        doReturn(false).when(mTemplateUrlService).isEeaChoiceCountry();
        doReturn(shouldShowUpdatedSettings).when(mTemplateUrlService).shouldShowUpdatedSettings();
        TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService);

        var adapter = new SearchEngineAdapter(mContext, mProfile);
        adapter.start();

        assertEquals(adapter.getCount(), 4);

        // Checking the data that was used to render the view.
        assertEquals(adapter.getItemViewType(0), SearchEngineAdapter.VIEW_TYPE_ITEM);
        verify(p1, never()).getShortName();
        View v = adapter.getView(0, null, null);
        verify(p1, atLeastOnce()).getShortName();
        assertEquals(v.findViewById(R.id.url).getVisibility(), View.VISIBLE);
        assertThat(v.findViewById(R.id.logo), is(expectLogos ? notNullValue() : nullValue()));

        assertEquals(adapter.getItemViewType(1), SearchEngineAdapter.VIEW_TYPE_ITEM);
        verify(p2, never()).getShortName();
        v = adapter.getView(1, null, null);
        verify(p2, atLeastOnce()).getShortName();
        assertEquals(v.findViewById(R.id.url).getVisibility(), View.GONE); // Because no keyword.
        assertThat(v.findViewById(R.id.logo), is(expectLogos ? notNullValue() : nullValue()));

        assertEquals(adapter.getItemViewType(2), SearchEngineAdapter.VIEW_TYPE_DIVIDER);
        assertNotNull(adapter.getView(2, null, null));

        assertEquals(adapter.getItemViewType(3), SearchEngineAdapter.VIEW_TYPE_ITEM);
        verify(c1, never()).getShortName();
        v = adapter.getView(3, null, null);
        verify(c1, atLeastOnce()).getShortName();
        assertEquals(v.findViewById(R.id.url).getVisibility(), View.VISIBLE);
        assertThat(v.findViewById(R.id.logo), is(expectLogos ? notNullValue() : nullValue()));
    }
}