| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.ntp.cards; | 5 package org.chromium.chrome.browser.ntp.cards; |
| 6 | 6 |
| 7 import static org.junit.Assert.assertEquals; | 7 import static org.junit.Assert.assertEquals; |
| 8 import static org.junit.Assert.assertFalse; | 8 import static org.junit.Assert.assertFalse; |
| 9 import static org.junit.Assert.assertNotEquals; | 9 import static org.junit.Assert.assertNotEquals; |
| 10 import static org.junit.Assert.assertThat; | |
| 11 import static org.junit.Assert.assertTrue; | 10 import static org.junit.Assert.assertTrue; |
| 12 import static org.junit.Assert.fail; | |
| 13 import static org.mockito.ArgumentMatchers.anyString; | 11 import static org.mockito.ArgumentMatchers.anyString; |
| 12 import static org.mockito.ArgumentMatchers.eq; |
| 14 import static org.mockito.Mockito.atLeastOnce; | 13 import static org.mockito.Mockito.atLeastOnce; |
| 15 import static org.mockito.Mockito.doNothing; | 14 import static org.mockito.Mockito.doNothing; |
| 15 import static org.mockito.Mockito.inOrder; |
| 16 import static org.mockito.Mockito.mock; | 16 import static org.mockito.Mockito.mock; |
| 17 import static org.mockito.Mockito.reset; | 17 import static org.mockito.Mockito.reset; |
| 18 import static org.mockito.Mockito.spy; | 18 import static org.mockito.Mockito.spy; |
| 19 import static org.mockito.Mockito.times; | 19 import static org.mockito.Mockito.times; |
| 20 import static org.mockito.Mockito.verify; | 20 import static org.mockito.Mockito.verify; |
| 21 import static org.mockito.Mockito.verifyNoMoreInteractions; |
| 21 import static org.mockito.Mockito.when; | 22 import static org.mockito.Mockito.when; |
| 22 | 23 |
| 23 import static org.chromium.base.test.util.Matchers.greaterThanOrEqualTo; | |
| 24 import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsUnitTestUt
ils.bindViewHolders; | 24 import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsUnitTestUt
ils.bindViewHolders; |
| 25 import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsUnitTestUt
ils.makeUiConfig; | 25 import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsUnitTestUt
ils.makeUiConfig; |
| 26 import static org.chromium.chrome.test.util.browser.suggestions.ContentSuggestio
nsTestUtils.createDummySuggestions; | 26 import static org.chromium.chrome.test.util.browser.suggestions.ContentSuggestio
nsTestUtils.createDummySuggestions; |
| 27 import static org.chromium.chrome.test.util.browser.suggestions.ContentSuggestio
nsTestUtils.explainFailedExpectation; | |
| 28 import static org.chromium.chrome.test.util.browser.suggestions.ContentSuggestio
nsTestUtils.registerCategory; | 27 import static org.chromium.chrome.test.util.browser.suggestions.ContentSuggestio
nsTestUtils.registerCategory; |
| 29 import static org.chromium.chrome.test.util.browser.suggestions.ContentSuggestio
nsTestUtils.viewTypeToString; | 28 import static org.chromium.chrome.test.util.browser.suggestions.ContentSuggestio
nsTestUtils.stringify; |
| 30 | 29 |
| 31 import android.content.res.Resources; | 30 import android.content.res.Resources; |
| 32 import android.support.v7.widget.RecyclerView; | 31 import android.support.v7.widget.RecyclerView; |
| 33 import android.support.v7.widget.RecyclerView.AdapterDataObserver; | 32 import android.support.v7.widget.RecyclerView.AdapterDataObserver; |
| 34 import android.view.View; | 33 import android.view.View; |
| 35 | 34 |
| 36 import org.junit.After; | 35 import org.junit.After; |
| 37 import org.junit.Before; | 36 import org.junit.Before; |
| 38 import org.junit.Rule; | 37 import org.junit.Rule; |
| 39 import org.junit.Test; | 38 import org.junit.Test; |
| 40 import org.junit.runner.RunWith; | 39 import org.junit.runner.RunWith; |
| 41 import org.mockito.ArgumentCaptor; | 40 import org.mockito.ArgumentCaptor; |
| 41 import org.mockito.InOrder; |
| 42 import org.mockito.Mock; | 42 import org.mockito.Mock; |
| 43 import org.mockito.MockitoAnnotations; | 43 import org.mockito.MockitoAnnotations; |
| 44 import org.mockito.exceptions.base.MockitoAssertionError; |
| 45 import org.mockito.internal.verification.Times; |
| 46 import org.mockito.internal.verification.api.VerificationDataInOrder; |
| 47 import org.mockito.verification.VerificationMode; |
| 44 import org.robolectric.RuntimeEnvironment; | 48 import org.robolectric.RuntimeEnvironment; |
| 45 import org.robolectric.annotation.Config; | 49 import org.robolectric.annotation.Config; |
| 46 import org.robolectric.annotation.Implementation; | 50 import org.robolectric.annotation.Implementation; |
| 47 import org.robolectric.annotation.Implements; | 51 import org.robolectric.annotation.Implements; |
| 48 import org.robolectric.shadows.ShadowResources; | 52 import org.robolectric.shadows.ShadowResources; |
| 49 | 53 |
| 50 import org.chromium.base.Callback; | 54 import org.chromium.base.Callback; |
| 51 import org.chromium.base.ContextUtils; | 55 import org.chromium.base.ContextUtils; |
| 52 import org.chromium.base.test.util.Feature; | 56 import org.chromium.base.test.util.Feature; |
| 53 import org.chromium.chrome.R; | 57 import org.chromium.chrome.R; |
| 54 import org.chromium.chrome.browser.ChromeFeatureList; | 58 import org.chromium.chrome.browser.ChromeFeatureList; |
| 55 import org.chromium.chrome.browser.DisableHistogramsRule; | 59 import org.chromium.chrome.browser.DisableHistogramsRule; |
| 56 import org.chromium.chrome.browser.ntp.ContextMenuManager; | 60 import org.chromium.chrome.browser.ntp.ContextMenuManager; |
| 57 import org.chromium.chrome.browser.ntp.cards.SignInPromo.SigninObserver; | 61 import org.chromium.chrome.browser.ntp.cards.SignInPromo.SigninObserver; |
| 58 import org.chromium.chrome.browser.ntp.snippets.CategoryInt; | 62 import org.chromium.chrome.browser.ntp.snippets.CategoryInt; |
| 59 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus; | 63 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus; |
| 64 import org.chromium.chrome.browser.ntp.snippets.KnownCategories; |
| 60 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; | 65 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; |
| 61 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; | 66 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; |
| 62 import org.chromium.chrome.browser.preferences.ChromePreferenceManager; | 67 import org.chromium.chrome.browser.preferences.ChromePreferenceManager; |
| 63 import org.chromium.chrome.browser.signin.SigninManager; | 68 import org.chromium.chrome.browser.signin.SigninManager; |
| 64 import org.chromium.chrome.browser.signin.SigninManager.SignInAllowedObserver; | 69 import org.chromium.chrome.browser.signin.SigninManager.SignInAllowedObserver; |
| 65 import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver; | 70 import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver; |
| 66 import org.chromium.chrome.browser.suggestions.ContentSuggestionsAdditionalActio
n; | 71 import org.chromium.chrome.browser.suggestions.ContentSuggestionsAdditionalActio
n; |
| 67 import org.chromium.chrome.browser.suggestions.DestructionObserver; | 72 import org.chromium.chrome.browser.suggestions.DestructionObserver; |
| 68 import org.chromium.chrome.browser.suggestions.SuggestionsEventReporter; | 73 import org.chromium.chrome.browser.suggestions.SuggestionsEventReporter; |
| 69 import org.chromium.chrome.browser.suggestions.SuggestionsRanker; | 74 import org.chromium.chrome.browser.suggestions.SuggestionsRanker; |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 101 private SigninManager mMockSigninManager; | 106 private SigninManager mMockSigninManager; |
| 102 @Mock | 107 @Mock |
| 103 private OfflinePageBridge mOfflinePageBridge; | 108 private OfflinePageBridge mOfflinePageBridge; |
| 104 @Mock | 109 @Mock |
| 105 private SuggestionsUiDelegate mUiDelegate; | 110 private SuggestionsUiDelegate mUiDelegate; |
| 106 | 111 |
| 107 /** | 112 /** |
| 108 * Stores information about a section that should be present in the adapter. | 113 * Stores information about a section that should be present in the adapter. |
| 109 */ | 114 */ |
| 110 private static class SectionDescriptor { | 115 private static class SectionDescriptor { |
| 111 public final int mNumSuggestions; | 116 public boolean mHeader = true; |
| 117 public final List<SnippetArticle> mSuggestions; |
| 112 public final boolean mStatusCard; | 118 public final boolean mStatusCard; |
| 113 public boolean mActionButton; | 119 public boolean mViewAllButton; |
| 120 public boolean mFetchButton; |
| 114 public boolean mProgressItem; | 121 public boolean mProgressItem; |
| 115 public SnippetArticle mFirstItem; | |
| 116 | 122 |
| 117 public SectionDescriptor(int numSuggestions) { | 123 public SectionDescriptor(List<SnippetArticle> suggestions) { |
| 118 mNumSuggestions = numSuggestions; | 124 mSuggestions = suggestions; |
| 119 mStatusCard = numSuggestions == 0; | 125 mStatusCard = suggestions.isEmpty(); |
| 120 } | 126 } |
| 121 | 127 |
| 122 public SectionDescriptor withActionButton() { | 128 public SectionDescriptor withoutHeader() { |
| 123 mActionButton = true; | 129 mHeader = false; |
| 130 return this; |
| 131 } |
| 132 |
| 133 public SectionDescriptor withViewAllButton() { |
| 134 mViewAllButton = true; |
| 135 return this; |
| 136 } |
| 137 |
| 138 public SectionDescriptor withFetchButton() { |
| 139 mFetchButton = true; |
| 124 return this; | 140 return this; |
| 125 } | 141 } |
| 126 | 142 |
| 127 public SectionDescriptor withProgress() { | 143 public SectionDescriptor withProgress() { |
| 128 mProgressItem = true; | 144 mProgressItem = true; |
| 129 return this; | 145 return this; |
| 130 } | 146 } |
| 131 | |
| 132 public SectionDescriptor withFirstItem(SnippetArticle firstItem) { | |
| 133 mFirstItem = firstItem; | |
| 134 return this; | |
| 135 } | |
| 136 } | 147 } |
| 137 | 148 |
| 138 /** | 149 /** |
| 139 * Checks the list of items from the adapter against a sequence of expectati
on, which is | 150 * Checks the list of items from the adapter against a sequence of expectati
on, which is |
| 140 * expressed as a sequence of calls to the {@link #expect} methods. | 151 * expressed as a sequence of calls to the {@code expect...()} methods. |
| 141 */ | 152 */ |
| 142 private static class ItemsMatcher { // TODO(pke): Find better name. | 153 private static class ItemsMatcher { // TODO(pke): Find better name. |
| 143 private final TreeNode mTreeNode; | 154 private final TreeNode mRoot; |
| 144 private int mCurrentIndex; | 155 private final NodeVisitor mVisitor = mock(NodeVisitor.class); |
| 156 private final InOrder mInOrder = inOrder(mVisitor); |
| 157 |
| 158 /** |
| 159 * The {@link org.mockito.internal.verification.Description} verificatio
n mode doesn't |
| 160 * support in-order verification, so we use a custom verification mode t
hat derives from the |
| 161 * default one. |
| 162 */ |
| 163 private final VerificationMode mVerification = new Times(1) { |
| 164 @Override |
| 165 public void verifyInOrder(VerificationDataInOrder data) { |
| 166 try { |
| 167 super.verifyInOrder(data); |
| 168 } catch (MockitoAssertionError e) { |
| 169 throw new MockitoAssertionError(e, stringify(mRoot)); |
| 170 } |
| 171 } |
| 172 }; |
| 145 | 173 |
| 146 public ItemsMatcher(TreeNode root) { | 174 public ItemsMatcher(TreeNode root) { |
| 147 mTreeNode = root; | 175 mRoot = root; |
| 176 root.visitItems(mVisitor); |
| 148 } | 177 } |
| 149 | 178 |
| 150 public void expect(@ItemViewType int expectedItemType) { | 179 public void expectSection(SectionDescriptor descriptor) { |
| 151 if (mCurrentIndex >= mTreeNode.getItemCount()) { | 180 if (descriptor.mHeader) { |
| 152 fail("Expected item of type " + viewTypeToString(expectedItemTyp
e) | 181 mInOrder.verify(mVisitor, mVerification).visitHeader(); |
| 153 + " but encountered end of list\n" | |
| 154 + explainFailedExpectation(mTreeNode, mCurrentIndex, exp
ectedItemType)); | |
| 155 } | |
| 156 if (mTreeNode.getItemViewType(mCurrentIndex) != expectedItemType) { | |
| 157 fail("Type mismatch at position " + mCurrentIndex + "\n" | |
| 158 + explainFailedExpectation(mTreeNode, mCurrentIndex, exp
ectedItemType)); | |
| 159 } | |
| 160 mCurrentIndex++; | |
| 161 } | |
| 162 | |
| 163 public void expect(SectionDescriptor descriptor) { | |
| 164 expect(ItemViewType.HEADER); | |
| 165 | |
| 166 if (descriptor.mFirstItem != null) { | |
| 167 if (mTreeNode.getSuggestionAt(mCurrentIndex) != descriptor.mFirs
tItem) { | |
| 168 fail("Wrong item at position " + mCurrentIndex + "\n" | |
| 169 + explainFailedExpectation( | |
| 170 mTreeNode, mCurrentIndex, ItemViewType.SNI
PPET)); | |
| 171 } | |
| 172 } | 182 } |
| 173 | 183 |
| 174 for (int i = 1; i <= descriptor.mNumSuggestions; i++) { | 184 for (SnippetArticle suggestion : descriptor.mSuggestions) { |
| 175 expect(ItemViewType.SNIPPET); | 185 mInOrder.verify(mVisitor, mVerification).visitSuggestion(eq(sugg
estion)); |
| 176 } | 186 } |
| 177 | 187 |
| 178 if (descriptor.mStatusCard) { | 188 if (descriptor.mStatusCard) { |
| 179 expect(ItemViewType.STATUS); | 189 mInOrder.verify(mVisitor, mVerification).visitNoSuggestionsItem(
); |
| 180 } | 190 } |
| 181 | 191 |
| 182 if (descriptor.mActionButton) { | 192 if (descriptor.mViewAllButton) { |
| 183 // TODO(bauerb): Verify the action. | 193 mInOrder.verify(mVisitor, mVerification) |
| 184 expect(ItemViewType.ACTION); | 194 .visitActionItem(ContentSuggestionsAdditionalAction.VIEW
_ALL); |
| 195 } |
| 196 |
| 197 if (descriptor.mFetchButton) { |
| 198 mInOrder.verify(mVisitor, mVerification) |
| 199 .visitActionItem(ContentSuggestionsAdditionalAction.FETC
H); |
| 185 } | 200 } |
| 186 | 201 |
| 187 if (descriptor.mProgressItem) { | 202 if (descriptor.mProgressItem) { |
| 188 expect(ItemViewType.PROGRESS); | 203 mInOrder.verify(mVisitor, mVerification).visitProgressItem(); |
| 189 } | 204 } |
| 190 } | 205 } |
| 191 | 206 |
| 207 public void expectAboveTheFoldItem() { |
| 208 mInOrder.verify(mVisitor, mVerification).visitAboveTheFoldItem(); |
| 209 } |
| 210 |
| 211 public void expectAllDismissedItem() { |
| 212 mInOrder.verify(mVisitor, mVerification).visitAllDismissedItem(); |
| 213 } |
| 214 |
| 215 public void expectFooter() { |
| 216 mInOrder.verify(mVisitor, mVerification).visitFooter(); |
| 217 } |
| 218 |
| 219 public void expectSpacingItem() { |
| 220 mInOrder.verify(mVisitor, mVerification).visitSpacingItem(); |
| 221 } |
| 222 |
| 192 public void expectEnd() { | 223 public void expectEnd() { |
| 193 assertEquals(mTreeNode.getItemCount(), mCurrentIndex); | 224 try { |
| 225 verifyNoMoreInteractions(mVisitor); |
| 226 } catch (MockitoAssertionError e) { |
| 227 throw new MockitoAssertionError(e, stringify(mRoot)); |
| 228 } |
| 194 } | 229 } |
| 195 } | 230 } |
| 196 | 231 |
| 197 @Before | 232 @Before |
| 198 public void setUp() { | 233 public void setUp() { |
| 199 MockitoAnnotations.initMocks(this); | 234 MockitoAnnotations.initMocks(this); |
| 200 | 235 |
| 201 ContextUtils.initApplicationContextForTests(RuntimeEnvironment.applicati
on); | 236 ContextUtils.initApplicationContextForTests(RuntimeEnvironment.applicati
on); |
| 202 | 237 |
| 203 // Set empty variation params for the test. | 238 // Set empty variation params for the test. |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 235 @Test | 270 @Test |
| 236 @Feature({"Ntp"}) | 271 @Feature({"Ntp"}) |
| 237 public void testSuggestionLoading() { | 272 public void testSuggestionLoading() { |
| 238 assertItemsFor(sectionWithStatusCard().withProgress()); | 273 assertItemsFor(sectionWithStatusCard().withProgress()); |
| 239 | 274 |
| 240 final int numSuggestions = 3; | 275 final int numSuggestions = 3; |
| 241 List<SnippetArticle> suggestions = createDummySuggestions(numSuggestions
, TEST_CATEGORY); | 276 List<SnippetArticle> suggestions = createDummySuggestions(numSuggestions
, TEST_CATEGORY); |
| 242 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 277 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 243 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 278 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 244 | 279 |
| 245 assertItemsFor(section(numSuggestions)); | 280 assertItemsFor(section(suggestions)); |
| 246 } | 281 } |
| 247 | 282 |
| 248 /** | 283 /** |
| 249 * Tests that the adapter keeps listening for suggestion updates. | 284 * Tests that the adapter keeps listening for suggestion updates. |
| 250 */ | 285 */ |
| 251 @Test | 286 @Test |
| 252 @Feature({"Ntp"}) | 287 @Feature({"Ntp"}) |
| 253 public void testSuggestionLoadingInitiallyEmpty() { | 288 public void testSuggestionLoadingInitiallyEmpty() { |
| 254 // If we don't get anything, we should be in the same situation as the i
nitial one. | 289 // If we don't get anything, we should be in the same situation as the i
nitial one. |
| 255 mSource.setSuggestionsForCategory(TEST_CATEGORY, new ArrayList<SnippetAr
ticle>()); | 290 mSource.setSuggestionsForCategory(TEST_CATEGORY, new ArrayList<SnippetAr
ticle>()); |
| 256 assertItemsFor(sectionWithStatusCard().withProgress()); | 291 assertItemsFor(sectionWithStatusCard().withProgress()); |
| 257 | 292 |
| 258 // We should load new suggestions when we get notified about them. | 293 // We should load new suggestions when we get notified about them. |
| 259 final int numSuggestions = 5; | 294 final int numSuggestions = 5; |
| 260 | 295 |
| 261 List<SnippetArticle> suggestions = createDummySuggestions(numSuggestions
, TEST_CATEGORY); | 296 List<SnippetArticle> suggestions = createDummySuggestions(numSuggestions
, TEST_CATEGORY); |
| 262 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 297 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 263 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 298 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 264 | 299 |
| 265 assertItemsFor(section(numSuggestions)); | 300 assertItemsFor(section(suggestions)); |
| 266 } | 301 } |
| 267 | 302 |
| 268 /** | 303 /** |
| 269 * Tests that the adapter clears the suggestions when asked to. | 304 * Tests that the adapter clears the suggestions when asked to. |
| 270 */ | 305 */ |
| 271 @Test | 306 @Test |
| 272 @Feature({"Ntp"}) | 307 @Feature({"Ntp"}) |
| 273 public void testSuggestionClearing() { | 308 public void testSuggestionClearing() { |
| 274 List<SnippetArticle> suggestions = createDummySuggestions(4, TEST_CATEGO
RY); | 309 List<SnippetArticle> suggestions = createDummySuggestions(4, TEST_CATEGO
RY); |
| 275 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 310 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 276 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 311 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 277 assertItemsFor(section(4)); | 312 assertItemsFor(section(suggestions)); |
| 278 | 313 |
| 279 // If we get told that the category is enabled, we just leave the curren
t suggestions do not | 314 // If we get told that the category is enabled, we just leave the curren
t suggestions do not |
| 280 // clear them. | 315 // clear them. |
| 281 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 316 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 282 assertItemsFor(section(4)); | 317 assertItemsFor(section(suggestions)); |
| 283 | 318 |
| 284 // When the category is disabled, the section should go away completely. | 319 // When the category is disabled, the section should go away completely. |
| 285 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.CATEGORY_EXPL
ICITLY_DISABLED); | 320 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.CATEGORY_EXPL
ICITLY_DISABLED); |
| 286 assertItemsFor(); | 321 assertItemsFor(); |
| 287 | 322 |
| 288 // Now we're in the "all dismissed" state. No suggestions should be acce
pted. | 323 // Now we're in the "all dismissed" state. No suggestions should be acce
pted. |
| 289 suggestions = createDummySuggestions(6, TEST_CATEGORY); | 324 suggestions = createDummySuggestions(6, TEST_CATEGORY); |
| 290 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 325 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 291 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 326 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 292 assertItemsFor(); | 327 assertItemsFor(); |
| 293 | 328 |
| 294 // After a full refresh, the adapter should accept suggestions again. | 329 // After a full refresh, the adapter should accept suggestions again. |
| 295 mSource.fireFullRefreshRequired(); | 330 mSource.fireFullRefreshRequired(); |
| 296 assertItemsFor(section(6)); | 331 assertItemsFor(section(suggestions)); |
| 297 } | 332 } |
| 298 | 333 |
| 299 /** | 334 /** |
| 300 * Tests that the adapter loads suggestions only when the status is favorabl
e. | 335 * Tests that the adapter loads suggestions only when the status is favorabl
e. |
| 301 */ | 336 */ |
| 302 @Test | 337 @Test |
| 303 @Feature({"Ntp"}) | 338 @Feature({"Ntp"}) |
| 304 public void testSuggestionLoadingBlock() { | 339 public void testSuggestionLoadingBlock() { |
| 305 List<SnippetArticle> suggestions = createDummySuggestions(3, TEST_CATEGO
RY); | 340 List<SnippetArticle> suggestions = createDummySuggestions(3, TEST_CATEGO
RY); |
| 306 | 341 |
| 307 // By default, status is INITIALIZING, so we can load suggestions. | 342 // By default, status is INITIALIZING, so we can load suggestions. |
| 308 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 343 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 309 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 344 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 310 assertItemsFor(section(3)); | 345 assertItemsFor(section(suggestions)); |
| 311 | 346 |
| 312 // Add another suggestion. | 347 // Add another suggestion. |
| 313 suggestions.add(new SnippetArticle(TEST_CATEGORY, "https://site.com/url1
", "title1", "pub1", | 348 suggestions.add(new SnippetArticle(TEST_CATEGORY, "https://site.com/url3
", "title3", "pub3", |
| 314 "txt1", "https://site.com/url1", 0, 0, 0)); | 349 "txt3", "https://site.com/url3", 0, 0, 0)); |
| 315 | 350 |
| 316 // When the provider is removed, we should not be able to load suggestio
ns. The UI should | 351 // When the provider is removed, we should not be able to load suggestio
ns. The UI should |
| 317 // stay the same though. | 352 // stay the same though. |
| 318 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.NOT_PROVIDED)
; | 353 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.NOT_PROVIDED)
; |
| 319 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 354 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 320 assertItemsFor(section(3)); | 355 assertItemsFor(section(suggestions.subList(0, 3))); |
| 321 | 356 |
| 322 // INITIALIZING lets us load suggestions still. | 357 // INITIALIZING lets us load suggestions still. |
| 323 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.INITIALIZING)
; | 358 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.INITIALIZING)
; |
| 324 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 359 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 325 assertItemsFor(sectionWithStatusCard().withProgress()); | 360 assertItemsFor(sectionWithStatusCard().withProgress()); |
| 326 | 361 |
| 327 // The adapter should now be waiting for new suggestions and the fourth
one should appear. | 362 // The adapter should now be waiting for new suggestions and the fourth
one should appear. |
| 328 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 363 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 329 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 364 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 330 assertItemsFor(section(4)); | 365 assertItemsFor(section(suggestions)); |
| 331 | 366 |
| 332 // When the category gets disabled, the section should go away and not l
oad any suggestions. | 367 // When the category gets disabled, the section should go away and not l
oad any suggestions. |
| 333 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.CATEGORY_EXPL
ICITLY_DISABLED); | 368 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.CATEGORY_EXPL
ICITLY_DISABLED); |
| 334 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 369 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 335 assertItemsFor(); | 370 assertItemsFor(); |
| 336 } | 371 } |
| 337 | 372 |
| 338 /** | 373 /** |
| 339 * Tests how the loading indicator reacts to status changes. | 374 * Tests how the loading indicator reacts to status changes. |
| 340 */ | 375 */ |
| (...skipping 22 matching lines...) Expand all Loading... |
| 363 /** | 398 /** |
| 364 * Tests that the entire section disappears if its status switches to LOADIN
G_ERROR or | 399 * Tests that the entire section disappears if its status switches to LOADIN
G_ERROR or |
| 365 * CATEGORY_EXPLICITLY_DISABLED. Also tests that they are not shown when the
NTP reloads. | 400 * CATEGORY_EXPLICITLY_DISABLED. Also tests that they are not shown when the
NTP reloads. |
| 366 */ | 401 */ |
| 367 @Test | 402 @Test |
| 368 @Feature({"Ntp"}) | 403 @Feature({"Ntp"}) |
| 369 public void testSectionClearingWhenUnavailable() { | 404 public void testSectionClearingWhenUnavailable() { |
| 370 List<SnippetArticle> suggestions = createDummySuggestions(5, TEST_CATEGO
RY); | 405 List<SnippetArticle> suggestions = createDummySuggestions(5, TEST_CATEGO
RY); |
| 371 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 406 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 372 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 407 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 373 assertItemsFor(section(5)); | 408 assertItemsFor(section(suggestions)); |
| 374 | 409 |
| 375 // When the category goes away with a hard error, the section is cleared
from the UI. | 410 // When the category goes away with a hard error, the section is cleared
from the UI. |
| 376 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.LOADING_ERROR
); | 411 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.LOADING_ERROR
); |
| 377 assertItemsFor(); | 412 assertItemsFor(); |
| 378 | 413 |
| 379 // Same when loading a new NTP. | 414 // Same when loading a new NTP. |
| 380 reloadNtp(); | 415 reloadNtp(); |
| 381 assertItemsFor(); | 416 assertItemsFor(); |
| 382 | 417 |
| 383 // Same for CATEGORY_EXPLICITLY_DISABLED. | 418 // Same for CATEGORY_EXPLICITLY_DISABLED. |
| 384 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 419 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 385 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 420 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 386 reloadNtp(); | 421 reloadNtp(); |
| 387 assertItemsFor(section(5)); | 422 assertItemsFor(section(suggestions)); |
| 388 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.CATEGORY_EXPL
ICITLY_DISABLED); | 423 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.CATEGORY_EXPL
ICITLY_DISABLED); |
| 389 assertItemsFor(); | 424 assertItemsFor(); |
| 390 | 425 |
| 391 reloadNtp(); | 426 reloadNtp(); |
| 392 assertItemsFor(); | 427 assertItemsFor(); |
| 393 } | 428 } |
| 394 | 429 |
| 395 /** | 430 /** |
| 396 * Tests that the UI remains untouched if a category switches to NOT_PROVIDE
D. | 431 * Tests that the UI remains untouched if a category switches to NOT_PROVIDE
D. |
| 397 */ | 432 */ |
| 398 @Test | 433 @Test |
| 399 @Feature({"Ntp"}) | 434 @Feature({"Ntp"}) |
| 400 public void testUIUntouchedWhenNotProvided() { | 435 public void testUIUntouchedWhenNotProvided() { |
| 401 List<SnippetArticle> suggestions = createDummySuggestions(4, TEST_CATEGO
RY); | 436 List<SnippetArticle> suggestions = createDummySuggestions(4, TEST_CATEGO
RY); |
| 402 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 437 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 403 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 438 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 404 assertItemsFor(section(4)); | 439 assertItemsFor(section(suggestions)); |
| 405 | 440 |
| 406 // When the category switches to NOT_PROVIDED, UI stays the same. | 441 // When the category switches to NOT_PROVIDED, UI stays the same. |
| 407 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.NOT_PROVIDED)
; | 442 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.NOT_PROVIDED)
; |
| 408 mSource.silentlyRemoveCategory(TEST_CATEGORY); | 443 mSource.silentlyRemoveCategory(TEST_CATEGORY); |
| 409 assertItemsFor(section(4)); | 444 assertItemsFor(section(suggestions)); |
| 410 | 445 |
| 411 reloadNtp(); | 446 reloadNtp(); |
| 412 assertItemsFor(); | 447 assertItemsFor(); |
| 413 } | 448 } |
| 414 | 449 |
| 415 /** | 450 /** |
| 416 * Tests that the UI updates on updated suggestions. | 451 * Tests that the UI updates on updated suggestions. |
| 417 */ | 452 */ |
| 418 @Test | 453 @Test |
| 419 @Feature({"Ntp"}) | 454 @Feature({"Ntp"}) |
| 420 public void testUIUpdatesOnNewSuggestionsWhenOtherSectionSeen() { | 455 public void testUIUpdatesOnNewSuggestionsWhenOtherSectionSeen() { |
| 421 List<SnippetArticle> suggestions = createDummySuggestions(4, TEST_CATEGO
RY); | 456 List<SnippetArticle> suggestions = createDummySuggestions(4, TEST_CATEGO
RY); |
| 422 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 457 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 423 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 458 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 424 | 459 |
| 425 @CategoryInt | 460 @CategoryInt |
| 426 final int otherCategory = TEST_CATEGORY + 1; | 461 final int otherCategory = TEST_CATEGORY + 1; |
| 427 List<SnippetArticle> otherSuggestions = createDummySuggestions(2, otherC
ategory); | 462 List<SnippetArticle> otherSuggestions = createDummySuggestions(2, otherC
ategory); |
| 428 mSource.setStatusForCategory(otherCategory, CategoryStatus.AVAILABLE); | 463 mSource.setStatusForCategory(otherCategory, CategoryStatus.AVAILABLE); |
| 429 mSource.setInfoForCategory( | 464 mSource.setInfoForCategory( |
| 430 otherCategory, new CategoryInfoBuilder(otherCategory).showIfEmpt
y().build()); | 465 otherCategory, new CategoryInfoBuilder(otherCategory).showIfEmpt
y().build()); |
| 431 mSource.setSuggestionsForCategory(otherCategory, otherSuggestions); | 466 mSource.setSuggestionsForCategory(otherCategory, otherSuggestions); |
| 432 | 467 |
| 433 reloadNtp(); | 468 reloadNtp(); |
| 434 assertItemsFor(section(4), section(2)); | 469 assertItemsFor(section(suggestions), section(otherSuggestions)); |
| 435 | 470 |
| 436 // Bind the whole section - indicate that it is being viewed. | 471 // Bind the whole section - indicate that it is being viewed. |
| 437 bindViewHolders(mAdapter.getSectionListForTesting().getSectionForTesting
(otherCategory)); | 472 bindViewHolders(mAdapter.getSectionListForTesting().getSectionForTesting
(otherCategory)); |
| 438 | 473 |
| 439 List<SnippetArticle> newSuggestions = createDummySuggestions(3, TEST_CAT
EGORY, "new"); | 474 List<SnippetArticle> newSuggestions = createDummySuggestions(3, TEST_CAT
EGORY, "new"); |
| 440 mSource.setSuggestionsForCategory(TEST_CATEGORY, newSuggestions); | 475 mSource.setSuggestionsForCategory(TEST_CATEGORY, newSuggestions); |
| 441 assertItemsFor(section(3), section(2)); | 476 assertItemsFor(section(newSuggestions), section(otherSuggestions)); |
| 442 | 477 |
| 443 reloadNtp(); | 478 reloadNtp(); |
| 444 assertItemsFor(section(3), section(2)); | 479 assertItemsFor(section(newSuggestions), section(otherSuggestions)); |
| 445 } | 480 } |
| 446 | 481 |
| 447 /** Tests whether a section stays visible if empty, if required. */ | 482 /** Tests whether a section stays visible if empty, if required. */ |
| 448 @Test | 483 @Test |
| 449 @Feature({"Ntp"}) | 484 @Feature({"Ntp"}) |
| 450 public void testSectionVisibleIfEmpty() { | 485 public void testSectionVisibleIfEmpty() { |
| 451 // Part 1: VisibleIfEmpty = true | 486 // Part 1: VisibleIfEmpty = true |
| 452 FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource(); | 487 FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource(); |
| 453 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.INI
TIALIZING); | 488 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.INI
TIALIZING); |
| 454 suggestionsSource.setInfoForCategory( | 489 suggestionsSource.setInfoForCategory( |
| 455 TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).showIfEmpt
y().build()); | 490 TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).showIfEmpt
y().build()); |
| 456 | 491 |
| 457 // 1.1 - Initial state | 492 // 1.1 - Initial state |
| 458 when(mUiDelegate.getSuggestionsSource()).thenReturn(suggestionsSource); | 493 when(mUiDelegate.getSuggestionsSource()).thenReturn(suggestionsSource); |
| 459 reloadNtp(); | 494 reloadNtp(); |
| 460 assertItemsFor(sectionWithStatusCard().withProgress()); | 495 assertItemsFor(sectionWithStatusCard().withProgress()); |
| 461 | 496 |
| 462 // 1.2 - With suggestions | 497 // 1.2 - With suggestions |
| 463 List<SnippetArticle> suggestions = | 498 List<SnippetArticle> suggestions = |
| 464 Collections.unmodifiableList(createDummySuggestions(3, TEST_CATE
GORY)); | 499 Collections.unmodifiableList(createDummySuggestions(3, TEST_CATE
GORY)); |
| 465 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVA
ILABLE); | 500 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVA
ILABLE); |
| 466 suggestionsSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 501 suggestionsSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 467 assertItemsFor(section(3)); | 502 assertItemsFor(section(suggestions)); |
| 468 | 503 |
| 469 // 1.3 - When all suggestions are dismissed | 504 // 1.3 - When all suggestions are dismissed |
| 470 SuggestionsSection section = | 505 SuggestionsSection section = |
| 471 mAdapter.getSectionListForTesting().getSectionForTesting(TEST_CA
TEGORY); | 506 mAdapter.getSectionListForTesting().getSectionForTesting(TEST_CA
TEGORY); |
| 472 assertSectionMatches(section(3), section); | 507 assertSectionMatches(section(suggestions), section); |
| 473 section.removeSuggestionById(suggestions.get(0).mIdWithinCategory); | 508 section.removeSuggestionById(suggestions.get(0).mIdWithinCategory); |
| 474 section.removeSuggestionById(suggestions.get(1).mIdWithinCategory); | 509 section.removeSuggestionById(suggestions.get(1).mIdWithinCategory); |
| 475 section.removeSuggestionById(suggestions.get(2).mIdWithinCategory); | 510 section.removeSuggestionById(suggestions.get(2).mIdWithinCategory); |
| 476 assertItemsFor(sectionWithStatusCard()); | 511 assertItemsFor(sectionWithStatusCard()); |
| 477 | 512 |
| 478 // Part 2: VisibleIfEmpty = false | 513 // Part 2: VisibleIfEmpty = false |
| 479 suggestionsSource = new FakeSuggestionsSource(); | 514 suggestionsSource = new FakeSuggestionsSource(); |
| 480 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.INI
TIALIZING); | 515 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.INI
TIALIZING); |
| 481 suggestionsSource.setInfoForCategory( | 516 suggestionsSource.setInfoForCategory( |
| 482 TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).build()); | 517 TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).build()); |
| 483 | 518 |
| 484 // 2.1 - Initial state | 519 // 2.1 - Initial state |
| 485 when(mUiDelegate.getSuggestionsSource()).thenReturn(suggestionsSource); | 520 when(mUiDelegate.getSuggestionsSource()).thenReturn(suggestionsSource); |
| 486 reloadNtp(); | 521 reloadNtp(); |
| 487 assertItemsFor(); | 522 assertItemsFor(); |
| 488 | 523 |
| 489 // 2.2 - With suggestions | 524 // 2.2 - With suggestions |
| 490 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVA
ILABLE); | 525 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVA
ILABLE); |
| 491 suggestionsSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 526 suggestionsSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 492 assertItemsFor(); | 527 assertItemsFor(); |
| 493 | 528 |
| 494 // 2.3 - When all suggestions are dismissed - N/A, suggestions don't get
added. | 529 // 2.3 - When all suggestions are dismissed - N/A, suggestions don't get
added. |
| 495 } | 530 } |
| 496 | 531 |
| 497 /** | 532 /** |
| 498 * Tests that the more button is shown for sections that declare it. | 533 * Tests that the more button is shown for sections that declare it. |
| 499 */ | 534 */ |
| 500 @Test | 535 @Test |
| 501 @Feature({"Ntp"}) | 536 @Feature({"Ntp"}) |
| 502 public void testMoreButton() { | 537 public void testViewAllButton() { |
| 503 // Part 1: With "View All" action | 538 // Part 1: With "View All" action |
| 504 FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource(); | 539 FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource(); |
| 505 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.INI
TIALIZING); | 540 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.INI
TIALIZING); |
| 506 suggestionsSource.setInfoForCategory(TEST_CATEGORY, | 541 suggestionsSource.setInfoForCategory(TEST_CATEGORY, |
| 507 new CategoryInfoBuilder(TEST_CATEGORY) | 542 new CategoryInfoBuilder(TEST_CATEGORY) |
| 508 .withAction(ContentSuggestionsAdditionalAction.VIEW_ALL) | 543 .withAction(ContentSuggestionsAdditionalAction.VIEW_ALL) |
| 509 .showIfEmpty() | 544 .showIfEmpty() |
| 510 .build()); | 545 .build()); |
| 511 | 546 |
| 512 // 1.1 - Initial state. | 547 // 1.1 - Initial state. |
| 513 when(mUiDelegate.getSuggestionsSource()).thenReturn(suggestionsSource); | 548 when(mUiDelegate.getSuggestionsSource()).thenReturn(suggestionsSource); |
| 514 reloadNtp(); | 549 reloadNtp(); |
| 515 assertItemsFor(sectionWithStatusCard().withActionButton().withProgress()
); | 550 assertItemsFor(sectionWithStatusCard().withViewAllButton().withProgress(
)); |
| 516 | 551 |
| 517 // 1.2 - With suggestions. | 552 // 1.2 - With suggestions. |
| 518 List<SnippetArticle> suggestions = | 553 List<SnippetArticle> suggestions = |
| 519 Collections.unmodifiableList(createDummySuggestions(3, TEST_CATE
GORY)); | 554 Collections.unmodifiableList(createDummySuggestions(3, TEST_CATE
GORY)); |
| 520 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVA
ILABLE); | 555 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVA
ILABLE); |
| 521 suggestionsSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 556 suggestionsSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 522 assertItemsFor(section(3).withActionButton()); | 557 assertItemsFor(section(suggestions).withViewAllButton()); |
| 523 | 558 |
| 524 // 1.3 - When all suggestions are dismissed. | 559 // 1.3 - When all suggestions are dismissed. |
| 525 SuggestionsSection section = | 560 SuggestionsSection section = |
| 526 mAdapter.getSectionListForTesting().getSectionForTesting(TEST_CA
TEGORY); | 561 mAdapter.getSectionListForTesting().getSectionForTesting(TEST_CA
TEGORY); |
| 527 assertSectionMatches(section(3).withActionButton(), section); | 562 assertSectionMatches(section(suggestions).withViewAllButton(), section); |
| 528 section.removeSuggestionById(suggestions.get(0).mIdWithinCategory); | 563 section.removeSuggestionById(suggestions.get(0).mIdWithinCategory); |
| 529 section.removeSuggestionById(suggestions.get(1).mIdWithinCategory); | 564 section.removeSuggestionById(suggestions.get(1).mIdWithinCategory); |
| 530 section.removeSuggestionById(suggestions.get(2).mIdWithinCategory); | 565 section.removeSuggestionById(suggestions.get(2).mIdWithinCategory); |
| 531 assertItemsFor(sectionWithStatusCard().withActionButton()); | 566 assertItemsFor(sectionWithStatusCard().withViewAllButton()); |
| 532 | 567 |
| 533 // Part 1: Without "View All" action | 568 // Part 1: Without "View All" action |
| 534 suggestionsSource = new FakeSuggestionsSource(); | 569 suggestionsSource = new FakeSuggestionsSource(); |
| 535 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.INI
TIALIZING); | 570 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.INI
TIALIZING); |
| 536 suggestionsSource.setInfoForCategory( | 571 suggestionsSource.setInfoForCategory( |
| 537 TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).showIfEmpt
y().build()); | 572 TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).showIfEmpt
y().build()); |
| 538 | 573 |
| 539 // 2.1 - Initial state. | 574 // 2.1 - Initial state. |
| 540 when(mUiDelegate.getSuggestionsSource()).thenReturn(suggestionsSource); | 575 when(mUiDelegate.getSuggestionsSource()).thenReturn(suggestionsSource); |
| 541 reloadNtp(); | 576 reloadNtp(); |
| 542 assertItemsFor(sectionWithStatusCard().withProgress()); | 577 assertItemsFor(sectionWithStatusCard().withProgress()); |
| 543 | 578 |
| 544 // 2.2 - With suggestions. | 579 // 2.2 - With suggestions. |
| 545 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVA
ILABLE); | 580 suggestionsSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVA
ILABLE); |
| 546 suggestionsSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 581 suggestionsSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 547 assertItemsFor(section(3)); | 582 assertItemsFor(section(suggestions)); |
| 548 | 583 |
| 549 // 2.3 - When all suggestions are dismissed. | 584 // 2.3 - When all suggestions are dismissed. |
| 550 section = mAdapter.getSectionListForTesting().getSectionForTesting(TEST_
CATEGORY); | 585 section = mAdapter.getSectionListForTesting().getSectionForTesting(TEST_
CATEGORY); |
| 551 assertSectionMatches(section(3), section); | 586 assertSectionMatches(section(suggestions), section); |
| 552 section.removeSuggestionById(suggestions.get(0).mIdWithinCategory); | 587 section.removeSuggestionById(suggestions.get(0).mIdWithinCategory); |
| 553 section.removeSuggestionById(suggestions.get(1).mIdWithinCategory); | 588 section.removeSuggestionById(suggestions.get(1).mIdWithinCategory); |
| 554 section.removeSuggestionById(suggestions.get(2).mIdWithinCategory); | 589 section.removeSuggestionById(suggestions.get(2).mIdWithinCategory); |
| 555 assertItemsFor(sectionWithStatusCard()); | 590 assertItemsFor(sectionWithStatusCard()); |
| 556 } | 591 } |
| 557 | 592 |
| 558 /** | 593 /** |
| 594 * Tests that the more button is shown for sections that declare it. |
| 595 */ |
| 596 @Test |
| 597 @Feature({"Ntp"}) |
| 598 public void testFetchButton() { |
| 599 @CategoryInt |
| 600 final int category = TEST_CATEGORY; |
| 601 |
| 602 // Part 1: With "Fetch more" action |
| 603 FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource(); |
| 604 suggestionsSource.setStatusForCategory(category, CategoryStatus.INITIALI
ZING); |
| 605 suggestionsSource.setInfoForCategory(category, |
| 606 new CategoryInfoBuilder(category) |
| 607 .withAction(ContentSuggestionsAdditionalAction.FETCH) |
| 608 .showIfEmpty() |
| 609 .build()); |
| 610 |
| 611 // 1.1 - Initial state. |
| 612 when(mUiDelegate.getSuggestionsSource()).thenReturn(suggestionsSource); |
| 613 reloadNtp(); |
| 614 assertItemsFor(sectionWithStatusCard().withFetchButton().withProgress())
; |
| 615 |
| 616 // 1.2 - With suggestions. |
| 617 List<SnippetArticle> articles = |
| 618 Collections.unmodifiableList(createDummySuggestions(3, category)
); |
| 619 suggestionsSource.setStatusForCategory(category, CategoryStatus.AVAILABL
E); |
| 620 suggestionsSource.setSuggestionsForCategory(category, articles); |
| 621 assertItemsFor(section(articles).withFetchButton()); |
| 622 |
| 623 // 1.3 - When all suggestions are dismissed. |
| 624 SuggestionsSection section = |
| 625 mAdapter.getSectionListForTesting().getSectionForTesting(categor
y); |
| 626 assertSectionMatches(section(articles).withFetchButton(), section); |
| 627 section.removeSuggestionById(articles.get(0).mIdWithinCategory); |
| 628 section.removeSuggestionById(articles.get(1).mIdWithinCategory); |
| 629 section.removeSuggestionById(articles.get(2).mIdWithinCategory); |
| 630 assertItemsFor(sectionWithStatusCard().withFetchButton()); |
| 631 |
| 632 // Part 1: Without "Fetch more" action |
| 633 suggestionsSource = new FakeSuggestionsSource(); |
| 634 suggestionsSource.setStatusForCategory(category, CategoryStatus.INITIALI
ZING); |
| 635 suggestionsSource.setInfoForCategory( |
| 636 category, new CategoryInfoBuilder(category).showIfEmpty().build(
)); |
| 637 |
| 638 // 2.1 - Initial state. |
| 639 when(mUiDelegate.getSuggestionsSource()).thenReturn(suggestionsSource); |
| 640 reloadNtp(); |
| 641 assertItemsFor(sectionWithStatusCard().withProgress()); |
| 642 |
| 643 // 2.2 - With suggestions. |
| 644 suggestionsSource.setStatusForCategory(category, CategoryStatus.AVAILABL
E); |
| 645 suggestionsSource.setSuggestionsForCategory(category, articles); |
| 646 assertItemsFor(section(articles)); |
| 647 |
| 648 // 2.3 - When all suggestions are dismissed. |
| 649 section = mAdapter.getSectionListForTesting().getSectionForTesting(categ
ory); |
| 650 assertSectionMatches(section(articles), section); |
| 651 section.removeSuggestionById(articles.get(0).mIdWithinCategory); |
| 652 section.removeSuggestionById(articles.get(1).mIdWithinCategory); |
| 653 section.removeSuggestionById(articles.get(2).mIdWithinCategory); |
| 654 assertItemsFor(sectionWithStatusCard()); |
| 655 } |
| 656 |
| 657 /** |
| 559 * Tests that invalidated suggestions are immediately removed. | 658 * Tests that invalidated suggestions are immediately removed. |
| 560 */ | 659 */ |
| 561 @Test | 660 @Test |
| 562 @Feature({"Ntp"}) | 661 @Feature({"Ntp"}) |
| 563 public void testSuggestionInvalidated() { | 662 public void testSuggestionInvalidated() { |
| 564 List<SnippetArticle> suggestions = createDummySuggestions(3, TEST_CATEGO
RY); | 663 List<SnippetArticle> suggestions = createDummySuggestions(3, TEST_CATEGO
RY); |
| 565 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 664 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 566 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 665 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 567 assertItemsFor(section(3)); | 666 assertItemsFor(section(suggestions)); |
| 568 assertArticlesEqual(suggestions, 2, 5); | |
| 569 | 667 |
| 570 SnippetArticle removed = suggestions.remove(1); | 668 SnippetArticle removed = suggestions.remove(1); |
| 571 mSource.fireSuggestionInvalidated(TEST_CATEGORY, removed.mIdWithinCatego
ry); | 669 mSource.fireSuggestionInvalidated(TEST_CATEGORY, removed.mIdWithinCatego
ry); |
| 572 assertArticlesEqual(suggestions, 2, 4); | 670 assertItemsFor(section(suggestions)); |
| 573 } | 671 } |
| 574 | 672 |
| 575 /** | 673 /** |
| 576 * Tests that the UI handles dynamically added (server-side) categories corr
ectly. | 674 * Tests that the UI handles dynamically added (server-side) categories corr
ectly. |
| 577 */ | 675 */ |
| 578 @Test | 676 @Test |
| 579 @Feature({"Ntp"}) | 677 @Feature({"Ntp"}) |
| 580 public void testDynamicCategories() { | 678 public void testDynamicCategories() { |
| 581 List<SnippetArticle> suggestions = createDummySuggestions(3, TEST_CATEGO
RY); | 679 List<SnippetArticle> suggestions = createDummySuggestions(3, TEST_CATEGO
RY); |
| 582 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); | 680 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 583 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); | 681 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 584 assertItemsFor(section(3)); | 682 assertItemsFor(section(suggestions)); |
| 585 | 683 |
| 586 int dynamicCategory1 = 1010; | 684 int dynamicCategory1 = 1010; |
| 587 List<SnippetArticle> dynamics1 = createDummySuggestions(5, dynamicCatego
ry1); | 685 List<SnippetArticle> dynamics1 = createDummySuggestions(5, dynamicCatego
ry1); |
| 588 mSource.setInfoForCategory(dynamicCategory1, | 686 mSource.setInfoForCategory(dynamicCategory1, |
| 589 new CategoryInfoBuilder(dynamicCategory1) | 687 new CategoryInfoBuilder(dynamicCategory1) |
| 590 .withAction(ContentSuggestionsAdditionalAction.VIEW_ALL) | 688 .withAction(ContentSuggestionsAdditionalAction.VIEW_ALL) |
| 591 .build()); | 689 .build()); |
| 592 mSource.setStatusForCategory(dynamicCategory1, CategoryStatus.AVAILABLE)
; | 690 mSource.setStatusForCategory(dynamicCategory1, CategoryStatus.AVAILABLE)
; |
| 593 mSource.setSuggestionsForCategory(dynamicCategory1, dynamics1); | 691 mSource.setSuggestionsForCategory(dynamicCategory1, dynamics1); |
| 594 reloadNtp(); | 692 reloadNtp(); |
| 595 | 693 |
| 596 assertItemsFor(section(3), section(5).withActionButton()); | 694 assertItemsFor(section(suggestions), section(dynamics1).withViewAllButto
n()); |
| 597 | 695 |
| 598 int dynamicCategory2 = 1011; | 696 int dynamicCategory2 = 1011; |
| 599 List<SnippetArticle> dynamics2 = createDummySuggestions(11, dynamicCateg
ory2); | 697 List<SnippetArticle> dynamics2 = createDummySuggestions(11, dynamicCateg
ory2); |
| 600 mSource.setInfoForCategory(dynamicCategory2, | 698 mSource.setInfoForCategory(dynamicCategory2, |
| 601 new CategoryInfoBuilder(dynamicCategory1).build()); | 699 new CategoryInfoBuilder(dynamicCategory1).build()); |
| 602 mSource.setStatusForCategory(dynamicCategory2, CategoryStatus.AVAILABLE)
; | 700 mSource.setStatusForCategory(dynamicCategory2, CategoryStatus.AVAILABLE)
; |
| 603 mSource.setSuggestionsForCategory(dynamicCategory2, dynamics2); | 701 mSource.setSuggestionsForCategory(dynamicCategory2, dynamics2); |
| 604 reloadNtp(); | 702 reloadNtp(); |
| 605 assertItemsFor(section(3), section(5).withActionButton(), section(11)); | 703 assertItemsFor( |
| 704 section(suggestions), section(dynamics1).withViewAllButton(), se
ction(dynamics2)); |
| 705 } |
| 706 |
| 707 @Test |
| 708 @Feature({"Ntp"}) |
| 709 public void testArticlesForYouSection() { |
| 710 // Show one section of suggestions from the test category, and one secti
on with Articles for |
| 711 // You. |
| 712 List<SnippetArticle> suggestions = createDummySuggestions(3, TEST_CATEGO
RY); |
| 713 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); |
| 714 mSource.setSuggestionsForCategory(TEST_CATEGORY, suggestions); |
| 715 |
| 716 mSource.setInfoForCategory(KnownCategories.ARTICLES, |
| 717 new CategoryInfoBuilder(KnownCategories.ARTICLES).build()); |
| 718 mSource.setStatusForCategory(KnownCategories.ARTICLES, CategoryStatus.AV
AILABLE); |
| 719 List<SnippetArticle> articles = createDummySuggestions(3, KnownCategorie
s.ARTICLES); |
| 720 mSource.setSuggestionsForCategory(KnownCategories.ARTICLES, articles); |
| 721 |
| 722 reloadNtp(); |
| 723 assertItemsFor(section(suggestions), section(articles)); |
| 724 |
| 725 // Remove the test category section. The remaining lone Articles for You
section should not |
| 726 // have a header. |
| 727 mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.NOT_PROVIDED)
; |
| 728 reloadNtp(); |
| 729 assertItemsFor(section(articles).withoutHeader()); |
| 606 } | 730 } |
| 607 | 731 |
| 608 /** | 732 /** |
| 609 * Tests that the order of the categories is kept. | 733 * Tests that the order of the categories is kept. |
| 610 */ | 734 */ |
| 611 @Test | 735 @Test |
| 612 @Feature({"Ntp"}) | 736 @Feature({"Ntp"}) |
| 613 public void testCategoryOrder() { | 737 public void testCategoryOrder() { |
| 614 int[] categories = {TEST_CATEGORY, TEST_CATEGORY + 2, TEST_CATEGORY + 3,
TEST_CATEGORY + 4}; | 738 int[] categories = {TEST_CATEGORY, TEST_CATEGORY + 2, TEST_CATEGORY + 3,
TEST_CATEGORY + 4}; |
| 615 FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource(); | 739 FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource(); |
| (...skipping 339 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 955 } | 1079 } |
| 956 | 1080 |
| 957 /** | 1081 /** |
| 958 * Asserts that the given {@link TreeNode} is a {@link SuggestionsSection} t
hat matches the | 1082 * Asserts that the given {@link TreeNode} is a {@link SuggestionsSection} t
hat matches the |
| 959 * given {@link SectionDescriptor}. | 1083 * given {@link SectionDescriptor}. |
| 960 * @param descriptor The section descriptor to match against. | 1084 * @param descriptor The section descriptor to match against. |
| 961 * @param section The section from the adapter. | 1085 * @param section The section from the adapter. |
| 962 */ | 1086 */ |
| 963 private void assertSectionMatches(SectionDescriptor descriptor, SuggestionsS
ection section) { | 1087 private void assertSectionMatches(SectionDescriptor descriptor, SuggestionsS
ection section) { |
| 964 ItemsMatcher matcher = new ItemsMatcher(section); | 1088 ItemsMatcher matcher = new ItemsMatcher(section); |
| 965 matcher.expect(descriptor); | 1089 matcher.expectSection(descriptor); |
| 966 matcher.expectEnd(); | 1090 matcher.expectEnd(); |
| 967 } | 1091 } |
| 968 | 1092 |
| 969 /** | 1093 /** |
| 970 * Asserts that {@link #mAdapter}.{@link NewTabPageAdapter#getItemCount()} c
orresponds to an NTP | 1094 * Asserts that {@link #mAdapter}.{@link NewTabPageAdapter#getItemCount()} c
orresponds to an NTP |
| 971 * with the given sections in it. | 1095 * with the given sections in it. |
| 972 * | 1096 * |
| 973 * @param descriptors A list of descriptors, each describing a section that
should be present on | 1097 * @param descriptors A list of descriptors, each describing a section that
should be present on |
| 974 * the UI. | 1098 * the UI. |
| 975 */ | 1099 */ |
| 976 private void assertItemsFor(SectionDescriptor... descriptors) { | 1100 private void assertItemsFor(SectionDescriptor... descriptors) { |
| 977 ItemsMatcher matcher = new ItemsMatcher(mAdapter.getRootForTesting()); | 1101 ItemsMatcher matcher = new ItemsMatcher(mAdapter.getRootForTesting()); |
| 978 matcher.expect(ItemViewType.ABOVE_THE_FOLD); | 1102 matcher.expectAboveTheFoldItem(); |
| 979 for (SectionDescriptor descriptor : descriptors) matcher.expect(descript
or); | 1103 for (SectionDescriptor descriptor : descriptors) matcher.expectSection(d
escriptor); |
| 980 if (descriptors.length == 0) { | 1104 if (descriptors.length == 0) { |
| 981 matcher.expect(ItemViewType.ALL_DISMISSED); | 1105 matcher.expectAllDismissedItem(); |
| 982 } else { | 1106 } else { |
| 983 matcher.expect(ItemViewType.FOOTER); | 1107 matcher.expectFooter(); |
| 984 } | 1108 } |
| 985 matcher.expect(ItemViewType.SPACING); | 1109 matcher.expectSpacingItem(); |
| 986 matcher.expectEnd(); | 1110 matcher.expectEnd(); |
| 987 } | 1111 } |
| 988 | 1112 |
| 989 /** | 1113 /** |
| 990 * To be used with {@link #assertItemsFor(SectionDescriptor...)}, for a sect
ion with | 1114 * To be used with {@link #assertItemsFor(SectionDescriptor...)}, for a sect
ion with |
| 991 * {@code numSuggestions} cards in it. | 1115 * {@code numSuggestions} cards in it. |
| 992 * @param numSuggestions The number of suggestions in the section. If there
are zero, use either | 1116 * @param suggestions The list of suggestions in the section. If the list is
empty, use either |
| 993 * no section at all (if it is not displayed) or | 1117 * no section at all (if it is not displayed) or |
| 994 * {@link #sectionWithStatusCard()}. | 1118 * {@link #sectionWithStatusCard()}. |
| 995 * @return A descriptor for the section. | 1119 * @return A descriptor for the section. |
| 996 */ | 1120 */ |
| 997 private SectionDescriptor section(int numSuggestions) { | 1121 private SectionDescriptor section(List<SnippetArticle> suggestions) { |
| 998 assert numSuggestions > 0; | 1122 assert !suggestions.isEmpty(); |
| 999 return new SectionDescriptor(numSuggestions); | 1123 return new SectionDescriptor(suggestions); |
| 1000 } | 1124 } |
| 1001 | 1125 |
| 1002 /** | 1126 /** |
| 1003 * To be used with {@link #assertItemsFor(SectionDescriptor...)}, for a sect
ion that has no | 1127 * To be used with {@link #assertItemsFor(SectionDescriptor...)}, for a sect
ion that has no |
| 1004 * suggestions, but a status card to be displayed. | 1128 * suggestions, but a status card to be displayed. |
| 1005 * @return A descriptor for the section. | 1129 * @return A descriptor for the section. |
| 1006 */ | 1130 */ |
| 1007 private SectionDescriptor sectionWithStatusCard() { | 1131 private SectionDescriptor sectionWithStatusCard() { |
| 1008 return new SectionDescriptor(0); | 1132 return new SectionDescriptor(Collections.<SnippetArticle>emptyList()); |
| 1009 } | 1133 } |
| 1010 | 1134 |
| 1011 private void reloadNtp() { | 1135 private void reloadNtp() { |
| 1012 mAdapter = new NewTabPageAdapter(mUiDelegate, mock(View.class), makeUiCo
nfig(), | 1136 mAdapter = new NewTabPageAdapter(mUiDelegate, mock(View.class), makeUiCo
nfig(), |
| 1013 mOfflinePageBridge, mock(ContextMenuManager.class), /* tileGroup
Delegate = | 1137 mOfflinePageBridge, mock(ContextMenuManager.class), /* tileGroup
Delegate = |
| 1014 */ null); | 1138 */ null); |
| 1015 mAdapter.refreshSuggestions(); | 1139 mAdapter.refreshSuggestions(); |
| 1016 } | 1140 } |
| 1017 | 1141 |
| 1018 private void assertArticlesEqual(List<SnippetArticle> articles, int start, i
nt end) { | |
| 1019 assertThat(mAdapter.getItemCount(), greaterThanOrEqualTo(end)); | |
| 1020 for (int i = start; i < end; i++) { | |
| 1021 assertEquals(articles.get(i - start), mAdapter.getSuggestionAt(i)); | |
| 1022 } | |
| 1023 } | |
| 1024 | |
| 1025 private boolean isSignInPromoVisible() { | 1142 private boolean isSignInPromoVisible() { |
| 1026 return mAdapter.getFirstPositionForType(ItemViewType.PROMO) != RecyclerV
iew.NO_POSITION; | 1143 return mAdapter.getFirstPositionForType(ItemViewType.PROMO) != RecyclerV
iew.NO_POSITION; |
| 1027 } | 1144 } |
| 1028 | 1145 |
| 1029 private int getCategory(TreeNode item) { | 1146 private int getCategory(TreeNode item) { |
| 1030 return ((SuggestionsSection) item).getCategory(); | 1147 return ((SuggestionsSection) item).getCategory(); |
| 1031 } | 1148 } |
| 1032 } | 1149 } |
| OLD | NEW |