blob: 60865a11a9a14a96bb6def72e686f276a34e3c17 [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_groups;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
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 androidx.annotation.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
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.tab.Tab;
import org.chromium.chrome.browser.tab.TabCreationState;
import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Tests for {@link TabGroupModelFilter}.
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TabGroupModelFilterUnitTest {
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 = 147;
private static final int TAB5_ID = 258;
private static final int TAB6_ID = 369;
private static final int TAB1_ROOT_ID = TAB1_ID;
private static final int TAB2_ROOT_ID = TAB2_ID;
private static final int TAB3_ROOT_ID = TAB2_ID;
private static final int TAB4_ROOT_ID = TAB4_ID;
private static final int TAB5_ROOT_ID = TAB5_ID;
private static final int TAB6_ROOT_ID = TAB5_ID;
private static final int TAB1_PARENT_TAB_ID = Tab.INVALID_TAB_ID;
private static final int TAB2_PARENT_TAB_ID = Tab.INVALID_TAB_ID;
private static final int TAB3_PARENT_TAB_ID = TAB2_ID;
private static final int TAB4_PARENT_TAB_ID = Tab.INVALID_TAB_ID;
private static final int TAB5_PARENT_TAB_ID = Tab.INVALID_TAB_ID;
private static final int TAB6_PARENT_TAB_ID = TAB5_ID;
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 int POSITION5 = 4;
private static final int POSITION6 = 5;
private static final int NEW_TAB_ID = 159;
@Mock
TabModel mTabModel;
@Mock
TabGroupModelFilter.Observer mTabGroupModelFilterObserver;
@Captor
ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
private TabImpl mTab1;
private TabImpl mTab2;
private TabImpl mTab3;
private TabImpl mTab4;
private TabImpl mTab5;
private TabImpl mTab6;
private List<Tab> mTabs = new ArrayList<>();
private TabGroupModelFilter mTabGroupModelFilter;
private InOrder mTabModelInOrder;
private TabImpl prepareTab(int tabId, int rootId, int parentTabId) {
TabImpl tab = mock(TabImpl.class);
doAnswer(invocation -> {
int newRootId = invocation.getArgument(0);
doReturn(newRootId).when(tab).getRootId();
return null;
})
.when(tab)
.setRootId(anyInt());
doReturn(tabId).when(tab).getId();
doReturn(parentTabId).when(tab).getParentId();
tab.setRootId(rootId);
return tab;
}
private void setUpTab() {
mTab1 = prepareTab(TAB1_ID, TAB1_ROOT_ID, TAB1_PARENT_TAB_ID);
mTab2 = prepareTab(TAB2_ID, TAB2_ROOT_ID, TAB2_PARENT_TAB_ID);
mTab3 = prepareTab(TAB3_ID, TAB3_ROOT_ID, TAB3_PARENT_TAB_ID);
mTab4 = prepareTab(TAB4_ID, TAB4_ROOT_ID, TAB4_PARENT_TAB_ID);
mTab5 = prepareTab(TAB5_ID, TAB5_ROOT_ID, TAB5_PARENT_TAB_ID);
mTab6 = prepareTab(TAB6_ID, TAB6_ROOT_ID, TAB6_PARENT_TAB_ID);
}
private void setUpTabModel() {
doAnswer(invocation -> {
Tab tab = invocation.getArgument(0);
int index = invocation.getArgument(1);
index = index == -1 ? mTabs.size() : index;
mTabs.add(index, tab);
return null;
})
.when(mTabModel)
.addTab(any(Tab.class), anyInt(), anyInt(), anyInt());
doAnswer(invocation -> {
int movedTabId = invocation.getArgument(0);
int newIndex = invocation.getArgument(1);
int oldIndex = TabModelUtils.getTabIndexById(mTabModel, movedTabId);
Tab tab = TabModelUtils.getTabById(mTabModel, movedTabId);
mTabs.remove(tab);
if (oldIndex < newIndex) --newIndex;
mTabs.add(newIndex, tab);
mTabModelObserverCaptor.getValue().didMoveTab(tab, newIndex, oldIndex);
return null;
})
.when(mTabModel)
.moveTab(anyInt(), anyInt());
doAnswer(invocation -> {
int index = invocation.getArgument(0);
return mTabs.get(index);
})
.when(mTabModel)
.getTabAt(anyInt());
doAnswer(invocation -> {
Tab tab = invocation.getArgument(0);
return mTabs.indexOf(tab);
})
.when(mTabModel)
.indexOf(any(Tab.class));
doAnswer(invocation -> mTabs.size()).when(mTabModel).getCount();
doReturn(0).when(mTabModel).index();
doNothing().when(mTabModel).addObserver(mTabModelObserverCaptor.capture());
mTabModelInOrder = inOrder(mTabModel);
}
private TabImpl addTabToTabModel() {
return addTabToTabModel(-1, null);
}
private TabImpl addTabToTabModel(int index, @Nullable TabImpl tab) {
if (tab == null) tab = prepareTab(NEW_TAB_ID, NEW_TAB_ID, Tab.INVALID_TAB_ID);
mTabModel.addTab(
tab, index, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
mTabModelObserverCaptor.getValue().didAddTab(
tab, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
return tab;
}
private void setupTabGroupModelFilter(boolean isTabRestoreCompleted, boolean isIncognito) {
mTabs.clear();
doReturn(isIncognito).when(mTabModel).isIncognito();
mTabGroupModelFilter = new TabGroupModelFilter(mTabModel);
mTabGroupModelFilter.addTabGroupObserver(mTabGroupModelFilterObserver);
doReturn(isIncognito).when(mTab1).isIncognito();
mTabModel.addTab(
mTab1, -1, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
mTabModelObserverCaptor.getValue().didAddTab(
mTab1, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
doReturn(isIncognito).when(mTab2).isIncognito();
mTabModel.addTab(
mTab2, -1, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
mTabModelObserverCaptor.getValue().didAddTab(
mTab2, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
doReturn(isIncognito).when(mTab3).isIncognito();
mTabModel.addTab(
mTab3, -1, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
mTabModelObserverCaptor.getValue().didAddTab(
mTab3, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
doReturn(isIncognito).when(mTab4).isIncognito();
mTabModel.addTab(
mTab4, -1, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
mTabModelObserverCaptor.getValue().didAddTab(
mTab4, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
doReturn(isIncognito).when(mTab5).isIncognito();
mTabModel.addTab(
mTab5, -1, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
mTabModelObserverCaptor.getValue().didAddTab(
mTab5, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
doReturn(isIncognito).when(mTab6).isIncognito();
mTabModel.addTab(
mTab6, -1, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
mTabModelObserverCaptor.getValue().didAddTab(
mTab6, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
if (isTabRestoreCompleted) {
mTabGroupModelFilter.restoreCompleted();
}
}
@Before
public void setUp() {
// After setUp, TabModel has 6 tabs in the following order: mTab1, mTab2, mTab3, mTab4,
// mTab5, mTab6. While mTab2 and mTab3 are in a group, and mTab5 and mTab6 are in a separate
// group.
RecordHistogram.setDisabledForTests(true);
MockitoAnnotations.initMocks(this);
setUpTab();
setUpTabModel();
setupTabGroupModelFilter(true, false);
}
@After
public void tearDown() {
RecordHistogram.setDisabledForTests(false);
}
@Test
public void setIncognito() {
setupTabGroupModelFilter(true, false);
setupTabGroupModelFilter(false, true);
assertThat(mTabGroupModelFilter.isIncognito(), equalTo(true));
assertThat(mTabModel.getCount(), equalTo(6));
}
@Test
// TODO(mattsimmons): This is actually testing behavior of the superclass but there's no unit
// tests for the superclass today. If one ever exists, this should move to that test.
public void isTabModelRestored() {
setupTabGroupModelFilter(false, false);
assertThat(mTabGroupModelFilter.isTabModelRestored(), equalTo(false));
setupTabGroupModelFilter(true, false);
assertThat(mTabGroupModelFilter.isTabModelRestored(), equalTo(true));
setupTabGroupModelFilter(false, true);
assertThat(mTabGroupModelFilter.isTabModelRestored(), equalTo(true));
setupTabGroupModelFilter(true, true);
assertThat(mTabGroupModelFilter.isTabModelRestored(), equalTo(true));
}
@Test
public void addTab_ToExistingGroup() {
TabImpl newTab = prepareTab(NEW_TAB_ID, NEW_TAB_ID, TAB1_ID);
doReturn(TabLaunchType.FROM_CHROME_UI).when(newTab).getLaunchType();
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
assertThat(mTabGroupModelFilter.indexOf(newTab),
equalTo(mTabGroupModelFilter.indexOf(mTab1)));
}
@Test
public void addTab_ToNewGroup() {
TabImpl newTab = prepareTab(NEW_TAB_ID, NEW_TAB_ID, Tab.INVALID_TAB_ID);
doReturn(TabLaunchType.FROM_CHROME_UI).when(newTab).getLaunchType();
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
assertThat(mTabGroupModelFilter.getCount(), equalTo(4));
addTabToTabModel(-1, newTab);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
assertThat(mTabGroupModelFilter.indexOf(newTab), equalTo(4));
assertThat(mTabGroupModelFilter.getCount(), equalTo(5));
}
@Test
public void addTab_SetRootId() {
TabImpl newTab = prepareTab(NEW_TAB_ID, NEW_TAB_ID, TAB1_ID);
doReturn(TabLaunchType.FROM_CHROME_UI).when(newTab).getLaunchType();
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(newTab.getRootId(), equalTo(TAB1_ROOT_ID));
}
@Test
public void addTab_DuringRestore() {
TabImpl newTab = prepareTab(NEW_TAB_ID, NEW_TAB_ID, TAB1_ID);
doReturn(TabLaunchType.FROM_RESTORE).when(newTab).getLaunchType();
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(newTab.getRootId(), equalTo(NEW_TAB_ID));
}
@Test
public void addTab_DuringResettingFilterState() {
mTabGroupModelFilter.resetFilterState();
verify(mock(TabImpl.class), never()).setRootId(anyInt());
}
@Test(expected = IllegalStateException.class)
public void addTab_ToWrongModel() {
TabImpl newTab = prepareTab(NEW_TAB_ID, NEW_TAB_ID, Tab.INVALID_TAB_ID);
addTabToTabModel(-1, newTab);
doReturn(false).when(mTabModel).isIncognito();
doReturn(true).when(newTab).isIncognito();
mTabGroupModelFilter.addTab(newTab);
}
@Test
public void moveTabOutOfGroup_NonRootTab_NoUpdateTabModel() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
assertThat(mTab3.getRootId(), equalTo(TAB2_ID));
assertThat(mTab6.getRootId(), equalTo(TAB5_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
mTabGroupModelFilter.moveTabOutOfGroup(TAB3_ID);
mTabGroupModelFilter.moveTabOutOfGroup(TAB6_ID);
verify(mTabGroupModelFilterObserver).willMoveTabOutOfGroup(mTab3, TAB2_ROOT_ID);
verify(mTabGroupModelFilterObserver).willMoveTabOutOfGroup(mTab6, TAB5_ROOT_ID);
verify(mTabModel, never()).moveTab(anyInt(), anyInt());
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab3, POSITION2);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab6, POSITION5);
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
assertThat(mTab6.getRootId(), equalTo(TAB6_ID));
}
@Test
public void moveTabOutOfGroup_RootTab_NoUpdateTabModel() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab3, mTab2, mTab4, mTab6, mTab5));
// Move Tab2 and Tab5 to the end of respective group so that root tab is the last tab in
// group. Plus one as offset because we are moving backwards in tab model.
mTabModel.moveTab(TAB2_ID, POSITION3 + 1);
mTabModel.moveTab(TAB5_ID, POSITION6 + 1);
mTabModelInOrder.verify(mTabModel, times(2)).moveTab(anyInt(), anyInt());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab3.getRootId(), equalTo(TAB2_ID));
assertThat(mTab6.getRootId(), equalTo(TAB5_ID));
mTabGroupModelFilter.moveTabOutOfGroup(TAB2_ID);
mTabGroupModelFilter.moveTabOutOfGroup(TAB5_ID);
mTabGroupModelFilterObserver.willMoveTabOutOfGroup(mTab2, TAB3_ID);
mTabGroupModelFilterObserver.willMoveTabOutOfGroup(mTab5, TAB6_ID);
mTabModelInOrder.verify(mTabModel, never()).moveTab(anyInt(), anyInt());
mTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab2, POSITION2);
mTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab5, POSITION5);
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
assertThat(mTab5.getRootId(), equalTo(TAB5_ID));
assertThat(mTab6.getRootId(), equalTo(TAB6_ID));
}
@Test
public void moveTabOutOfGroup_NonRootTab_FirstTab_UpdateTabModel() {
List<Tab> expectedTabModelBeforeUngroup =
new ArrayList<>(Arrays.asList(mTab1, mTab3, mTab2, mTab4, mTab5, mTab6));
List<Tab> expectedTabModelAfterUngroup =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
// Move Tab3 so that Tab3 is the first tab in group.
mTabModel.moveTab(TAB3_ID, POSITION2);
mTabModelInOrder.verify(mTabModel).moveTab(TAB3_ID, POSITION2);
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertThat(mTab3.getRootId(), equalTo(TAB2_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
mTabGroupModelFilter.moveTabOutOfGroup(TAB3_ID);
mTabGroupModelFilterObserver.willMoveTabOutOfGroup(mTab3, TAB2_ROOT_ID);
// Plus one as offset because we are moving backwards in tab model.
mTabModelInOrder.verify(mTabModel).moveTab(TAB3_ID, POSITION3 + 1);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab3, POSITION2);
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelAfterUngroup.toArray());
}
@Test
public void moveTabOutOfGroup_NonRootTab_NotFirstTab_UpdateTabModel() {
TabImpl newTab = prepareTab(NEW_TAB_ID, TAB2_ROOT_ID, TAB2_ID);
List<Tab> expectedTabModelBeforeUngroup =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, newTab, mTab4, mTab5, mTab6));
List<Tab> expectedTabModelAfterUngroup =
new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab, mTab3, mTab4, mTab5, mTab6));
// Add one tab to the end of {Tab2, Tab3} group so that Tab3 is neither the first nor the
// last tab in group.
addTabToTabModel(POSITION4, newTab);
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertThat(mTab3.getRootId(), equalTo(TAB2_ID));
assertThat(newTab.getRootId(), equalTo(TAB2_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
mTabGroupModelFilter.moveTabOutOfGroup(TAB3_ID);
verify(mTabGroupModelFilterObserver).willMoveTabOutOfGroup(mTab3, TAB2_ROOT_ID);
// Plus one as offset because we are moving backwards in tab model.
mTabModelInOrder.verify(mTabModel).moveTab(TAB3_ID, POSITION4 + 1);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab3, POSITION2);
assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertThat(newTab.getRootId(), equalTo(TAB2_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelAfterUngroup.toArray());
}
@Test
public void moveTabOutOfGroup_RootTab_FirstTab_UpdateTabModel() {
List<Tab> expectedTabModelBeforeUngroup =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
List<Tab> expectedTabModelAfterUngroup =
new ArrayList<>(Arrays.asList(mTab1, mTab3, mTab2, mTab4, mTab5, mTab6));
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertThat(mTab3.getRootId(), equalTo(TAB2_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
mTabGroupModelFilter.moveTabOutOfGroup(TAB2_ID);
verify(mTabGroupModelFilterObserver).willMoveTabOutOfGroup(mTab2, TAB3_ID);
// Plus one as offset because we are moving backwards in tab model.
verify(mTabModel).moveTab(mTab2.getId(), POSITION3 + 1);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab2, POSITION2);
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelAfterUngroup.toArray());
}
@Test
public void moveTabOutOfGroup_RootTab_NotFirstTab_UpdateTabModel() {
TabImpl newTab = prepareTab(NEW_TAB_ID, TAB2_ROOT_ID, TAB2_ID);
List<Tab> expectedTabModelBeforeUngroup =
new ArrayList<>(Arrays.asList(mTab1, newTab, mTab2, mTab3, mTab4, mTab5, mTab6));
List<Tab> expectedTabModelAfterUngroup =
new ArrayList<>(Arrays.asList(mTab1, newTab, mTab3, mTab2, mTab4, mTab5, mTab6));
// Add one tab to {Tab2, Tab3} group as the first tab in group, so that Tab2 is neither the
// first nor the last tab in group.
addTabToTabModel(POSITION2, newTab);
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertThat(mTab3.getRootId(), equalTo(TAB2_ID));
assertThat(newTab.getRootId(), equalTo(TAB2_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
mTabGroupModelFilter.moveTabOutOfGroup(TAB2_ID);
verify(mTabGroupModelFilterObserver).willMoveTabOutOfGroup(mTab2, NEW_TAB_ID);
// Plus one as offset because we are moving backwards in tab model.
verify(mTabModel).moveTab(mTab2.getId(), POSITION4 + 1);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab2, POSITION2);
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
// NEW_TAB_ID becomes the new root id for {Tab3, newTab} group.
assertThat(mTab3.getRootId(), equalTo(NEW_TAB_ID));
assertThat(mTab3.getRootId(), equalTo(NEW_TAB_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelAfterUngroup.toArray());
}
@Test
public void moveTabOutOfGroup_LastTab() {
List<Tab> expectedTabModelBeforeUngroup =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
mTabGroupModelFilter.moveTabOutOfGroup(TAB1_ID);
// Ungrouping the last tab in group should have no effect on tab model.
verify(mTabModel, never()).moveTab(anyInt(), anyInt());
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab1, POSITION1);
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
}
@Test
public void moveTabOutOfGroup_OtherGroupsLastShownIdUnchanged() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab3, mTab2, mTab4, mTab5, mTab6));
assertThat(mTab3.getRootId(), equalTo(TAB2_ID));
// By default, the last shown tab is the first tab in group by order in tab model.
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB5_ROOT_ID),
equalTo(TAB5_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB6_ROOT_ID),
equalTo(TAB5_ID));
// Specifically select a different tab in (Tab5, Tab6) group to change the last shown id in
// that group so that it is different from the default setting.
mTabGroupModelFilter.selectTab(mTab6);
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB5_ROOT_ID),
equalTo(TAB6_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB6_ROOT_ID),
equalTo(TAB6_ID));
mTabGroupModelFilter.moveTabOutOfGroup(TAB2_ID);
verify(mTabGroupModelFilterObserver).willMoveTabOutOfGroup(mTab2, TAB3_ID);
// Plus one as offset because we are moving backwards in tab model.
verify(mTabModel).moveTab(mTab2.getId(), POSITION3 + 1);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab2, POSITION2);
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
// After ungroup, last shown ids in groups that are unrelated to this ungroup should remain
// unchanged.
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB5_ROOT_ID),
equalTo(TAB6_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB6_ROOT_ID),
equalTo(TAB6_ID));
}
@Test
public void mergeTabToGroup_NoUpdateTabModel() {
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab2, mTab3, mTab4));
mTabGroupModelFilter.mergeTabsToGroup(mTab4.getId(), mTab2.getId());
verify(mTabModel, never()).moveTab(anyInt(), anyInt());
assertArrayEquals(mTabGroupModelFilter.getRelatedTabList(mTab4.getId()).toArray(),
expectedGroup.toArray());
}
@Test
public void mergeTabToGroup_UpdateTabModel() {
mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId());
verify(mTabModel).moveTab(mTab5.getId(), POSITION3 + 1);
}
@Test
public void mergeOneTabToTab_Forward() {
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab1, mTab4));
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab4, mTab2, mTab3, mTab5, mTab6));
int startIndex = POSITION1;
mTabGroupModelFilter.mergeTabsToGroup(mTab4.getId(), mTab1.getId());
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab4, TAB1_ROOT_ID);
verify(mTabModel).moveTab(mTab4.getId(), ++startIndex);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab4, mTab1.getId());
assertArrayEquals(mTabGroupModelFilter.getRelatedTabList(mTab4.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void mergeGroupToTab_Forward() {
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab1, mTab5, mTab6));
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab5, mTab6, mTab2, mTab3, mTab4));
int startIndex = POSITION1;
mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab1.getId());
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab6, TAB1_ROOT_ID);
verify(mTabModel).moveTab(mTab5.getId(), ++startIndex);
verify(mTabModel).moveTab(mTab6.getId(), ++startIndex);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab6, mTab1.getId());
assertArrayEquals(mTabGroupModelFilter.getRelatedTabList(mTab5.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void mergeGroupToGroup_Forward() {
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab2, mTab3, mTab5, mTab6));
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab5, mTab6, mTab4));
int startIndex = POSITION3;
mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId());
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab6, TAB2_ROOT_ID);
verify(mTabModel).moveTab(mTab5.getId(), ++startIndex);
verify(mTabModel).moveTab(mTab6.getId(), ++startIndex);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab6, mTab2.getId());
assertArrayEquals(mTabGroupModelFilter.getRelatedTabList(mTab5.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void mergeOneTabToTab_Backward() {
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab4, mTab1));
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab2, mTab3, mTab4, mTab1, mTab5, mTab6));
int startIndex = POSITION4;
mTabGroupModelFilter.mergeTabsToGroup(mTab1.getId(), mTab4.getId());
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab1, TAB4_ROOT_ID);
verify(mTabModel).moveTab(mTab1.getId(), startIndex + 1);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab1, mTab4.getId());
assertArrayEquals(mTabGroupModelFilter.getRelatedTabList(mTab1.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void mergeGroupToTab_Backward() {
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab4, mTab2, mTab3));
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab4, mTab2, mTab3, mTab5, mTab6));
int startIndex = POSITION4;
mTabGroupModelFilter.mergeTabsToGroup(mTab2.getId(), mTab4.getId());
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab3, TAB4_ROOT_ID);
verify(mTabModel).moveTab(mTab2.getId(), startIndex + 1);
verify(mTabModel).moveTab(mTab3.getId(), startIndex + 1);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab3, mTab4.getId());
assertArrayEquals(mTabGroupModelFilter.getRelatedTabList(mTab2.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void mergeListOfTabsToGroup_AllBackward() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab2, mTab3, mTab5, mTab6, mTab1, mTab4));
List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab1, mTab4));
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab5, false, false);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab1, TAB5_ROOT_ID);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab4, TAB5_ROOT_ID);
verify(mTabModel).moveTab(mTab1.getId(), POSITION6 + 1);
verify(mTabModel).moveTab(mTab4.getId(), POSITION6 + 1);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab1, mTab5.getId());
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab4, mTab5.getId());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void mergeListOfTabsToGroup_AllForward() {
TabImpl newTab = addTabToTabModel();
List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab4, newTab));
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab4, newTab, mTab2, mTab3, mTab5, mTab6));
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab1, false, false);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab4, TAB1_ROOT_ID);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(newTab, TAB1_ROOT_ID);
verify(mTabModel).moveTab(mTab4.getId(), POSITION1 + 1);
verify(mTabModel).moveTab(newTab.getId(), POSITION1 + 2);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab4, mTab1.getId());
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(newTab, mTab1.getId());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void mergeListOfTabsToGroup_AnyDirection() {
TabImpl newTab = addTabToTabModel();
List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab1, newTab));
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab2, mTab3, mTab4, mTab1, newTab, mTab5, mTab6));
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab4, false, false);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab1, TAB4_ROOT_ID);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(newTab, TAB4_ROOT_ID);
verify(mTabModel).moveTab(mTab1.getId(), POSITION4 + 1);
verify(mTabModel).moveTab(newTab.getId(), POSITION4 + 1);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab1, mTab4.getId());
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(newTab, mTab4.getId());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void merge_OtherGroupsLastShownIdUnchanged() {
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab1, mTab4));
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab4, mTab2, mTab3, mTab5, mTab6));
int startIndex = POSITION1;
// By default, the last shown tab is the first tab in group by order in tab model.
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB2_ROOT_ID),
equalTo(TAB2_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB3_ROOT_ID),
equalTo(TAB2_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB5_ROOT_ID),
equalTo(TAB5_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB6_ROOT_ID),
equalTo(TAB5_ID));
// Specifically select different tabs in (Tab2, Tab3) group and (Tab5, Tab6) group to change
// the last shown ids in respective groups so that it is different from the default setting.
mTabGroupModelFilter.selectTab(mTab3);
mTabGroupModelFilter.selectTab(mTab6);
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB2_ROOT_ID),
equalTo(TAB3_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB3_ROOT_ID),
equalTo(TAB3_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB5_ROOT_ID),
equalTo(TAB6_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB6_ROOT_ID),
equalTo(TAB6_ID));
mTabGroupModelFilter.mergeTabsToGroup(mTab4.getId(), mTab1.getId());
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab4, TAB1_ROOT_ID);
verify(mTabModel).moveTab(mTab4.getId(), ++startIndex);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab4, mTab1.getId());
assertArrayEquals(mTabGroupModelFilter.getRelatedTabList(mTab4.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
// After merge, last shown ids in groups that are unrelated to this merge should remain
// unchanged.
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB2_ROOT_ID),
equalTo(TAB3_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB3_ROOT_ID),
equalTo(TAB3_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB5_ROOT_ID),
equalTo(TAB6_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabIdForTesting(TAB6_ROOT_ID),
equalTo(TAB6_ID));
}
@Test
public void moveGroup_Backward() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab4, mTab2, mTab3, mTab5, mTab6));
int startIndex = POSITION4;
mTabGroupModelFilter.moveRelatedTabs(mTab2.getId(), startIndex + 1);
verify(mTabModel).moveTab(mTab2.getId(), startIndex + 1);
verify(mTabModel).moveTab(mTab3.getId(), startIndex + 1);
verify(mTabGroupModelFilterObserver).didMoveTabGroup(mTab3, POSITION3 - 1, startIndex);
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void moveGroup_Forward() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab5, mTab6, mTab4));
int startIndex = POSITION3;
mTabGroupModelFilter.moveRelatedTabs(mTab5.getId(), startIndex + 1);
verify(mTabModel).moveTab(mTab5.getId(), startIndex + 1);
verify(mTabModel).moveTab(mTab6.getId(), startIndex + 2);
verify(mTabGroupModelFilterObserver).didMoveTabGroup(mTab6, POSITION6, startIndex + 2);
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void ignoreUnrelatedMoveTab() {
// Simulate that the tab restoring is not yet finished.
setupTabGroupModelFilter(false, false);
mTabModelObserverCaptor.getValue().didMoveTab(mTab1, POSITION1, POSITION6);
mTabModelObserverCaptor.getValue().didMoveTab(mTab1, POSITION6, POSITION1);
mTabModelObserverCaptor.getValue().didMoveTab(mTab2, POSITION2, POSITION5);
mTabModelObserverCaptor.getValue().didMoveTab(mTab2, POSITION5, POSITION2);
// No call should be made here.
verify(mTabGroupModelFilterObserver, never())
.didMoveTabOutOfGroup(any(Tab.class), anyInt());
verify(mTabGroupModelFilterObserver, never()).didMergeTabToGroup(any(Tab.class), anyInt());
verify(mTabGroupModelFilterObserver, never())
.didMoveWithinGroup(any(Tab.class), anyInt(), anyInt());
verify(mTabGroupModelFilterObserver, never())
.didMoveTabGroup(any(Tab.class), anyInt(), anyInt());
}
@Test
public void undoGroupedTab_NoUpdateTabModel() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
// Simulate we just grouped mTab4 with mTab2 and mTab3
doReturn(TAB2_ROOT_ID).when(mTab4).getRootId();
mTabGroupModelFilter.resetFilterState();
assertThat(mTab4.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(1));
// Undo the grouped action
mTabGroupModelFilter.undoGroupedTab(mTab4, POSITION4, TAB4_ROOT_ID);
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab4.getRootId(), equalTo(TAB4_ROOT_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(2));
}
@Test
public void undoGroupedTab_Forward_UpdateTabModel() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
// Simulate we just grouped mTab1 with mTab4
doReturn(TAB4_ROOT_ID).when(mTab1).getRootId();
mTabModel.moveTab(mTab1.getId(), POSITION4 + 1);
mTabGroupModelFilter.resetFilterState();
assertThat(mTab1.getRootId(), equalTo(TAB4_ROOT_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab1), equalTo(1));
assertFalse(Arrays.equals(mTabs.toArray(), expectedTabModel.toArray()));
// Undo the grouped action.
mTabGroupModelFilter.undoGroupedTab(mTab1, POSITION1, TAB1_ROOT_ID);
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab1.getRootId(), equalTo(TAB1_ROOT_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab1), equalTo(0));
}
@Test
public void undoGroupedTab_Backward_UpdateTabModel() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
// Simulate we just grouped mTab4 with mTab1
doReturn(TAB1_ROOT_ID).when(mTab4).getRootId();
mTabModel.moveTab(mTab4.getId(), POSITION1 + 1);
mTabGroupModelFilter.resetFilterState();
assertThat(mTab4.getRootId(), equalTo(TAB1_ROOT_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(0));
assertFalse(Arrays.equals(mTabs.toArray(), expectedTabModel.toArray()));
// Undo the grouped action.
mTabGroupModelFilter.undoGroupedTab(mTab4, POSITION4, TAB4_ROOT_ID);
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab4.getRootId(), equalTo(TAB4_ROOT_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(2));
}
@Test(expected = AssertionError.class)
public void undoGroupedTab_AssertTest() {
// Simulate mTab6 is not in TabModel.
doReturn(5).when(mTabModel).getCount();
// Undo the grouped action.
mTabGroupModelFilter.undoGroupedTab(mTab6, POSITION1, TAB1_ROOT_ID);
}
@Test
public void skipRestoringStageMoveTab_Incognito() {
// Simulate tab model is in incognito mode, and thus tab restoring will not happen.
// Therefore, we should not ignore didMoveTab calls because we have already skipped the
// didMoveTab calls from restoring stage.
setupTabGroupModelFilter(false, true);
// Simulate that tab3 is going to be moved out of group.
mTab3.setRootId(TAB3_ID);
mTabModelObserverCaptor.getValue().didMoveTab(mTab3, POSITION3, POSITION6);
// Verify that the signal is not ignored.
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab3, POSITION2);
}
@Test
public void getNonGroupedTab() {
Tab[] expectedNonGroupedTabs = new Tab[]{mTab1, mTab4};
List<Tab> nonGroupedTabs = mTabGroupModelFilter.getTabsWithNoOtherRelatedTabs();
assertArrayEquals(expectedNonGroupedTabs, nonGroupedTabs.toArray());
// Group mTab4 with mTab2 and mTab3
mTabGroupModelFilter.mergeTabsToGroup(mTab4.getId(), mTab2.getId());
Tab[] expectedNonGroupedTabs_AfterGrouping = new Tab[]{mTab1};
List<Tab> nonGroupedTabs_AfterGrouping =
mTabGroupModelFilter.getTabsWithNoOtherRelatedTabs();
assertArrayEquals(expectedNonGroupedTabs_AfterGrouping,
nonGroupedTabs_AfterGrouping.toArray());
// UnGroup mTab3 for its group.
mTabGroupModelFilter.moveTabOutOfGroup(TAB3_ID);
Tab[] expectedNonGroupedTabs_AfterUnGrouping = new Tab[]{mTab1, mTab3};
List<Tab> nonGroupedTabs_AfterUnGrouping =
mTabGroupModelFilter.getTabsWithNoOtherRelatedTabs();
assertArrayEquals(expectedNonGroupedTabs_AfterUnGrouping,
nonGroupedTabs_AfterUnGrouping.toArray());
}
@Test
public void resetFilterStateTest() {
assertThat(mTab3.getRootId(), equalTo(TAB2_ROOT_ID));
mTab3.setRootId(TAB1_ROOT_ID);
mTabGroupModelFilter.resetFilterState();
assertThat(mTab3.getRootId(), equalTo(TAB1_ROOT_ID));
}
@Test
public void testGetRelatedTabListForRootId() {
Tab[] group1 = new Tab[] {mTab2, mTab3};
Tab[] group2 = new Tab[] {mTab5, mTab6};
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabListForRootId(TAB2_ROOT_ID).toArray(), group1);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabListForRootId(TAB3_ROOT_ID).toArray(), group1);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabListForRootId(TAB5_ROOT_ID).toArray(), group2);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabListForRootId(TAB6_ROOT_ID).toArray(), group2);
}
@Test
public void testIndexOfAnUndoableClosedTabNotCrashing() {
mTabGroupModelFilter.closeTab(mTab1);
mTabGroupModelFilter.indexOf(mTab1);
}
}