chromium/chrome/android/java/src/org/chromium/chrome/browser/tab/TabFaviconTest.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.tab;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;

import androidx.annotation.ColorInt;

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.MockitoAnnotations;
import org.robolectric.annotation.Config;

import org.chromium.base.ObserverList.RewindableIterator;
import org.chromium.base.UserDataHost;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.content_public.browser.WebContents;
import org.chromium.url.JUnitTestGURLs;

/** Unit tests for {@link TabFavicon}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TabFaviconTest {
    private static final int IDEAL_SIZE = 4;

    private static class EmptyIterator implements RewindableIterator<TabObserver> {
        @Override
        public boolean hasNext() {
            return false;
        }

        @Override
        public TabObserver next() {
            return null;
        }

        @Override
        public void rewind() {}
    }

    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private TabFavicon.Natives mTabFaviconJni;
    @Mock private TabImpl mTab;
    @Mock private Context mContext;
    @Mock private Resources mResources;
    @Mock private WebContents mWebContents;

    private UserDataHost mUserDataHost;
    private TabFavicon mTabFavicon;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mJniMocker.mock(TabFaviconJni.TEST_HOOKS, mTabFaviconJni);

        mUserDataHost = new UserDataHost();
        doReturn(mUserDataHost).when(mTab).getUserDataHost();
        doReturn(mContext).when(mTab).getThemedApplicationContext();
        doReturn(mResources).when(mContext).getResources();
        doReturn(IDEAL_SIZE).when(mResources).getDimensionPixelSize(anyInt());
        doReturn(false).when(mTab).isNativePage();
        doReturn(true).when(mTab).isInitialized();
        doReturn(mWebContents).when(mTab).getWebContents();
        doReturn(JUnitTestGURLs.EXAMPLE_URL).when(mTab).getUrl();
        doReturn(new EmptyIterator()).when(mTab).getTabObservers();

        mTabFavicon = TabFavicon.from(mTab);
    }

    private static Bitmap makeBitmap(int size, @ColorInt int color) {
        return makeBitmap(size, size, color);
    }

    private static Bitmap makeBitmap(int width, int height, @ColorInt int color) {
        Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        image.eraseColor(color);
        return image;
    }

    private void onFaviconAvailable(Bitmap bitmap) {
        // Mimic the behavior of the native call, where `TabFavicon#shouldUpdateFaviconForBrowserUI`
        // is checked first before sending the bitmap into Java layer.
        if (mTabFavicon.shouldUpdateFaviconForBrowserUI(bitmap.getWidth(), bitmap.getHeight())) {
            mTabFavicon.onFaviconAvailable(bitmap, JUnitTestGURLs.EXAMPLE_URL);
        }
    }

    @Test
    public void testOnFaviconAvailable_ReturnsBitmap() {
        Assert.assertNull(TabFavicon.getBitmap(mTab));

        onFaviconAvailable(makeBitmap(1, Color.GREEN));
        Bitmap bitmap = TabFavicon.getBitmap(mTab);
        Assert.assertNotNull(bitmap);
        Assert.assertEquals(Color.GREEN, bitmap.getPixel(0, 0));
    }

    @Test
    public void testOnFaviconAvailable_IdealSize() {
        onFaviconAvailable(makeBitmap(1, Color.RED));
        onFaviconAvailable(makeBitmap(IDEAL_SIZE, Color.GREEN));
        onFaviconAvailable(makeBitmap(IDEAL_SIZE / 2, Color.YELLOW));
        onFaviconAvailable(makeBitmap(IDEAL_SIZE * 2, Color.BLUE));

        Bitmap bitmap = TabFavicon.getBitmap(mTab);
        Assert.assertNotNull(bitmap);
        Assert.assertEquals(Color.GREEN, bitmap.getPixel(0, 0));
    }

    @Test
    public void testOnFaviconAvailable_SameSize() {
        // Inspired from https://crbug.com/1352674. Some pages purposefully switch favicons to try
        // to update the current favicon. This will typically result in the same sized favicon being
        // sent. So it's important that we update in this case.
        onFaviconAvailable(makeBitmap(1, Color.RED));
        onFaviconAvailable(makeBitmap(1, Color.GREEN));

        Bitmap bitmap = TabFavicon.getBitmap(mTab);
        Assert.assertNotNull(bitmap);
        Assert.assertEquals(Color.GREEN, bitmap.getPixel(0, 0));
    }
}