// 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_management;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
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.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.chromium.chrome.browser.tasks.tab_management.MessageCardViewProperties.MESSAGE_SERVICE_ACTION_PROVIDER;
import static org.chromium.chrome.browser.tasks.tab_management.MessageCardViewProperties.MESSAGE_SERVICE_DISMISS_ACTION_PROVIDER;
import android.app.Activity;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import androidx.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric;
import org.robolectric.shadows.ShadowLooper;
import org.chromium.base.Token;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.base.test.util.UserActionTester;
import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory;
import org.chromium.chrome.browser.data_sharing.DataSharingTabManager;
import org.chromium.chrome.browser.data_sharing.ui.shared_image_tiles.SharedImageTilesCoordinator;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
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.TabSelectionType;
import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeatures;
import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeaturesJni;
import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncServiceFactory;
import org.chromium.chrome.browser.tab_ui.RecyclerViewPosition;
import org.chromium.chrome.browser.tab_ui.TabUiThemeUtils;
import org.chromium.chrome.browser.tabmodel.TabClosureParams;
import org.chromium.chrome.browser.tabmodel.TabCreator;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver;
import org.chromium.chrome.browser.tasks.tab_management.MessageService.MessageType;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.tab_ui.R;
import org.chromium.components.data_sharing.DataSharingService;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.components.tab_group_sync.LocalTabGroupId;
import org.chromium.components.tab_group_sync.SavedTabGroup;
import org.chromium.components.tab_group_sync.TabGroupSyncService;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.KeyboardVisibilityDelegate;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.TestActivity;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.url.GURL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** Tests for {@link TabGridDialogMediator}. */
@SuppressWarnings({"ArraysAsListWithZeroOrOneArgument", "ResultOfMethodCallIgnored"})
@RunWith(BaseRobolectricTestRunner.class)
@EnableFeatures(ChromeFeatureList.DATA_SHARING)
public class TabGridDialogMediatorUnitTest {
private static final String TAB1_TITLE = "Tab1";
private static final String TAB2_TITLE = "Tab2";
private static final String TAB3_TITLE = "Tab3";
private static final String DIALOG_TITLE1 = "1 tab";
private static final String DIALOG_TITLE2 = "2 tabs";
private static final String CUSTOMIZED_DIALOG_TITLE = "Cool Tabs";
private static final String COLLABORATION_ID1 = "A";
private static final String GAIA_ID = "Z";
private static final String EMAIL = "[email protected]";
private static final String GROUP_TITLE = "My Group";
private static final int COLOR_2 = 1;
private static final int COLOR_3 = 2;
private static final int TAB1_ID = 456;
private static final int TAB2_ID = 789;
private static final int TAB3_ID = 123;
private static final int POSITION1 = 0;
private static final int POSITION2 = 1;
private static final Token TAB_GROUP_ID = new Token(1L, 2L);
@Rule public JniMocker mJniMocker = new JniMocker();
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private Profile mProfile;
@Mock private TabGroupSyncFeatures.Natives mTabGroupSyncFeaturesJniMock;
@Mock private View mView;
@Mock private TabGridDialogMediator.DialogController mDialogController;
@Mock private TabCreatorManager mTabCreatorManager;
@Mock private TabCreator mTabCreator;
@Mock private TabSwitcherResetHandler mTabSwitcherResetHandler;
@Mock private TabGridDialogMediator.AnimationSourceViewProvider mAnimationSourceViewProvider;
@Mock private TabGroupModelFilter mTabGroupModelFilter;
@Mock private TabModel mTabModel;
@Mock private TabListEditorCoordinator.TabListEditorController mTabListEditorController;
@Mock private TabGroupTitleEditor mTabGroupTitleEditor;
@Mock private EditText mTitleTextView;
@Mock private Editable mEditable;
@Mock private SnackbarManager mSnackbarManager;
@Mock private Supplier<RecyclerViewPosition> mRecyclerViewPositionSupplier;
@Mock private DataSharingTabManager mDataSharingTabManager;
@Mock private Runnable mShowColorPickerPopupRunnable;
@Mock private ActionConfirmationManager mActionConfirmationManager;
@Mock private IdentityServicesProvider mIdentityServicesProvider;
@Mock private IdentityManager mIdentityManager;
@Mock private TabGroupSyncService mTabGroupSyncService;
@Mock private DataSharingService mDataSharingService;
@Mock private SharedImageTilesCoordinator mSharedImageTilesCoordinator;
@Captor private ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
@Captor private ArgumentCaptor<TabGroupModelFilterObserver> mTabGroupModelFilterObserverCaptor;
@Captor private ArgumentCaptor<PropertyModel> mCollaborationActivityMessageCardCaptor;
private final ObservableSupplierImpl<TabModelFilter> mCurrentTabModelFilterSupplier =
new ObservableSupplierImpl<>();
private UserActionTester mActionTester;
private Tab mTab1;
private Tab mTab2;
private Activity mActivity;
private PropertyModel mModel;
private TabGridDialogMediator mMediator;
@Before
public void setUp() {
mActionTester = new UserActionTester();
mJniMocker.mock(TabGroupSyncFeaturesJni.TEST_HOOKS, mTabGroupSyncFeaturesJniMock);
doReturn(true).when(mTabGroupSyncFeaturesJniMock).isTabGroupSyncEnabled(mProfile);
when(mProfile.getOriginalProfile()).thenReturn(mProfile);
when(mProfile.isNativeInitialized()).thenReturn(true);
IdentityServicesProvider.setInstanceForTests(mIdentityServicesProvider);
when(mIdentityServicesProvider.getIdentityManager(any())).thenReturn(mIdentityManager);
TabGroupSyncServiceFactory.setForTesting(mTabGroupSyncService);
DataSharingServiceFactory.setForTesting(mDataSharingService);
mTab1 = prepareTab(TAB1_ID, TAB1_TITLE);
mTab2 = prepareTab(TAB2_ID, TAB2_TITLE);
List<Tab> tabs1 = new ArrayList<>(Arrays.asList(mTab1));
List<Tab> tabs2 = new ArrayList<>(Arrays.asList(mTab2));
mCurrentTabModelFilterSupplier.set(mTabGroupModelFilter);
doReturn(mProfile).when(mTabModel).getProfile();
doReturn(mTabModel).when(mTabGroupModelFilter).getTabModel();
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(POSITION2).when(mTabGroupModelFilter).indexOf(mTab2);
when(mTabGroupModelFilter.isIncognitoBranded()).thenReturn(false);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(tabs1).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(tabs2).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
doReturn(POSITION1).when(mTabGroupModelFilter).index();
doReturn(POSITION1).when(mTabModel).index();
doReturn(2).when(mTabModel).getCount();
doReturn(mTab1).when(mTabModel).getTabAt(POSITION1);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION2);
doReturn(mTab1).when(mTabModel).getTabById(TAB1_ID);
doReturn(mTab2).when(mTabModel).getTabById(TAB2_ID);
doReturn(POSITION1).when(mTabModel).indexOf(mTab1);
doReturn(POSITION2).when(mTabModel).indexOf(mTab2);
doNothing().when(mTabGroupModelFilter).addObserver(mTabModelObserverCaptor.capture());
doNothing()
.when(mTabGroupModelFilter)
.addTabGroupObserver(mTabGroupModelFilterObserverCaptor.capture());
doReturn(mView).when(mAnimationSourceViewProvider).getAnimationSourceViewForTab(anyInt());
doReturn(mTabCreator).when(mTabCreatorManager).getTabCreator(anyBoolean());
doReturn(mEditable).when(mTitleTextView).getText();
doReturn(CUSTOMIZED_DIALOG_TITLE).when(mEditable).toString();
doReturn(null).when(mRecyclerViewPositionSupplier).get();
mActivity = Robolectric.buildActivity(TestActivity.class).get();
mModel = spy(new PropertyModel(TabGridDialogProperties.ALL_KEYS));
mMediator =
new TabGridDialogMediator(
mActivity,
mDialogController,
mModel,
mCurrentTabModelFilterSupplier,
mTabCreatorManager,
mTabSwitcherResetHandler,
mRecyclerViewPositionSupplier,
mAnimationSourceViewProvider,
mSnackbarManager,
mSharedImageTilesCoordinator,
mDataSharingTabManager,
/* componentName= */ "",
mShowColorPickerPopupRunnable,
mActionConfirmationManager);
mMediator.initWithNative(() -> mTabListEditorController, mTabGroupTitleEditor);
assertThat(mTabModelObserverCaptor.getAllValues().isEmpty(), equalTo(false));
assertThat(mTabGroupModelFilterObserverCaptor.getAllValues().isEmpty(), equalTo(false));
}
@Test
public void setupListenersAndObservers() {
// These listeners and observers should be setup when the mediator is created.
assertThat(
mModel.get(TabGridDialogProperties.COLLAPSE_CLICK_LISTENER),
instanceOf(View.OnClickListener.class));
assertThat(
mModel.get(TabGridDialogProperties.ADD_CLICK_LISTENER),
instanceOf(View.OnClickListener.class));
}
@Test
public void setupTabGroupTitleEdit() {
// Setup editable title.
assertThat(
mMediator.getKeyboardVisibilityListenerForTesting(),
instanceOf(KeyboardVisibilityDelegate.KeyboardVisibilityListener.class));
assertThat(
mModel.get(TabGridDialogProperties.TITLE_TEXT_WATCHER),
instanceOf(TextWatcher.class));
assertThat(
mModel.get(TabGridDialogProperties.TITLE_TEXT_ON_FOCUS_LISTENER),
instanceOf(View.OnFocusChangeListener.class));
}
@Test
public void setupTabListEditor() {
// Setup selection editor for multiple items.
assertThat(
mModel.get(TabGridDialogProperties.MENU_CLICK_LISTENER),
instanceOf(View.OnClickListener.class));
ArgumentCaptor<List<TabListEditorAction>> captor =
ArgumentCaptor.forClass((Class) List.class);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mMediator.onToolbarMenuItemClick(R.id.select_tabs, TAB1_ID, /* collaborationId= */ null);
verify(mTabListEditorController).configureToolbarWithMenuItems(captor.capture());
verify(mRecyclerViewPositionSupplier, times(1)).get();
verify(mTabListEditorController).show(any(), eq(null));
List<TabListEditorAction> actions = captor.getValue();
assertThat(actions.get(0), instanceOf(TabListEditorSelectionAction.class));
assertThat(actions.get(1), instanceOf(TabListEditorCloseAction.class));
assertThat(actions.get(2), instanceOf(TabListEditorUngroupAction.class));
}
@Test
public void onClickAdd_HasCurrentTab() {
// Mock that the animation source view is not null, and the dialog is showing.
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, mView);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
View.OnClickListener listener = mModel.get(TabGridDialogProperties.ADD_CLICK_LISTENER);
listener.onClick(mView);
if (!DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)) {
verify(mDialogController).resetWithListOfTabs(null);
}
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(null));
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
if (!DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)) {
verify(mDialogController, times(2)).resetWithListOfTabs(null);
} else {
verify(mDialogController).resetWithListOfTabs(null);
}
verify(mTabCreator)
.createNewTab(
isA(LoadUrlParams.class), eq(TabLaunchType.FROM_TAB_GROUP_UI), eq(mTab1));
}
@Test
public void onClickAdd_NoCurrentTab() {
mMediator.setCurrentTabIdForTesting(Tab.INVALID_TAB_ID);
View.OnClickListener listener = mModel.get(TabGridDialogProperties.ADD_CLICK_LISTENER);
listener.onClick(mView);
verify(mTabCreator).launchNtp();
}
@Test
public void onClickCollapse() {
// Show the group of {tab1, tab2} in dialog to trigger the set of scrim observer.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
mMediator.onReset(tabGroup);
View.OnClickListener listener = mModel.get(TabGridDialogProperties.COLLAPSE_CLICK_LISTENER);
listener.onClick(mView);
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
verify(mDialogController).resetWithListOfTabs(null);
}
@Test
public void onClickScrim() {
// Show the group of {tab1, tab2} in dialog to trigger the set of scrim observer.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
mMediator.onReset(tabGroup);
Runnable scrimClickRunnable = mModel.get(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE);
scrimClickRunnable.run();
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
verify(mDialogController).resetWithListOfTabs(null);
}
@Test
public void onTitleTextChange_WithoutFocus() {
TextWatcher textWatcher = mModel.get(TabGridDialogProperties.TITLE_TEXT_WATCHER);
// Mock tab1 is the current tab for the dialog.
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.HEADER_TITLE, TAB1_TITLE);
assertThat(mEditable.toString(), equalTo(CUSTOMIZED_DIALOG_TITLE));
textWatcher.afterTextChanged(mEditable);
// TabGroupTitleEditor should not react to text change when there is no focus.
verify(mTabGroupTitleEditor, never()).storeTabGroupTitle(anyInt(), any(String.class));
verify(mTabGroupTitleEditor, never()).updateTabGroupTitle(any(Tab.class), anyString());
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(TAB1_TITLE));
assertThat(mMediator.getCurrentGroupModifiedTitleForTesting(), equalTo(null));
}
@Test
public void onTitleTextChange_WithFocus() {
TextWatcher textWatcher = mModel.get(TabGridDialogProperties.TITLE_TEXT_WATCHER);
// Mock tab1 is the current tab for the dialog.
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.HEADER_TITLE, TAB1_TITLE);
assertThat(mEditable.toString(), equalTo(CUSTOMIZED_DIALOG_TITLE));
// Focus on title TextView.
View.OnFocusChangeListener listener =
mModel.get(TabGridDialogProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
listener.onFocusChange(mTitleTextView, true);
textWatcher.afterTextChanged(mEditable);
assertThat(
mMediator.getCurrentGroupModifiedTitleForTesting(),
equalTo(CUSTOMIZED_DIALOG_TITLE));
}
@Test
public void onTitleTextFocusChange() {
View.OnFocusChangeListener listener =
mModel.get(TabGridDialogProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
assertThat(mMediator.getIsUpdatingTitleForTesting(), equalTo(false));
listener.onFocusChange(mTitleTextView, true);
assertThat(mMediator.getIsUpdatingTitleForTesting(), equalTo(true));
}
@Test
public void onKeyBoardVisibilityChanged_updateTextAndKeyboard() {
KeyboardVisibilityDelegate.KeyboardVisibilityListener listener =
mMediator.getKeyboardVisibilityListenerForTesting();
mModel.set(TabGridDialogProperties.TITLE_CURSOR_VISIBILITY, false);
mModel.set(TabGridDialogProperties.IS_TITLE_TEXT_FOCUSED, false);
listener.keyboardVisibilityChanged(true);
assertThat(mModel.get(TabGridDialogProperties.TITLE_CURSOR_VISIBILITY), equalTo(true));
assertThat(mModel.get(TabGridDialogProperties.IS_TITLE_TEXT_FOCUSED), equalTo(false));
assertThat(mModel.get(TabGridDialogProperties.IS_KEYBOARD_VISIBLE), equalTo(false));
listener.keyboardVisibilityChanged(false);
assertThat(mModel.get(TabGridDialogProperties.TITLE_CURSOR_VISIBILITY), equalTo(false));
assertThat(mModel.get(TabGridDialogProperties.IS_TITLE_TEXT_FOCUSED), equalTo(false));
assertThat(mModel.get(TabGridDialogProperties.IS_KEYBOARD_VISIBLE), equalTo(false));
}
@Test
public void onKeyBoardVisibilityChanged_StoreGroupTitle() {
KeyboardVisibilityDelegate.KeyboardVisibilityListener keyboardVisibilityListener =
mMediator.getKeyboardVisibilityListenerForTesting();
TextWatcher textWatcher = mModel.get(TabGridDialogProperties.TITLE_TEXT_WATCHER);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.HEADER_TITLE, TAB1_TITLE);
// Mock that tab1 is in a group.
createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID, TAB_GROUP_ID);
// Mock that keyboard shows and group title is updated.
keyboardVisibilityListener.keyboardVisibilityChanged(true);
View.OnFocusChangeListener onFocusChangeListener =
mModel.get(TabGridDialogProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
onFocusChangeListener.onFocusChange(mTitleTextView, true);
textWatcher.afterTextChanged(mEditable);
assertThat(
mMediator.getCurrentGroupModifiedTitleForTesting(),
equalTo(CUSTOMIZED_DIALOG_TITLE));
keyboardVisibilityListener.keyboardVisibilityChanged(false);
verify(mTabGroupTitleEditor).storeTabGroupTitle(eq(TAB1_ID), eq(CUSTOMIZED_DIALOG_TITLE));
verify(mTabGroupTitleEditor).updateTabGroupTitle(eq(mTab1), eq(CUSTOMIZED_DIALOG_TITLE));
assertThat(
mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
}
@Test
public void onKeyBoardVisibilityChanged_StoreGroupTitle_SingleTab() {
KeyboardVisibilityDelegate.KeyboardVisibilityListener keyboardVisibilityListener =
mMediator.getKeyboardVisibilityListenerForTesting();
TextWatcher textWatcher = mModel.get(TabGridDialogProperties.TITLE_TEXT_WATCHER);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.HEADER_TITLE, TAB1_TITLE);
// Mock that tab1 is in a group.
createTabGroup(new ArrayList<>(Arrays.asList(mTab1)), TAB1_ID, TAB_GROUP_ID);
// Mock that keyboard shows and group title is updated.
keyboardVisibilityListener.keyboardVisibilityChanged(true);
View.OnFocusChangeListener onFocusChangeListener =
mModel.get(TabGridDialogProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
onFocusChangeListener.onFocusChange(mTitleTextView, true);
textWatcher.afterTextChanged(mEditable);
assertThat(
mMediator.getCurrentGroupModifiedTitleForTesting(),
equalTo(CUSTOMIZED_DIALOG_TITLE));
keyboardVisibilityListener.keyboardVisibilityChanged(false);
verify(mTabGroupTitleEditor).storeTabGroupTitle(eq(TAB1_ID), eq(CUSTOMIZED_DIALOG_TITLE));
verify(mTabGroupTitleEditor).updateTabGroupTitle(eq(mTab1), eq(CUSTOMIZED_DIALOG_TITLE));
assertThat(
mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
}
@Test
public void onKeyBoardVisibilityChanged_NoFocus_NotStoreGroupTitle() {
KeyboardVisibilityDelegate.KeyboardVisibilityListener keyboardVisibilityListener =
mMediator.getKeyboardVisibilityListenerForTesting();
TextWatcher textWatcher = mModel.get(TabGridDialogProperties.TITLE_TEXT_WATCHER);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.HEADER_TITLE, TAB1_TITLE);
// Mock that tab1 is in a group.
createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID, TAB_GROUP_ID);
// Mock that keyboard shows but title edit text is not focused.
keyboardVisibilityListener.keyboardVisibilityChanged(true);
textWatcher.afterTextChanged(mEditable);
assertThat(mMediator.getIsUpdatingTitleForTesting(), equalTo(false));
keyboardVisibilityListener.keyboardVisibilityChanged(false);
verify(mTabGroupTitleEditor, never()).storeTabGroupTitle(anyInt(), anyString());
verify(mTabGroupTitleEditor, never()).updateTabGroupTitle(any(Tab.class), anyString());
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(TAB1_TITLE));
}
@Test
public void tabAddition() {
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE);
// Mock that the animation source view is not null, and the dialog is showing.
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, mView);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
doReturn(true).when(mTabGroupModelFilter).isTabModelRestored();
mTabModelObserverCaptor
.getValue()
.didAddTab(
newTab,
TabLaunchType.FROM_CHROME_UI,
TabCreationState.LIVE_IN_FOREGROUND,
false);
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(null));
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
verify(mDialogController).resetWithListOfTabs(null);
}
@Test
public void tabClosure_NotLast_NotCurrent() {
createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID, TAB_GROUP_ID);
// Mock that tab1 and tab2 are in the same group, but tab2 just gets closed.
doReturn(new ArrayList<>(Arrays.asList(mTab1)))
.when(mTabGroupModelFilter)
.getRelatedTabList(TAB2_ID);
doReturn(new ArrayList<>(Arrays.asList(mTab1)))
.when(mTabGroupModelFilter)
.getRelatedTabList(TAB1_ID);
// Mock tab1 is the current tab for the dialog.
mMediator.setCurrentTabIdForTesting(TAB1_ID);
// Mock dialog title is null and the dialog is showing.
mModel.set(TabGridDialogProperties.HEADER_TITLE, null);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mTabModelObserverCaptor.getValue().willCloseTab(mTab2, true);
// Current tab ID should not update.
assertThat(mMediator.getCurrentTabIdForTesting(), equalTo(TAB1_ID));
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(DIALOG_TITLE1));
verify(mTabSwitcherResetHandler).resetWithTabList(mTabGroupModelFilter, false);
}
@Test
public void tabClosure_NotLast_Current() {
// Mock that tab1 and tab2 are in the same group, but tab2 just gets closed.
doReturn(new ArrayList<>(Arrays.asList(mTab1)))
.when(mTabGroupModelFilter)
.getRelatedTabList(TAB2_ID);
// Mock tab2 is the current tab for the dialog.
mMediator.setCurrentTabIdForTesting(TAB2_ID);
// Mock dialog title is null and the dialog is showing.
mModel.set(TabGridDialogProperties.HEADER_TITLE, null);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mTabModelObserverCaptor.getValue().willCloseTab(mTab2, true);
// Current tab ID should be updated to TAB1_ID now.
assertThat(mMediator.getCurrentTabIdForTesting(), equalTo(TAB1_ID));
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(DIALOG_TITLE1));
verify(mTabSwitcherResetHandler).resetWithTabList(mTabGroupModelFilter, false);
}
@Test
public void tabClosure_Last_Current() {
// Mock that tab1 is the last tab in the group and it just gets closed.
doReturn(new ArrayList<>()).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
// As last tab in the group, tab1 is definitely the current tab for the dialog.
mMediator.setCurrentTabIdForTesting(TAB1_ID);
// Mock the dialog is showing and the animation source view is not null.
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, mView);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mTabModelObserverCaptor.getValue().willCloseTab(mTab1, true);
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(null));
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
verify(mDialogController).resetWithListOfTabs(null);
verify(mTabSwitcherResetHandler, never()).resetWithTabList(mTabGroupModelFilter, false);
mMediator.onReset(null);
assertThat(mMediator.getCurrentTabIdForTesting(), equalTo(Tab.INVALID_TAB_ID));
}
@Test
public void tabClosure_NonRootTab_StillGroupAfterClosure_WithStoredTitle() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
// Mock that tab1, tab2 and newTab are in the same group and tab1 is the root tab.
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE);
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
// Mock that newTab just get closed.
List<Tab> tabGroupAfterClosure = new ArrayList<>(Arrays.asList(mTab1, mTab2));
doReturn(tabGroupAfterClosure).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(tabGroupAfterClosure).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
// Mock that newTab is the current tab for the dialog.
mMediator.setCurrentTabIdForTesting(TAB3_ID);
// Mock that we have a stored title stored with reference to root ID of tab1.
doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB1_ID);
assertThat(
mTabGroupTitleEditor.getTabGroupTitle(mTab1.getRootId()),
equalTo(CUSTOMIZED_DIALOG_TITLE));
mTabModelObserverCaptor.getValue().willCloseTab(newTab, true);
// Dialog title should still be the stored title.
assertThat(
mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
}
@Test
public void tabClosure_RootTab_StillGroupAfterClosure_WithStoredTitle() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
// Mock that tab1, tab2 and newTab are in the same group and newTab is the root tab.
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE);
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
createTabGroup(tabGroup, TAB3_ID, TAB_GROUP_ID);
// Mock that newTab just get closed.
List<Tab> tabGroupAfterClosure = new ArrayList<>(Arrays.asList(mTab1, mTab2));
doReturn(tabGroupAfterClosure).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(tabGroupAfterClosure).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
// Mock that newTab is the current tab for the dialog.
mMediator.setCurrentTabIdForTesting(TAB3_ID);
// Mock that we have a stored title stored with reference to root ID of newTab.
doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB3_ID);
mTabModelObserverCaptor.getValue().willCloseTab(newTab, true);
// Dialog title should still be the stored title even if the root tab is closed.
assertThat(
mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
}
@Test
public void tabClosure_SingleTabAfterClosure_WithStoredTitle_SingleTabGroupSupported() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
// Mock that tab1, tab2 are in the same group and tab1 is the root tab.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
// Mock that tab2 just get closed.
List<Tab> tabGroupAfterClosure = new ArrayList<>(Arrays.asList(mTab1));
doReturn(tabGroupAfterClosure).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
// Mock that tab2 is the current tab for the dialog.
mMediator.setCurrentTabIdForTesting(TAB2_ID);
// Mock that we have a stored title stored with reference to root ID of tab1.
doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB1_ID);
mTabModelObserverCaptor.getValue().willCloseTab(mTab2, true);
assertThat(
mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
}
@Test
public void tabClosureUndone() {
// Mock that the dialog is showing.
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mModel.set(TabGridDialogProperties.HEADER_TITLE, null);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mTabModelObserverCaptor.getValue().tabClosureUndone(mTab1);
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(DIALOG_TITLE1));
verify(mTabSwitcherResetHandler).resetWithTabList(mTabGroupModelFilter, false);
ShadowLooper.runUiThreadTasks();
verify(mSnackbarManager).dismissSnackbars(eq(mMediator), eq(TAB1_ID));
}
@Test
public void tabClosureUndone_WithStoredTitle() {
// Mock that the dialog is showing.
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mModel.set(TabGridDialogProperties.HEADER_TITLE, null);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
// Mock that we have a stored title stored with reference to root ID of tab1.
doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB1_ID);
// Mock that tab1 and tab2 are in the same group, and we are undoing tab2.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
mTabModelObserverCaptor.getValue().tabClosureUndone(mTab2);
// If current group has a stored title, dialog title should be set to stored title when
// undoing a closure.
assertThat(
mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
verify(mTabSwitcherResetHandler).resetWithTabList(mTabGroupModelFilter, false);
ShadowLooper.runUiThreadTasks();
verify(mSnackbarManager).dismissSnackbars(eq(mMediator), eq(TAB2_ID));
}
@Test
public void tabClosureUndone_WithDialogHidden() {
// Mock that the dialog is hidden.
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, false);
mModel.set(TabGridDialogProperties.HEADER_TITLE, null);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mTabModelObserverCaptor.getValue().tabClosureUndone(mTab1);
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(DIALOG_TITLE1));
// Dialog should still be hidden.
assertThat(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE), equalTo(false));
verify(mTabSwitcherResetHandler, never()).resetWithTabList(mTabGroupModelFilter, false);
ShadowLooper.runUiThreadTasks();
verify(mSnackbarManager).dismissSnackbars(eq(mMediator), eq(TAB1_ID));
}
@Test
public void tabClosureCommitted() {
mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
ShadowLooper.runUiThreadTasks();
verify(mSnackbarManager).dismissSnackbars(eq(mMediator), eq(TAB1_ID));
}
@Test
public void onFinishingMultipleTabClosure() {
List<Tab> tabs = Arrays.asList(mTab1, mTab2);
mTabModelObserverCaptor
.getValue()
.onFinishingMultipleTabClosure(tabs, /* canRestore= */ true);
ShadowLooper.runUiThreadTasks();
verify(mSnackbarManager).dismissSnackbars(eq(mMediator), eq(tabs));
}
@Test
public void onFinishingMultipleTabClosure_singleTab() {
List<Tab> tabs = Arrays.asList(mTab1);
mTabModelObserverCaptor
.getValue()
.onFinishingMultipleTabClosure(tabs, /* canRestore= */ true);
ShadowLooper.runUiThreadTasks();
verify(mSnackbarManager).dismissSnackbars(eq(mMediator), eq(TAB1_ID));
}
@Test
public void allTabsClosureCommitted() {
mTabModelObserverCaptor.getValue().allTabsClosureCommitted(false);
ShadowLooper.runUiThreadTasks();
verify(mSnackbarManager).dismissSnackbars(eq(mMediator));
}
@Test
public void tabPendingClosure_DialogVisible() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mTabModelObserverCaptor.getValue().tabPendingClosure(mTab1);
verify(mSnackbarManager).showSnackbar(any(Snackbar.class));
}
@Test
public void tabPendingClosure_DialogInvisible() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, false);
mTabModelObserverCaptor.getValue().tabPendingClosure(mTab1);
verify(mSnackbarManager, never()).showSnackbar(any(Snackbar.class));
}
@Test
public void multipleTabsPendingClosure_DialogVisible() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mTabModelObserverCaptor
.getValue()
.multipleTabsPendingClosure(Arrays.asList(mTab1, mTab2), false);
verify(mSnackbarManager).showSnackbar(any(Snackbar.class));
}
@Test
public void multipleTabsPendingClosure_singleTab_DialogVisible() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mTabModelObserverCaptor.getValue().multipleTabsPendingClosure(Arrays.asList(mTab1), false);
verify(mSnackbarManager).showSnackbar(any(Snackbar.class));
}
@Test
public void multipleTabsPendingClosure_DialogInvisible() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, false);
mTabModelObserverCaptor
.getValue()
.multipleTabsPendingClosure(Arrays.asList(mTab1, mTab2), false);
verify(mSnackbarManager, never()).showSnackbar(any(Snackbar.class));
}
@Test
public void tabSelection_stripContext() {
mMediator.destroy();
mMediator =
new TabGridDialogMediator(
mActivity,
mDialogController,
mModel,
mCurrentTabModelFilterSupplier,
mTabCreatorManager,
/* tabSwitcherResetHandler= */ null,
mRecyclerViewPositionSupplier,
mAnimationSourceViewProvider,
mSnackbarManager,
mSharedImageTilesCoordinator,
mDataSharingTabManager,
/* componentName= */ "",
mShowColorPickerPopupRunnable,
mActionConfirmationManager);
mMediator.initWithNative(() -> mTabListEditorController, mTabGroupTitleEditor);
// Mock that the animation source view is not null, and the dialog is showing.
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, mView);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mTabModelObserverCaptor
.getValue()
.didSelectTab(mTab1, TabSelectionType.FROM_USER, Tab.INVALID_TAB_ID);
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(null));
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
verify(mDialogController).resetWithListOfTabs(null);
}
@Test
public void tabSelection_tabSwitcherContext() {
// Mock that the animation source view is not null, and the dialog is showing.
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, mView);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mTabModelObserverCaptor
.getValue()
.didSelectTab(mTab1, TabSelectionType.FROM_USER, Tab.INVALID_TAB_ID);
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, mView);
assertTrue(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
}
@Test
public void hideDialog_FadeOutAnimation() {
// Mock that the animation source view is null, and the dialog is showing.
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, null);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mMediator.hideDialog(false);
// Animation source view should not be specified.
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(null));
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
verify(mDialogController).resetWithListOfTabs(eq(null));
}
@Test
public void hideDialog_FadeOutAnimation_ClearsViewAnimation() {
// Mock that the animation source view is set, and the dialog is showing.
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, mView);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mMediator.hideDialog(false);
// Animation source view should not be specified.
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(null));
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
verify(mDialogController).resetWithListOfTabs(eq(null));
}
@Test
public void hideDialog_ZoomOutAnimation() {
// Mock that the animation source view is null, and the dialog is showing.
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, null);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mMediator.hideDialog(true);
// Animation source view should be specified.
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(mView));
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
verify(mDialogController).resetWithListOfTabs(eq(null));
}
@Test
public void hideDialog_ForcesAnimationToFinish() {
// Mock that the animation source view is null, and the dialog is showing.
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, null);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mMediator.hideDialog(true);
// Animation source view should be specified.
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(mView));
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
mMediator.hideDialog(false);
verify(mModel).set(TabGridDialogProperties.FORCE_ANIMATION_TO_FINISH, true);
assertFalse(mModel.get(TabGridDialogProperties.FORCE_ANIMATION_TO_FINISH));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
verify(mDialogController).resetWithListOfTabs(eq(null));
}
@Test
public void hideDialog_StoreModifiedGroupTitle() {
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.HEADER_TITLE, TAB1_TITLE);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
// Mock that tab1 is in a group.
createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID, TAB_GROUP_ID);
// Mock that we have a modified group title before dialog is hidden.
TextWatcher textWatcher = mModel.get(TabGridDialogProperties.TITLE_TEXT_WATCHER);
View.OnFocusChangeListener onFocusChangeListener =
mModel.get(TabGridDialogProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
onFocusChangeListener.onFocusChange(mTitleTextView, true);
textWatcher.afterTextChanged(mEditable);
assertThat(
mMediator.getCurrentGroupModifiedTitleForTesting(),
equalTo(CUSTOMIZED_DIALOG_TITLE));
mMediator.hideDialog(false);
verify(mTabGroupTitleEditor).storeTabGroupTitle(eq(TAB1_ID), eq(CUSTOMIZED_DIALOG_TITLE));
verify(mTabGroupTitleEditor).updateTabGroupTitle(eq(mTab1), eq(CUSTOMIZED_DIALOG_TITLE));
assertThat(
mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
}
@Test
public void hideDialog_ModifiedGroupTitleEmpty() {
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.HEADER_TITLE, TAB1_TITLE);
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
// Mock that tab1 is in a group.
createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID, TAB_GROUP_ID);
// Mock that we have a modified group title which is an empty string.
TextWatcher textWatcher = mModel.get(TabGridDialogProperties.TITLE_TEXT_WATCHER);
View.OnFocusChangeListener onFocusChangeListener =
mModel.get(TabGridDialogProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
onFocusChangeListener.onFocusChange(mTitleTextView, true);
doReturn("").when(mEditable).toString();
textWatcher.afterTextChanged(mEditable);
assertThat(mMediator.getCurrentGroupModifiedTitleForTesting(), equalTo(""));
mMediator.hideDialog(false);
// When updated title is a empty string, delete stored title and restore default title in
// PropertyModel.
verify(mTabGroupTitleEditor).deleteTabGroupTitle(eq(TAB1_ID));
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(DIALOG_TITLE2));
verify(mTabGroupTitleEditor).updateTabGroupTitle(eq(mTab1), eq(DIALOG_TITLE2));
}
@Test
public void hideDialog_NoModifiedGroupTitle() {
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.HEADER_TITLE, TAB1_TITLE);
// Mock that tab1 is in a group.
createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID, TAB_GROUP_ID);
mMediator.hideDialog(false);
// When title is not updated, don't store title when hide dialog.
verify(mTabGroupTitleEditor, never()).storeTabGroupTitle(anyInt(), anyString());
verify(mTabGroupTitleEditor, never()).updateTabGroupTitle(any(Tab.class), anyString());
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(TAB1_TITLE));
}
@Test
public void hideDialog_ClosingLastTab_SkipStoreGroupTitle() {
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.HEADER_TITLE, TAB1_TITLE);
// Mock that the last tab in the group is closed.
doReturn(new ArrayList<>()).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
// Mock that we have a modified group title before dialog is hidden.
TextWatcher textWatcher = mModel.get(TabGridDialogProperties.TITLE_TEXT_WATCHER);
View.OnFocusChangeListener onFocusChangeListener =
mModel.get(TabGridDialogProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
onFocusChangeListener.onFocusChange(mTitleTextView, true);
textWatcher.afterTextChanged(mEditable);
assertThat(
mMediator.getCurrentGroupModifiedTitleForTesting(),
equalTo(CUSTOMIZED_DIALOG_TITLE));
mMediator.hideDialog(false);
// Skip storing dialog title when the last tab is closing.
verify(mTabGroupTitleEditor, never()).storeTabGroupTitle(anyInt(), anyString());
verify(mTabGroupTitleEditor, never()).updateTabGroupTitle(any(Tab.class), anyString());
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(TAB1_TITLE));
}
@Test
public void hideDialog_SingleTab_SkipStoreGroupTitle() {
mMediator.setCurrentTabIdForTesting(TAB1_ID);
mModel.set(TabGridDialogProperties.HEADER_TITLE, TAB1_TITLE);
// Mock that tab1 is now a single tab.
createTabGroup(new ArrayList<>(Arrays.asList(mTab1)), TAB1_ID, TAB_GROUP_ID);
// Mock that we have a modified group title before dialog is hidden.
TextWatcher textWatcher = mModel.get(TabGridDialogProperties.TITLE_TEXT_WATCHER);
View.OnFocusChangeListener onFocusChangeListener =
mModel.get(TabGridDialogProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
onFocusChangeListener.onFocusChange(mTitleTextView, true);
textWatcher.afterTextChanged(mEditable);
assertThat(
mMediator.getCurrentGroupModifiedTitleForTesting(),
equalTo(CUSTOMIZED_DIALOG_TITLE));
mMediator.hideDialog(false);
// Skip storing dialog title when this group becomes a single tab.
verify(mTabGroupTitleEditor, never()).storeTabGroupTitle(anyInt(), anyString());
verify(mTabGroupTitleEditor, never()).updateTabGroupTitle(any(Tab.class), anyString());
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(TAB1_TITLE));
}
@Test
public void hideDialog_withTabGroupContinuation() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mMediator.hideDialog(false);
verify(mTabListEditorController).hide();
}
@Test
public void onReset_hideDialog() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, true);
mMediator.onReset(null);
assertFalse(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE));
// Simulate the animation finishing.
mModel.get(TabGridDialogProperties.VISIBILITY_LISTENER).finishedHidingDialogView();
verify(mDialogController).postHiding();
}
@Test
public void onReset_DialogNotVisible_NoOp() {
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, false);
mMediator.onReset(null);
verify(mDialogController).removeMessageCardItem(MessageType.COLLABORATION_ACTIVITY);
verifyNoMoreInteractions(mDialogController);
}
@Test
public void finishedHiding() {
mMediator.finishedHidingDialogView();
verify(mDialogController).removeMessageCardItem(MessageType.COLLABORATION_ACTIVITY);
verify(mDialogController).resetWithListOfTabs(null);
verify(mDialogController).postHiding();
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void showDialog_FromGTS_setSelectedColor() {
// Mock that tab1 and tab2 are in a group.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
// Mock that we have a stored color stored with reference to root ID of tab1.
// when(mTabGroupModelFilter.getTabGroupColor(mTab1.getRootId())).thenReturn(COLOR_2);
mModel.set(TabGridDialogProperties.TAB_GROUP_COLOR_ID, COLOR_2);
mMediator.onReset(tabGroup);
mMediator.setSelectedTabGroupColor(COLOR_3);
// Assert that the color has changed both in the property model and the model filter.
assertThat(mModel.get(TabGridDialogProperties.TAB_GROUP_COLOR_ID), equalTo(COLOR_3));
verify(mTabGroupModelFilter).setTabGroupColor(mTab1.getRootId(), COLOR_3);
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void showDialog_FromGTS() {
// Mock that the dialog is hidden and animation source view, header title and scrim click
// runnable are all null.
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, false);
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, null);
mModel.set(TabGridDialogProperties.HEADER_TITLE, null);
mModel.set(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE, null);
// Mock that tab1 and tab2 are in a group.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
when(mTabGroupModelFilter.getTabGroupColorWithFallback(mTab1.getRootId()))
.thenReturn(COLOR_2);
mMediator.onReset(tabGroup);
// Assert that a color and the incognito status were set.
assertThat(mModel.get(TabGridDialogProperties.IS_INCOGNITO), equalTo(false));
assertThat(mModel.get(TabGridDialogProperties.TAB_GROUP_COLOR_ID), equalTo(COLOR_2));
assertNull(mModel.get(TabGridDialogProperties.ANIMATION_BACKGROUND_COLOR));
assertThat(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE), equalTo(true));
// Scrim click runnable should be set as the current scrim runnable.
assertThat(
mModel.get(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE),
equalTo(mMediator.getScrimClickRunnableForTesting()));
// Animation source view should be updated with specific view.
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(mView));
// Dialog title should be updated.
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(DIALOG_TITLE2));
// Prepare dialog invoked.
verify(mDialogController).prepareDialog();
}
@Test
@EnableFeatures({
ChromeFeatureList.TAB_GROUP_PARITY_ANDROID,
ChromeFeatureList.FORCE_LIST_TAB_SWITCHER
})
public void showDialog_FromListGTS() {
// Mock that the dialog is hidden and animation source view, header title and scrim click
// runnable are all null.
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, false);
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, null);
mModel.set(TabGridDialogProperties.HEADER_TITLE, null);
mModel.set(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE, null);
// Mock that tab1 and tab2 are in a group.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
when(mTabGroupModelFilter.getTabGroupColorWithFallback(mTab1.getRootId()))
.thenReturn(COLOR_2);
mMediator.onReset(tabGroup);
// Assert that a color and the incognito status were set.
assertThat(mModel.get(TabGridDialogProperties.IS_INCOGNITO), equalTo(false));
assertThat(mModel.get(TabGridDialogProperties.TAB_GROUP_COLOR_ID), equalTo(COLOR_2));
int backgroundColor =
TabUiThemeUtils.getCardViewBackgroundColor(
mActivity, /* isIncognito= */ false, /* isSelected= */ false);
assertEquals(
mModel.get(TabGridDialogProperties.ANIMATION_BACKGROUND_COLOR).intValue(),
backgroundColor);
assertThat(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE), equalTo(true));
// Scrim click runnable should be set as the current scrim runnable.
assertThat(
mModel.get(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE),
equalTo(mMediator.getScrimClickRunnableForTesting()));
// Animation source view should be updated with specific view.
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(mView));
// Dialog title should be updated.
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(DIALOG_TITLE2));
// Prepare dialog invoked.
verify(mDialogController).prepareDialog();
}
@Test
public void showDialog_FromGTS_WithStoredTitle() {
// Mock that the dialog is hidden and animation source view, header title and scrim click
// runnable are all null.
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, false);
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, null);
mModel.set(TabGridDialogProperties.HEADER_TITLE, null);
mModel.set(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE, null);
// Mock that tab1 and tab2 are in a group.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
// Mock that we have a stored title stored with reference to root ID of tab1.
doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB1_ID);
mMediator.onReset(tabGroup);
assertThat(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE), equalTo(true));
// Scrim click runnable should be set as the current scrim runnable.
assertThat(
mModel.get(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE),
equalTo(mMediator.getScrimClickRunnableForTesting()));
// Animation source view should be updated with specific view.
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(mView));
// Dialog title should be updated with stored title.
assertThat(
mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void showDialog_FromStrip() {
// For strip we don't play zoom-in/zoom-out for show/hide dialog, and thus
// the animationParamsProvider is null.
mMediator =
new TabGridDialogMediator(
mActivity,
mDialogController,
mModel,
mCurrentTabModelFilterSupplier,
mTabCreatorManager,
mTabSwitcherResetHandler,
mRecyclerViewPositionSupplier,
/* animationSourceViewProvider= */ null,
mSnackbarManager,
mSharedImageTilesCoordinator,
mDataSharingTabManager,
/* componentName= */ "",
mShowColorPickerPopupRunnable,
mActionConfirmationManager);
mMediator.initWithNative(() -> mTabListEditorController, mTabGroupTitleEditor);
// Mock that the dialog is hidden and animation source view, header title and scrim click
// runnable are all null.
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, false);
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, null);
mModel.set(TabGridDialogProperties.HEADER_TITLE, null);
mModel.set(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE, null);
// Mock that tab1 and tab2 are in a group.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
when(mTabGroupModelFilter.getTabGroupColorWithFallback(mTab1.getRootId()))
.thenReturn(COLOR_2);
mMediator.onReset(tabGroup);
// Assert that a color and the incognito status were set.
assertThat(mModel.get(TabGridDialogProperties.IS_INCOGNITO), equalTo(false));
assertThat(mModel.get(TabGridDialogProperties.TAB_GROUP_COLOR_ID), equalTo(COLOR_2));
assertThat(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE), equalTo(true));
// Scrim observer should be set as the current scrim runnable.
assertThat(
mModel.get(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE),
equalTo(mMediator.getScrimClickRunnableForTesting()));
// Animation source view should not be specified.
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(null));
// Dialog title should be updated.
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(DIALOG_TITLE2));
// Prepare dialog invoked.
verify(mDialogController).prepareDialog();
}
@Test
public void showDialog_FromStrip_WithStoredTitle() {
// For strip we don't play zoom-in/zoom-out for show/hide dialog, and thus
// the animationParamsProvider is null.
mMediator =
new TabGridDialogMediator(
mActivity,
mDialogController,
mModel,
mCurrentTabModelFilterSupplier,
mTabCreatorManager,
mTabSwitcherResetHandler,
mRecyclerViewPositionSupplier,
/* animationSourceViewProvider= */ null,
mSnackbarManager,
mSharedImageTilesCoordinator,
mDataSharingTabManager,
/* componentName= */ "",
mShowColorPickerPopupRunnable,
mActionConfirmationManager);
mMediator.initWithNative(() -> mTabListEditorController, mTabGroupTitleEditor);
// Mock that the dialog is hidden and animation source view, header title and scrim click
// runnable are all null.
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, false);
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, null);
mModel.set(TabGridDialogProperties.HEADER_TITLE, null);
mModel.set(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE, null);
// Mock that tab1 and tab2 are in a group.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
// Mock that we have a stored title stored with reference to root ID of tab1.
doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB1_ID);
mMediator.onReset(tabGroup);
assertThat(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE), equalTo(true));
// Scrim observer should be set as the current scrim click runnable.
assertThat(
mModel.get(TabGridDialogProperties.SCRIMVIEW_CLICK_RUNNABLE),
equalTo(mMediator.getScrimClickRunnableForTesting()));
// Animation source view should not be specified.
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(null));
// Dialog title should be updated with stored title.
assertThat(
mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
}
@Test
public void showDialog_FromStrip_SetupAnimation() {
// For strip we don't play zoom-in/zoom-out for show/hide dialog, and thus
// the animationParamsProvider is null.
mMediator =
new TabGridDialogMediator(
mActivity,
mDialogController,
mModel,
mCurrentTabModelFilterSupplier,
mTabCreatorManager,
mTabSwitcherResetHandler,
mRecyclerViewPositionSupplier,
/* animationSourceViewProvider= */ null,
mSnackbarManager,
mSharedImageTilesCoordinator,
mDataSharingTabManager,
/* componentName= */ "",
mShowColorPickerPopupRunnable,
mActionConfirmationManager);
mMediator.initWithNative(() -> mTabListEditorController, mTabGroupTitleEditor);
// Mock that the dialog is hidden and animation source view is set to some mock view for
// testing purpose.
mModel.set(TabGridDialogProperties.IS_DIALOG_VISIBLE, false);
mModel.set(TabGridDialogProperties.ANIMATION_SOURCE_VIEW, mock(View.class));
// Mock that tab1 and tab2 are in a group.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
mMediator.onReset(tabGroup);
assertThat(mModel.get(TabGridDialogProperties.IS_DIALOG_VISIBLE), equalTo(true));
// Animation source view should be set to null so that dialog will setup basic animation.
assertThat(mModel.get(TabGridDialogProperties.ANIMATION_SOURCE_VIEW), equalTo(null));
}
@Test
public void testDialogToolbarMenu_SelectionModeV2() {
// Mock that currently the title text is focused and the keyboard is showing. The current
// tab is tab1 which is in a group of {tab1, tab2}.
mModel.set(TabGridDialogProperties.IS_TITLE_TEXT_FOCUSED, true);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
mMediator.onToolbarMenuItemClick(R.id.select_tabs, TAB1_ID, /* collaborationId= */ null);
assertThat(mModel.get(TabGridDialogProperties.IS_TITLE_TEXT_FOCUSED), equalTo(false));
verify(mRecyclerViewPositionSupplier, times(1)).get();
verify(mTabListEditorController).show(eq(tabGroup), eq(null));
assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.SelectTabs"));
}
@Test
public void testDialogToolbarMenu_EditGroupName() {
mModel.set(TabGridDialogProperties.IS_TITLE_TEXT_FOCUSED, false);
mMediator.setCurrentTabIdForTesting(TAB1_ID);
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
mMediator.onToolbarMenuItemClick(
R.id.edit_group_name, TAB1_ID, /* collaborationId= */ null);
assertTrue(mModel.get(TabGridDialogProperties.IS_TITLE_TEXT_FOCUSED));
assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.Rename"));
}
@Test
public void testDialogToolbarMenu_EditGroupColor() {
mMediator.setCurrentTabIdForTesting(TAB1_ID);
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
mMediator.onToolbarMenuItemClick(
R.id.edit_group_color, TAB1_ID, /* collaborationId= */ null);
verify(mShowColorPickerPopupRunnable).run();
assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.EditColor"));
}
@Test
public void testDialogToolbarMenu_CloseGroup() {
mMediator.setCurrentTabIdForTesting(TAB1_ID);
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
when(mTabGroupModelFilter.isIncognitoBranded()).thenReturn(true);
mMediator.onToolbarMenuItemClick(R.id.close_tab, TAB1_ID, /* collaborationId= */ null);
verify(mTabGroupModelFilter)
.closeTabs(TabClosureParams.closeTabs(tabGroup).hideTabGroups(true).build());
verifyNoInteractions(mActionConfirmationManager);
assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.Close"));
}
@Test
public void testDialogToolbarMenu_DeleteGroup() {
mMediator.setCurrentTabIdForTesting(TAB1_ID);
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
when(mTabGroupModelFilter.isIncognitoBranded()).thenReturn(true);
mMediator.onToolbarMenuItemClick(R.id.delete_tab, TAB1_ID, /* collaborationId= */ null);
verify(mTabGroupModelFilter).closeTabs(TabClosureParams.closeTabs(tabGroup).build());
assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.Delete"));
when(mTabGroupModelFilter.isIncognitoBranded()).thenReturn(false);
mMediator.onToolbarMenuItemClick(R.id.delete_tab, TAB1_ID, /* collaborationId= */ null);
verify(mActionConfirmationManager).processDeleteGroupAttempt(any());
assertEquals(2, mActionTester.getActionCount("TabGridDialogMenu.Delete"));
}
@Test
public void testDialogToolbarMenu_ManageSharing() {
resetForDataSharing(/* isShared= */ true);
mMediator.onToolbarMenuItemClick(R.id.manage_sharing, TAB1_ID, COLLABORATION_ID1);
assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.ManageSharing"));
verify(mDataSharingTabManager).showManageSharing(any(), eq(COLLABORATION_ID1));
}
@Test
public void testDialogToolbarMenu_RecentActivity() {
resetForDataSharing(/* isShared= */ true);
mMediator.onToolbarMenuItemClick(R.id.recent_activity, TAB1_ID, COLLABORATION_ID1);
assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.RecentActivity"));
verify(mDataSharingTabManager).showRecentActivity(COLLABORATION_ID1);
}
@Test
public void testDialogToolbarMenu_DeleteSharedGroup() {
resetForDataSharing(/* isShared= */ true);
mMediator.onToolbarMenuItemClick(R.id.delete_shared_group, TAB1_ID, COLLABORATION_ID1);
verify(mActionConfirmationManager).processDeleteSharedGroupAttempt(eq(GROUP_TITLE), any());
assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.DeleteShared"));
}
@Test
public void testDialogToolbarMenu_LeaveSharedGroup() {
resetForDataSharing(/* isShared= */ true);
CoreAccountInfo coreAccountInfo = CoreAccountInfo.createFromEmailAndGaiaId(EMAIL, GAIA_ID);
when(mIdentityManager.getPrimaryAccountInfo(anyInt())).thenReturn(coreAccountInfo);
mMediator.onToolbarMenuItemClick(R.id.leave_group, TAB1_ID, COLLABORATION_ID1);
verify(mActionConfirmationManager).processLeaveGroupAttempt(eq(GROUP_TITLE), any());
assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.LeaveShared"));
}
@Test
public void testSnackbarController_onAction_singleTab() {
mMediator.onAction(TAB1_ID);
verify(mTabModel).cancelTabClosure(eq(TAB1_ID));
}
@Test
public void testSnackbarController_onAction_multipleTabs() {
mMediator.onAction(Arrays.asList(mTab1, mTab2));
verify(mTabModel).cancelTabClosure(eq(TAB1_ID));
verify(mTabModel).cancelTabClosure(eq(TAB2_ID));
}
@Test
public void testSnackbarController_onDismissNoAction_singleTab() {
mMediator.onDismissNoAction(TAB1_ID);
verify(mTabModel).commitTabClosure(eq(TAB1_ID));
}
@Test
public void testSnackbarController_onDismissNoAction_multipleTabs() {
mMediator.onDismissNoAction(Arrays.asList(mTab1, mTab2));
verify(mTabModel).commitTabClosure(eq(TAB1_ID));
verify(mTabModel).commitTabClosure(eq(TAB2_ID));
}
@Test
public void testScrollToTab() {
// Mock that tab1, tab2 and newTab are in the same group and newTab is the root tab.
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE);
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
createTabGroup(tabGroup, TAB2_ID, TAB_GROUP_ID);
// Mock that mTab2 is the current tab for the dialog.
doReturn(0).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(0);
doReturn(tabGroup).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
// Reset and confirm scroll index.
mMediator.onReset(tabGroup);
assertEquals(1, mModel.get(TabGridDialogProperties.INITIAL_SCROLL_INDEX).intValue());
}
@Test
public void testTabUngroupBarText() {
// Mock that tab1 and tab2 are in the same group.
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, TAB1_ID, TAB_GROUP_ID);
mMediator.onReset(tabGroup);
// Check that the text indicates that this is not the last tab in the group.
assertEquals(
mActivity.getString(R.string.tab_grid_dialog_remove_from_group),
mModel.get(TabGridDialogProperties.DIALOG_UNGROUP_BAR_TEXT));
// Mock that tab1 is the only tab that remains in the group.
List<Tab> tabGroupAfterUngroup = new ArrayList<>(Arrays.asList(mTab1));
doReturn(tabGroupAfterUngroup).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
mMediator.onReset(tabGroupAfterUngroup);
// Check that the text indicates that this is the last tab in the group.
assertEquals(
mActivity.getString(R.string.remove_last_tab_action),
mModel.get(TabGridDialogProperties.DIALOG_UNGROUP_BAR_TEXT));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void testTabGroupColorUpdated() {
int rootId = TAB1_ID;
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, rootId, TAB_GROUP_ID);
mMediator.onReset(tabGroup);
assertNotEquals(mModel.get(TabGridDialogProperties.TAB_GROUP_COLOR_ID), COLOR_3);
mTabGroupModelFilterObserverCaptor.getValue().didChangeTabGroupColor(rootId, COLOR_3);
assertThat(mModel.get(TabGridDialogProperties.TAB_GROUP_COLOR_ID), equalTo(COLOR_3));
}
@Test
public void testTabGroupTitleUpdated() {
int rootId = TAB1_ID;
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, rootId, TAB_GROUP_ID);
when(mTabGroupTitleEditor.getTabGroupTitle(rootId)).thenReturn(CUSTOMIZED_DIALOG_TITLE);
mMediator.onReset(tabGroup);
assertEquals(mModel.get(TabGridDialogProperties.HEADER_TITLE), CUSTOMIZED_DIALOG_TITLE);
String newTitle = "BAR";
when(mTabGroupTitleEditor.getTabGroupTitle(rootId)).thenReturn(newTitle);
mTabGroupModelFilterObserverCaptor.getValue().didChangeTabGroupTitle(rootId, newTitle);
assertThat(mModel.get(TabGridDialogProperties.HEADER_TITLE), equalTo(newTitle));
}
@Test
public void destroy() {
mMediator.destroy();
verify(mTabGroupModelFilter).removeObserver(mTabModelObserverCaptor.capture());
assertFalse(mCurrentTabModelFilterSupplier.hasObservers());
}
@Test
public void testUpdateShareData_Incognito() {
reset(mSharedImageTilesCoordinator);
assertFalse(mModel.get(TabGridDialogProperties.SHOW_SHARE_BUTTON));
assertFalse(mModel.get(TabGridDialogProperties.SHOW_IMAGE_TILES));
when(mTabGroupModelFilter.isIncognitoBranded()).thenReturn(true);
resetForDataSharing(/* isShared= */ true);
assertFalse(mModel.get(TabGridDialogProperties.SHOW_SHARE_BUTTON));
assertFalse(mModel.get(TabGridDialogProperties.SHOW_IMAGE_TILES));
verify(mSharedImageTilesCoordinator).updateCollaborationId(null);
}
@Test
public void testShowOrUpdateCollaborationActivityMessageCard() {
reset(mSharedImageTilesCoordinator);
assertFalse(mModel.get(TabGridDialogProperties.SHOW_SHARE_BUTTON));
assertFalse(mModel.get(TabGridDialogProperties.SHOW_IMAGE_TILES));
when(mDialogController.messageCardExists(MessageType.COLLABORATION_ACTIVITY))
.thenReturn(true);
resetForDataSharing(/* isShared= */ true);
assertFalse(mModel.get(TabGridDialogProperties.SHOW_SHARE_BUTTON));
assertTrue(mModel.get(TabGridDialogProperties.SHOW_IMAGE_TILES));
verify(mSharedImageTilesCoordinator).updateCollaborationId(COLLABORATION_ID1);
verify(mDialogController, never()).addMessageCardItem(/* position= */ eq(0), any());
when(mDialogController.messageCardExists(MessageType.COLLABORATION_ACTIVITY))
.thenReturn(false);
resetForDataSharing(/* isShared= */ true);
verify(mDialogController).addMessageCardItem(/* position= */ eq(0), any());
resetForDataSharing(/* isShared= */ false);
assertTrue(mModel.get(TabGridDialogProperties.SHOW_SHARE_BUTTON));
assertFalse(mModel.get(TabGridDialogProperties.SHOW_IMAGE_TILES));
verify(mDialogController).removeMessageCardItem(MessageType.COLLABORATION_ACTIVITY);
verify(mSharedImageTilesCoordinator).updateCollaborationId(null);
}
@Test
public void testCollaborationActivityMessageCard_Dismiss() {
when(mDialogController.messageCardExists(MessageType.COLLABORATION_ACTIVITY))
.thenReturn(false);
resetForDataSharing(/* isShared= */ true);
verify(mDialogController)
.addMessageCardItem(
/* position= */ eq(0), mCollaborationActivityMessageCardCaptor.capture());
mCollaborationActivityMessageCardCaptor
.getValue()
.get(MESSAGE_SERVICE_DISMISS_ACTION_PROVIDER)
.dismiss(MessageType.COLLABORATION_ACTIVITY);
verify(mDialogController).removeMessageCardItem(MessageType.COLLABORATION_ACTIVITY);
}
@Test
public void testCollaborationActivityMessageCard_ClickNoCollaboration() {
resetForDataSharing(/* isShared= */ true);
verify(mDialogController)
.addMessageCardItem(
/* position= */ eq(0), mCollaborationActivityMessageCardCaptor.capture());
mMediator.setCurrentCollaborationIdForTesting(/* collaborationId= */ null);
mCollaborationActivityMessageCardCaptor
.getValue()
.get(MESSAGE_SERVICE_ACTION_PROVIDER)
.review();
verify(mDataSharingTabManager, never()).showRecentActivity(any());
verify(mDialogController).removeMessageCardItem(MessageType.COLLABORATION_ACTIVITY);
}
@Test
public void testCollaborationActivityMessageCard_Click() {
when(mDialogController.messageCardExists(MessageType.COLLABORATION_ACTIVITY))
.thenReturn(false);
resetForDataSharing(/* isShared= */ true);
verify(mDialogController)
.addMessageCardItem(
/* position= */ eq(0), mCollaborationActivityMessageCardCaptor.capture());
mCollaborationActivityMessageCardCaptor
.getValue()
.get(MESSAGE_SERVICE_ACTION_PROVIDER)
.review();
verify(mDataSharingTabManager).showRecentActivity(COLLABORATION_ID1);
}
private void resetForDataSharing(boolean isShared) {
int rootId = TAB1_ID;
List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabGroup, rootId, TAB_GROUP_ID);
setupSyncedGroup(isShared);
mMediator.onReset(tabGroup);
}
private void setupSyncedGroup(boolean isShared) {
SavedTabGroup savedTabGroup = new SavedTabGroup();
savedTabGroup.title = GROUP_TITLE;
savedTabGroup.collaborationId = isShared ? COLLABORATION_ID1 : null;
when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
}
private Tab prepareTab(int id, String title) {
Tab tab = TabUiUnitTestUtils.prepareTab(id, title, GURL.emptyGURL());
doReturn(true).when(tab).isIncognito();
return tab;
}
private void createTabGroup(List<Tab> tabs, int rootId, @Nullable Token tabGroupId) {
for (Tab tab : tabs) {
when(mTabGroupModelFilter.getRelatedTabList(tab.getId())).thenReturn(tabs);
when(mTabGroupModelFilter.getRelatedTabListForRootId(rootId)).thenReturn(tabs);
when(mTabGroupModelFilter.isTabInTabGroup(tab)).thenReturn(true);
when(tab.getRootId()).thenReturn(rootId);
when(tab.getTabGroupId()).thenReturn(tabGroupId);
}
}
}