package io.github.hidroh.materialistic;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.WindowManager;
import android.widget.PopupMenu;
import android.widget.TextView;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.fakes.RoboMenuItem;
import org.robolectric.shadows.ShadowNetworkInfo;
import org.robolectric.shadows.ShadowPopupMenu;
import org.robolectric.shadows.ShadowToast;
import org.robolectric.shadows.support.v4.SupportFragmentTestUtil;
import org.robolectric.util.ActivityController;
import java.io.IOException;
import javax.inject.Inject;
import javax.inject.Named;
import io.github.hidroh.materialistic.accounts.UserServices;
import io.github.hidroh.materialistic.data.Item;
import io.github.hidroh.materialistic.data.ItemManager;
import io.github.hidroh.materialistic.data.ResponseListener;
import io.github.hidroh.materialistic.data.TestHnItem;
import io.github.hidroh.materialistic.test.TestItem;
import io.github.hidroh.materialistic.test.TestLayoutManager;
import io.github.hidroh.materialistic.test.TestRunner;
import io.github.hidroh.materialistic.test.shadow.ShadowItemTouchHelper;
import io.github.hidroh.materialistic.test.shadow.ShadowRecyclerView;
import io.github.hidroh.materialistic.test.shadow.ShadowRecyclerViewAdapter;
import io.github.hidroh.materialistic.test.shadow.ShadowTextView;
import io.github.hidroh.materialistic.test.suite.SlowTest;
import io.github.hidroh.materialistic.widget.MultiPageItemRecyclerViewAdapter;
import io.github.hidroh.materialistic.widget.SinglePageItemRecyclerViewAdapter;
import io.github.hidroh.materialistic.widget.ToggleItemViewHolder;
import static io.github.hidroh.materialistic.test.shadow.CustomShadows.customShadowOf;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static org.assertj.android.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
@SuppressWarnings("ConstantConditions")
@Category(SlowTest.class)
@Config(shadows = {ShadowRecyclerViewAdapter.class,
ShadowTextView.class})
@RunWith(TestRunner.class)
public class ItemFragmentSinglePageTest {
@Inject @Named(ActivityModule.HN) ItemManager hackerNewsClient;
@Inject UserServices userServices;
@Captor ArgumentCaptor<ResponseListener<Item>> listener;
@Captor ArgumentCaptor<UserServices.Callback> voteCallback;
private RecyclerView recyclerView;
private SinglePageItemRecyclerViewAdapter adapter;
private ToggleItemViewHolder viewHolder;
private ToggleItemViewHolder viewHolder1;
private ToggleItemViewHolder viewHolder2;
private ItemFragmentMultiPageTest.TestItemActivity activity;
private ItemFragment fragment;
private ActivityController<ItemFragmentMultiPageTest.TestItemActivity> controller;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
TestApplication.applicationGraph.inject(this);
reset(hackerNewsClient);
reset(userServices);
shadowOf(((WindowManager) RuntimeEnvironment.application
.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay())
.setHeight(0); // no preload
shadowOf((ConnectivityManager) RuntimeEnvironment.application
.getSystemService(Context.CONNECTIVITY_SERVICE))
.setActiveNetworkInfo(ShadowNetworkInfo.newInstance(null,
ConnectivityManager.TYPE_WIFI, 0, true, true));
final TestHnItem item0 = new TestHnItem(1, 1) {
@Override
public long getNeighbour(int direction) {
if (direction == Navigable.DIRECTION_DOWN) {
return 4L;
}
return super.getNeighbour(direction);
}
};
item0.populate(new TestItem() { // level 0
@Override
public String getId() {
return "1";
}
@Override
public String getText() {
return "text";
}
@Override
public int getDescendants() {
return 1;
}
@Override
public long[] getKids() {
return new long[]{2L};
}
});
final TestHnItem item1 = new TestHnItem(2, 2);
item1.populate( new TestItem() { // level 1
@Override
public String getId() {
return "2";
}
@Override
public String getParent() {
return "1";
}
@Override
public boolean isDeleted() {
return true;
}
@Override
public String getText() {
return "text";
}
@Override
public int getDescendants() {
return 1;
}
@Override
public long[] getKids() {
return new long[]{3L};
}
});
item0.getKidItems()[0] = item1;
final TestHnItem item2 = new TestHnItem(3, 3);
item2.populate(new TestItem() { // level 2
@Override
public String getId() {
return "3";
}
@Override
public long[] getKids() {
return new long[0];
}
@Override
public int getDescendants() {
return 0;
}
@Override
public String getParent() {
return "2";
}
@Override
public boolean isDead() {
return true;
}
});
item1.getKidItems()[0] = item2;
TestHnItem item3 = new TestHnItem(4, 4) {
@Override
public long getNeighbour(int direction) {
if (direction == Navigable.DIRECTION_UP) {
return 1L;
}
return super.getNeighbour(direction);
}
};
item3.populate(new TestItem() { // level 0
@Override
public String getId() {
return "4";
}
@Override
public String getText() {
return "text";
}
@Override
public int getDescendants() {
return 0;
}
@Override
public long[] getKids() {
return new long[0];
}
});
TestHnItem story = new TestHnItem(0);
story.populate(new TestItem() {
@Override
public long[] getKids() {
return new long[]{1L, 4L};
}
@Override
public int getDescendants() {
return 4;
}
});
story.getKidItems()[0] = item0;
story.getKidItems()[1] = item3;
Bundle args = new Bundle();
args.putParcelable(ItemFragment.EXTRA_ITEM, story);
fragment = (ItemFragment) Fragment.instantiate(RuntimeEnvironment.application,
ItemFragment.class.getName(), args);
controller = Robolectric.buildActivity(ItemFragmentMultiPageTest.TestItemActivity.class);
activity = controller.create().start().resume().visible().get();
PreferenceManager.getDefaultSharedPreferences(activity)
.edit()
.putBoolean(activity.getString(R.string.pref_lazy_load), false)
.apply();
activity.getSupportFragmentManager()
.beginTransaction()
.add(R.id.content_frame, fragment, ItemFragment.class.getName())
.commit();
recyclerView = (RecyclerView) fragment.getView().findViewById(R.id.recycler_view);
adapter = (SinglePageItemRecyclerViewAdapter) recyclerView.getAdapter();
// auto expand all
viewHolder = (ToggleItemViewHolder) customShadowOf(adapter).getViewHolder(0);
viewHolder1 = (ToggleItemViewHolder) customShadowOf(adapter).getViewHolder(1);
viewHolder2 = (ToggleItemViewHolder) customShadowOf(adapter).getViewHolder(2);
}
@Test
public void testExpand() {
assertEquals(5, adapter.getItemCount()); // 4 items + footer
}
@Test
public void testGetItemType() {
assertEquals(0, adapter.getItemViewType(0));
}
@Test
public void testPendingItem() {
Bundle args = new Bundle();
args.putParcelable(ItemFragment.EXTRA_ITEM, new TestItem() {
@Override
public Item[] getKidItems() {
return new Item[]{new TestItem() {
@Override
public int getLocalRevision() {
return -1;
}
}};
}
@Override
public int getKidCount() {
return 1;
}
});
Fragment fragment = Fragment.instantiate(RuntimeEnvironment.application,
ItemFragment.class.getName(), args);
SupportFragmentTestUtil.startVisibleFragment(fragment, ItemFragmentMultiPageTest.TestItemActivity.class,
R.id.content_frame);
recyclerView = (RecyclerView) fragment.getView().findViewById(R.id.recycler_view);
adapter = (SinglePageItemRecyclerViewAdapter) recyclerView.getAdapter();
}
@Test
public void testToggle() {
// collapse all
viewHolder.itemView.findViewById(R.id.button_toggle).performClick();
assertEquals(3, adapter.getItemCount()); // 2 items + footer
// expand again, should add item when binding
viewHolder.itemView.findViewById(R.id.button_toggle).performClick();
assertEquals(5, adapter.getItemCount()); // 4 items + footer
}
@Config(shadows = {ShadowRecyclerView.class, ShadowItemTouchHelper.class})
@Test
public void testSwipeToToggle() {
ItemTouchHelper.SimpleCallback callback = (ItemTouchHelper.SimpleCallback)
customShadowOf(recyclerView).getItemTouchHelperCallback();
assertThat(callback.getSwipeThreshold(viewHolder)).isGreaterThan(0f);
assertThat(callback.onMove(recyclerView, viewHolder, viewHolder)).isFalse();
assertThat(callback.getSwipeDirs(recyclerView, viewHolder))
.isEqualTo(ItemTouchHelper.RIGHT);
callback.onChildDraw(mock(Canvas.class), recyclerView, viewHolder, 1f, 0f,
ItemTouchHelper.ACTION_STATE_SWIPE, true);
// collapse all
callback.onSwiped(viewHolder, ItemTouchHelper.RIGHT);
assertEquals(3, adapter.getItemCount()); // 2 items + footer
}
@Test
public void testDeleted() {
assertNull(shadowOf(viewHolder1.itemView.findViewById(R.id.posted)).getOnClickListener());
}
@Test
public void testDead() {
assertThat(((TextView) viewHolder.itemView.findViewById(R.id.text)))
.hasCurrentTextColor(ContextCompat.getColor(activity, R.color.blackT87));
assertThat(((TextView) viewHolder2.itemView.findViewById(R.id.text)))
.hasCurrentTextColor(ContextCompat.getColor(activity,
AppUtils.getThemedResId(activity, android.R.attr.textColorSecondary)));
}
@Test
public void testDefaultCollapsed() {
PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application)
.edit()
.putString(RuntimeEnvironment.application.getString(R.string.pref_comment_display),
RuntimeEnvironment.application.getString(R.string.pref_comment_display_value_collapsed))
.apply();
final TestItem item0 = new TestItem() { // level 0
@Override
public String getId() {
return "1";
}
@Override
public int getKidCount() {
return 1;
}
@Override
public Item[] getKidItems() {
return new Item[]{new TestItem() {
@Override
public String getId() {
return "2";
}
}};
}
};
Bundle args = new Bundle();
args.putParcelable(ItemFragment.EXTRA_ITEM, new TestItem() {
@Override
public Item[] getKidItems() {
return new Item[]{item0};
}
@Override
public int getKidCount() {
return 1;
}
});
Fragment fragment = Fragment.instantiate(RuntimeEnvironment.application,
ItemFragment.class.getName(), args);
SupportFragmentTestUtil.startVisibleFragment(fragment, ItemFragmentMultiPageTest.TestItemActivity.class,
R.id.content_frame);
recyclerView = (RecyclerView) fragment.getView().findViewById(R.id.recycler_view);
adapter = (SinglePageItemRecyclerViewAdapter) recyclerView.getAdapter();
assertEquals(2, adapter.getItemCount()); // item + footer
assertEquals(2, adapter.getItemCount()); // should not add kid to adapter
}
@Test
public void testSavedState() {
shadowOf(activity).recreate();
assertEquals(5, adapter.getItemCount());
}
@Test
public void testDefaultDisplayAllLines() {
assertThat(viewHolder.itemView.findViewById(R.id.more)).isNotVisible();
}
@Test
public void testChangeThreadDisplay() {
assertSinglePage();
PreferenceManager.getDefaultSharedPreferences(activity)
.edit()
.putString(activity.getString(R.string.pref_comment_display),
activity.getString(R.string.pref_comment_display_value_single))
.apply(); // still single
assertSinglePage();
PreferenceManager.getDefaultSharedPreferences(activity)
.edit()
.putString(activity.getString(R.string.pref_comment_display),
activity.getString(R.string.pref_comment_display_value_multiple))
.apply(); // multiple
assertMultiplePage();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Test
public void testVote() {
viewHolder.itemView.findViewById(R.id.button_more).performClick();
PopupMenu popupMenu = ShadowPopupMenu.getLatestPopupMenu();
assertNotNull(popupMenu);
shadowOf(popupMenu).getOnMenuItemClickListener()
.onMenuItemClick(new RoboMenuItem(R.id.menu_contextual_vote));
verify(userServices).voteUp(any(Context.class), any(), voteCallback.capture());
voteCallback.getValue().onDone(true);
assertEquals(activity.getString(R.string.voted), ShadowToast.getTextOfLatestToast());
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Test
public void testVoteItemPromptToLogin() {
viewHolder.itemView.findViewById(R.id.button_more).performClick();
PopupMenu popupMenu = ShadowPopupMenu.getLatestPopupMenu();
assertNotNull(popupMenu);
shadowOf(popupMenu).getOnMenuItemClickListener()
.onMenuItemClick(new RoboMenuItem(R.id.menu_contextual_vote));
verify(userServices).voteUp(any(Context.class), any(), voteCallback.capture());
voteCallback.getValue().onDone(false);
assertThat(shadowOf(activity).getNextStartedActivity())
.hasComponent(activity, LoginActivity.class);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Test
public void testVoteItemFailed() {
viewHolder.itemView.findViewById(R.id.button_more).performClick();
PopupMenu popupMenu = ShadowPopupMenu.getLatestPopupMenu();
assertNotNull(popupMenu);
shadowOf(popupMenu).getOnMenuItemClickListener()
.onMenuItemClick(new RoboMenuItem(R.id.menu_contextual_vote));
verify(userServices).voteUp(any(Context.class), any(), voteCallback.capture());
voteCallback.getValue().onError(new IOException());
assertEquals(activity.getString(R.string.vote_failed), ShadowToast.getTextOfLatestToast());
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Test
public void testReply() {
viewHolder.itemView.findViewById(R.id.button_more).performClick();
PopupMenu popupMenu = ShadowPopupMenu.getLatestPopupMenu();
assertNotNull(popupMenu);
shadowOf(popupMenu).getOnMenuItemClickListener()
.onMenuItemClick(new RoboMenuItem(R.id.menu_contextual_comment));
assertThat(shadowOf(activity).getNextStartedActivity())
.hasComponent(activity, ComposeActivity.class)
.hasExtra(ComposeActivity.EXTRA_PARENT_ID, "1");
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Test
public void testShare() {
TestApplication.addResolver(new Intent(Intent.ACTION_SEND));
viewHolder.itemView.findViewById(R.id.button_more).performClick();
PopupMenu popupMenu = ShadowPopupMenu.getLatestPopupMenu();
assertNotNull(popupMenu);
shadowOf(popupMenu).getOnMenuItemClickListener()
.onMenuItemClick(new RoboMenuItem(R.id.menu_contextual_share));
assertThat(shadowOf(activity).getNextStartedActivity())
.hasAction(Intent.ACTION_SEND);
}
@Config(shadows = ShadowRecyclerView.class)
@Test
public void testNavigate() {
ShadowRecyclerView shadowRecyclerView = customShadowOf(recyclerView);
TestLayoutManager testLayout
= new TestLayoutManager(activity);
recyclerView.setLayoutManager(testLayout);
fragment.onNavigate(Navigable.DIRECTION_DOWN);
assertThat(shadowRecyclerView.getScrollPosition()).isEqualTo(3);
shadowRecyclerView.getScrollListener()
.onScrollStateChanged(recyclerView, RecyclerView.SCROLL_STATE_IDLE);
testLayout.firstVisiblePosition = 3;
fragment.onNavigate(Navigable.DIRECTION_UP);
assertThat(shadowRecyclerView.getScrollPosition()).isEqualTo(0);
testLayout.firstVisiblePosition = 0;
fragment.onNavigate(Navigable.DIRECTION_RIGHT);
assertThat(shadowRecyclerView.getScrollPosition()).isEqualTo(1);
shadowRecyclerView.getScrollListener()
.onScrollStateChanged(recyclerView, RecyclerView.SCROLL_STATE_IDLE);
testLayout.firstVisiblePosition = 1;
fragment.onNavigate(Navigable.DIRECTION_LEFT);
assertThat(shadowRecyclerView.getScrollPosition()).isEqualTo(0);
shadowRecyclerView.getScrollListener()
.onScrollStateChanged(recyclerView, RecyclerView.SCROLL_STATE_IDLE);
}
@After
public void tearDown() {
reset(hackerNewsClient);
controller.pause().stop().destroy();
}
private void assertSinglePage() {
assertEquals(activity.getString(R.string.pref_comment_display_value_single),
Preferences.getCommentDisplayOption(activity));
assertThat(recyclerView.getAdapter()).isInstanceOf(SinglePageItemRecyclerViewAdapter.class);
}
private void assertMultiplePage() {
assertEquals(activity.getString(R.string.pref_comment_display_value_multiple),
Preferences.getCommentDisplayOption(activity));
assertThat(recyclerView.getAdapter()).isInstanceOf(MultiPageItemRecyclerViewAdapter.class);
}
}