chromium/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessorUnitTest.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.omnibox.suggestions.entity;

import static androidx.test.espresso.matcher.ViewMatchers.assertThat;

import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;

import androidx.test.filters.SmallTest;

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.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.annotation.Config;

import org.chromium.base.BaseSwitches;
import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
import org.chromium.chrome.browser.omnibox.styles.OmniboxDrawableState;
import org.chromium.chrome.browser.omnibox.styles.OmniboxImageSupplier;
import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProperties;
import org.chromium.chrome.browser.omnibox.suggestions.basic.BasicSuggestionProcessor.BookmarkState;
import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewProperties;
import org.chromium.components.omnibox.AutocompleteMatch;
import org.chromium.components.omnibox.AutocompleteMatchBuilder;
import org.chromium.components.omnibox.OmniboxFeatures;
import org.chromium.components.omnibox.OmniboxSuggestionType;
import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;

import java.util.Optional;

/** Tests for {@link EntitySuggestionProcessor}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@CommandLineFlags.Add(BaseSwitches.DISABLE_LOW_END_DEVICE_MODE)
public class EntitySuggestionProcessorUnitTest {
    private static final GURL WEB_URL = JUnitTestGURLs.URL_1;
    private static final GURL WEB_URL_2 = JUnitTestGURLs.URL_2;
    private static final GURL SEARCH_URL = JUnitTestGURLs.SEARCH_URL;

    public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule();

    private @Mock SuggestionHost mSuggestionHost;
    private @Mock OmniboxImageSupplier mImageSupplier;
    private @Mock Bitmap mBitmap;
    private @Mock BookmarkState mBookmarkState;
    private @Mock UrlBarEditingTextStateProvider mTextProvider;

    private EntitySuggestionProcessor mProcessor;

    /**
     * Base Suggestion class that can be used for testing. Holds all mechanisms that are required to
     * processSuggestion and validate suggestions.
     */
    class SuggestionTestHelper {
        // Stores created AutocompleteMatch
        protected final AutocompleteMatch mSuggestion;
        // Stores PropertyModel for the suggestion.
        protected final PropertyModel mModel;

        private SuggestionTestHelper(AutocompleteMatch suggestion, PropertyModel model) {
            mSuggestion = suggestion;
            mModel = model;
        }

        /** Get Drawable associated with the suggestion. */
        Drawable getIcon() {
            final OmniboxDrawableState state = mModel.get(BaseSuggestionViewProperties.ICON);
            return state == null ? null : state.drawable;
        }
    }

    /** Create fake Entity suggestion. */
    SuggestionTestHelper createSuggestion(
            String subject, String description, String color, GURL url) {
        AutocompleteMatch suggestion =
                AutocompleteMatchBuilder.searchWithType(OmniboxSuggestionType.SEARCH_SUGGEST_ENTITY)
                        .setDisplayText(subject)
                        .setDescription(description)
                        .setImageUrl(url)
                        .setImageDominantColor(color)
                        .build();
        PropertyModel model = mProcessor.createModel();
        return new SuggestionTestHelper(suggestion, model);
    }

    /** Populate model for associated suggestion. */
    void processSuggestion(SuggestionTestHelper helper) {
        mProcessor.populateModel(helper.mSuggestion, helper.mModel, 0);
    }

    @Before
    public void setUp() {
        mProcessor =
                new EntitySuggestionProcessor(
                        ContextUtils.getApplicationContext(),
                        mSuggestionHost,
                        mTextProvider,
                        Optional.of(mImageSupplier),
                        mBookmarkState);
        doReturn("").when(mTextProvider).getTextWithoutAutocomplete();
    }

    @Test
    @SmallTest
    public void contentTest_basicContent() {
        SuggestionTestHelper suggHelper = createSuggestion("subject", "details", null, SEARCH_URL);
        processSuggestion(suggHelper);
        Assert.assertEquals(
                "subject",
                suggHelper.mModel.get(SuggestionViewProperties.TEXT_LINE_1_TEXT).toString());
        Assert.assertEquals(
                "details",
                suggHelper.mModel.get(SuggestionViewProperties.TEXT_LINE_2_TEXT).toString());
    }

    @Test
    @SmallTest
    public void decorationTest_noColorOrImage() {
        SuggestionTestHelper suggHelper = createSuggestion("", "", null, SEARCH_URL);
        processSuggestion(suggHelper);

        Assert.assertNotNull(suggHelper.getIcon());
        assertThat(suggHelper.getIcon(), instanceOf(BitmapDrawable.class));
    }

    @Test
    @SmallTest
    public void decorationTest_validHexColor_lowMemoryDevice() {
        OmniboxFeatures.setIsLowMemoryDeviceForTesting(true);
        SuggestionTestHelper suggHelper = createSuggestion("", "", "#fedcba", SEARCH_URL);
        processSuggestion(suggHelper);

        assertThat(suggHelper.getIcon(), instanceOf(BitmapDrawable.class));
    }

    @Test
    @SmallTest
    public void decorationTest_validNamedColor() {
        SuggestionTestHelper suggHelper = createSuggestion("", "", "red", SEARCH_URL);
        processSuggestion(suggHelper);

        assertThat(suggHelper.getIcon(), instanceOf(ColorDrawable.class));
        ColorDrawable icon = (ColorDrawable) suggHelper.getIcon();
        Assert.assertEquals(icon.getColor(), Color.RED);
    }

    @Test
    @SmallTest
    public void decorationTest_invalidColor() {
        // Note, fallback is the bitmap drawable representing a search loupe.
        SuggestionTestHelper suggHelper = createSuggestion("", "", "", SEARCH_URL);
        processSuggestion(suggHelper);
        assertThat(suggHelper.getIcon(), instanceOf(BitmapDrawable.class));

        suggHelper = createSuggestion("", "", "#", SEARCH_URL);
        processSuggestion(suggHelper);
        assertThat(suggHelper.getIcon(), instanceOf(BitmapDrawable.class));

        suggHelper = createSuggestion("", "", "invalid", SEARCH_URL);
        processSuggestion(suggHelper);
        assertThat(suggHelper.getIcon(), instanceOf(BitmapDrawable.class));
    }

    @Test
    @SmallTest
    public void fetchImage_withSupplier() {
        SuggestionTestHelper suggHelper = createSuggestion("", "", "red", WEB_URL);
        processSuggestion(suggHelper);

        final ArgumentCaptor<Callback<Bitmap>> callback = ArgumentCaptor.forClass(Callback.class);
        verify(mImageSupplier).fetchImage(eq(WEB_URL), callback.capture());

        assertThat(suggHelper.getIcon(), instanceOf(ColorDrawable.class));
        callback.getValue().onResult(mBitmap);
        assertThat(suggHelper.getIcon(), instanceOf(BitmapDrawable.class));
        Assert.assertEquals(mBitmap, ((BitmapDrawable) suggHelper.getIcon()).getBitmap());
    }

    @Test
    @SmallTest
    public void fetchImage_withoutSupplier() {
        mProcessor =
                new EntitySuggestionProcessor(
                        ContextUtils.getApplicationContext(),
                        mSuggestionHost,
                        mTextProvider,
                        /* imageSupplier= */ Optional.empty(),
                        mBookmarkState);
        SuggestionTestHelper suggHelper = createSuggestion("", "", "red", WEB_URL);
        processSuggestion(suggHelper);
        verifyNoMoreInteractions(mImageSupplier);
        // Expect a fallback icon.
        Assert.assertNotNull(suggHelper.getIcon());
    }

    @Test
    public void doesProcessSuggestion_entitySuggestion() {
        SuggestionTestHelper suggHelper = createSuggestion("", "", "red", WEB_URL);
        Assert.assertTrue(mProcessor.doesProcessSuggestion(suggHelper.mSuggestion, 0));
    }

    @Test
    public void doesProcessSuggestion_nonEntitySuggestion() {
        AutocompleteMatch suggestion =
                AutocompleteMatchBuilder.searchWithType(OmniboxSuggestionType.SEARCH_SUGGEST)
                        .build();
        Assert.assertFalse(mProcessor.doesProcessSuggestion(suggestion, 0));
    }

    @Test
    public void getViewTypeId_forFullTestCoverage() {
        Assert.assertEquals(OmniboxSuggestionUiType.ENTITY_SUGGESTION, mProcessor.getViewTypeId());
    }

    @Test
    public void populateModel_suggestionTextDoesNotWrap() {
        SuggestionTestHelper suggHelper = createSuggestion("subject", "details", null, SEARCH_URL);
        processSuggestion(suggHelper);
        Assert.assertFalse(suggHelper.mModel.get(SuggestionViewProperties.ALLOW_WRAP_AROUND));
    }
}