| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.browser.tasks.tab_management; |
| |
| import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLICK; |
| |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.hamcrest.CoreMatchers.instanceOf; |
| import static org.hamcrest.CoreMatchers.not; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.ArgumentMatchers.refEq; |
| 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.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import static org.chromium.chrome.browser.flags.ChromeFeatureList.TAB_GROUPS_ANDROID; |
| import static org.chromium.chrome.browser.flags.ChromeFeatureList.TAB_GROUPS_CONTINUATION_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.TAB_SUGGESTION; |
| 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.graphics.Bitmap; |
| import android.graphics.Color; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| 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.recyclerview.widget.GridLayoutManager; |
| import androidx.recyclerview.widget.ItemTouchHelper; |
| import androidx.recyclerview.widget.RecyclerView; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TestRule; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Captor; |
| import org.mockito.InOrder; |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.mockito.MockitoAnnotations; |
| import org.robolectric.annotation.Config; |
| |
| import org.chromium.base.Callback; |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.UserDataHost; |
| import org.chromium.base.metrics.RecordHistogram; |
| import org.chromium.base.test.BaseRobolectricTestRunner; |
| import org.chromium.base.test.util.JniMocker; |
| import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; |
| import org.chromium.chrome.browser.feature_engagement.TrackerFactory; |
| import org.chromium.chrome.browser.flags.CachedFeatureFlags; |
| import org.chromium.chrome.browser.flags.ChromeFeatureList; |
| import org.chromium.chrome.browser.multiwindow.MultiWindowUtils; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; |
| import org.chromium.chrome.browser.tab.Tab; |
| import org.chromium.chrome.browser.tab.TabCreationState; |
| import org.chromium.chrome.browser.tab.TabImpl; |
| import org.chromium.chrome.browser.tab.TabLaunchType; |
| import org.chromium.chrome.browser.tab.TabObserver; |
| import org.chromium.chrome.browser.tabmodel.EmptyTabModelFilter; |
| import org.chromium.chrome.browser.tabmodel.TabModel; |
| import org.chromium.chrome.browser.tabmodel.TabModelFilter; |
| import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider; |
| import org.chromium.chrome.browser.tabmodel.TabModelObserver; |
| import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl; |
| import org.chromium.chrome.browser.tasks.pseudotab.TabAttributeCache; |
| import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter; |
| import org.chromium.chrome.browser.tasks.tab_management.TabProperties.UiType; |
| import org.chromium.chrome.browser.ui.favicon.FaviconHelper; |
| import org.chromium.chrome.tab_ui.R; |
| import org.chromium.chrome.test.util.browser.Features; |
| 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.search_engines.TemplateUrlService; |
| import org.chromium.content_public.browser.LoadUrlParams; |
| import org.chromium.content_public.browser.NavigationController; |
| import org.chromium.content_public.browser.NavigationEntry; |
| import org.chromium.content_public.browser.NavigationHistory; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.ui.base.PageTransition; |
| import org.chromium.ui.modelutil.PropertyModel; |
| import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * Tests for {@link TabListMediator}. |
| */ |
| @RunWith(BaseRobolectricTestRunner.class) |
| @Config(manifest = Config.NONE) |
| // clang-format off |
| @Features.EnableFeatures(ChromeFeatureList.TAB_TO_GTS_ANIMATION) |
| @Features.DisableFeatures({TAB_GROUPS_ANDROID, TAB_GROUPS_CONTINUATION_ANDROID}) |
| public class TabListMediatorUnitTest { |
| // clang-format on |
| @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 NEW_TITLE = "New title"; |
| private static final String CUSTOMIZED_DIALOG_TITLE1 = "Cool Tabs"; |
| private static final String TAB_GROUP_TITLES_FILE_NAME = "tab_group_titles"; |
| private static final String TAB1_DOMAIN = "tab1.com"; |
| private static final String TAB2_DOMAIN = "tab2.com"; |
| private static final String TAB3_DOMAIN = "tab3.com"; |
| private static final String NEW_DOMAIN = "new.com"; |
| private static final String TAB1_URL = "https://" + TAB1_DOMAIN; |
| private static final String TAB2_URL = "https://" + TAB2_DOMAIN; |
| private static final String TAB3_URL = "https://" + TAB3_DOMAIN; |
| private static final String NEW_URL = "https://" + NEW_DOMAIN; |
| private static final int TAB1_ID = 456; |
| private static final int TAB2_ID = 789; |
| private static final int TAB3_ID = 123; |
| private static final int POSITION1 = 0; |
| private static final int POSITION2 = 1; |
| |
| @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 |
| TabModelSelectorImpl mTabModelSelector; |
| @Mock |
| TabModelFilterProvider mTabModelFilterProvider; |
| @Mock |
| TabModelFilter mTabModelFilter; |
| @Mock |
| TabModel mTabModel; |
| @Mock |
| TabListFaviconProvider mTabListFaviconProvider; |
| @Mock |
| RecyclerView mRecyclerView; |
| @Mock |
| RecyclerView.Adapter mAdapter; |
| @Mock |
| TabGroupModelFilter mTabGroupModelFilter; |
| @Mock |
| EmptyTabModelFilter mEmptyTabModelFilter; |
| @Mock |
| TabListMediator.TabGridDialogHandler mTabGridDialogHandler; |
| @Mock |
| TabListMediator.GridCardOnClickListenerProvider mGridCardOnClickListenerProvider; |
| @Mock |
| Drawable mFaviconDrawable; |
| @Mock |
| Bitmap mFaviconBitmap; |
| @Mock |
| Activity mContext; |
| @Mock |
| TabListMediator.TabActionListener mOpenGroupActionListener; |
| @Mock |
| GridLayoutManager mGridLayoutManager; |
| @Mock |
| Profile mProfile; |
| @Mock |
| Tracker mTracker; |
| @Mock |
| TabListMediator.TitleProvider mTitleProvider; |
| @Mock |
| UrlUtilities.Natives mUrlUtilitiesJniMock; |
| @Mock |
| TabListMediator.TabGridAccessibilityHelper mTabGridAccessibilityHelper; |
| @Mock |
| TemplateUrlService mTemplateUrlService; |
| |
| @Captor |
| ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor; |
| @Captor |
| ArgumentCaptor<FaviconHelper.FaviconImageCallback> mFaviconCallbackCaptor; |
| @Captor |
| ArgumentCaptor<TabObserver> mTabObserverCaptor; |
| @Captor |
| ArgumentCaptor<Callback<Drawable>> mCallbackCaptor; |
| @Captor |
| ArgumentCaptor<TabGroupModelFilter.Observer> mTabGroupModelFilterObserverCaptor; |
| @Captor |
| ArgumentCaptor<ComponentCallbacks> mComponentCallbacksCaptor; |
| @Captor |
| ArgumentCaptor<TemplateUrlService.TemplateUrlServiceObserver> mTemplateUrlServiceObserver; |
| |
| private TabImpl mTab1; |
| private TabImpl mTab2; |
| private TabListMediator mMediator; |
| private TabListModel mModel; |
| private SimpleRecyclerViewAdapter.ViewHolder mViewHolder1; |
| private SimpleRecyclerViewAdapter.ViewHolder mViewHolder2; |
| private RecyclerView.ViewHolder mDummyViewHolder1; |
| private RecyclerView.ViewHolder mDummyViewHolder2; |
| private View mItemView1 = mock(View.class); |
| private View mItemView2 = mock(View.class); |
| private TabModelObserver mMediatorTabModelObserver; |
| private TabGroupModelFilter.Observer mMediatorTabGroupModelFilterObserver; |
| |
| @Before |
| public void setUp() { |
| RecordHistogram.setDisabledForTests(true); |
| |
| MockitoAnnotations.initMocks(this); |
| mMocker.mock(UrlUtilitiesJni.TEST_HOOKS, mUrlUtilitiesJniMock); |
| |
| CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, false); |
| TabUiFeatureUtilities.ENABLE_SEARCH_CHIP.setForTesting(true); |
| 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); |
| mDummyViewHolder1 = prepareDummyViewHolder(mItemView1, POSITION1); |
| mDummyViewHolder2 = prepareDummyViewHolder(mItemView2, POSITION2); |
| List<Tab> tabs1 = new ArrayList<>(Arrays.asList(mTab1)); |
| List<Tab> tabs2 = new ArrayList<>(Arrays.asList(mTab2)); |
| |
| List<TabModel> tabModelList = new ArrayList<>(); |
| tabModelList.add(mTabModel); |
| |
| doNothing() |
| .when(mTabContentManager) |
| .getTabThumbnailWithCallback(anyInt(), any(), anyBoolean(), anyBoolean()); |
| doReturn(mTabModel).when(mTabModelSelector).getCurrentModel(); |
| doReturn(tabModelList).when(mTabModelSelector).getModels(); |
| |
| doReturn(mTabModelFilterProvider).when(mTabModelSelector).getTabModelFilterProvider(); |
| doReturn(mTabModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter(); |
| doReturn(mTab1).when(mTabModelSelector).getCurrentTab(); |
| doReturn(TAB1_ID).when(mTabModelSelector).getCurrentTabId(); |
| doNothing() |
| .when(mTabModelFilterProvider) |
| .addTabModelFilterObserver(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); |
| doNothing().when(mTab1).addObserver(mTabObserverCaptor.capture()); |
| doReturn(0).when(mTabModel).index(); |
| doReturn(2).when(mTabModel).getCount(); |
| doNothing() |
| .when(mTabListFaviconProvider) |
| .getFaviconForUrlAsync(anyString(), anyBoolean(), mCallbackCaptor.capture()); |
| doReturn(mFaviconDrawable) |
| .when(mTabListFaviconProvider) |
| .getFaviconForUrlSync(anyString(), anyBoolean(), any(Bitmap.class)); |
| doReturn(mTab1).when(mTabModelSelector).getTabById(TAB1_ID); |
| doReturn(mTab2).when(mTabModelSelector).getTabById(TAB2_ID); |
| 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(mContext).registerComponentCallbacks(mComponentCallbacksCaptor.capture()); |
| doReturn(mGridLayoutManager).when(mRecyclerView).getLayoutManager(); |
| doReturn(TAB1_DOMAIN) |
| .when(mUrlUtilitiesJniMock) |
| .getDomainAndRegistry(eq(TAB1_URL), anyBoolean()); |
| doReturn(TAB2_DOMAIN) |
| .when(mUrlUtilitiesJniMock) |
| .getDomainAndRegistry(eq(TAB2_URL), anyBoolean()); |
| doReturn(TAB3_DOMAIN) |
| .when(mUrlUtilitiesJniMock) |
| .getDomainAndRegistry(eq(TAB3_URL), anyBoolean()); |
| doNothing().when(mTemplateUrlService).addObserver(mTemplateUrlServiceObserver.capture()); |
| doReturn(true).when(mTabListFaviconProvider).isInitialized(); |
| |
| mModel = new TabListModel(); |
| TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService); |
| mMediator = new TabListMediator(mContext, mModel, mTabModelSelector, |
| mTabContentManager::getTabThumbnailWithCallback, mTitleProvider, |
| mTabListFaviconProvider, false, null, mGridCardOnClickListenerProvider, null, |
| getClass().getSimpleName(), UiType.CLOSABLE); |
| mMediator.initWithNative(mProfile); |
| mMediator.registerOrientationListener(mGridLayoutManager); |
| TrackerFactory.setTrackerForTests(mTracker); |
| } |
| |
| @After |
| public void tearDown() { |
| RecordHistogram.setDisabledForTests(false); |
| CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, null); |
| TabAttributeCache.clearAllForTesting(); |
| getGroupTitleSharedPreferences().edit().clear(); |
| } |
| |
| private static SharedPreferences getGroupTitleSharedPreferences() { |
| return ContextUtils.getApplicationContext().getSharedPreferences( |
| TAB_GROUP_TITLES_FILE_NAME, Context.MODE_PRIVATE); |
| } |
| |
| @Test |
| public void initializesWithCurrentTabs() { |
| initAndAssertAllProperties(); |
| } |
| |
| @Test |
| public void updatesTitle_WithoutStoredTitle() { |
| initAndAssertAllProperties(); |
| |
| assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE)); |
| |
| doReturn(NEW_TITLE).when(mTitleProvider).getTitle(mTab1); |
| mTabObserverCaptor.getValue().onTitleUpdated(mTab1); |
| |
| assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(NEW_TITLE)); |
| } |
| |
| @Test |
| @Features.EnableFeatures(TAB_GROUPS_CONTINUATION_ANDROID) |
| public void updatesTitle_WithStoredTitle_TabGroup() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // Mock that tab1 and new tab are in the same group with root ID as TAB1_ID. |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab)); |
| createTabGroup(tabs, TAB1_ID); |
| |
| // Mock that we have a stored title stored with reference to root ID of tab1. |
| getGroupTitleSharedPreferences() |
| .edit() |
| .putString(String.valueOf(mTab1.getRootId()), CUSTOMIZED_DIALOG_TITLE1) |
| .apply(); |
| 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 updatesFavicon_SingleTab_GTS() { |
| initAndAssertAllProperties(); |
| mMediator.setActionOnAllRelatedTabsForTesting(true); |
| |
| mModel.get(0).model.set(TabProperties.FAVICON, null); |
| assertNull(mModel.get(0).model.get(TabProperties.FAVICON)); |
| |
| mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap); |
| |
| assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON)); |
| } |
| |
| @Test |
| public void updatesFavicon_SingleTab_NonGTS() { |
| initAndAssertAllProperties(); |
| |
| mModel.get(0).model.set(TabProperties.FAVICON, null); |
| assertNull(mModel.get(0).model.get(TabProperties.FAVICON)); |
| |
| mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap); |
| |
| assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON)); |
| } |
| |
| @Test |
| public void updatesFavicon_TabGroup_GTS() { |
| initAndAssertAllProperties(); |
| mMediator.setActionOnAllRelatedTabsForTesting(true); |
| |
| assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON)); |
| // Assert that tab1 is in a group. |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| doReturn(Arrays.asList(mTab1, newTab)).when(mTabModelFilter).getRelatedTabList(eq(TAB1_ID)); |
| |
| mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap); |
| |
| assertNull(mModel.get(0).model.get(TabProperties.FAVICON)); |
| } |
| |
| @Test |
| public void updateFavicon_StaleIndex() { |
| initAndAssertAllProperties(); |
| mModel.get(0).model.set(TabProperties.FAVICON, null); |
| mModel.get(1).model.set(TabProperties.FAVICON, null); |
| |
| mMediator.updateFaviconForTab(mTab2, null); |
| assertThat(mModel.indexFromId(TAB2_ID), equalTo(1)); |
| // Before executing callback, there is a deletion in tab list model which makes the index |
| // stale. |
| mModel.removeAt(0); |
| assertThat(mModel.indexFromId(TAB2_ID), equalTo(0)); |
| |
| // Start to execute callback. |
| mCallbackCaptor.getValue().onResult(mFaviconDrawable); |
| |
| assertThat(mModel.get(0).model.get(TabProperties.FAVICON), equalTo(mFaviconDrawable)); |
| } |
| |
| @Test |
| public void sendsSelectSignalCorrectly() { |
| initAndAssertAllProperties(); |
| |
| 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)); |
| } |
| |
| @Test |
| public void sendsCloseSignalCorrectly() { |
| initAndAssertAllProperties(); |
| |
| mModel.get(1) |
| .model.get(TabProperties.TAB_CLOSED_LISTENER) |
| .run(mModel.get(1).model.get(TabProperties.TAB_ID)); |
| |
| verify(mTabModel).closeTab(eq(mTab2), eq(null), eq(false), eq(false), eq(true)); |
| } |
| |
| @Test |
| public void sendsMoveTabSignalCorrectlyWithoutGroup() { |
| initAndAssertAllProperties(); |
| TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback(); |
| doReturn(mEmptyTabModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter(); |
| |
| itemTouchHelperCallback.onMove(mRecyclerView, mViewHolder1, mViewHolder2); |
| |
| verify(mTabModel).moveTab(eq(TAB1_ID), eq(2)); |
| } |
| |
| @Test |
| public void sendsMoveTabSignalCorrectlyWithGroup() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback(); |
| itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true); |
| |
| doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter(); |
| |
| itemTouchHelperCallback.onMove(mRecyclerView, mViewHolder1, mViewHolder2); |
| |
| verify(mTabGroupModelFilter).moveRelatedTabs(eq(TAB1_ID), eq(2)); |
| } |
| |
| @Test |
| public void sendsMoveTabSignalCorrectlyWithinGroup() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG); |
| |
| getItemTouchHelperCallback().onMove(mRecyclerView, mViewHolder1, mViewHolder2); |
| |
| verify(mTabModel).moveTab(eq(TAB1_ID), eq(2)); |
| } |
| |
| @Test |
| public void sendsMergeTabSignalCorrectly() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback(); |
| itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true); |
| itemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1); |
| itemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2); |
| itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1); |
| |
| doReturn(mAdapter).when(mRecyclerView).getAdapter(); |
| |
| // Simulate the drop action. |
| itemTouchHelperCallback.onSelectedChanged( |
| mDummyViewHolder2, 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)); |
| } |
| |
| @Test |
| public void neverSendsMergeTabSignal_Without_Group() { |
| initAndAssertAllProperties(); |
| |
| mMediator.setActionOnAllRelatedTabsForTesting(true); |
| TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback(); |
| itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true); |
| itemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1); |
| itemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2); |
| itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1); |
| |
| doReturn(mAdapter).when(mRecyclerView).getAdapter(); |
| |
| // Simulate the drop action. |
| itemTouchHelperCallback.onSelectedChanged( |
| mDummyViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE); |
| |
| verify(mGridLayoutManager, never()).removeView(any(View.class)); |
| } |
| |
| @Test |
| public void sendsUngroupSignalCorrectly() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG); |
| |
| TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback(); |
| itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(false); |
| itemTouchHelperCallback.setUnGroupTabIndexForTesting(POSITION1); |
| itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1); |
| |
| doReturn(mAdapter).when(mRecyclerView).getAdapter(); |
| doReturn(1).when(mAdapter).getItemCount(); |
| |
| // Simulate the ungroup action. |
| itemTouchHelperCallback.onSelectedChanged( |
| mDummyViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE); |
| |
| verify(mTabGroupModelFilter).moveTabOutOfGroup(eq(TAB1_ID)); |
| verify(mGridLayoutManager).removeView(mItemView1); |
| } |
| |
| @Test |
| public void tabClosure() { |
| initAndAssertAllProperties(); |
| |
| assertThat(mModel.size(), equalTo(2)); |
| |
| mTabModelObserverCaptor.getValue().willCloseTab(mTab2, false); |
| |
| assertThat(mModel.size(), equalTo(1)); |
| assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID)); |
| } |
| |
| @Test |
| public void tabClosure_IgnoresUpdatesForTabsOutsideOfModel() { |
| initAndAssertAllProperties(); |
| |
| mTabModelObserverCaptor.getValue().willCloseTab( |
| prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL), false); |
| |
| assertThat(mModel.size(), equalTo(2)); |
| } |
| |
| @Test |
| public void tabAddition_RestoreNotComplete() { |
| initAndAssertAllProperties(); |
| mMediator.setActionOnAllRelatedTabsForTesting(true); |
| assertThat(mTabModelFilter.isTabModelRestored(), equalTo(false)); |
| |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| doReturn(mTab1).when(mTabModelFilter).getTabAt(0); |
| doReturn(mTab2).when(mTabModelFilter).getTabAt(1); |
| doReturn(newTab).when(mTabModelFilter).getTabAt(2); |
| doReturn(3).when(mTabModelFilter).getCount(); |
| doReturn(Arrays.asList(newTab)).when(mTabModelFilter).getRelatedTabList(eq(TAB3_ID)); |
| assertThat(mModel.size(), equalTo(2)); |
| |
| mTabModelObserverCaptor.getValue().didAddTab( |
| newTab, TabLaunchType.FROM_RESTORE, TabCreationState.LIVE_IN_FOREGROUND); |
| |
| // When tab restoring stage is not yet finished, this tab info should not be added to |
| // property model. |
| assertThat(mModel.size(), equalTo(2)); |
| } |
| |
| @Test |
| public void tabAddition_Restore() { |
| initAndAssertAllProperties(); |
| mMediator.setActionOnAllRelatedTabsForTesting(true); |
| // Mock that tab restoring stage is over. |
| doReturn(true).when(mTabModelFilter).isTabModelRestored(); |
| TabListMediator.TabActionListener actionListenerBeforeUpdate = |
| mModel.get(1).model.get(TabProperties.TAB_SELECTED_LISTENER); |
| |
| // Mock that newTab was in the same group with tab, and now it is restored. |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| List<Tab> tabs = Arrays.asList(mTab2, newTab); |
| doReturn(mTab1).when(mTabModelFilter).getTabAt(0); |
| doReturn(mTab2).when(mTabModelFilter).getTabAt(1); |
| doReturn(2).when(mTabModelFilter).getCount(); |
| doReturn(1).when(mTabModelFilter).indexOf(newTab); |
| doReturn(tabs).when(mTabModelFilter).getRelatedTabList(eq(TAB3_ID)); |
| doReturn(tabs).when(mTabModelFilter).getRelatedTabList(eq(TAB2_ID)); |
| assertThat(mModel.size(), equalTo(2)); |
| |
| mTabModelObserverCaptor.getValue().didAddTab( |
| newTab, TabLaunchType.FROM_RESTORE, TabCreationState.LIVE_IN_FOREGROUND); |
| |
| TabListMediator.TabActionListener actionListenerAfterUpdate = |
| mModel.get(1).model.get(TabProperties.TAB_SELECTED_LISTENER); |
| // The selection listener should be updated which indicates that corresponding property |
| // model is updated. |
| assertThat(actionListenerBeforeUpdate, not(actionListenerAfterUpdate)); |
| assertThat(mModel.size(), equalTo(2)); |
| } |
| |
| @Test |
| public void tabAddition_Restore_SyncingTabListModelWithTabModel() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| // Mock that tab restoring stage is over. |
| doReturn(true).when(mTabGroupModelFilter).isTabModelRestored(); |
| |
| // 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); |
| 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(); |
| |
| mMediatorTabModelObserver.didAddTab( |
| mTab2, TabLaunchType.FROM_RESTORE, TabCreationState.LIVE_IN_FOREGROUND); |
| assertThat(mModel.size(), equalTo(0)); |
| |
| mMediatorTabModelObserver.didAddTab( |
| mTab1, TabLaunchType.FROM_RESTORE, TabCreationState.LIVE_IN_FOREGROUND); |
| assertThat(mModel.size(), equalTo(1)); |
| } |
| |
| @Test |
| public void tabAddition_GTS() { |
| initAndAssertAllProperties(); |
| mMediator.setActionOnAllRelatedTabsForTesting(true); |
| doReturn(true).when(mTabModelFilter).isTabModelRestored(); |
| |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| doReturn(mTab1).when(mTabModelFilter).getTabAt(0); |
| doReturn(mTab2).when(mTabModelFilter).getTabAt(1); |
| doReturn(newTab).when(mTabModelFilter).getTabAt(2); |
| doReturn(3).when(mTabModelFilter).getCount(); |
| doReturn(Arrays.asList(newTab)) |
| .when(mTabModelFilter) |
| .getRelatedTabList(eq(TAB3_ID)); |
| assertThat(mModel.size(), equalTo(2)); |
| |
| mTabModelObserverCaptor.getValue().didAddTab( |
| newTab, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND); |
| |
| 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_Skip() { |
| initAndAssertAllProperties(); |
| mMediator.setActionOnAllRelatedTabsForTesting(true); |
| doReturn(true).when(mTabModelFilter).isTabModelRestored(); |
| |
| // Add a new tab to the group with mTab2. |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| doReturn(mTab1).when(mTabModelFilter).getTabAt(0); |
| doReturn(mTab2).when(mTabModelFilter).getTabAt(1); |
| doReturn(2).when(mTabModelFilter).getCount(); |
| doReturn(Arrays.asList(mTab2, newTab)) |
| .when(mTabModelFilter) |
| .getRelatedTabList(eq(TAB3_ID)); |
| assertThat(mModel.size(), equalTo(2)); |
| |
| mTabModelObserverCaptor.getValue().didAddTab( |
| newTab, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND); |
| |
| assertThat(mModel.size(), equalTo(2)); |
| } |
| |
| @Test |
| public void tabAddition_GTS_Middle() { |
| initAndAssertAllProperties(); |
| mMediator.setActionOnAllRelatedTabsForTesting(true); |
| doReturn(true).when(mTabModelFilter).isTabModelRestored(); |
| |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| doReturn(mTab1).when(mTabModelFilter).getTabAt(0); |
| doReturn(newTab).when(mTabModelFilter).getTabAt(1); |
| doReturn(mTab2).when(mTabModelFilter).getTabAt(2); |
| doReturn(3).when(mTabModelFilter).getCount(); |
| doReturn(Arrays.asList(newTab)) |
| .when(mTabModelFilter) |
| .getRelatedTabList(eq(TAB3_ID)); |
| assertThat(mModel.size(), equalTo(2)); |
| |
| mTabModelObserverCaptor.getValue().didAddTab( |
| newTab, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND); |
| |
| 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() { |
| initAndAssertAllProperties(); |
| doReturn(true).when(mTabModelFilter).isTabModelRestored(); |
| |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| doReturn(3).when(mTabModel).getCount(); |
| doReturn(Arrays.asList(mTab1, mTab2, newTab)) |
| .when(mTabModelFilter) |
| .getRelatedTabList(eq(TAB1_ID)); |
| assertThat(mModel.size(), equalTo(2)); |
| |
| mTabModelObserverCaptor.getValue().didAddTab( |
| newTab, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND); |
| |
| 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() { |
| initAndAssertAllProperties(); |
| doReturn(true).when(mTabModelFilter).isTabModelRestored(); |
| |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| doReturn(3).when(mTabModel).getCount(); |
| doReturn(Arrays.asList(mTab1, newTab, mTab2)) |
| .when(mTabModelFilter) |
| .getRelatedTabList(eq(TAB1_ID)); |
| assertThat(mModel.size(), equalTo(2)); |
| |
| mTabModelObserverCaptor.getValue().didAddTab( |
| newTab, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND); |
| |
| 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() { |
| initAndAssertAllProperties(); |
| doReturn(true).when(mTabModelFilter).isTabModelRestored(); |
| |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| // newTab is of another group. |
| doReturn(Arrays.asList(mTab1, mTab2)).when(mTabModelFilter).getRelatedTabList(eq(TAB1_ID)); |
| assertThat(mModel.size(), equalTo(2)); |
| |
| mTabModelObserverCaptor.getValue().didAddTab( |
| newTab, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND); |
| |
| assertThat(mModel.size(), equalTo(2)); |
| } |
| |
| @Test |
| public void tabSelection() { |
| initAndAssertAllProperties(); |
| |
| 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)); |
| assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true)); |
| } |
| |
| @Test |
| public void tabClosureUndone() { |
| initAndAssertAllProperties(); |
| |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| doReturn(3).when(mTabModel).getCount(); |
| doReturn(Arrays.asList(mTab1, mTab2, newTab)) |
| .when(mTabModelFilter) |
| .getRelatedTabList(eq(TAB1_ID)); |
| |
| mTabModelObserverCaptor.getValue().tabClosureUndone(newTab); |
| |
| 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 tabMergeIntoGroup() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // 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. |
| doReturn(new ArrayList<>(Arrays.asList(mTab1, mTab2))) |
| .when(mTabGroupModelFilter) |
| .getRelatedTabList(TAB1_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)); |
| assertNotNull(mModel.get(1).model.get(TabProperties.FAVICON)); |
| |
| mMediatorTabGroupModelFilterObserver.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(TAB1_TITLE)); |
| assertNull(mModel.get(0).model.get(TabProperties.FAVICON)); |
| } |
| |
| @Test |
| public void tabMoveOutOfGroup_GTS_Moved_Tab_Selected() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // Assume that two tabs are in the same group before ungroup. |
| List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab2)); |
| mMediator.resetWithListOfTabs(tabs, false, 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(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION2); |
| doReturn(2).when(mTabGroupModelFilter).getCount(); |
| |
| mMediatorTabGroupModelFilterObserver.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() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // Assume that two tabs are in the same group before ungroup. |
| List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1)); |
| mMediator.resetWithListOfTabs(tabs, false, 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(); |
| |
| mMediatorTabGroupModelFilterObserver.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() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // Assume that tab1 is a single tab. |
| List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1)); |
| mMediator.resetWithListOfTabs(tabs, false, false); |
| doReturn(1).when(mTabGroupModelFilter).getCount(); |
| doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1); |
| doReturn(tabs).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID); |
| |
| // Ungroup the single tab. |
| mMediatorTabGroupModelFilterObserver.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)); |
| } |
| |
| @Test |
| public void tabMoveOutOfGroup_Dialog() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG); |
| |
| // 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)); |
| |
| mMediatorTabGroupModelFilterObserver.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() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG); |
| |
| // Assume that tab1 is a single tab. |
| List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1)); |
| mMediator.resetWithListOfTabs(tabs, false, false); |
| doReturn(1).when(mTabGroupModelFilter).getCount(); |
| doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1); |
| doReturn(tabs).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID); |
| |
| // Ungroup the single tab. |
| mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab1, POSITION1); |
| |
| verify(mTabGridDialogHandler).updateDialogContent(Tab.INVALID_TAB_ID); |
| } |
| |
| @Test |
| public void tabMoveOutOfGroup_Strip() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_STRIP); |
| |
| // 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)); |
| |
| mMediatorTabGroupModelFilterObserver.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 tabMovementWithoutGroup_Forward() { |
| initAndAssertAllProperties(); |
| |
| doReturn(mEmptyTabModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter(); |
| |
| 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)); |
| |
| mTabModelObserverCaptor.getValue().didMoveTab(mTab2, 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 tabMovementWithoutGroup_Backward() { |
| initAndAssertAllProperties(); |
| |
| doReturn(mEmptyTabModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter(); |
| |
| 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)); |
| |
| mTabModelObserverCaptor.getValue().didMoveTab(mTab1, 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_Forward() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // Assume that moveTab in TabModel is finished. |
| doReturn(mTab1).when(mTabModel).getTabAt(POSITION2); |
| doReturn(mTab2).when(mTabModel).getTabAt(POSITION1); |
| doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter(); |
| |
| 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)); |
| |
| mMediatorTabGroupModelFilterObserver.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() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // Assume that moveTab in TabModel is finished. |
| doReturn(mTab1).when(mTabModel).getTabAt(POSITION2); |
| doReturn(mTab2).when(mTabModel).getTabAt(POSITION1); |
| doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter(); |
| |
| 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)); |
| |
| mMediatorTabGroupModelFilterObserver.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_Forward() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG); |
| |
| // Assume that moveTab in TabModel is finished. |
| doReturn(mTab1).when(mTabModel).getTabAt(POSITION2); |
| doReturn(mTab2).when(mTabModel).getTabAt(POSITION1); |
| doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter(); |
| 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)); |
| |
| mMediatorTabGroupModelFilterObserver.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_Backward() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG); |
| |
| // Assume that moveTab in TabModel is finished. |
| doReturn(mTab1).when(mTabModel).getTabAt(POSITION2); |
| doReturn(mTab2).when(mTabModel).getTabAt(POSITION1); |
| doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter(); |
| 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)); |
| |
| mMediatorTabGroupModelFilterObserver.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 undoGrouped_One_Adjacent_Tab() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // 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, 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); |
| |
| mMediatorTabGroupModelFilterObserver.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() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // 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, false); |
| assertThat(mModel.size(), equalTo(2)); |
| |
| // Assume undo grouping tab3 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); |
| |
| mMediatorTabGroupModelFilterObserver.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() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // 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, false); |
| assertThat(mModel.size(), equalTo(2)); |
| |
| // Assume undo grouping tab3 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); |
| |
| mMediatorTabGroupModelFilterObserver.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 updateSpanCount_Portrait_SingleWindow() { |
| initAndAssertAllProperties(); |
| // Mock that we are switching to portrait mode. |
| Configuration configuration = new Configuration(); |
| configuration.orientation = Configuration.ORIENTATION_PORTRAIT; |
| // Mock that we are in single window mode. |
| MultiWindowUtils.getInstance().setIsInMultiWindowModeForTesting(false); |
| |
| mComponentCallbacksCaptor.getValue().onConfigurationChanged(configuration); |
| |
| verify(mGridLayoutManager).setSpanCount(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_PORTRAIT); |
| } |
| |
| @Test |
| public void updateSpanCount_Landscape_SingleWindow() { |
| initAndAssertAllProperties(); |
| // Mock that we are switching to landscape mode. |
| Configuration configuration = new Configuration(); |
| configuration.orientation = Configuration.ORIENTATION_LANDSCAPE; |
| // Mock that we are in single window mode. |
| MultiWindowUtils.getInstance().setIsInMultiWindowModeForTesting(false); |
| |
| mComponentCallbacksCaptor.getValue().onConfigurationChanged(configuration); |
| |
| verify(mGridLayoutManager) |
| .setSpanCount(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_LANDSCAPE); |
| } |
| |
| @Test |
| public void updateSpanCount_MultiWindow() { |
| initAndAssertAllProperties(); |
| Configuration portraitConfiguration = new Configuration(); |
| portraitConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT; |
| Configuration landscapeConfiguration = new Configuration(); |
| landscapeConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; |
| // Mock that we are in multi window mode. |
| MultiWindowUtils.getInstance().setIsInMultiWindowModeForTesting(true); |
| |
| mComponentCallbacksCaptor.getValue().onConfigurationChanged(landscapeConfiguration); |
| mComponentCallbacksCaptor.getValue().onConfigurationChanged(portraitConfiguration); |
| mComponentCallbacksCaptor.getValue().onConfigurationChanged(landscapeConfiguration); |
| |
| // The span count is fixed to 2 for multi window mode regardless of the orientation change. |
| verify(mGridLayoutManager, times(3)) |
| .setSpanCount(TabListCoordinator.GRID_LAYOUT_SPAN_COUNT_PORTRAIT); |
| } |
| |
| @Test |
| public void resetWithListOfTabs_MruOrder() { |
| List<Tab> tabs = new ArrayList<>(); |
| for (int i = 0; i < mTabModel.getCount(); i++) { |
| tabs.add(mTabModel.getTabAt(i)); |
| } |
| assertThat(tabs.size(), equalTo(2)); |
| |
| long timestamp1 = 1; |
| long timestamp2 = 2; |
| doReturn(timestamp1).when(mTab1).getTimestampMillis(); |
| doReturn(timestamp2).when(mTab2).getTimestampMillis(); |
| mMediator.resetWithListOfTabs(tabs, /*quickMode =*/false, /*mruMode =*/true); |
| |
| assertThat(mModel.size(), equalTo(2)); |
| assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID)); |
| assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID)); |
| assertThat(mMediator.indexOfTab(TAB1_ID), equalTo(1)); |
| assertThat(mMediator.indexOfTab(TAB2_ID), equalTo(0)); |
| |
| doReturn(timestamp2).when(mTab1).getTimestampMillis(); |
| doReturn(timestamp1).when(mTab2).getTimestampMillis(); |
| mMediator.resetWithListOfTabs(tabs, /*quickMode =*/false, /*mruMode =*/true); |
| |
| 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(mMediator.indexOfTab(TAB1_ID), equalTo(0)); |
| assertThat(mMediator.indexOfTab(TAB2_ID), equalTo(1)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void getLatestTitle_NotGTS() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG); |
| // Mock that we have a stored title stored with reference to root ID of tab1. |
| getGroupTitleSharedPreferences() |
| .edit() |
| .putString(String.valueOf(mTab1.getRootId()), CUSTOMIZED_DIALOG_TITLE1) |
| .apply(); |
| 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); |
| |
| // Even if we have a stored title, we only show it in tab switcher. |
| assertThat(mMediator.getLatestTitleForTab(mTab1), equalTo(TAB1_TITLE)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void getLatestTitle_SingleTab_GTS() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| // Mock that we have a stored title stored with reference to root ID of tab1. |
| getGroupTitleSharedPreferences() |
| .edit() |
| .putString(String.valueOf(mTab1.getRootId()), CUSTOMIZED_DIALOG_TITLE1) |
| .apply(); |
| 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); |
| |
| // We never show stored title for single tab. |
| assertThat(mMediator.getLatestTitleForTab(mTab1), equalTo(TAB1_TITLE)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void getLatestTitle_Stored_GTS() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| // Mock that we have a stored title stored with reference to root ID of tab1. |
| getGroupTitleSharedPreferences() |
| .edit() |
| .putString(String.valueOf(mTab1.getRootId()), CUSTOMIZED_DIALOG_TITLE1) |
| .apply(); |
| 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); |
| |
| assertThat(mMediator.getLatestTitleForTab(mTab1), equalTo(CUSTOMIZED_DIALOG_TITLE1)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void updateTabGroupTitle_GTS() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| 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. |
| TabImpl newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); |
| List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab)); |
| createTabGroup(tabs, TAB1_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)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void tabGroupTitleEditor_storeTitle() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| TabGroupTitleEditor tabGroupTitleEditor = mMediator.getTabGroupTitleEditor(); |
| |
| assertNull(getGroupTitleSharedPreferences().getString( |
| String.valueOf(mTab1.getRootId()), null)); |
| |
| tabGroupTitleEditor.storeTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1); |
| assertEquals(CUSTOMIZED_DIALOG_TITLE1, |
| getGroupTitleSharedPreferences().getString( |
| String.valueOf(mTab1.getRootId()), null)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void tabGroupTitleEditor_deleteTitle() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| TabGroupTitleEditor tabGroupTitleEditor = mMediator.getTabGroupTitleEditor(); |
| |
| getGroupTitleSharedPreferences() |
| .edit() |
| .putString(String.valueOf(mTab1.getRootId()), CUSTOMIZED_DIALOG_TITLE1) |
| .apply(); |
| assertEquals(CUSTOMIZED_DIALOG_TITLE1, |
| getGroupTitleSharedPreferences().getString( |
| String.valueOf(mTab1.getRootId()), null)); |
| |
| tabGroupTitleEditor.deleteTabGroupTitle(mTab1.getRootId()); |
| assertNull(getGroupTitleSharedPreferences().getString( |
| String.valueOf(mTab1.getRootId()), null)); |
| } |
| |
| @Test |
| public void addSpecialItem() { |
| 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() { |
| 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, false, 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(expected = AssertionError.class) |
| public void addSpecialItem_withoutTabListModelProperties() { |
| mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, new PropertyModel()); |
| } |
| |
| @Test |
| public void removeSpecialItem_Message() { |
| PropertyModel model = mock(PropertyModel.class); |
| int expectedMessageType = FOR_TESTING; |
| int wrongMessageType = TAB_SUGGESTION; |
| 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 |
| @Features.DisableFeatures({TAB_GROUPS_ANDROID}) |
| public void testUrlUpdated_forSingleTab_GTS_GroupNotEnabled() { |
| initAndAssertAllProperties(); |
| assertNotEquals(NEW_URL, mModel.get(POSITION1).model.get(TabProperties.URL)); |
| |
| doReturn(NEW_URL).when(mTab1).getUrlString(); |
| mTabObserverCaptor.getValue().onUrlUpdated(mTab1); |
| |
| // TabProperties.URL is empty string if TabGroupsAndroidContinuationEnabled is false. |
| assertEquals("", mModel.get(POSITION1).model.get(TabProperties.URL)); |
| assertEquals("", mModel.get(POSITION2).model.get(TabProperties.URL)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void testUrlUpdated_forSingleTab_GTS() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| assertNotEquals(NEW_URL, mModel.get(POSITION1).model.get(TabProperties.URL)); |
| |
| doReturn(NEW_URL).when(mTab1).getUrlString(); |
| mTabObserverCaptor.getValue().onUrlUpdated(mTab1); |
| |
| assertEquals(NEW_URL, mModel.get(POSITION1).model.get(TabProperties.URL)); |
| assertEquals(TAB2_URL, mModel.get(POSITION2).model.get(TabProperties.URL)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void testUrlUpdated_forGroup_GTS() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2)); |
| createTabGroup(tabs, TAB1_ID); |
| doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1); |
| doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2); |
| |
| mMediatorTabGroupModelFilterObserver.didMergeTabToGroup(mTab2, TAB1_ID); |
| assertEquals(TAB1_DOMAIN + ", " + TAB2_DOMAIN, |
| mModel.get(POSITION1).model.get(TabProperties.URL)); |
| |
| doReturn(NEW_DOMAIN) |
| .when(mUrlUtilitiesJniMock) |
| .getDomainAndRegistry(eq(NEW_URL), anyBoolean()); |
| |
| // Update URL for mTab1. |
| doReturn(NEW_URL).when(mTab1).getUrlString(); |
| mTabObserverCaptor.getValue().onUrlUpdated(mTab1); |
| |
| assertEquals(NEW_DOMAIN + ", " + TAB2_DOMAIN, |
| mModel.get(POSITION1).model.get(TabProperties.URL)); |
| |
| // Update URL for mTab2. |
| doReturn(NEW_URL).when(mTab2).getUrlString(); |
| mTabObserverCaptor.getValue().onUrlUpdated(mTab2); |
| |
| assertEquals( |
| NEW_DOMAIN + ", " + NEW_DOMAIN, mModel.get(POSITION1).model.get(TabProperties.URL)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void testUrlUpdated_forGroup_Dialog() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG); |
| List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2)); |
| createTabGroup(tabs, TAB1_ID); |
| doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1); |
| doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab2); |
| |
| mMediatorTabGroupModelFilterObserver.didMergeTabToGroup(mTab2, TAB1_ID); |
| assertEquals(TAB1_URL, mModel.get(POSITION1).model.get(TabProperties.URL)); |
| assertEquals(TAB2_URL, mModel.get(POSITION2).model.get(TabProperties.URL)); |
| |
| doReturn(NEW_DOMAIN) |
| .when(mUrlUtilitiesJniMock) |
| .getDomainAndRegistry(eq(NEW_URL), anyBoolean()); |
| |
| // Update URL for mTab1. |
| doReturn(NEW_URL).when(mTab1).getUrlString(); |
| mTabObserverCaptor.getValue().onUrlUpdated(mTab1); |
| |
| assertEquals(NEW_URL, mModel.get(POSITION1).model.get(TabProperties.URL)); |
| assertEquals(TAB2_URL, mModel.get(POSITION2).model.get(TabProperties.URL)); |
| |
| // Update URL for mTab2. |
| doReturn(NEW_URL).when(mTab2).getUrlString(); |
| mTabObserverCaptor.getValue().onUrlUpdated(mTab2); |
| |
| assertEquals(NEW_URL, mModel.get(POSITION1).model.get(TabProperties.URL)); |
| assertEquals(NEW_URL, mModel.get(POSITION2).model.get(TabProperties.URL)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void testUrlUpdated_forUnGroup() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2)); |
| createTabGroup(tabs, TAB1_ID); |
| |
| mMediatorTabGroupModelFilterObserver.didMergeTabToGroup(mTab2, TAB1_ID); |
| assertEquals(TAB1_DOMAIN + ", " + TAB2_DOMAIN, |
| mModel.get(POSITION1).model.get(TabProperties.URL)); |
| |
| // Assume that TabGroupModelFilter is already updated. |
| when(mTabGroupModelFilter.getRelatedTabList(TAB1_ID)).thenReturn(Arrays.asList(mTab1)); |
| when(mTabGroupModelFilter.getRelatedTabList(TAB2_ID)).thenReturn(Arrays.asList(mTab2)); |
| doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1); |
| doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2); |
| doReturn(2).when(mTabGroupModelFilter).getCount(); |
| |
| mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab2, POSITION1); |
| assertEquals(TAB1_URL, mModel.get(POSITION1).model.get(TabProperties.URL)); |
| assertEquals(TAB2_URL, mModel.get(POSITION2).model.get(TabProperties.URL)); |
| } |
| |
| @Test |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void testOnInitializeAccessibilityNodeInfo() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| |
| // 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 |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void testPerformAccessibilityAction() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| 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 |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void testPerformAccessibilityAction_defaultAccessibilityAction() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| 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 |
| @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) |
| public void testPerformAccessibilityAction_InvalidIndex() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| 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(); |
| mMediator.setActionOnAllRelatedTabsForTesting(true); |
| |
| assertThat(mModel.size(), equalTo(2)); |
| mTabModelObserverCaptor.getValue().willCloseTab(mTab2, false); |
| 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(); |
| mMediator.setActionOnAllRelatedTabsForTesting(true); |
| |
| assertThat(mModel.size(), equalTo(2)); |
| mTabModelObserverCaptor.getValue().willCloseTab(mTab2, false); |
| assertThat(mModel.size(), equalTo(1)); |
| |
| // Assume that TabModelFilter is already updated to reflect closed tab is undone. |
| doReturn(2).when(mTabModelFilter).getCount(); |
| doReturn(mTab1).when(mTabModelFilter).getTabAt(POSITION1); |
| doReturn(mTab2).when(mTabModelFilter).getTabAt(POSITION2); |
| when(mTabModelFilter.getRelatedTabList(TAB1_ID)).thenReturn(Arrays.asList(mTab1)); |
| when(mTabModelFilter.getRelatedTabList(TAB2_ID)).thenReturn(Arrays.asList(mTab2)); |
| |
| mTabModelObserverCaptor.getValue().tabClosureUndone(mTab2); |
| assertThat(mModel.size(), equalTo(2)); |
| // First time is when mTab2 initially added to mModel; second time is when mTab2 added back |
| // to mModel because of undo action. |
| verify(mTab2, times(2)).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, false, false); |
| assertThat(showQuickly, equalTo(true)); |
| |
| // Create a PropertyModel that is not a tab and add it to the existing TabListModel. |
| PropertyModel propertyModel = new PropertyModel.Builder(NewTabTileViewProperties.ALL_KEYS) |
| .with(CARD_TYPE, OTHERS) |
| .build(); |
| mMediator.addSpecialItemToModel( |
| mModel.size(), TabProperties.UiType.NEW_TAB_TILE, propertyModel); |
| assertThat(mModel.size(), equalTo(tabs.size() + 1)); |
| |
| // TabListModel unchange check should ignore the non-Tab item. |
| showQuickly = mMediator.resetWithListOfTabs(tabs, false, false); |
| assertThat(showQuickly, equalTo(true)); |
| } |
| |
| @Test |
| public void testSearchTermProperty() { |
| initAndAssertAllProperties(); |
| List<Tab> tabs = new ArrayList<>(); |
| for (int i = 0; i < mTabModel.getCount(); i++) { |
| tabs.add(mTabModel.getTabAt(i)); |
| } |
| // The fast path to trigger updateTab(). |
| boolean showQuickly = mMediator.resetWithListOfTabs(tabs, false, false); |
| assertThat(showQuickly, equalTo(true)); |
| |
| assertThat(mModel.size(), equalTo(2)); |
| assertThat(mModel.get(0).model.get(TabProperties.SEARCH_QUERY), equalTo(null)); |
| assertThat(mModel.get(1).model.get(TabProperties.SEARCH_QUERY), equalTo(null)); |
| |
| String searchTerm1 = "hello world"; |
| String searchTerm2 = "y'all"; |
| TabAttributeCache.setLastSearchTermForTesting(TAB1_ID, searchTerm1); |
| TabAttributeCache.setLastSearchTermForTesting(TAB2_ID, searchTerm2); |
| showQuickly = mMediator.resetWithListOfTabs(tabs, false, false); |
| assertThat(showQuickly, equalTo(true)); |
| assertThat(mModel.get(0).model.get(TabProperties.SEARCH_QUERY), equalTo(searchTerm1)); |
| assertThat(mModel.get(1).model.get(TabProperties.SEARCH_QUERY), equalTo(searchTerm2)); |
| |
| TabAttributeCache.setLastSearchTermForTesting(TAB1_ID, null); |
| TabAttributeCache.setLastSearchTermForTesting(TAB2_ID, null); |
| showQuickly = mMediator.resetWithListOfTabs(tabs, false, false); |
| assertThat(showQuickly, equalTo(true)); |
| assertThat(mModel.get(0).model.get(TabProperties.SEARCH_QUERY), equalTo(null)); |
| assertThat(mModel.get(1).model.get(TabProperties.SEARCH_QUERY), equalTo(null)); |
| |
| // The slow path to trigger addTabInfoToModel(). |
| tabs = new ArrayList<>(Arrays.asList(mTab1)); |
| TabAttributeCache.setLastSearchTermForTesting(TAB1_ID, searchTerm1); |
| showQuickly = mMediator.resetWithListOfTabs(tabs, false, false); |
| assertThat(showQuickly, equalTo(false)); |
| assertThat(mModel.size(), equalTo(1)); |
| assertThat(mModel.get(0).model.get(TabProperties.SEARCH_QUERY), equalTo(searchTerm1)); |
| } |
| |
| @Test |
| public void testSearchTermProperty_TabGroups_TabSwitcher() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); |
| String searchTerm1 = "hello world"; |
| TabAttributeCache.setLastSearchTermForTesting(TAB1_ID, searchTerm1); |
| |
| mMediator.resetWithListOfTabs(new ArrayList<>(Arrays.asList(mTab1)), false, false); |
| assertThat(mModel.size(), equalTo(1)); |
| assertThat(mModel.get(0).model.get(TabProperties.SEARCH_QUERY), equalTo(searchTerm1)); |
| |
| createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID); |
| mMediator.resetWithListOfTabs(new ArrayList<>(Arrays.asList(mTab1)), false, false); |
| assertThat(mModel.size(), equalTo(1)); |
| assertThat(mModel.get(0).model.get(TabProperties.SEARCH_QUERY), equalTo(null)); |
| } |
| |
| @Test |
| public void testSearchTermProperty_TabGroups_Dialog() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG); |
| createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID); |
| String searchTerm1 = "hello world"; |
| TabAttributeCache.setLastSearchTermForTesting(TAB1_ID, searchTerm1); |
| |
| mMediator.resetWithListOfTabs(new ArrayList<>(Arrays.asList(mTab1)), false, false); |
| assertThat(mModel.size(), equalTo(1)); |
| assertThat(mModel.get(0).model.get(TabProperties.SEARCH_QUERY), equalTo(searchTerm1)); |
| } |
| |
| @Test |
| public void testSearchTermProperty_TabGroups_Strip() { |
| setUpForTabGroupOperation(TabListMediatorType.TAB_STRIP); |
| createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID); |
| String searchTerm1 = "hello world"; |
| TabAttributeCache.setLastSearchTermForTesting(TAB1_ID, searchTerm1); |
| |
| mMediator.resetWithListOfTabs(new ArrayList<>(Arrays.asList(mTab1)), false, false); |
| assertThat(mModel.size(), equalTo(1)); |
| assertThat(mModel.get(0).model.get(TabProperties.SEARCH_QUERY), equalTo(null)); |
| } |
| |
| @Test |
| public void navigateToLastSearchQuery() { |
| initAndAssertAllProperties(); |
| |
| String otherUrl = "https://example.com"; |
| String searchUrl = "https://www.google.com/search?q=test"; |
| String searchTerm = "test"; |
| String searchUrl2 = "https://www.google.com/search?q=query"; |
| String searchTerm2 = "query"; |
| TemplateUrlService service = Mockito.mock(TemplateUrlService.class); |
| doReturn(null).when(service).getSearchQueryForUrl(otherUrl); |
| doReturn(searchTerm).when(service).getSearchQueryForUrl(searchUrl); |
| doReturn(searchTerm2).when(service).getSearchQueryForUrl(searchUrl2); |
| TemplateUrlServiceFactory.setInstanceForTesting(service); |
| |
| WebContents webContents = mock(WebContents.class); |
| doReturn(webContents).when(mTab1).getWebContents(); |
| NavigationController navigationController = mock(NavigationController.class); |
| doReturn(navigationController).when(webContents).getNavigationController(); |
| NavigationHistory navigationHistory = mock(NavigationHistory.class); |
| doReturn(navigationHistory).when(navigationController).getNavigationHistory(); |
| doReturn(true).when(navigationController).canGoToOffset(anyInt()); |
| doReturn(2).when(navigationHistory).getCurrentEntryIndex(); |
| NavigationEntry navigationEntry1 = mock(NavigationEntry.class); |
| NavigationEntry navigationEntry0 = mock(NavigationEntry.class); |
| doReturn(navigationEntry1).when(navigationHistory).getEntryAtIndex(1); |
| doReturn(navigationEntry0).when(navigationHistory).getEntryAtIndex(0); |
| |
| InOrder inOrder = Mockito.inOrder(mTab1); |
| |
| // No searches. |
| doReturn(otherUrl).when(navigationEntry1).getOriginalUrl(); |
| doReturn(otherUrl).when(navigationEntry0).getOriginalUrl(); |
| TabListMediator.SearchTermChipUtils.navigateToLastSearchQuery(mTab1); |
| inOrder.verify(mTab1, never()).loadUrl(any()); |
| |
| // Has SRP. |
| doReturn(searchUrl).when(navigationEntry1).getOriginalUrl(); |
| doReturn(otherUrl).when(navigationEntry0).getOriginalUrl(); |
| TabListMediator.SearchTermChipUtils.navigateToLastSearchQuery(mTab1); |
| inOrder.verify(mTab1).loadUrl( |
| refEq(new LoadUrlParams(searchUrl, PageTransition.KEYWORD_GENERATED))); |
| |
| // Has earlier SRP. |
| doReturn(otherUrl).when(navigationEntry1).getOriginalUrl(); |
| doReturn(searchUrl2).when(navigationEntry0).getOriginalUrl(); |
| TabListMediator.SearchTermChipUtils.navigateToLastSearchQuery(mTab1); |
| inOrder.verify(mTab1).loadUrl( |
| refEq(new LoadUrlParams(searchUrl2, PageTransition.KEYWORD_GENERATED))); |
| |
| // Latest one wins. |
| doReturn(searchUrl).when(navigationEntry1).getOriginalUrl(); |
| doReturn(searchUrl2).when(navigationEntry0).getOriginalUrl(); |
| TabListMediator.SearchTermChipUtils.navigateToLastSearchQuery(mTab1); |
| inOrder.verify(mTab1).loadUrl( |
| refEq(new LoadUrlParams(searchUrl, PageTransition.KEYWORD_GENERATED))); |
| |
| // Rejected by canGoToOffset(). |
| doReturn(false).when(navigationController).canGoToOffset(eq(-1)); |
| TabListMediator.SearchTermChipUtils.navigateToLastSearchQuery(mTab1); |
| inOrder.verify(mTab1).loadUrl( |
| refEq(new LoadUrlParams(searchUrl2, PageTransition.KEYWORD_GENERATED))); |
| |
| // Reset canGoToOffset(). |
| doReturn(true).when(navigationController).canGoToOffset(anyInt()); |
| TabListMediator.SearchTermChipUtils.navigateToLastSearchQuery(mTab1); |
| inOrder.verify(mTab1).loadUrl( |
| refEq(new LoadUrlParams(searchUrl, PageTransition.KEYWORD_GENERATED))); |
| |
| // Only care about previous ones. |
| doReturn(1).when(navigationHistory).getCurrentEntryIndex(); |
| TabListMediator.SearchTermChipUtils.navigateToLastSearchQuery(mTab1); |
| inOrder.verify(mTab1).loadUrl( |
| refEq(new LoadUrlParams(searchUrl2, PageTransition.KEYWORD_GENERATED))); |
| } |
| |
| @Test |
| public void searchListener() { |
| initAndAssertAllProperties(); |
| |
| String otherUrl = "https://example.com"; |
| String searchUrl = "https://www.google.com/search?q=test"; |
| String searchTerm = "test"; |
| TemplateUrlService service = Mockito.mock(TemplateUrlService.class); |
| doReturn(null).when(service).getSearchQueryForUrl(otherUrl); |
| doReturn(searchTerm).when(service).getSearchQueryForUrl(searchUrl); |
| TemplateUrlServiceFactory.setInstanceForTesting(service); |
| |
| WebContents webContents = mock(WebContents.class); |
| doReturn(webContents).when(mTab1).getWebContents(); |
| NavigationController navigationController = mock(NavigationController.class); |
| doReturn(navigationController).when(webContents).getNavigationController(); |
| NavigationHistory navigationHistory = mock(NavigationHistory.class); |
| doReturn(navigationHistory).when(navigationController).getNavigationHistory(); |
| doReturn(true).when(navigationController).canGoToOffset(anyInt()); |
| doReturn(2).when(navigationHistory).getCurrentEntryIndex(); |
| NavigationEntry navigationEntry1 = mock(NavigationEntry.class); |
| NavigationEntry navigationEntry0 = mock(NavigationEntry.class); |
| doReturn(navigationEntry1).when(navigationHistory).getEntryAtIndex(1); |
| doReturn(navigationEntry0).when(navigationHistory).getEntryAtIndex(0); |
| doReturn(otherUrl).when(navigationEntry1).getOriginalUrl(); |
| doReturn(searchUrl).when(navigationEntry0).getOriginalUrl(); |
| |
| mModel.get(0) |
| .model.get(TabProperties.SEARCH_LISTENER) |
| .run(mModel.get(0).model.get(TabProperties.TAB_ID)); |
| |
| verify(mGridCardOnClickListenerProvider) |
| .onTabSelecting(mModel.get(0).model.get(TabProperties.TAB_ID)); |
| verify(mTab1).loadUrl( |
| refEq(new LoadUrlParams(searchUrl, PageTransition.KEYWORD_GENERATED))); |
| } |
| |
| @Test |
| public void searchListener_frozenTab() { |
| initAndAssertAllProperties(); |
| |
| String searchUrl = "https://www.google.com/search?q=test"; |
| String searchTerm = "test"; |
| TemplateUrlService service = Mockito.mock(TemplateUrlService.class); |
| doReturn(searchTerm).when(service).getSearchQueryForUrl(searchUrl); |
| TemplateUrlServiceFactory.setInstanceForTesting(service); |
| |
| WebContents webContents = mock(WebContents.class); |
| NavigationController navigationController = mock(NavigationController.class); |
| doReturn(navigationController).when(webContents).getNavigationController(); |
| NavigationHistory navigationHistory = mock(NavigationHistory.class); |
| doReturn(navigationHistory).when(navigationController).getNavigationHistory(); |
| doReturn(true).when(navigationController).canGoToOffset(anyInt()); |
| doReturn(1).when(navigationHistory).getCurrentEntryIndex(); |
| NavigationEntry navigationEntry = mock(NavigationEntry.class); |
| doReturn(navigationEntry).when(navigationHistory).getEntryAtIndex(0); |
| doReturn(searchUrl).when(navigationEntry).getOriginalUrl(); |
| |
| mModel.get(0) |
| .model.get(TabProperties.SEARCH_LISTENER) |
| .run(mModel.get(0).model.get(TabProperties.TAB_ID)); |
| |
| verify(mGridCardOnClickListenerProvider) |
| .onTabSelecting(mModel.get(0).model.get(TabProperties.TAB_ID)); |
| verify(navigationController, never()).goToOffset(0); |
| |
| doReturn(webContents).when(mTab1).getWebContents(); |
| mTabObserverCaptor.getValue().onPageLoadStarted(mTab1, searchUrl); |
| verify(mTab1).loadUrl( |
| refEq(new LoadUrlParams(searchUrl, PageTransition.KEYWORD_GENERATED))); |
| } |
| |
| @Test |
| public void testSearchChipAdaptiveIcon_Disabled() { |
| // Mock that google is the default search engine, and the search chip adaptive icon field |
| // is set as false. |
| doReturn(true).when(mTemplateUrlService).isDefaultSearchEngineGoogle(); |
| |
| // Re-initialize the mediator to setup TemplateUrlServiceObserver if needed. |
| mMediator = new TabListMediator(mContext, mModel, mTabModelSelector, |
| mTabContentManager::getTabThumbnailWithCallback, mTitleProvider, |
| mTabListFaviconProvider, true, null, null, null, getClass().getSimpleName(), |
| TabProperties.UiType.CLOSABLE); |
| mMediator.initWithNative(mProfile); |
| |
| initAndAssertAllProperties(); |
| |
| // When the search chip adaptive icon is turned off, the search chip icon is initialized as |
| // R.drawable.ic_search even if the default search engine is google. |
| for (int i = 0; i < mModel.size(); i++) { |
| assertThat(mModel.get(i).model.get(TabProperties.SEARCH_CHIP_ICON_DRAWABLE_ID), |
| equalTo(R.drawable.ic_search)); |
| } |
| } |
| |
| @Test |
| public void testSearchChipAdaptiveIcon_ChangeWithSetting() { |
| // Mock that google is the default search engine, and the search chip adaptive icon is |
| // turned on. |
| TabUiFeatureUtilities.ENABLE_SEARCH_CHIP_ADAPTIVE.setForTesting(true); |
| doReturn(true).when(mTemplateUrlService).isDefaultSearchEngineGoogle(); |
| |
| // Re-initialize the mediator to setup TemplateUrlServiceObserver if needed. |
| mMediator = new TabListMediator(mContext, mModel, mTabModelSelector, |
| mTabContentManager::getTabThumbnailWithCallback, mTitleProvider, |
| mTabListFaviconProvider, true, null, null, null, getClass().getSimpleName(), |
| TabProperties.UiType.CLOSABLE); |
| mMediator.initWithNative(mProfile); |
| |
| initAndAssertAllProperties(); |
| |
| // The search chip icon should be initialized as R.drawable.ic_logo_googleg_24dp. |
| for (int i = 0; i < mModel.size(); i++) { |
| assertThat(mModel.get(i).model.get(TabProperties.SEARCH_CHIP_ICON_DRAWABLE_ID), |
| equalTo(R.drawable.ic_logo_googleg_24dp)); |
| } |
| |
| // Mock that user has switched to a non-google search engine as default. |
| doReturn(false).when(mTemplateUrlService).isDefaultSearchEngineGoogle(); |
| mTemplateUrlServiceObserver.getValue().onTemplateURLServiceChanged(); |
| |
| // The search chip icon should be updated to R.drawable.ic_search. |
| for (int i = 0; i < mModel.size(); i++) { |
| assertThat(mModel.get(i).model.get(TabProperties.SEARCH_CHIP_ICON_DRAWABLE_ID), |
| equalTo(R.drawable.ic_search)); |
| } |
| |
| // Mock that user has switched to google as default search engine. |
| doReturn(true).when(mTemplateUrlService).isDefaultSearchEngineGoogle(); |
| mTemplateUrlServiceObserver.getValue().onTemplateURLServiceChanged(); |
| |
| // The search chip icon should be updated as R.drawable.ic_logo_googleg_24dp. |
| for (int i = 0; i < mModel.size(); i++) { |
| assertThat(mModel.get(i).model.get(TabProperties.SEARCH_CHIP_ICON_DRAWABLE_ID), |
| equalTo(R.drawable.ic_logo_googleg_24dp)); |
| } |
| } |
| |
| private void initAndAssertAllProperties() { |
| List<Tab> tabs = new ArrayList<>(); |
| for (int i = 0; i < mTabModel.getCount(); i++) { |
| tabs.add(mTabModel.getTabAt(i)); |
| } |
| mMediator.resetWithListOfTabs(tabs, false, false); |
| for (Callback<Drawable> callback : mCallbackCaptor.getAllValues()) { |
| callback.onResult(new ColorDrawable(Color.RED)); |
| } |
| |
| 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(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE)); |
| assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE)); |
| |
| assertThat(mModel.get(0).model.get(TabProperties.FAVICON), instanceOf(Drawable.class)); |
| assertThat(mModel.get(1).model.get(TabProperties.FAVICON), instanceOf(Drawable.class)); |
| |
| 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(0).model.get(TabProperties.THUMBNAIL_FETCHER), |
| instanceOf(TabListMediator.ThumbnailFetcher.class)); |
| assertThat(mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER), |
| instanceOf(TabListMediator.ThumbnailFetcher.class)); |
| |
| 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_CLOSED_LISTENER), |
| instanceOf(TabListMediator.TabActionListener.class)); |
| assertThat(mModel.get(1).model.get(TabProperties.TAB_CLOSED_LISTENER), |
| instanceOf(TabListMediator.TabActionListener.class)); |
| } |
| |
| private TabImpl prepareTab(int id, String title, String url) { |
| TabImpl tab = mock(TabImpl.class); |
| when(tab.getView()).thenReturn(mock(View.class)); |
| when(tab.getUserDataHost()).thenReturn(new UserDataHost()); |
| doReturn(id).when(tab).getId(); |
| doReturn(id).when(tab).getRootId(); |
| doReturn(url).when(tab).getUrlString(); |
| doReturn(title).when(tab).getTitle(); |
| doReturn(true).when(tab).isIncognito(); |
| doReturn(title).when(mTitleProvider).getTitle(tab); |
| return tab; |
| } |
| |
| private SimpleRecyclerViewAdapter.ViewHolder prepareViewHolder(int id, int position) { |
| SimpleRecyclerViewAdapter.ViewHolder viewHolder = |
| mock(SimpleRecyclerViewAdapter.ViewHolder.class); |
| PropertyModel model = new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID) |
| .with(TabProperties.TAB_ID, id) |
| .with(CARD_TYPE, TAB) |
| .build(); |
| viewHolder.model = model; |
| doReturn(position).when(viewHolder).getAdapterPosition(); |
| return viewHolder; |
| } |
| |
| private RecyclerView.ViewHolder prepareDummyViewHolder(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, mProfile); |
| } |
| |
| private void setUpForTabGroupOperation(@TabListMediatorType int type) { |
| doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter(); |
| doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getTabModelFilter(true); |
| doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getTabModelFilter(false); |
| doNothing() |
| .when(mTabGroupModelFilter) |
| .addTabGroupObserver(mTabGroupModelFilterObserverCaptor.capture()); |
| doNothing() |
| .when(mTabModelFilterProvider) |
| .addTabModelFilterObserver(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.UiType.CLOSABLE; |
| } else if (type == TabListMediatorType.TAB_STRIP) { |
| uiType = TabProperties.UiType.STRIP; |
| } |
| // TODO(crbug.com/1058196): avoid re-instanciate TabListMediator by using annotation. |
| CachedFeatureFlags.setForTesting(TAB_GROUPS_ANDROID, true); |
| |
| mMediator = new TabListMediator(mContext, mModel, mTabModelSelector, |
| mTabContentManager::getTabThumbnailWithCallback, mTitleProvider, |
| mTabListFaviconProvider, actionOnRelatedTabs, null, null, handler, |
| getClass().getSimpleName(), uiType); |
| mMediator.initWithNative(mProfile); |
| |
| // There are two TabModelObserver and two TabGroupModelFilter.Observer added when |
| // initializing TabListMediator, one set from TabListMediator and the other from |
| // TabGroupTitleEditor. Here we only test the ones from TabListMediator. |
| mMediatorTabModelObserver = mTabModelObserverCaptor.getAllValues().get(1); |
| mMediatorTabGroupModelFilterObserver = |
| mTabGroupModelFilterObserverCaptor.getAllValues().get(0); |
| |
| initAndAssertAllProperties(); |
| } |
| |
| private void createTabGroup(List<Tab> tabs, int rootId) { |
| for (Tab tab : tabs) { |
| when(mTabGroupModelFilter.getRelatedTabList(tab.getId())).thenReturn(tabs); |
| doReturn(rootId).when((TabImpl) tab).getRootId(); |
| } |
| } |
| } |