chromium/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderUnitTest.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.tasks.tab_management;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.FETCH_VIEW_BY_INDEX_CALLBACK;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.FOCUS_TAB_INDEX_FOR_ACCESSIBILITY;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.GET_VISIBLE_RANGE_CALLBACK;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_SCROLLING_SUPPLIER_CALLBACK;

import android.view.View;
import android.view.accessibility.AccessibilityEvent;

import androidx.annotation.NonNull;
import androidx.core.util.Function;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
import androidx.test.filters.SmallTest;

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.robolectric.annotation.LooperMode;

import org.chromium.base.Callback;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.ui.modelutil.PropertyModel;

/** Robolectric tests for {@link TabListContainerViewBinder}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@LooperMode(LooperMode.Mode.PAUSED)
public class TabListContainerViewBinderUnitTest {
    private static class MockViewHolder extends RecyclerView.ViewHolder {
        public MockViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }

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

    @Mock private TabListRecyclerView mTabListRecyclerViewMock;
    @Mock private LinearLayoutManager mLinearLayoutManager;
    @Mock private View mViewMock1;
    @Mock private View mViewMock2;
    @Mock Callback<Function<Integer, View>> mFetchViewByIndexCallback;
    @Mock Callback<Supplier<Pair<Integer, Integer>>> mGetVisibleRangeCallback;
    @Mock Callback<ObservableSupplier<Boolean>> mIsScrollingSupplierCallback;

    @Captor ArgumentCaptor<Function<Integer, View>> mFetchViewByIndexCaptor;
    @Captor ArgumentCaptor<Supplier<Pair<Integer, Integer>>> mGetVisibleRangeCaptor;
    @Captor ArgumentCaptor<OnScrollListener> mOnScrollListenerCaptor;
    @Captor ArgumentCaptor<ObservableSupplier<Boolean>> mOnScrollingSupplierCaptor;

    @Before
    public void setUp() {
        when(mTabListRecyclerViewMock.getLayoutManager()).thenReturn(mLinearLayoutManager);
    }

    @Test
    @SmallTest
    public void testFocusTabIndexForAccessibilityProperty() {
        MockViewHolder viewHolder = spy(new MockViewHolder(mViewMock1));
        doReturn(viewHolder).when(mTabListRecyclerViewMock).findViewHolderForAdapterPosition(eq(2));
        PropertyModel propertyModel =
                new PropertyModel.Builder(TabListContainerProperties.ALL_KEYS)
                        .with(FOCUS_TAB_INDEX_FOR_ACCESSIBILITY, 2)
                        .build();

        TabListContainerViewBinder.bind(
                propertyModel, mTabListRecyclerViewMock, FOCUS_TAB_INDEX_FOR_ACCESSIBILITY);

        verify(mViewMock1).requestFocus();
        verify(mViewMock1).sendAccessibilityEvent(eq(AccessibilityEvent.TYPE_VIEW_FOCUSED));
    }

    @Test
    @SmallTest
    public void testFetchViewByIndexCallback() {
        MockViewHolder viewHolder1 = spy(new MockViewHolder(mViewMock1));
        MockViewHolder viewHolder2 = spy(new MockViewHolder(mViewMock2));
        doReturn(viewHolder1).when(mTabListRecyclerViewMock).findViewHolderForAdapterPosition(0);
        doReturn(viewHolder2).when(mTabListRecyclerViewMock).findViewHolderForAdapterPosition(1);
        PropertyModel propertyModel =
                new PropertyModel.Builder(TabListContainerProperties.ALL_KEYS)
                        .with(FETCH_VIEW_BY_INDEX_CALLBACK, mFetchViewByIndexCallback)
                        .build();

        TabListContainerViewBinder.bind(
                propertyModel, mTabListRecyclerViewMock, FETCH_VIEW_BY_INDEX_CALLBACK);

        verify(mFetchViewByIndexCallback).onResult(mFetchViewByIndexCaptor.capture());
        assertEquals(mViewMock1, mFetchViewByIndexCaptor.getValue().apply(0));
        assertEquals(mViewMock2, mFetchViewByIndexCaptor.getValue().apply(1));
        assertEquals(null, mFetchViewByIndexCaptor.getValue().apply(2));
    }

    @Test
    @SmallTest
    public void testGetVisibleRangeCallback() {
        PropertyModel propertyModel =
                new PropertyModel.Builder(TabListContainerProperties.ALL_KEYS)
                        .with(GET_VISIBLE_RANGE_CALLBACK, mGetVisibleRangeCallback)
                        .build();

        TabListContainerViewBinder.bind(
                propertyModel, mTabListRecyclerViewMock, GET_VISIBLE_RANGE_CALLBACK);

        when(mLinearLayoutManager.findFirstCompletelyVisibleItemPosition()).thenReturn(1);
        when(mLinearLayoutManager.findLastCompletelyVisibleItemPosition()).thenReturn(2);
        verify(mGetVisibleRangeCallback).onResult(mGetVisibleRangeCaptor.capture());
        Pair<Integer, Integer> range = mGetVisibleRangeCaptor.getValue().get();
        assertNotNull(range);
        assertEquals(1, range.first.intValue());
        assertEquals(2, range.second.intValue());
    }

    @Test
    @SmallTest
    public void testIsScrollingSupplierCallback() {
        PropertyModel propertyModel =
                new PropertyModel.Builder(TabListContainerProperties.ALL_KEYS)
                        .with(IS_SCROLLING_SUPPLIER_CALLBACK, mIsScrollingSupplierCallback)
                        .build();

        TabListContainerViewBinder.bind(
                propertyModel, mTabListRecyclerViewMock, IS_SCROLLING_SUPPLIER_CALLBACK);

        verify(mTabListRecyclerViewMock).addOnScrollListener(mOnScrollListenerCaptor.capture());
        OnScrollListener listener = mOnScrollListenerCaptor.getValue();
        verify(mIsScrollingSupplierCallback).onResult(mOnScrollingSupplierCaptor.capture());
        ObservableSupplier<Boolean> isScrollingSupplier = mOnScrollingSupplierCaptor.getValue();

        listener.onScrollStateChanged(mTabListRecyclerViewMock, RecyclerView.SCROLL_STATE_IDLE);
        assertFalse(isScrollingSupplier.get());

        listener.onScrollStateChanged(mTabListRecyclerViewMock, RecyclerView.SCROLL_STATE_DRAGGING);
        assertTrue(isScrollingSupplier.get());

        listener.onScrollStateChanged(mTabListRecyclerViewMock, RecyclerView.SCROLL_STATE_SETTLING);
        assertTrue(isScrollingSupplier.get());

        listener.onScrollStateChanged(mTabListRecyclerViewMock, RecyclerView.SCROLL_STATE_IDLE);
        assertFalse(isScrollingSupplier.get());
    }
}