chromium/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripTabDragShadowViewUnitTest.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.compositor.overlays.strip;

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.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.util.Size;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.ColorRes;
import androidx.appcompat.content.res.AppCompatResources;
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.chromium.base.Callback;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.LayerTitleCache;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabUtils;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.chrome.browser.tab_ui.TabThumbnailView;
import org.chromium.chrome.browser.tasks.tab_management.TabUiThemeUtil;
import org.chromium.chrome.browser.ui.favicon.FaviconHelper.FaviconImageCallback;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.TestActivity;

/** Unit tests for {@link StripTabDragShadowView}. */
@RunWith(BaseRobolectricTestRunner.class)
public class StripTabDragShadowViewUnitTest {
    @Rule
    public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
            new ActivityScenarioRule<>(TestActivity.class);

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

    @Captor private ArgumentCaptor<FaviconImageCallback> mGetFaviconCallbackCaptor;
    @Captor private ArgumentCaptor<Callback<Bitmap>> mGetThumbnailCallbackCaptor;

    @Mock private Supplier<TabContentManager> mMockTabContentManagerSupplier;
    @Mock private Supplier<LayerTitleCache> mMockLayerTitleCacheSupplier;
    @Mock private StripTabDragShadowView.ShadowUpdateHost mMockShadowUpdateHost;

    @Mock private TabContentManager mMockTabContentManager;
    @Mock private LayerTitleCache mMockLayerTitleCache;
    @Mock private Tab mMockTab;
    @Mock private Bitmap mMockThumbnailBitmap;
    @Mock private Bitmap mMockOriginalFaviconBitmap;
    @Mock private Bitmap mMockHistoryFaviconBitmap;
    @Mock private WebContents mMockWebContents;

    private static final int TAB_ID = 10;

    private Activity mActivity;
    private StripTabDragShadowView mStripTabDragShadowView;

    private View mCardView;
    private TextView mTitleView;
    private ImageView mFaviconView;
    private TabThumbnailView mThumbnailView;

    @Before
    public void setUp() {
        mActivityScenarioRule
                .getScenario()
                .onActivity(
                        activity -> {
                            mActivity = activity;
                            mActivity.setTheme(R.style.Theme_BrowserUI_DayNight);
                        });

        when(mMockTabContentManagerSupplier.get()).thenReturn(mMockTabContentManager);
        when(mMockLayerTitleCacheSupplier.get()).thenReturn(mMockLayerTitleCache);

        when(mMockTab.getId()).thenReturn(TAB_ID);

        mStripTabDragShadowView =
                (StripTabDragShadowView)
                        mActivity
                                .getLayoutInflater()
                                .inflate(R.layout.strip_tab_drag_shadow_view, null);
        mStripTabDragShadowView.initialize(
                /* browserControlsStateProvider= */ null,
                mMockTabContentManagerSupplier,
                mMockLayerTitleCacheSupplier,
                mMockShadowUpdateHost);

        mCardView = mStripTabDragShadowView.findViewById(R.id.card_view);
        mTitleView = mStripTabDragShadowView.findViewById(R.id.tab_title);
        mFaviconView = mStripTabDragShadowView.findViewById(R.id.tab_favicon);
        mThumbnailView = mStripTabDragShadowView.findViewById(R.id.tab_thumbnail);

        mActivity.setContentView(mStripTabDragShadowView);
    }

    @Test
    public void testSetTab() {
        mStripTabDragShadowView.prepareForDrag(mMockTab, 0);

        assertEquals(
                "Should have set Tab on drag start.",
                mMockTab,
                mStripTabDragShadowView.getTabForTesting());
        verify(mMockTab).addObserver(any());
    }

    @Test
    public void testClear() {
        mStripTabDragShadowView.prepareForDrag(mMockTab, 0);

        mStripTabDragShadowView.clear();

        assertNull(
                "Should no longer be tied to Tab on drag end.",
                mStripTabDragShadowView.getTabForTesting());
        verify(mMockTab).removeObserver(any());
    }

    @Test
    public void testUpdate_LayoutSize() {
        mStripTabDragShadowView.prepareForDrag(mMockTab, 0);

        int expectedWidth = StripTabDragShadowView.WIDTH_DP;
        int expectedHeight =
                TabUtils.deriveGridCardHeight(
                        expectedWidth, mActivity, /* browserControlsStateProvider= */ null);
        LayoutParams layoutParams = mStripTabDragShadowView.getLayoutParams();
        assertEquals("Unexpected view width.", expectedWidth, layoutParams.width);
        assertEquals("Unexpected view height.", expectedHeight, layoutParams.height);
    }

