blob: 1af7a4196483208a4bdacadcb4d33e5d8e0017c6 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.tasks.tab_management;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.chromium.base.GarbageCollectionTestUtils.canBeGarbageCollected;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.test.filters.MediumTest;
import com.google.protobuf.ByteString;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.chromium.base.Callback;
import org.chromium.base.FeatureList;
import org.chromium.base.test.UiThreadTest;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge.OptimizationGuideCallback;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeJni;
import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.MockTab;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.state.CriticalPersistedTabData;
import org.chromium.chrome.browser.tab.state.LevelDBPersistedDataStorage;
import org.chromium.chrome.browser.tab.state.LevelDBPersistedDataStorageJni;
import org.chromium.chrome.browser.tab.state.PersistedTabDataConfiguration;
import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData;
import org.chromium.chrome.tab_ui.R;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.browser_ui.widget.chips.ChipView;
import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
import org.chromium.components.commerce.PriceTracking.BuyableProduct;
import org.chromium.components.commerce.PriceTracking.PriceTrackingData;
import org.chromium.components.commerce.PriceTracking.ProductPrice;
import org.chromium.components.commerce.PriceTracking.ProductPriceUpdate;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.CommonTypesProto.Any;
import org.chromium.components.payments.CurrencyFormatter;
import org.chromium.components.payments.CurrencyFormatterJni;
import org.chromium.content_public.browser.BrowserContextHandle;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.test.util.BlankUiTestActivityTestCase;
import org.chromium.ui.widget.ButtonCompat;
import org.chromium.ui.widget.ChromeImageView;
import org.chromium.url.GURL;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests for the {@link androidx.recyclerview.widget.RecyclerView.ViewHolder} classes for {@link
* TabListCoordinator}.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
"enable-features=" + ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study",
"force-fieldtrials=Study/Group"})
public class TabListViewHolderTest extends BlankUiTestActivityTestCase {
@Rule
public JniMocker mMocker = new JniMocker();
private static final int TAB1_ID = 456;
private static final int TAB2_ID = 789;
private static final String EXPECTED_PRICE_STRING = "$287";
private static final String EXPECTED_PREVIOUS_PRICE_STRING = "$314";
private static final ProductPriceUpdate PRODUCT_PRICE_UPDATE =
ProductPriceUpdate.newBuilder()
.setOldPrice(createProductPrice(10_000_000L, "USD"))
.setNewPrice(createProductPrice(5_000_000L, "USD"))
.build();
private static final BuyableProduct BUYABLE_PRODUCT =
BuyableProduct.newBuilder()
.setCurrentPrice(createProductPrice(5_000_000L, "USD"))
.build();
private static final PriceTrackingData PRICE_TRACKING_DATA =
PriceTrackingData.newBuilder()
.setBuyableProduct(BUYABLE_PRODUCT)
.setProductUpdate(PRODUCT_PRICE_UPDATE)
.build();
private static final Any ANY_PRICE_TRACKING_DATA =
Any.newBuilder()
.setValue(ByteString.copyFrom(PRICE_TRACKING_DATA.toByteArray()))
.build();
private static ProductPrice createProductPrice(long amountMicros, String currencyCode) {
return ProductPrice.newBuilder()
.setCurrencyCode(currencyCode)
.setAmountMicros(amountMicros)
.build();
}
private static final String USD_CURRENCY_SYMBOL = "$";
private static final String EXPECTED_PRICE = "$5.00";
private static final String EXPECTED_PREVIOUS_PRICE = "$10";
private static final String EXPECTED_CONTENT_DESCRIPTION =
"The price of this item recently dropped from $10 to $5.00";
private static final GURL TEST_GURL = new GURL("https://www.google.com");
private ViewGroup mTabGridView;
private PropertyModel mGridModel;
private PropertyModelChangeProcessor mGridMCP;
private ViewGroup mTabStripView;
private PropertyModel mStripModel;
private PropertyModelChangeProcessor mStripMCP;
private ViewGroup mSelectableTabGridView;
private PropertyModel mSelectableModel;
private PropertyModelChangeProcessor mSelectableMCP;
private ViewGroup mTabListView;
private ViewGroup mSelectableTabListView;
@Mock
private Profile mProfile;
@Mock
private LevelDBPersistedDataStorage.Natives mLevelDBPersistedTabDataStorage;
@Mock
private UrlUtilities.Natives mUrlUtilitiesJniMock;
@Mock
private CurrencyFormatter.Natives mCurrencyFormatterJniMock;
@Mock
private OptimizationGuideBridge.Natives mOptimizationGuideBridgeJniMock;
private TabListMediator.ThumbnailFetcher mMockThumbnailProvider =
new TabListMediator.ThumbnailFetcher(new TabListMediator.ThumbnailProvider() {
@Override
public void getTabThumbnailWithCallback(int tabId, Callback<Bitmap> callback,
boolean forceUpdate, boolean writeToCache) {
Bitmap bitmap = mShouldReturnBitmap
? Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
: null;
callback.onResult(bitmap);
mThumbnailFetchedCount.incrementAndGet();
}
}, Tab.INVALID_TAB_ID, false, false);
private AtomicInteger mThumbnailFetchedCount = new AtomicInteger();
private TabListMediator.TabActionListener mMockCloseListener =
new TabListMediator.TabActionListener() {
@Override
public void run(int tabId) {
mCloseClicked.set(true);
mCloseTabId.set(tabId);
}
};
private AtomicBoolean mCloseClicked = new AtomicBoolean();
private AtomicInteger mCloseTabId = new AtomicInteger();
private TabListMediator.TabActionListener mMockSelectedListener =
new TabListMediator.TabActionListener() {
@Override
public void run(int tabId) {
mSelectClicked.set(true);
mSelectTabId.set(tabId);
}
};
private AtomicBoolean mSelectClicked = new AtomicBoolean();
private AtomicInteger mSelectTabId = new AtomicInteger();
private TabListMediator.TabActionListener mMockCreateGroupButtonListener =
new TabListMediator.TabActionListener() {
@Override
public void run(int tabId) {
mCreateGroupButtonClicked.set(true);
mCreateGroupTabId.set(tabId);
}
};
private AtomicBoolean mCreateGroupButtonClicked = new AtomicBoolean();
private AtomicInteger mCreateGroupTabId = new AtomicInteger();
private boolean mShouldReturnBitmap;
@Override
public void setUpTest() throws Exception {
super.setUpTest();
MockitoAnnotations.initMocks(this);
TabUiTestHelper.applyThemeOverlays(getActivity());
ViewGroup view = new LinearLayout(getActivity());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
TestThreadUtils.runOnUiThreadBlocking(() -> {
getActivity().setContentView(view, params);
mTabGridView = (ViewGroup) getActivity().getLayoutInflater().inflate(
R.layout.closable_tab_grid_card_item, null);
mTabStripView = (ViewGroup) getActivity().getLayoutInflater().inflate(
R.layout.tab_strip_item, null);
mSelectableTabGridView = (ViewGroup) getActivity().getLayoutInflater().inflate(
R.layout.selectable_tab_grid_card_item, null);
mSelectableTabListView = (ViewGroup) getActivity().getLayoutInflater().inflate(
R.layout.selectable_tab_list_card_item, null);
mTabListView = (ViewGroup) getActivity().getLayoutInflater().inflate(
R.layout.closable_tab_list_card_item, null);
view.addView(mTabGridView);
view.addView(mTabStripView);
view.addView(mSelectableTabGridView);
view.addView(mSelectableTabListView);
view.addView(mTabListView);
});
int mSelectedTabBackgroundDrawableId = R.drawable.selected_tab_background;
TestThreadUtils.runOnUiThreadBlocking(() -> {
mGridModel = new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
.with(TabProperties.TAB_ID, TAB1_ID)
.with(TabProperties.TAB_SELECTED_LISTENER, mMockSelectedListener)
.with(TabProperties.TAB_CLOSED_LISTENER, mMockCloseListener)
.with(TabProperties.SELECTED_TAB_BACKGROUND_DRAWABLE_ID,
mSelectedTabBackgroundDrawableId)
.build();
mStripModel = new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_STRIP)
.with(TabProperties.TAB_SELECTED_LISTENER, mMockSelectedListener)
.with(TabProperties.TAB_CLOSED_LISTENER, mMockCloseListener)
.build();
mSelectableModel =
new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
.with(TabProperties.SELECTABLE_TAB_CLICKED_LISTENER,
mMockSelectedListener)
.with(TabProperties.TAB_SELECTION_DELEGATE, new SelectionDelegate<>())
.with(TabProperties.SELECTED_TAB_BACKGROUND_DRAWABLE_ID,
mSelectedTabBackgroundDrawableId)
.build();
mGridMCP = PropertyModelChangeProcessor.create(
mGridModel, mTabGridView, TabGridViewBinder::bindClosableTab);
mStripMCP = PropertyModelChangeProcessor.create(
mStripModel, mTabStripView, TabStripViewBinder::bind);
mSelectableMCP = PropertyModelChangeProcessor.create(
mSelectableModel, mSelectableTabGridView, TabGridViewBinder::bindSelectableTab);
PropertyModelChangeProcessor.create(mSelectableModel, mSelectableTabListView,
TabListViewBinder::bindSelectableListTab);
PropertyModelChangeProcessor.create(
mGridModel, mTabListView, TabListViewBinder::bindListTab);
});
mMocker.mock(LevelDBPersistedDataStorageJni.TEST_HOOKS, mLevelDBPersistedTabDataStorage);
doNothing()
.when(mLevelDBPersistedTabDataStorage)
.init(any(LevelDBPersistedDataStorage.class), any(BrowserContextHandle.class));
doReturn(false).when(mProfile).isOffTheRecord();
LevelDBPersistedDataStorage.setSkipNativeAssertionsForTesting(true);
Profile.setLastUsedProfileForTesting(mProfile);
mMocker.mock(UrlUtilitiesJni.TEST_HOOKS, mUrlUtilitiesJniMock);
mMocker.mock(CurrencyFormatterJni.TEST_HOOKS, mCurrencyFormatterJniMock);
doReturn(1L)
.when(mCurrencyFormatterJniMock)
.initCurrencyFormatterAndroid(
any(CurrencyFormatter.class), anyString(), anyString());
doNothing().when(mCurrencyFormatterJniMock).setMaxFractionalDigits(anyLong(), anyInt());
doReturn(1L).when(mOptimizationGuideBridgeJniMock).init();
mMocker.mock(OptimizationGuideBridgeJni.TEST_HOOKS, mOptimizationGuideBridgeJniMock);
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
}
private void testGridSelected(ViewGroup holder, PropertyModel model) {
model.set(TabProperties.IS_SELECTED, true);
Assert.assertTrue(TabUiTestHelper.isTabViewSelected(holder));
model.set(TabProperties.IS_SELECTED, false);
Assert.assertFalse(TabUiTestHelper.isTabViewSelected(holder));
}
private void tabListSelected(ViewGroup holder, PropertyModel model) {
model.set(TabProperties.IS_SELECTED, true);
Assert.assertNotNull(holder.getForeground());
model.set(TabProperties.IS_SELECTED, false);
Assert.assertNull(holder.getForeground());
}
private void testSelectableTabClickToSelect(
ViewGroup view, PropertyModel model, boolean isLongClick) {
Runnable clickTask = () -> {
if (isLongClick) {
view.performLongClick();
} else {
view.performClick();
}
};
model.set(TabProperties.IS_SELECTED, false);
clickTask.run();
Assert.assertTrue(mSelectClicked.get());
mSelectClicked.set(false);
model.set(TabProperties.IS_SELECTED, true);
clickTask.run();
Assert.assertTrue(mSelectClicked.get());
}
@Test
@MediumTest
@UiThreadTest
public void testSelected() {
testGridSelected(mTabGridView, mGridModel);
mStripModel.set(TabProperties.IS_SELECTED, true);
Assert.assertNotNull(((FrameLayout) mTabStripView).getForeground());
mStripModel.set(TabProperties.IS_SELECTED, false);
Assert.assertNull(((FrameLayout) mTabStripView).getForeground());
testGridSelected(mSelectableTabGridView, mSelectableModel);
mSelectableModel.set(TabProperties.IS_SELECTED, true);
ImageView actionButton = mSelectableTabGridView.findViewById(R.id.action_button);
Assert.assertEquals(1, actionButton.getBackground().getLevel());
Assert.assertNotNull(actionButton.getDrawable());
Assert.assertEquals(255, actionButton.getDrawable().getAlpha());
mSelectableModel.set(TabProperties.IS_SELECTED, false);
Assert.assertEquals(0, actionButton.getBackground().getLevel());
Assert.assertEquals(0, actionButton.getDrawable().getAlpha());
tabListSelected(mSelectableTabListView, mSelectableModel);
mSelectableModel.set(TabProperties.IS_SELECTED, true);
ImageView endButton = mSelectableTabListView.findViewById(R.id.end_button);
Assert.assertEquals(1, endButton.getBackground().getLevel());
Assert.assertNotNull(endButton.getDrawable());
Assert.assertEquals(255, actionButton.getDrawable().getAlpha());
mSelectableModel.set(TabProperties.IS_SELECTED, false);
Assert.assertEquals(0, endButton.getBackground().getLevel());
Assert.assertEquals(0, endButton.getDrawable().getAlpha());
}
@Test
@MediumTest
public void testAnimationRestored() {
View backgroundView = mTabGridView.findViewById(R.id.background_view);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mGridModel.set(TabProperties.IS_SELECTED, true);
mGridModel.set(TabProperties.CARD_ANIMATION_STATUS,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
});
CriteriaHelper.pollUiThread(
() -> !((ClosableTabGridView) mTabGridView).getIsAnimatingForTesting());
Assert.assertEquals(View.GONE, backgroundView.getVisibility());
Assert.assertTrue(TabUiTestHelper.isTabViewSelected(mTabGridView));
TestThreadUtils.runOnUiThreadBlocking(() -> {
mGridModel.set(TabProperties.IS_SELECTED, false);
mGridModel.set(TabProperties.CARD_ANIMATION_STATUS,
ClosableTabGridView.AnimationStatus.CARD_RESTORE);
});
CriteriaHelper.pollUiThread(
() -> !((ClosableTabGridView) mTabGridView).getIsAnimatingForTesting());
Assert.assertEquals(View.GONE, backgroundView.getVisibility());
Assert.assertFalse(TabUiTestHelper.isTabViewSelected(mTabGridView));
}
@Test
@MediumTest
@UiThreadTest
public void testTitle() {
final String title = "Surf the cool webz";
mGridModel.set(TabProperties.TITLE, title);
TextView textView = mTabGridView.findViewById(R.id.tab_title);
Assert.assertEquals(textView.getText(), title);
mSelectableModel.set(TabProperties.TITLE, title);
textView = mSelectableTabGridView.findViewById(R.id.tab_title);
Assert.assertEquals(textView.getText(), title);
textView = mSelectableTabListView.findViewById(R.id.title);
Assert.assertEquals(textView.getText(), title);
}
@Test
@MediumTest
@UiThreadTest
public void testThumbnail() {
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
ImageView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
assertThat("Thumbnail should be set to place holder drawable.", thumbnail.getDrawable(),
instanceOf(ColorDrawable.class));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, null);
Assert.assertNull("Thumbnail should be release when thumbnail fetcher is set to null.",
thumbnail.getDrawable());
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Assert.assertEquals(2, mThumbnailFetchedCount.get());
}
@SuppressWarnings("UnusedAssignment") // Intentionally set to null for garbage collection.
@Test
@MediumTest
@UiThreadTest
public void testThumbnailGCAfterNullBitmap() {
ImageView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Bitmap bitmap = ((BitmapDrawable) thumbnail.getDrawable()).getBitmap();
WeakReference<Bitmap> ref = new WeakReference<>(bitmap);
bitmap = null;
Assert.assertFalse(canBeGarbageCollected(ref));
mShouldReturnBitmap = false;
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
Assert.assertTrue(canBeGarbageCollected(ref));
Assert.assertEquals(2, mThumbnailFetchedCount.get());
}
@SuppressWarnings("UnusedAssignment") // Intentionally set to null for garbage collection.
@Test
@MediumTest
@UiThreadTest
public void testThumbnailGCAfterNewBitmap() {
ImageView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Bitmap bitmap = ((BitmapDrawable) thumbnail.getDrawable()).getBitmap();
WeakReference<Bitmap> ref = new WeakReference<>(bitmap);
bitmap = null;
Assert.assertFalse(canBeGarbageCollected(ref));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
Assert.assertTrue(canBeGarbageCollected(ref));
Assert.assertEquals(2, mThumbnailFetchedCount.get());
}
@SuppressWarnings("UnusedAssignment") // Intentionally set to null for garbage collection.
@Test
@MediumTest
@UiThreadTest
public void testResetThumbnailGC() {
ImageView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Bitmap bitmap = ((BitmapDrawable) thumbnail.getDrawable()).getBitmap();
WeakReference<Bitmap> ref = new WeakReference<>(bitmap);
bitmap = null;
Assert.assertFalse(canBeGarbageCollected(ref));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, null);
Assert.assertTrue(canBeGarbageCollected(ref));
}
@SuppressWarnings("UnusedAssignment") // Intentionally set to null for garbage collection.
@Test
@MediumTest
@UiThreadTest
public void testHiddenGC() {
ImageView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Bitmap bitmap = ((BitmapDrawable) thumbnail.getDrawable()).getBitmap();
WeakReference<Bitmap> ref = new WeakReference<>(bitmap);
bitmap = null;
Assert.assertFalse(canBeGarbageCollected(ref));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, null);
Assert.assertTrue(canBeGarbageCollected(ref));
Assert.assertNull("Thumbnail should be release when thumbnail fetcher is set to null.",
thumbnail.getDrawable());
Assert.assertEquals(1, mThumbnailFetchedCount.get());
}
@Test
@MediumTest
@UiThreadTest
public void testHiddenThenShow() {
ImageView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Assert.assertEquals(1, mThumbnailFetchedCount.get());
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, null);
Assert.assertNull("Thumbnail should be release when thumbnail fetcher is set to null.",
thumbnail.getDrawable());
Assert.assertEquals(1, mThumbnailFetchedCount.get());
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Assert.assertEquals(2, mThumbnailFetchedCount.get());
}
@Test
@MediumTest
@UiThreadTest
public void testClickToSelect() {
Assert.assertFalse(mSelectClicked.get());
mTabGridView.performClick();
Assert.assertTrue(mSelectClicked.get());
int firstSelectId = mSelectTabId.get();
Assert.assertEquals(TAB1_ID, firstSelectId);
mSelectClicked.set(false);
mSelectTabId.set(Tab.INVALID_TAB_ID);
Assert.assertFalse(mSelectClicked.get());
mTabListView.performClick();
Assert.assertTrue(mSelectClicked.get());
firstSelectId = mSelectTabId.get();
Assert.assertEquals(TAB1_ID, firstSelectId);
mSelectClicked.set(false);
mGridModel.set(TabProperties.TAB_ID, TAB2_ID);
mTabGridView.performClick();
Assert.assertTrue(mSelectClicked.get());
int secondSelectId = mSelectTabId.get();
// When TAB_ID in PropertyModel is updated, binder should select tab with updated tab ID.
Assert.assertEquals(TAB2_ID, secondSelectId);
Assert.assertNotEquals(firstSelectId, secondSelectId);
mSelectClicked.set(false);
mSelectTabId.set(Tab.INVALID_TAB_ID);
Assert.assertFalse(mSelectClicked.get());
mTabListView.performClick();
Assert.assertTrue(mSelectClicked.get());
secondSelectId = mSelectTabId.get();
Assert.assertEquals(TAB2_ID, secondSelectId);
Assert.assertNotEquals(firstSelectId, secondSelectId);
mSelectClicked.set(false);
mGridModel.set(TabProperties.TAB_SELECTED_LISTENER, null);
mTabGridView.performClick();
Assert.assertFalse(mSelectClicked.get());
mTabListView.performClick();
Assert.assertFalse(mSelectClicked.get());
mSelectClicked.set(false);
ImageButton button = mTabStripView.findViewById(R.id.tab_strip_item_button);
mStripModel.set(TabProperties.IS_SELECTED, false);
button.performClick();
Assert.assertTrue(mSelectClicked.get());
mSelectClicked.set(false);
mStripModel.set(TabProperties.IS_SELECTED, true);
button.performClick();
Assert.assertFalse(mSelectClicked.get());
mSelectClicked.set(false);
testSelectableTabClickToSelect(mSelectableTabGridView, mSelectableModel, false);
testSelectableTabClickToSelect(mSelectableTabGridView, mSelectableModel, true);
// For the List version, we need to trigger the click from the view that has id
// content_view, because of the xml hierarchy.
ViewGroup selectableTabListContent = mSelectableTabListView.findViewById(R.id.content_view);
testSelectableTabClickToSelect(selectableTabListContent, mSelectableModel, false);
testSelectableTabClickToSelect(selectableTabListContent, mSelectableModel, true);
}
@Test
@MediumTest
@UiThreadTest
public void testClickToClose() {
ImageView gridActionButton = mTabGridView.findViewById(R.id.action_button);
ImageView listActionButton = mTabListView.findViewById(R.id.end_button);
ImageButton button = mTabStripView.findViewById(R.id.tab_strip_item_button);
Assert.assertFalse(mCloseClicked.get());
gridActionButton.performClick();
Assert.assertTrue(mCloseClicked.get());
int firstCloseId = mCloseTabId.get();
Assert.assertEquals(TAB1_ID, firstCloseId);
mCloseClicked.set(false);
mCloseTabId.set(Tab.INVALID_TAB_ID);
Assert.assertFalse(mCloseClicked.get());
listActionButton.performClick();
Assert.assertTrue(mCloseClicked.get());
firstCloseId = mCloseTabId.get();
Assert.assertEquals(TAB1_ID, firstCloseId);
mCloseClicked.set(false);
mGridModel.set(TabProperties.TAB_ID, TAB2_ID);
gridActionButton.performClick();
Assert.assertTrue(mCloseClicked.get());
mCloseClicked.set(false);
int secondClosed = mCloseTabId.get();
mCloseClicked.set(false);
mCloseTabId.set(Tab.INVALID_TAB_ID);
Assert.assertFalse(mCloseClicked.get());
listActionButton.performClick();
Assert.assertTrue(mCloseClicked.get());
secondClosed = mCloseTabId.get();
// When TAB_ID in PropertyModel is updated, binder should close tab with updated tab ID.
Assert.assertEquals(TAB2_ID, secondClosed);
Assert.assertNotEquals(firstCloseId, secondClosed);
mCloseClicked.set(false);
mGridModel.set(TabProperties.TAB_CLOSED_LISTENER, null);
gridActionButton.performClick();
Assert.assertFalse(mCloseClicked.get());
listActionButton.performClick();
Assert.assertFalse(mCloseClicked.get());
mStripModel.set(TabProperties.IS_SELECTED, true);
button.performClick();
Assert.assertTrue(mCloseClicked.get());
mCloseClicked.set(false);
mStripModel.set(TabProperties.IS_SELECTED, false);
button.performClick();
Assert.assertFalse(mCloseClicked.get());
}
@Test
@MediumTest
@UiThreadTest
public void testSetCreateGroupListener() {
ButtonCompat actionButton = mTabGridView.findViewById(R.id.create_group_button);
// By default, the create group button is invisible.
Assert.assertEquals(View.GONE, actionButton.getVisibility());
// When setup with actual listener, the button should be visible.
mGridModel.set(TabProperties.CREATE_GROUP_LISTENER, mMockCreateGroupButtonListener);
Assert.assertEquals(View.VISIBLE, actionButton.getVisibility());
Assert.assertFalse(mCreateGroupButtonClicked.get());
actionButton.performClick();
Assert.assertTrue(mCreateGroupButtonClicked.get());
mCreateGroupButtonClicked.set(false);
int firstCreateGroupId = mCreateGroupTabId.get();
Assert.assertEquals(TAB1_ID, firstCreateGroupId);
mGridModel.set(TabProperties.TAB_ID, TAB2_ID);
actionButton.performClick();
Assert.assertTrue(mCreateGroupButtonClicked.get());
mCreateGroupButtonClicked.set(false);
int secondCreateGroupId = mCreateGroupTabId.get();
// When TAB_ID in PropertyModel is updated, binder should create group with updated tab ID.
Assert.assertEquals(TAB2_ID, secondCreateGroupId);
Assert.assertNotEquals(firstCreateGroupId, secondCreateGroupId);
mGridModel.set(TabProperties.CREATE_GROUP_LISTENER, null);
actionButton.performClick();
Assert.assertFalse(mCreateGroupButtonClicked.get());
// When CREATE_GROUP_LISTENER is set to null, the button should be invisible.
Assert.assertEquals(View.GONE, actionButton.getVisibility());
}
@Test
@MediumTest
@UiThreadTest
public void testSearchTermChip() {
String searchTerm = "hello world";
testGridSelected(mTabGridView, mGridModel);
ChipView pageInfoButton = mTabGridView.findViewById(R.id.page_info_button);
mGridModel.set(TabProperties.SEARCH_QUERY, searchTerm);
Assert.assertEquals(View.VISIBLE, pageInfoButton.getVisibility());
Assert.assertEquals(searchTerm, pageInfoButton.getPrimaryTextView().getText());
mGridModel.set(TabProperties.SEARCH_QUERY, null);
Assert.assertEquals(View.GONE, pageInfoButton.getVisibility());
mGridModel.set(TabProperties.SEARCH_QUERY, searchTerm);
Assert.assertEquals(View.VISIBLE, pageInfoButton.getVisibility());
mGridModel.set(TabProperties.SEARCH_QUERY, null);
Assert.assertEquals(View.GONE, pageInfoButton.getVisibility());
}
@Test
@MediumTest
@UiThreadTest
public void testPriceStringPriceDrop() {
Tab tab = MockTab.createAndInitialize(1, false);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setPriceStrings(EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
testPriceString(
tab, fetcher, View.VISIBLE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
}
@Test
@MediumTest
@UiThreadTest
public void testPriceStringNullPriceDrop() {
Tab tab = MockTab.createAndInitialize(1, false);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setNullPriceDrop();
testPriceString(
tab, fetcher, View.GONE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
}
@Test
@MediumTest
@UiThreadTest
public void testPriceStringPriceDropThenNull() {
Tab tab = MockTab.createAndInitialize(1, false);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setPriceStrings(EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
testPriceString(
tab, fetcher, View.VISIBLE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
fetcher.setNullPriceDrop();
testPriceString(
tab, fetcher, View.GONE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
}
@Test
@MediumTest
@UiThreadTest
public void testPriceStringTurnFeatureOff() {
Tab tab = MockTab.createAndInitialize(1, false);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setPriceStrings(EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
testPriceString(
tab, fetcher, View.VISIBLE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
mGridModel.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, null);
PriceCardView priceCardView = mTabGridView.findViewById(R.id.price_info_box_outer);
Assert.assertEquals(View.GONE, priceCardView.getVisibility());
}
private void testPriceString(Tab tab, MockShoppingPersistedTabDataFetcher fetcher,
int expectedVisibility, String expectedCurrentPrice, String expectedPreviousPrice) {
setPriceTrackingEnabledForTesting(true);
testGridSelected(mTabGridView, mGridModel);
PriceCardView priceCardView = mTabGridView.findViewById(R.id.price_info_box_outer);
TextView currentPrice = mTabGridView.findViewById(R.id.current_price);
TextView previousPrice = mTabGridView.findViewById(R.id.previous_price);
mGridModel.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, fetcher);
Assert.assertEquals(expectedVisibility, priceCardView.getVisibility());
if (expectedVisibility == View.VISIBLE) {
Assert.assertEquals(expectedCurrentPrice, currentPrice.getText());
Assert.assertEquals(expectedPreviousPrice, previousPrice.getText());
}
}
static class MockShoppingPersistedTabData extends ShoppingPersistedTabData {
private PriceDrop mPriceDrop;
MockShoppingPersistedTabData(Tab tab) {
super(tab);
}
public void setPriceStrings(String priceString, String previousPriceString) {
mPriceDrop = new PriceDrop(priceString, previousPriceString);
}
@Override
public PriceDrop getPriceDrop() {
return mPriceDrop;
}
}
/**
* Mock {@link TabListMediator.ShoppingPersistedTabDataFetcher} for testing purposes
*/
static class MockShoppingPersistedTabDataFetcher
extends TabListMediator.ShoppingPersistedTabDataFetcher {
private ShoppingPersistedTabData mShoppingPersistedTabData;
MockShoppingPersistedTabDataFetcher(Tab tab) {
super(tab, null);
}
public void setPriceStrings(String priceString, String previousPriceString) {
mShoppingPersistedTabData = new MockShoppingPersistedTabData(mTab);
((MockShoppingPersistedTabData) mShoppingPersistedTabData)
.setPriceStrings(priceString, previousPriceString);
}
public void setNullPriceDrop() {
mShoppingPersistedTabData = new MockShoppingPersistedTabData(mTab);
}
@Override
public void fetch(Callback<ShoppingPersistedTabData> callback) {
callback.onResult(mShoppingPersistedTabData);
}
}
@Test
@MediumTest
@UiThreadTest
public void testSearchListener() {
ChipView pageInfoButton = mTabGridView.findViewById(R.id.page_info_button);
AtomicInteger clickedTabId = new AtomicInteger(Tab.INVALID_TAB_ID);
TabListMediator.TabActionListener searchListener = clickedTabId::set;
mGridModel.set(TabProperties.PAGE_INFO_LISTENER, searchListener);
pageInfoButton.performClick();
Assert.assertEquals(TAB1_ID, clickedTabId.get());
clickedTabId.set(Tab.INVALID_TAB_ID);
mGridModel.set(TabProperties.PAGE_INFO_LISTENER, null);
pageInfoButton.performClick();
Assert.assertEquals(Tab.INVALID_TAB_ID, clickedTabId.get());
}
@Test
@MediumTest
@UiThreadTest
public void testSearchChipIcon() {
ChipView pageInfoButton = mTabGridView.findViewById(R.id.page_info_button);
View iconView = pageInfoButton.getChildAt(0);
Assert.assertTrue(iconView instanceof ChromeImageView);
ChromeImageView iconImageView = (ChromeImageView) iconView;
mGridModel.set(TabProperties.PAGE_INFO_ICON_DRAWABLE_ID, R.drawable.ic_logo_googleg_24dp);
Drawable googleDrawable = iconImageView.getDrawable();
mGridModel.set(TabProperties.PAGE_INFO_ICON_DRAWABLE_ID, R.drawable.ic_search);
Drawable magnifierDrawable = iconImageView.getDrawable();
Assert.assertNotEquals(magnifierDrawable, googleDrawable);
}
@Test
@MediumTest
public void testPriceDropEndToEnd() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
ShoppingPersistedTabData.onDeferredStartup();
ShoppingPersistedTabData.enablePriceTrackingWithOptimizationGuideForTesting();
PersistedTabDataConfiguration.setUseTestConfig(true);
setPriceTrackingEnabledForTesting(true);
mockCurrencyFormatter();
mockUrlUtilities();
mockOptimizationGuideResponse(OptimizationGuideDecision.TRUE, ANY_PRICE_TRACKING_DATA);
MockTab tab = (MockTab) MockTab.createAndInitialize(1, false);
tab.setGurlOverrideForTesting(TEST_GURL);
tab.setIsInitialized(true);
CriticalPersistedTabData.from(tab).setTimestampMillis(System.currentTimeMillis());
TabListMediator.ShoppingPersistedTabDataFetcher fetcher =
new TabListMediator.ShoppingPersistedTabDataFetcher(tab, null);
mGridModel.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, fetcher);
testGridSelected(mTabGridView, mGridModel);
});
CriteriaHelper.pollUiThread(
()
-> EXPECTED_PRICE.equals(
((TextView) mTabGridView.findViewById(R.id.current_price))
.getText()));
CriteriaHelper.pollUiThread(
()
-> EXPECTED_PREVIOUS_PRICE.equals(
((TextView) mTabGridView.findViewById(R.id.previous_price))
.getText()));
CriteriaHelper.pollUiThread(()
-> EXPECTED_CONTENT_DESCRIPTION.equals(
((PriceCardView) mTabGridView.findViewById(
R.id.price_info_box_outer))
.getContentDescription()));
}
private void mockCurrencyFormatter() {
doAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) {
StringBuilder sb = new StringBuilder();
sb.append(USD_CURRENCY_SYMBOL);
sb.append(invocation.getArguments()[2]);
return sb.toString();
}
})
.when(mCurrencyFormatterJniMock)
.format(anyLong(), any(CurrencyFormatter.class), anyString());
}
private void mockUrlUtilities() {
doAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) {
return (String) invocation.getArguments()[0];
}
})
.when(mUrlUtilitiesJniMock)
.escapeQueryParamValue(anyString(), anyBoolean());
}
private void mockOptimizationGuideResponse(
@OptimizationGuideDecision int decision, Any metadata) {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
OptimizationGuideCallback callback =
(OptimizationGuideCallback) invocation.getArguments()[3];
callback.onOptimizationGuideDecision(decision, metadata);
return null;
}
})
.when(mOptimizationGuideBridgeJniMock)
.canApplyOptimization(
anyLong(), any(GURL.class), anyInt(), any(OptimizationGuideCallback.class));
}
@Override
public void tearDownTest() throws Exception {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mStripMCP.destroy();
mGridMCP.destroy();
mSelectableMCP.destroy();
});
super.tearDownTest();
}
private void setPriceTrackingEnabledForTesting(boolean value) {
FeatureList.TestValues testValues = new FeatureList.TestValues();
// Required by MockTab.
testValues.addFeatureFlagOverride(ChromeFeatureList.CONTINUOUS_SEARCH, true);
testValues.addFeatureFlagOverride(ChromeFeatureList.COMMERCE_PRICE_TRACKING, true);
testValues.addFieldTrialParamOverride(ChromeFeatureList.COMMERCE_PRICE_TRACKING,
PriceTrackingFeatures.PRICE_TRACKING_PARAM, String.valueOf(value));
FeatureList.setTestValues(testValues);
}
}