package io.github.hidroh.materialistic;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.SearchManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.AppBarLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.widget.PopupMenu;
import android.widget.TextView;
import org.junit.After;
import org.junit.Assert;
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.shadows.ShadowAlertDialog;
import org.robolectric.shadows.ShadowContentObserver;
import org.robolectric.shadows.ShadowPopupMenu;
import org.robolectric.shadows.ShadowToast;
import org.robolectric.util.ActivityController;
import java.io.IOException;
import java.util.Set;
import javax.inject.Inject;
import io.github.hidroh.materialistic.accounts.UserServices;
import io.github.hidroh.materialistic.data.FavoriteManager;
import io.github.hidroh.materialistic.data.LocalItemManager;
import io.github.hidroh.materialistic.data.MaterialisticProvider;
import io.github.hidroh.materialistic.data.TestFavorite;
import io.github.hidroh.materialistic.data.TestHnItem;
import io.github.hidroh.materialistic.data.WebItem;
import io.github.hidroh.materialistic.test.TestFavoriteActivity;
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.suite.SlowTest;
import static io.github.hidroh.materialistic.test.shadow.CustomShadows.customShadowOf;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.assertj.android.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
@Category(SlowTest.class)
@Config(shadows = {ShadowRecyclerViewAdapter.class})
@RunWith(TestRunner.class)
public class FavoriteActivityTest {
private ActivityController<TestFavoriteActivity> controller;
private TestFavoriteActivity activity;
private RecyclerView.Adapter adapter;
private RecyclerView recyclerView;
private Fragment fragment;
@Inject FavoriteManager favoriteManager;
@Inject ActionViewResolver actionViewResolver;
@Inject UserServices userServices;
@Inject KeyDelegate keyDelegate;
@Captor ArgumentCaptor<Set<String>> selection;
@Captor ArgumentCaptor<View.OnClickListener> searchViewClickListener;
@Captor ArgumentCaptor<SearchView.OnCloseListener> searchViewCloseListener;
@Captor ArgumentCaptor<UserServices.Callback> userServicesCallback;
@Captor ArgumentCaptor<LocalItemManager.Observer> observerCaptor;
private ShadowRecyclerViewAdapter shadowAdapter;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
TestApplication.applicationGraph.inject(this);
reset(favoriteManager);
reset(userServices);
reset(keyDelegate);
reset(actionViewResolver.getActionView(mock(MenuItem.class)));
controller = Robolectric.buildActivity(TestFavoriteActivity.class);
when(favoriteManager.getSize()).thenReturn(2);
when(favoriteManager.getItem(eq(0))).thenReturn(new TestFavorite(
"1", "http://example.com", "title", System.currentTimeMillis()));
when(favoriteManager.getItem(eq(1))).thenReturn(new TestFavorite(
"2", "http://example.com", "ask HN", System.currentTimeMillis()));
activity = controller.create().postCreate(null).start().resume().visible().get(); // skip menu due to search view
recyclerView = (RecyclerView) activity.findViewById(R.id.recycler_view);
adapter = recyclerView.getAdapter();
shadowAdapter = customShadowOf(adapter);
fragment = activity.getSupportFragmentManager().findFragmentById(android.R.id.list);
verify(keyDelegate).attach(any(Activity.class));
verify(favoriteManager).attach(any(Context.class), any(LoaderManager.class),
observerCaptor.capture(), any());
}
@Test
public void testNewIntent() {
when(favoriteManager.getSize()).thenReturn(1);
controller.newIntent(new Intent().putExtra(SearchManager.QUERY, "title"));
assertEquals(1, adapter.getItemCount());
controller.newIntent(new Intent()); // should not clear filter
assertEquals(1, adapter.getItemCount());
}
@Test
public void testOptionsMenuClear() {
assertTrue(shadowOf(activity).getOptionsMenu().findItem(R.id.menu_clear).isVisible());
shadowOf(activity).clickMenuItem(R.id.menu_clear);
AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
assertEquals(2, adapter.getItemCount());
shadowOf(activity).clickMenuItem(R.id.menu_clear);
dialog = ShadowAlertDialog.getLatestAlertDialog();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
verify(favoriteManager).clear(any(Context.class), any());
when(favoriteManager.getSize()).thenReturn(0);
observerCaptor.getValue().onChanged();
assertEquals(0, adapter.getItemCount());
}
@Test
public void testSearchView() {
SearchView searchView = (SearchView) actionViewResolver.getActionView(mock(MenuItem.class));
verify(searchView, atLeastOnce()).setOnSearchClickListener(searchViewClickListener.capture());
verify(searchView, atLeastOnce()).setOnCloseListener(searchViewCloseListener.capture());
searchViewClickListener.getAllValues()
.get(searchViewClickListener.getAllValues().size() - 1)
.onClick(searchView);
assertFalse(((FavoriteFragment) fragment).startActionMode(null));
SearchView.OnCloseListener closeListener = searchViewCloseListener.getAllValues()
.get(searchViewCloseListener.getAllValues().size() - 1);
closeListener.onClose();
assertEquals(2, adapter.getItemCount());
((FavoriteFragment) fragment).filter("ask");
verify(favoriteManager, times(2)).attach(any(Context.class), any(LoaderManager.class),
observerCaptor.capture(), any());
when(favoriteManager.getSize()).thenReturn(1);
when(favoriteManager.getItem(eq(0))).thenReturn(new TestFavorite(
"2", "http://example.com", "ask HN", System.currentTimeMillis()));
observerCaptor.getValue().onChanged();
assertEquals(1, adapter.getItemCount());
reset(searchView);
closeListener.onClose();
verify(searchView).setQuery(eq(FavoriteActivity.EMPTY_QUERY), eq(true));
}
@Test
public void testItemClick() {
assertEquals(2, adapter.getItemCount());
RecyclerView.ViewHolder holder = shadowAdapter.getViewHolder(0);
holder.itemView.performClick();
assertNotNull(shadowOf(activity).getNextStartedActivity());
}
@Test
public void testActionMode() {
RecyclerView.ViewHolder holder = shadowAdapter.getViewHolder(0);
holder.itemView.performLongClick();
assertNotNull(activity.actionModeCallback);
}
@Test
public void testSelectionToggle() {
RecyclerView.ViewHolder holder = shadowAdapter.getViewHolder(0);
holder.itemView.performLongClick();
holder.itemView.performClick();
assertNull(shadowOf(activity).getNextStartedActivity());
holder.itemView.performClick();
assertNull(shadowOf(activity).getNextStartedActivity());
}
@Test
public void testDelete() {
RecyclerView.ViewHolder holder = shadowAdapter.getViewHolder(0);
holder.itemView.performLongClick();
ActionMode actionMode = mock(ActionMode.class);
activity.actionModeCallback.onActionItemClicked(actionMode, new RoboMenuItem(R.id.menu_clear));
AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
assertEquals(2, adapter.getItemCount());
activity.actionModeCallback.onActionItemClicked(actionMode, new RoboMenuItem(R.id.menu_clear));
dialog = ShadowAlertDialog.getLatestAlertDialog();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
verify(favoriteManager).remove(any(Context.class), selection.capture());
assertThat(selection.getValue()).contains("1");
verify(actionMode).finish();
when(favoriteManager.getSize()).thenReturn(1);
observerCaptor.getValue().onChanged();
assertEquals(1, adapter.getItemCount());
}
@Config(shadows = {ShadowRecyclerView.class, ShadowItemTouchHelper.class})
@Test
public void testSwipeToDelete() {
RecyclerView.ViewHolder holder = shadowAdapter.getViewHolder(0);
customShadowOf(recyclerView).getItemTouchHelperCallback()
.onSwiped(holder, ItemTouchHelper.LEFT);
verify(favoriteManager).remove(any(Context.class), anyCollection());
when(favoriteManager.getSize()).thenReturn(1);
observerCaptor.getValue().onChanged();
assertEquals(1, adapter.getItemCount());
assertThat((TextView) activity.findViewById(R.id.snackbar_text))
.isNotNull()
.containsText(R.string.toast_removed);
activity.findViewById(R.id.snackbar_action).performClick();
verify(favoriteManager).add(any(Context.class), any(WebItem.class));
when(favoriteManager.getSize()).thenReturn(2);
observerCaptor.getValue().onChanged();
assertEquals(2, adapter.getItemCount());
}
@Test
public void testExport() {
shadowOf(activity).clickMenuItem(R.id.menu_export);
verify(favoriteManager).export(any(Context.class), any());
}
@Test
public void testFilter() {
assertTrue(shadowOf(activity).getOptionsMenu().findItem(R.id.menu_search).isVisible());
Intent intent = new Intent();
intent.putExtra(SearchManager.QUERY, "blah");
controller.newIntent(intent);
verify(favoriteManager).attach(any(Context.class), any(LoaderManager.class),
any(LocalItemManager.Observer.class), eq("blah"));
intent = new Intent();
intent.putExtra(SearchManager.QUERY, "ask");
controller.newIntent(intent);
verify(favoriteManager).attach(any(Context.class), any(LoaderManager.class),
any(LocalItemManager.Observer.class), eq("ask"));
}
@Test
public void testSaveState() {
Bundle outState = new Bundle();
controller.saveInstanceState(outState);
ActivityController<TestFavoriteActivity> controller = Robolectric
.buildActivity(TestFavoriteActivity.class)
.create(outState)
.postCreate(outState)
.start()
.resume()
.visible();
assertEquals(2, ((RecyclerView) controller.get().findViewById(R.id.recycler_view))
.getAdapter().getItemCount());
controller.pause().stop().destroy();
reset(keyDelegate);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Test
public void testVoteItem() {
shadowAdapter.getViewHolder(0).itemView.findViewById(R.id.button_more).performClick();
PopupMenu popupMenu = ShadowPopupMenu.getLatestPopupMenu();
Assert.assertNotNull(popupMenu);
shadowOf(popupMenu).getOnMenuItemClickListener()
.onMenuItemClick(new RoboMenuItem(R.id.menu_contextual_vote));
verify(userServices).voteUp(any(Context.class), any(), userServicesCallback.capture());
userServicesCallback.getValue().onDone(true);
assertEquals(activity.getString(R.string.voted), ShadowToast.getTextOfLatestToast());
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Test
public void testVoteItemPromptToLogin() {
shadowAdapter.getViewHolder(0).itemView.findViewById(R.id.button_more).performClick();
PopupMenu popupMenu = ShadowPopupMenu.getLatestPopupMenu();
Assert.assertNotNull(popupMenu);
shadowOf(popupMenu).getOnMenuItemClickListener()
.onMenuItemClick(new RoboMenuItem(R.id.menu_contextual_vote));
verify(userServices).voteUp(any(Context.class), any(), userServicesCallback.capture());
userServicesCallback.getValue().onDone(false);
assertThat(shadowOf(activity).getNextStartedActivity())
.hasComponent(activity, LoginActivity.class);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Test
public void testVoteItemFailed() {
shadowAdapter.getViewHolder(0).itemView.findViewById(R.id.button_more).performClick();
PopupMenu popupMenu = ShadowPopupMenu.getLatestPopupMenu();
Assert.assertNotNull(popupMenu);
shadowOf(popupMenu).getOnMenuItemClickListener()
.onMenuItemClick(new RoboMenuItem(R.id.menu_contextual_vote));
verify(userServices).voteUp(any(Context.class), any(), userServicesCallback.capture());
userServicesCallback.getValue().onError(new IOException());
assertEquals(activity.getString(R.string.vote_failed), ShadowToast.getTextOfLatestToast());
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Test
public void testReply() {
shadowAdapter.getViewHolder(0).itemView.findViewById(R.id.button_more).performClick();
PopupMenu popupMenu = ShadowPopupMenu.getLatestPopupMenu();
Assert.assertNotNull(popupMenu);
shadowOf(popupMenu).getOnMenuItemClickListener()
.onMenuItemClick(new RoboMenuItem(R.id.menu_contextual_comment));
assertThat(shadowOf(activity).getNextStartedActivity())
.hasComponent(activity, ComposeActivity.class);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Test
public void testShare() {
TestApplication.addResolver(new Intent(Intent.ACTION_SEND));
shadowAdapter.getViewHolder(0).itemView.findViewById(R.id.button_more).performClick();
PopupMenu popupMenu = ShadowPopupMenu.getLatestPopupMenu();
Assert.assertNotNull(popupMenu);
shadowOf(popupMenu).getOnMenuItemClickListener()
.onMenuItemClick(new RoboMenuItem(R.id.menu_contextual_share));
assertThat(shadowOf(activity).getNextStartedActivity())
.hasAction(Intent.ACTION_SEND);
}
@Test
public void testRemoveClearSelection() {
ShadowContentObserver observer = shadowOf(shadowOf(activity.getContentResolver())
.getContentObservers(MaterialisticProvider.URI_FAVORITE)
.iterator()
.next());
observer.dispatchChange(false, MaterialisticProvider.URI_FAVORITE
.buildUpon()
.appendPath("remove")
.appendPath("3")
.build());
assertNull(activity.getSelectedItem());
activity.onItemSelected(new TestHnItem(1L));
observer.dispatchChange(false, MaterialisticProvider.URI_FAVORITE
.buildUpon()
.appendPath("remove")
.appendPath("2")
.build());
assertNotNull(activity.getSelectedItem());
observer.dispatchChange(false, MaterialisticProvider.URI_FAVORITE
.buildUpon()
.appendPath("remove")
.appendPath("1")
.build());
assertNull(activity.getSelectedItem());
}
@Test
public void testClearClearSelection() {
activity.onItemSelected(new TestHnItem(1L));
shadowOf(shadowOf(activity.getContentResolver())
.getContentObservers(MaterialisticProvider.URI_FAVORITE)
.iterator()
.next())
.dispatchChange(false, MaterialisticProvider.URI_FAVORITE
.buildUpon()
.appendPath("clear")
.build());
assertNull(activity.getSelectedItem());
}
@Test
public void testVolumeNavigation() {
activity.onKeyDown(KeyEvent.KEYCODE_VOLUME_UP,
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP));
verify(keyDelegate).setScrollable(any(Scrollable.class), any(AppBarLayout.class));
verify(keyDelegate).onKeyDown(anyInt(), any(KeyEvent.class));
activity.onKeyUp(KeyEvent.KEYCODE_VOLUME_UP,
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP));
verify(keyDelegate).onKeyUp(anyInt(), any(KeyEvent.class));
activity.onKeyLongPress(KeyEvent.KEYCODE_VOLUME_UP,
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP));
verify(keyDelegate).onKeyLongPress(anyInt(), any(KeyEvent.class));
}
@After
public void tearDown() {
controller.pause().stop();
verify(keyDelegate).detach(any(Activity.class));
controller.destroy();
}
}