package io.github.hidroh.materialistic; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; 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.internal.ShadowExtractor; import org.robolectric.shadows.ShadowNetworkInfo; import org.robolectric.util.ActivityController; import javax.inject.Inject; import javax.inject.Named; 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.data.WebItem; import io.github.hidroh.materialistic.test.TestItem; import io.github.hidroh.materialistic.test.TestRunner; import io.github.hidroh.materialistic.test.shadow.CustomShadows; import io.github.hidroh.materialistic.test.shadow.ShadowPreferenceFragmentCompat; import io.github.hidroh.materialistic.test.shadow.ShadowRecyclerViewAdapter; import io.github.hidroh.materialistic.test.shadow.ShadowSwipeRefreshLayout; import io.github.hidroh.materialistic.test.suite.SlowTest; import static junit.framework.Assert.assertEquals; import static org.assertj.android.api.Assertions.assertThat; import static org.assertj.android.support.v4.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @SuppressWarnings("ConstantConditions") @Category(SlowTest.class) @Config(shadows = {ShadowSwipeRefreshLayout.class, ShadowRecyclerViewAdapter.class, ShadowPreferenceFragmentCompat.class}) @RunWith(TestRunner.class) public class ItemFragmentMultiPageTest { @Inject @Named(ActivityModule.HN) ItemManager hackerNewsClient; @Captor ArgumentCaptor<ResponseListener<Item>> listener; private ActivityController<TestItemActivity> controller; private TestItemActivity activity; @Before public void setUp() { MockitoAnnotations.initMocks(this); TestApplication.applicationGraph.inject(this); reset(hackerNewsClient); shadowOf((ConnectivityManager) RuntimeEnvironment.application .getSystemService(Context.CONNECTIVITY_SERVICE)) .setActiveNetworkInfo(ShadowNetworkInfo.newInstance(null, ConnectivityManager.TYPE_WIFI, 0, true, true)); PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application) .edit() .putString(RuntimeEnvironment.application.getString(R.string.pref_comment_display), RuntimeEnvironment.application.getString(R.string.pref_comment_display_value_multiple)) .putBoolean(RuntimeEnvironment.application.getString(R.string.pref_lazy_load), false) .apply(); controller = Robolectric.buildActivity(TestItemActivity.class); activity = controller.create().start().resume().get(); } @Test public void testEmptyView() { Bundle args = new Bundle(); args.putParcelable(ItemFragment.EXTRA_ITEM, new TestItem() { }); Fragment fragment = Fragment.instantiate(RuntimeEnvironment.application, ItemFragment.class.getName(), args); makeVisible(fragment); assertThat(fragment.getView().findViewById(R.id.empty)).isVisible(); } @Test public void testWebItem() { WebItem webItem = mock(WebItem.class); when(webItem.getId()).thenReturn("1"); Bundle args = new Bundle(); args.putParcelable(ItemFragment.EXTRA_ITEM, webItem); Fragment fragment = Fragment.instantiate(RuntimeEnvironment.application, ItemFragment.class.getName(), args); makeVisible(fragment); verify(hackerNewsClient).getItem(eq("1"), eq(ItemManager.MODE_DEFAULT), listener.capture()); listener.getValue().onResponse(new TestItem() { @Override public Item[] getKidItems() { return new Item[]{new TestItem() { }}; } @Override public int getKidCount() { return 1; } }); assertThat(fragment.getView().findViewById(R.id.empty)).isNotVisible(); } @Test public void testBindLocalKidData() { Item story = new TestHnItem(0L); story.populate(new TestItem() { @Override public int getDescendants() { return 1; } @Override public long[] getKids() { return new long[]{1L}; } }); story.getKidItems()[0].populate(new TestItem() { @Override public String getText() { return "text"; } @Override public long[] getKids() { return new long[]{2L}; } @Override public int getDescendants() { return 1; } }); Bundle args = new Bundle(); args.putParcelable(ItemFragment.EXTRA_ITEM, story); Fragment fragment = Fragment.instantiate(RuntimeEnvironment.application, ItemFragment.class.getName(), args); makeVisible(fragment); assertThat(fragment.getView().findViewById(R.id.empty)).isNotVisible(); RecyclerView recyclerView = (RecyclerView) fragment.getView().findViewById(R.id.recycler_view); RecyclerView.ViewHolder viewHolder = CustomShadows.customShadowOf(recyclerView.getAdapter()) .getViewHolder(0); assertThat((TextView) viewHolder.itemView.findViewById(R.id.text)).hasTextString("text"); assertThat(viewHolder.itemView.findViewById(R.id.comment)).isVisible(); viewHolder.itemView.findViewById(R.id.comment).performClick(); Intent actual = shadowOf(fragment.getActivity()).getNextStartedActivity(); assertEquals(ItemActivity.class.getName(), actual.getComponent().getClassName()); assertThat(actual).hasExtra(ItemActivity.EXTRA_OPEN_COMMENTS, true); } @Test public void testBindRemoteKidData() { Bundle args = new Bundle(); Item item = new TestHnItem(2L); item.populate(new TestHnItem(2L) { @Override public long[] getKids() { return new long[]{1L}; } }); args.putParcelable(ItemFragment.EXTRA_ITEM, item); Fragment fragment = Fragment.instantiate(RuntimeEnvironment.application, ItemFragment.class.getName(), args); makeVisible(fragment); verify(hackerNewsClient).getItem(eq("1"), eq(ItemManager.MODE_DEFAULT), listener.capture()); listener.getValue().onResponse(new TestHnItem(1L) { @Override public String getTitle() { return "title"; } }); assertEquals(1, item.getKidItems()[0].getLocalRevision()); assertEquals("title", item.getKidItems()[0].getTitle()); } @Test public void testRefresh() { WebItem webItem = mock(WebItem.class); when(webItem.getId()).thenReturn("1"); Bundle args = new Bundle(); args.putParcelable(ItemFragment.EXTRA_ITEM, webItem); Fragment fragment = Fragment.instantiate(RuntimeEnvironment.application, ItemFragment.class.getName(), args); makeVisible(fragment); ShadowSwipeRefreshLayout shadowSwipeRefreshLayout = (ShadowSwipeRefreshLayout) ShadowExtractor.extract(fragment.getView().findViewById(R.id.swipe_layout)); shadowSwipeRefreshLayout.getOnRefreshListener().onRefresh(); verify(hackerNewsClient).getItem(eq("1"), eq(ItemManager.MODE_DEFAULT), listener.capture()); verify(hackerNewsClient).getItem(eq("1"), eq(ItemManager.MODE_NETWORK), listener.capture()); listener.getAllValues().get(1).onResponse(new TestHnItem(1L)); assertThat((SwipeRefreshLayout) fragment.getView().findViewById(R.id.swipe_layout)) .isNotRefreshing(); verify(((TestItemActivity) fragment.getActivity()).itemChangedListener) .onItemChanged(any(Item.class)); } @Test public void testRefreshFailed() { WebItem webItem = mock(WebItem.class); when(webItem.getId()).thenReturn("1"); Bundle args = new Bundle(); args.putParcelable(ItemFragment.EXTRA_ITEM, webItem); Fragment fragment = Fragment.instantiate(RuntimeEnvironment.application, ItemFragment.class.getName(), args); makeVisible(fragment); ShadowSwipeRefreshLayout shadowSwipeRefreshLayout = (ShadowSwipeRefreshLayout) ShadowExtractor.extract(fragment.getView().findViewById(R.id.swipe_layout)); shadowSwipeRefreshLayout.getOnRefreshListener().onRefresh(); verify(hackerNewsClient).getItem(eq("1"), eq(ItemManager.MODE_DEFAULT), listener.capture()); verify(hackerNewsClient).getItem(eq("1"), eq(ItemManager.MODE_NETWORK), listener.capture()); listener.getAllValues().get(1).onError(null); assertThat((SwipeRefreshLayout) fragment.getView().findViewById(R.id.swipe_layout)) .isNotRefreshing(); } @Test public void testDisplayMenu() { WebItem webItem = mock(WebItem.class); when(webItem.getId()).thenReturn("1"); Bundle args = new Bundle(); args.putParcelable(ItemFragment.EXTRA_ITEM, webItem); Fragment fragment = Fragment.instantiate(RuntimeEnvironment.application, ItemFragment.class.getName(), args); makeVisible(fragment); fragment.onOptionsItemSelected(new RoboMenuItem(R.id.menu_comments)); assertThat(fragment.getFragmentManager()) .hasFragmentWithTag(PopupSettingsFragment.class.getName()); } @After public void tearDown() { reset(hackerNewsClient); controller.pause().stop().destroy(); } private void makeVisible(Fragment fragment) { activity.getSupportFragmentManager().beginTransaction() .add(R.id.content_frame, fragment, "tag") .commit(); View view = activity.findViewById(R.id.recycler_view); view.measure(0, 0); view.layout(0, 0, 100, 1000); } public static class TestItemActivity extends InjectableActivity implements ItemFragment.ItemChangedListener { ItemFragment.ItemChangedListener itemChangedListener = mock(ItemFragment.ItemChangedListener.class); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_item); setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); } @Override public void onItemChanged(@NonNull Item item) { itemChangedListener.onItemChanged(item); } } }