/*
* 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.support.v4.app.FragmentManager;
import com.shipdream.lib.android.mvc.BaseTestCase;
import com.shipdream.lib.android.mvc.Forwarder;
import com.shipdream.lib.android.mvc.MvcComponent;
import com.shipdream.lib.android.mvc.NavigationManager;
import com.shipdream.lib.android.mvc.Preparer;
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 junit.framework.Assert;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Random;
import javax.inject.Inject;
import javax.inject.Singleton;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
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 TestCaseNavigationFromController extends BaseTestCase<MvcTestActivityNavigation> {
private Logger logger = LoggerFactory.getLogger(getClass());
@Inject
private NavigationManager navigationManager;
private Comp modudle;
private DisposeCheckerE disposeCheckerEMock;
private DisposeCheckerF disposeCheckerFMock;
private DisposeCheckerG disposeCheckerGMock;
public TestCaseNavigationFromController() {
super(MvcTestActivityNavigation.class);
}
public static class Comp {
TestCaseNavigationFromController testCaseNavigation;
@Singleton
@Provides
public DisposeCheckerE providesDisposeCheckerE() {
return testCaseNavigation.disposeCheckerEMock;
}
@Singleton
@Provides
public DisposeCheckerF providesDisposeCheckerF() {
return testCaseNavigation.disposeCheckerFMock;
}
@Singleton
@Provides
public DisposeCheckerG providesDisposeCheckerG() {
return testCaseNavigation.disposeCheckerGMock;
}
}
@Override
protected void prepareDependencies(MvcComponent testComponent) throws Exception {
disposeCheckerEMock = mock(DisposeCheckerE.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
logger.debug("Dispose checker E");
return null;
}
}).when(disposeCheckerEMock).onDestroy();
disposeCheckerFMock = mock(DisposeCheckerF.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
logger.debug("Dispose checker F");
return null;
}
}).when(disposeCheckerFMock).onDestroy();
disposeCheckerGMock = mock(DisposeCheckerG.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
logger.debug("Dispose checker G");
return null;
}
}).when(disposeCheckerGMock).onDestroy();
modudle = new Comp();
modudle.testCaseNavigation = this;
testComponent.register(modudle);
}
@Override
protected Class<MvcTestActivityNavigation> getActivityClass() {
return MvcTestActivityNavigation.class;
}
@Override
public void setUp() throws Exception {
super.setUp();
navTo(ControllerA.class);
}
@Test
public void test_back_navigation_should_skip_interim_location() throws Throwable {
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
assertFragmentsCount(1);
navigationManager.navigate(this).to(ControllerB.class, new Forwarder().setInterim(true));
waitTest();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
assertFragmentsCount(2);
navigationManager.navigate(this).to(ControllerC.class, new Forwarder().setInterim(true));
waitTest();
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
assertFragmentsCount(3);
navigationManager.navigate(this).to(ControllerD.class);
waitTest();
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
assertFragmentsCount(4);
navigationManager.navigate(this).back();
waitTest();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
onView(withText(NavFragmentB.class.getSimpleName())).check(doesNotExist());
onView(withText(NavFragmentC.class.getSimpleName())).check(doesNotExist());
onView(withText(NavFragmentD.class.getSimpleName())).check(doesNotExist());
assertFragmentsCount(1);
navigationManager.navigate(this).to(ControllerB.class, new Forwarder().setInterim(true));
waitTest();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
assertFragmentsCount(2);
navigationManager.navigate(this).to(ControllerC.class, new Forwarder().setInterim(true));
waitTest();
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
assertFragmentsCount(3);
navigationManager.navigate(this).back();
waitTest();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
onView(withText(NavFragmentB.class.getSimpleName())).check(doesNotExist());
onView(withText(NavFragmentC.class.getSimpleName())).check(doesNotExist());
onView(withText(NavFragmentD.class.getSimpleName())).check(doesNotExist());
assertFragmentsCount(1);
navigationManager.navigate(this).to(ControllerB.class);
waitTest();
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
assertFragmentsCount(2);
navigationManager.navigate(this).to(ControllerC.class, new Forwarder().setInterim(true));
waitTest();
onView(withText(NavFragmentC.class.getSimpleName())).check(matches(isDisplayed()));
assertFragmentsCount(3);
navigationManager.navigate(this).to(ControllerD.class);
waitTest();
onView(withText(NavFragmentD.class.getSimpleName())).check(matches(isDisplayed()));
assertFragmentsCount(4);
navigationManager.navigate(this).back();
waitTest();
onView(withText(NavFragmentA.class.getSimpleName())).check(doesNotExist());
onView(withText(NavFragmentB.class.getSimpleName())).check(matches(isDisplayed()));
onView(withText(NavFragmentC.class.getSimpleName())).check(doesNotExist());
onView(withText(NavFragmentD.class.getSimpleName())).check(doesNotExist());
assertFragmentsCount(2);
navigationManager.navigate(this).back();
waitTest();
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
onView(withText(NavFragmentB.class.getSimpleName())).check(doesNotExist());
onView(withText(NavFragmentC.class.getSimpleName())).check(doesNotExist());
onView(withText(NavFragmentD.class.getSimpleName())).check(doesNotExist());
assertFragmentsCount(1);
}
private void assertFragmentsCount(int count) {
int actualFrags = 0;
int actualStackCount = 0;
FragmentManager fm = activity.getDelegateFragment().getChildFragmentManager();
if (fm.getFragments() != null) {
for (int i = 0; i < fm.getFragments().size(); ++i) {
if (fm.getFragments().get(i) != null) {
++actualFrags;
}
}
actualStackCount = fm.getBackStackEntryCount();
}
Assert.assertEquals(count, actualFrags);
Assert.assertEquals(count, actualStackCount);
}
@Test
public void test_should_release_injected_object_by_pure_navigation_controller_navigation() throws Throwable {
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
final String val = "Value = " + new Random().nextInt();
instrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
navigationManager.navigate(this).with(ControllerE.class, new Preparer<ControllerE>() {
@Override
public void prepare(ControllerE instance) {
instance.setValue(val);
}
}).to(ControllerE.class);
}
});
//The value set to controller e in Injector.graph().use should be retained during the
//navigation
onView(withText(val)).check(matches(isDisplayed()));
//The controller should not be disposed yet
verify(disposeCheckerEMock, times(0)).onDestroy();
navigateBackByFragment();
//Controller should be disposed after navigated away from fragment E
waitTest();
verify(disposeCheckerEMock, times(1)).onDestroy();
}
@Test
public void test_should_release_injected_object_by_chained_navigation_controller_navigation() throws Throwable {
onView(withText(NavFragmentA.class.getSimpleName())).check(matches(isDisplayed()));
final String valE = "ValueE = " + new Random().nextInt();
final String valF = "ValueF = " + new Random().nextInt();
final String valG = "ValueG = " + new Random().nextInt();
resetDisposeCheckers();
instrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
navigationManager.navigate(this).with(ControllerE.class, new Preparer<ControllerE>() {
@Override
public void prepare(ControllerE instance) {
instance.setValue(valE);
}
}).to(ControllerE.class);
}
});
onView(withText(valE)).check(matches(isDisplayed()));
verify(disposeCheckerEMock, times(0)).onDestroy();
verify(disposeCheckerFMock, times(0)).onDestroy();
verify(disposeCheckerGMock, times(0)).onDestroy();
resetDisposeCheckers();
instrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
navigationManager.navigate(this).with(ControllerF.class, new Preparer<ControllerF>() {
@Override
public void prepare(ControllerF instance) {
instance.setValue(valF);
}
}).to(ControllerF.class);
}
});
waitTest();
onView(withText(valF)).check(matches(isDisplayed()));
verify(disposeCheckerEMock, times(0)).onDestroy();
verify(disposeCheckerFMock, times(0)).onDestroy();
verify(disposeCheckerGMock, times(0)).onDestroy();
resetDisposeCheckers();
instrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
navigationManager.navigate(this).with(ControllerG.class, new Preparer<ControllerG>() {
@Override
public void prepare(ControllerG instance) {
instance.setValue(valG);
}
}).to(ControllerG.class);
}
});
waitTest();
onView(withText(valG)).check(matches(isDisplayed()));
verify(disposeCheckerEMock, times(0)).onDestroy();
verify(disposeCheckerFMock, times(0)).onDestroy();
verify(disposeCheckerGMock, times(0)).onDestroy();
resetDisposeCheckers();
//The value set to controller e in Injector.graph().use should be retained during the
//navigation
navigateBackByFragment();
onView(withText(valF)).check(matches(isDisplayed()));
verify(disposeCheckerEMock, times(0)).onDestroy();
verify(disposeCheckerFMock, times(0)).onDestroy();
verify(disposeCheckerGMock, times(0)).onDestroy();
resetDisposeCheckers();
navigateBackByFragment();
onView(withText(valE)).check(matches(isDisplayed()));
verify(disposeCheckerEMock, times(0)).onDestroy();
verify(disposeCheckerFMock, times(1)).onDestroy();
//__MvcGraphHelper retaining all instances is dangerous. Try to only retain relevant injected instances.
verify(disposeCheckerGMock, times(0)).onDestroy();
resetDisposeCheckers();
navigateBackByFragment();
verify(disposeCheckerEMock, times(1)).onDestroy();
verify(disposeCheckerFMock, times(0)).onDestroy();
verify(disposeCheckerGMock, times(1)).onDestroy();
}
private void resetDisposeCheckers() {
reset(disposeCheckerEMock);
reset(disposeCheckerFMock);
reset(disposeCheckerGMock);
}
}