/*
* Copyright 2016 Kejun Xia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.shipdream.lib.android.mvc.view.nav;
import android.content.pm.ActivityInfo;
import com.google.gson.Gson;
import com.shipdream.lib.android.mvc.BaseTestCase;
import com.shipdream.lib.android.mvc.Forwarder;
import com.shipdream.lib.android.mvc.Mvc;
import com.shipdream.lib.android.mvc.MvcComponent;
import com.shipdream.lib.android.mvc.NavigationManager;
import com.shipdream.lib.android.mvc.view.injection.controller.ControllerA;
import com.shipdream.lib.android.mvc.view.injection.controller.ControllerB;
import com.shipdream.lib.android.mvc.view.injection.controller.ControllerC;
import com.shipdream.lib.android.mvc.view.injection.controller.ControllerD;
import com.shipdream.lib.poke.Provides;
import com.shipdream.lib.poke.exception.PokeException;
import com.shipdream.lib.poke.exception.ProviderMissingException;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class TestCaseNavigationBasic extends BaseTestCase <MvcTestActivityNavigation> {
private Logger logger = LoggerFactory.getLogger(getClass());
@Inject
private NavigationManager navigationManager;
@Inject
private AnotherController anotherPresenter;
private Comp comp;
private DisposeCheckerA disposeCheckerAMock;
private DisposeCheckerB disposeCheckerBMock;
private DisposeCheckerC disposeCheckerCMock;
private DisposeCheckerD disposeCheckerDMock;
public TestCaseNavigationBasic() {
super(MvcTestActivityNavigation.class);
}
public static class Comp {
TestCaseNavigationBasic testCaseNavigation;
@Singleton
@Provides
public DisposeCheckerA providesDisposeCheckerA() {
return testCaseNavigation.disposeCheckerAMock;
}
@Singleton
@Provides
public DisposeCheckerB providesDisposeCheckerB() {
return testCaseNavigation.disposeCheckerBMock;
}
@Singleton
@Provides
public DisposeCheckerC providesDisposeCheckerC() {
return testCaseNavigation.disposeCheckerCMock;
}
@Singleton
@Provides
public DisposeCheckerD providesDisposeCheckerD() {
return testCaseNavigation.disposeCheckerDMock;
}
}
@Override
protected void prepareDependencies(MvcComponent testComponent) throws Exception {
disposeCheckerAMock = mock(DisposeCheckerA.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
LoggerFactory.getLogger(TestCaseNavigationBasic.class).debug("Dispose checker A");
return null;
}
}).when(disposeCheckerAMock).onDestroy();
disposeCheckerBMock = mock(DisposeCheckerB.class);
disposeCheckerCMock = mock(DisposeCheckerC.class);
disposeCheckerDMock = mock(DisposeCheckerD.class);
comp = new Comp();
comp.testCaseNavigation = this;
try {
Mvc.graph().getRootComponent().unregister(comp);
} catch (ProviderMissingException e) {
e.printStackTrace();
}
Mvc.graph().getRootComponent().register(comp);
}
@Override
protected Class<MvcTestActivityNavigation> getActivityClass() {
return MvcTestActivityNavigation.class;
}
@Override
public void setUp() throws Exception {
super.setUp();
navTo(ControllerA.class);
}
@Override
public void tearDown() throws Exception {
Mvc.graph().getRootComponent().unregister(comp);
super.tearDown();
}
private NavigationManager.Model getNavManagerModel() throws PokeException {
return navigationManager.getModel();
}
@Test
public void testShouldReleaseInjectionsAfterFragmentsArePoppedOut() throws Throwable {
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
verify(disposeCheckerAMock, times(0)).onDestroy();
verify(disposeCheckerBMock, times(0)).onDestroy();
verify(disposeCheckerCMock, times(0)).onDestroy();
verify(disposeCheckerDMock, times(0)).onDestroy();
testNavigateToA();
testNavigateToB();
waitTest();
verify(disposeCheckerAMock, times(0)).onDestroy();
verify(disposeCheckerBMock, times(0)).onDestroy();
verify(disposeCheckerCMock, times(0)).onDestroy();
verify(disposeCheckerDMock, times(0)).onDestroy();
testNavigateToC();
waitTest();
verify(disposeCheckerAMock, times(0)).onDestroy();
verify(disposeCheckerBMock, times(0)).onDestroy();
verify(disposeCheckerCMock, times(0)).onDestroy();
verify(disposeCheckerDMock, times(0)).onDestroy();
testNavigateToD();
waitTest();
verify(disposeCheckerAMock, times(0)).onDestroy();
verify(disposeCheckerBMock, times(0)).onDestroy();
verify(disposeCheckerCMock, times(0)).onDestroy();
verify(disposeCheckerDMock, times(0)).onDestroy();
navigateBackByFragment();
waitTest();
verify(disposeCheckerAMock, times(0)).onDestroy();
verify(disposeCheckerBMock, times(0)).onDestroy();
verify(disposeCheckerCMock, times(0)).onDestroy();
verify(disposeCheckerDMock, times(1)).onDestroy();
//A->B->C
reset(disposeCheckerAMock);
reset(disposeCheckerBMock);
reset(disposeCheckerCMock);
reset(disposeCheckerDMock);
testNavigateToD();
waitTest();
verify(disposeCheckerAMock, times(0)).onDestroy();
verify(disposeCheckerBMock, times(0)).onDestroy();
verify(disposeCheckerCMock, times(0)).onDestroy();
verify(disposeCheckerDMock, times(0)).onDestroy();
//A->B->C->D
reset(disposeCheckerAMock);
reset(disposeCheckerBMock);
reset(disposeCheckerCMock);
reset(disposeCheckerDMock);
navigationManager.navigate(this).back(null);
waitTest();
verify(disposeCheckerAMock, times(0)).onDestroy();
verify(disposeCheckerBMock, times(1)).onDestroy();
verify(disposeCheckerCMock, times(1)).onDestroy();
verify(disposeCheckerDMock, times(1)).onDestroy();
//A
reset(disposeCheckerAMock);
reset(disposeCheckerBMock);
reset(disposeCheckerCMock);
reset(disposeCheckerDMock);
navigateBackByFragment();
verify(disposeCheckerAMock, times(1)).onDestroy();
verify(disposeCheckerBMock, times(0)).onDestroy();
verify(disposeCheckerCMock, times(0)).onDestroy();
verify(disposeCheckerDMock, times(0)).onDestroy();
}
@Test
public void testNavigateAmongFragments() throws Throwable {
waitTest();
testNavigateToB();
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
testNavigateToC();
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
testNavigateToD();
navigateBackByFragment();
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
navigateBackByFragment();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
navigateBackByFragment();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
testNavigateToD();
bringBack(pressHome());
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
testNavigateToA();
testNavigateToD();
testNavigateToA();
testNavigateToB();
testNavigateToA();
testNavigateToC();
navigateBackByFragment();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
navigateBackByFragment();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
bringBack(pressHome());
navigateBackByFragment();
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
navigateBackByFragment();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
}
@Test
public void testNavigateAmongFragmentsClearingHistory() throws Throwable {
testNavigateToA();
testNavigateToD();
testNavigateToC();
testNavigateToB();
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
testNavigateToD();
testNavigateToA();
navTo(ControllerC.class, new Forwarder().clearTo(ControllerB.class));
waitTest();
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
navigateBackByFragment();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
navigateBackByFragment();
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
bringBack(pressHome());
navigateBackByFragment();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
testNavigateToC();
testNavigateToB();
testNavigateToD();
testNavigateToA();
navTo(ControllerC.class, new Forwarder().clearAll());
waitTest();
testNavigateToB();
testNavigateToD();
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
testNavigateToA();
navigateBackByFragment();
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
bringBack(pressHome());
navigateBackByFragment();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
navigateBackByFragment();
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
}
@Test
public void testNavigateBackFragmentsClearingHistory() throws Throwable {
testNavigateToA();
testNavigateToD();
testNavigateToC();
testNavigateToB();
bringBack(pressHome());
testNavigateToD();
testNavigateToA();
navigationManager.navigate(this).back(ControllerB.class);
waitTest();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
bringBack(pressHome());
navigateBackByFragment();
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
testNavigateToC();
testNavigateToB();
testNavigateToD();
bringBack(pressHome());
testNavigateToA();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
testNavigateToB();
testNavigateToD();
testNavigateToA();
navigateBackByFragment();
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
bringBack(pressHome());
navigateBackByFragment();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
}
@Test
public void testShouldRetainAndRestoreNavigateModelCorrectly() throws Throwable {
testNavigateToA();
testNavigateToB();
testNavigateToC();
NavigationManager.Model originalModel = getNavManagerModel();
bringBack(pressHome());
NavigationManager.Model currentModel = getNavManagerModel();
Gson gson = new Gson();
Assert.assertEquals(gson.toJson(originalModel), gson.toJson(currentModel));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
waitTest();
currentModel = getNavManagerModel();
Assert.assertEquals(gson.toJson(originalModel), gson.toJson(currentModel));
bringBack(pressHome());
bringBack(pressHome());
currentModel = getNavManagerModel();
Assert.assertEquals(gson.toJson(originalModel), gson.toJson(currentModel));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
waitTest();
currentModel = getNavManagerModel();
Assert.assertEquals(gson.toJson(originalModel), gson.toJson(currentModel));
bringBack(pressHome());
currentModel = getNavManagerModel();
Assert.assertEquals(gson.toJson(originalModel), gson.toJson(currentModel));
rotateMainActivity(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
bringBack(pressHome());
currentModel = getNavManagerModel();
Assert.assertEquals(gson.toJson(originalModel), gson.toJson(currentModel));
}
@Test
public void test_should_not_push_fragments_to_back_stack_with_interim_nav_location() throws InterruptedException {
navTo(ControllerA.class);
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerB.class, new Forwarder().setInterim(true));
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerC.class);
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
}
@Test
public void test_should_be_able_to_skip_interim_item_with_clear_history_nav_location() throws InterruptedException {
getInstrumentation().waitForIdleSync();
navTo(ControllerA.class);
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerB.class);
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerC.class, new Forwarder().setInterim(true));
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerD.class, new Forwarder().clearTo(ControllerB.class));
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
}
@Test
public void test_should_pass_nav_location_when_clear_history_land_on_interim_location() throws InterruptedException {
navTo(ControllerA.class);
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerB.class);
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerC.class, new Forwarder().setInterim(true));
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerD.class, new Forwarder().clearTo(ControllerC.class));
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
}
@Test
public void test_nav_back_from_interim_location_should_be_same_as_from_non_interim_locaiton() throws InterruptedException {
navTo(ControllerA.class);
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerB.class);
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerC.class, new Forwarder().setInterim(true));
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
navTo(ControllerC.class);
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
navigateBackByFragment();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
}
private void testNavigateToA() {
navTo(ControllerA.class);
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
}
private void testNavigateToB() {
navTo(ControllerB.class);
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
}
private void testNavigateToC() {
navTo(ControllerC.class);
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
}
private void testNavigateToD() {
navTo(ControllerD.class);
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
}
}