chromium/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkImageFetcherTest.java

// Copyright 2023 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.bookmarks;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import static org.chromium.ui.test.util.MockitoHelper.doCallback;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.Pair;

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

import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.page_image_service.ImageServiceBridgeJni;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.bookmarks.BookmarkItem;
import org.chromium.components.bookmarks.BookmarkType;
import org.chromium.components.browser_ui.widget.RoundedIconGenerator;
import org.chromium.components.favicon.IconType;
import org.chromium.components.favicon.LargeIconBridge;
import org.chromium.components.favicon.LargeIconBridge.LargeIconCallback;
import org.chromium.components.image_fetcher.ImageFetcher;
import org.chromium.components.power_bookmarks.PowerBookmarkMeta;
import org.chromium.components.power_bookmarks.PowerBookmarkMeta.Image;
import org.chromium.components.power_bookmarks.ShoppingSpecifics;
import org.chromium.ui.base.TestActivity;
import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;

import java.util.Arrays;

/** Unit tests for {@link BookmarkImageFetcher}. */
@Batch(Batch.UNIT_TESTS)
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class BookmarkImageFetcherTest {
    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();

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

    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private BookmarkModel mBookmarkModel;
    @Mock private LargeIconBridge mLargeIconBridge;
    @Mock private RoundedIconGenerator mIconGenerator;
    @Mock private ImageFetcher mImageFetcher;
    @Mock private Callback<Drawable> mDrawableCallback;
    @Mock private Callback<Pair<Drawable, Drawable>> mFolderDrawablesCallback;
    @Mock private Profile mProfile;
    @Mock private ImageServiceBridgeJni mImageServiceBridgeJni;

    @Captor private ArgumentCaptor<Drawable> mDrawableCaptor;
    @Captor private ArgumentCaptor<Pair<Drawable, Drawable>> mFolderDrawablesCaptor;
    @Captor private ArgumentCaptor<Callback<GURL>> mGURLCallbackCaptor;
    @Captor private ArgumentCaptor<Callback<Bitmap>> mBitmapCallbackCaptor;

    private final BookmarkId mFolderId = new BookmarkId(/* id= */ 1, BookmarkType.NORMAL);
    private final BookmarkId mBookmarkId1 = new BookmarkId(/* id= */ 2, BookmarkType.NORMAL);
    private final BookmarkId mBookmarkId2 = new BookmarkId(/* id= */ 3, BookmarkType.NORMAL);

    private final BookmarkItem mFolderItem =
            new BookmarkItem(
                    mFolderId, "Folder", null, true, null, true, false, 0, false, 0, false);
    private final BookmarkItem mBookmarkItem1 =
            new BookmarkItem(
                    mBookmarkId1,
                    "Bookmark1",
                    JUnitTestGURLs.EXAMPLE_URL,
                    false,
                    mFolderId,
                    true,
                    false,
                    0,
                    false,
                    0,
                    false);
    private final BookmarkItem mBookmarkItem2 =
            new BookmarkItem(
                    mBookmarkId2,
                    "Bookmark1",
                    JUnitTestGURLs.EXAMPLE_URL,
                    false,
                    mFolderId,
                    true,
                    false,
                    0,
                    false,
                    0,
                    false);
    private final Bitmap mBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);

    private Activity mActivity;
    private BookmarkImageFetcher mBookmarkImageFetcher;

    @Before
    public void setUp() {
        mJniMocker.mock(ImageServiceBridgeJni.TEST_HOOKS, mImageServiceBridgeJni);

        mActivityScenarioRule
                .getScenario()
                .onActivity(
                        (activity) -> {
                            mActivity = spy(activity);

                            // Setup BookmarkModel.
                            doReturn(true).when(mBookmarkModel).doesBookmarkExist(any());
                            doReturn(Arrays.asList(mBookmarkId1, mBookmarkId2))
                                    .when(mBookmarkModel)
                                    .getChildIds(mFolderId);
                            doReturn(mFolderItem).when(mBookmarkModel).getBookmarkById(mFolderId);
                            doReturn(mBookmarkItem1)
                                    .when(mBookmarkModel)
                                    .getBookmarkById(mBookmarkId1);
                            doReturn(mBookmarkItem2)
                                    .when(mBookmarkModel)
                                    .getBookmarkById(mBookmarkId2);

                            // Setup LargeIconBridge.
                            doCallback(
                                            3,
                                            (LargeIconCallback callback) -> {
                                                callback.onLargeIconAvailable(
                                                        mBitmap,
                                                        Color.GREEN,
                                                        false,
                                                        IconType.FAVICON);
                                            })
                                    .when(mLargeIconBridge)
                                    .getLargeIconForUrl(any(), anyInt(), anyInt(), any());

                            // Setup image fetching.
                            doCallback(
                                            1,
                                            (Callback<Bitmap> callback) -> {
                                                callback.onResult(mBitmap);
                                            })
                                    .when(mImageFetcher)
                                    .fetchImage(any(), any());
                            doCallback(
                                            4,
                                            (Callback<GURL> callback) -> {
                                                callback.onResult(JUnitTestGURLs.EXAMPLE_URL);
                                            })
                                    .when(mImageServiceBridgeJni)
                                    .fetchImageUrlFor(
                                            anyLong(), anyBoolean(), anyInt(), any(), any());

                            mBookmarkImageFetcher =
                                    new BookmarkImageFetcher(
                                            mProfile,
                                            mActivity,
                                            mBookmarkModel,
                                            mImageFetcher,
                                            mLargeIconBridge,
                                            mIconGenerator,
                                            1,
                                            1);
                            mBookmarkImageFetcher.setupFetchProperties(mIconGenerator, 100, 100);
                        });
    }

    @Test
    public void testFetchFirstTwoImagesForFolder() {
        mBookmarkImageFetcher.fetchFirstTwoImagesForFolder(mFolderItem, mFolderDrawablesCallback);
        verify(mFolderDrawablesCallback).onResult(mFolderDrawablesCaptor.capture());

        Pair<Drawable, Drawable> drawables = mFolderDrawablesCaptor.getValue();
        assertNotNull(drawables.first);
        assertNotNull(drawables.second);
    }

    @Test
    public void testFetchFirstTwoImagesForFolder_nullChild() {
        doReturn(null).when(mBookmarkModel).getBookmarkById(mBookmarkId2);

        mBookmarkImageFetcher.fetchFirstTwoImagesForFolder(mFolderItem, mFolderDrawablesCallback);
        verify(mFolderDrawablesCallback).onResult(mFolderDrawablesCaptor.capture());

        Pair<Drawable, Drawable> drawables = mFolderDrawablesCaptor.getValue();
        assertNotNull(drawables.first);
        assertNull(drawables.second);
    }

    @Test
    public void testFetchImageForBookmarkWithFaviconFallback() {
        mBookmarkImageFetcher.fetchImageForBookmarkWithFaviconFallback(
                mBookmarkItem1, mDrawableCallback);
        verify(mDrawableCallback).onResult(mDrawableCaptor.capture());
        // There shouldn't be any interaction with large icon bridge since an image was found.
        verify(mLargeIconBridge, times(0)).getLargeIconForUrl(any(), anyInt(), anyInt(), any());

        assertNotNull(mDrawableCaptor.getValue());
    }

    @Test
    public void testFetchImageForBookmarkWithFaviconFallback_withShoppingSpecifics() {
        String metaImageUrl = "https://shoppingmeta.com/";
        PowerBookmarkMeta meta =
                PowerBookmarkMeta.newBuilder()
                        .setShoppingSpecifics(
                                ShoppingSpecifics.newBuilder().setProductClusterId(1234L).build())
                        .setLeadImage(Image.newBuilder().setUrl(metaImageUrl).build())
                        .build();

        doReturn(meta).when(mBookmarkModel).getPowerBookmarkMeta(mBookmarkId1);
        mBookmarkImageFetcher.fetchImageForBookmarkWithFaviconFallback(
                mBookmarkItem1, mDrawableCallback);

        ArgumentCaptor<ImageFetcher.Params> paramsArgumentCaptor =
                ArgumentCaptor.forClass(ImageFetcher.Params.class);
        verify(mImageFetcher).fetchImage(paramsArgumentCaptor.capture(), any());
        assertEquals(paramsArgumentCaptor.getValue().url, metaImageUrl);

        verify(mDrawableCallback).onResult(mDrawableCaptor.capture());
        // There shouldn't be any interaction with large icon bridge since an image was found.
        verify(mLargeIconBridge, times(0)).getLargeIconForUrl(any(), anyInt(), anyInt(), any());

        assertNotNull(mDrawableCaptor.getValue());
    }

    @Test
    public void testFetchImageForBookmarkWithFaviconFallback_fallbackToFavicon() {
        doCallback(
                        1,
                        (Callback<Bitmap> callback) -> {
                            callback.onResult(null);
                        })
                .when(mImageFetcher)
                .fetchImage(any(), any());

        mBookmarkImageFetcher.fetchImageForBookmarkWithFaviconFallback(
                mBookmarkItem1, mDrawableCallback);
        verify(mDrawableCallback).onResult(mDrawableCaptor.capture());
        verify(mLargeIconBridge).getLargeIconForUrl(any(), anyInt(), anyInt(), any());

        assertNotNull(mDrawableCaptor.getValue());
    }

    @Test
    public void testFetchFaviconForBookmark() {
        mBookmarkImageFetcher.fetchFaviconForBookmark(mBookmarkItem1, mDrawableCallback);
        verify(mDrawableCallback).onResult(mDrawableCaptor.capture());
        // There shouldn't be any interaction with large icon bridge since an image was found.
        verify(mLargeIconBridge).getLargeIconForUrl(any(), anyInt(), anyInt(), any());

        assertNotNull(mDrawableCaptor.getValue());
    }
}