    @Test
    public void testUpdate_LayoutSize_Expand() {
        mStripTabDragShadowView.prepareForDrag(mMockTab, 0);
        assertNull(
                "Should not be animating.", mStripTabDragShadowView.getRunningAnimatorForTesting());

        mStripTabDragShadowView.expand();

        Resources resources = mActivity.getResources();
        int expectedWidth = 0;
        int expectedHeight =
                resources.getDimensionPixelSize(R.dimen.tab_grid_card_header_height)
                        + (2 * resources.getDimensionPixelSize(R.dimen.tab_grid_card_margin));
        LayoutParams layoutParams = mStripTabDragShadowView.getLayoutParams();
        assertEquals("Unexpected view width.", expectedWidth, layoutParams.width);
        assertEquals("Unexpected view height.", expectedHeight, layoutParams.height);
        assertNotNull(
                "Should be animating.", mStripTabDragShadowView.getRunningAnimatorForTesting());
    }

    @Test
    public void testUpdate_ThumbnailRequestSuccess() {
        mStripTabDragShadowView.prepareForDrag(mMockTab, 0);

        verify(mMockTabContentManager)
                .getTabThumbnailWithCallback(
                        eq(TAB_ID), any(Size.class), mGetThumbnailCallbackCaptor.capture());

        mGetThumbnailCallbackCaptor.getValue().onResult(mMockThumbnailBitmap);
        assertEquals(
                "Should be using returned thumbnail.",
                ((BitmapDrawable) mThumbnailView.getDrawable()).getBitmap(),
                mMockThumbnailBitmap);
    }

    @Test
    public void testUpdate_ThumbnailRequestFailure() {
        mStripTabDragShadowView.prepareForDrag(mMockTab, 0);
        verify(mMockTabContentManager)
                .getTabThumbnailWithCallback(
                        eq(TAB_ID), any(Size.class), mGetThumbnailCallbackCaptor.capture());

        mGetThumbnailCallbackCaptor.getValue().onResult(null);
        assertEquals(
                "Should be using default thumbnail.",
                mThumbnailView.getIconDrawableForTesting(),
                mThumbnailView.getDrawable());
    }

    @Test
    public void testUpdate_OriginalFavicon() {
        when(mMockTab.getWebContents()).thenReturn(mMockWebContents);
        when(mMockLayerTitleCache.getOriginalFavicon(any(Tab.class)))
                .thenReturn(mMockOriginalFaviconBitmap);

        mStripTabDragShadowView.prepareForDrag(mMockTab, 0);

        assertEquals(
                "Should be using original favicon.",
                mMockOriginalFaviconBitmap,
                ((BitmapDrawable) mFaviconView.getDrawable()).getBitmap());
    }

    @Test
    public void testUpdate_HistoryFavicon() {
        when(mMockLayerTitleCache.getOriginalFavicon(any(Tab.class)))
                .thenReturn(mMockOriginalFaviconBitmap);

        mStripTabDragShadowView.prepareForDrag(mMockTab, 0);
        verify(mMockLayerTitleCache)
                .fetchFaviconWithCallback(eq(mMockTab), mGetFaviconCallbackCaptor.capture());
        mGetFaviconCallbackCaptor
                .getValue()
                .onFaviconAvailable(mMockHistoryFaviconBitmap, /* iconUrl= */ null);

        assertEquals(
                "Should be using favicon from history.",
                mMockHistoryFaviconBitmap,
                ((BitmapDrawable) mFaviconView.getDrawable()).getBitmap());
    }

    @Test
    public void testSetIncognito() {
        boolean incognito = true;
        when(mMockTab.isIncognito()).thenReturn(incognito);

        mStripTabDragShadowView.prepareForDrag(mMockTab, 0);

        @ColorRes
        int expectedBackgroundColor =
                TabUiThemeUtil.getTabStripContainerColor(
                        mActivity,
                        incognito,
                        /* foreground= */ true,
                        /* isReordering= */ false,
                        /* isPlaceholder= */ false,
                        /* isHovered= */ false);
        assertEquals(
                "Unexpected drag shadow color.",
                expectedBackgroundColor,
                mCardView.getBackgroundTintList().getDefaultColor());

        @ColorRes
        int expectedTitleColor =
                AppCompatResources.getColorStateList(
                                mActivity, R.color.compositor_tab_title_bar_text_incognito)
                        .getDefaultColor();
        assertEquals(
                "Unexpected title color", expectedTitleColor, mTitleView.getCurrentTextColor());
    }
}