| // Copyright 2020 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.feed.v2; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.anyBoolean; |
| import static org.mockito.Mockito.anyInt; |
| import static org.mockito.Mockito.anyLong; |
| import static org.mockito.Mockito.eq; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.app.Activity; |
| import android.support.test.filters.SmallTest; |
| import android.view.View; |
| import android.widget.FrameLayout; |
| import android.widget.LinearLayout; |
| import android.widget.TextView; |
| |
| import androidx.recyclerview.widget.RecyclerView; |
| |
| import com.google.protobuf.ByteString; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TestRule; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.ArgumentMatchers; |
| import org.mockito.Captor; |
| import org.mockito.InOrder; |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.mockito.MockitoAnnotations; |
| import org.robolectric.Robolectric; |
| import org.robolectric.annotation.Config; |
| import org.robolectric.shadows.ShadowLog; |
| |
| import org.chromium.base.Callback; |
| import org.chromium.base.test.BaseRobolectricTestRunner; |
| import org.chromium.base.test.util.JniMocker; |
| import org.chromium.chrome.browser.AppHooks; |
| import org.chromium.chrome.browser.AppHooksImpl; |
| import org.chromium.chrome.browser.help.HelpAndFeedback; |
| import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.chrome.browser.tab.MockTab; |
| import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; |
| import org.chromium.chrome.browser.xsurface.FeedActionsHandler; |
| import org.chromium.chrome.browser.xsurface.ProcessScope; |
| import org.chromium.chrome.test.util.browser.Features; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; |
| import org.chromium.components.feed.proto.FeedUiProto.Slice; |
| import org.chromium.components.feed.proto.FeedUiProto.StreamUpdate; |
| import org.chromium.components.feed.proto.FeedUiProto.StreamUpdate.SliceUpdate; |
| import org.chromium.components.feed.proto.FeedUiProto.XSurfaceSlice; |
| import org.chromium.ui.mojom.WindowOpenDisposition; |
| |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** Unit tests for {@link FeedStreamSeSurface}. */ |
| @RunWith(BaseRobolectricTestRunner.class) |
| @Config(manifest = Config.NONE) |
| public class FeedStreamSurfaceTest { |
| private static final String TEST_DATA = "test"; |
| private static final String TEST_URL = "https://www.chromium.org"; |
| private static final int LOAD_MORE_TRIGGER_LOOKAHEAD = 5; |
| private FeedStreamSurface mFeedStreamSurface; |
| private Activity mActivity; |
| private RecyclerView mRecyclerView; |
| private LinearLayout mParent; |
| private FakeLinearLayoutManager mLayoutManager; |
| private FeedListContentManager mContentManager; |
| |
| @Mock |
| private SnackbarManager mSnackbarManager; |
| @Mock |
| private FeedActionsHandler.SnackbarController mSnackbarController; |
| @Mock |
| private BottomSheetController mBottomSheetController; |
| @Mock |
| private NativePageNavigationDelegate mPageNavigationDelegate; |
| @Mock |
| private HelpAndFeedback mHelpAndFeedback; |
| @Mock |
| Profile mProfileMock; |
| @Mock |
| private FeedServiceBridge.Natives mFeedServiceBridgeJniMock; |
| |
| @Captor |
| private ArgumentCaptor<Map<String, String>> mMapCaptor; |
| |
| @Rule |
| public JniMocker mocker = new JniMocker(); |
| // Enable the Features class, so we can call code which checks to see if features are enabled |
| // without crashing. |
| @Rule |
| public TestRule mFeaturesProcessorRule = new Features.JUnitProcessor(); |
| |
| @Mock |
| private FeedStreamSurface.Natives mFeedStreamSurfaceJniMock; |
| |
| @Mock |
| private AppHooksImpl mAppHooks; |
| @Mock |
| private ProcessScope mProcessScope; |
| |
| @Before |
| public void setUp() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| mActivity = Robolectric.buildActivity(Activity.class).get(); |
| mParent = new LinearLayout(mActivity); |
| mocker.mock(FeedStreamSurfaceJni.TEST_HOOKS, mFeedStreamSurfaceJniMock); |
| mocker.mock(FeedServiceBridgeJni.TEST_HOOKS, mFeedServiceBridgeJniMock); |
| |
| when(mFeedServiceBridgeJniMock.getLoadMoreTriggerLookahead()) |
| .thenReturn(LOAD_MORE_TRIGGER_LOOKAHEAD); |
| |
| when(mAppHooks.getExternalSurfaceProcessScope(any())).thenReturn(mProcessScope); |
| |
| AppHooks.setInstanceForTesting(mAppHooks); |
| |
| Profile.setLastUsedProfileForTesting(mProfileMock); |
| mFeedStreamSurface = Mockito.spy(new FeedStreamSurface(mActivity, false, mSnackbarManager, |
| mPageNavigationDelegate, mBottomSheetController, mHelpAndFeedback)); |
| mContentManager = mFeedStreamSurface.getFeedListContentManagerForTesting(); |
| mFeedStreamSurface.mRootView = Mockito.spy(mFeedStreamSurface.mRootView); |
| mRecyclerView = mFeedStreamSurface.mRootView; |
| mLayoutManager = new FakeLinearLayoutManager(mActivity); |
| mRecyclerView.setLayoutManager(mLayoutManager); |
| // Print logs to stdout. |
| ShadowLog.stream = System.out; |
| } |
| |
| @After |
| public void tearDown() { |
| FeedStreamSurface.shutdownForTesting(); |
| AppHooks.setInstanceForTesting(null); |
| } |
| |
| @Test |
| @SmallTest |
| public void testAddSlicesOnStreamUpdated() { |
| // Add 3 new slices at first. |
| StreamUpdate update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("c")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(3, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("a")); |
| assertEquals(1, mContentManager.findContentPositionByKey("b")); |
| assertEquals(2, mContentManager.findContentPositionByKey("c")); |
| |
| // Add 2 more slices. |
| update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("b")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("c")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("d")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("e")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(5, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("a")); |
| assertEquals(1, mContentManager.findContentPositionByKey("b")); |
| assertEquals(2, mContentManager.findContentPositionByKey("c")); |
| assertEquals(3, mContentManager.findContentPositionByKey("d")); |
| assertEquals(4, mContentManager.findContentPositionByKey("e")); |
| } |
| |
| @Test |
| @SmallTest |
| public void testAddNewSlicesWithSameIds() { |
| // Add 2 new slices at first. |
| StreamUpdate update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(2, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("a")); |
| assertEquals(1, mContentManager.findContentPositionByKey("b")); |
| |
| // Add 2 new slice with same ids as before. |
| update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(2, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("b")); |
| assertEquals(1, mContentManager.findContentPositionByKey("a")); |
| } |
| |
| @Test |
| @SmallTest |
| public void testRemoveSlicesOnStreamUpdated() { |
| // Add 3 new slices at first. |
| StreamUpdate update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("c")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(3, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("a")); |
| assertEquals(1, mContentManager.findContentPositionByKey("b")); |
| assertEquals(2, mContentManager.findContentPositionByKey("c")); |
| |
| // Remove 1 slice. |
| update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("c")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(2, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("a")); |
| assertEquals(1, mContentManager.findContentPositionByKey("c")); |
| |
| // Remove 2 slices. |
| update = StreamUpdate.newBuilder().build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(0, mContentManager.getItemCount()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testReorderSlicesOnStreamUpdated() { |
| // Add 3 new slices at first. |
| StreamUpdate update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("c")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(3, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("a")); |
| assertEquals(1, mContentManager.findContentPositionByKey("b")); |
| assertEquals(2, mContentManager.findContentPositionByKey("c")); |
| |
| // Reorder 1 slice. |
| update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("c")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("b")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(3, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("c")); |
| assertEquals(1, mContentManager.findContentPositionByKey("a")); |
| assertEquals(2, mContentManager.findContentPositionByKey("b")); |
| |
| // Reorder 2 slices. |
| update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("b")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("c")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(3, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("a")); |
| assertEquals(1, mContentManager.findContentPositionByKey("b")); |
| assertEquals(2, mContentManager.findContentPositionByKey("c")); |
| } |
| |
| @Test |
| @SmallTest |
| public void testComplexOperationsOnStreamUpdated() { |
| // Add 3 new slices at first. |
| StreamUpdate update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("c")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("d")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("e")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(5, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("a")); |
| assertEquals(1, mContentManager.findContentPositionByKey("b")); |
| assertEquals(2, mContentManager.findContentPositionByKey("c")); |
| assertEquals(3, mContentManager.findContentPositionByKey("d")); |
| assertEquals(4, mContentManager.findContentPositionByKey("e")); |
| |
| // Combo of add, remove and reorder operations. |
| update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("f")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("g")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("h")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("c")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("e")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("i")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(7, mContentManager.getItemCount()); |
| assertEquals(0, mContentManager.findContentPositionByKey("f")); |
| assertEquals(1, mContentManager.findContentPositionByKey("g")); |
| assertEquals(2, mContentManager.findContentPositionByKey("a")); |
| assertEquals(3, mContentManager.findContentPositionByKey("h")); |
| assertEquals(4, mContentManager.findContentPositionByKey("c")); |
| assertEquals(5, mContentManager.findContentPositionByKey("e")); |
| assertEquals(6, mContentManager.findContentPositionByKey("i")); |
| } |
| |
| @Test |
| @SmallTest |
| public void testAddHeaderViews() { |
| View v0 = new View(mActivity); |
| View v1 = new View(mActivity); |
| |
| mFeedStreamSurface.setHeaderViews(Arrays.asList(v0, v1)); |
| assertEquals(2, mContentManager.getItemCount()); |
| assertEquals(v0, getNativeView(0)); |
| assertEquals(v1, getNativeView(1)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testUpdateHeaderViews() { |
| View v0 = new View(mActivity); |
| View v1 = new View(mActivity); |
| |
| mFeedStreamSurface.setHeaderViews(Arrays.asList(v0, v1)); |
| assertEquals(2, mContentManager.getItemCount()); |
| assertEquals(v0, getNativeView(0)); |
| assertEquals(v1, getNativeView(1)); |
| |
| View v2 = new View(mActivity); |
| View v3 = new View(mActivity); |
| |
| mFeedStreamSurface.setHeaderViews(Arrays.asList(v2, v0, v3)); |
| assertEquals(3, mContentManager.getItemCount()); |
| assertEquals(v2, getNativeView(0)); |
| assertEquals(v0, getNativeView(1)); |
| assertEquals(v3, getNativeView(2)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testComplexOperationsOnStreamUpdatedAfterSetHeaderViews() { |
| // Set 2 header views first. These should always be there throughout stream update. |
| View v0 = new View(mActivity); |
| View v1 = new View(mActivity); |
| mFeedStreamSurface.setHeaderViews(Arrays.asList(v0, v1)); |
| assertEquals(2, mContentManager.getItemCount()); |
| assertEquals(v0, getNativeView(0)); |
| assertEquals(v1, getNativeView(1)); |
| final int headers = 2; |
| |
| // Add 3 new slices at first. |
| StreamUpdate update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("c")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("d")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("e")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(headers + 5, mContentManager.getItemCount()); |
| assertEquals(headers + 0, mContentManager.findContentPositionByKey("a")); |
| assertEquals(headers + 1, mContentManager.findContentPositionByKey("b")); |
| assertEquals(headers + 2, mContentManager.findContentPositionByKey("c")); |
| assertEquals(headers + 3, mContentManager.findContentPositionByKey("d")); |
| assertEquals(headers + 4, mContentManager.findContentPositionByKey("e")); |
| |
| // Combo of add, remove and reorder operations. |
| update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("f")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("g")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("h")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("c")) |
| .addUpdatedSlices(createSliceUpdateForExistingSlice("e")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("i")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(headers + 7, mContentManager.getItemCount()); |
| assertEquals(headers + 0, mContentManager.findContentPositionByKey("f")); |
| assertEquals(headers + 1, mContentManager.findContentPositionByKey("g")); |
| assertEquals(headers + 2, mContentManager.findContentPositionByKey("a")); |
| assertEquals(headers + 3, mContentManager.findContentPositionByKey("h")); |
| assertEquals(headers + 4, mContentManager.findContentPositionByKey("c")); |
| assertEquals(headers + 5, mContentManager.findContentPositionByKey("e")); |
| assertEquals(headers + 6, mContentManager.findContentPositionByKey("i")); |
| } |
| |
| @Test |
| @SmallTest |
| public void testNavigateTab() { |
| when(mPageNavigationDelegate.openUrl(anyInt(), any())).thenReturn(new MockTab(1, false)); |
| mFeedStreamSurface.navigateTab(TEST_URL, null); |
| verify(mPageNavigationDelegate) |
| .openUrl(ArgumentMatchers.eq(WindowOpenDisposition.CURRENT_TAB), any()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testNavigateNewTab() { |
| when(mPageNavigationDelegate.openUrl(anyInt(), any())).thenReturn(new MockTab(1, false)); |
| mFeedStreamSurface.navigateNewTab(TEST_URL); |
| verify(mPageNavigationDelegate) |
| .openUrl(ArgumentMatchers.eq(WindowOpenDisposition.NEW_FOREGROUND_TAB), any()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testNavigateIncognitoTab() { |
| when(mPageNavigationDelegate.openUrl(anyInt(), any())).thenReturn(new MockTab(1, false)); |
| mFeedStreamSurface.navigateIncognitoTab(TEST_URL); |
| verify(mPageNavigationDelegate) |
| .openUrl(ArgumentMatchers.eq(WindowOpenDisposition.OFF_THE_RECORD), any()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testSendFeedback() { |
| final String testUrl = "https://www.chromium.org"; |
| final String testTitle = "Chromium based browsers for the win!"; |
| final String xSurfaceCardTitle = "Card Title"; |
| final String cardTitle = "CardTitle"; |
| final String cardUrl = "CardUrl"; |
| // Arrange. |
| Map<String, String> productSpecificDataMap = new HashMap<>(); |
| productSpecificDataMap.put(FeedStreamSurface.XSURFACE_CARD_URL, testUrl); |
| productSpecificDataMap.put(xSurfaceCardTitle, testTitle); |
| |
| // Act. |
| mFeedStreamSurface.sendFeedback(productSpecificDataMap); |
| |
| // Assert. |
| verify(mHelpAndFeedback) |
| .showFeedback(any(), any(), eq(testUrl), eq(FeedStreamSurface.FEEDBACK_REPORT_TYPE), |
| mMapCaptor.capture(), eq(FeedStreamSurface.FEEDBACK_CONTEXT)); |
| |
| // Check that the map contents are as expected. |
| assertThat(mMapCaptor.getValue()).containsEntry(cardUrl, testUrl); |
| assertThat(mMapCaptor.getValue()).containsEntry(cardTitle, testTitle); |
| } |
| |
| @Test |
| @SmallTest |
| public void testShowSnackbar() { |
| mFeedStreamSurface.showSnackbar( |
| "message", "Undo", FeedActionsHandler.SnackbarDuration.SHORT, mSnackbarController); |
| verify(mSnackbarManager).showSnackbar(any()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testShowBottomSheet() { |
| mFeedStreamSurface.showBottomSheet(new TextView(mActivity)); |
| verify(mBottomSheetController).requestShowContent(any(), anyBoolean()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testDismissBottomSheet() { |
| mFeedStreamSurface.showBottomSheet(new TextView(mActivity)); |
| mFeedStreamSurface.dismissBottomSheet(); |
| verify(mBottomSheetController).hideContent(any(), anyBoolean()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testRemoveContentsOnSurfaceClosed() { |
| // Set 2 header views first. |
| View v0 = new View(mActivity); |
| View v1 = new View(mActivity); |
| mFeedStreamSurface.setHeaderViews(Arrays.asList(v0, v1)); |
| assertEquals(2, mContentManager.getItemCount()); |
| assertEquals(v0, getNativeView(0)); |
| assertEquals(v1, getNativeView(1)); |
| final int headers = 2; |
| |
| // Add 3 new slices. |
| StreamUpdate update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("c")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| assertEquals(headers + 3, mContentManager.getItemCount()); |
| |
| // Closing the surface should remove all non-header contents. |
| mFeedStreamSurface.surfaceClosed(); |
| assertEquals(headers, mContentManager.getItemCount()); |
| assertEquals(v0, getNativeView(0)); |
| assertEquals(v1, getNativeView(1)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testLoadMoreOnDismissal() { |
| final int itemCount = 10; |
| |
| // loadMore not triggered due to last visible item not falling into lookahead range. |
| mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD - 1); |
| mLayoutManager.setItemCount(itemCount); |
| mFeedStreamSurface.commitDismissal(0); |
| verify(mFeedStreamSurfaceJniMock, never()) |
| .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class)); |
| |
| // loadMore triggered. |
| mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD + 1); |
| mLayoutManager.setItemCount(itemCount); |
| mFeedStreamSurface.commitDismissal(0); |
| verify(mFeedStreamSurfaceJniMock) |
| .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testLoadMoreOnNavigateNewTab() { |
| final int itemCount = 10; |
| |
| // loadMore not triggered due to last visible item not falling into lookahead range. |
| mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD - 1); |
| mLayoutManager.setItemCount(itemCount); |
| mFeedStreamSurface.navigateNewTab(""); |
| verify(mFeedStreamSurfaceJniMock, never()) |
| .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class)); |
| |
| // loadMore triggered. |
| mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD + 1); |
| mLayoutManager.setItemCount(itemCount); |
| mFeedStreamSurface.navigateNewTab(""); |
| verify(mFeedStreamSurfaceJniMock) |
| .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testLoadMoreOnNavigateIncognitoTab() { |
| final int itemCount = 10; |
| |
| // loadMore not triggered due to last visible item not falling into lookahead range. |
| mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD - 1); |
| mLayoutManager.setItemCount(itemCount); |
| mFeedStreamSurface.navigateIncognitoTab(""); |
| verify(mFeedStreamSurfaceJniMock, never()) |
| .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class)); |
| |
| // loadMore triggered. |
| mLayoutManager.setLastVisiblePosition(itemCount - LOAD_MORE_TRIGGER_LOOKAHEAD + 1); |
| mLayoutManager.setItemCount(itemCount); |
| mFeedStreamSurface.navigateIncognitoTab(""); |
| verify(mFeedStreamSurfaceJniMock) |
| .loadMore(anyLong(), any(FeedStreamSurface.class), any(Callback.class)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testSurfaceOpenedAndClosed() { |
| // Calling surfaceOpened() before startup() should not trigger native open call. |
| mFeedStreamSurface.surfaceOpened(); |
| verify(mFeedStreamSurfaceJniMock, never()) |
| .surfaceOpened(anyLong(), any(FeedStreamSurface.class)); |
| |
| // Calling surfaceClosed() before startup() should not trigger native open call. |
| mFeedStreamSurface.surfaceClosed(); |
| verify(mFeedStreamSurfaceJniMock, never()) |
| .surfaceClosed(anyLong(), any(FeedStreamSurface.class)); |
| |
| FeedStreamSurface.startup(); |
| |
| // Calling surfaceOpened() after startup() should trigger native open call. |
| mFeedStreamSurface.surfaceOpened(); |
| verify(mFeedStreamSurfaceJniMock).surfaceOpened(anyLong(), any(FeedStreamSurface.class)); |
| |
| // Calling surfaceClosed() after startup() should trigger native open call. |
| mFeedStreamSurface.surfaceClosed(); |
| verify(mFeedStreamSurfaceJniMock).surfaceClosed(anyLong(), any(FeedStreamSurface.class)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testClearAll() { |
| FeedStreamSurface.startup(); |
| InOrder order = Mockito.inOrder(mFeedStreamSurfaceJniMock, mProcessScope); |
| mFeedStreamSurface.surfaceOpened(); |
| order.verify(mFeedStreamSurfaceJniMock) |
| .surfaceOpened(anyLong(), any(FeedStreamSurface.class)); |
| |
| FeedStreamSurface.clearAll(); |
| order.verify(mFeedStreamSurfaceJniMock) |
| .surfaceClosed(anyLong(), any(FeedStreamSurface.class)); |
| order.verify(mProcessScope).resetAccount(); |
| order.verify(mFeedStreamSurfaceJniMock) |
| .surfaceOpened(anyLong(), any(FeedStreamSurface.class)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testFindChildViewContainingDescendentNullParameters() { |
| View v = new View(mActivity); |
| assertEquals(null, mFeedStreamSurface.findChildViewContainingDescendent(null, v)); |
| assertEquals(null, mFeedStreamSurface.findChildViewContainingDescendent(v, null)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testFindChildViewContainingDescendentNotADescendent() { |
| View v1 = new View(mActivity); |
| LinearLayout v2 = new LinearLayout(mActivity); |
| View v2Child = new View(mActivity); |
| v2.addView(v2Child); |
| |
| assertEquals(null, mFeedStreamSurface.findChildViewContainingDescendent(v1, v2)); |
| assertEquals(null, mFeedStreamSurface.findChildViewContainingDescendent(v1, v2Child)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testFindChildViewContainingDescendentDirectDescendent() { |
| LinearLayout parent = new LinearLayout(mActivity); |
| View child = new View(mActivity); |
| parent.addView(child); |
| |
| assertEquals(child, mFeedStreamSurface.findChildViewContainingDescendent(parent, child)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testFindChildViewContainingDescendentIndirectDescendent() { |
| LinearLayout parent = new LinearLayout(mActivity); |
| LinearLayout child = new LinearLayout(mActivity); |
| View grandChild = new View(mActivity); |
| parent.addView(child); |
| child.addView(grandChild); |
| |
| assertEquals( |
| child, mFeedStreamSurface.findChildViewContainingDescendent(parent, grandChild)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testNavigateReportsCorrectSlice() { |
| StreamUpdate update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| |
| View childA = new View(mActivity); |
| mRecyclerView.addView(childA); |
| View childB = new View(mActivity); |
| mRecyclerView.addView(childB); |
| |
| // findChildViewContainingDescendent() won't work on its own because mRecyclerView is a |
| // mockito spy, and therefore child.getParent() != mRecyclerView. |
| Mockito.doReturn(childA) |
| .when(mFeedStreamSurface) |
| .findChildViewContainingDescendent(mRecyclerView, childA); |
| Mockito.doReturn(childB) |
| .when(mFeedStreamSurface) |
| .findChildViewContainingDescendent(mRecyclerView, childB); |
| Mockito.doReturn(0).when(mRecyclerView).getChildAdapterPosition(childA); |
| Mockito.doReturn(1).when(mRecyclerView).getChildAdapterPosition(childB); |
| |
| mFeedStreamSurface.navigateTab("http://someurl", childB); |
| mFeedStreamSurface.navigateNewTab("http://someurl", childA); |
| |
| verify(mFeedStreamSurfaceJniMock) |
| .reportOpenAction(anyLong(), any(FeedStreamSurface.class), eq("b")); |
| verify(mFeedStreamSurfaceJniMock) |
| .reportOpenInNewTabAction(anyLong(), any(FeedStreamSurface.class), eq("a")); |
| } |
| |
| @Test |
| @SmallTest |
| public void testNavigateFromBottomSheetReportsCorrectSlice() { |
| StreamUpdate update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| |
| View childA = new View(mActivity); |
| mRecyclerView.addView(childA); |
| View childB = new View(mActivity); |
| mRecyclerView.addView(childB); |
| LinearLayout bottomSheetView = new LinearLayout(mActivity); |
| View menuItem = new View(mActivity); |
| bottomSheetView.addView(menuItem); |
| |
| // findChildViewContainingDescendent() won't work on its own because mRecyclerView is a |
| // mockito spy, and therefore child.getParent() != mRecyclerView. |
| Mockito.doReturn(childA) |
| .when(mFeedStreamSurface) |
| .findChildViewContainingDescendent(mRecyclerView, childA); |
| Mockito.doReturn(childB) |
| .when(mFeedStreamSurface) |
| .findChildViewContainingDescendent(mRecyclerView, childB); |
| Mockito.doReturn(0).when(mRecyclerView).getChildAdapterPosition(childA); |
| Mockito.doReturn(1).when(mRecyclerView).getChildAdapterPosition(childB); |
| |
| mFeedStreamSurface.showBottomSheet(bottomSheetView, childB); |
| mFeedStreamSurface.navigateTab("http://someurl", menuItem); |
| mFeedStreamSurface.dismissBottomSheet(); |
| mFeedStreamSurface.navigateNewTab("http://someurl", menuItem); |
| |
| verify(mFeedStreamSurfaceJniMock) |
| .reportOpenAction(anyLong(), any(FeedStreamSurface.class), eq("b")); |
| // Bottom sheet closed for this navigation, so slice cannot be found. |
| verify(mFeedStreamSurfaceJniMock) |
| .reportOpenInNewTabAction(anyLong(), any(FeedStreamSurface.class), eq("")); |
| } |
| |
| @Test |
| @SmallTest |
| public void testNavigateNoSliceFound() { |
| StreamUpdate update = StreamUpdate.newBuilder() |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a")) |
| .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("b")) |
| .build(); |
| mFeedStreamSurface.onStreamUpdated(update.toByteArray()); |
| |
| View nonConnectedView = new View(mActivity); |
| |
| // findChildViewContainingDescendent() won't work on its own because mRecyclerView is a |
| // mockito spy, and therefore child.getParent() != mRecyclerView. |
| Mockito.doReturn(null) |
| .when(mFeedStreamSurface) |
| .findChildViewContainingDescendent(mRecyclerView, nonConnectedView); |
| |
| mFeedStreamSurface.navigateTab("http://someurl", nonConnectedView); |
| |
| verify(mFeedStreamSurfaceJniMock) |
| .reportOpenAction(anyLong(), any(FeedStreamSurface.class), eq("")); |
| } |
| |
| private SliceUpdate createSliceUpdateForExistingSlice(String sliceId) { |
| return SliceUpdate.newBuilder().setSliceId(sliceId).build(); |
| } |
| |
| private SliceUpdate createSliceUpdateForNewXSurfaceSlice(String sliceId) { |
| return SliceUpdate.newBuilder().setSlice(createXSurfaceSSlice(sliceId)).build(); |
| } |
| |
| private Slice createXSurfaceSSlice(String sliceId) { |
| return Slice.newBuilder() |
| .setSliceId(sliceId) |
| .setXsurfaceSlice(XSurfaceSlice.newBuilder() |
| .setXsurfaceFrame(ByteString.copyFromUtf8(TEST_DATA)) |
| .build()) |
| .build(); |
| } |
| |
| private View getNativeView(int index) { |
| View view = ((FeedListContentManager.NativeViewContent) mContentManager.getContent(index)) |
| .getNativeView(mParent); |
| assertNotNull(view); |
| assertTrue(view instanceof FrameLayout); |
| return ((FrameLayout) view).getChildAt(0); |
| } |
| } |