package io.github.hidroh.materialistic; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.TextView; import org.assertj.android.api.Assertions; 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.annotation.Config; import org.robolectric.fakes.RoboMenuItem; import org.robolectric.internal.ShadowExtractor; import org.robolectric.shadows.ShadowToast; import org.robolectric.util.ActivityController; import javax.inject.Inject; import javax.inject.Named; import io.github.hidroh.materialistic.data.HackerNewsClient; 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.ListActivity; import io.github.hidroh.materialistic.test.TestItem; import io.github.hidroh.materialistic.test.TestRunner; import io.github.hidroh.materialistic.test.shadow.ShadowPreferenceFragmentCompat; import io.github.hidroh.materialistic.test.shadow.ShadowSnackbar; import io.github.hidroh.materialistic.test.shadow.ShadowSwipeRefreshLayout; import io.github.hidroh.materialistic.test.suite.SlowTest; import io.github.hidroh.materialistic.widget.ListRecyclerViewAdapter; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static org.assertj.android.support.v4.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; @Category(SlowTest.class) @Config(shadows = {ShadowSwipeRefreshLayout.class, ShadowSnackbar.class, ShadowPreferenceFragmentCompat.class}) @RunWith(TestRunner.class) public class ListFragmentTest { private ActivityController<ListActivity> controller; private ListActivity activity; @Inject @Named(ActivityModule.HN) ItemManager itemManager; @Captor ArgumentCaptor<ResponseListener<Item[]>> listener; @Before public void setUp() { MockitoAnnotations.initMocks(this); TestApplication.applicationGraph.inject(this); reset(itemManager); controller = Robolectric.buildActivity(ListActivity.class) .create().postCreate(null).start().resume().visible(); activity = controller.get(); } @Test public void testOnCreate() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args)) .commit(); assertThat((SwipeRefreshLayout) activity.findViewById(R.id.swipe_layout)).isRefreshing(); controller.pause().stop().destroy(); } @Test public void testRefresh() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args)) .commit(); ShadowSwipeRefreshLayout shadowSwipeRefreshLayout = (ShadowSwipeRefreshLayout) ShadowExtractor.extract(activity.findViewById(R.id.swipe_layout)); shadowSwipeRefreshLayout.getOnRefreshListener().onRefresh(); // should trigger another data request verify(itemManager).getStories(any(String.class), eq(ItemManager.MODE_DEFAULT), any(ResponseListener.class)); verify(itemManager).getStories(any(String.class), eq(ItemManager.MODE_NETWORK), any(ResponseListener.class)); controller.pause().stop().destroy(); } @Test public void testHighlightNewItems() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args)) .commit(); verify(itemManager).getStories(any(), eq(ItemManager.MODE_DEFAULT), listener.capture()); listener.getValue().onResponse(new Item[]{new TestItem() { @Override public String getId() { return "1"; } }}); reset(itemManager); ShadowSwipeRefreshLayout shadowSwipeRefreshLayout = (ShadowSwipeRefreshLayout) ShadowExtractor.extract(activity.findViewById(R.id.swipe_layout)); shadowSwipeRefreshLayout.getOnRefreshListener().onRefresh(); // should trigger another data request verify(itemManager).getStories(any(String.class), eq(ItemManager.MODE_NETWORK), listener.capture()); listener.getValue().onResponse(new Item[]{ new TestHnItem(1L), new TestHnItem(2L) }); assertEquals(2, ((RecyclerView) activity.findViewById(R.id.recycler_view)).getAdapter().getItemCount()); View snackbarView = ShadowSnackbar.getLatestView(); Assertions.assertThat((TextView) snackbarView.findViewById(R.id.snackbar_text)) .isNotNull() .containsText(activity.getResources().getQuantityString(R.plurals.new_stories_count, 1, 1)); snackbarView.findViewById(R.id.snackbar_action).performClick(); assertEquals(1, ((RecyclerView) activity.findViewById(R.id.recycler_view)).getAdapter().getItemCount()); snackbarView = ShadowSnackbar.getLatestView(); Assertions.assertThat((TextView) snackbarView.findViewById(R.id.snackbar_text)) .isNotNull() .containsText(activity.getResources().getQuantityString(R.plurals.showing_new_stories, 1, 1)); snackbarView.findViewById(R.id.snackbar_action).performClick(); assertEquals(2, ((RecyclerView) activity.findViewById(R.id.recycler_view)).getAdapter().getItemCount()); controller.pause().stop().destroy(); } @Test public void testConfigurationChanged() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); Fragment fragment = Fragment.instantiate(activity, ListFragment.class.getName(), args); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, fragment) .commit(); verify(itemManager).getStories(any(), eq(ItemManager.MODE_DEFAULT), listener.capture()); listener.getValue().onResponse(new Item[]{new TestItem() { }}); reset(itemManager); Bundle state = new Bundle(); fragment.onSaveInstanceState(state); fragment.onActivityCreated(state); // should not trigger another data request verify(itemManager, never()).getStories(any(String.class), eq(ItemManager.MODE_DEFAULT), any(ResponseListener.class)); controller.pause().stop().destroy(); } @Test public void testEmptyResponse() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args)) .commit(); verify(itemManager).getStories(any(), eq(ItemManager.MODE_DEFAULT), listener.capture()); listener.getValue().onResponse(new Item[0]); assertThat((SwipeRefreshLayout) activity.findViewById(R.id.swipe_layout)).isNotRefreshing(); Assertions.assertThat(activity.findViewById(R.id.empty)).isNotVisible(); controller.pause().stop().destroy(); } @Test public void testErrorResponse() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args)) .commit(); verify(itemManager).getStories(any(), eq(ItemManager.MODE_DEFAULT), listener.capture()); listener.getValue().onError(null); assertThat((SwipeRefreshLayout) activity.findViewById(R.id.swipe_layout)).isNotRefreshing(); Assertions.assertThat(activity.findViewById(R.id.empty)).isVisible(); controller.pause().stop().destroy(); } @Test public void testRefreshError() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args)) .commit(); verify(itemManager).getStories(any(), eq(ItemManager.MODE_DEFAULT), listener.capture()); listener.getValue().onResponse(new Item[]{new TestItem() {}}); Assertions.assertThat(activity.findViewById(R.id.empty)).isNotVisible(); reset(itemManager); ShadowSwipeRefreshLayout shadowSwipeRefreshLayout = (ShadowSwipeRefreshLayout) ShadowExtractor.extract(activity.findViewById(R.id.swipe_layout)); shadowSwipeRefreshLayout.getOnRefreshListener().onRefresh(); verify(itemManager).getStories(any(), eq(ItemManager.MODE_NETWORK), listener.capture()); listener.getValue().onError(null); Assertions.assertThat(activity.findViewById(R.id.empty)).isNotVisible(); assertNotNull(ShadowToast.getLatestToast()); controller.pause().stop().destroy(); } @Test public void testErrorWhenDetached() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args)) .commit(); verify(itemManager).getStories(any(), eq(ItemManager.MODE_DEFAULT), listener.capture()); controller.pause().stop().destroy(); listener.getValue().onError(null); // no exception } @Test public void testResponseWhenDetached() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args)) .commit(); verify(itemManager).getStories(any(), eq(ItemManager.MODE_DEFAULT), listener.capture()); controller.pause().stop().destroy(); listener.getValue().onResponse(null); // no exception } @Test public void testLayoutToggle() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args)) .commit(); assertCompactView(); PreferenceManager.getDefaultSharedPreferences(activity) .edit() .putBoolean(activity.getString(R.string.pref_list_item_view), true) .apply(); assertCardView(); PreferenceManager.getDefaultSharedPreferences(activity) .edit() .putBoolean(activity.getString(R.string.pref_list_item_view), false) .apply(); assertCompactView(); controller.pause().stop().destroy(); } @Test public void testTogglePreferenceChange() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args), ListFragment.class.getName()) .commit(); assertCompactView(); controller.pause(); PreferenceManager.getDefaultSharedPreferences(activity) .edit() .putBoolean(activity.getString(R.string.pref_list_item_view), true) .apply(); controller.resume().postResume(); activity.getSupportFragmentManager().findFragmentByTag(ListFragment.class.getName()) .onPrepareOptionsMenu(shadowOf(activity).getOptionsMenu()); assertCardView(); controller.pause().stop().destroy(); } @Test public void testListMenu() { Bundle args = new Bundle(); args.putString(ListFragment.EXTRA_ITEM_MANAGER, HackerNewsClient.class.getName()); args.putString(ListFragment.EXTRA_FILTER, ItemManager.TOP_FETCH_MODE); activity.getSupportFragmentManager() .beginTransaction() .add(android.R.id.list, Fragment.instantiate(activity, ListFragment.class.getName(), args), ListFragment.class.getName()) .commit(); activity.getSupportFragmentManager().findFragmentByTag(ListFragment.class.getName()) .onOptionsItemSelected(new RoboMenuItem(R.id.menu_list)); assertThat(activity.getSupportFragmentManager()) .hasFragmentWithTag(PopupSettingsFragment.class.getName()); } private void assertCardView() { assertThat(((ListRecyclerViewAdapter) ((RecyclerView) activity.findViewById(R.id.recycler_view)).getAdapter()) .isCardViewEnabled()) .isTrue(); } private void assertCompactView() { assertThat(((ListRecyclerViewAdapter) ((RecyclerView) activity.findViewById(R.id.recycler_view)).getAdapter()) .isCardViewEnabled()) .isFalse(); } }