chromium/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherThumbnailTest.java

// Copyright 2020 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.tasks.tab_management;

import static androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.withId;

import static org.hamcrest.Matchers.allOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;

import android.view.View;
import android.widget.ImageView;

import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.NoMatchingViewException;
import androidx.test.espresso.ViewAssertion;
import androidx.test.filters.MediumTest;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.ui.test.util.UiRestriction;
import org.chromium.ui.widget.ViewLookupCachingFrameLayout;

/** Tests for the thumbnail view in Grid Tab Switcher. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({
    ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
    "force-fieldtrials=Study/Group"
})
@Restriction({
    UiRestriction.RESTRICTION_TYPE_PHONE,
    Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE
})
public class TabSwitcherThumbnailTest {
    @Rule
    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();

    private ThumbnailFetcher mNullThumbnailFetcher =
            new ThumbnailFetcher(
                    (tabId, thumbnailSize, isSelected, callback) -> callback.onResult(null),
                    Tab.INVALID_TAB_ID);

    @Before
    public void setUp() {
        mActivityTestRule.startMainActivityWithURL(UrlConstants.NTP_URL);
        TabGridViewBinder.setThumbnailFeatureForTesting(mNullThumbnailFetcher);
    }

    @Test
    @MediumTest
    public void testThumbnailDynamicAspectRatioWhenCaptured_FixedWhenShown() {
        // With this flag bitmap aspect ratios are not applied. Check that the resultant image views
        // still display at the right size.
        int tabCounts = 11;
        TabUiTestHelper.prepareTabsWithThumbnail(mActivityTestRule, tabCounts, 0, "about:blank");
        TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity());
        verifyAllThumbnailHeightWithAspectRatio(tabCounts, 0.85f);

        // With hard cleanup.
        TabUiTestHelper.leaveTabSwitcher(mActivityTestRule.getActivity());
        TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity());
        verifyAllThumbnailHeightWithAspectRatio(tabCounts, 0.85f);
    }

    @Test
    @MediumTest
    public void testThumbnailAspectRatio_point85() {
        int tabCounts = 11;
        TabUiTestHelper.prepareTabsWithThumbnail(mActivityTestRule, tabCounts, 0, "about:blank");
        TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity());
        verifyAllThumbnailHeightWithAspectRatio(tabCounts, 0.85f);
    }

    @Test
    @MediumTest
    public void testThumbnail_withSoftCleanup() {
        int tabCounts = 11;
        TabUiTestHelper.prepareTabsWithThumbnail(mActivityTestRule, tabCounts, 0, "about:blank");
        TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity());
        verifyAllThumbnailHeightWithAspectRatio(tabCounts, .85f);

        // With soft cleanup.
        TabUiTestHelper.leaveTabSwitcher(mActivityTestRule.getActivity());
        TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity());
        // There is a chance this will fail without the current changes. Soft cleanup sets the
        // fetcher to null, which triggers TabGridViewBinder#releaseThumbnail. If the view still
        // under measuring, then its height can be zero after measurement.
        verifyAllThumbnailHeightWithAspectRatio(tabCounts, .85f);
    }

    private void verifyAllThumbnailHeightWithAspectRatio(int tabCounts, float ratio) {
        // The last tab is currently selected, we are at the bottom of tab switcher when it shows.
        // There is a higher chance for the test to fail with backward counting, because after the
        // view being recycled, its height might have the correct measurement.
        for (int i = tabCounts - 1; i >= 0; i--) {
            onViewWaiting(
                            allOf(
                                    isDescendantOfA(
                                            withId(
                                                    TabUiTestHelper.getTabSwitcherAncestorId(
                                                            mActivityTestRule.getActivity()))),
                                    withId(R.id.tab_list_recycler_view)))
                    .perform(scrollToPosition(i))
                    .check(ThumbnailHeightAssertion.notZeroAt(i))
                    .check(ThumbnailAspectRatioAssertion.havingAspectRatioAt(ratio, i));
        }
    }

    private static class ThumbnailAspectRatioAssertion implements ViewAssertion {
        public static ThumbnailAspectRatioAssertion havingAspectRatioAt(float ratio, int position) {
            return new ThumbnailAspectRatioAssertion(ratio, position);
        }

        private int mPosition;
        private float mExpectedRatio;

        ThumbnailAspectRatioAssertion(float ratio, int position) {
            mExpectedRatio = ratio;
            mPosition = position;
        }

        @Override
        public void check(View view, NoMatchingViewException noMatchException) {
            if (noMatchException != null) throw noMatchException;

            RecyclerView recyclerView = (RecyclerView) view;
            RecyclerView.ViewHolder viewHolder =
                    recyclerView.findViewHolderForAdapterPosition(mPosition);
            if (viewHolder != null) {
                ViewLookupCachingFrameLayout tabView =
                        (ViewLookupCachingFrameLayout) viewHolder.itemView;
                ImageView thumbnail = (ImageView) tabView.fastFindViewById(R.id.tab_thumbnail);
                float thumbnailRatio = thumbnail.getWidth() * 1.f / thumbnail.getHeight();
                assertEquals(mExpectedRatio, thumbnailRatio, 0.01);
            }
        }
    }

    private static class ThumbnailHeightAssertion implements ViewAssertion {
        public static ThumbnailHeightAssertion notZeroAt(int position) {
            return new ThumbnailHeightAssertion(position);
        }

        private int mPosition;

        ThumbnailHeightAssertion(int position) {
            mPosition = position;
        }

        @Override
        public void check(View view, NoMatchingViewException noMatchException) {
            if (noMatchException != null) throw noMatchException;

            RecyclerView recyclerView = (RecyclerView) view;
            RecyclerView.ViewHolder viewHolder =
                    recyclerView.findViewHolderForAdapterPosition(mPosition);
            if (viewHolder != null) {
                ViewLookupCachingFrameLayout tabView =
                        (ViewLookupCachingFrameLayout) viewHolder.itemView;
                ImageView thumbnail = (ImageView) tabView.fastFindViewById(R.id.tab_thumbnail);
                assertNotEquals("Thumbnail's height should not be zero", 0, thumbnail.getHeight());
            }
        }
    }
}