blob: 3473b9b855073af09bb01ddcdeb380dc1860a0e9 [file] [log] [blame]
// 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.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLICK;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
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.Mockito.doAnswer;
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.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.chromium.chrome.browser.flags.ChromeFeatureList.START_SURFACE_ANDROID;
import static org.chromium.chrome.browser.tasks.tab_management.MessageCardViewProperties.MESSAGE_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.MessageService.MessageType.FOR_TESTING;
import static org.chromium.chrome.browser.tasks.tab_management.MessageService.MessageType.IPH;
import static org.chromium.chrome.browser.tasks.tab_management.MessageService.MessageType.PRICE_MESSAGE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.CARD_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.ModelType.MESSAGE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.ModelType.OTHERS;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.ModelType.TAB;
import android.app.Activity;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Pair;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.google.protobuf.ByteString;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.base.FeatureList;
import org.chromium.base.Token;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.shared_preferences.SharedPreferencesManager;
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;
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.build.BuildConfig;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge.OptimizationGuideCallback;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeFactory;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeFactoryJni;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
import org.chromium.chrome.browser.price_tracking.PriceTrackingUtilities;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.chrome.browser.tab.MockTab;
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.TabSelectionType;
import org.chromium.chrome.browser.tab.state.PersistedTabDataConfiguration;
import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData;
import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData.PriceDrop;
import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeatures;
import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeaturesJni;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider;
import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider.TabFavicon;
import org.chromium.chrome.browser.tab_ui.ThumbnailProvider;
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.TabGroupColorUtils;
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.PriceMessageService.PriceTabData;
import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
import org.chromium.chrome.browser.tasks.tab_management.TabListMediator.ShoppingPersistedTabDataFetcher;
import org.chromium.chrome.browser.tasks.tab_management.TabListMediator.ThumbnailFetcher;
import org.chromium.chrome.browser.tasks.tab_management.TabProperties.TabActionState;
import org.chromium.chrome.browser.tasks.tab_management.TabProperties.UiType;
import org.chromium.chrome.tab_ui.R;
import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
import org.chromium.components.commerce.PriceTracking.BuyableProduct;
import org.chromium.components.commerce.PriceTracking.PriceTrackingData;
import org.chromium.components.commerce.PriceTracking.ProductPrice;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.CommonTypesProto.Any;
import org.chromium.components.optimization_guide.proto.HintsProto;
import org.chromium.components.search_engines.TemplateUrlService;
import org.chromium.components.tab_groups.TabGroupColorId;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/** Tests for {@link TabListMediator}. */
@SuppressWarnings({
"ArraysAsListWithZeroOrOneArgument",
"ResultOfMethodCallIgnored",
"ConstantConditions"
})
@RunWith(BaseRobolectricTestRunner.class)
@Config(
manifest = Config.NONE,
instrumentedPackages = {
"androidx.recyclerview.widget.RecyclerView" // required to mock final
})
@LooperMode(LooperMode.Mode.LEGACY)
@EnableFeatures({
ChromeFeatureList.TAB_TO_GTS_ANIMATION,
ChromeFeatureList.ANDROID_TAB_GROUP_STABLE_IDS
})
@DisableFeatures({START_SURFACE_ANDROID})
public class TabListMediatorUnitTest {
@Rule public TestRule mProcessor = new Features.JUnitProcessor();
@Rule public JniMocker mMocker = new JniMocker();
private static final String TAB1_TITLE = "Tab1";
private static final String TAB2_TITLE = "Tab2";
private static final String TAB3_TITLE = "Tab3";
private static final String TAB4_TITLE = "Tab4";
private static final String TAB5_TITLE = "Tab5";
private static final String TAB6_TITLE = "Tab6";
private static final String TAB7_TITLE = "Tab7";
private static final String NEW_TITLE = "New title";
private static final String CUSTOMIZED_DIALOG_TITLE1 = "Cool Tabs";
private static final String TAB_GROUP_COLORS_FILE_NAME = "tab_group_colors";
private static final String TAB_GROUP_TITLES_FILE_NAME = "tab_group_titles";
private static final GURL TAB1_URL = JUnitTestGURLs.URL_1;
private static final GURL TAB2_URL = JUnitTestGURLs.URL_2;
private static final GURL TAB3_URL = JUnitTestGURLs.URL_3;
private static final GURL TAB4_URL = JUnitTestGURLs.RED_1;
private static final GURL TAB5_URL = JUnitTestGURLs.RED_2;
private static final GURL TAB6_URL = JUnitTestGURLs.RED_3;
private static final GURL TAB7_URL = JUnitTestGURLs.URL_1;
private static final String NEW_URL = JUnitTestGURLs.EXAMPLE_URL.getSpec();
private static final int COLOR_2 = 1;
private static final int TAB1_ID = 456;
private static final int TAB2_ID = 789;
private static final int TAB3_ID = 123;
private static final int TAB4_ID = 290;
private static final int TAB5_ID = 147;
private static final int TAB6_ID = 258;
private static final int TAB7_ID = 369;
private static final int POSITION1 = 0;
private static final int POSITION2 = 1;
private static final Token TAB_GROUP_ID = new Token(829L, 283L);
private static final BuyableProduct BUYABLE_PRODUCT_PROTO_INITIAL =
BuyableProduct.newBuilder()
.setCurrentPrice(createProductPrice(123456789012345L, "USD"))
.build();
private static ProductPrice createProductPrice(long amountMicros, String currencyCode) {
return ProductPrice.newBuilder()
.setCurrencyCode(currencyCode)
.setAmountMicros(amountMicros)
.build();
}
private static final PriceTrackingData PRICE_TRACKING_BUYABLE_PRODUCT_INITIAL =
PriceTrackingData.newBuilder().setBuyableProduct(BUYABLE_PRODUCT_PROTO_INITIAL).build();
private static final Any ANY_BUYABLE_PRODUCT_INITIAL =
Any.newBuilder()
.setValue(
ByteString.copyFrom(
PRICE_TRACKING_BUYABLE_PRODUCT_INITIAL.toByteArray()))
.build();
private static final Any ANY_EMPTY = Any.newBuilder().build();
@IntDef({
TabListMediatorType.TAB_SWITCHER,
TabListMediatorType.TAB_STRIP,
TabListMediatorType.TAB_GRID_DIALOG
})
@Retention(RetentionPolicy.SOURCE)
public @interface TabListMediatorType {
int TAB_SWITCHER = 0;
int TAB_STRIP = 1;
int TAB_GRID_DIALOG = 2;
int NUM_ENTRIES = 3;
}
@Mock TabContentManager mTabContentManager;
@Mock TabModel mTabModel;
@Mock TabModel mIncognitoTabModel;
@Mock TabListFaviconProvider mTabListFaviconProvider;
@Mock TabGroupColorFaviconProvider mTabGroupColorFaviconProvider;
@Mock TabListFaviconProvider.TabFaviconFetcher mTabFaviconFetcher;
@Mock RecyclerView mRecyclerView;
@Mock TabListRecyclerView mTabListRecyclerView;
@Mock RecyclerView.Adapter mAdapter;
@Mock TabGroupModelFilter mTabGroupModelFilter;
@Mock TabGroupModelFilter mIncognitoTabGroupModelFilter;
@Mock TabListMediator.TabGridDialogHandler mTabGridDialogHandler;
@Mock TabListMediator.GridCardOnClickListenerProvider mGridCardOnClickListenerProvider;
@Mock TabFavicon mFavicon;
@Mock Bitmap mFaviconBitmap;
@Mock Activity mActivity;
@Mock TabListMediator.TabActionListener mOpenGroupActionListener;
@Mock GridLayoutManager mGridLayoutManager;
@Mock GridLayoutManager.SpanSizeLookup mSpanSizeLookup;
@Mock Profile mProfile;
@Mock Tracker mTracker;
@Mock UrlUtilities.Natives mUrlUtilitiesJniMock;
@Mock OptimizationGuideBridgeFactory.Natives mOptimizationGuideBridgeFactoryJniMock;
@Mock OptimizationGuideBridge mOptimizationGuideBridge;
@Mock TabListMediator.TabGridAccessibilityHelper mTabGridAccessibilityHelper;
@Mock TemplateUrlService mTemplateUrlService;
@Mock PriceWelcomeMessageController mPriceWelcomeMessageController;
@Mock ShoppingPersistedTabData mShoppingPersistedTabData;
@Mock SelectionDelegate<Integer> mSelectionDelegate;
@Mock ModalDialogManager mModalDialogManager;
@Mock ActionConfirmationManager mActionConfirmationManager;
@Mock TabGroupSyncFeatures.Natives mTabGroupSyncFeaturesJniMock;
@Captor ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
@Captor ArgumentCaptor<TabObserver> mTabObserverCaptor;
@Captor ArgumentCaptor<Callback<TabFavicon>> mCallbackCaptor;
@Captor ArgumentCaptor<TabGroupModelFilterObserver> mTabGroupModelFilterObserverCaptor;
@Captor ArgumentCaptor<ComponentCallbacks> mComponentCallbacksCaptor;
@Captor
ArgumentCaptor<TemplateUrlService.TemplateUrlServiceObserver> mTemplateUrlServiceObserver;
@Captor ArgumentCaptor<RecyclerView.OnScrollListener> mOnScrollListenerCaptor;
private final ObservableSupplierImpl<TabModelFilter> mCurrentTabModelFilterSupplier =
new ObservableSupplierImpl<>();
private Tab mTab1;
private Tab mTab2;
private TabListMediator mMediator;
private TabListModel mModel;
private SimpleRecyclerViewAdapter.ViewHolder mViewHolder1;
private SimpleRecyclerViewAdapter.ViewHolder mViewHolder2;
private RecyclerView.ViewHolder mFakeViewHolder1;
private RecyclerView.ViewHolder mFakeViewHolder2;
private View mItemView1 = mock(View.class);
private View mItemView2 = mock(View.class);
private PriceDrop mPriceDrop;
private PriceTabData mPriceTabData;
private String mTab1Domain;
private String mTab2Domain;
private String mTab3Domain;
private String mNewDomain;
private GURL mFaviconUrl;
private Resources mResources;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mMocker.mock(UrlUtilitiesJni.TEST_HOOKS, mUrlUtilitiesJniMock);
mMocker.mock(
OptimizationGuideBridgeFactoryJni.TEST_HOOKS,
mOptimizationGuideBridgeFactoryJniMock);
mMocker.mock(TabGroupSyncFeaturesJni.TEST_HOOKS, mTabGroupSyncFeaturesJniMock);
doReturn(mOptimizationGuideBridge)
.when(mOptimizationGuideBridgeFactoryJniMock)
.getForProfile(mProfile);
mResources = spy(RuntimeEnvironment.application.getResources());
when(mActivity.getResources()).thenReturn(mResources);
when(mResources.getInteger(org.chromium.ui.R.integer.min_screen_width_bucket))
.thenReturn(1);
mTab1Domain = TAB1_URL.getHost().replace("www.", "");
mTab2Domain = TAB2_URL.getHost().replace("www.", "");
mTab3Domain = TAB3_URL.getHost().replace("www.", "");
mNewDomain = new GURL(NEW_URL).getHost().replace("www.", "");
mFaviconUrl = JUnitTestGURLs.RED_1;
mTab1 = prepareTab(TAB1_ID, TAB1_TITLE, TAB1_URL);
mTab2 = prepareTab(TAB2_ID, TAB2_TITLE, TAB2_URL);
mViewHolder1 = prepareViewHolder(TAB1_ID, POSITION1);
mViewHolder2 = prepareViewHolder(TAB2_ID, POSITION2);
mFakeViewHolder1 = prepareFakeViewHolder(mItemView1, POSITION1);
mFakeViewHolder2 = prepareFakeViewHolder(mItemView2, POSITION2);
List<Tab> tabs1 = new ArrayList<>(Arrays.asList(mTab1));
List<Tab> tabs2 = new ArrayList<>(Arrays.asList(mTab2));
doNothing()
.when(mTabContentManager)
.getTabThumbnailWithCallback(anyInt(), any(), any(), anyBoolean(), anyBoolean());
// Mock that tab restoring stage is over.
doReturn(true).when(mTabGroupModelFilter).isTabModelRestored();
doReturn(true).when(mIncognitoTabGroupModelFilter).isTabModelRestored();
doReturn(mProfile).when(mTabModel).getProfile();
doReturn(mTabModel).when(mTabGroupModelFilter).getTabModel();
doReturn(mIncognitoTabModel).when(mIncognitoTabGroupModelFilter).getTabModel();
mCurrentTabModelFilterSupplier.set(mTabGroupModelFilter);
doNothing().when(mTabGroupModelFilter).addObserver(mTabModelObserverCaptor.capture());
doReturn(mTab1).when(mTabModel).getTabAt(POSITION1);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION2);
doReturn(POSITION1).when(mTabModel).indexOf(mTab1);
doReturn(POSITION2).when(mTabModel).indexOf(mTab2);
doReturn(POSITION1).when(mTabModel).index();
doReturn(mTab1).when(mIncognitoTabModel).getTabAt(POSITION1);
doReturn(mTab2).when(mIncognitoTabModel).getTabAt(POSITION2);
doNothing().when(mTab1).addObserver(mTabObserverCaptor.capture());
doReturn(0).when(mTabModel).index();
doReturn(2).when(mTabModel).getCount();
doReturn(2).when(mIncognitoTabModel).getCount();
doNothing()
.when(mTabListFaviconProvider)
.getFaviconForUrlAsync(any(GURL.class), anyBoolean(), mCallbackCaptor.capture());
doReturn(mFavicon)
.when(mTabListFaviconProvider)
.getFaviconFromBitmap(any(Bitmap.class), any(GURL.class));
doNothing().when(mTabFaviconFetcher).fetch(mCallbackCaptor.capture());
doReturn(mTabFaviconFetcher)
.when(mTabListFaviconProvider)
.getDefaultFaviconFetcher(anyBoolean());
doReturn(mTabFaviconFetcher)
.when(mTabListFaviconProvider)
.getFaviconForUrlFetcher(any(GURL.class), anyBoolean());
doReturn(mTabFaviconFetcher)
.when(mTabListFaviconProvider)
.getFaviconFromBitmapFetcher(any(Bitmap.class), any(GURL.class));
doReturn(mTabFaviconFetcher)
.when(mTabListFaviconProvider)
.getComposedFaviconImageFetcher(any(), anyBoolean());
doReturn(mTabFaviconFetcher)
.when(mTabGroupColorFaviconProvider)
.getFaviconFromTabGroupColorFetcher(anyInt(), anyBoolean());
doReturn(2).when(mTabGroupModelFilter).getCount();
doReturn(tabs1).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(tabs2).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(POSITION2).when(mTabGroupModelFilter).indexOf(mTab2);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(mOpenGroupActionListener)
.when(mGridCardOnClickListenerProvider)
.openTabGridDialog(any(Tab.class));
doNothing().when(mActivity).registerComponentCallbacks(mComponentCallbacksCaptor.capture());
doReturn(mGridLayoutManager).when(mRecyclerView).getLayoutManager();
doReturn(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_COMPACT)
.when(mGridLayoutManager)
.getSpanCount();
doReturn(mSpanSizeLookup).when(mGridLayoutManager).getSpanSizeLookup();
doReturn(mTab1Domain)
.when(mUrlUtilitiesJniMock)
.getDomainAndRegistry(eq(TAB1_URL.getSpec()), anyBoolean());
doReturn(mTab2Domain)
.when(mUrlUtilitiesJniMock)
.getDomainAndRegistry(eq(TAB2_URL.getSpec()), anyBoolean());
doReturn(mTab3Domain)
.when(mUrlUtilitiesJniMock)
.getDomainAndRegistry(eq(TAB3_URL.getSpec()), anyBoolean());
doNothing().when(mTemplateUrlService).addObserver(mTemplateUrlServiceObserver.capture());
doReturn(true).when(mTabListFaviconProvider).isInitialized();
mModel = new TabListModel();
TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService);
PriceTrackingFeatures.setPriceTrackingEnabledForTesting(false);
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.GRID);
doAnswer(
invocation -> {
int position = invocation.getArgument(0);
int itemType = mModel.get(position).type;
if (itemType == UiType.MESSAGE || itemType == UiType.LARGE_MESSAGE) {
return mGridLayoutManager.getSpanCount();
}
return 1;
})
.when(mSpanSizeLookup)
.getSpanSize(anyInt());
doAnswer(
invocation -> {
int rootId = invocation.getArgument(0);
String title = invocation.getArgument(1);
when(mTabGroupModelFilter.getTabGroupTitle(rootId)).thenReturn(title);
return null;
})
.when(mTabGroupModelFilter)
.setTabGroupTitle(anyInt(), anyString());
}
@After
public void tearDown() {
ProfileManager.resetForTesting();
}
private static SharedPreferences getGroupColorSharedPreferences() {
return ContextUtils.getApplicationContext()
.getSharedPreferences(TAB_GROUP_COLORS_FILE_NAME, Context.MODE_PRIVATE);
}
private static SharedPreferences getGroupTitleSharedPreferences() {
return ContextUtils.getApplicationContext()
.getSharedPreferences(TAB_GROUP_TITLES_FILE_NAME, Context.MODE_PRIVATE);
}
@Test
public void initializesWithCurrentTabs() {
initAndAssertAllProperties();
}
@Test
public void resetWithNullTabs() {
mMediator.resetWithListOfTabs(null, false);
verify(mTabGroupModelFilter).removeObserver(any());
verify(mTabGroupModelFilter).removeTabGroupObserver(any());
}
@Test
public void updatesTitle_WithoutStoredTitle_Tab() {
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
when(mTab1.getTitle()).thenReturn(NEW_TITLE);
mTabObserverCaptor.getValue().onTitleUpdated(mTab1);
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(NEW_TITLE));
}
@Test
public void updatesTitle_WithoutStoredTitle_TabGroup() {
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(tabs, false);
String defaultTitle = TabGroupTitleEditor.getDefaultTitle(mActivity, tabs.size());
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(defaultTitle));
}
@Test
public void updatesTitle_WithStoredTitle_TabGroup() {
// Mock that tab1 and new tab are in the same group with root ID as TAB1_ID.
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
// Mock that we have a stored title stored with reference to root ID of tab1.
mTabGroupModelFilter.setTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
mTabObserverCaptor.getValue().onTitleUpdated(mTab1);
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE1));
}
@Test
public void updatesTitle_OnTabGroupTitleChange() {
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
mTabGroupModelFilter.setTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
mTabGroupModelFilterObserverCaptor
.getValue()
.didChangeTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE1));
}
@Test
public void updatesTitle_OnTabGroupTitleChange_Tab() {
mTabGroupModelFilter.setTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
mTabGroupModelFilterObserverCaptor
.getValue()
.didChangeTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
// Ignored as the tab is not in a group.
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
}
@Test
public void updatesTitle_OnTabGroupTitleChange_Empty() {
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
mTabGroupModelFilter.setTabGroupTitle(mTab1.getRootId(), "");
mTabGroupModelFilterObserverCaptor.getValue().didChangeTabGroupTitle(mTab1.getRootId(), "");
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo("2 tabs"));
}
@Test
public void updatesTitle_OnTabGroupColorChange_Tab() {
doReturn(mock(TabListFaviconProvider.TabFaviconFetcher.class))
.when(mTabGroupColorFaviconProvider)
.getFaviconFromTabGroupColorFetcher(anyInt(), anyBoolean());
var oldFaviconFetcher = mModel.get(0).model.get(TabProperties.FAVICON_FETCHER);
mTabGroupModelFilter.setTabGroupColor(mTab1.getRootId(), TabGroupColorId.BLUE);
mTabGroupModelFilterObserverCaptor
.getValue()
.didChangeTabGroupColor(mTab1.getRootId(), TabGroupColorId.BLUE);
// Ignored as the tab is not in a group.
assertThat(
mModel.get(0).model.get(TabProperties.FAVICON_FETCHER), equalTo(oldFaviconFetcher));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void updatesColor_OnTabGroupColorChange_Grid() {
doReturn(mock(TabListFaviconProvider.TabFaviconFetcher.class))
.when(mTabGroupColorFaviconProvider)
.getFaviconFromTabGroupColorFetcher(anyInt(), anyBoolean());
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
var oldFaviconFetcher = mModel.get(0).model.get(TabProperties.FAVICON_FETCHER);
mTabGroupModelFilter.setTabGroupColor(mTab1.getRootId(), TabGroupColorId.BLUE);
mTabGroupModelFilterObserverCaptor
.getValue()
.didChangeTabGroupColor(mTab1.getRootId(), TabGroupColorId.BLUE);
assertNotEquals(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER), oldFaviconFetcher);
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void updatesColor_OnTabGroupColorChange_List() {
mTabGroupModelFilter.setTabGroupColor(mTab1.getRootId(), TabGroupColorId.GREY);
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
assertNotEquals(
mModel.get(0).model.get(TabProperties.TAB_GROUP_COLOR_ID), TabGroupColorId.BLUE);
mTabGroupModelFilter.setTabGroupColor(mTab1.getRootId(), TabGroupColorId.BLUE);
mTabGroupModelFilterObserverCaptor
.getValue()
.didChangeTabGroupColor(mTab1.getRootId(), TabGroupColorId.BLUE);
assertEquals(
mModel.get(0).model.get(TabProperties.TAB_GROUP_COLOR_ID), TabGroupColorId.BLUE);
}
@Test
public void updatesFaviconFetcher_SingleTab_GTS() {
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap, mFaviconUrl);
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
TabListFaviconProvider.TabFavicon[] favicon = new TabListFaviconProvider.TabFavicon[1];
mModel.get(0)
.model
.get(TabProperties.FAVICON_FETCHER)
.fetch(
tabFavicon -> {
favicon[0] = tabFavicon;
});
mCallbackCaptor.getValue().onResult(mFavicon);
assertEquals(favicon[0], mFavicon);
}
@Test
public void updatesFaviconFetcher_SingleTabGroup_GTS() {
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
createTabGroup(Arrays.asList(mTab1), TAB1_ID, TAB_GROUP_ID);
var oldThumbnailFetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap, mFaviconUrl);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
assertNotEquals(
oldThumbnailFetcher, mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void updatesFaviconFetcher_SingleTab_NonGTS() {
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap, mFaviconUrl);
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
TabListFaviconProvider.TabFavicon[] favicon = new TabListFaviconProvider.TabFavicon[1];
mModel.get(0)
.model
.get(TabProperties.FAVICON_FETCHER)
.fetch(
tabFavicon -> {
favicon[0] = tabFavicon;
});
mCallbackCaptor.getValue().onResult(mFavicon);
assertEquals(favicon[0], mFavicon);
}
@Test
@DisableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void updatesFaviconFetcher_TabGroup_GTS() {
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
// Assert that tab1 is in a group.
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
createTabGroup(Arrays.asList(mTab1, newTab), TAB1_ID, TAB_GROUP_ID);
var oldThumbnailFetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap, mFaviconUrl);
// Change to non-null once parity is launched.
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
assertNotEquals(
oldThumbnailFetcher, mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void updatesFaviconFetcher_TabGroup_ListGTS() {
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
// Assert that tab1 is in a group.
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
createTabGroup(Arrays.asList(mTab1, newTab), TAB1_ID, TAB_GROUP_ID);
List<GURL> urls = Arrays.asList(mTab1.getUrl(), newTab.getUrl());
mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap, mFaviconUrl);
verify(mTabListFaviconProvider).getComposedFaviconImageFetcher(eq(urls), anyBoolean());
assertNull(mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
public void updatesFaviconFetcher_TabGroup_ListGTS_SingleTab() {
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
// Assert that tab1 is in a group.
createTabGroup(Arrays.asList(mTab1), TAB1_ID, TAB_GROUP_ID);
List<GURL> urls = Arrays.asList(mTab1.getUrl());
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
assertNull(mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap, mFaviconUrl);
// Don't use the composed fetcher if there is only a single tab.
verify(mTabListFaviconProvider, never())
.getComposedFaviconImageFetcher(eq(urls), anyBoolean());
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
assertNull(mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void updatesFaviconFetcher_Navigation_NoOpSameDocument() {
doReturn(mFavicon).when(mTabListFaviconProvider).getDefaultFavicon(anyBoolean());
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
NavigationHandle navigationHandle = mock(NavigationHandle.class);
when(navigationHandle.getUrl()).thenReturn(TAB2_URL);
when(navigationHandle.isSameDocument()).thenReturn(true);
mTabObserverCaptor
.getValue()
.onDidStartNavigationInPrimaryMainFrame(mTab1, navigationHandle);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
public void updatesFaviconFetcher_Navigation_NoOpSameUrl() {
doReturn(mFavicon).when(mTabListFaviconProvider).getDefaultFavicon(anyBoolean());
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
NavigationHandle navigationHandle = mock(NavigationHandle.class);
when(navigationHandle.getUrl()).thenReturn(TAB1_URL);
when(navigationHandle.isSameDocument()).thenReturn(false);
mTabObserverCaptor
.getValue()
.onDidStartNavigationInPrimaryMainFrame(mTab1, navigationHandle);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
public void updatesFaviconFetcher_Navigation_NoOpNtpUrl() {
doReturn(mFavicon).when(mTabListFaviconProvider).getDefaultFavicon(anyBoolean());
GURL ntpUrl = JUnitTestGURLs.NTP_URL;
doReturn("")
.when(mUrlUtilitiesJniMock)
.getDomainAndRegistry(eq(ntpUrl.getSpec()), anyBoolean());
NavigationHandle navigationHandle = mock(NavigationHandle.class);
when(navigationHandle.getUrl()).thenReturn(TAB2_URL);
when(navigationHandle.isSameDocument()).thenReturn(false);
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, ntpUrl);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(0);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(1);
doReturn(newTab).when(mTabGroupModelFilter).getTabAt(2);
doReturn(3).when(mTabGroupModelFilter).getCount();
doReturn(Arrays.asList(newTab)).when(mTabGroupModelFilter).getRelatedTabList(eq(TAB3_ID));
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor
.getValue()
.didAddTab(
newTab,
TabLaunchType.FROM_CHROME_UI,
TabCreationState.LIVE_IN_FOREGROUND,
false);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.get(2).model.get(TabProperties.TAB_ID), equalTo(TAB3_ID));
assertThat(mModel.get(2).model.get(TabProperties.TITLE), equalTo(TAB3_TITLE));
verify(newTab).addObserver(mTabObserverCaptor.getValue());
mModel.get(2).model.set(TabProperties.FAVICON_FETCHER, null);
assertNull(mModel.get(2).model.get(TabProperties.FAVICON_FETCHER));
mTabObserverCaptor
.getValue()
.onDidStartNavigationInPrimaryMainFrame(newTab, navigationHandle);
assertNull(mModel.get(2).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
public void updatesFaviconFetcher_Navigation() {
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
NavigationHandle navigationHandle = mock(NavigationHandle.class);
when(navigationHandle.isSameDocument()).thenReturn(false);
when(navigationHandle.getUrl()).thenReturn(TAB2_URL);
mTabObserverCaptor
.getValue()
.onDidStartNavigationInPrimaryMainFrame(mTab1, navigationHandle);
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
public void updatesFaviconFetcher_Navigation_NoOpTabGroup() {
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(true);
NavigationHandle navigationHandle = mock(NavigationHandle.class);
when(navigationHandle.isSameDocument()).thenReturn(false);
when(navigationHandle.getUrl()).thenReturn(TAB2_URL);
mTabObserverCaptor
.getValue()
.onDidStartNavigationInPrimaryMainFrame(mTab1, navigationHandle);
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
public void sendsSelectSignalCorrectly() {
mModel.get(1)
.model
.get(TabProperties.TAB_SELECTED_LISTENER)
.run(mModel.get(1).model.get(TabProperties.TAB_ID));
verify(mGridCardOnClickListenerProvider)
.onTabSelecting(mModel.get(1).model.get(TabProperties.TAB_ID), true);
}
@Test
public void sendsOpenGroupSignalCorrectly_SingleTabGroup() {
List<Tab> tabs = Arrays.asList(mTab1);
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(Arrays.asList(mTab1, mTab2), false);
mModel.get(0)
.model
.get(TabProperties.TAB_SELECTED_LISTENER)
.run(mModel.get(0).model.get(TabProperties.TAB_ID));
verify(mOpenGroupActionListener).run(TAB1_ID);
}
@Test
public void sendsOpenGroupSignalCorrectly_TabGroup() {
List<Tab> tabs = Arrays.asList(mTab1, mTab2);
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(Arrays.asList(mTab1, mTab2), false);
mModel.get(0)
.model
.get(TabProperties.TAB_SELECTED_LISTENER)
.run(mModel.get(0).model.get(TabProperties.TAB_ID));
verify(mOpenGroupActionListener).run(TAB1_ID);
}
@Test
public void sendsCloseSignalCorrectly() {
mModel.get(1)
.model
.get(TabProperties.TAB_ACTION_BUTTON_LISTENER)
.run(mModel.get(1).model.get(TabProperties.TAB_ID));
verify(mTabModel).closeTab(eq(mTab2), eq(null), eq(false), eq(true));
}
@Test
public void sendsMoveTabSignalCorrectlyWithGroup() {
TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true);
itemTouchHelperCallback.onMove(mRecyclerView, mViewHolder1, mViewHolder2);
verify(mTabGroupModelFilter).moveRelatedTabs(eq(TAB1_ID), eq(2));
}
@Test
public void sendsMoveTabSignalCorrectlyWithinGroup() {
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
getItemTouchHelperCallback().onMove(mRecyclerView, mViewHolder1, mViewHolder2);
verify(mTabModel).moveTab(eq(TAB1_ID), eq(2));
}
@Test
public void sendsMergeTabSignalCorrectly() {
TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true);
itemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
itemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
itemTouchHelperCallback.getMovementFlags(mRecyclerView, mFakeViewHolder1);
doReturn(mAdapter).when(mRecyclerView).getAdapter();
// Simulate the drop action.
itemTouchHelperCallback.onSelectedChanged(
mFakeViewHolder2, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mTabGroupModelFilter).mergeTabsToGroup(eq(TAB2_ID), eq(TAB1_ID));
verify(mGridLayoutManager).removeView(mItemView2);
verify(mTracker).notifyEvent(eq(EventConstants.TAB_DRAG_AND_DROP_TO_GROUP));
}
// Regression test for https://crbug.com/1372487
@Test
public void handlesGroupMergeCorrectly_InOrder() {
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
Tab tab4 = prepareTab(TAB4_ID, TAB4_TITLE, TAB4_URL);
when(mTabModel.getTabAt(2)).thenReturn(tab3);
when(mTabModel.getTabAt(3)).thenReturn(tab4);
View itemView3 = mock(View.class);
View itemView4 = mock(View.class);
RecyclerView.ViewHolder fakeViewHolder3 = prepareFakeViewHolder(itemView3, 2);
RecyclerView.ViewHolder fakeViewHolder4 = prepareFakeViewHolder(itemView4, 3);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3, tab4));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(4));
// Merge 2 to 1.
TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true);
itemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
itemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
itemTouchHelperCallback.getMovementFlags(mRecyclerView, mFakeViewHolder1);
doReturn(mAdapter).when(mRecyclerView).getAdapter();
itemTouchHelperCallback.onSelectedChanged(
mFakeViewHolder2, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mTabGroupModelFilter).mergeTabsToGroup(eq(TAB2_ID), eq(TAB1_ID));
verify(mGridLayoutManager).removeView(mItemView2);
verify(mTracker).notifyEvent(eq(EventConstants.TAB_DRAG_AND_DROP_TO_GROUP));
when(mTabGroupModelFilter.getRelatedTabList(TAB2_ID))
.thenReturn(Arrays.asList(mTab1, mTab2));
when(mTabModel.indexOf(mTab1)).thenReturn(POSITION1);
when(mTabModel.indexOf(mTab2)).thenReturn(POSITION2);
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(mTab2, TAB1_ID);
assertThat(mModel.size(), equalTo(3));
mFakeViewHolder1 = prepareFakeViewHolder(mItemView1, 0);
fakeViewHolder3 = prepareFakeViewHolder(itemView3, 1);
fakeViewHolder4 = prepareFakeViewHolder(itemView4, 2);
// Merge 4 to 3.
when(mTabGroupModelFilter.getTabAt(1)).thenReturn(tab3);
when(mTabGroupModelFilter.getTabAt(2)).thenReturn(tab4);
itemTouchHelperCallback.setHoveredTabIndexForTesting(1);
itemTouchHelperCallback.setSelectedTabIndexForTesting(2);
itemTouchHelperCallback.getMovementFlags(mRecyclerView, fakeViewHolder3);
itemTouchHelperCallback.onSelectedChanged(
fakeViewHolder4, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mTabGroupModelFilter).mergeTabsToGroup(eq(TAB4_ID), eq(TAB3_ID));
verify(mGridLayoutManager).removeView(itemView4);
verify(mTracker, times(2)).notifyEvent(eq(EventConstants.TAB_DRAG_AND_DROP_TO_GROUP));
when(mTabGroupModelFilter.getRelatedTabList(TAB4_ID)).thenReturn(Arrays.asList(tab3, tab4));
when(mTabGroupModelFilter.getRelatedTabList(TAB3_ID)).thenReturn(Arrays.asList(tab3, tab4));
when(mTabModel.indexOf(tab3)).thenReturn(2);
when(mTabModel.indexOf(tab4)).thenReturn(3);
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(tab4, TAB3_ID);
assertThat(mModel.size(), equalTo(2));
mFakeViewHolder1 = prepareFakeViewHolder(mItemView1, 0);
fakeViewHolder3 = prepareFakeViewHolder(itemView3, 1);
// Merge 3 to 1.
when(mTabGroupModelFilter.getTabAt(0)).thenReturn(mTab1);
when(mTabGroupModelFilter.getTabAt(1)).thenReturn(tab3);
itemTouchHelperCallback.setHoveredTabIndexForTesting(0);
itemTouchHelperCallback.setSelectedTabIndexForTesting(1);
itemTouchHelperCallback.getMovementFlags(mRecyclerView, mFakeViewHolder1);
itemTouchHelperCallback.onSelectedChanged(
fakeViewHolder3, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mTabGroupModelFilter).mergeTabsToGroup(eq(TAB3_ID), eq(TAB1_ID));
verify(mGridLayoutManager).removeView(itemView3);
verify(mTracker, times(3)).notifyEvent(eq(EventConstants.TAB_DRAG_AND_DROP_TO_GROUP));
when(mTabGroupModelFilter.getRelatedTabList(TAB3_ID))
.thenReturn(Arrays.asList(mTab1, mTab2, tab3, tab4));
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(tab3, TAB1_ID);
assertThat(mModel.size(), equalTo(1));
}
@Test
public void sendsUngroupSignalCorrectly() {
TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(false);
itemTouchHelperCallback.setUnGroupTabIndexForTesting(POSITION1);
itemTouchHelperCallback.getMovementFlags(mRecyclerView, mFakeViewHolder1);
doReturn(mAdapter).when(mRecyclerView).getAdapter();
doReturn(1).when(mAdapter).getItemCount();
// Simulate the ungroup action.
itemTouchHelperCallback.onSelectedChanged(
mFakeViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
verify(mTabGroupModelFilter).moveTabOutOfGroup(eq(TAB1_ID));
verify(mGridLayoutManager).removeView(mItemView1);
}
@Test
public void tabClosure() {
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor.getValue().willCloseTab(mTab2, true);
verify(mTab2).removeObserver(any());
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
}
@Test
public void tabRemoval() {
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor.getValue().tabRemoved(mTab2);
verify(mTab2).removeObserver(any());
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
}
@Test
public void tabClosure_IgnoresUpdatesForTabsOutsideOfModel() {
mTabModelObserverCaptor
.getValue()
.willCloseTab(prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL), true);
assertThat(mModel.size(), equalTo(2));
}
@Test
public void tabAddition_Restore_SyncingTabListModelWithTabModel() {
// Mock that tab1 and tab2 are in the same group, and they are being restored. The
// TabListModel has been cleaned out before the restoring happens. This case could happen
// within a incognito tab group when user switches between light/dark mode.
createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID, TAB_GROUP_ID);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(1).when(mTabGroupModelFilter).getCount();
mModel.clear();
mTabModelObserverCaptor
.getValue()
.didAddTab(
mTab2,
TabLaunchType.FROM_RESTORE,
TabCreationState.LIVE_IN_FOREGROUND,
false);
assertThat(mModel.size(), equalTo(0));
mTabModelObserverCaptor
.getValue()
.didAddTab(
mTab1,
TabLaunchType.FROM_RESTORE,
TabCreationState.LIVE_IN_FOREGROUND,
false);
assertThat(mModel.size(), equalTo(1));
}
@Test
public void tabAddition_GTS() {
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(0);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(1);
doReturn(newTab).when(mTabGroupModelFilter).getTabAt(2);
doReturn(3).when(mTabGroupModelFilter).getCount();
doReturn(Arrays.asList(newTab)).when(mTabGroupModelFilter).getRelatedTabList(eq(TAB3_ID));
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor
.getValue()
.didAddTab(
newTab,
TabLaunchType.FROM_TAB_SWITCHER_UI,
TabCreationState.LIVE_IN_FOREGROUND,
false);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.get(2).model.get(TabProperties.TAB_ID), equalTo(TAB3_ID));
assertThat(mModel.get(2).model.get(TabProperties.TITLE), equalTo(TAB3_TITLE));
}
@Test
public void tabAddition_GTS_delayAdd() {
mMediator.setComponentNameForTesting(TabSwitcherCoordinator.COMPONENT_NAME);
initAndAssertAllProperties();
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(0);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(1);
doReturn(newTab).when(mTabGroupModelFilter).getTabAt(2);
doReturn(Arrays.asList(mTab1, mTab2, newTab))
.when(mTabGroupModelFilter)
.getRelatedTabList(TAB1_ID);
doReturn(3).when(mTabGroupModelFilter).getCount();
doReturn(Arrays.asList(newTab)).when(mTabGroupModelFilter).getRelatedTabList(eq(TAB3_ID));
assertThat(mModel.size(), equalTo(2));
// Add tab marked as delayed
mTabModelObserverCaptor
.getValue()
.didAddTab(
newTab,
TabLaunchType.FROM_TAB_SWITCHER_UI,
TabCreationState.LIVE_IN_FOREGROUND,
true);
// Verify tab did not get added and delayed tab is captured.
assertThat(mModel.size(), equalTo(2));
assertThat(mMediator.getTabToAddDelayedForTesting(), equalTo(newTab));
// Select delayed tab
mTabModelObserverCaptor
.getValue()
.didSelectTab(newTab, TabSelectionType.FROM_USER, mTab1.getId());
// Assert old tab is still marked as selected
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
when(mTabModel.getTabAt(2)).thenReturn(newTab);
when(mTabModel.getCount()).thenReturn(3);
// Hide GTS to complete tab addition and selection
mMediator.postHiding();
// Assert tab added and selected. Assert old tab is de-selected.
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(2).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertNull(mMediator.getTabToAddDelayedForTesting());
verify(mTab1).removeObserver(mTabObserverCaptor.getValue());
verify(mTab2).removeObserver(mTabObserverCaptor.getValue());
verify(newTab).removeObserver(mTabObserverCaptor.getValue());
verify(mTabGroupModelFilter).removeObserver(mTabModelObserverCaptor.getValue());
verify(mTabGroupModelFilter)
.removeTabGroupObserver(mTabGroupModelFilterObserverCaptor.getValue());
}
@Test
public void tabAddition_GTS_Skip() {
// Add a new tab to the group with mTab2.
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(0);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(1);
doReturn(2).when(mTabGroupModelFilter).getCount();
doReturn(Arrays.asList(mTab2, newTab))
.when(mTabGroupModelFilter)
.getRelatedTabList(eq(TAB3_ID));
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor
.getValue()
.didAddTab(
newTab,
TabLaunchType.FROM_TAB_SWITCHER_UI,
TabCreationState.LIVE_IN_FOREGROUND,
false);
assertThat(mModel.size(), equalTo(2));
}
@Test
public void tabAddition_GTS_Middle() {
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(0);
doReturn(newTab).when(mTabGroupModelFilter).getTabAt(1);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(2);
doReturn(3).when(mTabGroupModelFilter).getCount();
doReturn(Arrays.asList(newTab)).when(mTabGroupModelFilter).getRelatedTabList(eq(TAB3_ID));
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor
.getValue()
.didAddTab(
newTab,
TabLaunchType.FROM_CHROME_UI,
TabCreationState.LIVE_IN_FOREGROUND,
false);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB3_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB3_TITLE));
}
@Test
public void tabAddition_Dialog_End() {
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
doReturn(true).when(mTabGroupModelFilter).isTabModelRestored();
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
doReturn(3).when(mTabModel).getCount();
doReturn(Arrays.asList(mTab1, mTab2, newTab))
.when(mTabGroupModelFilter)
.getRelatedTabList(eq(TAB1_ID));
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor
.getValue()
.didAddTab(
newTab,
TabLaunchType.FROM_CHROME_UI,
TabCreationState.LIVE_IN_FOREGROUND,
false);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.get(2).model.get(TabProperties.TAB_ID), equalTo(TAB3_ID));
assertThat(mModel.get(2).model.get(TabProperties.TITLE), equalTo(TAB3_TITLE));
}
@Test
public void tabAddition_Dialog_Middle() {
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
doReturn(true).when(mTabGroupModelFilter).isTabModelRestored();
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
doReturn(3).when(mTabModel).getCount();
doReturn(Arrays.asList(mTab1, newTab, mTab2))
.when(mTabGroupModelFilter)
.getRelatedTabList(eq(TAB1_ID));
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor
.getValue()
.didAddTab(
newTab,
TabLaunchType.FROM_CHROME_UI,
TabCreationState.LIVE_IN_FOREGROUND,
false);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB3_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB3_TITLE));
}
@Test
public void tabAddition_Dialog_Skip() {
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
doReturn(true).when(mTabGroupModelFilter).isTabModelRestored();
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
// newTab is of another group.
doReturn(Arrays.asList(mTab1, mTab2))
.when(mTabGroupModelFilter)
.getRelatedTabList(eq(TAB1_ID));
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor
.getValue()
.didAddTab(
newTab,
TabLaunchType.FROM_CHROME_UI,
TabCreationState.LIVE_IN_FOREGROUND,
false);
assertThat(mModel.size(), equalTo(2));
}
@Test
public void tabSelection() {
ThumbnailFetcher tab1Fetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
ThumbnailFetcher tab2Fetcher = mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER);
mTabModelObserverCaptor
.getValue()
.didSelectTab(mTab2, TabLaunchType.FROM_CHROME_UI, TAB1_ID);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertNotEquals(mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER), tab1Fetcher);
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertNotEquals(mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER), tab2Fetcher);
}
@Test
public void tabSelection_Group() {
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab2, newTab));
createTabGroup(tabs, TAB2_ID, TAB_GROUP_ID);
ThumbnailFetcher tab1Fetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
ThumbnailFetcher tab2Fetcher = mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER);
// Select tab 3 although the represenative tab 2 should update.
mTabModelObserverCaptor
.getValue()
.didSelectTab(newTab, TabLaunchType.FROM_CHROME_UI, TAB1_ID);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertNotEquals(mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER), tab1Fetcher);
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertNotEquals(mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER), tab2Fetcher);
}
@Test
public void tabSelection_updatePreviousSelectedTabThumbnailFetcher() {
mMediator =
new TabListMediator(
mActivity,
mModel,
TabListMode.GRID,
mModalDialogManager,
mCurrentTabModelFilterSupplier,
() -> mTabModel,
getTabThumbnailCallback(),
mTabListFaviconProvider,
mTabGroupColorFaviconProvider,
true,
null,
mGridCardOnClickListenerProvider,
null,
null,
getClass().getSimpleName(),
TabActionState.CLOSABLE,
mActionConfirmationManager);
mMediator.initWithNative(mProfile);
initAndAssertAllProperties();
// mTabModelObserverCaptor captures on every resetWithListOfTabs call.
verify(mTabGroupModelFilter, times(2)).addObserver(mTabModelObserverCaptor.capture());
ThumbnailFetcher tab1Fetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
ThumbnailFetcher tab2Fetcher = mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER);
mTabModelObserverCaptor
.getValue()
.didSelectTab(mTab2, TabLaunchType.FROM_CHROME_UI, TAB1_ID);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertNotEquals(tab1Fetcher, mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertNotEquals(tab2Fetcher, mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void tabClosureUndone() {
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor.getValue().willCloseTab(mTab2, true);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
mTabModelObserverCaptor.getValue().tabClosureUndone(mTab2);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
}
@Test
public void tabClosureUndone_SingleTabGroup() {
assertThat(mModel.size(), equalTo(2));
createTabGroup(Arrays.asList(mTab2), TAB2_ID, TAB_GROUP_ID);
mMediator.getTabGroupTitleEditor().storeTabGroupTitle(TAB2_ID, CUSTOMIZED_DIALOG_TITLE1);
mTabModelObserverCaptor.getValue().willCloseTab(mTab2, true);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
mTabModelObserverCaptor.getValue().tabClosureUndone(mTab2);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE1));
}
@Test
@DisableFeatures({ChromeFeatureList.TAB_GROUP_PARITY_ANDROID})
public void tabMergeIntoGroup() {
// Assume that moveTab in TabModel is finished. Selected tab in the group becomes mTab1.
doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION1);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
// Assume that reset in TabGroupModelFilter is finished.
createTabGroup(Arrays.asList(mTab1, mTab2), TAB1_ID, TAB_GROUP_ID);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
assertThat(mModel.indexFromId(TAB1_ID), equalTo(POSITION1));
assertThat(mModel.indexFromId(TAB2_ID), equalTo(POSITION2));
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
assertNotNull(mModel.get(1).model.get(TabProperties.FAVICON_FETCHER));
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(mTab1, TAB2_ID);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo("2 tabs"));
assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
@EnableFeatures({ChromeFeatureList.TAB_GROUP_PARITY_ANDROID})
public void tabMergeIntoGroup_Parity() {
// Assume that moveTab in TabModel is finished. Selected tab in the group becomes mTab1.
doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION1);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
// Assume that reset in TabGroupModelFilter is finished.
createTabGroup(Arrays.asList(mTab1, mTab2), TAB1_ID, TAB_GROUP_ID);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
assertThat(mModel.indexFromId(TAB1_ID), equalTo(POSITION1));
assertThat(mModel.indexFromId(TAB2_ID), equalTo(POSITION2));
var oldFetcher = mModel.get(0).model.get(TabProperties.FAVICON_FETCHER);
assertNotNull(oldFetcher);
assertNotNull(mModel.get(1).model.get(TabProperties.FAVICON_FETCHER));
doReturn(mock(TabListFaviconProvider.TabFaviconFetcher.class))
.when(mTabGroupColorFaviconProvider)
.getFaviconFromTabGroupColorFetcher(anyInt(), anyBoolean());
mTabGroupModelFilter.setTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(mTab1, TAB2_ID);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE1));
var newFetcher = mModel.get(0).model.get(TabProperties.FAVICON_FETCHER);
assertNotEquals(oldFetcher, newFetcher);
}
@Test
public void tabMergeIntoGroup_Dialog() {
createTabGroup(List.of(mTab1), TAB1_ID, TAB_GROUP_ID);
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
mMediator.resetWithListOfTabs(Arrays.asList(mTab1), false);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
createTabGroup(List.of(mTab1, mTab2), TAB1_ID, TAB_GROUP_ID);
when(mTabGroupModelFilter.getGroupLastShownTabId(TAB1_ID)).thenReturn(TAB1_ID);
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(mTab2, TAB1_ID);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
verify(mTabGridDialogHandler).updateDialogContent(TAB1_ID);
}
@Test
public void tabMergeIntoGroup_Dialog_NoOp() {
createTabGroup(List.of(mTab1), TAB1_ID, TAB_GROUP_ID);
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
mMediator.resetWithListOfTabs(Arrays.asList(mTab1), false);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
createTabGroup(List.of(mTab2), TAB2_ID, new Token(7, 9));
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(mTab2, TAB2_ID);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
verify(mTabGridDialogHandler, never()).updateDialogContent(TAB1_ID);
}
@Test
public void tabMoveOutOfGroup_GTS_Moved_Tab_Selected() {
// Assume that two tabs are in the same group before ungroup.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab2));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
// Assume that TabGroupModelFilter is already updated.
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(POSITION2).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(false).when(mTabGroupModelFilter).isTabInTabGroup(mTab1);
doReturn(2).when(mTabGroupModelFilter).getCount();
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true));
}
@Test
public void tabMoveOutOfGroup_GTS_Origin_Tab_Selected() {
// Assume that two tabs are in the same group before ungroup.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
// Assume that TabGroupModelFilter is already updated.
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(2).when(mTabGroupModelFilter).getCount();
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab2, POSITION1);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
}
@Test
public void tabMoveOutOfGroup_GTS_LastTab() {
// Assume that tab1 is a single tab group that became a single tab.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
mMediator.resetWithListOfTabs(tabs, false);
doReturn(1).when(mTabGroupModelFilter).getCount();
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(tabs).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
// These properties should get reset.
mModel.get(0).model.set(TabProperties.TITLE, CUSTOMIZED_DIALOG_TITLE1);
ThumbnailFetcher fetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
// Ungroup the single tab.
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
assertNotEquals(fetcher, mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void tabMoveOutOfGroup_GTS_TabAdditionWithSameId() {
// Assume that two tabs are in the same group before ungroup.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
// Assume that TabGroupModelFilter is already updated.
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(2).when(mTabGroupModelFilter).getCount();
// The ungroup will add tab1 to the TabListModel at index 0. Note that before this addition,
// there is the PropertyModel represents the group with the same id at the same index. The
// addition should still take effect in this case.
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION2);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
}
@Test
public void testShoppingFetcherActiveForForUngroupedTabs() {
prepareForPriceDrop();
resetWithRegularTabs(false);
assertThat(mModel.size(), equalTo(2));
assertThat(
mModel.get(0).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER),
instanceOf(TabListMediator.ShoppingPersistedTabDataFetcher.class));
assertThat(
mModel.get(1).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER),
instanceOf(TabListMediator.ShoppingPersistedTabDataFetcher.class));
}
@Test
public void testShoppingFetcherInactiveForForGroupedTabs() {
prepareForPriceDrop();
resetWithRegularTabs(true);
assertThat(mModel.size(), equalTo(2));
assertNull(mModel.get(0).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER));
assertNull(mModel.get(1).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER));
}
@Test
public void testShoppingFetcherGroupedThenUngrouped() {
prepareForPriceDrop();
resetWithRegularTabs(true);
assertThat(mModel.size(), equalTo(2));
assertNull(mModel.get(0).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER));
assertNull(mModel.get(1).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER));
resetWithRegularTabs(false);
assertThat(mModel.size(), equalTo(2));
assertThat(
mModel.get(0).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER),
instanceOf(TabListMediator.ShoppingPersistedTabDataFetcher.class));
assertThat(
mModel.get(1).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER),
instanceOf(TabListMediator.ShoppingPersistedTabDataFetcher.class));
}
@Test
public void testShoppingFetcherUngroupedThenGrouped() {
prepareForPriceDrop();
resetWithRegularTabs(false);
assertThat(mModel.size(), equalTo(2));
assertThat(
mModel.get(0).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER),
instanceOf(TabListMediator.ShoppingPersistedTabDataFetcher.class));
assertThat(
mModel.get(1).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER),
instanceOf(TabListMediator.ShoppingPersistedTabDataFetcher.class));
resetWithRegularTabs(true);
assertThat(mModel.size(), equalTo(2));
assertNull(mModel.get(0).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER));
assertNull(mModel.get(1).model.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER));
}
/** Set flags and initialize for verifying price drop behavior */
private void prepareForPriceDrop() {
setPriceTrackingEnabledForTesting(true);
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
PersistedTabDataConfiguration.setUseTestConfig(true);
initAndAssertAllProperties();
}
/**
* Reset mediator with non-incognito tabs which are optionally grouped
*
* @param isGrouped true if the tabs should be grouped
*/
private void resetWithRegularTabs(boolean isGrouped) {
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(0);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(1);
doReturn(2).when(mTabGroupModelFilter).getCount();
if (isGrouped) {
doReturn(Arrays.asList(mTab1, mTab2))
.when(mTabGroupModelFilter)
.getRelatedTabList(eq(TAB1_ID));
doReturn(Arrays.asList(mTab1, mTab2))
.when(mTabGroupModelFilter)
.getRelatedTabList(eq(TAB2_ID));
doReturn(true).when(mTabGroupModelFilter).isTabInTabGroup(mTab1);
doReturn(true).when(mTabGroupModelFilter).isTabInTabGroup(mTab2);
} else {
doReturn(Arrays.asList(mTab1))
.when(mTabGroupModelFilter)
.getRelatedTabList(eq(TAB1_ID));
doReturn(Arrays.asList(mTab2))
.when(mTabGroupModelFilter)
.getRelatedTabList(eq(TAB2_ID));
doReturn(false).when(mTabGroupModelFilter).isTabInTabGroup(mTab1);
doReturn(false).when(mTabGroupModelFilter).isTabInTabGroup(mTab2);
}
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
doReturn(false).when(mTab1).isIncognito();
doReturn(false).when(mTab2).isIncognito();
mMediator.resetWithListOfTabs(tabs, false);
}
@Test
public void tabMoveOutOfGroup_Dialog() {
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
// Assume that filter is already updated.
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION1);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
verify(mTabGridDialogHandler).updateDialogContent(TAB2_ID);
}
@Test
public void tabMoveOutOfGroup_Dialog_LastTab() {
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
// Assume that tab1 is a single tab.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
mMediator.resetWithListOfTabs(tabs, false);
doReturn(1).when(mTabGroupModelFilter).getCount();
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(tabs).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
// Ungroup the single tab.
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
verify(mTabGridDialogHandler).updateDialogContent(Tab.INVALID_TAB_ID);
}
@Test
public void tabMoveOutOfGroup_Strip() {
setUpTabListMediator(TabListMediatorType.TAB_STRIP, TabListMode.GRID);
// Assume that filter is already updated.
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION1);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
verify(mTabGridDialogHandler, never()).updateDialogContent(anyInt());
}
@Test
public void tabMovementWithGroup_Forward() {
// Assume that moveTab in TabModel is finished.
doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION1);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabGroup(mTab2, POSITION2, POSITION1);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
}
@Test
public void tabMovementWithGroup_Backward() {
// Assume that moveTab in TabModel is finished.
doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION1);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabGroup(mTab1, POSITION1, POSITION2);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
}
@Test
public void tabMovementWithinGroup_TabGridDialog_Forward() {
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
// Assume that moveTab in TabModel is finished.
doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION1);
doReturn(TAB1_ID).when(mTab1).getRootId();
doReturn(TAB1_ID).when(mTab2).getRootId();
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
mTabGroupModelFilterObserverCaptor
.getValue()
.didMoveWithinGroup(mTab2, POSITION2, POSITION1);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
}
@Test
public void tabMovementWithinGroup_TabGridDialog_Backward() {
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
// Assume that moveTab in TabModel is finished.
doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION1);
doReturn(TAB1_ID).when(mTab1).getRootId();
doReturn(TAB1_ID).when(mTab2).getRootId();
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
mTabGroupModelFilterObserverCaptor
.getValue()
.didMoveWithinGroup(mTab1, POSITION1, POSITION2);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
}
@Test
public void tabMovementWithinGroup_TabSwitcher_Forward() {
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
// Setup three tabs with groups (mTab1, mTab2) and tab3.
List<Tab> group = new ArrayList<>(Arrays.asList(mTab1, mTab2));
List<Integer> groupTabIds = new ArrayList<>(Arrays.asList(TAB1_ID, TAB2_ID));
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB1_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB2_ID);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(tab3).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2);
doReturn(POSITION2).when(mTabGroupModelFilter).indexOf(tab3);
doReturn(mTab1).when(mTabModel).getTabAt(POSITION1);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION2);
doReturn(tab3).when(mTabModel).getTabAt(2);
doReturn(TAB1_ID).when(mTab1).getRootId();
doReturn(TAB1_ID).when(mTab2).getRootId();
doReturn(TAB3_ID).when(tab3).getRootId();
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(2));
// Select tab3 so the group doesn't have the selected tab.
doReturn(2).when(mTabModel).index();
mTabModelObserverCaptor
.getValue()
.didSelectTab(tab3, TabLaunchType.FROM_CHROME_UI, TAB1_ID);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB3_ID));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true));
// Assume that moveTab in TabModel is finished (swap mTab1 and mTab2).
group = new ArrayList<>(Arrays.asList(mTab2, mTab1));
groupTabIds = new ArrayList<>(Arrays.asList(TAB2_ID, TAB1_ID));
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB1_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB2_ID);
doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION1);
// mTab1 is first in group before the move.
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
ThumbnailFetcher tab1Fetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
mTabGroupModelFilterObserverCaptor
.getValue()
.didMoveWithinGroup(mTab2, POSITION2, POSITION1);
// mTab1 is still first in group after the move (last selected), but the thumbnail updated.
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
// TODO(crbug.com/40242432): Make this an assertion and don't update.
// Thumbnail order was: tab1, tab2, tab3. Now: tab1, tab2, tab3. Update is precautionary.
assertNotEquals(tab1Fetcher, mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void tabMovementWithinGroup_TabSwitcher_Backward() {
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
// Setup three tabs with groups (mTab1, mTab2) and tab3.
List<Tab> group = new ArrayList<>(Arrays.asList(mTab1, mTab2));
List<Integer> groupTabIds = new ArrayList<>(Arrays.asList(TAB1_ID, TAB2_ID));
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB1_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB2_ID);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(tab3).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2);
doReturn(POSITION2).when(mTabGroupModelFilter).indexOf(tab3);
doReturn(mTab1).when(mTabModel).getTabAt(POSITION1);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION2);
doReturn(tab3).when(mTabModel).getTabAt(2);
doReturn(TAB1_ID).when(mTab1).getRootId();
doReturn(TAB1_ID).when(mTab2).getRootId();
doReturn(TAB3_ID).when(tab3).getRootId();
// Select tab3 so the group doesn't have the selected tab.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(2));
doReturn(2).when(mTabModel).index();
mTabModelObserverCaptor
.getValue()
.didSelectTab(tab3, TabLaunchType.FROM_CHROME_UI, TAB1_ID);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB3_ID));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true));
// Assume that moveTab in TabModel is finished (swap mTab1 and mTab2).
group = new ArrayList<>(Arrays.asList(mTab2, mTab1));
groupTabIds = new ArrayList<>(Arrays.asList(TAB2_ID, TAB1_ID));
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB1_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB2_ID);
doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION1);
// mTab1 is first in group before the move.
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
ThumbnailFetcher tab1Fetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
mTabGroupModelFilterObserverCaptor
.getValue()
.didMoveWithinGroup(mTab1, POSITION1, POSITION2);
// mTab1 is first in group after the move (last selected), but the thumbnail updated.
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
// TODO(crbug.com/40242432): Make this an assertion and don't update.
// Thumbnail order was: tab1, tab2, tab3. Now: tab1, tab2, tab3. Update is precautionary.
assertNotEquals(tab1Fetcher, mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void tabMovementWithinGroup_TabSwitcher_SelectedNotMoved() {
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
// Setup three tabs grouped together.
List<Tab> group = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
List<Integer> groupTabIds = new ArrayList<>(Arrays.asList(TAB1_ID, TAB2_ID, TAB3_ID));
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB3_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB1_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB2_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB3_ID);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(tab3);
doReturn(mTab1).when(mTabModel).getTabAt(POSITION1);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION2);
doReturn(tab3).when(mTabModel).getTabAt(2);
doReturn(TAB1_ID).when(mTab1).getRootId();
doReturn(TAB1_ID).when(mTab2).getRootId();
doReturn(TAB1_ID).when(tab3).getRootId();
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(1));
// Assume that moveTab in TabModel is finished.
// mTab1 selected.
doReturn(POSITION1).when(mTabModel).index();
mTabModelObserverCaptor
.getValue()
.didSelectTab(mTab1, TabLaunchType.FROM_CHROME_UI, TAB1_ID);
// Swap mTab2 and tab3.
doReturn(mTab2).when(mTabModel).getTabAt(2);
doReturn(tab3).when(mTabModel).getTabAt(POSITION2);
group = new ArrayList<>(Arrays.asList(mTab1, tab3, mTab2));
groupTabIds = new ArrayList<>(Arrays.asList(TAB1_ID, TAB3_ID, TAB2_ID));
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB3_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB1_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB2_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB3_ID);
// mTab1 selected before update.
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
ThumbnailFetcher tab1Fetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
mTabGroupModelFilterObserverCaptor.getValue().didMoveWithinGroup(mTab2, POSITION2, 2);
// mTab1 still selected after the move (last selected), but the thumbnail updated.
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
// TODO(crbug.com/40242432): Make this an assertion.
// Thumbnail order was: tab1, tab2, tab3. Now: tab1, tab3, tab2.
assertNotEquals(tab1Fetcher, mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void tabMovementWithinGroup_TabSwitcher_SelectedMoved() {
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
// Setup three tabs grouped together.
List<Tab> group = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
List<Integer> groupTabIds = new ArrayList<>(Arrays.asList(TAB1_ID, TAB2_ID, TAB3_ID));
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB3_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB1_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB2_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB3_ID);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(tab3);
doReturn(mTab1).when(mTabModel).getTabAt(POSITION1);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION2);
doReturn(tab3).when(mTabModel).getTabAt(2);
doReturn(TAB1_ID).when(mTab1).getRootId();
doReturn(TAB1_ID).when(mTab2).getRootId();
doReturn(TAB1_ID).when(tab3).getRootId();
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(1));
// Assume that moveTab in TabModel is finished.
// mTab1 selected (at new position).
doReturn(2).when(mTabModel).index();
mTabModelObserverCaptor
.getValue()
.didSelectTab(mTab1, TabLaunchType.FROM_CHROME_UI, TAB1_ID);
// Swap mTab1 and mTab3.
doReturn(mTab1).when(mTabModel).getTabAt(2);
doReturn(tab3).when(mTabModel).getTabAt(POSITION1);
group = new ArrayList<>(Arrays.asList(tab3, mTab2, mTab1));
groupTabIds = new ArrayList<>(Arrays.asList(TAB3_ID, TAB2_ID, TAB1_ID));
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
doReturn(group).when(mTabGroupModelFilter).getRelatedTabList(TAB3_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB1_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB2_ID);
doReturn(groupTabIds).when(mTabGroupModelFilter).getRelatedTabIds(TAB3_ID);
// mTab1 selected before update.
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
ThumbnailFetcher tab1Fetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
mTabGroupModelFilterObserverCaptor.getValue().didMoveWithinGroup(mTab1, 2, POSITION1);
// mTab1 still selected after the move (last selected), but the thumbnail updated.
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
// TODO(crbug.com/40242432): Make this an assertion.
// Thumbnail order was: tab1, tab2, tab3. Now: tab1, tab3, tab2.
assertNotEquals(tab1Fetcher, mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void undoGrouped_One_Adjacent_Tab() {
// Assume there are 3 tabs in TabModel, mTab2 just grouped with mTab1;
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(2));
// Assume undo grouping mTab2 with mTab1.
doReturn(3).when(mTabGroupModelFilter).getCount();
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(tab3).when(mTabGroupModelFilter).getTabAt(2);
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab2, POSITION1);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.indexFromId(TAB1_ID), equalTo(0));
assertThat(mModel.indexFromId(TAB2_ID), equalTo(1));
assertThat(mModel.indexFromId(TAB3_ID), equalTo(2));
}
@Test
public void undoForwardGrouped_One_Tab() {
// Assume there are 3 tabs in TabModel, tab3 just grouped with mTab1;
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(2));
// Assume undo grouping tab3 with mTab1.
doReturn(3).when(mTabGroupModelFilter).getCount();
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(POSITION2).when(mTabGroupModelFilter).indexOf(mTab2);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(2).when(mTabGroupModelFilter).indexOf(tab3);
doReturn(tab3).when(mTabGroupModelFilter).getTabAt(2);
doReturn(false).when(mTabGroupModelFilter).isTabInTabGroup(tab3);
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(tab3, POSITION1);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.indexFromId(TAB1_ID), equalTo(0));
assertThat(mModel.indexFromId(TAB2_ID), equalTo(1));
assertThat(mModel.indexFromId(TAB3_ID), equalTo(2));
}
@Test
public void undoBackwardGrouped_One_Tab() {
// Assume there are 3 tabs in TabModel, mTab1 just grouped with mTab2;
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab2, tab3));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(2));
// Assume undo grouping mTab1 from mTab2.
doReturn(3).when(mTabGroupModelFilter).getCount();
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(POSITION2).when(mTabGroupModelFilter).indexOf(mTab2);
doReturn(tab3).when(mTabGroupModelFilter).getTabAt(2);
doReturn(2).when(mTabGroupModelFilter).indexOf(tab3);
doReturn(false).when(mTabGroupModelFilter).isTabInTabGroup(mTab1);
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION2);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.indexFromId(TAB1_ID), equalTo(0));
assertThat(mModel.indexFromId(TAB2_ID), equalTo(1));
assertThat(mModel.indexFromId(TAB3_ID), equalTo(2));
}
@Test
public void undoForwardGrouped_BetweenGroups() {
// Assume there are 3 tabs in TabModel, tab3, tab4, just grouped with mTab1;
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
Tab tab4 = prepareTab(TAB4_ID, TAB4_TITLE, TAB4_URL);
doReturn(4).when(mTabModel).getCount();
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(1));
// Assume undo grouping tab3 with mTab1.
doReturn(2).when(mTabGroupModelFilter).getCount();
// Undo tab 3.
List<Tab> relatedTabs = Arrays.asList(tab3);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(mTab1).when(mTabModel).getTabAt(0);
doReturn(mTab2).when(mTabModel).getTabAt(1);
doReturn(tab4).when(mTabModel).getTabAt(2);
doReturn(tab3).when(mTabModel).getTabAt(3);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(tab4);
doReturn(0).when(mTabModel).indexOf(mTab1);
doReturn(1).when(mTabModel).indexOf(mTab2);
doReturn(2).when(mTabModel).indexOf(tab4);
doReturn(tab3).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(POSITION2).when(mTabGroupModelFilter).indexOf(tab3);
doReturn(3).when(mTabModel).indexOf(tab3);
doReturn(false).when(mTabGroupModelFilter).isTabInTabGroup(tab3);
doReturn(true).when(mTabGroupModelFilter).isTabInTabGroup(tab4);
doReturn(relatedTabs).when(mTabGroupModelFilter).getRelatedTabList(TAB3_ID);
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(tab3, POSITION1);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.indexFromId(TAB1_ID), equalTo(0));
assertThat(mModel.indexFromId(TAB2_ID), equalTo(-1));
assertThat(mModel.indexFromId(TAB3_ID), equalTo(1));
assertThat(mModel.indexFromId(TAB4_ID), equalTo(-1));
// Undo tab 4
relatedTabs = Arrays.asList(tab3, tab4);
doReturn(POSITION2).when(mTabGroupModelFilter).indexOf(tab4);
doReturn(2).when(mTabModel).indexOf(tab3);
doReturn(3).when(mTabModel).indexOf(tab4);
doReturn(true).when(mTabGroupModelFilter).isTabInTabGroup(tab3);
doReturn(true).when(mTabGroupModelFilter).isTabInTabGroup(tab4);
doReturn(tab3).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(tab3).when(mTabModel).getTabAt(2);
doReturn(tab4).when(mTabModel).getTabAt(3);
doReturn(relatedTabs).when(mTabGroupModelFilter).getRelatedTabList(TAB3_ID);
doReturn(relatedTabs).when(mTabGroupModelFilter).getRelatedTabList(TAB4_ID);
doReturn(TAB3_ID).when(tab4).getRootId();
doReturn(2).when(mTabGroupModelFilter).getRelatedTabCountForRootId(TAB3_ID);
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(tab4, POSITION1);
assertThat(mModel.size(), equalTo(2));
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(tab4, TAB4_ID);
assertThat(mModel.size(), equalTo(2));
assertThat(mModel.indexFromId(TAB1_ID), equalTo(0));
assertThat(mModel.indexFromId(TAB2_ID), equalTo(-1));
assertThat(mModel.indexFromId(TAB3_ID), equalTo(1));
assertThat(mModel.indexFromId(TAB4_ID), equalTo(-1));
}
@Test
public void updateSpanCount_Portrait_SingleWindow() {
initAndAssertAllProperties();
// Mock that we are switching to portrait mode.
Configuration configuration = new Configuration();
configuration.orientation = Configuration.ORIENTATION_PORTRAIT;
configuration.screenWidthDp = TabListCoordinator.MAX_SCREEN_WIDTH_COMPACT_DP - 1;
mComponentCallbacksCaptor.getValue().onConfigurationChanged(configuration);
verify(mGridLayoutManager).setSpanCount(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_COMPACT);
}
@Test
public void updateSpanCount_Landscape_SingleWindow() {
initAndAssertAllProperties();
// Mock that we are switching to landscape mode.
Configuration configuration = new Configuration();
configuration.orientation = Configuration.ORIENTATION_LANDSCAPE;
configuration.screenWidthDp = TabListCoordinator.MAX_SCREEN_WIDTH_MEDIUM_DP - 1;
mComponentCallbacksCaptor.getValue().onConfigurationChanged(configuration);
verify(mGridLayoutManager).setSpanCount(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_MEDIUM);
}
@Test
@Config(qualifiers = "sw600dp")
public void updateSpanCount_onTablet_multipleScreenWidths() {
initAndAssertAllProperties(3);
// Mock tablet
when(mResources.getInteger(R.integer.min_screen_width_bucket))
.thenReturn(TabListCoordinator.MAX_SCREEN_WIDTH_MEDIUM_DP + 1);
Configuration portraitConfiguration = new Configuration();
portraitConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
// Compact width
portraitConfiguration.screenWidthDp = TabListCoordinator.MAX_SCREEN_WIDTH_COMPACT_DP - 1;
mComponentCallbacksCaptor.getValue().onConfigurationChanged(portraitConfiguration);
verify(mGridLayoutManager).setSpanCount(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_COMPACT);
// Medium width
portraitConfiguration.screenWidthDp = TabListCoordinator.MAX_SCREEN_WIDTH_MEDIUM_DP - 1;
mComponentCallbacksCaptor.getValue().onConfigurationChanged(portraitConfiguration);
verify(mGridLayoutManager).setSpanCount(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_MEDIUM);
// Large width
portraitConfiguration.screenWidthDp = TabListCoordinator.MAX_SCREEN_WIDTH_MEDIUM_DP + 1;
mComponentCallbacksCaptor.getValue().onConfigurationChanged(portraitConfiguration);
verify(mGridLayoutManager).setSpanCount(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_LARGE);
}
@Test
public void getLatestTitle_NotGTS() {
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
// Mock that we have a stored title stored with reference to root ID of tab1.
mTabGroupModelFilter.setTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mMediator.getTabGroupTitleEditor().getTabGroupTitle(mTab1.getRootId()),
equalTo(CUSTOMIZED_DIALOG_TITLE1));
// Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
// Even if we have a stored title, we only show it in tab switcher.
assertThat(
mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ true), equalTo(TAB1_TITLE));
}
@Test
public void getLatestTitle_SingleTabGroupSupported_GTS() {
// Mock that we have a stored title stored with reference to root ID of tab1.
mTabGroupModelFilter.setTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mMediator.getTabGroupTitleEditor().getTabGroupTitle(mTab1.getRootId()),
equalTo(CUSTOMIZED_DIALOG_TITLE1));
// Mock that tab1 is a single tab.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
// We never show stored title for single tab.
assertThat(
mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ true),
equalTo(CUSTOMIZED_DIALOG_TITLE1));
}
@Test
public void getLatestTitle_SingleTabGroupNotSupported_GTS() {
// Mock that we have a stored title stored with reference to root ID of tab1.
mTabGroupModelFilter.setTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mMediator.getTabGroupTitleEditor().getTabGroupTitle(mTab1.getRootId()),
equalTo(CUSTOMIZED_DIALOG_TITLE1));
// Mock that tab1 is a single tab.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
createTabGroup(tabs, TAB1_ID, null);
when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(false);
// We never show stored title for single tab.
assertThat(
mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ true), equalTo(TAB1_TITLE));
}
@Test
public void getLatestTitle_Stored_GTS() {
// Mock that we have a stored title stored with reference to root ID of tab1.
mTabGroupModelFilter.setTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mMediator.getTabGroupTitleEditor().getTabGroupTitle(mTab1.getRootId()),
equalTo(CUSTOMIZED_DIALOG_TITLE1));
// Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
assertThat(
mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ true),
equalTo(CUSTOMIZED_DIALOG_TITLE1));
}
@Test
public void getLatestTitle_Default_GTS() {
// Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
assertThat(
mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ true), equalTo("2 tabs"));
}
@Test
public void getLatestTitle_NoDefault_GTS() {
// Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
assertThat(mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ false), equalTo(""));
}
@Test
public void updateTabGroupTitle_GTS() {
setUpTabGroupCardDescriptionString();
String targetString = "Expand tab group with 2 tabs.";
assertThat(mModel.get(POSITION1).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
// Mock that tab1 and newTab are in the same group and group root id is TAB1_ID.
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
mMediator.getTabGroupTitleEditor().updateTabGroupTitle(mTab1, CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.TITLE),
equalTo(CUSTOMIZED_DIALOG_TITLE1));
assertThat(
mModel.get(POSITION1).model.get(TabProperties.CONTENT_DESCRIPTION_STRING),
equalTo(targetString));
}
@Test
public void updateTabGroupTitle_SingleTab_GTS() {
setUpTabGroupCardDescriptionString();
String targetString = "Expand tab group with 1 tab.";
assertThat(mModel.get(POSITION1).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
createTabGroup(Arrays.asList(mTab1), TAB1_ID, TAB_GROUP_ID);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
mMediator.getTabGroupTitleEditor().updateTabGroupTitle(mTab1, CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.TITLE),
equalTo(CUSTOMIZED_DIALOG_TITLE1));
assertThat(
mModel.get(POSITION1).model.get(TabProperties.CONTENT_DESCRIPTION_STRING),
equalTo(targetString));
}
@Test
public void tabGroupTitleEditor_storeTitle() {
TabGroupTitleEditor tabGroupTitleEditor = mMediator.getTabGroupTitleEditor();
tabGroupTitleEditor.storeTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
verify(mTabGroupModelFilter).setTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
}
@Test
public void tabGroupTitleEditor_deleteTitle() {
TabGroupTitleEditor tabGroupTitleEditor = mMediator.getTabGroupTitleEditor();
tabGroupTitleEditor.deleteTabGroupTitle(mTab1.getRootId());
verify(mTabGroupModelFilter).deleteTabGroupTitle(mTab1.getRootId());
}
@Test
public void addSpecialItem() {
mMediator.resetWithListOfTabs(null, false);
PropertyModel model = mock(PropertyModel.class);
when(model.get(CARD_TYPE)).thenReturn(MESSAGE);
mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, model);
assertTrue(mModel.size() > 0);
assertEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
}
@Test
public void addSpecialItem_notPersistOnReset() {
mMediator.resetWithListOfTabs(null, false);
PropertyModel model = mock(PropertyModel.class);
when(model.get(CARD_TYPE)).thenReturn(MESSAGE);
mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, model);
assertEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
mMediator.resetWithListOfTabs(tabs, /* quickMode= */ false);
assertThat(mModel.size(), equalTo(2));
assertNotEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
assertNotEquals(TabProperties.UiType.DIVIDER, mModel.get(1).type);
mMediator.addSpecialItemToModel(1, TabProperties.UiType.DIVIDER, model);
assertThat(mModel.size(), equalTo(3));
assertEquals(TabProperties.UiType.DIVIDER, mModel.get(1).type);
}
@Test
public void addSpecialItem_withoutTabListModelProperties() {
if (!BuildConfig.ENABLE_ASSERTS) return;
mMediator.resetWithListOfTabs(null, false);
try {
mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, new PropertyModel());
} catch (AssertionError e) {
return;
}
fail("PropertyModel#validateKey() assert should have failed.");
}
@Test
public void removeSpecialItem_Message() {
mMediator.resetWithListOfTabs(null, false);
PropertyModel model = mock(PropertyModel.class);
int expectedMessageType = FOR_TESTING;
int wrongMessageType = PRICE_MESSAGE;
when(model.get(CARD_TYPE)).thenReturn(MESSAGE);
when(model.get(MESSAGE_TYPE)).thenReturn(expectedMessageType);
mMediator.addSpecialItemToModel(0, TabProperties.UiType.MESSAGE, model);
assertEquals(1, mModel.size());
mMediator.removeSpecialItemFromModel(TabProperties.UiType.MESSAGE, wrongMessageType);
assertEquals(1, mModel.size());
mMediator.removeSpecialItemFromModel(TabProperties.UiType.MESSAGE, expectedMessageType);
assertEquals(0, mModel.size());
}
@Test
public void removeSpecialItem_Message_PriceMessage() {
mMediator.resetWithListOfTabs(null, false);
PropertyModel model = mock(PropertyModel.class);
int expectedMessageType = PRICE_MESSAGE;
int wrongMessageType = IPH;
when(model.get(CARD_TYPE)).thenReturn(MESSAGE);
when(model.get(MESSAGE_TYPE)).thenReturn(expectedMessageType);
mMediator.addSpecialItemToModel(0, TabProperties.UiType.LARGE_MESSAGE, model);
assertEquals(1, mModel.size());
mMediator.removeSpecialItemFromModel(TabProperties.UiType.MESSAGE, wrongMessageType);
assertEquals(1, mModel.size());
mMediator.removeSpecialItemFromModel(
TabProperties.UiType.LARGE_MESSAGE, expectedMessageType);
assertEquals(0, mModel.size());
}
@Test
public void testUrlUpdated_forSingleTab_GTS() {
assertNotEquals(mNewDomain, mModel.get(POSITION1).model.get(TabProperties.URL_DOMAIN));
doReturn(mNewDomain)
.when(mUrlUtilitiesJniMock)
.getDomainAndRegistry(eq(NEW_URL), anyBoolean());
doReturn(new GURL(NEW_URL)).when(mTab1).getUrl();
mTabObserverCaptor.getValue().onUrlUpdated(mTab1);
assertEquals(mNewDomain, mModel.get(POSITION1).model.get(TabProperties.URL_DOMAIN));
assertEquals(mTab2Domain, mModel.get(POSITION2).model.get(TabProperties.URL_DOMAIN));
}
@Test
public void testUrlUpdated_forGroup_GTS() {
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2);
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(mTab2, TAB1_ID);
assertEquals(
mTab1Domain + ", " + mTab2Domain,
mModel.get(POSITION1).model.get(TabProperties.URL_DOMAIN));
doReturn(mNewDomain)
.when(mUrlUtilitiesJniMock)
.getDomainAndRegistry(eq(NEW_URL), anyBoolean());
// Update URL_DOMAIN for mTab1.
doReturn(new GURL(NEW_URL)).when(mTab1).getUrl();
mTabObserverCaptor.getValue().onUrlUpdated(mTab1);
assertEquals(
mNewDomain + ", " + mTab2Domain,
mModel.get(POSITION1).model.get(TabProperties.URL_DOMAIN));
// Update URL_DOMAIN for mTab2.
doReturn(new GURL(NEW_URL)).when(mTab2).getUrl();
mTabObserverCaptor.getValue().onUrlUpdated(mTab2);
assertEquals(
mNewDomain + ", " + mNewDomain,
mModel.get(POSITION1).model.get(TabProperties.URL_DOMAIN));
}
@Test
public void testUrlUpdated_forGroup_Dialog() {
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2);
setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
verify(mTab2, times(1)).addObserver(mTabObserverCaptor.getValue());
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(mTab2, TAB1_ID);
assertEquals(mTab1Domain, mModel.get(POSITION1).model.get(TabProperties.URL_DOMAIN));
assertEquals(mTab2Domain, mModel.get(POSITION2).model.get(TabProperties.URL_DOMAIN));
verify(mTab2, times(2)).addObserver(mTabObserverCaptor.getValue());
doReturn(mNewDomain)
.when(mUrlUtilitiesJniMock)
.getDomainAndRegistry(eq(NEW_URL), anyBoolean());
// Update URL_DOMAIN for mTab1.
doReturn(new GURL(NEW_URL)).when(mTab1).getUrl();
mTabObserverCaptor.getValue().onUrlUpdated(mTab1);
assertEquals(mNewDomain, mModel.get(POSITION1).model.get(TabProperties.URL_DOMAIN));
assertEquals(mTab2Domain, mModel.get(POSITION2).model.get(TabProperties.URL_DOMAIN));
// Update URL_DOMAIN for mTab2.
doReturn(new GURL(NEW_URL)).when(mTab2).getUrl();
mTabObserverCaptor.getValue().onUrlUpdated(mTab2);
assertEquals(mNewDomain, mModel.get(POSITION1).model.get(TabProperties.URL_DOMAIN));
assertEquals(mNewDomain, mModel.get(POSITION2).model.get(TabProperties.URL_DOMAIN));
}
@Test
public void testUrlUpdated_forUnGroup() {
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(mTab2, TAB1_ID);
assertEquals(
mTab1Domain + ", " + mTab2Domain,
mModel.get(POSITION1).model.get(TabProperties.URL_DOMAIN));
// Assume that TabGroupModelFilter is already updated.
when(mTabGroupModelFilter.getRelatedTabList(TAB1_ID)).thenReturn(Arrays.asList(mTab1));
when(mTabGroupModelFilter.getRelatedTabList(TAB2_ID)).thenReturn(Arrays.asList(mTab2));
when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(true);
when(mTabGroupModelFilter.isTabInTabGroup(mTab2)).thenReturn(false);
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
doReturn(TAB1_ID).when(mTab1).getRootId();
doReturn(TAB2_ID).when(mTab2).getRootId();
doReturn(1).when(mTabGroupModelFilter).getRelatedTabCountForRootId(TAB1_ID);
doReturn(1).when(mTabGroupModelFilter).getRelatedTabCountForRootId(TAB2_ID);
doReturn(2).when(mTabGroupModelFilter).getCount();
mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab2, POSITION1);
assertEquals(mTab1Domain, mModel.get(POSITION1).model.get(TabProperties.URL_DOMAIN));
assertEquals(mTab2Domain, mModel.get(POSITION2).model.get(TabProperties.URL_DOMAIN));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void testTabGroupCreation_listMode() {
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
TabListMediator mMediatorSpy = spy(mMediator);
when(mTabGroupModelFilter.isTabInTabGroup(any())).thenReturn(true);
doReturn(true).when(mMediatorSpy).isTabInTabGroup(any());
mMediatorSpy.setComponentNameForTesting(TabSwitcherCoordinator.COMPONENT_NAME);
initAndAssertAllProperties(mMediatorSpy);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
// Get the next suggested color id to mock the setting of a color on tab group creation.
int nextSuggestedColorId = TabGroupColorUtils.getNextSuggestedColorId(mTabGroupModelFilter);
// Assert that the next suggested color was assigned to that group.
assertEquals(
nextSuggestedColorId,
mModel.get(POSITION1).model.get(TabProperties.TAB_GROUP_COLOR_ID));
// Fake a different color on the group to check that the color was set properly on group
// creation.
mModel.get(POSITION1).model.set(TabProperties.TAB_GROUP_COLOR_ID, COLOR_2);
mTabGroupModelFilterObserverCaptor
.getValue()
.didCreateNewGroup(mTab1, mTabGroupModelFilter);
assertEquals(
nextSuggestedColorId,
mModel.get(POSITION1).model.get(TabProperties.TAB_GROUP_COLOR_ID));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void testRefreshTabList_colorIcon() {
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
TabListMediator mMediatorSpy = spy(mMediator);
when(mTabGroupModelFilter.isTabInTabGroup(any())).thenReturn(true);
doReturn(true).when(mMediatorSpy).isTabInTabGroup(any());
mMediatorSpy.setComponentNameForTesting(TabSwitcherCoordinator.COMPONENT_NAME);
initAndAssertAllProperties(mMediatorSpy);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
// Get the next suggested color id to mock the setting of a color on tab group creation.
int nextSuggestedColorId = TabGroupColorUtils.getNextSuggestedColorId(mTabGroupModelFilter);
// Assert that the next suggested color was assigned to that group.
assertEquals(
nextSuggestedColorId,
mModel.get(POSITION1).model.get(TabProperties.TAB_GROUP_COLOR_ID));
// Fake a different color on the group to check that the color was set properly on refresh.
mModel.get(POSITION1).model.set(TabProperties.TAB_GROUP_COLOR_ID, COLOR_2);
// Pass in a mocked newly created tab group.
mMediatorSpy.resetWithListOfTabs(tabs, /* quickMode= */ false);
assertEquals(
nextSuggestedColorId,
mModel.get(POSITION1).model.get(TabProperties.TAB_GROUP_COLOR_ID));
}
@Test
public void testOnInitializeAccessibilityNodeInfo() {
// Setup related mocks and initialize needed components.
AccessibilityNodeInfo accessibilityNodeInfo = mock(AccessibilityNodeInfo.class);
AccessibilityAction action1 = new AccessibilityAction(R.id.move_tab_left, "left");
AccessibilityAction action2 = new AccessibilityAction(R.id.move_tab_right, "right");
AccessibilityAction action3 = new AccessibilityAction(R.id.move_tab_up, "up");
doReturn(new ArrayList<>(Arrays.asList(action1, action2, action3)))
.when(mTabGridAccessibilityHelper)
.getPotentialActionsForView(mItemView1);
InOrder accessibilityNodeInfoInOrder = Mockito.inOrder(accessibilityNodeInfo);
assertNull(mMediator.getAccessibilityDelegateForTesting());
mMediator.setupAccessibilityDelegate(mTabGridAccessibilityHelper);
View.AccessibilityDelegate delegate = mMediator.getAccessibilityDelegateForTesting();
assertNotNull(delegate);
delegate.onInitializeAccessibilityNodeInfo(mItemView1, accessibilityNodeInfo);
accessibilityNodeInfoInOrder.verify(accessibilityNodeInfo).addAction(eq(action1));
accessibilityNodeInfoInOrder.verify(accessibilityNodeInfo).addAction(eq(action2));
accessibilityNodeInfoInOrder.verify(accessibilityNodeInfo).addAction(eq(action3));
}
@Test
public void testPerformAccessibilityAction() {
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
// Setup related mocks and initialize needed components.
Bundle args = mock(Bundle.class);
int action = R.id.move_tab_left;
// Mock that the action indicates that tab2 will move left and thus tab2 and tab1 should
// switch positions.
doReturn(new Pair<>(1, 0))
.when(mTabGridAccessibilityHelper)
.getPositionsOfReorderAction(mItemView1, action);
doReturn(true).when(mTabGridAccessibilityHelper).isReorderAction(action);
assertNull(mMediator.getAccessibilityDelegateForTesting());
mMediator.setupAccessibilityDelegate(mTabGridAccessibilityHelper);
View.AccessibilityDelegate delegate = mMediator.getAccessibilityDelegateForTesting();
assertNotNull(delegate);
delegate.performAccessibilityAction(mItemView1, action, args);
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
}
@Test
public void testPerformAccessibilityAction_defaultAccessibilityAction() {
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
// Setup related mocks and initialize needed components.
Bundle args = mock(Bundle.class);
int action = ACTION_CLICK;
// Mock that the action indicates that tab2 will move to position 2 which is invalid.
doReturn(false).when(mTabGridAccessibilityHelper).isReorderAction(action);
assertNull(mMediator.getAccessibilityDelegateForTesting());
mMediator.setupAccessibilityDelegate(mTabGridAccessibilityHelper);
View.AccessibilityDelegate delegate = mMediator.getAccessibilityDelegateForTesting();
assertNotNull(delegate);
delegate.performAccessibilityAction(mItemView1, action, args);
verify(mTabGridAccessibilityHelper, never())
.getPositionsOfReorderAction(mItemView1, action);
}
@Test
public void testPerformAccessibilityAction_InvalidIndex() {
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
// Setup related mocks and initialize needed components.
Bundle args = mock(Bundle.class);
int action = R.id.move_tab_left;
// Mock that the action indicates that tab2 will move to position 2 which is invalid.
doReturn(new Pair<>(1, 2))
.when(mTabGridAccessibilityHelper)
.getPositionsOfReorderAction(mItemView1, action);
assertNull(mMediator.getAccessibilityDelegateForTesting());
mMediator.setupAccessibilityDelegate(mTabGridAccessibilityHelper);
View.AccessibilityDelegate delegate = mMediator.getAccessibilityDelegateForTesting();
assertNotNull(delegate);
delegate.performAccessibilityAction(mItemView1, action, args);
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
}
@Test
public void testTabObserverRemovedFromClosedTab() {
initAndAssertAllProperties();
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor.getValue().willCloseTab(mTab2, true);
verify(mTab2).removeObserver(mTabObserverCaptor.getValue());
assertThat(mModel.size(), equalTo(1));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
}
@Test
public void testTabObserverReattachToUndoClosedTab() {
initAndAssertAllProperties();
// Called twice in test set up due to reset with list & adding tab to model.
verify(mTab2, times(2)).addObserver(mTabObserverCaptor.getValue());
assertThat(mModel.size(), equalTo(2));
mTabModelObserverCaptor.getValue().willCloseTab(mTab2, true);
assertThat(mModel.size(), equalTo(1));
verify(mTab2).removeObserver(any());
// Assume that TabModelFilter is already updated to reflect closed tab is undone.
doReturn(2).when(mTabGroupModelFilter).getCount();
doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
when(mTabGroupModelFilter.getRelatedTabList(TAB1_ID)).thenReturn(Arrays.asList(mTab1));
when(mTabGroupModelFilter.getRelatedTabList(TAB2_ID)).thenReturn(Arrays.asList(mTab2));
mTabModelObserverCaptor.getValue().tabClosureUndone(mTab2);
assertThat(mModel.size(), equalTo(2));
// Verify the count increased when we added the tab to the model.
verify(mTab2, times(3)).addObserver(mTabObserverCaptor.getValue());
}
@Test
public void testUnchangeCheckIgnoreNonTabs() {
initAndAssertAllProperties();
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mTabModel.getCount(); i++) {
tabs.add(mTabModel.getTabAt(i));
}
boolean showQuickly = mMediator.resetWithListOfTabs(tabs, /* quickMode= */ false);
assertThat(showQuickly, equalTo(true));
// Create a PropertyModel that is not a tab and add it to the existing TabListModel.
PropertyModel propertyModel = mock(PropertyModel.class);
when(propertyModel.get(CARD_TYPE)).thenReturn(OTHERS);
mMediator.addSpecialItemToModel(mModel.size(), TabProperties.UiType.MESSAGE, propertyModel);
assertThat(mModel.size(), equalTo(tabs.size() + 1));
// TabListModel unchange check should ignore the non-Tab item.
showQuickly = mMediator.resetWithListOfTabs(tabs, /* quickMode= */ false);
assertThat(showQuickly, equalTo(true));
}
// TODO(crbug.com/40168614): the assertThat in fetch callback is never reached.
@Test
public void testPriceTrackingProperty() {
setPriceTrackingEnabledForTesting(true);
for (boolean signedInAndSyncEnabled : new boolean[] {false, true}) {
for (boolean priceTrackingEnabled : new boolean[] {false, true}) {
for (boolean incognito : new boolean[] {false, true}) {
TabListMediator mMediatorSpy = spy(mMediator);
doReturn(false).when(mMediatorSpy).isTabInTabGroup(any());
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(
signedInAndSyncEnabled);
PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
PriceTrackingUtilities.TRACK_PRICES_ON_TABS, priceTrackingEnabled);
Map<GURL, Any> responses = new HashMap<>();
responses.put(TAB1_URL, ANY_BUYABLE_PRODUCT_INITIAL);
responses.put(TAB2_URL, ANY_EMPTY);
mockOptimizationGuideResponse(OptimizationGuideDecision.TRUE, responses);
PersistedTabDataConfiguration.setUseTestConfig(true);
initAndAssertAllProperties(mMediatorSpy);
List<Tab> tabs = new ArrayList<>();
doReturn(incognito).when(mTab1).isIncognito();
doReturn(incognito).when(mTab2).isIncognito();
for (int i = 0; i < 2; i++) {
long timestamp = System.currentTimeMillis();
Tab tab = mTabModel.getTabAt(i);
doReturn(timestamp).when(tab).getTimestampMillis();
}
tabs.add(mTabModel.getTabAt(0));
tabs.add(mTabModel.getTabAt(1));
mMediatorSpy.resetWithListOfTabs(tabs, /* quickMode= */ false);
if (signedInAndSyncEnabled && priceTrackingEnabled && !incognito) {
mModel.get(0)
.model
.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER)
.fetch(
(shoppingPersistedTabData) -> {
assertThat(
shoppingPersistedTabData.getPriceMicros(),
equalTo(123456789012345L));
});
mModel.get(1)
.model
.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER)
.fetch(
(shoppingPersistedTabData) -> {
assertThat(
shoppingPersistedTabData.getPriceMicros(),
equalTo(
ShoppingPersistedTabData
.NO_PRICE_KNOWN));
});
} else {
assertNull(
mModel.get(0)
.model
.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER));
assertNull(
mModel.get(1)
.model
.get(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER));
}
}
}
}
// Set incognito status back to how it was
doReturn(true).when(mTab1).isIncognito();
doReturn(true).when(mTab2).isIncognito();
}
@Test
public void testGetPriceWelcomeMessageInsertionIndex() {
initWithThreeTabs();
doReturn(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_COMPACT)
.when(mGridLayoutManager)
.getSpanCount();
assertThat(mMediator.getPriceWelcomeMessageInsertionIndex(), equalTo(2));
doReturn(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_MEDIUM)
.when(mGridLayoutManager)
.getSpanCount();
assertThat(mMediator.getPriceWelcomeMessageInsertionIndex(), equalTo(3));
}
@Test
public void testUpdateLayout_PriceMessage() {
initAndAssertAllProperties();
addSpecialItem(1, TabProperties.UiType.LARGE_MESSAGE, PRICE_MESSAGE);
assertThat(mModel.lastIndexForMessageItemFromType(PRICE_MESSAGE), equalTo(1));
doAnswer(
invocation -> {
int position = invocation.getArgument(0);
int itemType = mModel.get(position).type;
if (itemType == TabProperties.UiType.LARGE_MESSAGE) {
return mGridLayoutManager.getSpanCount();
}
return 1;
})
.when(mSpanSizeLookup)
.getSpanSize(anyInt());
mMediator.updateLayout();
assertThat(mModel.lastIndexForMessageItemFromType(PRICE_MESSAGE), equalTo(1));
setPriceTrackingEnabledForTesting(true);
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
PriceTrackingUtilities.PRICE_WELCOME_MESSAGE_CARD, true);
mMediator.updateLayout();
assertThat(mModel.lastIndexForMessageItemFromType(PRICE_MESSAGE), equalTo(2));
}
@Test
public void testUpdateLayout_Divider() {
initAndAssertAllProperties();
addSpecialItem(1, TabProperties.UiType.DIVIDER, 0);
assertThat(mModel.get(1).type, equalTo(TabProperties.UiType.DIVIDER));
doAnswer(
invocation -> {
int position = invocation.getArgument(0);
int itemType = mModel.get(position).type;
if (itemType == TabProperties.UiType.DIVIDER) {
return mGridLayoutManager.getSpanCount();
}
return 1;
})
.when(mSpanSizeLookup)
.getSpanSize(anyInt());
PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
PriceTrackingUtilities.PRICE_WELCOME_MESSAGE_CARD, true);
mMediator.updateLayout();
assertThat(mModel.get(1).type, equalTo(TabProperties.UiType.DIVIDER));
}
@Test
public void testIndexOfNthTabCard() {
initAndAssertAllProperties();
addSpecialItem(1, TabProperties.UiType.LARGE_MESSAGE, PRICE_MESSAGE);
assertThat(mModel.lastIndexForMessageItemFromType(PRICE_MESSAGE), equalTo(1));
assertThat(mModel.indexOfNthTabCard(-1), equalTo(TabModel.INVALID_TAB_INDEX));
assertThat(mModel.indexOfNthTabCard(0), equalTo(0));
assertThat(mModel.indexOfNthTabCard(1), equalTo(2));
assertThat(mModel.indexOfNthTabCard(2), equalTo(3));
}
@Test
public void testGetTabCardCountsBefore() {
initAndAssertAllProperties();
addSpecialItem(1, TabProperties.UiType.LARGE_MESSAGE, PRICE_MESSAGE);
assertThat(mModel.lastIndexForMessageItemFromType(PRICE_MESSAGE), equalTo(1));
assertThat(mModel.getTabCardCountsBefore(-1), equalTo(TabModel.INVALID_TAB_INDEX));
assertThat(mModel.getTabCardCountsBefore(0), equalTo(0));
assertThat(mModel.getTabCardCountsBefore(1), equalTo(1));
assertThat(mModel.getTabCardCountsBefore(2), equalTo(1));
assertThat(mModel.getTabCardCountsBefore(3), equalTo(2));
}
@Test
public void testGetTabIndexBefore() {
initAndAssertAllProperties();
addSpecialItem(1, TabProperties.UiType.LARGE_MESSAGE, PRICE_MESSAGE);
assertThat(mModel.lastIndexForMessageItemFromType(PRICE_MESSAGE), equalTo(1));
assertThat(mModel.getTabIndexBefore(2), equalTo(0));
assertThat(mModel.getTabIndexBefore(0), equalTo(TabModel.INVALID_TAB_INDEX));
}
@Test
public void testGetTabIndexAfter() {
initAndAssertAllProperties();
addSpecialItem(1, TabProperties.UiType.LARGE_MESSAGE, PRICE_MESSAGE);
assertThat(mModel.lastIndexForMessageItemFromType(PRICE_MESSAGE), equalTo(1));
assertThat(mModel.getTabIndexAfter(0), equalTo(2));
assertThat(mModel.getTabIndexAfter(2), equalTo(TabModel.INVALID_TAB_INDEX));
}
@Test
public void testListObserver_OnItemRangeInserted() {
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
setPriceTrackingEnabledForTesting(true);
mMediator =
new TabListMediator(
mActivity,
mModel,
TabListMode.GRID,
mModalDialogManager,
mCurrentTabModelFilterSupplier,
() -> mTabModel,
getTabThumbnailCallback(),
mTabListFaviconProvider,
mTabGroupColorFaviconProvider,
true,
null,
null,
null,
null,
getClass().getSimpleName(),
TabProperties.TabActionState.CLOSABLE,
mActionConfirmationManager);
mMediator.registerOrientationListener(mGridLayoutManager);
mMediator.initWithNative(mProfile);
initAndAssertAllProperties();
PropertyModel model = mock(PropertyModel.class);
when(model.get(CARD_TYPE)).thenReturn(MESSAGE);
when(model.get(MESSAGE_TYPE)).thenReturn(PRICE_MESSAGE);
mMediator.addSpecialItemToModel(1, TabProperties.UiType.LARGE_MESSAGE, model);
assertThat(mModel.lastIndexForMessageItemFromType(PRICE_MESSAGE), equalTo(2));
}
@Test
public void testListObserver_OnItemRangeRemoved() {
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
setPriceTrackingEnabledForTesting(true);
mMediator =
new TabListMediator(
mActivity,
mModel,
TabListMode.GRID,
mModalDialogManager,
mCurrentTabModelFilterSupplier,
() -> mTabModel,
getTabThumbnailCallback(),
mTabListFaviconProvider,
mTabGroupColorFaviconProvider,
true,
null,
null,
null,
null,
getClass().getSimpleName(),
TabProperties.TabActionState.CLOSABLE,
mActionConfirmationManager);
mMediator.registerOrientationListener(mGridLayoutManager);
mMediator.initWithNative(mProfile);
initWithThreeTabs();
PropertyModel model = mock(PropertyModel.class);
when(model.get(CARD_TYPE)).thenReturn(MESSAGE);
when(model.get(MESSAGE_TYPE)).thenReturn(PRICE_MESSAGE);
mMediator.addSpecialItemToModel(2, TabProperties.UiType.LARGE_MESSAGE, model);
assertThat(mModel.lastIndexForMessageItemFromType(PRICE_MESSAGE), equalTo(2));
mModel.removeAt(0);
assertThat(mModel.lastIndexForMessageItemFromType(PRICE_MESSAGE), equalTo(2));
}
@Test
public void testMaybeShowPriceWelcomeMessage() {
prepareTestMaybeShowPriceWelcomeMessage();
ShoppingPersistedTabDataFetcher fetcher =
new ShoppingPersistedTabDataFetcher(mTab1, () -> mPriceWelcomeMessageController);
fetcher.maybeShowPriceWelcomeMessage(mShoppingPersistedTabData);
verify(mPriceWelcomeMessageController, times(1)).showPriceWelcomeMessage(mPriceTabData);
}
@Test
public void testMaybeShowPriceWelcomeMessage_MessageDisabled() {
prepareTestMaybeShowPriceWelcomeMessage();
ShoppingPersistedTabDataFetcher fetcher =
new ShoppingPersistedTabDataFetcher(mTab1, () -> mPriceWelcomeMessageController);
PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
PriceTrackingUtilities.PRICE_WELCOME_MESSAGE_CARD, false);
assertThat(
PriceTrackingUtilities.isPriceWelcomeMessageCardEnabled(mProfile), equalTo(false));
fetcher.maybeShowPriceWelcomeMessage(mShoppingPersistedTabData);
verify(mPriceWelcomeMessageController, times(0)).showPriceWelcomeMessage(mPriceTabData);
}
@Test
public void testMaybeShowPriceWelcomeMessage_SupplierIsNull() {
prepareTestMaybeShowPriceWelcomeMessage();
new ShoppingPersistedTabDataFetcher(mTab1, null)
.maybeShowPriceWelcomeMessage(mShoppingPersistedTabData);
verify(mPriceWelcomeMessageController, times(0)).showPriceWelcomeMessage(mPriceTabData);
}
@Test
public void testMaybeShowPriceWelcomeMessage_SupplierContainsNull() {
prepareTestMaybeShowPriceWelcomeMessage();
Supplier<PriceWelcomeMessageController> supplier = () -> null;
new ShoppingPersistedTabDataFetcher(mTab1, supplier)
.maybeShowPriceWelcomeMessage(mShoppingPersistedTabData);
verify(mPriceWelcomeMessageController, times(0)).showPriceWelcomeMessage(mPriceTabData);
}
@Test
public void testMaybeShowPriceWelcomeMessage_NoPriceDrop() {
prepareTestMaybeShowPriceWelcomeMessage();
ShoppingPersistedTabDataFetcher fetcher =
new ShoppingPersistedTabDataFetcher(mTab1, () -> mPriceWelcomeMessageController);
fetcher.maybeShowPriceWelcomeMessage(null);
verify(mPriceWelcomeMessageController, times(0)).showPriceWelcomeMessage(mPriceTabData);
doReturn(null).when(mShoppingPersistedTabData).getPriceDrop();
fetcher.maybeShowPriceWelcomeMessage(mShoppingPersistedTabData);
verify(mPriceWelcomeMessageController, times(0)).showPriceWelcomeMessage(mPriceTabData);
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void testUpdateFaviconFetcherForGroup_gridModeWithColors() {
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.GRID);
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
when(mTabModel.isIncognito()).thenReturn(false);
// Mock that we have a stored color stored with reference to root ID of tab1.
getGroupColorSharedPreferences()
.edit()
.putInt(String.valueOf(mTab1.getRootId()), COLOR_2)
.apply();
// Test a group of three.
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap, mFaviconUrl);
verify(mTabGroupColorFaviconProvider).getFaviconFromTabGroupColorFetcher(COLOR_2, false);
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
public void testUpdateFaviconFetcherForGroup() {
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
// Test a group of three.
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(List.of(mTab1), false);
List<GURL> urls = new ArrayList<>(Arrays.asList(TAB1_URL, TAB2_URL, TAB3_URL));
verify(mTabListFaviconProvider).getComposedFaviconImageFetcher(eq(urls), anyBoolean());
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
// Try it from the TabObserver.
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap, mFaviconUrl);
verify(mTabListFaviconProvider, times(2))
.getComposedFaviconImageFetcher(eq(urls), anyBoolean());
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
// Test a group of five.
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
Tab tab4 = prepareTab(0, "tab 4", TAB2_URL);
Tab tab5 = prepareTab(1, "tab 5", JUnitTestGURLs.EXAMPLE_URL);
tabs.addAll(Arrays.asList(tab4, tab5));
createTabGroup(tabs, TAB2_ID, TAB_GROUP_ID);
mTabObserverCaptor.getValue().onFaviconUpdated(mTab2, mFaviconBitmap, mFaviconUrl);
urls = new ArrayList<>(Arrays.asList(TAB1_URL, TAB2_URL, TAB3_URL, TAB2_URL));
verify(mTabListFaviconProvider).getComposedFaviconImageFetcher(eq(urls), anyBoolean());
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
public void testUpdateFaviconFetcherForGroup_StaleIndex_SelectAnotherTabWithinGroup() {
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
mModel.get(1).model.set(TabProperties.FAVICON_FETCHER, null);
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab2, tab3));
List<GURL> group1Urls = new ArrayList<>(Arrays.asList(mTab2.getUrl(), tab3.getUrl()));
createTabGroup(group1, TAB2_ID, TAB_GROUP_ID);
assertEquals(1, mModel.indexFromId(TAB2_ID));
mTabObserverCaptor.getValue().onFaviconUpdated(mTab2, mFaviconBitmap, mFaviconUrl);
verify(mTabListFaviconProvider)
.getComposedFaviconImageFetcher(eq(group1Urls), anyBoolean());
// Simulate selecting another Tab within TabGroup.
mModel.get(1).model.set(TabProperties.TAB_ID, TAB3_ID);
assertNotEquals(1, mModel.indexFromId(TAB2_ID));
assertNotNull(mModel.get(1).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
public void testUpdateFaviconFetcherForGroup_StaleIndex_CloseTab() {
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
mModel.get(1).model.set(TabProperties.FAVICON_FETCHER, null);
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab2, tab3));
List<GURL> group1Urls = new ArrayList<>(Arrays.asList(mTab2.getUrl(), tab3.getUrl()));
createTabGroup(group1, TAB2_ID, TAB_GROUP_ID);
assertEquals(1, mModel.indexFromId(TAB2_ID));
mTabObserverCaptor.getValue().onFaviconUpdated(mTab2, mFaviconBitmap, mFaviconUrl);
verify(mTabListFaviconProvider)
.getComposedFaviconImageFetcher(eq(group1Urls), anyBoolean());
// Simulate closing mTab1 at index 0.
mModel.removeAt(0);
assertEquals(0, mModel.indexFromId(TAB2_ID));
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
}
@Test
public void testUpdateFaviconFetcherForGroup_StaleIndex_Reset() {
setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
mModel.get(1).model.set(TabProperties.FAVICON_FETCHER, null);
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab2, tab3));
List<GURL> group1Urls = new ArrayList<>(Arrays.asList(mTab2.getUrl(), tab3.getUrl()));
createTabGroup(group1, TAB2_ID, TAB_GROUP_ID);
assertEquals(1, mModel.indexFromId(TAB2_ID));
mTabObserverCaptor.getValue().onFaviconUpdated(mTab2, mFaviconBitmap, mFaviconUrl);
verify(mTabListFaviconProvider)
.getComposedFaviconImageFetcher(eq(group1Urls), anyBoolean());
// Simulate TabListMediator reset with null.
mModel.set(new ArrayList<>());
}
@Test(expected = AssertionError.class)
public void testGetDomainOnDestroyedTab() {
Tab tab = new MockTab(TAB1_ID, mProfile);
tab.destroy();
TabListMediator.getDomain(tab);
}
@Test
public void testTabDescriptionStringSetup() {
// Setup the string template.
setUpTabGroupCardDescriptionString();
String targetString = "Expand tab group with 2 tabs.";
// Setup a tab group with {tab2, tab3}.
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mTabModel.getCount(); i++) {
tabs.add(mTabModel.getTabAt(i));
}
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab2, tab3));
createTabGroup(group1, TAB2_ID, TAB_GROUP_ID);
// Reset with show quickly.
assertThat(mMediator.resetWithListOfTabs(tabs, false), equalTo(true));
assertThat(
mModel.get(POSITION2).model.get(TabProperties.CONTENT_DESCRIPTION_STRING),
equalTo(targetString));
// Reset without show quickly.
mModel.clear();
assertThat(mMediator.resetWithListOfTabs(tabs, false), equalTo(false));
assertThat(
mModel.get(POSITION2).model.get(TabProperties.CONTENT_DESCRIPTION_STRING),
equalTo(targetString));
// Set group name.
targetString = String.format("Expand %s tab group with 2 tabs.", CUSTOMIZED_DIALOG_TITLE1);
mMediator.getTabGroupTitleEditor().storeTabGroupTitle(TAB2_ID, CUSTOMIZED_DIALOG_TITLE1);
mMediator.getTabGroupTitleEditor().updateTabGroupTitle(mTab2, CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mModel.get(POSITION2).model.get(TabProperties.CONTENT_DESCRIPTION_STRING),
equalTo(targetString));
}
@Test
@DisableFeatures({
ChromeFeatureList.TAB_GROUP_PARITY_ANDROID,
ChromeFeatureList.TAB_GROUP_PANE_ANDROID
})
public void testCloseButtonDescriptionStringSetup_TabSwitcher() {
setUpCloseButtonDescriptionString(false);
String targetString = "Close Tab1 tab";
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mTabModel.getCount(); i++) {
tabs.add(mTabModel.getTabAt(i));
}
mMediator.resetWithListOfTabs(tabs, false);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
equalTo(targetString));
// Create tab group.
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, tab3));
createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
setUpCloseButtonDescriptionString(true);
targetString = "Close tab group with 2 tabs.";
mMediator.resetWithListOfTabs(tabs, false);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
equalTo(targetString));
// Set group name.
targetString = String.format("Close %s group with 2 tabs.", CUSTOMIZED_DIALOG_TITLE1);
mMediator.getTabGroupTitleEditor().storeTabGroupTitle(TAB1_ID, CUSTOMIZED_DIALOG_TITLE1);
mMediator.getTabGroupTitleEditor().updateTabGroupTitle(mTab1, CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
equalTo(targetString));
}
@Test
@DisableFeatures({
ChromeFeatureList.TAB_GROUP_PARITY_ANDROID,
ChromeFeatureList.TAB_GROUP_PANE_ANDROID
})
public void testCloseButtonDescriptionStringSetup_SingleTabGroup_TabSwitcher() {
// Create tab group.
List<Tab> tabs = Arrays.asList(mTab1);
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
setUpCloseButtonDescriptionString(true);
String targetString = "Close tab group with 1 tab.";
mMediator.resetWithListOfTabs(tabs, false);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
equalTo(targetString));
// Set group name.
targetString = String.format("Close %s group with 1 tab.", CUSTOMIZED_DIALOG_TITLE1);
mMediator.getTabGroupTitleEditor().storeTabGroupTitle(TAB1_ID, CUSTOMIZED_DIALOG_TITLE1);
mMediator.getTabGroupTitleEditor().updateTabGroupTitle(mTab1, CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
equalTo(targetString));
}
@Test
@DisableFeatures({ChromeFeatureList.TAB_GROUP_PANE_ANDROID})
@EnableFeatures({ChromeFeatureList.TAB_GROUP_PARITY_ANDROID})
public void testCloseButtonDescriptionStringWithColorSetup_TabSwitcher() {
// Create tab group.
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, tab3));
createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
String targetString = "Close tab group with 2 tabs, color Grey.";
mMediator.resetWithListOfTabs(group1, false);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
equalTo(targetString));
// Set group name.
targetString =
String.format("Close %s group with 2 tabs, color Grey.", CUSTOMIZED_DIALOG_TITLE1);
mMediator.getTabGroupTitleEditor().storeTabGroupTitle(TAB1_ID, CUSTOMIZED_DIALOG_TITLE1);
mMediator.getTabGroupTitleEditor().updateTabGroupTitle(mTab1, CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
equalTo(targetString));
}
@Test
@EnableFeatures({
ChromeFeatureList.TAB_GROUP_PANE_ANDROID,
ChromeFeatureList.TAB_GROUP_PARITY_ANDROID
})
public void testActionButtonDescriptionStringGroupOverflowMenu_TabSwitcher() {
// Create tab group.
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, tab3));
createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
String targetString = "Open the tab group action menu for tab group 2 tabs";
mMediator.resetWithListOfTabs(group1, false);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
equalTo(targetString));
// Set group name.
targetString =
String.format(
"Open the tab group action menu for tab group %s",
CUSTOMIZED_DIALOG_TITLE1);
mMediator.getTabGroupTitleEditor().storeTabGroupTitle(TAB1_ID, CUSTOMIZED_DIALOG_TITLE1);
mMediator.getTabGroupTitleEditor().updateTabGroupTitle(mTab1, CUSTOMIZED_DIALOG_TITLE1);
assertThat(
mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
equalTo(targetString));
}
@Test
public void testRecordPriceAnnotationsEnabledMetrics() {
setPriceTrackingEnabledForTesting(true);
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
String histogramName = "Commerce.PriceDrop.AnnotationsEnabled";
SharedPreferencesManager preferencesManager = ChromeSharedPreferences.getInstance();
long presetTime = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
preferencesManager.writeLong(
ChromePreferenceKeys.PRICE_TRACKING_ANNOTATIONS_ENABLED_METRICS_TIMESTAMP,
presetTime);
mMediator.recordPriceAnnotationsEnabledMetrics();
assertThat(RecordHistogram.getHistogramTotalCountForTesting(histogramName), equalTo(1));
long updatedTime =
preferencesManager.readLong(
ChromePreferenceKeys.PRICE_TRACKING_ANNOTATIONS_ENABLED_METRICS_TIMESTAMP,
presetTime);
assertNotEquals(presetTime, updatedTime);
// This metrics should only be recorded once within one day.
mMediator.recordPriceAnnotationsEnabledMetrics();
assertThat(RecordHistogram.getHistogramTotalCountForTesting(histogramName), equalTo(1));
assertEquals(
updatedTime,
preferencesManager.readLong(
ChromePreferenceKeys.PRICE_TRACKING_ANNOTATIONS_ENABLED_METRICS_TIMESTAMP,
-1));
}
@Test
public void testPriceDropSeen() throws TimeoutException {
setPriceTrackingEnabledForTesting(true);
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
PriceTrackingUtilities.TRACK_PRICES_ON_TABS, true);
doReturn(false).when(mTab1).isIncognito();
doReturn(false).when(mTab2).isIncognito();
List<Tab> tabs = new ArrayList<>();
tabs.add(mTabModel.getTabAt(0));
tabs.add(mTabModel.getTabAt(1));
mMediator.resetWithListOfTabs(tabs, /* quickMode= */ false);
prepareRecyclerViewForScroll();
mMediator.registerOnScrolledListener(mRecyclerView);
verify(mRecyclerView).addOnScrollListener(mOnScrollListenerCaptor.capture());
mOnScrollListenerCaptor
.getValue()
.onScrolled(mRecyclerView, /* dx= */ mTabModel.getCount(), /* dy= */ 0);
assertEquals(2, mMediator.getViewedTabIdsForTesting().size());
}
@Test
public void testSelectableUpdates_withoutRelated() {
when(mSelectionDelegate.isItemSelected(TAB1_ID)).thenReturn(true);
when(mSelectionDelegate.isItemSelected(TAB2_ID)).thenReturn(false);
when(mSelectionDelegate.isItemSelected(TAB3_ID)).thenReturn(false);
mMediator =
new TabListMediator(
mActivity,
mModel,
TabListMode.GRID,
mModalDialogManager,
mCurrentTabModelFilterSupplier,
() -> mTabModel,
getTabThumbnailCallback(),
mTabListFaviconProvider,
mTabGroupColorFaviconProvider,
true,
() -> {
return mSelectionDelegate;
},
null,
null,
null,
getClass().getSimpleName(),
TabProperties.TabActionState.SELECTABLE,
mActionConfirmationManager);
mMediator.registerOrientationListener(mGridLayoutManager);
mMediator.initWithNative(mProfile);
initAndAssertAllProperties();
when(mSelectionDelegate.isItemSelected(TAB1_ID)).thenReturn(false);
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(2).model.get(TabProperties.IS_SELECTED), equalTo(false));
when(mTabGroupModelFilter.isTabInTabGroup(mTab2)).thenReturn(false);
ThumbnailFetcher fetcher2 = mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER);
mModel.get(1).model.get(TabProperties.SELECTABLE_TAB_CLICKED_LISTENER).run(TAB2_ID);
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertEquals(fetcher2, mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void testSelectableUpdates_withRelated() {
when(mSelectionDelegate.isItemSelected(TAB1_ID)).thenReturn(true);
when(mSelectionDelegate.isItemSelected(TAB2_ID)).thenReturn(false);
when(mSelectionDelegate.isItemSelected(TAB3_ID)).thenReturn(false);
mMediator =
new TabListMediator(
mActivity,
mModel,
TabListMode.GRID,
mModalDialogManager,
mCurrentTabModelFilterSupplier,
() -> mTabModel,
getTabThumbnailCallback(),
mTabListFaviconProvider,
mTabGroupColorFaviconProvider,
true,
() -> {
return mSelectionDelegate;
},
null,
null,
null,
getClass().getSimpleName(),
TabProperties.TabActionState.SELECTABLE,
mActionConfirmationManager);
mMediator.registerOrientationListener(mGridLayoutManager);
mMediator.initWithNative(mProfile);
initAndAssertAllProperties();
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
when(mSelectionDelegate.isItemSelected(TAB1_ID)).thenReturn(false);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(2).model.get(TabProperties.IS_SELECTED), equalTo(false));
when(mTabGroupModelFilter.isTabInTabGroup(mTab2)).thenReturn(true);
ThumbnailFetcher fetcher2 = mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER);
mModel.get(1).model.get(TabProperties.SELECTABLE_TAB_CLICKED_LISTENER).run(TAB2_ID);
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertNotEquals(fetcher2, mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void testSelectableUpdates_onReset() {
when(mSelectionDelegate.isItemSelected(TAB1_ID)).thenReturn(true);
when(mSelectionDelegate.isItemSelected(TAB2_ID)).thenReturn(false);
when(mSelectionDelegate.isItemSelected(TAB3_ID)).thenReturn(false);
mMediator =
new TabListMediator(
mActivity,
mModel,
TabListMode.GRID,
mModalDialogManager,
mCurrentTabModelFilterSupplier,
() -> mTabModel,
getTabThumbnailCallback(),
mTabListFaviconProvider,
mTabGroupColorFaviconProvider,
true,
() -> {
return mSelectionDelegate;
},
null,
null,
null,
getClass().getSimpleName(),
TabProperties.TabActionState.SELECTABLE,
mActionConfirmationManager);
mMediator.registerOrientationListener(mGridLayoutManager);
mMediator.initWithNative(mProfile);
initAndAssertAllProperties();
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
Tab tab4 = prepareTab(TAB4_ID, TAB4_TITLE, TAB4_URL);
when(mTabGroupModelFilter.getRelatedTabList(TAB1_ID)).thenReturn(Arrays.asList(mTab1));
when(mTabGroupModelFilter.getRelatedTabList(TAB2_ID))
.thenReturn(Arrays.asList(mTab2, tab4));
when(mTabGroupModelFilter.getRelatedTabList(TAB3_ID)).thenReturn(Arrays.asList(tab3));
when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(false);
when(mTabGroupModelFilter.isTabInTabGroup(mTab2)).thenReturn(true);
when(mTabGroupModelFilter.isTabInTabGroup(tab4)).thenReturn(true);
when(mTabGroupModelFilter.isTabInTabGroup(tab3)).thenReturn(false);
List<Tab> tabs = Arrays.asList(mTab1, mTab2, tab3);
when(mSelectionDelegate.isItemSelected(TAB1_ID)).thenReturn(false);
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(2).model.get(TabProperties.IS_SELECTED), equalTo(false));
when(mSelectionDelegate.isItemSelected(TAB1_ID)).thenReturn(true);
when(mSelectionDelegate.isItemSelected(TAB2_ID)).thenReturn(true);
when(mSelectionDelegate.isItemSelected(TAB3_ID)).thenReturn(false);
ThumbnailFetcher fetcher1 = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
ThumbnailFetcher fetcher2 = mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER);
ThumbnailFetcher fetcher3 = mModel.get(2).model.get(TabProperties.THUMBNAIL_FETCHER);
mMediator.resetWithListOfTabs(tabs, true);
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertThat(mModel.get(2).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertEquals(fetcher1, mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
assertNotEquals(fetcher2, mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER));
assertEquals(fetcher3, mModel.get(2).model.get(TabProperties.THUMBNAIL_FETCHER));
}
@Test
public void testChangingTabModelFilters() {
mCurrentTabModelFilterSupplier.set(mIncognitoTabGroupModelFilter);
verify(mTabGroupModelFilter).removeObserver(any());
verify(mTabGroupModelFilter).removeTabGroupObserver(any());
// Not added until the next resetWithListOfTabs call.
verify(mIncognitoTabGroupModelFilter, never()).addObserver(any());
verify(mIncognitoTabGroupModelFilter, never()).addTabGroupObserver(any());
}
@Test
public void testSpecialItemExist() {
mMediator.resetWithListOfTabs(null, false);
PropertyModel model = mock(PropertyModel.class);
when(model.get(CARD_TYPE)).thenReturn(MESSAGE);
when(model.get(MESSAGE_TYPE)).thenReturn(FOR_TESTING);
mMediator.addSpecialItemToModel(0, TabProperties.UiType.LARGE_MESSAGE, model);
assertTrue(mModel.size() > 0);
assertTrue(mMediator.specialItemExistsInModel(FOR_TESTING));
assertFalse(mMediator.specialItemExistsInModel(PRICE_MESSAGE));
assertTrue(mMediator.specialItemExistsInModel(MessageService.MessageType.ALL));
}
@Test
public void tabClosure_updatesTabGroup_inTabSwitcher() {
initAndAssertAllProperties();
// Mock that tab1 and tab3 are in the same group and group root id is TAB1_ID.
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(List.of(mTab1, mTab2), true);
ThumbnailFetcher fetcherBefore = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
assertEquals(2, mModel.size());
mMediator.setActionOnAllRelatedTabsForTesting(true);
doReturn(true).when(mTabGroupModelFilter).tabGroupExistsForRootId(TAB1_ID);
doReturn(false).when(mTab1).isClosing();
mTabModelObserverCaptor.getValue().willCloseTab(tab3, true);
assertEquals(2, mModel.size());
ThumbnailFetcher fetcherAfter = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
assertThat(fetcherBefore, not(fetcherAfter));
}
@Test
public void tabClosure_doesNotUpdateTabGroup_inTabSwitcher_WhenClosing() {
initAndAssertAllProperties();
// Mock that tab1 and tab3 are in the same group and group root id is TAB1_ID.
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(List.of(mTab1, mTab2), true);
ThumbnailFetcher fetcherBefore = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
assertEquals(2, mModel.size());
mMediator.setActionOnAllRelatedTabsForTesting(true);
doReturn(true).when(mTabGroupModelFilter).tabGroupExistsForRootId(TAB1_ID);
doReturn(true).when(mTab1).isClosing();
mTabModelObserverCaptor.getValue().willCloseTab(tab3, true);
assertEquals(2, mModel.size());
ThumbnailFetcher fetcherAfter = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
assertThat(fetcherBefore, equalTo(fetcherAfter));
}
@Test
public void tabClosure_ignoresUpdateForTabGroup_outsideTabSwitcher() {
initAndAssertAllProperties();
TabListMediator.TabActionListener actionListenerBeforeUpdate =
mModel.get(0).model.get(TabProperties.TAB_SELECTED_LISTENER);
// Mock that tab1 and tab3 are in the same group and group root id is TAB1_ID.
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
assertEquals(2, mModel.size());
mMediator.setActionOnAllRelatedTabsForTesting(false);
doReturn(true).when(mTabGroupModelFilter).tabGroupExistsForRootId(TAB1_ID);
mTabModelObserverCaptor.getValue().willCloseTab(mTab1, true);
assertEquals(1, mModel.size());
TabListMediator.TabActionListener actionListenerAfterUpdate =
mModel.get(0).model.get(TabProperties.TAB_SELECTED_LISTENER);
// The selection listener should remain unchanged, since the property model of the tab group
// should not get updated when the closure is triggered from outside the tab switcher.
assertThat(actionListenerBeforeUpdate, equalTo(actionListenerAfterUpdate));
}
@Test
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PANE_ANDROID)
public void testIsTabGroup_TabSwitcher() {
mMediator.setComponentNameForTesting(TabSwitcherCoordinator.COMPONENT_NAME);
doReturn(true).when(mTabGroupSyncFeaturesJniMock).isTabGroupSyncEnabled(mProfile);
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mTabModel.getCount(); i++) {
tabs.add(mTabModel.getTabAt(i));
}
// Create tab group.
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, tab3));
createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(tabs, false);
// Assert that a tab group status was recorded.
assertTrue(mModel.get(POSITION1).model.get(TabProperties.TAB_GROUP_INFO).getIsTabGroup());
}
@Test
public void testOnMenuItemClickedCallback_CloseGroupInTabSwitcher() {
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mTabModel.getCount(); i++) {
tabs.add(mTabModel.getTabAt(i));
}
// Create tab group.
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(tabs, false);
// Assert that the callback performs as expected.
assertNotNull(mModel.get(POSITION1).model.get(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK));
when(mTabModel.getTabAt(0)).thenReturn(mTab1);
when(mTabGroupModelFilter.getRelatedTabListForRootId(TAB1_ID)).thenReturn(tabs);
mModel.get(POSITION1)
.model
.get(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK)
.onClick(R.id.close_tab, TAB1_ID);
verify(mTabGroupModelFilter)
.closeMultipleTabs(tabs, /* canUndo= */ true, /* hideTabGroups= */ true);
}
@Test
public void testOnMenuItemClickedCallback_UngroupInTabSwitcher_IncognitoNoShow() {
mCurrentTabModelFilterSupplier.set(mIncognitoTabGroupModelFilter);
when(mIncognitoTabModel.isIncognito()).thenReturn(true);
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mIncognitoTabModel.getCount(); i++) {
tabs.add(mIncognitoTabModel.getTabAt(i));
}
// Create tab group.
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(tabs, false);
// Assert that the callback performs as expected.
assertNotNull(mModel.get(POSITION1).model.get(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK));
when(mIncognitoTabModel.getTabAt(0)).thenReturn(mTab1);
when(mIncognitoTabGroupModelFilter.getRelatedTabListForRootId(TAB1_ID)).thenReturn(tabs);
mModel.get(POSITION1)
.model
.get(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK)
.onClick(R.id.ungroup_tab, TAB1_ID);
verify(mIncognitoTabGroupModelFilter).moveTabOutOfGroup(TAB1_ID);
}
@Test
public void testOnMenuItemClickedCallback_DeleteGroupInTabSwitcher_IncognitoNoShow() {
mCurrentTabModelFilterSupplier.set(mIncognitoTabGroupModelFilter);
when(mIncognitoTabModel.isIncognito()).thenReturn(true);
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mIncognitoTabModel.getCount(); i++) {
tabs.add(mIncognitoTabModel.getTabAt(i));
}
// Create tab group.
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, mTab2));
createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(tabs, false);
// Assert that the callback performs as expected.
assertNotNull(mModel.get(POSITION1).model.get(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK));
when(mIncognitoTabModel.getTabAt(0)).thenReturn(mTab1);
when(mIncognitoTabGroupModelFilter.getRelatedTabListForRootId(TAB1_ID)).thenReturn(tabs);
mModel.get(POSITION1)
.model
.get(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK)
.onClick(R.id.delete_tab, TAB1_ID);
verify(mIncognitoTabGroupModelFilter)
.closeMultipleTabs(tabs, /* canUndo= */ true, /* hideTabGroups= */ false);
}
@Test
public void testOnMenuItemClickedCallback_CloseGroupInTabSwitcher_SingleTabGroup() {
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mTabModel.getCount(); i++) {
tabs.add(mTabModel.getTabAt(i));
}
// Create tab group.
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1));
createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
mMediator.resetWithListOfTabs(tabs, false);
// Assert that the callback performs as expected.
assertNotNull(mModel.get(POSITION1).model.get(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK));
when(mTabModel.getTabAt(0)).thenReturn(mTab1);
when(mTabGroupModelFilter.getRelatedTabListForRootId(TAB1_ID)).thenReturn(tabs);
mModel.get(POSITION1)
.model
.get(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK)
.onClick(R.id.close_tab, TAB1_ID);
verify(mTabGroupModelFilter)
.closeMultipleTabs(tabs, /* canUndo= */ true, /* hideTabGroups= */ true);
}
@Test
public void testQuickDeleteAnimationTabFiltering() {
// Add five more tabs.
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
Tab tab4 = prepareTab(TAB4_ID, TAB4_TITLE, TAB4_URL);
Tab tab5 = prepareTab(TAB5_ID, TAB5_TITLE, TAB5_URL);
Tab tab6 = prepareTab(TAB6_ID, TAB6_TITLE, TAB6_URL);
Tab tab7 = prepareTab(TAB7_ID, TAB7_TITLE, TAB7_URL);
when(mTabModel.getTabAt(4)).thenReturn(tab7);
// Mock that tab3 and tab4 are in the same group and group root id is TAB3_ID.
List<Tab> groupTabs1 = new ArrayList<>(Arrays.asList(tab3, tab4));
createTabGroup(groupTabs1, TAB3_ID, TAB_GROUP_ID, 2);
when(mTabGroupModelFilter.getTabAt(2)).thenReturn(tab3);
// Mock that tab5 and tab6 are in the same group and group root id is TAB5_ID.
List<Tab> groupTabs2 = new ArrayList<>(Arrays.asList(tab5, tab6));
createTabGroup(groupTabs2, TAB5_ID, TAB_GROUP_ID, 3);
when(mTabGroupModelFilter.getTabAt(3)).thenReturn(tab5);
Rect tab1Rect = new Rect();
tab1Rect.bottom = 1;
when(mTabListRecyclerView.getRectOfCurrentThumbnail(0, TAB1_ID)).thenReturn(tab1Rect);
Rect tab2Rect = new Rect();
tab2Rect.bottom = 1;
when(mTabListRecyclerView.getRectOfCurrentThumbnail(1, TAB2_ID)).thenReturn(tab2Rect);
Rect tab3Rect = new Rect();
tab3Rect.bottom = 2;
when(mTabListRecyclerView.getRectOfCurrentThumbnail(2, TAB3_ID)).thenReturn(tab3Rect);
Rect tab5Rect = new Rect();
tab5Rect.bottom = 2;
when(mTabListRecyclerView.getRectOfCurrentThumbnail(3, TAB5_ID)).thenReturn(tab5Rect);
// Mock tab7 is outside the screen view.
when(mTabListRecyclerView.getRectOfCurrentThumbnail(4, TAB7_ID)).thenReturn(null);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3, tab5, tab7));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(5));
TreeMap<Integer, List<Integer>> resultMap = new TreeMap<>();
List<Tab> tabsToFade = new ArrayList<>(Arrays.asList(mTab1, tab4, tab5, tab6, tab7));
mMediator.getOrderOfTabsForQuickDeleteAnimation(
mTabListRecyclerView, tabsToFade, resultMap);
assertThat(resultMap.keySet(), contains(1, 2));
// Tab 1 and group tab 5 & 6 should be filtered for animation.
assertThat(resultMap.get(1), contains(0));
assertThat(resultMap.get(2), contains(3));
}
private void setUpCloseButtonDescriptionString(boolean isGroup) {
if (isGroup) {
doAnswer(
invocation -> {
String title = invocation.getArgument(1);
String num = invocation.getArgument(2);
return String.format("Close %s group with %s tabs", title, num);
})
.when(mActivity)
.getString(anyInt(), anyString(), anyString());
doAnswer(
invocation -> {
String num = invocation.getArgument(1);
return String.format("Close tab group with %s tabs", num);
})
.when(mActivity)
.getString(anyInt(), anyString());
} else {
doAnswer(
invocation -> {
String title = invocation.getArgument(1);
return String.format("Close %s tab", title);
})
.when(mActivity)
.getString(anyInt(), anyString());
}
}
private void setUpTabGroupCardDescriptionString() {
doAnswer(
invocation -> {
String title = invocation.getArgument(1);
String num = invocation.getArgument(2);
return String.format("Expand %s tab group with %s tabs.", title, num);
})
.when(mActivity)
.getString(anyInt(), anyString(), anyString());
doAnswer(
invocation -> {
String num = invocation.getArgument(1);
return String.format("Expand tab group with %s tabs.", num);
})
.when(mActivity)
.getString(anyInt(), anyString());
}
// initAndAssertAllProperties called with regular mMediator
private void initAndAssertAllProperties() {
initAndAssertAllProperties(mMediator);
}
// initAndAssertAllProperties called with regular mMediator
private void initAndAssertAllProperties(int extraTabCount) {
int index = mTabModel.getCount();
int totalCount = mTabModel.getCount() + extraTabCount;
while (index < totalCount) {
Tab tab = prepareTab(index, TAB1_TITLE, TAB1_URL);
doReturn(tab).when(mTabModel).getTabAt(index);
doReturn(index).when(mTabModel).indexOf(tab);
index++;
}
doReturn(totalCount).when(mTabModel).getCount();
initAndAssertAllProperties(mMediator);
}
// initAndAssertAllProperties called with custom mMediator (e.g. if spy needs to be used)
private void initAndAssertAllProperties(TabListMediator mediator) {
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mTabModel.getCount(); i++) {
tabs.add(mTabModel.getTabAt(i));
}
int tabGroupModelFilterObserverCount =
mTabGroupModelFilterObserverCaptor.getAllValues().size();
int tabModelObserverCount = mTabModelObserverCaptor.getAllValues().size();
mediator.resetWithListOfTabs(tabs, false);
assertEquals(mTabModelObserverCaptor.getAllValues().size(), tabModelObserverCount + 1);
assertEquals(
mTabGroupModelFilterObserverCaptor.getAllValues().size(),
tabGroupModelFilterObserverCount + 1);
for (Callback<TabFavicon> callback : mCallbackCaptor.getAllValues()) {
callback.onResult(mFavicon);
}
assertThat(mModel.size(), equalTo(mTabModel.getCount()));
assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
if (!mTabGroupModelFilter.isTabInTabGroup(mTab1)) {
assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
}
if (!mTabGroupModelFilter.isTabInTabGroup(mTab2)) {
assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
}
assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
assertNotNull(mModel.get(1).model.get(TabProperties.FAVICON_FETCHER));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
if (mMediator.getTabListModeForTesting() == TabListMode.GRID) {
assertThat(
mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER),
instanceOf(TabListMediator.ThumbnailFetcher.class));
assertThat(
mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER),
instanceOf(TabListMediator.ThumbnailFetcher.class));
} else {
assertNull(mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER));
assertNull(mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER));
}
if (mModel.get(0).model.get(TabProperties.SELECTABLE_TAB_CLICKED_LISTENER) != null) return;
assertThat(
mModel.get(0).model.get(TabProperties.TAB_SELECTED_LISTENER),
instanceOf(TabListMediator.TabActionListener.class));
assertThat(
mModel.get(1).model.get(TabProperties.TAB_SELECTED_LISTENER),
instanceOf(TabListMediator.TabActionListener.class));
assertThat(
mModel.get(0).model.get(TabProperties.TAB_ACTION_BUTTON_LISTENER),
instanceOf(TabListMediator.TabActionListener.class));
assertThat(
mModel.get(1).model.get(TabProperties.TAB_ACTION_BUTTON_LISTENER),
instanceOf(TabListMediator.TabActionListener.class));
}
private Tab prepareTab(int id, String title, GURL url) {
Tab tab = TabUiUnitTestUtils.prepareTab(id, title, url);
when(tab.getView()).thenReturn(mock(View.class));
doReturn(true).when(tab).isIncognito();
when(tab.getTitle()).thenReturn(title);
int count = mTabModel.getCount();
doReturn(tab).when(mTabModel).getTabAt(count);
doReturn(count).when(mTabModel).getCount();
when(mTabModel.getTabById(id)).thenReturn(tab);
when(mIncognitoTabModel.getTabById(id)).thenReturn(tab);
when(mTabGroupModelFilter.getRelatedTabCountForRootId(id)).thenReturn(1);
return tab;
}
private SimpleRecyclerViewAdapter.ViewHolder prepareViewHolder(int id, int position) {
SimpleRecyclerViewAdapter.ViewHolder viewHolder =
mock(SimpleRecyclerViewAdapter.ViewHolder.class);
viewHolder.model =
new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
.with(TabProperties.TAB_ID, id)
.with(CARD_TYPE, TAB)
.build();
doReturn(position).when(viewHolder).getAdapterPosition();
return viewHolder;
}
private RecyclerView.ViewHolder prepareFakeViewHolder(View itemView, int index) {
RecyclerView.ViewHolder viewHolder = new RecyclerView.ViewHolder(itemView) {};
when(mRecyclerView.findViewHolderForAdapterPosition(index)).thenReturn(viewHolder);
return viewHolder;
}
private TabGridItemTouchHelperCallback getItemTouchHelperCallback() {
return (TabGridItemTouchHelperCallback) mMediator.getItemTouchHelperCallback(0f, 0f, 0f);
}
private void setUpTabListMediator(@TabListMediatorType int type, @TabListMode int mode) {
if (mMediator != null) {
mMediator.resetWithListOfTabs(null, false);
mMediator.destroy();
mMediator = null;
}
doNothing()
.when(mTabGroupModelFilter)
.addTabGroupObserver(mTabGroupModelFilterObserverCaptor.capture());
doNothing().when(mTabGroupModelFilter).addObserver(mTabModelObserverCaptor.capture());
TabListMediator.TabGridDialogHandler handler =
type == TabListMediatorType.TAB_GRID_DIALOG ? mTabGridDialogHandler : null;
boolean actionOnRelatedTabs = type == TabListMediatorType.TAB_SWITCHER;
int uiType = 0;
if (type == TabListMediatorType.TAB_SWITCHER
|| type == TabListMediatorType.TAB_GRID_DIALOG) {
uiType = TabProperties.TabActionState.CLOSABLE;
} else if (type == TabListMediatorType.TAB_STRIP) {
uiType = TabProperties.UiType.STRIP;
}
ThumbnailProvider thumbnailProvider =
mode == TabListMode.GRID ? getTabThumbnailCallback() : null;
mMediator =
new TabListMediator(
mActivity,
mModel,
mode,
mModalDialogManager,
mCurrentTabModelFilterSupplier,
() -> mTabModel,
thumbnailProvider,
mTabListFaviconProvider,
mTabGroupColorFaviconProvider,
actionOnRelatedTabs,
null,
mGridCardOnClickListenerProvider,
handler,
null,
getClass().getSimpleName(),
uiType,
mActionConfirmationManager);
TrackerFactory.setTrackerForTests(mTracker);
mMediator.registerOrientationListener(mGridLayoutManager);
mMediator.initWithNative(mProfile);
initAndAssertAllProperties();
}
private void createTabGroup(List<Tab> tabs, int rootId, @Nullable Token tabGroupId) {
when(mTabGroupModelFilter.getRelatedTabCountForRootId(rootId)).thenReturn(tabs.size());
List<Integer> tabIds = tabs.stream().map(Tab::getId).collect(Collectors.toList());
for (Tab tab : tabs) {
when(mTabGroupModelFilter.getRelatedTabIds(tab.getId())).thenReturn(tabIds);
when(mTabGroupModelFilter.getRelatedTabList(tab.getId())).thenReturn(tabs);
when(mTabGroupModelFilter.isTabInTabGroup(tab)).thenReturn(true);
when(tab.getRootId()).thenReturn(rootId);
when(tab.getTabGroupId()).thenReturn(tabGroupId);
}
}
private void createTabGroup(List<Tab> tabs, int rootId, @Nullable Token tabGroupId, int index) {
when(mTabGroupModelFilter.getRelatedTabCountForRootId(rootId)).thenReturn(tabs.size());
List<Integer> tabIds = tabs.stream().map(Tab::getId).collect(Collectors.toList());
for (Tab tab : tabs) {
when(mTabGroupModelFilter.getRelatedTabIds(tab.getId())).thenReturn(tabIds);
when(mTabGroupModelFilter.getRelatedTabList(tab.getId())).thenReturn(tabs);
when(mTabGroupModelFilter.isTabInTabGroup(tab)).thenReturn(true);
when(tab.getRootId()).thenReturn(rootId);
when(tab.getTabGroupId()).thenReturn(tabGroupId);
when(mTabGroupModelFilter.indexOf(tab)).thenReturn(index);
}
}
private void mockOptimizationGuideResponse(
@OptimizationGuideDecision int decision, Map<GURL, Any> responses) {
for (Map.Entry<GURL, Any> responseEntry : responses.entrySet()) {
doAnswer(
new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
OptimizationGuideCallback callback =
(OptimizationGuideCallback)
invocation.getArguments()[2];
callback.onOptimizationGuideDecision(
decision, responseEntry.getValue());
return null;
}
})
.when(mOptimizationGuideBridge)
.canApplyOptimization(
eq(responseEntry.getKey()),
any(HintsProto.OptimizationType.class),
any(OptimizationGuideCallback.class));
}
}
private void initWithThreeTabs() {
Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
mMediator.resetWithListOfTabs(tabs, false);
assertThat(mModel.size(), equalTo(3));
assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
assertThat(mModel.get(2).model.get(TabProperties.IS_SELECTED), equalTo(false));
}
private void addSpecialItem(int index, @UiType int uiType, int itemIdentifier) {
PropertyModel model = mock(PropertyModel.class);
when(model.get(CARD_TYPE)).thenReturn(MESSAGE);
if (uiType == TabProperties.UiType.MESSAGE
|| uiType == TabProperties.UiType.LARGE_MESSAGE) {
when(model.get(MESSAGE_TYPE)).thenReturn(itemIdentifier);
}
// Avoid auto-updating the layout when inserting the special card.
doReturn(1).when(mSpanSizeLookup).getSpanSize(anyInt());
mMediator.addSpecialItemToModel(index, uiType, model);
}
private void prepareTestMaybeShowPriceWelcomeMessage() {
initAndAssertAllProperties();
setPriceTrackingEnabledForTesting(true);
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
PriceTrackingUtilities.PRICE_WELCOME_MESSAGE_CARD, true);
mPriceDrop = new PriceDrop("1", "2");
mPriceTabData = new PriceTabData(TAB1_ID, mPriceDrop);
doReturn(mPriceDrop).when(mShoppingPersistedTabData).getPriceDrop();
}
private void prepareRecyclerViewForScroll() {
View seenView = mock(View.class);
for (int i = 0; i < mTabModel.getCount(); i++) {
when(mRecyclerView.getChildAt(i)).thenReturn(seenView);
}
doReturn(true).when(mGridLayoutManager).isViewPartiallyVisible(seenView, false, true);
doReturn(mTabModel.getCount()).when(mRecyclerView).getChildCount();
}
private ThumbnailProvider getTabThumbnailCallback() {
return (tabId, thumbnailSize, callback, forceUpdate, writeToCache, isSelected) -> {
mTabContentManager.getTabThumbnailWithCallback(
tabId, thumbnailSize, callback, forceUpdate, writeToCache);
};
}
private static void setPriceTrackingEnabledForTesting(boolean value) {
FeatureList.TestValues testValues = new FeatureList.TestValues();
testValues.addFeatureFlagOverride(ChromeFeatureList.COMMERCE_PRICE_TRACKING, true);
testValues.addFieldTrialParamOverride(
ChromeFeatureList.COMMERCE_PRICE_TRACKING,
PriceTrackingFeatures.PRICE_DROP_IPH_ENABLED_PARAM,
String.valueOf(value));
FeatureList.setTestValues(testValues);
PriceTrackingFeatures.setPriceTrackingEnabledForTesting(value);
}
}