blob: 4656ea94701e2f84a945e3a527fe9034050d1fe5 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.CARD_ALPHA;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.CARD_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.ModelType.TAB;
import android.graphics.Canvas;
import android.view.View;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelFilter;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.ui.modelutil.MVCListAdapter;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* Tests for {@link TabGridItemTouchHelperCallback}.
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
public class TabGridItemTouchHelperCallbackUnitTest {
@Rule
public TestRule mProcessor = new Features.JUnitProcessor();
private static final String TAB1_TITLE = "Tab1";
private static final String TAB2_TITLE = "Tab2";
private static final String TAB3_TITLE = "Tab3";
private static final String TAB4_TITLE = "Tab4";
private static final int TAB1_ID = 456;
private static final int TAB2_ID = 789;
private static final int TAB3_ID = 123;
private static final int TAB4_ID = 357;
private static final int POSITION1 = 0;
private static final int POSITION2 = 1;
private static final int POSITION3 = 2;
private static final int POSITION4 = 3;
private static final float THRESHOLD = 2f;
@Mock
Canvas mCanvas;
@Mock
RecyclerView mRecyclerView;
@Mock
RecyclerView.Adapter mAdapter;
@Mock
TabModel mTabModel;
@Mock
TabModelSelectorImpl mTabModelSelector;
@Mock
TabModelFilterProvider mTabModelFilterProvider;
@Mock
TabListMediator.TabActionListener mTabClosedListener;
@Mock
TabGroupModelFilter mTabGroupModelFilter;
@Mock
EmptyTabModelFilter mEmptyTabModelFilter;
@Mock
TabListMediator.TabGridDialogHandler mTabGridDialogHandler;
@Mock
Profile mProfile;
@Mock
Tracker mTracker;
@Mock
GridLayoutManager mGridLayoutManager;
private SimpleRecyclerViewAdapter.ViewHolder mMockViewHolder1;
private SimpleRecyclerViewAdapter.ViewHolder mMockViewHolder2;
private RecyclerView.ViewHolder mDummyViewHolder1;
private RecyclerView.ViewHolder mDummyViewHolder2;
private RecyclerView.ViewHolder mDummyViewHolder3;
private RecyclerView.ViewHolder mDummyViewHolder4;
private View mItemView1;
private View mItemView2;
private View mItemView3;
private View mItemView4;
private TabGridItemTouchHelperCallback mItemTouchHelperCallback;
private TabListModel mModel;
@Before
public void setUp() {
RecordHistogram.setDisabledForTests(true);
MockitoAnnotations.initMocks(this);
Tab tab1 = prepareTab(TAB1_ID, TAB1_TITLE);
Tab tab2 = prepareTab(TAB2_ID, TAB2_TITLE);
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE);
Tab tab4 = prepareTab(TAB4_ID, TAB4_TITLE);
mMockViewHolder1 = prepareMockViewHolder(TAB1_ID, POSITION1);
mMockViewHolder2 = prepareMockViewHolder(TAB2_ID, POSITION2);
// Mock four cards in a grid layout. Each card is of width 4 and height 4. Both the side
// gaps and top gaps between adjacent cards are 1.
mItemView1 = prepareItemView(0, 0, 4, 4);
mItemView2 = prepareItemView(5, 0, 9, 4);
mItemView3 = prepareItemView(0, 5, 4, 9);
mItemView4 = prepareItemView(5, 5, 9, 9);
mDummyViewHolder1 = prepareDummyViewHolder(mItemView1);
mDummyViewHolder2 = prepareDummyViewHolder(mItemView2);
mDummyViewHolder3 = prepareDummyViewHolder(mItemView3);
mDummyViewHolder4 = prepareDummyViewHolder(mItemView4);
List<TabModel> tabModelList = new ArrayList<>();
tabModelList.add(mTabModel);
doReturn(mTabModel).when(mTabModelSelector).getCurrentModel();
doReturn(tabModelList).when(mTabModelSelector).getModels();
doReturn(mTabModelFilterProvider).when(mTabModelSelector).getTabModelFilterProvider();
doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
doReturn(tab1).when(mTabModel).getTabAt(POSITION1);
doReturn(tab2).when(mTabModel).getTabAt(POSITION2);
doReturn(tab3).when(mTabModel).getTabAt(POSITION3);
doReturn(tab4).when(mTabModel).getTabAt(POSITION4);
doReturn(4).when(mTabModel).getCount();
doReturn(tab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(tab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(tab3).when(mTabGroupModelFilter).getTabAt(POSITION3);
doReturn(tab4).when(mTabGroupModelFilter).getTabAt(POSITION4);
setupRecyclerView();
mModel = new TabListModel();
setupItemTouchHelperCallback(false);
TrackerFactory.setTrackerForTests(mTracker);
}
private void setupRecyclerView() {
doReturn(mAdapter).when(mRecyclerView).getAdapter();
doReturn(mGridLayoutManager).when(mRecyclerView).getLayoutManager();
doReturn(12).when(mRecyclerView).getBottom();
doReturn(4).when(mRecyclerView).getChildCount();
doReturn(4).when(mAdapter).getItemCount();
when(mRecyclerView.getChildAt(POSITION1)).thenReturn(mItemView1);
when(mRecyclerView.getChildAt(POSITION2)).thenReturn(mItemView2);
when(mRecyclerView.getChildAt(POSITION3)).thenReturn(mItemView3);
when(mRecyclerView.getChildAt(POSITION4)).thenReturn(mItemView4);
doReturn(mRecyclerView).when(mItemView1).getParent();
doReturn(mRecyclerView).when(mItemView2).getParent();
doReturn(mRecyclerView).when(mItemView3).getParent();
doReturn(mRecyclerView).when(mItemView4).getParent();
when(mRecyclerView.findViewHolderForAdapterPosition(POSITION1))
.thenReturn(mDummyViewHolder1);
when(mRecyclerView.findViewHolderForAdapterPosition(POSITION2))
.thenReturn(mDummyViewHolder2);
when(mRecyclerView.findViewHolderForAdapterPosition(POSITION3))
.thenReturn(mDummyViewHolder3);
when(mRecyclerView.findViewHolderForAdapterPosition(POSITION4))
.thenReturn(mDummyViewHolder4);
}
private void setupItemTouchHelperCallback(boolean isDialog) {
mItemTouchHelperCallback = new TabGridItemTouchHelperCallback(mModel, mTabModelSelector,
mTabClosedListener, isDialog ? mTabGridDialogHandler : null, "", !isDialog);
mItemTouchHelperCallback.setupCallback(THRESHOLD, THRESHOLD, THRESHOLD, mProfile);
mItemTouchHelperCallback.getMovementFlags(mRecyclerView, mMockViewHolder1);
}
@After
public void tearDown() {
RecordHistogram.setDisabledForTests(false);
}
@Test
public void initializesWithCurrentTabs() {
initAndAssertAllProperties();
}
@Test
public void onStartDraggingTab() {
initAndAssertAllProperties();
assertThat(mModel.get(0).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.CARD_RESTORE));
assertThat(mModel.get(0).model.get(CARD_ALPHA), equalTo(1f));
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder1, ItemTouchHelper.ACTION_STATE_DRAG);
assertThat(mModel.get(0).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_IN));
assertThat(mModel.get(0).model.get(CARD_ALPHA), equalTo(0.8f));
}
@Test
public void onSwipeTab_Delete() {
initAndAssertAllProperties();
mItemTouchHelperCallback.onSwiped(mMockViewHolder1, POSITION1);
verify(mTabClosedListener).run(TAB1_ID);
}
@Test
public void onReleaseTab_NoMerge() {
initAndAssertAllProperties();
// Simulate the selection of card#1 in TabListModel.
mModel.get(0).model.set(TabProperties.CARD_ANIMATION_STATUS,
ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_IN);
mModel.get(0).model.set(CARD_ALPHA, 0.8f);
mItemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION1);
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
assertThat(mModel.get(0).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_OUT));
assertThat(mModel.get(0).model.get(CARD_ALPHA), equalTo(1f));
assertThat(mModel.get(1).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.CARD_RESTORE));
assertThat(mModel.get(1).model.get(CARD_ALPHA), equalTo(1f));
}
@Test
public void onReleaseTab_MergeBackward() {
initAndAssertAllProperties();
// Simulate the selection of card#1 in TabListModel.
mModel.get(0).model.set(TabProperties.CARD_ANIMATION_STATUS,
ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_IN);
mModel.get(0).model.set(CARD_ALPHA, 0.8f);
mItemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION1);
// Simulate hovering on card#2.
mModel.get(1).model.set(TabProperties.CARD_ANIMATION_STATUS,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
mItemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION2);
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mTabGroupModelFilter).mergeTabsToGroup(TAB1_ID, TAB2_ID);
verify(mGridLayoutManager).removeView(mItemView1);
verify(mTracker).notifyEvent(eq(EventConstants.TAB_DRAG_AND_DROP_TO_GROUP));
assertThat(mModel.get(0).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_OUT));
}
@Test
public void onReleaseTab_MergeForward() {
initAndAssertAllProperties();
// Simulate the selection of card#2 in TabListModel.
mModel.get(1).model.set(TabProperties.CARD_ANIMATION_STATUS,
ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_IN);
mModel.get(1).model.set(CARD_ALPHA, 0.8f);
mItemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
// Simulate hovering on card#1.
mModel.get(0).model.set(TabProperties.CARD_ANIMATION_STATUS,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
mItemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder2, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mGridLayoutManager).removeView(mItemView2);
verify(mTabGroupModelFilter).mergeTabsToGroup(TAB2_ID, TAB1_ID);
verify(mTracker).notifyEvent(eq(EventConstants.TAB_DRAG_AND_DROP_TO_GROUP));
assertThat(mModel.get(0).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_OUT));
}
@Test
@Features.DisableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
public void onReleaseTab_MergeBackward_WithoutGroup() {
initAndAssertAllProperties();
doReturn(mEmptyTabModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
// Simulate the selection of card#1 in TabListModel.
mModel.get(0).model.set(TabProperties.CARD_ANIMATION_STATUS,
ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_IN);
mModel.get(0).model.set(CARD_ALPHA, 0.8f);
mItemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION1);
// Simulate hovering on card#2.
mItemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION2);
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
assertThat(mModel.get(0).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_OUT));
assertThat(mModel.get(0).model.get(CARD_ALPHA), equalTo(1f));
// Merge signal should never be sent.
verify(mTabGroupModelFilter, never()).mergeTabsToGroup(anyInt(), anyInt());
verify(mGridLayoutManager, never()).removeView(any(View.class));
}
@Test
@Features.DisableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
public void onReleaseTab_MergeForward_WithoutGroup() {
initAndAssertAllProperties();
doReturn(mEmptyTabModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
// Simulate the selection of card#2 in TabListModel.
mModel.get(1).model.set(TabProperties.CARD_ANIMATION_STATUS,
ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_IN);
mModel.get(1).model.set(CARD_ALPHA, 0.8f);
mItemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
// Simulate hovering on card#1.
mItemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder2, ItemTouchHelper.ACTION_STATE_IDLE);
assertThat(mModel.get(1).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_OUT));
assertThat(mModel.get(1).model.get(CARD_ALPHA), equalTo(1f));
// Merge signal should never be sent.
verify(mTabGroupModelFilter, never()).mergeTabsToGroup(anyInt(), anyInt());
verify(mGridLayoutManager, never()).removeView(any(View.class));
}
@Test
public void onReleaseTab_Merge_CleanOut() {
initAndAssertAllProperties();
// Simulate the selection of card#2 in TabListModel.
mItemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
// Simulate hovering on card#1.
mItemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
// Simulate that the recyclerView is cleaned out when the drop-to-merge happens.
doReturn(null).when(mRecyclerView).findViewHolderForAdapterPosition(anyInt());
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder2, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mGridLayoutManager, never()).removeView(mItemView2);
verify(mTabGroupModelFilter, never()).mergeTabsToGroup(TAB2_ID, TAB1_ID);
verify(mTracker, never()).notifyEvent(eq(EventConstants.TAB_DRAG_AND_DROP_TO_GROUP));
}
@Test
public void onReleaseTab_Merge_Scrolling() {
initAndAssertAllProperties();
// Simulate the selection of card#2 in TabListModel.
mItemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
// Simulate hovering on card#1.
mItemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
// Simulate that the recyclerView is scrolling when the drop-to-merge happens.
when(mRecyclerView.isComputingLayout()).thenReturn(true);
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder2, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mGridLayoutManager, never()).removeView(mItemView2);
verify(mTabGroupModelFilter, never()).mergeTabsToGroup(TAB2_ID, TAB1_ID);
verify(mTracker, never()).notifyEvent(eq(EventConstants.TAB_DRAG_AND_DROP_TO_GROUP));
}
@Test
public void onReleaseTab_UngroupBar_Hide() {
initAndAssertAllProperties();
setupItemTouchHelperCallback(true);
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mTabGridDialogHandler)
.updateUngroupBarStatus(TabGridDialogParent.UngroupBarStatus.HIDE);
}
@Test
public void onReleaseTab_Ungroup() {
initAndAssertAllProperties();
setupItemTouchHelperCallback(true);
mItemTouchHelperCallback.setUnGroupTabIndexForTesting(POSITION1);
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mTabGroupModelFilter).moveTabOutOfGroup(TAB1_ID);
verify(mTabGridDialogHandler)
.updateUngroupBarStatus(TabGridDialogParent.UngroupBarStatus.HIDE);
verify(mGridLayoutManager).removeView(mItemView1);
}
@Test
public void onReleaseTab_Ungroup_Scrolling() {
initAndAssertAllProperties();
setupItemTouchHelperCallback(true);
mItemTouchHelperCallback.setUnGroupTabIndexForTesting(POSITION1);
// Simulate that the recyclerView is scrolling when the drop-to-ungroup happens.
when(mRecyclerView.isComputingLayout()).thenReturn(true);
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mTabGroupModelFilter, never()).moveTabOutOfGroup(TAB1_ID);
verify(mTabGridDialogHandler)
.updateUngroupBarStatus(TabGridDialogParent.UngroupBarStatus.HIDE);
verify(mGridLayoutManager, never()).removeView(mItemView1);
}
@Test
public void onReleaseTab_Ungroup_CleanOut() {
initAndAssertAllProperties();
setupItemTouchHelperCallback(true);
mItemTouchHelperCallback.setUnGroupTabIndexForTesting(POSITION1);
// Simulate that the recyclerView is cleaned out when the drop-to-ungroup happens.
doReturn(null).when(mRecyclerView).findViewHolderForAdapterPosition(anyInt());
mItemTouchHelperCallback.onSelectedChanged(
mMockViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mTabGroupModelFilter, never()).moveTabOutOfGroup(TAB1_ID);
verify(mTabGridDialogHandler)
.updateUngroupBarStatus(TabGridDialogParent.UngroupBarStatus.HIDE);
verify(mGridLayoutManager, never()).removeView(mItemView1);
}
@Test
public void onDragTab_Hovered_GTS_Horizontal() {
initAndAssertAllProperties();
// Drag card#1 rightwards to hover on card#2.
verifyDrag(mDummyViewHolder1, 5, 0, POSITION2,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
// Drag card#2 leftwards to hover on card#1.
verifyDrag(mDummyViewHolder2, -5, 0, POSITION1,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
}
@Test
public void onDragTab_Hovered_GTS_Vertical() {
initAndAssertAllProperties();
// Drag card#1 downwards to hover on card#3.
verifyDrag(mDummyViewHolder1, 0, 5, POSITION3,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
// Drag card#3 upwards to hover on card#1.
verifyDrag(mDummyViewHolder3, 0, -5, POSITION1,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
}
@Test
public void onDragTab_Hovered_GTS_Diagonal() {
initAndAssertAllProperties();
// Drag card#1 diagonally to hover on card#4.
verifyDrag(mDummyViewHolder1, 5, 5, POSITION4,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
// Drag card#4 diagonally to hover on card#1.
verifyDrag(mDummyViewHolder4, -5, -5, POSITION1,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
// Drag card#2 diagonally to hover on card#3.
verifyDrag(mDummyViewHolder2, -5, 5, POSITION3,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
// Drag card#3 diagonally to hover on card#2.
verifyDrag(mDummyViewHolder3, 5, -5, POSITION2,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
}
@Test
public void onDragTab_NotHovered_GTS_Horizontal() {
initAndAssertAllProperties();
// With merge threshold equal to 2, any horizontal drag with |dX| <= (5 - threshold) should
// never trigger hovering.
verifyDrag(mDummyViewHolder1, 3, 0, POSITION2,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder2, -3, 0, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
// With merge threshold equal to 2, any horizontal drag with |dX| >= (5 + threshold) should
// never trigger hovering.
verifyDrag(mDummyViewHolder1, 7, 0, POSITION2,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder2, -7, 0, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
}
@Test
public void onDragTab_NotHovered_GTS_Vertical() {
initAndAssertAllProperties();
// With merge threshold equal to 2, any vertical drag with |dY| <= (5 - threshold) should
// never trigger hovering.
verifyDrag(mDummyViewHolder1, 0, 3, POSITION3,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder3, 0, -3, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
// With merge threshold equal to 2, any vertical drag with |dY| >= (5 + threshold) should
// never trigger hovering.
verifyDrag(mDummyViewHolder1, 0, 7, POSITION3,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder3, 0, -7, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
}
@Test
public void onDragTab_NotHovered_GTS_Diagonal() {
initAndAssertAllProperties();
// With merge threshold equal to 2, any diagonal drag with |dX| <= (5 - threshold) or |dY|
// <= (5 - threshold) should never trigger hovering.
verifyDrag(mDummyViewHolder1, 3, 4, POSITION4,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder1, 4, 3, POSITION4,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder4, -4, -3, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder4, -3, -4, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
// With merge threshold equal to 2, any vertical drag with |dX| >= (5 + threshold) or |dY|
// >= (5 + threshold) should never trigger hovering.
verifyDrag(mDummyViewHolder1, 7, 6, POSITION4,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder1, 6, 7, POSITION4,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder4, -6, -7, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder4, -7, -6, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
}
@Test
public void onDragTab_Restore_Hovered_GTS() {
initAndAssertAllProperties();
// Simulate the process of hovering card#1 on card#2.
verifyDrag(mDummyViewHolder1, 5, 0, POSITION2,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
// Continue to drag card#1 horizontally so that it is no longer hovering on card#2.
verifyDrag(mDummyViewHolder1, 10, 0, POSITION2,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_OUT);
// Simulate the process of hovering card#1 on card#3.
verifyDrag(mDummyViewHolder1, 0, 5, POSITION3,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
// Continue to drag card#1 vertically so that it is no longer hovering on card#3.
verifyDrag(mDummyViewHolder1, 0, 10, POSITION3,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_OUT);
// Simulate the process of hovering card#1 on card#4.
verifyDrag(mDummyViewHolder1, 5, 5, POSITION4,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN);
// Continue to drag card#1 diagonally so that it is no longer hovering on card#4.
verifyDrag(mDummyViewHolder1, 10, 10, POSITION4,
ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_OUT);
}
@Test
@Features.DisableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
public void onDragTab_Hovered_GTS_WithoutGroup() {
initAndAssertAllProperties();
doReturn(mEmptyTabModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
// Hovering shouldn't make any difference.
verifyDrag(mDummyViewHolder1, 5, 0, POSITION2,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder2, -5, 0, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder1, 0, 5, POSITION3,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder3, 0, -5, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder1, 5, 5, POSITION4,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder4, -5, -5, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
}
@Test
public void onDragTab_Hovered_NonGTS() {
initAndAssertAllProperties();
// Suppose drag happens in components other than GTS.
mItemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(false);
// Hovering shouldn't make any difference.
verifyDrag(mDummyViewHolder1, 5, 0, POSITION2,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder2, -5, 0, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder1, 0, 5, POSITION3,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder3, 0, -5, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder1, 5, 5, POSITION4,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
verifyDrag(mDummyViewHolder4, -5, -5, POSITION1,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
}
@Test
public void onDragTab_Ungroup() {
initAndAssertAllProperties();
setupItemTouchHelperCallback(true);
// Simulate dragging card#1 down to the ungroup bar.
mItemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION1);
mItemTouchHelperCallback.onChildDraw(mCanvas, mRecyclerView, mDummyViewHolder1, 0, 7,
ItemTouchHelper.ACTION_STATE_DRAG, true);
verify(mTabGridDialogHandler)
.updateUngroupBarStatus(TabGridDialogParent.UngroupBarStatus.HOVERED);
// Simulate dragging card#3 down to the ungroup bar.
mItemTouchHelperCallback.onChildDraw(mCanvas, mRecyclerView, mDummyViewHolder1, 0, 2,
ItemTouchHelper.ACTION_STATE_DRAG, true);
verify(mTabGridDialogHandler)
.updateUngroupBarStatus(TabGridDialogParent.UngroupBarStatus.HOVERED);
}
@Test
public void onDragTab_NotUngroup() {
initAndAssertAllProperties();
setupItemTouchHelperCallback(true);
// With recyclerview bottom equal to 12 and ungroup threshold equal to 2, any drag with
// itemview.bottom + dY <= 10 should never trigger ungroup.
mItemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION1);
mItemTouchHelperCallback.onChildDraw(mCanvas, mRecyclerView, mDummyViewHolder1, 0, 6,
ItemTouchHelper.ACTION_STATE_DRAG, true);
// Simulate dragging card#3 down to the ungroup bar.
mItemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION3);
mItemTouchHelperCallback.onChildDraw(mCanvas, mRecyclerView, mDummyViewHolder3, 0, 1,
ItemTouchHelper.ACTION_STATE_DRAG, true);
verify(mTabGridDialogHandler, times(2))
.updateUngroupBarStatus(TabGridDialogParent.UngroupBarStatus.SHOW);
verify(mTabGridDialogHandler, never())
.updateUngroupBarStatus(TabGridDialogParent.UngroupBarStatus.HOVERED);
}
@Test
public void onDragTab_AfterRelease() {
initAndAssertAllProperties();
setupItemTouchHelperCallback(true);
// Simulate that drop is finished, but there are some extra onChildDraw calls.
mItemTouchHelperCallback.setSelectedTabIndexForTesting(TabModel.INVALID_TAB_INDEX);
// Simulate dragging the tab down to the ungroup bar.
mItemTouchHelperCallback.onChildDraw(mCanvas, mRecyclerView, mDummyViewHolder1, 0, 8,
ItemTouchHelper.ACTION_STATE_DRAG, true);
verify(mTabGridDialogHandler, never()).updateUngroupBarStatus(anyInt());
}
@Test
public void onDraggingAnimationEnd_Stale() {
initAndAssertAllProperties();
setupItemTouchHelperCallback(false);
// Mock that when the dragging animation ends, the recyclerView is in an inconsistent state:
// recyclerView should be cleaned out, yet the animated view is stale.
mItemTouchHelperCallback.setCurrentActionStateForTesting(ItemTouchHelper.ACTION_STATE_DRAG);
doReturn(1).when(mRecyclerView).getChildCount();
doReturn(0).when(mAdapter).getItemCount();
mItemTouchHelperCallback.clearView(mRecyclerView, mDummyViewHolder1);
verify(mGridLayoutManager).removeView(mItemView1);
}
@Test
public void onDraggingAnimationEnd_NonStale() {
initAndAssertAllProperties();
setupItemTouchHelperCallback(false);
// Mock that when the dragging animation ends, the recyclerView is in consistent state.
mItemTouchHelperCallback.setCurrentActionStateForTesting(ItemTouchHelper.ACTION_STATE_DRAG);
assertThat(mRecyclerView.getChildCount(), equalTo(mAdapter.getItemCount()));
mItemTouchHelperCallback.clearView(mRecyclerView, mDummyViewHolder1);
verify(mGridLayoutManager, never()).removeView(mItemView1);
}
@Test
public void messageItemNotDraggable() {
when(mMockViewHolder1.getItemViewType()).thenReturn(TabProperties.UiType.MESSAGE);
setupItemTouchHelperCallback(false);
assertFalse(
mItemTouchHelperCallback.hasDragFlagForTesting(mRecyclerView, mMockViewHolder1));
}
@Test
public void messageItemSwipeable() {
when(mMockViewHolder1.getItemViewType()).thenReturn(TabProperties.UiType.MESSAGE);
setupItemTouchHelperCallback(false);
assertTrue(mItemTouchHelperCallback.hasSwipeFlag(mRecyclerView, mMockViewHolder1));
}
@Test
public void messageItemNotDropable() {
when(mMockViewHolder1.getItemViewType()).thenReturn(TabProperties.UiType.MESSAGE);
setupItemTouchHelperCallback(false);
assertFalse(mItemTouchHelperCallback.canDropOver(
mRecyclerView, mMockViewHolder2, mMockViewHolder1));
}
@Test(expected = AssertionError.class)
public void messageItemOnMoveFail() {
when(mMockViewHolder1.getItemViewType()).thenReturn(TabProperties.UiType.MESSAGE);
setupItemTouchHelperCallback(false);
mItemTouchHelperCallback.onMove(mRecyclerView, mMockViewHolder1, mMockViewHolder2);
}
@Test
public void newTabTileNotDraggable() {
when(mMockViewHolder1.getItemViewType()).thenReturn(TabProperties.UiType.NEW_TAB_TILE);
setupItemTouchHelperCallback(false);
assertFalse(
mItemTouchHelperCallback.hasDragFlagForTesting(mRecyclerView, mMockViewHolder1));
}
@Test
public void newTabTileNotSwipeable() {
when(mMockViewHolder1.getItemViewType()).thenReturn(TabProperties.UiType.NEW_TAB_TILE);
setupItemTouchHelperCallback(false);
assertFalse(mItemTouchHelperCallback.hasSwipeFlag(mRecyclerView, mMockViewHolder1));
}
@Test
public void newTabTileNotDropable() {
when(mMockViewHolder1.getItemViewType()).thenReturn(TabProperties.UiType.NEW_TAB_TILE);
setupItemTouchHelperCallback(false);
assertFalse(mItemTouchHelperCallback.canDropOver(
mRecyclerView, mMockViewHolder2, mMockViewHolder1));
}
@Test(expected = AssertionError.class)
public void newTabTileOnMoveFail() {
when(mMockViewHolder1.getItemViewType()).thenReturn(TabProperties.UiType.NEW_TAB_TILE);
setupItemTouchHelperCallback(false);
mItemTouchHelperCallback.onMove(mRecyclerView, mMockViewHolder1, mMockViewHolder2);
}
private void verifyDrag(
RecyclerView.ViewHolder viewHolder, float dX, float dY, int targetIndex, int status) {
// Simulate the process of dragging one card to a position.
mItemTouchHelperCallback.onChildDraw(mCanvas, mRecyclerView, viewHolder, dX, dY,
ItemTouchHelper.ACTION_STATE_DRAG, true);
// Verify the card in target index is in correct status.
assertThat(mModel.get(targetIndex).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(status));
}
private void initAndAssertAllProperties() {
for (int i = 0; i < mTabModel.getCount(); i++) {
Tab tab = mTabModel.getTabAt(i);
addTabInfoModel(tab);
}
assertThat(mModel.size(), equalTo(4));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(2).model.get(TabProperties.TAB_ID), equalTo(TAB3_ID));
assertThat(mModel.get(3).model.get(TabProperties.TAB_ID), equalTo(TAB4_ID));
assertThat(mModel.get(0).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.CARD_RESTORE));
assertThat(mModel.get(1).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.CARD_RESTORE));
assertThat(mModel.get(2).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.CARD_RESTORE));
assertThat(mModel.get(3).model.get(TabProperties.CARD_ANIMATION_STATUS),
equalTo(ClosableTabGridView.AnimationStatus.CARD_RESTORE));
assertThat(mModel.get(0).model.get(CARD_ALPHA), equalTo(1f));
assertThat(mModel.get(1).model.get(CARD_ALPHA), equalTo(1f));
assertThat(mModel.get(2).model.get(CARD_ALPHA), equalTo(1f));
assertThat(mModel.get(3).model.get(CARD_ALPHA), equalTo(1f));
}
private void addTabInfoModel(Tab tab) {
PropertyKey[] testKeysTabGrid = new PropertyKey[] {
TabProperties.TAB_ID, TabProperties.CARD_ANIMATION_STATUS, CARD_ALPHA, CARD_TYPE};
PropertyModel tabInfo = new PropertyModel.Builder(testKeysTabGrid)
.with(TabProperties.TAB_ID, tab.getId())
.with(TabProperties.CARD_ANIMATION_STATUS,
ClosableTabGridView.AnimationStatus.CARD_RESTORE)
.with(CARD_ALPHA, 1f)
.with(CARD_TYPE, TAB)
.build();
mModel.add(new MVCListAdapter.ListItem(0, tabInfo));
}
private Tab prepareTab(int id, String title) {
Tab tab = mock(Tab.class);
doReturn(id).when(tab).getId();
doReturn(title).when(tab).getTitle();
return tab;
}
private SimpleRecyclerViewAdapter.ViewHolder prepareMockViewHolder(int id, int position) {
SimpleRecyclerViewAdapter.ViewHolder viewHolder =
mock(SimpleRecyclerViewAdapter.ViewHolder.class);
viewHolder.model = new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
.with(TabProperties.TAB_ID, id)
.with(CARD_TYPE, TAB)
.build();
doReturn(position).when(viewHolder).getAdapterPosition();
return viewHolder;
}
private View prepareItemView(int left, int top, int right, int bottom) {
View view = mock(View.class);
doReturn(left).when(view).getLeft();
doReturn(top).when(view).getTop();
doReturn(right).when(view).getRight();
doReturn(bottom).when(view).getBottom();
return view;
}
private RecyclerView.ViewHolder prepareDummyViewHolder(View itemView) {
return new RecyclerView.ViewHolder(itemView) {};
}
}