Index: chrome/android/junit/src/org/chromium/chrome/browser/suggestions/TileGroupTest.java |
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/TileGroupTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/TileGroupTest.java |
index fc193a783bc4aff78d0d5a57c500662d7fffbe7b..045a9a02162397a75537910ec170d774815dace3 100644 |
--- a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/TileGroupTest.java |
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/TileGroupTest.java |
@@ -4,35 +4,54 @@ |
package org.chromium.chrome.browser.suggestions; |
-import static junit.framework.TestCase.assertNotNull; |
- |
-import static org.mockito.Mockito.inOrder; |
+import static org.hamcrest.CoreMatchers.is; |
+import static org.junit.Assert.assertNotNull; |
+import static org.junit.Assert.assertThat; |
+import static org.junit.Assert.fail; |
+import static org.mockito.ArgumentMatchers.any; |
+import static org.mockito.ArgumentMatchers.anyBoolean; |
+import static org.mockito.ArgumentMatchers.anyInt; |
+import static org.mockito.Mockito.doAnswer; |
import static org.mockito.Mockito.mock; |
+import static org.mockito.Mockito.never; |
+import static org.mockito.Mockito.reset; |
import static org.mockito.Mockito.verify; |
+import static org.mockito.Mockito.verifyNoMoreInteractions; |
+import static org.mockito.Mockito.when; |
+import android.content.Context; |
import android.content.res.Resources; |
+import android.graphics.Bitmap; |
+import android.graphics.Color; |
import android.support.annotation.ColorRes; |
import android.support.annotation.DimenRes; |
+import android.support.annotation.LayoutRes; |
+import android.view.LayoutInflater; |
+import android.view.View; |
+import android.view.ViewGroup; |
+import android.widget.FrameLayout; |
+import org.hamcrest.CoreMatchers; |
import org.junit.Before; |
import org.junit.Rule; |
import org.junit.Test; |
import org.junit.runner.RunWith; |
-import org.mockito.InOrder; |
+import org.mockito.ArgumentCaptor; |
import org.mockito.Mock; |
import org.mockito.MockitoAnnotations; |
+import org.mockito.invocation.InvocationOnMock; |
+import org.mockito.stubbing.Answer; |
import org.robolectric.RuntimeEnvironment; |
import org.robolectric.annotation.Config; |
+import org.robolectric.annotation.Implementation; |
import org.robolectric.annotation.Implements; |
-import org.robolectric.annotation.RealObject; |
import org.robolectric.shadows.ShadowResources; |
import org.chromium.chrome.R; |
-import org.chromium.chrome.browser.ChromeFeatureList; |
import org.chromium.chrome.browser.EnableFeatures; |
+import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback; |
import org.chromium.chrome.browser.ntp.ContextMenuManager; |
-import org.chromium.chrome.browser.ntp.cards.NodeParent; |
-import org.chromium.chrome.browser.ntp.cards.SuggestionsSection; |
+import org.chromium.chrome.browser.ntp.NTPTileSource; |
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; |
import org.chromium.testing.local.LocalRobolectricTestRunner; |
@@ -40,7 +59,10 @@ |
* Unit tests for {@link TileGroup}. |
*/ |
@RunWith(LocalRobolectricTestRunner.class) |
-@Config(manifest = Config.NONE, shadows = {TileGroupTest.TileShadowResources.class}) |
+@Config(manifest = Config.NONE, |
+ shadows = {TileGroupTest.TileShadowResources.class, |
+ TileGroupTest.ShadowLayoutInflater.class}) |
+@EnableFeatures({}) |
public class TileGroupTest { |
private static final int MAX_TILES_TO_FETCH = 4; |
private static final int TILE_TITLE_LINES = 1; |
@@ -50,15 +72,7 @@ |
public EnableFeatures.Processor mEnableFeatureProcessor = new EnableFeatures.Processor(); |
@Mock |
- private SuggestionsSection.Delegate mDelegate; |
- @Mock |
- private NodeParent mParent; |
- @Mock |
- private SuggestionsUiDelegate mUiDelegate; |
- @Mock |
private TileGroup.Observer mTileGroupObserver; |
- @Mock |
- private OfflinePageBridge mOfflinePageBridge; |
private FakeTileGroupDelegate mTileGroupDelegate; |
@@ -70,36 +84,243 @@ public void setUp() { |
} |
@Test |
- @EnableFeatures({ChromeFeatureList.NTP_OFFLINE_PAGES_FEATURE_NAME}) |
public void testInitialiseWithEmptyTileList() { |
- TileGroup tileGroup = new TileGroup(RuntimeEnvironment.application, mUiDelegate, |
- mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
- mOfflinePageBridge, TILE_TITLE_LINES); |
+ TileGroup tileGroup = |
+ new TileGroup(RuntimeEnvironment.application, mock(SuggestionsUiDelegate.class), |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
tileGroup.startObserving(MAX_TILES_TO_FETCH); |
notifyTileUrlsAvailable(); |
+ // The TileGroup.Observer methods should be called even though no tiles are added, which is |
+ // an initialisation but not real state change. |
verify(mTileGroupObserver).onTileCountChanged(); |
verify(mTileGroupObserver).onLoadTaskCompleted(); |
verify(mTileGroupObserver).onTileDataChanged(); |
} |
@Test |
- @EnableFeatures({ChromeFeatureList.NTP_OFFLINE_PAGES_FEATURE_NAME}) |
public void testInitialiseWithTileList() { |
- TileGroup tileGroup = new TileGroup(RuntimeEnvironment.application, mUiDelegate, |
- mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
- mOfflinePageBridge, TILE_TITLE_LINES); |
+ TileGroup tileGroup = |
+ new TileGroup(RuntimeEnvironment.application, mock(SuggestionsUiDelegate.class), |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
+ tileGroup.startObserving(MAX_TILES_TO_FETCH); |
+ |
+ notifyTileUrlsAvailable(URLS); |
+ |
+ verify(mTileGroupObserver).onTileCountChanged(); |
+ verify(mTileGroupObserver).onLoadTaskCompleted(); |
+ verify(mTileGroupObserver).onTileDataChanged(); |
+ } |
+ |
+ @Test |
+ public void testReceiveNewTilesWithoutChanges() { |
+ TileGroup tileGroup = |
+ new TileGroup(RuntimeEnvironment.application, mock(SuggestionsUiDelegate.class), |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
+ tileGroup.startObserving(MAX_TILES_TO_FETCH); |
+ |
+ // First initialisation |
+ notifyTileUrlsAvailable(URLS); |
+ reset(mTileGroupObserver); |
+ |
+ // Notify the same thing. No changes so|mTileGroupObserver| should not be notified. |
+ notifyTileUrlsAvailable(URLS); |
+ verifyNoMoreInteractions(mTileGroupObserver); |
+ } |
+ |
+ @Test |
+ public void testReceiveNewTilesWithDataChanges() { |
+ TileGroup tileGroup = |
+ new TileGroup(RuntimeEnvironment.application, mock(SuggestionsUiDelegate.class), |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
+ tileGroup.startObserving(MAX_TILES_TO_FETCH); |
+ |
+ // First initialisation |
+ notifyTileUrlsAvailable(URLS); |
+ reset(mTileGroupObserver); |
+ |
+ // Notify the about different URLs, but the same number. #onTileCountChanged() should not be |
+ // called. |
+ notifyTileUrlsAvailable("foo", "bar"); |
+ verify(mTileGroupObserver, never()).onTileCountChanged(); // Tile count is still 2 |
+ verify(mTileGroupObserver, never()).onLoadTaskCompleted(); // No load task the second time. |
+ verify(mTileGroupObserver).onTileDataChanged(); // Data DID change. |
+ } |
+ |
+ @Test |
+ public void testReceiveNewTilesWithCountChanges() { |
+ TileGroup tileGroup = |
+ new TileGroup(RuntimeEnvironment.application, mock(SuggestionsUiDelegate.class), |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
+ tileGroup.startObserving(MAX_TILES_TO_FETCH); |
+ |
+ // First initialisation |
+ notifyTileUrlsAvailable(URLS); |
+ reset(mTileGroupObserver); |
+ |
+ notifyTileUrlsAvailable(URLS[0]); |
+ verify(mTileGroupObserver).onTileCountChanged(); // Tile count DID change. |
+ verify(mTileGroupObserver, never()).onLoadTaskCompleted(); // No load task the second time. |
+ verify(mTileGroupObserver).onTileDataChanged(); // Data DID change. |
+ } |
+ |
+ @Test |
+ public void testRenderTileView() { |
+ TileGroup tileGroup = |
+ new TileGroup(RuntimeEnvironment.application, mock(SuggestionsUiDelegate.class), |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
+ tileGroup.startObserving(MAX_TILES_TO_FETCH); |
+ ViewGroup layout = new FrameLayout(RuntimeEnvironment.application, null); |
+ |
+ // Initialise the internal list of tiles |
+ notifyTileUrlsAvailable(URLS); |
+ |
+ // Render them to the layout. |
+ tileGroup.renderTileViews(layout, false, false); |
+ assertThat(layout.getChildCount(), is(2)); |
+ assertThat(((TileView) layout.getChildAt(0)).getUrl(), is(URLS[0])); |
+ assertThat(((TileView) layout.getChildAt(1)).getUrl(), is(URLS[1])); |
+ } |
+ |
+ @Test |
+ public void testRenderTileViewReplacing() { |
+ TileGroup tileGroup = |
+ new TileGroup(RuntimeEnvironment.application, mock(SuggestionsUiDelegate.class), |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
tileGroup.startObserving(MAX_TILES_TO_FETCH); |
+ notifyTileUrlsAvailable(URLS); |
+ |
+ // Initialise the layout with views whose URLs don't match the ones of the new tiles. |
+ ViewGroup layout = new FrameLayout(RuntimeEnvironment.application, null); |
+ TileView view1 = mock(TileView.class); |
+ layout.addView(view1); |
+ TileView view2 = mock(TileView.class); |
+ layout.addView(view2); |
+ |
+ // The tiles should be updated, the old ones removed. |
+ tileGroup.renderTileViews(layout, false, false); |
+ assertThat(layout.getChildCount(), is(2)); |
+ assertThat(layout.indexOfChild(view1), is(-1)); |
+ assertThat(layout.indexOfChild(view2), is(-1)); |
+ verify(view1, never()).updateIfDataChanged(tileGroup.getTiles()[0]); |
+ verify(view2, never()).updateIfDataChanged(tileGroup.getTiles()[1]); |
+ } |
+ |
+ @Test |
+ public void testRenderTileViewRecycling() { |
+ TileGroup tileGroup = |
+ new TileGroup(RuntimeEnvironment.application, mock(SuggestionsUiDelegate.class), |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
+ tileGroup.startObserving(MAX_TILES_TO_FETCH); |
notifyTileUrlsAvailable(URLS); |
- InOrder inOrder = inOrder(mTileGroupObserver); |
- inOrder.verify(mTileGroupObserver).onTileCountChanged(); |
- inOrder.verify(mTileGroupObserver).onLoadTaskCompleted(); |
- inOrder.verify(mTileGroupObserver).onTileDataChanged(); |
- inOrder.verifyNoMoreInteractions(); |
+ // Initialise the layout with views whose URLs match the ones of the new tiles. |
+ ViewGroup layout = new FrameLayout(RuntimeEnvironment.application, null); |
+ TileView view1 = mock(TileView.class); |
+ when(view1.getUrl()).thenReturn(URLS[0]); |
+ layout.addView(view1); |
+ |
+ TileView view2 = mock(TileView.class); |
+ when(view2.getUrl()).thenReturn(URLS[1]); |
+ layout.addView(view2); |
+ |
+ // The tiles should be updated, the old ones reused. |
+ tileGroup.renderTileViews(layout, false, false); |
+ assertThat(layout.getChildCount(), is(2)); |
+ assertThat(layout.getChildAt(0), CoreMatchers.<View>is(view1)); |
+ assertThat(layout.getChildAt(1), CoreMatchers.<View>is(view2)); |
+ verify(view1).updateIfDataChanged(tileGroup.getTiles()[0]); |
+ verify(view2).updateIfDataChanged(tileGroup.getTiles()[1]); |
+ } |
+ |
+ @Test |
+ public void testIconLoading() { |
+ SuggestionsUiDelegate uiDelegate = mock(SuggestionsUiDelegate.class); |
+ TileGroup tileGroup = new TileGroup(RuntimeEnvironment.application, uiDelegate, |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
+ tileGroup.startObserving(MAX_TILES_TO_FETCH); |
+ notifyTileUrlsAvailable(URLS); // Initialise the internal state to include the test tile. |
+ reset(mTileGroupObserver); |
+ Tile tile = tileGroup.getTiles()[0]; |
+ |
+ ViewGroup layout = new FrameLayout(RuntimeEnvironment.application, null); |
+ tileGroup.buildTileView(tile, layout, /* trackLoadTask = */ true, /* condensed = */ false); |
+ |
+ verify(mTileGroupObserver).onLoadTaskAdded(); |
+ |
+ ArgumentCaptor<LargeIconCallback> captor = ArgumentCaptor.forClass(LargeIconCallback.class); |
+ verify(uiDelegate).getLargeIconForUrl(any(String.class), anyInt(), captor.capture()); |
+ for (LargeIconCallback cb : captor.getAllValues()) { |
+ cb.onLargeIconAvailable(mock(Bitmap.class), Color.BLACK, /* isColorDefault = */ false); |
+ } |
+ |
+ verify(mTileGroupObserver).onLoadTaskCompleted(); |
+ verify(mTileGroupObserver).onTileIconChanged(tile); |
+ } |
+ |
+ @Test |
+ public void testIconLoadingNoTask() { |
+ SuggestionsUiDelegate uiDelegate = mock(SuggestionsUiDelegate.class); |
+ TileGroup tileGroup = new TileGroup(RuntimeEnvironment.application, uiDelegate, |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
+ tileGroup.startObserving(MAX_TILES_TO_FETCH); |
+ notifyTileUrlsAvailable(URLS); // Initialise the internal state to include the test tile. |
+ reset(mTileGroupObserver); |
+ Tile tile = tileGroup.getTiles()[0]; |
+ |
+ ViewGroup layout = new FrameLayout(RuntimeEnvironment.application, null); |
+ tileGroup.buildTileView(tile, layout, /* trackLoadTask = */ false, /* condensed = */ false); |
+ |
+ verify(mTileGroupObserver, never()).onLoadTaskAdded(); |
+ |
+ ArgumentCaptor<LargeIconCallback> captor = ArgumentCaptor.forClass(LargeIconCallback.class); |
+ verify(uiDelegate).getLargeIconForUrl(any(String.class), anyInt(), captor.capture()); |
+ for (LargeIconCallback cb : captor.getAllValues()) { |
+ cb.onLargeIconAvailable(mock(Bitmap.class), Color.BLACK, /* isColorDefault = */ false); |
+ } |
+ |
+ verify(mTileGroupObserver, never()).onLoadTaskCompleted(); |
+ verify(mTileGroupObserver).onTileIconChanged(tile); |
+ } |
+ |
+ @Test |
+ public void testIconLoadingWhenTileNotRegistered() { |
+ SuggestionsUiDelegate uiDelegate = mock(SuggestionsUiDelegate.class); |
+ TileGroup tileGroup = new TileGroup(RuntimeEnvironment.application, uiDelegate, |
+ mock(ContextMenuManager.class), mTileGroupDelegate, mTileGroupObserver, |
+ mock(OfflinePageBridge.class), TILE_TITLE_LINES); |
+ tileGroup.startObserving(MAX_TILES_TO_FETCH); |
+ reset(mTileGroupObserver); |
+ Tile tile = new Tile("title", URLS[0], "", 0, NTPTileSource.POPULAR); |
+ |
+ ViewGroup layout = new FrameLayout(RuntimeEnvironment.application, null); |
+ tileGroup.buildTileView(tile, layout, /* trackLoadTask = */ true, /* condensed = */ false); |
+ verify(mTileGroupObserver).onLoadTaskAdded(); |
+ |
+ ArgumentCaptor<LargeIconCallback> captor = ArgumentCaptor.forClass(LargeIconCallback.class); |
+ verify(uiDelegate).getLargeIconForUrl(any(String.class), anyInt(), captor.capture()); |
+ captor.getValue().onLargeIconAvailable(mock(Bitmap.class), Color.BLACK, false); |
+ |
+ verify(mTileGroupObserver).onLoadTaskCompleted(); |
+ verify(mTileGroupObserver, never()).onTileIconChanged(tile); |
} |
+ /** |
+ * Notifies the tile group of new tiles created from the provided URLs. Requires |
+ * {@link TileGroup#startObserving(int)} to have been called on the tile group under test. |
+ * @see TileGroup#onMostVisitedURLsAvailable(String[], String[], String[], int[]) |
+ */ |
private void notifyTileUrlsAvailable(String... urls) { |
assertNotNull("MostVisitedObserver has not been registered.", mTileGroupDelegate.mObserver); |
@@ -111,6 +332,25 @@ private void notifyTileUrlsAvailable(String... urls) { |
titles, urls, whitelistIconPaths, sources); |
} |
+ /** |
+ * Creates a mocked {@link TileView} that will return the URL of the tile it has been |
+ * initialised with. |
+ */ |
+ private static TileView createMockTileView() { |
+ final TileView tileView = mock(TileView.class); |
+ doAnswer(new Answer<Void>() { |
+ @Override |
+ public Void answer(InvocationOnMock invocation) throws Throwable { |
+ Tile tile = invocation.getArgument(0); |
+ when(tileView.getUrl()).thenReturn(tile.getUrl()); |
+ return null; |
+ } |
+ }) |
+ .when(tileView) |
+ .initialize(any(Tile.class), anyInt(), anyBoolean()); |
+ return tileView; |
+ } |
+ |
private class FakeTileGroupDelegate implements TileGroup.Delegate { |
public MostVisitedSites.Observer mObserver; |
@@ -140,17 +380,38 @@ public void destroy() {} |
*/ |
@Implements(Resources.class) |
public static class TileShadowResources extends ShadowResources { |
- @RealObject |
- private Resources mRealResources; |
+ @Implementation |
public int getDimensionPixelSize(@DimenRes int id) { |
- if (id == R.dimen.tile_view_icon_size) return 2; |
- return mRealResources.getDimensionPixelSize(id); |
+ if (id == R.dimen.tile_view_icon_size) return 48; |
+ |
+ throw new IllegalArgumentException(); |
} |
- @SuppressWarnings("deprecation") |
+ @Implementation |
public int getColor(@ColorRes int id) { |
- if (id == R.color.default_favicon_background_color) return 2; |
- return mRealResources.getColor(id); |
+ if (id == R.color.default_favicon_background_color) return Color.BLACK; |
+ |
+ throw new IllegalArgumentException(); |
+ } |
+ } |
+ |
+ /** Intercepts calls to inflate views to replace them with mocks. */ |
+ @Implements(LayoutInflater.class) |
+ public static class ShadowLayoutInflater { |
+ @Implementation |
+ public static LayoutInflater from(Context context) { |
+ LayoutInflater layoutInflater = mock(LayoutInflater.class); |
+ when(layoutInflater.inflate(anyInt(), any(ViewGroup.class), anyBoolean())) |
+ .thenAnswer(new Answer<View>() { |
+ @Override |
+ public View answer(InvocationOnMock invocation) throws Throwable { |
+ @LayoutRes |
+ int layoutId = invocation.getArgument(0); |
+ if (layoutId != R.layout.tile_view) fail("Unexpected resource id."); |
+ return createMockTileView(); |
+ } |
+ }); |
+ return layoutInflater; |
} |
} |
} |