// Copyright 2019 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_groups;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
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.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.chromium.ui.test.util.MockitoHelper.doFunction;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.Nullable;
import androidx.collection.ArraySet;
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.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.ContextUtils;
import org.chromium.base.ObserverList;
import org.chromium.base.Token;
import org.chromium.base.TokenJni;
import org.chromium.base.UserDataHost;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
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.tab.TabCreationState;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.TabStateAttributes;
import org.chromium.chrome.browser.tab.TabStateAttributes.DirtinessState;
import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeatures;
import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeaturesJni;
import org.chromium.chrome.browser.tabmodel.TabClosureParams;
import org.chromium.chrome.browser.tabmodel.TabList;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver.DidRemoveTabGroupReason;
import org.chromium.components.tab_groups.TabGroupColorId;
import org.chromium.ui.test.util.MockitoHelper;
import org.chromium.url.GURL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/** Tests for {@link TabGroupModelFilter}. */
@SuppressWarnings("ResultOfMethodCallIgnored")
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@EnableFeatures({
ChromeFeatureList.TAB_GROUP_PARITY_ANDROID,
ChromeFeatureList.TAB_STRIP_GROUP_COLLAPSE
})
public class TabGroupModelFilterUnitTest {
private static final int TAB1_ID = 11;
private static final int TAB2_ID = 12;
private static final int TAB3_ID = 13;
private static final int TAB4_ID = 14;
private static final int TAB5_ID = 15;
private static final int TAB6_ID = 16;
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 Token TAB1_TAB_GROUP_ID = null;
private static final Token TAB2_TAB_GROUP_ID = new Token(2L, 2L);
private static final Token TAB3_TAB_GROUP_ID = TAB2_TAB_GROUP_ID;
private static final Token TAB4_TAB_GROUP_ID = null;
private static final Token TAB5_TAB_GROUP_ID = new Token(5L, 2L);
private static final Token TAB6_TAB_GROUP_ID = TAB5_TAB_GROUP_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_0 = 20;
private static final int NEW_TAB_ID_1 = 21;
private static final int NEW_TAB_ID_2 = 22;
private static final String TAB_GROUP_TITLES_FILE_NAME = "tab_group_titles";
private static final String TAB_TITLE = "Tab";
private static final String TAB_GROUP_COLORS_FILE_NAME = "tab_group_colors";
private static final int COLOR_ID = 0;
private static final String TAB_GROUP_SYNC_IDS_FILE_NAME = "tab_group_sync_ids";
private static final String TAB_GROUP_COLLAPSED_FILE_NAME = "tab_group_collapsed";
@Rule public JniMocker mJniMocker = new JniMocker();
@Mock Profile mProfile;
@Mock Token.Natives mTokenJniMock;
@Mock TabGroupSyncFeatures.Natives mTabGroupSyncFeaturesJniMock;
@Mock TabModel mTabModel;
@Mock TabList mComprehensiveModel;
@Mock TabGroupModelFilterObserver mTabGroupModelFilterObserver;
@Mock Context mContext;
@Mock SharedPreferences mSharedPreferencesTitle;
@Mock SharedPreferences mSharedPreferencesColor;
@Mock SharedPreferences mSharedPreferencesSyncId;
@Mock SharedPreferences mSharedPreferencesCollapsed;
@Mock SharedPreferences.Editor mEditor;
@Mock TabStateAttributes.Observer mAttributesObserver;
@Captor ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
private Tab mTab1;
private Tab mTab2;
private Tab mTab3;
private Tab mTab4;
private Tab mTab5;
private Tab mTab6;
private List<Tab> mTabs = new ArrayList<>();
private TabGroupModelFilter mTabGroupModelFilter;
private InOrder mTabModelInOrder;
private InOrder mModelAndObserverInOrder;
private Tab prepareTab(int tabId, int rootId, @Nullable Token tabGroupId, int parentTabId) {
Tab tab = mock(Tab.class, "Tab " + tabId);
doReturn(true).when(tab).isInitialized();
doReturn(tabId).when(tab).getId();
ObserverList<TabObserver> tabObserverList = new ObserverList<>();
MockitoHelper.doCallback((TabObserver obs) -> tabObserverList.addObserver(obs))
.when(tab)
.addObserver(any());
MockitoHelper.doCallback((TabObserver obs) -> tabObserverList.removeObserver(obs))
.when(tab)
.removeObserver(any());
doAnswer(
invocation -> {
int newRootId = invocation.getArgument(0);
when(tab.getRootId()).thenReturn(newRootId);
for (TabObserver observer : tabObserverList) {
observer.onRootIdChanged(tab, newRootId);
}
return null;
})
.when(tab)
.setRootId(anyInt());
tab.setRootId(rootId);
doAnswer(
invocation -> {
Token newTabGroupId = invocation.getArgument(0);
when(tab.getTabGroupId()).thenReturn(newTabGroupId);
for (TabObserver observer : tabObserverList) {
observer.onTabGroupIdChanged(tab, newTabGroupId);
}
return null;
})
.when(tab)
.setTabGroupId(any());
tab.setTabGroupId(tabGroupId);
doReturn(parentTabId).when(tab).getParentId();
when(tab.getUrl()).thenReturn(GURL.emptyGURL());
when(tab.getUserDataHost()).thenReturn(new UserDataHost());
TabStateAttributes.createForTab(tab, TabCreationState.LIVE_IN_FOREGROUND);
TabStateAttributes.from(tab).addObserver(mAttributesObserver);
return tab;
}
private void setUpTab() {
mTab1 = prepareTab(TAB1_ID, TAB1_ROOT_ID, TAB1_TAB_GROUP_ID, TAB1_PARENT_TAB_ID);
mTab2 = prepareTab(TAB2_ID, TAB2_ROOT_ID, TAB2_TAB_GROUP_ID, TAB2_PARENT_TAB_ID);
mTab3 = prepareTab(TAB3_ID, TAB3_ROOT_ID, TAB3_TAB_GROUP_ID, TAB3_PARENT_TAB_ID);
mTab4 = prepareTab(TAB4_ID, TAB4_ROOT_ID, TAB4_TAB_GROUP_ID, TAB4_PARENT_TAB_ID);
mTab5 = prepareTab(TAB5_ID, TAB5_ROOT_ID, TAB5_TAB_GROUP_ID, TAB5_PARENT_TAB_ID);
mTab6 = prepareTab(TAB6_ID, TAB6_ROOT_ID, TAB6_TAB_GROUP_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);
// Mirror behavior of real tab model here.
if (oldIndex == newIndex || oldIndex + 1 == newIndex) return null;
Tab tab = mTabModel.getTabById(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());
doFunction(mTabs::get).when(mTabModel).getTabAt(anyInt());
doAnswer(
invocation -> {
int tabId = invocation.getArgument(0);
return mTabs.stream()
.filter(t -> t.getId() == tabId)
.findAny()
.orElse(null);
})
.when(mTabModel)
.getTabById(anyInt());
doFunction(mTabs::indexOf).when(mTabModel).indexOf(any(Tab.class));
doAnswer(invocation -> mTabs.size()).when(mTabModel).getCount();
doReturn(0).when(mTabModel).index();
doNothing().when(mTabModel).addObserver(mTabModelObserverCaptor.capture());
doReturn(mComprehensiveModel).when(mTabModel).getComprehensiveModel();
doAnswer(invocation -> mTabs.size()).when(mComprehensiveModel).getCount();
doFunction(mTabs::get).when(mComprehensiveModel).getTabAt(anyInt());
mTabModelInOrder = inOrder(mTabModel);
}
private Tab addTabToTabModel() {
return addTabToTabModel(-1, null);
}
private Tab addTabToTabModel(int index, @Nullable Tab tab) {
if (tab == null) tab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, 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,
false);
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,
false);
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,
false);
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,
false);
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,
false);
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,
false);
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,
false);
if (isTabRestoreCompleted) {
mTabGroupModelFilter.restoreCompleted();
assertTrue(mTabGroupModelFilter.isTabModelRestored());
}
doReturn(mSharedPreferencesTitle)
.when(mContext)
.getSharedPreferences(TAB_GROUP_TITLES_FILE_NAME, Context.MODE_PRIVATE);
doReturn(mSharedPreferencesColor)
.when(mContext)
.getSharedPreferences(TAB_GROUP_COLORS_FILE_NAME, Context.MODE_PRIVATE);
doReturn(mSharedPreferencesSyncId)
.when(mContext)
.getSharedPreferences(TAB_GROUP_SYNC_IDS_FILE_NAME, Context.MODE_PRIVATE);
doReturn(mSharedPreferencesCollapsed)
.when(mContext)
.getSharedPreferences(TAB_GROUP_COLLAPSED_FILE_NAME, Context.MODE_PRIVATE);
ContextUtils.initApplicationContextForTests(mContext);
when(mSharedPreferencesTitle.getString(anyString(), any())).thenReturn(TAB_TITLE);
when(mSharedPreferencesColor.getInt(anyString(), anyInt()))
.thenReturn(TabGroupColorUtils.INVALID_COLOR_ID);
when(mSharedPreferencesCollapsed.getBoolean(anyString(), anyBoolean())).thenReturn(true);
when(mSharedPreferencesTitle.edit()).thenReturn(mEditor);
when(mSharedPreferencesColor.edit()).thenReturn(mEditor);
when(mSharedPreferencesSyncId.edit()).thenReturn(mEditor);
when(mSharedPreferencesCollapsed.edit()).thenReturn(mEditor);
when(mEditor.putString(anyString(), anyString())).thenReturn(mEditor);
when(mEditor.putInt(anyString(), anyInt())).thenReturn(mEditor);
when(mEditor.putBoolean(anyString(), anyBoolean())).thenReturn(mEditor);
when(mEditor.remove(anyString())).thenReturn(mEditor);
mModelAndObserverInOrder = inOrder(mTabModel, mTabGroupModelFilterObserver);
}
@Before
public void setUp() {
TabGroupModelFilter.SKIP_TAB_GROUP_CREATION_DIALOG.setForTesting(false);
// 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.
MockitoAnnotations.initMocks(this);
mJniMocker.mock(TokenJni.TEST_HOOKS, mTokenJniMock);
mJniMocker.mock(TabGroupSyncFeaturesJni.TEST_HOOKS, mTabGroupSyncFeaturesJniMock);
when(mProfile.isNativeInitialized()).thenReturn(true);
when(mTabModel.getProfile()).thenReturn(mProfile);
setUpTab();
setUpTabModel();
setupTabGroupModelFilter(true, false);
}
@Test
public void setIncognito() {
setupTabGroupModelFilter(true, false);
setupTabGroupModelFilter(false, true);
assertThat(mTabGroupModelFilter.isIncognito(), equalTo(true));
assertThat(mTabModel.getCount(), equalTo(6));
}
@Test
public void addTab_ToExistingGroupSingleTab() {
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, TAB1_ID);
doReturn(TabLaunchType.FROM_TAB_GROUP_UI).when(newTab).getLaunchType();
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
Token tabGroupId = new Token(93L, 42L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
assertEquals(mTab1.getTabGroupId(), tabGroupId);
assertEquals(mTab1.getTabGroupId(), newTab.getTabGroupId());
assertThat(
mTabGroupModelFilter.indexOf(newTab), equalTo(mTabGroupModelFilter.indexOf(mTab1)));
}
@Test
public void addTab_ToExistingGroupedTab() {
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, TAB2_ID);
doReturn(TabLaunchType.FROM_TAB_GROUP_UI).when(newTab).getLaunchType();
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
assertEquals(mTab2.getTabGroupId(), TAB2_TAB_GROUP_ID);
assertEquals(mTab2.getTabGroupId(), newTab.getTabGroupId());
assertThat(
mTabGroupModelFilter.indexOf(newTab), equalTo(mTabGroupModelFilter.indexOf(mTab2)));
}
@Test
public void addTab_ToNewGroup() {
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, 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));
assertNull(newTab.getTabGroupId());
}
@Test
public void addTab_ToNewGroup_NotAtEnd() {
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, Tab.INVALID_TAB_ID);
doReturn(TabLaunchType.FROM_CHROME_UI).when(newTab).getLaunchType();
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
assertThat(mTabGroupModelFilter.getCount(), equalTo(4));
// Add a tab to the model not at the end and ensure the indexes are updated correctly for
// all other tabs and groups.
addTabToTabModel(1, newTab);
assertNull(newTab.getTabGroupId());
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
assertThat(mTabGroupModelFilter.getCount(), equalTo(5));
assertThat(mTabGroupModelFilter.getTotalTabCount(), equalTo(7));
assertThat(mTabGroupModelFilter.indexOf(mTab1), equalTo(0));
assertThat(mTabGroupModelFilter.indexOf(newTab), equalTo(1));
assertThat(mTabGroupModelFilter.indexOf(mTab2), equalTo(2));
assertThat(mTabGroupModelFilter.indexOf(mTab3), equalTo(2));
assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(3));
assertThat(mTabGroupModelFilter.indexOf(mTab5), equalTo(4));
assertThat(mTabGroupModelFilter.indexOf(mTab6), equalTo(4));
}
@Test
public void addTab_SetRootIdAndTabGroupId() {
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, TAB1_ID);
doReturn(TabLaunchType.FROM_TAB_GROUP_UI).when(newTab).getLaunchType();
Token tabGroupId = new Token(93L, 42L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(newTab.getRootId(), equalTo(TAB1_ROOT_ID));
assertEquals(mTab1.getTabGroupId(), tabGroupId);
assertEquals(mTab1.getTabGroupId(), newTab.getTabGroupId());
}
@Test
public void rootIdToStableIdAndBackConversion() {
// Test existing IDs.
assertEquals(TAB2_ROOT_ID, mTabGroupModelFilter.getRootIdFromStableId(TAB2_TAB_GROUP_ID));
assertEquals(TAB2_TAB_GROUP_ID, mTabGroupModelFilter.getStableIdFromRootId(TAB2_ROOT_ID));
assertEquals(null, mTabGroupModelFilter.getStableIdFromRootId(TAB1_ROOT_ID));
// Test non-existing IDs.
assertEquals(
Tab.INVALID_TAB_ID,
mTabGroupModelFilter.getRootIdFromStableId(new Token(93L, 42L)));
assertEquals(null, mTabGroupModelFilter.getStableIdFromRootId(1000));
}
@Test
public void addTab_TabLaunchedFromTabGroupUi() {
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, TAB1_ID);
doReturn(TabLaunchType.FROM_TAB_GROUP_UI).when(newTab).getLaunchType();
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
Token tabGroupId = new Token(93L, 42L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(newTab.getRootId(), equalTo(TAB1_ROOT_ID));
assertEquals(mTab1.getTabGroupId(), tabGroupId);
assertEquals(mTab1.getTabGroupId(), newTab.getTabGroupId());
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
}
@Test
public void addTab_TabLaunchedFromLongPressBackgroundInGroup() {
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, TAB1_ID);
doReturn(TabLaunchType.FROM_LONGPRESS_BACKGROUND_IN_GROUP).when(newTab).getLaunchType();
assertNull(mTab1.getTabGroupId());
assertNull(newTab.getTabGroupId());
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
Token tabGroupId = new Token(93L, 42L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(newTab.getRootId(), equalTo(TAB1_ROOT_ID));
assertEquals(mTab1.getTabGroupId(), tabGroupId);
assertEquals(mTab1.getTabGroupId(), newTab.getTabGroupId());
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(newTab, TAB1_ROOT_ID);
verify(mTabGroupModelFilterObserver).didCreateNewGroup(newTab, mTabGroupModelFilter);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
}
@Test
public void addTab_TabLaunchedFromLongPressBackgroundInGroup_NotRestoredToGroupOnUndo() {
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, TAB1_ID);
doReturn(TabLaunchType.FROM_LONGPRESS_BACKGROUND_IN_GROUP).when(newTab).getLaunchType();
assertNull(mTab1.getTabGroupId());
assertNull(newTab.getTabGroupId());
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
Token tabGroupId = new Token(93L, 42L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
// Create a new tab in the tab group via launch type.
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(newTab.getRootId(), equalTo(TAB1_ROOT_ID));
assertEquals(mTab1.getTabGroupId(), tabGroupId);
assertEquals(mTab1.getTabGroupId(), newTab.getTabGroupId());
assertTrue(mTabGroupModelFilter.isTabInTabGroup(newTab));
verify(mTabGroupModelFilterObserver).didCreateNewGroup(newTab, mTabGroupModelFilter);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
// Move the new tab out of the tab group.
mTabGroupModelFilter.moveTabOutOfGroupInDirection(newTab.getId(), /* trailing= */ true);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(newTab, POSITION1);
assertFalse(mTabGroupModelFilter.isTabInTabGroup(newTab));
assertThat(newTab.getRootId(), equalTo(NEW_TAB_ID_0));
assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab1));
// Start to close the new tab.
mTabGroupModelFilter.willCloseTab(newTab, /* didCloseAlone= */ true);
// Undo the closure.
mTabGroupModelFilter.tabClosureUndone(newTab);
// Assert on undo the new tab is not re-added to the tab group it was originally in.
assertThat(newTab.getRootId(), equalTo(NEW_TAB_ID_0));
assertFalse(mTabGroupModelFilter.isTabInTabGroup(newTab));
assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab1));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void addTab_TabLaunchedFromLongPressBackgroundInGroupToExistingGroup() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, TAB1_ID);
doReturn(TabLaunchType.FROM_LONGPRESS_BACKGROUND_IN_GROUP).when(newTab).getLaunchType();
Token tabGroupId = new Token(93L, 42L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
mTabGroupModelFilter.createSingleTabGroup(mTab1, true);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab1, mTabGroupModelFilter);
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(newTab.getRootId(), equalTo(TAB1_ROOT_ID));
assertEquals(mTab1.getTabGroupId(), tabGroupId);
assertEquals(mTab1.getTabGroupId(), newTab.getTabGroupId());
// Verify this isn't called a second time.
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab1, mTabGroupModelFilter);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
}
@Test
public void addTab_TabLaunchedFromChromeUi() {
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, TAB1_ID);
doReturn(TabLaunchType.FROM_CHROME_UI).when(newTab).getLaunchType();
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(newTab.getRootId(), equalTo(NEW_TAB_ID_0));
assertNull(newTab.getTabGroupId());
}
@Test
public void addTab_DuringRestore() {
setupTabGroupModelFilter(false, false);
assertFalse(mTabGroupModelFilter.isTabModelRestored());
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, TAB1_ID);
doReturn(TabLaunchType.FROM_RESTORE).when(newTab).getLaunchType();
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(newTab.getRootId(), equalTo(NEW_TAB_ID_0));
assertNull(newTab.getTabGroupId());
}
@Test
public void addTab_ThemeChangeReparenting() {
// When tab is added due to theme change reparenting, their launch type remains unchanged.
setupTabGroupModelFilter(false, false);
assertFalse(mTabGroupModelFilter.isTabModelRestored());
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, TAB1_ID);
doReturn(TabLaunchType.FROM_LONGPRESS_BACKGROUND).when(newTab).getLaunchType();
addTabToTabModel(POSITION1 + 1, newTab);
assertThat(newTab.getRootId(), equalTo(NEW_TAB_ID_0));
assertNull(newTab.getTabGroupId());
}
@Test
public void addTab_DuringResettingFilterState() {
mTabGroupModelFilter.resetFilterState();
verify(mock(Tab.class), never()).setRootId(anyInt());
verify(mock(Tab.class), never()).setTabGroupId(any());
}
@Test(expected = IllegalStateException.class)
public void addTab_ToWrongModel() {
Tab newTab = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, Tab.INVALID_TAB_ID);
addTabToTabModel(-1, newTab);
doReturn(false).when(mTabModel).isIncognito();
doReturn(true).when(newTab).isIncognito();
mTabGroupModelFilter.addTab(newTab, /* fromUndo= */ false);
}
@Test
public void isTabInTabGroup() {
assertFalse(mTabGroupModelFilter.isTabInTabGroup(mTab1));
assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab2));
assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab3));
assertFalse(mTabGroupModelFilter.isTabInTabGroup(mTab4));
assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab5));
assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab6));
}
@Test
public void testGroupMembershipOfTabAfterClose() {
assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab2));
assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab3));
assertEquals(TAB2_ID, mTab2.getRootId());
assertEquals(TAB2_ID, mTab3.getRootId());
mTabGroupModelFilter.closeTab(mTab2);
assertFalse(mTabGroupModelFilter.isTabInTabGroup(mTab2));
assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab3));
assertEquals(TAB3_ID, mTab2.getRootId());
assertEquals(TAB3_ID, mTab3.getRootId());
verify(mTabGroupModelFilterObserver).didChangeGroupRootId(TAB2_ID, TAB3_ID);
}
@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));
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB5_TAB_GROUP_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));
assertEquals(mTab2.getTabGroupId(), TAB2_TAB_GROUP_ID);
assertNull(mTab3.getTabGroupId());
assertEquals(mTab5.getTabGroupId(), TAB5_TAB_GROUP_ID);
assertNull(mTab6.getTabGroupId());
}
@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));
assertEquals(mTab2.getTabGroupId(), TAB2_TAB_GROUP_ID);
assertEquals(mTab3.getTabGroupId(), TAB2_TAB_GROUP_ID);
assertEquals(mTab5.getTabGroupId(), TAB5_TAB_GROUP_ID);
assertEquals(mTab6.getTabGroupId(), TAB5_TAB_GROUP_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));
assertNull(mTab2.getTabGroupId());
assertEquals(mTab3.getTabGroupId(), TAB2_TAB_GROUP_ID);
assertNull(mTab5.getTabGroupId());
assertEquals(mTab6.getTabGroupId(), TAB5_TAB_GROUP_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));
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_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));
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertNull(mTab3.getTabGroupId());
assertArrayEquals(mTabs.toArray(), expectedTabModelAfterUngroup.toArray());
}
@Test
public void moveTabOutOfGroup_NonRootTab_NotFirstTab_UpdateTabModel() {
Tab newTab = prepareTab(NEW_TAB_ID_0, TAB2_ROOT_ID, TAB2_TAB_GROUP_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_ROOT_ID));
assertThat(mTab3.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(newTab.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(newTab.getTabGroupId(), equalTo(TAB2_TAB_GROUP_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_ROOT_ID));
assertThat(newTab.getRootId(), equalTo(TAB2_ROOT_ID));
assertNull(mTab3.getTabGroupId());
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(newTab.getTabGroupId(), equalTo(TAB2_TAB_GROUP_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));
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_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));
assertNull(mTab2.getTabGroupId());
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelAfterUngroup.toArray());
}
@Test
public void moveTabOutOfGroup_RootTab_NotFirstTab_UpdateTabModel() {
Tab newTab = prepareTab(NEW_TAB_ID_0, TAB2_ROOT_ID, TAB2_TAB_GROUP_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));
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(newTab.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
mTabGroupModelFilter.moveTabOutOfGroup(TAB2_ID);
verify(mTabGroupModelFilterObserver).willMoveTabOutOfGroup(mTab2, NEW_TAB_ID_0);
// 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));
assertNull(mTab2.getTabGroupId());
// NEW_TAB_ID_0 becomes the new root id for {Tab3, newTab} group.
assertThat(mTab3.getRootId(), equalTo(NEW_TAB_ID_0));
assertThat(mTab3.getRootId(), equalTo(NEW_TAB_ID_0));
// The TabGroupId is stable and does not change.
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(newTab.getTabGroupId(), equalTo(TAB2_TAB_GROUP_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());
Token tabGroupId = new Token(374893L, 83942L);
mTab1.setTabGroupId(tabGroupId);
assertNotNull(mTab1.getTabGroupId());
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);
verify(mTabGroupModelFilterObserver)
.didRemoveTabGroup(mTab1.getRootId(), tabGroupId, DidRemoveTabGroupReason.UNGROUP);
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
assertNull(mTab1.getTabGroupId());
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void moveTabOutOfGroup_LastTab_WithTabGroupId() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
List<Tab> expectedTabModelBeforeUngroup =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
Token tabGroupId = new Token(374893L, 83942L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
mTabGroupModelFilter.createSingleTabGroup(mTab1, true);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
assertEquals(mTab1.getTabGroupId(), tabGroupId);
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab1, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab1, mTab1.getId());
verify(mTabGroupModelFilterObserver, never())
.didCreateGroup(
anyList(),
anyList(),
anyList(),
anyList(),
anyString(),
anyInt(),
anyBoolean());
assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab1));
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).willMoveTabOutOfGroup(mTab1, TAB1_ROOT_ID);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab1, POSITION1);
verify(mTabGroupModelFilterObserver)
.didRemoveTabGroup(mTab1.getRootId(), tabGroupId, DidRemoveTabGroupReason.UNGROUP);
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
assertNull(mTab1.getTabGroupId());
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
}
@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.getGroupLastShownTabId(TAB5_ROOT_ID), equalTo(TAB5_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(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.getGroupLastShownTabId(TAB5_ROOT_ID), equalTo(TAB6_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(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));
assertNull(mTab2.getTabGroupId());
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
// After ungroup, last shown ids in groups that are unrelated to this ungroup should remain
// unchanged.
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(TAB5_ROOT_ID), equalTo(TAB6_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(TAB6_ROOT_ID), equalTo(TAB6_ID));
}
@Test
public void moveTabOutOfGroupInDirection_NotTrailing() {
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));
assertArrayEquals(mTabs.toArray(), expectedTabModelBeforeUngroup.toArray());
mTabGroupModelFilter.moveTabOutOfGroupInDirection(TAB3_ID, false);
verify(mTabModel).moveTab(mTab3.getId(), POSITION2);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab3, POSITION3);
verify(mTabGroupModelFilterObserver, never()).didChangeGroupRootId(anyInt(), anyInt());
assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertNull(mTab3.getTabGroupId());
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertArrayEquals(mTabs.toArray(), expectedTabModelAfterUngroup.toArray());
verify(mAttributesObserver).onTabStateDirtinessChanged(mTab3, DirtinessState.DIRTY);
verifyNoMoreInteractions(mAttributesObserver);
}
@Test
public void moveTabOutOfGroupInDirection_NewRootId() {
assertEquals(TAB2_ID, mTab2.getRootId());
assertEquals(TAB2_ID, mTab3.getRootId());
mTabGroupModelFilter.moveTabOutOfGroupInDirection(TAB2_ID, false);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab2, POSITION3);
verify(mTabGroupModelFilterObserver).didChangeGroupRootId(TAB2_ID, TAB3_ID);
assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
assertNull(mTab2.getTabGroupId());
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
verify(mAttributesObserver).onTabStateDirtinessChanged(mTab2, DirtinessState.DIRTY);
verify(mAttributesObserver).onTabStateDirtinessChanged(mTab3, DirtinessState.DIRTY);
verifyNoMoreInteractions(mAttributesObserver);
}
@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());
verify(mTabGroupModelFilterObserver, never())
.didCreateNewGroup(mTab4, mTabGroupModelFilter);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabList(mTab4.getId()).toArray(),
expectedGroup.toArray());
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab4.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
verify(mAttributesObserver).onTabStateDirtinessChanged(mTab4, DirtinessState.DIRTY);
verifyNoMoreInteractions(mAttributesObserver);
}
@Test
public void mergeTabToGroup_UpdateTabModel() {
mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId());
verify(mTabModel).moveTab(mTab5.getId(), POSITION3 + 1);
verify(mTabGroupModelFilterObserver, never())
.didCreateNewGroup(mTab5, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver)
.didRemoveTabGroup(mTab5.getId(), TAB5_TAB_GROUP_ID, DidRemoveTabGroupReason.MERGE);
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
}
@Test
public void mergeTabToGroup_SkipUpdateTabModel() {
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab2, mTab3, mTab5, mTab6));
mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId(), true);
verify(mTabModel, never()).moveTab(anyInt(), anyInt());
verify(mTabGroupModelFilterObserver, never())
.didCreateNewGroup(mTab5, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver)
.didRemoveTabGroup(mTab5.getId(), TAB5_TAB_GROUP_ID, DidRemoveTabGroupReason.MERGE);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabList(mTab5.getId()).toArray(),
expectedGroup.toArray());
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void mergeOneTabToTab_Forward() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
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;
Token tabGroupId = new Token(38294L, 2191L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
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());
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab1, mTabGroupModelFilter);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabList(mTab4.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab1.getTabGroupId(), equalTo(tabGroupId));
assertThat(mTab4.getTabGroupId(), equalTo(tabGroupId));
}
@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(mTab5, TAB1_ROOT_ID);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab6, TAB1_ROOT_ID);
verify(mTabModel).moveTab(mTab5.getId(), ++startIndex);
verify(mTabModel).moveTab(mTab6.getId(), ++startIndex);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab5, TAB1_ROOT_ID);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab6, TAB1_ROOT_ID);
verify(mTabGroupModelFilterObserver, never())
.didCreateNewGroup(mTab6, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver)
.didRemoveTabGroup(TAB5_ROOT_ID, null, DidRemoveTabGroupReason.MERGE);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabList(mTab5.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab1.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
}
@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(mTab5, TAB2_ROOT_ID);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab6, TAB2_ROOT_ID);
verify(mTabModel).moveTab(mTab5.getId(), ++startIndex);
verify(mTabModel).moveTab(mTab6.getId(), ++startIndex);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab5, mTab2.getId());
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab6, mTab2.getId());
verify(mTabGroupModelFilterObserver, never())
.didCreateNewGroup(mTab6, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver)
.didRemoveTabGroup(mTab5.getId(), TAB5_TAB_GROUP_ID, DidRemoveTabGroupReason.MERGE);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabList(mTab5.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void mergeOneTabToTab_Backward() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
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;
Token tabGroupId = new Token(94321L, 7328L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
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());
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab4, mTabGroupModelFilter);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabList(mTab1.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab1.getTabGroupId(), equalTo(tabGroupId));
assertThat(mTab4.getTabGroupId(), equalTo(tabGroupId));
}
@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(mTab2, TAB4_ROOT_ID);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(mTab3, TAB4_ROOT_ID);
verify(mTabModel).moveTab(mTab2.getId(), startIndex + 1);
verify(mTabModel).moveTab(mTab3.getId(), startIndex + 1);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab2, mTab4.getId());
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab3, mTab4.getId());
verify(mTabGroupModelFilterObserver, never())
.didCreateNewGroup(mTab2, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver)
.didRemoveTabGroup(mTab2.getId(), TAB4_TAB_GROUP_ID, DidRemoveTabGroupReason.MERGE);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabList(mTab2.getId()).toArray(),
expectedGroup.toArray());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab4.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
}
@Test
public void mergeTabsToGroup_Collapsed() {
mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId());
verify(mTabGroupModelFilterObserver)
.didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(true));
}
@Test
public void mergeTabsToGroup_SourceExpanded() {
when(mSharedPreferencesCollapsed.getBoolean(eq(String.valueOf(TAB5_ROOT_ID)), anyBoolean()))
.thenReturn(false);
mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId());
verify(mTabGroupModelFilterObserver)
.didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(true));
}
@Test
public void mergeTabsToGroup_DestinationExpanded() {
when(mSharedPreferencesCollapsed.getBoolean(eq(String.valueOf(TAB2_ROOT_ID)), anyBoolean()))
.thenReturn(false);
mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId());
verify(mTabGroupModelFilterObserver)
.didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(false));
}
@Test
@DisableFeatures(ChromeFeatureList.TAB_STRIP_GROUP_COLLAPSE)
public void mergeTabsToGroup_CollapsedWithoutFeature() {
mTabGroupModelFilter.mergeTabsToGroup(mTab5.getId(), mTab2.getId());
verify(mTabGroupModelFilterObserver)
.didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(false));
}
@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);
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());
// Attempt to merge single tabs with group tabs.
verify(mTabGroupModelFilterObserver, never())
.didCreateNewGroup(mTab5, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver, never()).didRemoveTabGroup(anyInt(), any(), anyInt());
assertThat(mTab5.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
assertThat(mTab4.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
verify(mAttributesObserver).onTabStateDirtinessChanged(mTab1, DirtinessState.DIRTY);
verify(mAttributesObserver).onTabStateDirtinessChanged(mTab4, DirtinessState.DIRTY);
verifyNoMoreInteractions(mAttributesObserver);
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void mergeListOfTabsToGroup_AllForward() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
Tab 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));
Token tabGroupId = new Token(123L, 567L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab1, 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());
// Attempt to merge all single tabs, resulting in a new group creation.
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab1, mTabGroupModelFilter);
assertThat(mTab1.getTabGroupId(), equalTo(tabGroupId));
assertThat(mTab4.getTabGroupId(), equalTo(tabGroupId));
assertThat(newTab.getTabGroupId(), equalTo(tabGroupId));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void mergeListOfTabsToGroup_AnyDirection() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
Tab 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));
Token tabGroupId = new Token(1234L, 4567L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab4, 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());
// Attempt to merge all single tabs, resulting in a new group creation.
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab4, mTabGroupModelFilter);
assertThat(mTab1.getTabGroupId(), equalTo(tabGroupId));
assertThat(mTab4.getTabGroupId(), equalTo(tabGroupId));
assertThat(newTab.getTabGroupId(), equalTo(tabGroupId));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void mergeListOfTabsToGroup_InOrder() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
Tab newTab0 = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, Tab.INVALID_TAB_ID);
addTabToTabModel(-1, newTab0);
Tab newTab1 = prepareTab(NEW_TAB_ID_1, NEW_TAB_ID_1, null, Tab.INVALID_TAB_ID);
addTabToTabModel(-1, newTab1);
Tab newTab2 = prepareTab(NEW_TAB_ID_2, NEW_TAB_ID_2, null, Tab.INVALID_TAB_ID);
addTabToTabModel(-1, newTab2);
List<Tab> expectedTabModel =
new ArrayList<>(
Arrays.asList(
mTab1, mTab2, mTab3, mTab4, mTab5, mTab6, newTab0, newTab1,
newTab2));
List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(newTab1, newTab2));
Token tabGroupId = new Token(91234L, 84567L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, newTab0, false);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(newTab1, newTab0.getId());
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(newTab2, newTab0.getId());
verify(mTabModel, never()).moveTab(anyInt(), anyInt());
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(newTab1, newTab0.getId());
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(newTab2, newTab0.getId());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
// Attempt to merge all single tabs, resulting in a new group creation.
verify(mTabGroupModelFilterObserver).didCreateNewGroup(newTab0, mTabGroupModelFilter);
assertThat(newTab0.getTabGroupId(), equalTo(tabGroupId));
assertThat(newTab1.getTabGroupId(), equalTo(tabGroupId));
assertThat(newTab2.getTabGroupId(), equalTo(tabGroupId));
}
@Test
public void mergeListOfTabsToGroup_BackGroup() {
Token tabGroupId = new Token(234L, 342L);
Tab newTab0 = prepareTab(NEW_TAB_ID_0, NEW_TAB_ID_0, null, Tab.INVALID_TAB_ID);
addTabToTabModel(-1, newTab0);
Tab newTab1 = prepareTab(NEW_TAB_ID_1, NEW_TAB_ID_1, tabGroupId, Tab.INVALID_TAB_ID);
addTabToTabModel(-1, newTab1);
Tab newTab2 = prepareTab(NEW_TAB_ID_2, NEW_TAB_ID_1, tabGroupId, Tab.INVALID_TAB_ID);
addTabToTabModel(-1, newTab2);
List<Tab> expectedTabModel =
new ArrayList<>(
Arrays.asList(
mTab1, mTab2, mTab3, mTab4, mTab5, mTab6, newTab1, newTab2,
newTab0));
List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(newTab1, newTab2, newTab0));
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, newTab1, false);
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(newTab1, newTab1.getId());
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(newTab2, newTab1.getId());
verify(mTabGroupModelFilterObserver).willMergeTabToGroup(newTab0, newTab1.getId());
verify(mTabModel).moveTab(newTab0.getId(), 9);
// Skip newTab1
verify(mTabGroupModelFilterObserver).didMoveWithinGroup(newTab2, 8, 8);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(newTab0, newTab1.getId());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
// Attempt to merge a single tab with group tabs.
verify(mTabGroupModelFilterObserver, never())
.didCreateNewGroup(newTab1, mTabGroupModelFilter);
assertThat(newTab0.getTabGroupId(), equalTo(tabGroupId));
assertThat(newTab1.getTabGroupId(), equalTo(tabGroupId));
assertThat(newTab2.getTabGroupId(), equalTo(tabGroupId));
}
@Test
public void mergeListOfTabsToGroup_MultipleGroups() {
List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4));
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab5, false);
verify(mTabGroupModelFilterObserver)
.didRemoveTabGroup(mTab2.getId(), TAB2_TAB_GROUP_ID, DidRemoveTabGroupReason.MERGE);
assertEquals(mTab5.getId(), mTab1.getRootId());
assertEquals(mTab5.getId(), mTab2.getRootId());
assertEquals(mTab5.getId(), mTab3.getRootId());
assertEquals(mTab5.getId(), mTab4.getRootId());
assertEquals(mTab5.getId(), mTab5.getRootId());
assertEquals(mTab5.getId(), mTab6.getRootId());
assertEquals(mTab5.getTabGroupId(), mTab1.getTabGroupId());
assertEquals(mTab5.getTabGroupId(), mTab2.getTabGroupId());
assertEquals(mTab5.getTabGroupId(), mTab3.getTabGroupId());
assertEquals(mTab5.getTabGroupId(), mTab4.getTabGroupId());
assertEquals(mTab5.getTabGroupId(), mTab5.getTabGroupId());
assertEquals(mTab5.getTabGroupId(), mTab6.getTabGroupId());
}
@Test
public void mergeListOfTabsToGroup_Collapsed() {
List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab5, mTab6));
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab4, true);
verify(mTabGroupModelFilterObserver)
.didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(true));
}
@Test
public void mergeListOfTabsToGroup_SourceExpanded() {
when(mSharedPreferencesCollapsed.getBoolean(eq(String.valueOf(TAB5_ROOT_ID)), anyBoolean()))
.thenReturn(false);
List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab5, mTab6));
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab4, true);
verify(mTabGroupModelFilterObserver)
.didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(true));
}
@Test
@DisableFeatures(ChromeFeatureList.TAB_STRIP_GROUP_COLLAPSE)
public void mergeListOfTabsToGroup_CollapsedWithoutFeature() {
List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab5, mTab6));
mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab4, true);
verify(mTabGroupModelFilterObserver)
.didCreateGroup(any(), any(), any(), any(), any(), anyInt(), eq(false));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void merge_OtherGroupsLastShownIdUnchanged() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
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.getGroupLastShownTabId(TAB2_ROOT_ID), equalTo(TAB2_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(TAB3_ROOT_ID), equalTo(TAB2_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(TAB5_ROOT_ID), equalTo(TAB5_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(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.getGroupLastShownTabId(TAB2_ROOT_ID), equalTo(TAB3_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(TAB3_ROOT_ID), equalTo(TAB3_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(TAB5_ROOT_ID), equalTo(TAB6_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(TAB6_ROOT_ID), equalTo(TAB6_ID));
Token tabGroupId = new Token(91234L, 84567L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
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());
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab1, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver, never()).didRemoveTabGroup(anyInt(), any(), anyInt());
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.getGroupLastShownTabId(TAB2_ROOT_ID), equalTo(TAB3_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(TAB3_ROOT_ID), equalTo(TAB3_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(TAB5_ROOT_ID), equalTo(TAB6_ID));
assertThat(mTabGroupModelFilter.getGroupLastShownTabId(TAB6_ROOT_ID), equalTo(TAB6_ID));
assertThat(mTab1.getTabGroupId(), equalTo(tabGroupId));
assertThat(mTab4.getTabGroupId(), equalTo(tabGroupId));
}
@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);
mModelAndObserverInOrder
.verify(mTabGroupModelFilterObserver)
.willMoveTabGroup(POSITION2, startIndex + 1);
mModelAndObserverInOrder.verify(mTabModel).moveTab(mTab2.getId(), startIndex + 1);
mModelAndObserverInOrder.verify(mTabModel).moveTab(mTab3.getId(), startIndex + 1);
mModelAndObserverInOrder
.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);
mModelAndObserverInOrder
.verify(mTabGroupModelFilterObserver)
.willMoveTabGroup(POSITION5, startIndex + 1);
mModelAndObserverInOrder.verify(mTabModel).moveTab(mTab5.getId(), startIndex + 1);
mModelAndObserverInOrder.verify(mTabModel).moveTab(mTab6.getId(), startIndex + 2);
mModelAndObserverInOrder
.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);
assertFalse(mTabGroupModelFilter.isTabModelRestored());
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());
// Ignore any move incognito tabs before TabModel restored.
setupTabGroupModelFilter(false, true);
assertFalse(mTabGroupModelFilter.isTabModelRestored());
mTabModelObserverCaptor.getValue().didMoveTab(mTab1, POSITION1, POSITION6);
// 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
mTab4.setRootId(TAB2_ROOT_ID);
mTab4.setTabGroupId(TAB2_TAB_GROUP_ID);
mTabGroupModelFilter.resetFilterState();
assertThat(mTab4.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(1));
TabStateAttributes.from(mTab4).clearTabStateDirtiness();
reset(mAttributesObserver);
// Undo the grouped action
mTabGroupModelFilter.undoGroupedTab(mTab4, POSITION4, TAB4_ROOT_ID, TAB4_TAB_GROUP_ID);
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab4.getRootId(), equalTo(TAB4_ROOT_ID));
assertNull(mTab4.getTabGroupId());
assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(2));
verify(mAttributesObserver).onTabStateDirtinessChanged(mTab4, DirtinessState.DIRTY);
verifyNoMoreInteractions(mAttributesObserver);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab4, POSITION2);
verify(mTabGroupModelFilterObserver, never()).didMergeTabToGroup(eq(mTab4), anyInt());
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void undoGroupedTab_Forward_UpdateTabModel() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
Token tabGroupId = new Token(91234L, 84567L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
mTabGroupModelFilter.createSingleTabGroup(mTab4, false);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab4, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab4, mTab4.getId());
verify(mTabGroupModelFilterObserver, never())
.didCreateGroup(any(), any(), any(), any(), any(), anyInt(), anyBoolean());
assertThat(mTab4.getTabGroupId(), equalTo(tabGroupId));
// Simulate we just grouped mTab1 with mTab4
mTab1.setRootId(TAB4_ROOT_ID);
mTab1.setTabGroupId(tabGroupId);
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()));
reset(mTabGroupModelFilterObserver);
// Undo the grouped action.
mTabGroupModelFilter.undoGroupedTab(mTab1, POSITION1, TAB1_ROOT_ID, TAB1_TAB_GROUP_ID);
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab1.getRootId(), equalTo(TAB1_ROOT_ID));
assertNull(mTab1.getTabGroupId());
assertThat(mTabGroupModelFilter.indexOf(mTab1), equalTo(0));
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab1, POSITION3);
verify(mTabGroupModelFilterObserver, never()).didMergeTabToGroup(eq(mTab1), anyInt());
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void undoGroupedTab_Backward_UpdateTabModel() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
Token tabGroupId = new Token(91234L, 84567L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(2));
mTabGroupModelFilter.createSingleTabGroup(mTab1, true);
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
assertThat(mTab1.getTabGroupId(), equalTo(tabGroupId));
// Simulate we just grouped mTab4 with mTab1
mTab4.setRootId(TAB1_ROOT_ID);
mTab4.setTabGroupId(tabGroupId);
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()));
reset(mTabGroupModelFilterObserver);
// Undo the grouped action.
mTabGroupModelFilter.undoGroupedTab(mTab4, POSITION4, TAB4_ROOT_ID, TAB4_TAB_GROUP_ID);
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab4.getRootId(), equalTo(TAB4_ROOT_ID));
assertNull(mTab4.getTabGroupId());
assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(2));
assertThat(mTabGroupModelFilter.getTabGroupCount(), equalTo(3));
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab4, POSITION1);
verify(mTabGroupModelFilterObserver, never()).didMergeTabToGroup(eq(mTab4), anyInt());
}
@Test
public void undoGroupedTab_MultipleGroupUndoNoMovement() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
// Simulate we just grouped mTab4 and the group (mTab5, mTab6) with the group (mTab2,
// mTab3).
mTab4.setRootId(TAB2_ROOT_ID);
mTab5.setRootId(TAB2_ROOT_ID);
mTab6.setRootId(TAB2_ROOT_ID);
mTab4.setTabGroupId(TAB2_TAB_GROUP_ID);
mTab5.setTabGroupId(TAB2_TAB_GROUP_ID);
mTab6.setTabGroupId(TAB2_TAB_GROUP_ID);
mTabGroupModelFilter.resetFilterState();
assertThat(mTab4.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTab5.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTab6.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTab4.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(1));
assertThat(mTabGroupModelFilter.indexOf(mTab5), equalTo(1));
assertThat(mTabGroupModelFilter.indexOf(mTab6), equalTo(1));
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
// Undo the grouped action in reverse order so indexes are correct.
mTabGroupModelFilter.undoGroupedTab(mTab6, POSITION6, TAB5_ROOT_ID, TAB5_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab6, POSITION2);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab6, mTab6.getId());
mTabGroupModelFilter.undoGroupedTab(mTab5, POSITION5, TAB5_ROOT_ID, TAB5_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab5, POSITION2);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab5, mTab6.getId());
mTabGroupModelFilter.undoGroupedTab(mTab4, POSITION4, TAB4_ROOT_ID, TAB4_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab4, POSITION2);
verify(mTabGroupModelFilterObserver, never()).didMergeTabToGroup(eq(mTab4), anyInt());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab4.getRootId(), equalTo(TAB4_ROOT_ID));
assertThat(mTab5.getRootId(), equalTo(TAB5_ROOT_ID));
assertThat(mTab6.getRootId(), equalTo(TAB5_ROOT_ID));
assertThat(mTab4.getTabGroupId(), equalTo(TAB4_TAB_GROUP_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab4), equalTo(2));
assertThat(mTabGroupModelFilter.indexOf(mTab5), equalTo(3));
assertThat(mTabGroupModelFilter.indexOf(mTab6), equalTo(3));
}
@Test
public void undoGroupedTabGroup_ToTab() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
// Simulate we just grouped the group (mTab2, mTab3) with mTab1.
mTab1.setRootId(TAB1_ROOT_ID);
mTab2.setRootId(TAB1_ROOT_ID);
mTab3.setRootId(TAB1_ROOT_ID);
mTab1.setTabGroupId(TAB2_TAB_GROUP_ID);
mTab2.setTabGroupId(TAB2_TAB_GROUP_ID);
mTab3.setTabGroupId(TAB2_TAB_GROUP_ID);
mTabGroupModelFilter.resetFilterState();
assertThat(mTab1.getRootId(), equalTo(TAB1_ROOT_ID));
assertThat(mTab2.getRootId(), equalTo(TAB1_ROOT_ID));
assertThat(mTab3.getRootId(), equalTo(TAB1_ROOT_ID));
assertThat(mTab1.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab1), equalTo(0));
assertThat(mTabGroupModelFilter.indexOf(mTab2), equalTo(0));
assertThat(mTabGroupModelFilter.indexOf(mTab3), equalTo(0));
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
// Undo the grouped action in reverse order so indexes are correct.
mTabGroupModelFilter.undoGroupedTab(mTab3, POSITION2, TAB2_ROOT_ID, TAB2_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab3, POSITION1);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab3, mTab3.getId());
mTabGroupModelFilter.undoGroupedTab(mTab2, POSITION2, TAB2_ROOT_ID, TAB2_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab2, POSITION1);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab2, mTab3.getId());
mTabGroupModelFilter.undoGroupedTab(mTab1, POSITION1, TAB1_ROOT_ID, null);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab1, POSITION1);
verify(mTabGroupModelFilterObserver, never()).didMergeTabToGroup(eq(mTab1), anyInt());
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
assertThat(mTab1.getRootId(), equalTo(TAB1_ROOT_ID));
assertThat(mTab2.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTab3.getRootId(), equalTo(TAB2_ROOT_ID));
assertNull(mTab1.getTabGroupId());
assertThat(mTab2.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab3.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab1), equalTo(0));
assertThat(mTabGroupModelFilter.indexOf(mTab2), equalTo(1));
assertThat(mTabGroupModelFilter.indexOf(mTab3), equalTo(1));
}
@Test
public void undoGroupedTab_MultipleGroupUndoWithMovement() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab6, mTab5, mTab4));
// Simulate we just grouped the group (mTab5, mTab6) with the group (mTab2, mTab3).
mTab5.setRootId(TAB2_ROOT_ID);
mTab6.setRootId(TAB2_ROOT_ID);
mTab5.setTabGroupId(TAB2_TAB_GROUP_ID);
mTab6.setTabGroupId(TAB2_TAB_GROUP_ID);
mTabModel.moveTab(mTab5.getId(), POSITION3 + 1);
mTabModel.moveTab(mTab6.getId(), POSITION3 + 1);
mTabGroupModelFilter.resetFilterState();
assertThat(mTab5.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTab6.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab5), equalTo(1));
assertThat(mTabGroupModelFilter.indexOf(mTab6), equalTo(1));
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
// Undo the grouped action in reverse order so indexes are correct.
expectedTabModel = new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
mTabGroupModelFilter.undoGroupedTab(mTab6, POSITION6, TAB5_ROOT_ID, TAB5_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab6, POSITION2);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab6, mTab6.getId());
mTabGroupModelFilter.undoGroupedTab(mTab5, POSITION5, TAB5_ROOT_ID, TAB5_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab5, POSITION2);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab5, mTab6.getId());
assertThat(mTab5.getRootId(), equalTo(TAB5_ROOT_ID));
assertThat(mTab6.getRootId(), equalTo(TAB5_ROOT_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab5), equalTo(3));
assertThat(mTabGroupModelFilter.indexOf(mTab6), equalTo(3));
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@Test
public void undoGroupedTab_MultipleGroupUndoWithMovement_MergeListIncludesAllTabs() {
List<Tab> expectedTabModel =
new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab6, mTab5, mTab4));
// Simulate we just grouped the group (mTab5, mTab6) with the group (mTab2, mTab3).
mTab5.setRootId(TAB2_ROOT_ID);
mTab6.setRootId(TAB2_ROOT_ID);
mTab5.setTabGroupId(TAB2_TAB_GROUP_ID);
mTab6.setTabGroupId(TAB2_TAB_GROUP_ID);
mTabModel.moveTab(mTab5.getId(), POSITION3 + 1);
mTabModel.moveTab(mTab6.getId(), POSITION3 + 1);
mTabGroupModelFilter.resetFilterState();
assertThat(mTab5.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTab6.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB2_TAB_GROUP_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab5), equalTo(1));
assertThat(mTabGroupModelFilter.indexOf(mTab6), equalTo(1));
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
// Undo the grouped action in reverse order so indexes are correct.
expectedTabModel = new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
mTabGroupModelFilter.undoGroupedTab(mTab6, POSITION6, TAB5_ROOT_ID, TAB5_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab6, POSITION2);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab6, mTab6.getId());
mTabGroupModelFilter.undoGroupedTab(mTab5, POSITION5, TAB5_ROOT_ID, TAB5_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab5, POSITION2);
verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab5, mTab6.getId());
mTabGroupModelFilter.undoGroupedTab(mTab3, POSITION3, TAB2_ROOT_ID, TAB2_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver, never()).didMoveTabOutOfGroup(eq(mTab3), anyInt());
verify(mTabGroupModelFilterObserver, never()).didMergeTabToGroup(eq(mTab3), anyInt());
mTabGroupModelFilter.undoGroupedTab(mTab2, POSITION2, TAB2_ROOT_ID, TAB2_TAB_GROUP_ID);
verify(mTabGroupModelFilterObserver, never()).didMoveTabOutOfGroup(eq(mTab2), anyInt());
verify(mTabGroupModelFilterObserver, never()).didMergeTabToGroup(eq(mTab2), anyInt());
assertThat(mTab5.getRootId(), equalTo(TAB5_ROOT_ID));
assertThat(mTab6.getRootId(), equalTo(TAB5_ROOT_ID));
assertThat(mTab5.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
assertThat(mTab6.getTabGroupId(), equalTo(TAB5_TAB_GROUP_ID));
assertThat(mTabGroupModelFilter.indexOf(mTab5), equalTo(3));
assertThat(mTabGroupModelFilter.indexOf(mTab6), equalTo(3));
assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
}
@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, TAB1_TAB_GROUP_ID);
}
@Test
public void moveTab_Incognito() {
setupTabGroupModelFilter(false, true);
assertFalse(mTabGroupModelFilter.isTabModelRestored());
mTabGroupModelFilter.markTabStateInitialized();
assertTrue(mTabGroupModelFilter.isTabModelRestored());
// Simulate that tab3 is going to be moved out of group.
mTab3.setRootId(TAB3_ID);
mTab3.setTabGroupId(null);
mTabModelObserverCaptor.getValue().didMoveTab(mTab3, POSITION3, POSITION6);
// Verify that the signal is not ignored.
verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab3, POSITION2);
}
@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 testGetRelatedTabCountForRootId() {
assertEquals(
"Should have 1 related tab.",
1,
mTabGroupModelFilter.getRelatedTabCountForRootId(TAB1_ROOT_ID));
assertEquals(
"Should have 2 related tabs.",
2,
mTabGroupModelFilter.getRelatedTabCountForRootId(TAB2_ROOT_ID));
assertEquals(
"Should have 2 related tabs.",
2,
mTabGroupModelFilter.getRelatedTabCountForRootId(TAB3_ROOT_ID));
assertEquals(
"Should have 1 related tab.",
1,
mTabGroupModelFilter.getRelatedTabCountForRootId(TAB4_ROOT_ID));
assertEquals(
"Should have 2 related tabs.",
2,
mTabGroupModelFilter.getRelatedTabCountForRootId(TAB5_ROOT_ID));
assertEquals(
"Should have 2 related tabs.",
2,
mTabGroupModelFilter.getRelatedTabCountForRootId(TAB6_ROOT_ID));
}
@Test
public void testIndexOfAnUndoableClosedTabNotCrashing() {
mTabGroupModelFilter.closeTab(mTab1);
mTabGroupModelFilter.indexOf(mTab1);
}
@Test
public void testGetTotalTabCount() {
assertThat("Should have 4 group tabs", mTabGroupModelFilter.getCount(), equalTo(4));
int totalTabCount = mTabGroupModelFilter.getTotalTabCount();
assertThat("Should have 6 total tabs", totalTabCount, equalTo(6));
}
@Test
public void mergeGroupToGroupNonAdjacent_notifyFilterObserver() {
// Override the setup behaviour for color SharedPreferences since after #didCreateNewGroup
// is emitted, a color will have been set.
when(mSharedPreferencesColor.getInt(anyString(), anyInt())).thenReturn(COLOR_ID);
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab5, mTab6, mTab2, mTab3));
List<Tab> expectedSourceTabs = mTabGroupModelFilter.getRelatedTabList(mTab2.getId());
List<Integer> originalIndexes = new ArrayList<>();
List<Integer> originalRootIds = new ArrayList<>();
List<Token> originalTabGroupIds = new ArrayList<>();
List<Tab> expectedNotifiedTabs = new ArrayList();
expectedNotifiedTabs.add(mTab5);
expectedNotifiedTabs.addAll(expectedSourceTabs);
originalIndexes.add(
TabModelUtils.getTabIndexById(mTabGroupModelFilter.getTabModel(), mTab5.getId()));
originalRootIds.add(mTab5.getRootId());
originalTabGroupIds.add(mTab5.getTabGroupId());
for (Tab tab : expectedSourceTabs) {
// Use tab2 initial index for both related tabs index as the logic moves tab2 after
// saving its index but before retrieving index for related tab3.
originalIndexes.add(
TabModelUtils.getTabIndexById(
mTabGroupModelFilter.getTabModel(), mTab2.getId()));
originalRootIds.add(tab.getRootId());
originalTabGroupIds.add(tab.getTabGroupId());
}
mTabGroupModelFilter.mergeTabsToGroup(mTab2.getId(), mTab5.getId(), false);
verify(mTabGroupModelFilterObserver, never())
.didCreateNewGroup(mTab2, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver)
.didCreateGroup(
expectedNotifiedTabs,
originalIndexes,
originalRootIds,
originalTabGroupIds,
TAB_TITLE,
COLOR_ID,
/* destinationGroupTitleCollapsed= */ true);
verify(mTabGroupModelFilterObserver)
.didRemoveTabGroup(mTab2.getId(), TAB2_TAB_GROUP_ID, DidRemoveTabGroupReason.MERGE);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabList(mTab2.getId()).toArray(),
expectedGroup.toArray());
assertEquals(mTab5.getTabGroupId(), TAB5_TAB_GROUP_ID);
assertEquals(mTab6.getTabGroupId(), TAB5_TAB_GROUP_ID);
assertEquals(mTab2.getTabGroupId(), TAB5_TAB_GROUP_ID);
assertEquals(mTab3.getTabGroupId(), TAB5_TAB_GROUP_ID);
}
@Test
public void mergeGroupToTabAdjacent_notifyFilterObserver() {
// Override the setup behaviour for color SharedPreferences since after #didCreateNewGroup
// is emitted, a color will have been set.
when(mSharedPreferencesColor.getInt(anyString(), anyInt())).thenReturn(COLOR_ID);
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab4, mTab2, mTab3));
List<Tab> expectedSourceTabs = mTabGroupModelFilter.getRelatedTabList(mTab3.getId());
List<Integer> originalIndexes = new ArrayList<>();
List<Integer> originalRootIds = new ArrayList<>();
List<Token> originalTabGroupIds = new ArrayList<>();
List<Tab> expectedNotifiedTabs = new ArrayList();
expectedNotifiedTabs.add(mTab4);
expectedNotifiedTabs.addAll(expectedSourceTabs);
originalIndexes.add(
TabModelUtils.getTabIndexById(mTabGroupModelFilter.getTabModel(), mTab4.getId()));
originalRootIds.add(mTab4.getRootId());
originalTabGroupIds.add(mTab4.getTabGroupId());
for (Tab tab : expectedSourceTabs) {
originalIndexes.add(
TabModelUtils.getTabIndexById(
mTabGroupModelFilter.getTabModel(), mTab2.getId()));
originalRootIds.add(tab.getRootId());
originalTabGroupIds.add(tab.getTabGroupId());
}
mTabGroupModelFilter.mergeTabsToGroup(mTab3.getId(), mTab4.getId(), false);
verify(mTabGroupModelFilterObserver, never())
.didCreateNewGroup(mTab4, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver)
.didCreateGroup(
expectedNotifiedTabs,
originalIndexes,
originalRootIds,
originalTabGroupIds,
TAB_TITLE,
COLOR_ID,
/* destinationGroupTitleCollapsed= */ true);
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabList(mTab2.getId()).toArray(),
expectedGroup.toArray());
assertEquals(mTab4.getTabGroupId(), TAB2_TAB_GROUP_ID);
assertEquals(mTab2.getTabGroupId(), TAB2_TAB_GROUP_ID);
assertEquals(mTab3.getTabGroupId(), TAB2_TAB_GROUP_ID);
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void mergeTabToTab_notifyFilterObserver() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
// Override the setup behaviour for color SharedPreferences since after #didCreateNewGroup
// is emitted, a color will have been set.
when(mSharedPreferencesColor.getInt(anyString(), anyInt())).thenReturn(COLOR_ID);
List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab4, mTab1));
Token tabGroupId = new Token(33L, 82L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
mTabGroupModelFilter.mergeTabsToGroup(mTab1.getId(), mTab4.getId(), false);
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab4, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver, never())
.didCreateGroup(
anyList(),
anyList(),
anyList(),
anyList(),
anyString(),
anyInt(),
anyBoolean());
assertArrayEquals(
mTabGroupModelFilter.getRelatedTabList(mTab1.getId()).toArray(),
expectedGroup.toArray());
assertEquals(mTab1.getTabGroupId(), tabGroupId);
assertEquals(mTab4.getTabGroupId(), tabGroupId);
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_CREATION_DIALOG_ANDROID)
public void mergeTabToTab_doNotNotifyFilterObserver() {
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING.setForTesting(false);
Token tabGroupId = new Token(33L, 28L);
when(mTokenJniMock.createRandom()).thenReturn(tabGroupId);
mTabGroupModelFilter.mergeTabsToGroup(mTab1.getId(), mTab4.getId(), true);
verify(mTabGroupModelFilterObserver).didCreateNewGroup(mTab4, mTabGroupModelFilter);
verify(mTabGroupModelFilterObserver, never())
.didCreateGroup(any(), any(), any(), any(), any(), anyInt(), anyBoolean());
assertEquals(mTab1.getTabGroupId(), tabGroupId);
assertEquals(mTab4.getTabGroupId(), tabGroupId);
}
@Test
public void testTabGroupIds() {
assertEquals(mTab1.getRootId(), TAB1_ROOT_ID);
assertEquals(mTab1.getTabGroupId(), TAB1_TAB_GROUP_ID);
assertEquals(mTab2.getRootId(), TAB2_ROOT_ID);
assertEquals(mTab2.getTabGroupId(), TAB2_TAB_GROUP_ID);
assertEquals(mTab3.getRootId(), TAB3_ROOT_ID);
assertEquals(mTab3.getTabGroupId(), TAB3_TAB_GROUP_ID);
assertEquals(mTab4.getRootId(), TAB4_ROOT_ID);
assertEquals(mTab4.getTabGroupId(), TAB4_TAB_GROUP_ID);
assertEquals(mTab5.getRootId(), TAB5_ROOT_ID);
assertEquals(mTab5.getTabGroupId(), TAB5_TAB_GROUP_ID);
assertEquals(mTab6.getRootId(), TAB6_ROOT_ID);
assertEquals(mTab6.getTabGroupId(), TAB6_TAB_GROUP_ID);
}
@Test
public void testAssignTabGroupIds() {
mTab1.setTabGroupId(null);
mTab2.setTabGroupId(null);
mTab3.setTabGroupId(null);
mTab4.setTabGroupId(null);
mTab5.setTabGroupId(null);
mTab6.setTabGroupId(null);
Token tabGroupIdTab2 = new Token(1L, 2L);
Token tabGroupIdTab5 = new Token(5L, 6L);
Token tabGroupIdUnused = new Token(3L, 3L);
when(mTokenJniMock.createRandom())
.thenReturn(tabGroupIdTab2)
.thenReturn(tabGroupIdTab5)
.thenReturn(tabGroupIdUnused);
mTabGroupModelFilter.addTabGroupIdsForAllTabGroups();
assertEquals(mTab1.getRootId(), TAB1_ROOT_ID);
assertNull(mTab1.getTabGroupId());
assertEquals(mTab2.getRootId(), TAB2_ROOT_ID);
assertEquals(mTab2.getTabGroupId(), tabGroupIdTab2);
assertEquals(mTab3.getRootId(), TAB3_ROOT_ID);
assertEquals(mTab3.getTabGroupId(), tabGroupIdTab2);
assertEquals(mTab4.getRootId(), TAB4_ROOT_ID);
assertNull(mTab4.getTabGroupId());
assertEquals(mTab5.getRootId(), TAB5_ROOT_ID);
assertEquals(mTab5.getTabGroupId(), tabGroupIdTab5);
assertEquals(mTab6.getRootId(), TAB6_ROOT_ID);
assertEquals(mTab6.getTabGroupId(), tabGroupIdTab5);
}
@Test
public void testRelatedTabsExistForRootId() {
assertThat(mTab1.getRootId(), equalTo(TAB1_ROOT_ID));
assertThat(mTab3.getRootId(), equalTo(TAB2_ROOT_ID));
assertThat(mTab6.getRootId(), equalTo(TAB5_ROOT_ID));
mTabGroupModelFilter.removeTab(mTab1);
mTabGroupModelFilter.removeTab(mTab3);
mTabGroupModelFilter.removeTab(mTab5);
assertFalse(mTabGroupModelFilter.tabGroupExistsForRootId(mTab1.getRootId()));
assertTrue(mTabGroupModelFilter.tabGroupExistsForRootId(mTab3.getRootId()));
assertTrue(mTabGroupModelFilter.tabGroupExistsForRootId(mTab5.getRootId()));
}
@Test
public void testGetAllTabGroupRootIds() {
// With the given setup, mTab2 and mTab3 are in a group and mTab5 and mTab6 are in another
// group.
Set<Integer> rootIds = new ArraySet<>();
rootIds.add(mTab2.getRootId());
rootIds.add(mTab5.getRootId());
assertEquals(rootIds, mTabGroupModelFilter.getAllTabGroupRootIds());
}
@Test
public void testGetAllTabGroupIds() {
// With the given setup, mTab2 and mTab3 are in a group and mTab5 and mTab6 are in another
// group.
Set<Token> tabGroupIds = new ArraySet<>();
tabGroupIds.add(mTab2.getTabGroupId());
tabGroupIds.add(mTab5.getTabGroupId());
assertEquals(tabGroupIds, mTabGroupModelFilter.getAllTabGroupIds());
}
@Test
public void testGetLazyAllTabRootIdsInComprehensiveModel() {
// With the given setup, mTab2 and mTab3 are in a group and mTab5 and mTab6 are in another
// group. Tabs 1 and 4 are also unique.
Set<Integer> rootIds = new ArraySet<>();
rootIds.add(mTab1.getRootId());
rootIds.add(mTab2.getRootId());
rootIds.add(mTab4.getRootId());
rootIds.add(mTab5.getRootId());
assertEquals(
rootIds,
mTabGroupModelFilter
.getLazyAllRootIdsInComprehensiveModel(new ArrayList<Tab>())
.get());
}
@Test
public void testGetLazyAllTabGroupIdsInComprehensiveModel() {
// With the given setup, mTab2 and mTab3 are in a group and mTab5 and mTab6 are in another
// group.
Set<Token> tabGroupIds = new ArraySet<>();
tabGroupIds.add(mTab2.getTabGroupId());
tabGroupIds.add(mTab5.getTabGroupId());
assertEquals(
tabGroupIds,
mTabGroupModelFilter
.getLazyAllTabGroupIdsInComprehensiveModel(new ArrayList<Tab>())
.get());
}
@Test
public void testGetLazyAllTabGroupIdsInComprehensiveModel_ExcludePartial() {
// With the given setup, mTab2 and mTab3 are in a group and mTab5 and mTab6 are in another
// group.
Set<Token> tabGroupIds = new ArraySet<>();
tabGroupIds.add(mTab2.getTabGroupId());
tabGroupIds.add(mTab5.getTabGroupId());
assertEquals(
tabGroupIds,
mTabGroupModelFilter
.getLazyAllTabGroupIdsInComprehensiveModel(List.of(mTab2, mTab5))
.get());
}
@Test
public void testGetLazyAllTabGroupIdsInComprehensiveModel_ExcludeFull() {
// With the given setup, mTab2 and mTab3 are in a group and mTab5 and mTab6 are in another
// group.
Set<Token> tabGroupIds = new ArraySet<>();
tabGroupIds.add(mTab5.getTabGroupId());
assertEquals(
tabGroupIds,
mTabGroupModelFilter
.getLazyAllTabGroupIdsInComprehensiveModel(List.of(mTab2, mTab3))
.get());
}
@Test
public void testSetTabGroupTitle() {
mTabGroupModelFilter.setTabGroupTitle(TAB2_ROOT_ID, "Foo");
verify(mTabGroupModelFilterObserver).didChangeTabGroupTitle(TAB2_ROOT_ID, "Foo");
}
@Test
public void testDeleteTabGroupTitle() {
mTabGroupModelFilter.deleteTabGroupTitle(TAB2_ROOT_ID);
verify(mTabGroupModelFilterObserver)
.didChangeTabGroupTitle(TAB2_ROOT_ID, /* newTitle= */ null);
}
@Test
public void testGetOrCreateTabGroupColor() {
assertEquals(
TabGroupColorId.GREY,
mTabGroupModelFilter.getTabGroupColorWithFallback(TAB1_ROOT_ID));
when(mSharedPreferencesColor.getInt(eq(String.valueOf(TAB2_ROOT_ID)), anyInt()))
.thenReturn(TabGroupColorId.BLUE);
assertEquals(
TabGroupColorId.BLUE,
mTabGroupModelFilter.getTabGroupColorWithFallback(TAB2_ROOT_ID));
}
@Test
public void testSetTabGroupColor() {
mTabGroupModelFilter.setTabGroupColor(TAB2_ROOT_ID, TabGroupColorId.GREY);
verify(mTabGroupModelFilterObserver)
.didChangeTabGroupColor(TAB2_ROOT_ID, TabGroupColorId.GREY);
}
@Test
public void testSetTabGroupCollapsed() {
mTabGroupModelFilter.setTabGroupCollapsed(TAB2_ROOT_ID, /* isCollapsed= */ true);
verify(mTabGroupModelFilterObserver)
.didChangeTabGroupCollapsed(TAB2_ROOT_ID, /* isCollapsed= */ true);
}
@Test
public void testDeleteTabGroupCollapsed() {
mTabGroupModelFilter.deleteTabGroupCollapsed(TAB2_ROOT_ID);
verify(mTabGroupModelFilterObserver)
.didChangeTabGroupCollapsed(TAB2_ROOT_ID, /* isCollapsed= */ false);
}
@Test
public void testSetTabGroupSyncId() {
String prefKey = String.valueOf(TAB2_ROOT_ID);
mTabGroupModelFilter.setTabGroupSyncId(TAB2_ROOT_ID, "Foo");
verify(mEditor).putString(eq(prefKey), eq("Foo"));
mTabGroupModelFilter.getTabGroupSyncId(TAB2_ROOT_ID);
verify(mSharedPreferencesSyncId).getString(eq(prefKey), eq(null));
}
@Test
@EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
public void testCloseGroup_Hiding_Undone() {
doReturn(true).when(mTabGroupSyncFeaturesJniMock).isTabGroupSyncEnabled(mProfile);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
List<Tab> groupWithTab2AndTab3 = List.of(mTab2, mTab3);
// allowUndo will always be true for pending tab closure, but use false just to verify it is
// forwarded correctly.
var params =
TabClosureParams.closeTabs(groupWithTab2AndTab3)
.allowUndo(false)
.hideTabGroups(true)
.build();
mTabGroupModelFilter.closeTabs(params);
verify(mTabModel).closeTabs(params);
assertTrue(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
mTabGroupModelFilter.willCloseTab(mTab2, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab3, /* didCloseAlone= */ false);
mTabGroupModelFilter.tabClosureUndone(mTab2);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
mTabGroupModelFilter.tabClosureUndone(mTab3);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
}
@Test
@EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
public void testCloseGroup_Hiding_Committed() {
doReturn(true).when(mTabGroupSyncFeaturesJniMock).isTabGroupSyncEnabled(mProfile);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
List<Tab> groupWithTab2AndTab3 = List.of(mTab2, mTab3);
var params = TabClosureParams.closeTabs(groupWithTab2AndTab3).hideTabGroups(true).build();
mTabGroupModelFilter.closeTabs(params);
verify(mTabModel).closeTabs(params);
assertTrue(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
mTabGroupModelFilter.willCloseTab(mTab2, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab3, /* didCloseAlone= */ false);
mTabs.remove(mTab2);
mTabs.remove(mTab3);
mTabGroupModelFilter.onFinishingMultipleTabClosure(
groupWithTab2AndTab3, /* canRestore= */ true);
// The root ID might have mutated so just assert on the last two.
verify(mTabGroupModelFilterObserver)
.didRemoveTabGroup(
anyInt(), eq(TAB2_TAB_GROUP_ID), eq(DidRemoveTabGroupReason.CLOSE));
verify(mTabGroupModelFilterObserver)
.committedTabGroupClosure(TAB2_TAB_GROUP_ID, /* wasHiding= */ true);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
}
@Test
@EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
public void testCloseMultipleTabs_Hiding_GroupInParts() {
doReturn(true).when(mTabGroupSyncFeaturesJniMock).isTabGroupSyncEnabled(mProfile);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
List<Tab> listWithTab2AndTab4 = List.of(mTab2, mTab4);
var params = TabClosureParams.closeTabs(listWithTab2AndTab4).hideTabGroups(true).build();
mTabGroupModelFilter.closeTabs(params);
verify(mTabModel).closeTabs(params);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
mTabGroupModelFilter.willCloseTab(mTab2, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab4, /* didCloseAlone= */ false);
mTabs.remove(mTab2);
mTabs.remove(mTab4);
mTabGroupModelFilter.onFinishingMultipleTabClosure(
listWithTab2AndTab4, /* canRestore= */ true);
verify(mTabGroupModelFilterObserver, never()).committedTabGroupClosure(any(), anyBoolean());
// Close the remainder of the group separately.
List<Tab> groupWithTab3 = List.of(mTab3);
params = TabClosureParams.closeTabs(groupWithTab3).hideTabGroups(true).build();
mTabGroupModelFilter.closeTabs(params);
verify(mTabModel).closeTabs(params);
assertTrue(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
mTabGroupModelFilter.willCloseTab(mTab3, /* didCloseAlone= */ false);
mTabs.remove(mTab3);
mTabGroupModelFilter.onFinishingMultipleTabClosure(groupWithTab3, /* canRestore= */ true);
verify(mTabGroupModelFilterObserver)
.committedTabGroupClosure(TAB2_TAB_GROUP_ID, /* wasHiding= */ true);
}
@Test
@EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
public void testCloseGroup_Deleted_Committed() {
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
List<Tab> groupWithTab2AndTab3 = List.of(mTab2, mTab3);
var params = TabClosureParams.closeTabs(groupWithTab2AndTab3).build();
mTabGroupModelFilter.closeTabs(params);
verify(mTabModel).closeTabs(params);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
mTabGroupModelFilter.willCloseTab(mTab2, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab3, /* didCloseAlone= */ false);
mTabs.remove(mTab2);
mTabs.remove(mTab3);
mTabGroupModelFilter.onFinishingMultipleTabClosure(
groupWithTab2AndTab3, /* canRestore= */ true);
verify(mTabGroupModelFilterObserver)
.committedTabGroupClosure(TAB2_TAB_GROUP_ID, /* wasHiding= */ false);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
}
@Test
@EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
public void testCloseAllTabs_Hiding_Undone() {
doReturn(true).when(mTabGroupSyncFeaturesJniMock).isTabGroupSyncEnabled(mProfile);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB5_TAB_GROUP_ID));
var params = TabClosureParams.closeAllTabs().hideTabGroups(true).build();
mTabGroupModelFilter.closeTabs(params);
verify(mTabModel).closeTabs(params);
assertTrue(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
assertTrue(mTabGroupModelFilter.isTabGroupHiding(TAB5_TAB_GROUP_ID));
mTabGroupModelFilter.willCloseTab(mTab1, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab2, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab3, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab4, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab5, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab6, /* didCloseAlone= */ false);
mTabs.remove(mTab2);
mTabs.remove(mTab3);
mTabs.remove(mTab4);
mTabs.remove(mTab5);
mTabs.remove(mTab6);
mTabGroupModelFilter.tabClosureUndone(mTab1);
assertTrue(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
mTabs.add(mTab2);
mTabGroupModelFilter.tabClosureUndone(mTab2);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
mTabs.add(mTab3);
mTabGroupModelFilter.tabClosureUndone(mTab3);
mTabs.add(mTab4);
mTabGroupModelFilter.tabClosureUndone(mTab4);
mTabs.add(mTab5);
assertTrue(mTabGroupModelFilter.isTabGroupHiding(TAB5_TAB_GROUP_ID));
mTabGroupModelFilter.tabClosureUndone(mTab5);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB5_TAB_GROUP_ID));
mTabs.remove(mTab6);
mTabGroupModelFilter.tabClosureUndone(mTab6);
}
/**
* The partial tab-by-tab commit tested here is impossible with how {@link
* PendingTabClosureManager} works, but for testing it is useful to verify the behavior is
* correct.
*/
@Test
@EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
public void testCloseAllTabs_Hiding_PartialCommit() {
doReturn(true).when(mTabGroupSyncFeaturesJniMock).isTabGroupSyncEnabled(mProfile);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB5_TAB_GROUP_ID));
var params = TabClosureParams.closeAllTabs().hideTabGroups(true).build();
mTabGroupModelFilter.closeTabs(params);
verify(mTabModel).closeTabs(params);
assertTrue(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
assertTrue(mTabGroupModelFilter.isTabGroupHiding(TAB5_TAB_GROUP_ID));
mTabGroupModelFilter.willCloseTab(mTab1, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab2, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab3, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab4, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab5, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab6, /* didCloseAlone= */ false);
mTabs.remove(mTab2);
mTabs.remove(mTab3);
mTabs.remove(mTab4);
mTabs.remove(mTab5);
mTabs.remove(mTab6);
mTabGroupModelFilter.tabClosureUndone(mTab1);
assertTrue(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
mTabs.add(mTab2);
mTabGroupModelFilter.tabClosureUndone(mTab2);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
mTabs.add(mTab3);
mTabGroupModelFilter.tabClosureUndone(mTab3);
mTabGroupModelFilter.onFinishingMultipleTabClosure(
List.of(mTab4, mTab5, mTab6), /* canRestore= */ true);
verify(mTabGroupModelFilterObserver, never())
.committedTabGroupClosure(eq(TAB2_TAB_GROUP_ID), anyBoolean());
verify(mTabGroupModelFilterObserver)
.committedTabGroupClosure(TAB5_TAB_GROUP_ID, /* wasHiding= */ true);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB5_TAB_GROUP_ID));
}
@Test
@EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
public void testCloseAllTabs_Deleted() {
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB5_TAB_GROUP_ID));
var params = TabClosureParams.closeAllTabs().hideTabGroups(false).build();
mTabGroupModelFilter.closeTabs(params);
verify(mTabModel).closeTabs(params);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB5_TAB_GROUP_ID));
mTabGroupModelFilter.willCloseTab(mTab1, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab2, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab3, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab4, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab5, /* didCloseAlone= */ false);
mTabGroupModelFilter.willCloseTab(mTab6, /* didCloseAlone= */ false);
mTabs.remove(mTab1);
mTabs.remove(mTab2);
mTabs.remove(mTab3);
mTabs.remove(mTab4);
mTabs.remove(mTab5);
mTabs.remove(mTab6);
mTabGroupModelFilter.onFinishingMultipleTabClosure(
List.of(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6), /* canRestore= */ true);
verify(mTabGroupModelFilterObserver)
.committedTabGroupClosure(TAB2_TAB_GROUP_ID, /* wasHiding= */ false);
verify(mTabGroupModelFilterObserver)
.committedTabGroupClosure(TAB5_TAB_GROUP_ID, /* wasHiding= */ false);
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB2_TAB_GROUP_ID));
assertFalse(mTabGroupModelFilter.isTabGroupHiding(TAB5_TAB_GROUP_ID));
}
@Test
public void testWillMergingCreateNewGroup_NewGroup() {
// Mock a merge between mTab1 and mTab4, neither of which are in a group.
List<Tab> tabsToMerge = List.of(mTab1, mTab4);
assertTrue(mTabGroupModelFilter.willMergingCreateNewGroup(tabsToMerge));
}
@Test
public void testWillMergingCreateNewGroup_ExistingGroup() {
// Mock a merge between mTab1, mTab2 and mTab3, of which the latter 2 are in a group.
List<Tab> tabsToMerge = List.of(mTab1, mTab2, mTab3);
assertFalse(mTabGroupModelFilter.willMergingCreateNewGroup(tabsToMerge));
}
}