Commit 6c65a58b authored by bauerb's avatar bauerb Committed by Commit bot

[Android NTP] Reduce API surface of NewTabPageAdapter.

Remove:
* getSignInPromoPosition()
* getSuggestionPosition()

Change to default visibility:
* getBottomSpacerPosition()
* getLastContentItemPosition()

Change to private:
* hasAllBeenDismissed()
* getSuggestionsSection()
* getChildPositionOffset()

Two methods are added for tests:
* getSectionForTesting()
* getRootForTesting()

BUG=616090

Review-Url: https://codereview.chromium.org/2532953002
Cr-Commit-Position: refs/heads/master@{#436327}
parent 78660e87
......@@ -32,7 +32,6 @@ import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
......@@ -118,10 +117,10 @@ public class NewTabPageAdapter
// We use our own implementation of the dismissal animation, so we don't call the
// parent implementation. (by default it changes the translation-X and elevation)
mRecyclerView.updateViewStateForDismiss(dX, viewHolder);
mRecyclerView.updateViewStateForDismiss(dX, (NewTabPageViewHolder) viewHolder);
// If there is another item that should be animated at the same time, do the same to it.
ViewHolder siblingViewHolder = getDismissSibling(viewHolder);
NewTabPageViewHolder siblingViewHolder = getDismissSibling(viewHolder);
if (siblingViewHolder != null) {
mRecyclerView.updateViewStateForDismiss(dX, siblingViewHolder);
}
......@@ -388,26 +387,14 @@ public class NewTabPageAdapter
return RecyclerView.NO_POSITION;
}
public int getSignInPromoPosition() {
return getChildPositionOffset(mSigninPromo);
}
public int getBottomSpacerPosition() {
int getBottomSpacerPosition() {
return getChildPositionOffset(mBottomSpacer);
}
public int getLastContentItemPosition() {
int getLastContentItemPosition() {
return getChildPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mFooter);
}
public int getSuggestionPosition(SnippetArticle article) {
for (int i = 0; i < mRoot.getItemCount(); i++) {
SnippetArticle articleToCheck = mRoot.getSuggestionAt(i);
if (articleToCheck != null && articleToCheck.equals(article)) return i;
}
return RecyclerView.NO_POSITION;
}
private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions,
@CategoryStatusEnum int status) {
// Count the number of suggestions before this category.
......@@ -560,24 +547,16 @@ public class NewTabPageAdapter
/**
* Returns another view holder that should be dismissed at the same time as the provided one.
*/
public ViewHolder getDismissSibling(ViewHolder viewHolder) {
public NewTabPageViewHolder getDismissSibling(ViewHolder viewHolder) {
int swipePos = viewHolder.getAdapterPosition();
int siblingPosDelta = mRoot.getDismissSiblingPosDelta(swipePos);
if (siblingPosDelta == 0) return null;
return mRecyclerView.findViewHolderForAdapterPosition(siblingPosDelta + swipePos);
return (NewTabPageViewHolder) mRecyclerView.findViewHolderForAdapterPosition(
siblingPosDelta + swipePos);
}
/**
* @return The info associated to the provided category.
* @throws NullPointerException if {@code category} isn't currently registered with the adapter.
* */
public SuggestionsCategoryInfo getCategoryInfo(@CategoryInt int category) {
return mSections.get(category).getCategoryInfo();
}
@VisibleForTesting
public boolean hasAllBeenDismissed() {
private boolean hasAllBeenDismissed() {
return mSections.isEmpty() && !mSigninPromo.isVisible();
}
......@@ -601,20 +580,13 @@ public class NewTabPageAdapter
* @return Returns the {@link SuggestionsSection} that contains the item at
* {@code itemPosition}, or null if the item is not part of one.
*/
@VisibleForTesting
SuggestionsSection getSuggestionsSection(int itemPosition) {
private SuggestionsSection getSuggestionsSection(int itemPosition) {
TreeNode child = mRoot.getChildForPosition(itemPosition);
if (!(child instanceof SuggestionsSection)) return null;
return (SuggestionsSection) child;
}
@VisibleForTesting
List<TreeNode> getChildren() {
return Collections.unmodifiableList(mChildren);
}
@VisibleForTesting
int getChildPositionOffset(TreeNode child) {
private int getChildPositionOffset(TreeNode child) {
return mRoot.getStartingOffsetForChild(child);
}
......@@ -632,12 +604,20 @@ public class NewTabPageAdapter
return RecyclerView.NO_POSITION;
}
private void announceItemRemoved(String suggestionTitle) {
SuggestionsSection getSectionForTesting(@CategoryInt int category) {
return mSections.get(category);
}
InnerNode getRootForTesting() {
return mRoot;
}
private void announceItemRemoved(String itemTitle) {
// In tests the RecyclerView can be null.
if (mRecyclerView == null) return;
mRecyclerView.announceForAccessibility(mRecyclerView.getResources().getString(
R.string.ntp_accessibility_item_removed, suggestionTitle));
R.string.ntp_accessibility_item_removed, itemTitle));
}
private void announceItemRemoved(@StringRes int stringToAnnounce) {
......
......@@ -185,7 +185,7 @@ public class NewTabPageRecyclerView extends RecyclerView implements TouchDisable
* Updates the space added at the end of the list to make sure the above/below the fold
* distinction can be preserved.
*/
public void refreshBottomSpacing() {
private void refreshBottomSpacing() {
ViewHolder bottomSpacingViewHolder = findBottomSpacer();
// It might not be in the layout yet if it's not visible or ready to be displayed.
......@@ -536,8 +536,8 @@ public class NewTabPageRecyclerView extends RecyclerView implements TouchDisable
* @param dX The amount of horizontal displacement caused by user's action.
* @param viewHolder The view holder containing the view to be updated.
*/
public void updateViewStateForDismiss(float dX, ViewHolder viewHolder) {
if (!((NewTabPageViewHolder) viewHolder).isDismissable()) return;
public void updateViewStateForDismiss(float dX, NewTabPageViewHolder viewHolder) {
if (!viewHolder.isDismissable()) return;
viewHolder.itemView.setTranslationX(dX);
......
......@@ -50,7 +50,7 @@ public class SuggestionsSection extends InnerNode {
mOfflinePageBridge = offlinePageBridge;
mHeader = new SectionHeader(info.getTitle());
mSuggestionsList = new SuggestionsList(this);
mSuggestionsList = new SuggestionsList(this, info);
mStatus = StatusItem.createNoSuggestionsItem(this);
mMoreButton = new ActionItem(this);
mProgressIndicator = new ProgressItem(this);
......@@ -61,9 +61,11 @@ public class SuggestionsSection extends InnerNode {
private static class SuggestionsList extends ChildNode implements Iterable<SnippetArticle> {
private final List<SnippetArticle> mSuggestions = new ArrayList<>();
private final SuggestionsCategoryInfo mCategoryInfo;
public SuggestionsList(NodeParent parent) {
public SuggestionsList(NodeParent parent, SuggestionsCategoryInfo categoryInfo) {
super(parent);
mCategoryInfo = categoryInfo;
}
@Override
......@@ -80,7 +82,8 @@ public class SuggestionsSection extends InnerNode {
@Override
public void onBindViewHolder(NewTabPageViewHolder holder, int position) {
assert holder instanceof SnippetArticleViewHolder;
((SnippetArticleViewHolder) holder).onBindViewHolder(getSuggestionAt(position));
((SnippetArticleViewHolder) holder)
.onBindViewHolder(getSuggestionAt(position), mCategoryInfo);
}
@Override
......
......@@ -66,6 +66,7 @@ public class SnippetArticleViewHolder
private FetchImageCallback mImageCallback;
private SnippetArticle mArticle;
private SuggestionsCategoryInfo mCategoryInfo;
private int mPublisherFaviconSizePx;
private final boolean mUseFaviconService;
......@@ -149,10 +150,9 @@ public class SnippetArticleViewHolder
* Updates the layout taking into account screen dimensions and the type of snippet displayed.
*/
private void updateLayout() {
SuggestionsCategoryInfo info =
mRecyclerView.getNewTabPageAdapter().getCategoryInfo(mArticle.mCategory);
boolean narrow = mUiConfig.getCurrentDisplayStyle() == UiConfig.DISPLAY_STYLE_NARROW;
boolean minimal = info.getCardLayout() == ContentSuggestionsCardLayout.MINIMAL_CARD;
boolean minimal =
mCategoryInfo.getCardLayout() == ContentSuggestionsCardLayout.MINIMAL_CARD;
// If the screen is narrow or we are using the minimal layout, hide the article snippet.
boolean hideSnippet = narrow || minimal;
......@@ -185,13 +185,14 @@ public class SnippetArticleViewHolder
mPublisherBar.setLayoutParams(params);
}
public void onBindViewHolder(SnippetArticle article) {
public void onBindViewHolder(SnippetArticle article, SuggestionsCategoryInfo categoryInfo) {
super.onBindViewHolder();
// No longer listen for offline status changes to the old article.
if (mArticle != null) mArticle.setOfflineStatusChangeRunnable(null);
mArticle = article;
mCategoryInfo = categoryInfo;
updateLayout();
mHeadlineTextView.setText(mArticle.mTitle);
......
......@@ -117,7 +117,7 @@ public class NewTabPageRecyclerViewTest extends ChromeTabbedActivityTestBase {
// Scroll the last suggestion into view and click it.
SnippetArticle suggestion = suggestions.get(suggestions.size() - 1);
int suggestionPosition = getAdapter().getSuggestionPosition(suggestion);
int suggestionPosition = getSuggestionPosition(suggestion);
scrollToPosition(suggestionPosition);
final View suggestionView = waitForView(suggestionPosition);
ChromeTabUtils.waitForTabPageLoaded(mTab, new Runnable() {
......@@ -135,12 +135,13 @@ public class NewTabPageRecyclerViewTest extends ChromeTabbedActivityTestBase {
public void testAllDismissed() throws InterruptedException, TimeoutException {
setSuggestionsAndWaitForUpdate(3);
assertEquals(3, mSource.getSuggestionsForCategory(KnownCategories.ARTICLES).size());
assertFalse(getAdapter().hasAllBeenDismissed());
assertEquals(RecyclerView.NO_POSITION,
getAdapter().getFirstPositionForType(ItemViewType.ALL_DISMISSED));
assertEquals(1, mSource.getCategories().length);
assertEquals(KnownCategories.ARTICLES, mSource.getCategories()[0]);
// Dismiss the sign in promo.
int signinPromoPosition = getAdapter().getSignInPromoPosition();
int signinPromoPosition = getAdapter().getFirstPositionForType(ItemViewType.PROMO);
scrollToPosition(signinPromoPosition);
View signinPromoView = waitForView(signinPromoPosition);
getAdapter().dismissItem(signinPromoPosition);
......@@ -155,11 +156,11 @@ public class NewTabPageRecyclerViewTest extends ChromeTabbedActivityTestBase {
waitForViewToDetach(cardView);
cardPosition = getAdapter().getFirstCardPosition();
}
assertTrue(getAdapter().hasAllBeenDismissed());
assertEquals(0, mSource.getCategories().length);
// Click the refresh button on the all dismissed item.
int allDismissedPosition = getAdapter().getLastContentItemPosition();
int allDismissedPosition = getAdapter().getFirstPositionForType(ItemViewType.ALL_DISMISSED);
assertTrue(allDismissedPosition != RecyclerView.NO_POSITION);
scrollToPosition(allDismissedPosition);
View allDismissedView = waitForView(allDismissedPosition);
singleClickView(allDismissedView.findViewById(R.id.action_button));
......@@ -178,8 +179,7 @@ public class NewTabPageRecyclerViewTest extends ChromeTabbedActivityTestBase {
assertEquals(10, suggestions.size());
// Scroll a suggestion into view.
int suggestionPosition =
getAdapter().getSuggestionPosition(suggestions.get(suggestions.size() - 1));
int suggestionPosition = getSuggestionPosition(suggestions.get(suggestions.size() - 1));
scrollToPosition(suggestionPosition);
View suggestionView = waitForView(suggestionPosition);
......@@ -246,6 +246,15 @@ public class NewTabPageRecyclerViewTest extends ChromeTabbedActivityTestBase {
return getRecyclerView().getNewTabPageAdapter();
}
private int getSuggestionPosition(SnippetArticle article) {
NewTabPageAdapter adapter = getAdapter();
for (int i = 0; i < adapter.getItemCount(); i++) {
SnippetArticle articleToCheck = adapter.getSuggestionAt(i);
if (articleToCheck != null && articleToCheck.equals(article)) return i;
}
return RecyclerView.NO_POSITION;
}
private void scrollToPosition(final int position) {
final NewTabPageRecyclerView recyclerView = getRecyclerView();
......
......@@ -6,10 +6,10 @@ package org.chromium.chrome.browser.ntp.cards;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
......@@ -74,10 +74,12 @@ public class NewTabPageAdapterTest {
private FakeSuggestionsSource mSource;
private NewTabPageAdapter mAdapter;
@Mock
private SigninManager mMockSigninManager;
@Mock
private OfflinePageBridge mOfflinePageBridge;
@Mock private NewTabPageManager mNewTabPageManager;
@Mock
private NewTabPageManager mNewTabPageManager;
/**
* Stores information about a section that should be present in the adapter.
......@@ -109,19 +111,19 @@ public class NewTabPageAdapterTest {
* expressed as a sequence of calls to the {@link #expect} methods.
*/
private static class ItemsMatcher { // TODO(pke): Find better name.
private final NewTabPageAdapter mAdapter;
private final TreeNode mTreeNode;
private int mCurrentIndex;
public ItemsMatcher(NewTabPageAdapter adapter, int startingIndex) {
mAdapter = adapter;
mCurrentIndex = startingIndex;
public ItemsMatcher(TreeNode root) {
mTreeNode = root;
}
public void expect(@ItemViewType int expectedItemType) {
if (mCurrentIndex >= mAdapter.getItemCount()) {
if (mCurrentIndex >= mTreeNode.getItemCount()) {
fail("Expected item of type " + expectedItemType + " but encountered end of list");
}
@ItemViewType int itemType = mAdapter.getItemViewType(mCurrentIndex);
@ItemViewType
int itemType = mTreeNode.getItemViewType(mCurrentIndex);
assertEquals("Type mismatch at position " + mCurrentIndex, expectedItemType, itemType);
mCurrentIndex++;
}
......@@ -146,63 +148,9 @@ public class NewTabPageAdapterTest {
}
}
public void expectPosition(int expectedPosition) {
assertEquals(expectedPosition, mCurrentIndex);
}
}
/**
* Asserts that the given {@link TreeNode} is a {@link SuggestionsSection} that matches the
* given {@link SectionDescriptor}.
* @param descriptor The section descriptor to match against.
* @param node The node from the adapter.
*/
private void assertMatches(SectionDescriptor descriptor, TreeNode node) {
int offset = mAdapter.getChildPositionOffset(node);
ItemsMatcher matcher = new ItemsMatcher(mAdapter, offset);
matcher.expect(descriptor);
matcher.expectPosition(offset + node.getItemCount());
}
/**
* Asserts that {@link #mAdapter}.{@link NewTabPageAdapter#getItemCount()} corresponds to an
* NTP with the given sections in it.
* @param descriptors A list of descriptors, each describing a section that should be present on
* the UI.
*/
private void assertItemsFor(SectionDescriptor... descriptors) {
ItemsMatcher matcher = new ItemsMatcher(mAdapter, 0);
matcher.expect(ItemViewType.ABOVE_THE_FOLD);
for (SectionDescriptor descriptor : descriptors) matcher.expect(descriptor);
if (descriptors.length == 0) {
matcher.expect(ItemViewType.ALL_DISMISSED);
} else {
matcher.expect(ItemViewType.FOOTER);
public void expectEnd() {
assertEquals(mTreeNode.getItemCount(), mCurrentIndex);
}
matcher.expect(ItemViewType.SPACING);
matcher.expectPosition(mAdapter.getItemCount());
}
/**
* To be used with {@link #assertItemsFor(SectionDescriptor...)}, for a section with
* {@code numSuggestions} cards in it.
* @param numSuggestions The number of suggestions in the section. If there are zero, use either
* no section at all (if it is not displayed) or
* {@link #sectionWithStatusCard()}.
* @return A descriptor for the section.
*/
private SectionDescriptor section(int numSuggestions) {
assert numSuggestions > 0;
return new SectionDescriptor(numSuggestions);
}
/**
* To be used with {@link #assertItemsFor(SectionDescriptor...)}, for a section that has no
* suggestions, but a status card to be displayed.
* @return A descriptor for the section.
*/
private SectionDescriptor sectionWithStatusCard() {
return new SectionDescriptor(0);
}
@Before
......@@ -212,7 +160,6 @@ public class NewTabPageAdapterTest {
// Initialise the sign in state. We will be signed in by default in the tests.
assertFalse(ChromePreferenceManager.getInstance(RuntimeEnvironment.application)
.getNewTabPageSigninPromoDismissed());
mMockSigninManager = mock(SigninManager.class);
SigninManager.setInstanceForTesting(mMockSigninManager);
when(mMockSigninManager.isSignedInOnNative()).thenReturn(true);
when(mMockSigninManager.isSignInAllowed()).thenReturn(true);
......@@ -230,7 +177,7 @@ public class NewTabPageAdapterTest {
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(mSource);
when(mNewTabPageManager.isCurrentPage()).thenReturn(true);
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
}
@After
......@@ -361,8 +308,7 @@ public class NewTabPageAdapterTest {
@Test
@Feature({"Ntp"})
public void testProgressIndicatorDisplay() {
int progressPos = mAdapter.getFirstPositionForType(ItemViewType.FOOTER) - 1;
SuggestionsSection section = mAdapter.getSuggestionsSection(progressPos);
SuggestionsSection section = mAdapter.getSectionForTesting(KnownCategories.ARTICLES);
ProgressItem progress = section.getProgressItemForTesting();
mSource.setStatusForCategory(KnownCategories.ARTICLES, CategoryStatus.INITIALIZING);
......@@ -395,20 +341,19 @@ public class NewTabPageAdapterTest {
assertItemsFor();
// Same when loading a new NTP.
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
assertItemsFor();
// Same for CATEGORY_EXPLICITLY_DISABLED.
mSource.setStatusForCategory(KnownCategories.ARTICLES, CategoryStatus.AVAILABLE);
mSource.setSuggestionsForCategory(KnownCategories.ARTICLES, snippets);
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
assertItemsFor(section(5));
mSource.setStatusForCategory(
KnownCategories.ARTICLES, CategoryStatus.CATEGORY_EXPLICITLY_DISABLED);
assertItemsFor();
// Same when loading a new NTP.
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
assertItemsFor();
}
......@@ -428,8 +373,7 @@ public class NewTabPageAdapterTest {
mSource.silentlyRemoveCategory(KnownCategories.ARTICLES);
assertItemsFor(section(4));
// But it disappears when loading a new NTP.
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
assertItemsFor();
}
......@@ -439,31 +383,27 @@ public class NewTabPageAdapterTest {
public void testSectionVisibleIfEmpty() {
@CategoryInt
final int category = 42;
final int sectionIdx = 1; // section 0 is the above-the-fold item, we test the one after.
final List<SnippetArticle> articles =
Collections.unmodifiableList(createDummySuggestions(3));
FakeSuggestionsSource suggestionsSource;
// Part 1: VisibleIfEmpty = true
suggestionsSource = new FakeSuggestionsSource();
FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource();
suggestionsSource.setStatusForCategory(category, CategoryStatus.INITIALIZING);
suggestionsSource.setInfoForCategory(category,
new CategoryInfoBuilder(category).showIfEmpty().build());
// 1.1 - Initial state
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
assertItemsFor(sectionWithStatusCard().withProgress());
// 1.2 - With suggestions
List<SnippetArticle> articles = Collections.unmodifiableList(createDummySuggestions(3));
suggestionsSource.setStatusForCategory(category, CategoryStatus.AVAILABLE);
suggestionsSource.setSuggestionsForCategory(category, articles);
assertItemsFor(section(3));
// 1.3 - When all suggestions are dismissed
assertEquals(SuggestionsSection.class, mAdapter.getChildren().get(sectionIdx).getClass());
SuggestionsSection section42 = (SuggestionsSection) mAdapter.getChildren().get(sectionIdx);
assertMatches(section(3), section42);
SuggestionsSection section42 = mAdapter.getSectionForTesting(category);
assertSectionMatches(section(3), section42);
section42.removeSuggestion(articles.get(0));
section42.removeSuggestion(articles.get(1));
section42.removeSuggestion(articles.get(2));
......@@ -476,7 +416,7 @@ public class NewTabPageAdapterTest {
// 2.1 - Initial state
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
assertItemsFor();
// 2.2 - With suggestions
......@@ -495,14 +435,9 @@ public class NewTabPageAdapterTest {
public void testMoreButton() {
@CategoryInt
final int category = 42;
final int sectionIdx = 1; // section 0 is the above the fold, we test the one after.
final List<SnippetArticle> articles =
Collections.unmodifiableList(createDummySuggestions(3));
FakeSuggestionsSource suggestionsSource;
SuggestionsSection section42;
// Part 1: With "View All" action
suggestionsSource = new FakeSuggestionsSource();
FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource();
suggestionsSource.setStatusForCategory(category, CategoryStatus.INITIALIZING);
suggestionsSource.setInfoForCategory(category, new CategoryInfoBuilder(category)
.withViewAllAction()
......@@ -511,18 +446,18 @@ public class NewTabPageAdapterTest {
// 1.1 - Initial state.
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
assertItemsFor(sectionWithStatusCard().withActionButton().withProgress());
// 1.2 - With suggestions.
List<SnippetArticle> articles = Collections.unmodifiableList(createDummySuggestions(3));
suggestionsSource.setStatusForCategory(category, CategoryStatus.AVAILABLE);
suggestionsSource.setSuggestionsForCategory(category, articles);
assertItemsFor(section(3).withActionButton());
// 1.3 - When all suggestions are dismissed.
assertEquals(SuggestionsSection.class, mAdapter.getChildren().get(sectionIdx).getClass());
section42 = (SuggestionsSection) mAdapter.getChildren().get(sectionIdx);
assertMatches(section(3).withActionButton(), section42);
SuggestionsSection section42 = mAdapter.getSectionForTesting(category);
assertSectionMatches(section(3).withActionButton(), section42);
section42.removeSuggestion(articles.get(0));
section42.removeSuggestion(articles.get(1));
section42.removeSuggestion(articles.get(2));
......@@ -536,7 +471,7 @@ public class NewTabPageAdapterTest {
// 2.1 - Initial state.
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
assertItemsFor(sectionWithStatusCard().withProgress());
// 2.2 - With suggestions.
......@@ -545,22 +480,14 @@ public class NewTabPageAdapterTest {
assertItemsFor(section(3));
// 2.3 - When all suggestions are dismissed.
assertEquals(SuggestionsSection.class, mAdapter.getChildren().get(sectionIdx).getClass());
section42 = (SuggestionsSection) mAdapter.getChildren().get(sectionIdx);
assertMatches(section(3), section42);
section42 = mAdapter.getSectionForTesting(category);
assertSectionMatches(section(3), section42);
section42.removeSuggestion(articles.get(0));
section42.removeSuggestion(articles.get(1));
section42.removeSuggestion(articles.get(2));
assertItemsFor(sectionWithStatusCard());
}
private void assertArticlesEqual(List<SnippetArticle> articles, int start, int end) {
assertThat(mAdapter.getItemCount(), greaterThanOrEqualTo(end));
for (int i = start; i < end; i++) {
assertEquals(articles.get(i - start), mAdapter.getSuggestionAt(i));
}
}
/**
* Tests that invalidated suggestions are immediately removed.
*/
......@@ -596,8 +523,7 @@ public class NewTabPageAdapterTest {
.build());
mSource.setStatusForCategory(dynamicCategory1, CategoryStatus.AVAILABLE);
mSource.setSuggestionsForCategory(dynamicCategory1, dynamics1);
// Reload
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
assertItemsFor(section(3), section(5).withActionButton());
......@@ -607,8 +533,7 @@ public class NewTabPageAdapterTest {
new CategoryInfoBuilder(dynamicCategory1).build());
mSource.setStatusForCategory(dynamicCategory2, CategoryStatus.AVAILABLE);
mSource.setSuggestionsForCategory(dynamicCategory2, dynamics2);
// Reload
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
assertItemsFor(section(3), section(5).withActionButton(), section(11));
}
......@@ -621,16 +546,14 @@ public class NewTabPageAdapterTest {
// Above-the-fold, sign in promo, all-dismissed, footer, spacer.
final int basicChildCount = 5;
FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource();
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
registerCategory(suggestionsSource, KnownCategories.ARTICLES, 0);
registerCategory(suggestionsSource, KnownCategories.BOOKMARKS, 0);
registerCategory(suggestionsSource, KnownCategories.PHYSICAL_WEB_PAGES, 0);
registerCategory(suggestionsSource, KnownCategories.DOWNLOADS, 0);
reloadNtp();
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
NewTabPageAdapter ntpAdapter = new NewTabPageAdapter(
mNewTabPageManager, null, null, mOfflinePageBridge);
List<TreeNode> children = ntpAdapter.getChildren();
List<TreeNode> children = mAdapter.getRootForTesting().getChildren();
assertEquals(basicChildCount + 4, children.size());
assertEquals(AboveTheFoldItem.class, children.get(0).getClass());
assertEquals(SuggestionsSection.class, children.get(1).getClass());
......@@ -644,16 +567,14 @@ public class NewTabPageAdapterTest {
// With a different order.
suggestionsSource = new FakeSuggestionsSource();
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
registerCategory(suggestionsSource, KnownCategories.ARTICLES, 0);
registerCategory(suggestionsSource, KnownCategories.PHYSICAL_WEB_PAGES, 0);
registerCategory(suggestionsSource, KnownCategories.DOWNLOADS, 0);
registerCategory(suggestionsSource, KnownCategories.BOOKMARKS, 0);
reloadNtp();
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
ntpAdapter = new NewTabPageAdapter(
mNewTabPageManager, null, null, mOfflinePageBridge);
children = ntpAdapter.getChildren();
children = mAdapter.getRootForTesting().getChildren();
assertEquals(basicChildCount + 4, children.size());
assertEquals(AboveTheFoldItem.class, children.get(0).getClass());
assertEquals(SuggestionsSection.class, children.get(1).getClass());
......@@ -667,19 +588,17 @@ public class NewTabPageAdapterTest {
// With unknown categories.
suggestionsSource = new FakeSuggestionsSource();
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
registerCategory(suggestionsSource, KnownCategories.ARTICLES, 0);
registerCategory(suggestionsSource, KnownCategories.PHYSICAL_WEB_PAGES, 0);
registerCategory(suggestionsSource, KnownCategories.DOWNLOADS, 0);
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
ntpAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
// The adapter is already initialised, it will not accept new categories anymore.
registerCategory(suggestionsSource, 42, 1);
registerCategory(suggestionsSource, KnownCategories.BOOKMARKS, 1);
children = ntpAdapter.getChildren();
children = mAdapter.getRootForTesting().getChildren();
assertEquals(basicChildCount + 3, children.size());
assertEquals(AboveTheFoldItem.class, children.get(0).getClass());
assertEquals(SuggestionsSection.class, children.get(1).getClass());
......@@ -694,16 +613,12 @@ public class NewTabPageAdapterTest {
@Feature({"Ntp"})
public void testChangeNotifications() {
FakeSuggestionsSource suggestionsSource = spy(new FakeSuggestionsSource());
// Allow using dismissSuggestion() instead of throwing UnsupportedOperationException.
doNothing().when(suggestionsSource).dismissSuggestion(any(SnippetArticle.class));
registerCategory(suggestionsSource, KnownCategories.ARTICLES, 3);
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
NewTabPageAdapter adapter = new NewTabPageAdapter(
mNewTabPageManager, null, null, mOfflinePageBridge);
reloadNtp();
AdapterDataObserver dataObserver = mock(AdapterDataObserver.class);
adapter.registerAdapterDataObserver(dataObserver);
reset(dataObserver); // reset notification changes from initialisation.
mAdapter.registerAdapterDataObserver(dataObserver);
// Adapter content:
// Idx | Item
......@@ -714,24 +629,26 @@ public class NewTabPageAdapterTest {
// 5 | Footer
// 6 | Spacer
adapter.dismissItem(3); // Dismiss the second suggestion of the second section.
mAdapter.dismissItem(3); // Dismiss the second suggestion of the second section.
verify(dataObserver).onItemRangeRemoved(3, 1);
verify(dataObserver).onItemRangeChanged(5, 1, null);
// Make sure the call with the updated position works properly.
adapter.dismissItem(3);
mAdapter.dismissItem(3);
verify(dataObserver, times(2)).onItemRangeRemoved(3, 1);
verify(dataObserver).onItemRangeChanged(4, 1, null);
reset(dataObserver);
verifyNoMoreInteractions(dataObserver);
// Dismiss the last suggestion in the section. We should now show the status card.
adapter.dismissItem(2);
reset(dataObserver);
mAdapter.dismissItem(2);
verify(dataObserver).onItemRangeRemoved(2, 1); // Suggestion removed
verify(dataObserver).onItemRangeChanged(3, 1, null); // Spacer refresh
verify(dataObserver).onItemRangeInserted(2, 1); // Status card added
verify(dataObserver).onItemRangeChanged(4, 1, null); // Spacer refresh
verify(dataObserver).onItemRangeInserted(3, 1); // Action item added
verify(dataObserver).onItemRangeChanged(5, 1, null); // Spacer refresh
verifyNoMoreInteractions(dataObserver);
// Adapter content:
// Idx | Item
......@@ -748,7 +665,7 @@ public class NewTabPageAdapterTest {
reset(dataObserver);
suggestionsSource.setSuggestionsForCategory(
KnownCategories.ARTICLES, createDummySuggestions(newSuggestionCount));
adapter.onNewSuggestions(KnownCategories.ARTICLES);
mAdapter.onNewSuggestions(KnownCategories.ARTICLES);
verify(dataObserver).onItemRangeInserted(2, newSuggestionCount);
verify(dataObserver).onItemRangeChanged(5 + newSuggestionCount, 1, null); // Spacer refresh
verify(dataObserver, times(2)).onItemRangeRemoved(2 + newSuggestionCount, 1);
......@@ -768,13 +685,14 @@ public class NewTabPageAdapterTest {
reset(dataObserver);
suggestionsSource.setSuggestionsForCategory(
KnownCategories.ARTICLES, createDummySuggestions(0));
adapter.onCategoryStatusChanged(KnownCategories.ARTICLES, CategoryStatus.SIGNED_OUT);
mAdapter.onCategoryStatusChanged(KnownCategories.ARTICLES, CategoryStatus.SIGNED_OUT);
verify(dataObserver).onItemRangeRemoved(2, newSuggestionCount);
verify(dataObserver).onItemRangeChanged(3, 1, null); // Spacer refresh
verify(dataObserver).onItemRangeInserted(2, 1); // Status card added
verify(dataObserver).onItemRangeChanged(4, 1, null); // Spacer refresh
verify(dataObserver).onItemRangeInserted(3, 1); // Action item added
verify(dataObserver).onItemRangeChanged(5, 1, null); // Spacer refresh
verifyNoMoreInteractions(dataObserver);
}
@Test
......@@ -787,27 +705,8 @@ public class NewTabPageAdapterTest {
doNothing().when(mNewTabPageManager).addDestructionObserver(observers.capture());
NewTabPageAdapter adapter =
new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
TreeNode signinPromo = adapter.getChildren().get(2);
// Adapter content:
// Idx | Item | Item Index
// ----|--------------------|-------------
// 0 | Above-the-fold | 0
// 1 | Header | 1
// 2 | Status | 1
// 3 | Action | 1
// 4 | Progress Indicator | 1
// 5 | Sign in promo | 2
// 6 | Footer | 3
// 7 | Spacer | 4
assertEquals(1, signinPromo.getItemCount());
assertEquals(ItemViewType.PROMO, signinPromo.getItemViewType(0));
// verify(mNewTabPageManager).addDestructionObserver(observers.capture());
reloadNtp();
assertTrue(isSignInPromoVisible());
// Note: As currently implemented, these two variables should point to the same object, a
// SignInPromo.SigninObserver
......@@ -823,18 +722,18 @@ public class NewTabPageAdapterTest {
}
signInStateObserver.onSignedIn();
assertEquals(0, signinPromo.getItemCount());
assertFalse(isSignInPromoVisible());
signInStateObserver.onSignedOut();
assertEquals(1, signinPromo.getItemCount());
assertTrue(isSignInPromoVisible());
when(mMockSigninManager.isSignInAllowed()).thenReturn(false);
signInAllowedObserver.onSignInAllowedChanged();
assertEquals(0, signinPromo.getItemCount());
assertFalse(isSignInPromoVisible());
when(mMockSigninManager.isSignInAllowed()).thenReturn(true);
signInAllowedObserver.onSignInAllowedChanged();
assertEquals(1, signinPromo.getItemCount());
assertTrue(isSignInPromoVisible());
}
@Test
......@@ -844,38 +743,18 @@ public class NewTabPageAdapterTest {
when(mMockSigninManager.isSignedInOnNative()).thenReturn(false);
ChromePreferenceManager.getInstance(RuntimeEnvironment.application)
.setNewTabPageSigninPromoDismissed(false);
reloadNtp();
NewTabPageAdapter adapter =
new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
final int signInPromoIndex = adapter.getFirstPositionForType(ItemViewType.PROMO);
assertEquals(6, adapter.getChildren().size());
TreeNode signinPromo = adapter.getChildren().get(2);
final int signInPromoPosition = mAdapter.getFirstPositionForType(ItemViewType.PROMO);
assertNotEquals(RecyclerView.NO_POSITION, signInPromoPosition);
mAdapter.dismissItem(signInPromoPosition);
// Adapter content:
// Idx | Item | Item Index
// ----|--------------------|-------------
// 0 | Above-the-fold | 0
// 1 | Header | 1
// 2 | Status | 1
// 3 | Action | 1
// 4 | Progress Indicator | 1
// 5 | Sign in promo | 2
// 6 | All dismissed | 3
// 7 | Footer | 4
// 8 | Spacer | 5
assertEquals(ItemViewType.PROMO, signinPromo.getItemViewType(0));
adapter.dismissItem(signInPromoIndex);
assertEquals(0, signinPromo.getItemCount());
assertFalse(isSignInPromoVisible());
assertTrue(ChromePreferenceManager.getInstance(RuntimeEnvironment.application)
.getNewTabPageSigninPromoDismissed());
adapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
assertEquals(6, adapter.getChildren().size());
// The items below the signin promo move up, footer is now at the position of the promo.
assertEquals(ItemViewType.FOOTER, adapter.getItemViewType(signInPromoIndex));
reloadNtp();
assertFalse(isSignInPromoVisible());
}
@Test
......@@ -987,6 +866,75 @@ public class NewTabPageAdapterTest {
mAdapter.getFirstPositionForType(ItemViewType.ALL_DISMISSED));
}
/**
* Asserts that the given {@link TreeNode} is a {@link SuggestionsSection} that matches the
* given {@link SectionDescriptor}.
* @param descriptor The section descriptor to match against.
* @param section The section from the adapter.
*/
private void assertSectionMatches(SectionDescriptor descriptor, SuggestionsSection section) {
ItemsMatcher matcher = new ItemsMatcher(section);
matcher.expect(descriptor);
matcher.expectEnd();
}
/**
* Asserts that {@link #mAdapter}.{@link NewTabPageAdapter#getItemCount()} corresponds to an NTP
* with the given sections in it.
*
* @param descriptors A list of descriptors, each describing a section that should be present on
* the UI.
*/
private void assertItemsFor(SectionDescriptor... descriptors) {
ItemsMatcher matcher = new ItemsMatcher(mAdapter.getRootForTesting());
matcher.expect(ItemViewType.ABOVE_THE_FOLD);
for (SectionDescriptor descriptor : descriptors) matcher.expect(descriptor);
if (descriptors.length == 0) {
matcher.expect(ItemViewType.ALL_DISMISSED);
} else {
matcher.expect(ItemViewType.FOOTER);
}
matcher.expect(ItemViewType.SPACING);
matcher.expectEnd();
}
/**
* To be used with {@link #assertItemsFor(SectionDescriptor...)}, for a section with
* {@code numSuggestions} cards in it.
* @param numSuggestions The number of suggestions in the section. If there are zero, use either
* no section at all (if it is not displayed) or
* {@link #sectionWithStatusCard()}.
* @return A descriptor for the section.
*/
private SectionDescriptor section(int numSuggestions) {
assert numSuggestions > 0;
return new SectionDescriptor(numSuggestions);
}
/**
* To be used with {@link #assertItemsFor(SectionDescriptor...)}, for a section that has no
* suggestions, but a status card to be displayed.
* @return A descriptor for the section.
*/
private SectionDescriptor sectionWithStatusCard() {
return new SectionDescriptor(0);
}
private void reloadNtp() {
mAdapter = new NewTabPageAdapter(mNewTabPageManager, null, null, mOfflinePageBridge);
}
private void assertArticlesEqual(List<SnippetArticle> articles, int start, int end) {
assertThat(mAdapter.getItemCount(), greaterThanOrEqualTo(end));
for (int i = start; i < end; i++) {
assertEquals(articles.get(i - start), mAdapter.getSuggestionAt(i));
}
}
private boolean isSignInPromoVisible() {
return mAdapter.getFirstPositionForType(ItemViewType.PROMO) != RecyclerView.NO_POSITION;
}
/** Registers the category with the reload action */
private void registerCategory(FakeSuggestionsSource suggestionsSource,
@CategoryInt int category, int suggestionCount) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment