Compare commits

...

6 Commits

Author SHA1 Message Date
EricKuck 5f138e5d43 Version bump for 3.1.0 release 2021-07-02 11:18:50 -05:00
Paul Woitaschek 03701d05a9 Add basic support for compose (#644)
Includes required lifecycle and saved state owners
2021-07-02 10:23:00 -05:00
Paul Woitaschek a19968e0c9 Update dokka 2021-06-18 08:19:31 +02:00
Paul Woitaschek 5501ab2ac8 Remove the deprecated jcenter repo. 2021-06-17 21:48:52 +02:00
Paul Woitaschek 804fdb615e Update Gradle to 7.1 2021-06-16 12:29:18 +02:00
EricKuck c01b2a74d6 Version bump 2021-02-05 15:33:01 -06:00
50 changed files with 3490 additions and 3114 deletions
+8 -6
View File
@@ -8,10 +8,11 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
- name: set up JDK 11
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: 'adopt'
java-version: '11'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Test with Gradle
@@ -23,10 +24,11 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
- name: set up JDK 11
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: 'adopt'
java-version: '11'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Test with Gradle
+1
View File
@@ -6,3 +6,4 @@
/caches
/gradle.xml
/modules.xml
/compiler.xml
+9 -1
View File
@@ -118,7 +118,15 @@
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="CALL_PARAMETERS_WRAP" value="5" />
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_PARAMETERS_WRAP" value="5" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="ASSIGNMENT_WRAP" value="1" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
+8 -8
View File
@@ -20,30 +20,30 @@ Conductor is architecture-agnostic and does not try to force any design decision
## Installation
```gradle
implementation 'com.bluelinelabs:conductor:3.0.0'
implementation 'com.bluelinelabs:conductor:3.1.0'
// AndroidX Transition change handlers:
implementation 'com.bluelinelabs:conductor-androidx-transition:3.0.0'
implementation 'com.bluelinelabs:conductor-androidx-transition:3.1.0'
// ViewPager PagerAdapter:
implementation 'com.bluelinelabs:conductor-viewpager:3.0.0'
implementation 'com.bluelinelabs:conductor-viewpager:3.1.0'
// ViewPager2 Adapter:
implementation 'com.bluelinelabs:conductor-viewpager2:3.0.0'
implementation 'com.bluelinelabs:conductor-viewpager2:3.1.0'
// RxJava2 lifecycle support:
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.0.0'
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.1.0'
// RxJava2 Autodispose support:
implementation 'com.bluelinelabs:conductor-autodispose:3.0.0'
implementation 'com.bluelinelabs:conductor-autodispose:3.1.0'
// Lifecycle-aware Controllers (architecture components):
implementation 'com.bluelinelabs:conductor-archlifecycle:3.0.0'
implementation 'com.bluelinelabs:conductor-archlifecycle:3.1.0'
```
**SNAPSHOT**
Just use `3.0.1-SNAPSHOT` as your version number in any of the dependencies above and add the url to the snapshot repository:
Just use `3.1.1-SNAPSHOT` as your version number in any of the dependencies above and add the url to the snapshot repository:
```gradle
allprojects {
-2
View File
@@ -4,7 +4,6 @@ buildscript {
repositories {
mavenCentral()
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:$agpVersion"
@@ -18,7 +17,6 @@ allprojects {
repositories {
mavenCentral()
google()
jcenter()
}
}
+1
View File
@@ -8,6 +8,7 @@ dependencies {
compileOnly rootProject.ext.lintapi
compileOnly rootProject.ext.lintchecks
testImplementation rootProject.ext.junit
testImplementation rootProject.ext.lint
testImplementation rootProject.ext.lintTests
}
@@ -1,25 +0,0 @@
package com.bluelinelabs.conductor.lint;
import com.android.tools.lint.detector.api.Issue;
import java.util.Arrays;
import java.util.List;
import static com.android.tools.lint.detector.api.ApiKt.CURRENT_API;
@SuppressWarnings({"unused", "UnstableApiUsage"})
public final class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
@Override
public List<Issue> getIssues() {
return Arrays.asList(
ControllerIssueDetector.ISSUE,
ControllerChangeHandlerIssueDetector.ISSUE
);
}
@Override
public int getApi() {
return CURRENT_API;
}
}
@@ -0,0 +1,24 @@
package com.bluelinelabs.conductor.lint
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.android.tools.lint.client.api.IssueRegistry as LintIssueRegistry
@Suppress("UnstableApiUsage", "unused")
class IssueRegistry : LintIssueRegistry() {
override val issues = listOf(
ControllerIssueDetector.ISSUE,
ControllerChangeHandlerIssueDetector.ISSUE
)
override val api: Int = CURRENT_API
private val githubIssueLink = "https://github.com/bluelinelabs/Conductor/issues/new"
override val vendor = Vendor(
vendorName = "Conductor",
feedbackUrl = githubIssueLink,
contact = githubIssueLink
)
}
@@ -1,13 +1,13 @@
package com.bluelinelabs.conductor.lint;
import com.android.tools.lint.checks.infrastructure.LintDetectorTest;
import org.intellij.lang.annotations.Language;
import org.junit.Test;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.intellij.lang.annotations.Language;
import org.junit.Test;
@SuppressWarnings("UnstableApiUsage")
public class ControllerChangeHandlerDetectorTest {
@@ -17,7 +17,7 @@ public class ControllerChangeHandlerDetectorTest {
+ "^\n"
+ "1 errors, 0 warnings\n";
private final LintDetectorTest.TestFile controllerChangeHandlerStub = java(
private final TestFile controllerChangeHandlerStub = java(
"package com.bluelinelabs.conductor;\n"
+ "abstract class ControllerChangeHandler {}"
);
@@ -1,13 +1,13 @@
package com.bluelinelabs.conductor.lint;
import com.android.tools.lint.checks.infrastructure.LintDetectorTest;
import org.intellij.lang.annotations.Language;
import org.junit.Test;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.intellij.lang.annotations.Language;
import org.junit.Test;
@SuppressWarnings("UnstableApiUsage")
public class ControllerDetectorTest {
@@ -22,7 +22,7 @@ public class ControllerDetectorTest {
+ "^\n"
+ "1 errors, 0 warnings\n";
private final LintDetectorTest.TestFile controllerStub = java(
private final TestFile controllerStub = java(
"package com.bluelinelabs.conductor;\n"
+ "abstract class Controller {}"
);
+1 -1
View File
@@ -14,7 +14,7 @@ android {
dependencies {
testImplementation rootProject.ext.junit
testImplementation rootProject.ext.roboelectric
testImplementation rootProject.ext.robolectric
implementation rootProject.ext.androidxAppCompat
implementation project(':conductor')
@@ -1,69 +1,31 @@
package com.bluelinelabs.conductor.viewpager
import android.app.Activity
import android.os.Looper.getMainLooper
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.ViewCompat
import androidx.viewpager.widget.ViewPager
import com.bluelinelabs.conductor.Conductor
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
import com.bluelinelabs.conductor.viewpager.util.TestController
import com.bluelinelabs.conductor.viewpager.util.TestActivity
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class StateSaveTests {
private val pager: ViewPager
private val pagerAdapter: RouterPagerAdapter
private val destroyedItems = mutableListOf<Int>()
private val testController = Robolectric.buildActivity(TestActivity::class.java)
.setup()
.get()
.testController()
init {
val activityController = Robolectric.buildActivity(Activity::class.java).setup()
val layout = FrameLayout(activityController.get())
activityController.get().setContentView(layout)
val router = Conductor.attachRouter(activityController.get(), FrameLayout(activityController.get()), null)
val controller = TestController()
router.setRoot(with(controller))
pager = ViewPager(activityController.get()).also {
it.id = ViewCompat.generateViewId()
}
layout.addView(pager)
pager.offscreenPageLimit = 1
pagerAdapter = object : RouterPagerAdapter(controller) {
override fun configureRouter(router: Router, position: Int) {
if (!router.hasRootController()) {
router.setRoot(with(TestController()))
}
}
override fun getCount(): Int {
return 20
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
destroyedItems.add(position)
}
}
pager.adapter = pagerAdapter
shadowOf(getMainLooper()).idle()
}
private val pagerAdapter = testController.pagerAdapter
private val pager = testController.pager
private val destroyedItems = testController.destroyedItems
@Test
fun testNoMaxSaves() {
// Load all pages
for (i in 0 until pagerAdapter.count) {
pager.currentItem = i
shadowOf(getMainLooper()).idle()
}
// Ensure all non-visible pages are saved
@@ -81,13 +43,11 @@ class StateSaveTests {
// Load all pages
for (i in 0 until pagerAdapter.count) {
pager.currentItem = i
shadowOf(getMainLooper()).idle()
}
val firstSelectedItem = pagerAdapter.count / 2
for (i in pagerAdapter.count downTo firstSelectedItem) {
pager.currentItem = i
shadowOf(getMainLooper()).idle()
}
var savedPages = pagerAdapter.savedPages
@@ -103,7 +63,6 @@ class StateSaveTests {
val secondSelectedItem = 1
for (i in firstSelectedItem downTo secondSelectedItem) {
pager.currentItem = i
shadowOf(getMainLooper()).idle()
}
savedPages = pagerAdapter.savedPages
@@ -0,0 +1,85 @@
package com.bluelinelabs.conductor.viewpager.util
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.ViewCompat
import androidx.viewpager.widget.ViewPager
import com.bluelinelabs.conductor.Conductor
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.asTransaction
import com.bluelinelabs.conductor.viewpager.RouterPagerAdapter
class TestActivity : Activity() {
private lateinit var router: Router
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
router = Conductor.attachRouter(
this,
findViewById(android.R.id.content),
savedInstanceState
)
if (!router.hasRootController()) {
router.setRoot(TestController().asTransaction())
}
}
fun testController(): TestController {
return router.backstack.single().controller as TestController
}
}
class TestController : Controller() {
val destroyedItems = mutableListOf<Int>()
lateinit var pagerAdapter: RouterPagerAdapter
lateinit var pager: ViewPager
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedViewState: Bundle?
): View {
pager = ViewPager(container.context).also {
it.id = ViewCompat.generateViewId()
}
pager.offscreenPageLimit = 1
pagerAdapter = object : RouterPagerAdapter(this) {
override fun configureRouter(router: Router, position: Int) {
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(PageController()))
}
}
override fun getCount(): Int {
return 20
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
destroyedItems.add(position)
}
}
pager.adapter = pagerAdapter
return pager
}
}
class PageController : Controller() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedViewState: Bundle?
): View {
return FrameLayout(container.context)
}
}
@@ -1,21 +0,0 @@
package com.bluelinelabs.conductor.viewpager.util;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bluelinelabs.conductor.Controller;
public class TestController extends Controller {
@Override @NonNull
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState) {
return new FrameLayout(inflater.getContext());
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ android {
dependencies {
testImplementation rootProject.ext.junit
testImplementation rootProject.ext.roboelectric
testImplementation rootProject.ext.robolectric
implementation rootProject.ext.androidxAppCompat
implementation rootProject.ext.androidxViewPager2
+4 -1
View File
@@ -14,8 +14,11 @@ android {
}
dependencies {
implementation savedState
testImplementation rootProject.ext.junit
testImplementation rootProject.ext.roboelectric
testImplementation rootProject.ext.robolectric
implementation archComponentsLifecycle
api rootProject.ext.androidxAnnotations
api kotlinStd
@@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bluelinelabs.conductor.internal.ClassUtils;
import com.bluelinelabs.conductor.internal.OwnViewTreeLifecycleAndRegistry;
import com.bluelinelabs.conductor.internal.RouterRequiringFunc;
import com.bluelinelabs.conductor.internal.ViewAttachHandler;
import com.bluelinelabs.conductor.internal.ViewAttachHandler.ViewAttachListener;
@@ -142,6 +143,7 @@ public abstract class Controller {
this.args = args != null ? args : new Bundle(getClass().getClassLoader());
instanceId = UUID.randomUUID().toString();
ensureRequiredConstructor();
OwnViewTreeLifecycleAndRegistry.Companion.own(this);
}
/**
@@ -24,7 +24,8 @@ private constructor(
private var pushControllerChangeHandler: ControllerChangeHandler? = null,
private var popControllerChangeHandler: ControllerChangeHandler? = null,
private var attachedToRouter: Boolean = false,
@RestrictTo(LIBRARY)
@get:RestrictTo(LIBRARY)
@set:RestrictTo(LIBRARY)
var transactionIndex: Int = INVALID_INDEX
) {
@@ -0,0 +1,163 @@
package com.bluelinelabs.conductor.internal
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.ViewTreeSavedStateRegistryOwner
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.R
/**
* This class sets the [ViewTreeLifecycleOwner] and [ViewTreeSavedStateRegistryOwner] which is
* necessary for Jetpack Compose. By setting these, the view state restoration and compose lifecycle
* play together with the lifecycle of the [Controller].
*/
internal class OwnViewTreeLifecycleAndRegistry private constructor(
controller: Controller
) : LifecycleOwner, SavedStateRegistryOwner {
private lateinit var lifecycleRegistry: LifecycleRegistry
private lateinit var savedStateRegistryController: SavedStateRegistryController
private var hasSavedState = false
private var savedRegistryState = Bundle.EMPTY
init {
controller.addLifecycleListener(object : Controller.LifecycleListener() {
override fun preCreateView(controller: Controller) {
hasSavedState = false
lifecycleRegistry = LifecycleRegistry(this@OwnViewTreeLifecycleAndRegistry)
savedStateRegistryController = SavedStateRegistryController.create(
this@OwnViewTreeLifecycleAndRegistry
)
savedStateRegistryController.performRestore(savedRegistryState)
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
override fun postCreateView(controller: Controller, view: View) {
/**
* If the consumer of the library already has its own [ViewTreeLifecycleOwner] or
* [ViewTreeSavedStateRegistryOwner] set, don't overwrite it but assume that they're doing
* it on purpose.
*/
if (
view.getTag(R.id.view_tree_lifecycle_owner) == null &&
view.getTag(R.id.view_tree_saved_state_registry_owner) == null
) {
ViewTreeLifecycleOwner.set(view, this@OwnViewTreeLifecycleAndRegistry)
ViewTreeSavedStateRegistryOwner.set(
view,
this@OwnViewTreeLifecycleAndRegistry
)
}
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
}
override fun postAttach(controller: Controller, view: View) {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
override fun onChangeEnd(
changeController: Controller,
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
// Should only happen if pushing another controller over this one was aborted
if (
controller == changeController &&
changeType.isEnter &&
changeHandler.removesFromViewOnPush() &&
changeController.view?.windowToken != null &&
lifecycleRegistry.currentState == Lifecycle.State.STARTED
) {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
}
// AbstractComposeView adds its own OnAttachStateChangeListener by default. Since it
// does this on init, its detach callbacks get called before ours, which prevents us
// from saving state in onDetach. The if statement in here should detect upcoming
// detachment.
override fun onChangeStart(
changeController: Controller,
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
if (
controller == changeController &&
!changeType.isEnter &&
changeHandler.removesFromViewOnPush() &&
lifecycleRegistry.currentState == Lifecycle.State.RESUMED
) {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
savedRegistryState = Bundle()
savedStateRegistryController.performSave(savedRegistryState)
hasSavedState = true
}
}
override fun preDetach(controller: Controller, view: View) {
// Should only happen if pushing this controller was aborted
if (lifecycleRegistry.currentState == Lifecycle.State.RESUMED) {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
}
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
override fun onSaveInstanceState(controller: Controller, outState: Bundle) {
outState.putBundle(KEY_SAVED_STATE, savedRegistryState)
}
override fun onSaveViewState(controller: Controller, outState: Bundle) {
if (!hasSavedState) {
savedRegistryState = Bundle()
savedStateRegistryController.performSave(savedRegistryState)
}
}
override fun onRestoreInstanceState(controller: Controller, savedInstanceState: Bundle) {
savedRegistryState = savedInstanceState.getBundle(KEY_SAVED_STATE)
}
override fun preDestroyView(controller: Controller, view: View) {
if (controller.isBeingDestroyed && controller.router.backstackSize == 0) {
val parent = view.parent as? View
parent?.addOnAttachStateChangeListener(object :
View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) = Unit
override fun onViewDetachedFromWindow(v: View?) {
parent.removeOnAttachStateChangeListener(this)
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
})
} else {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
}
})
}
override fun getLifecycle() = lifecycleRegistry
override fun getSavedStateRegistry() = savedStateRegistryController.savedStateRegistry
companion object {
private const val KEY_SAVED_STATE = "Registry.savedState"
fun own(target: Controller) {
OwnViewTreeLifecycleAndRegistry(target)
}
}
}
@@ -1,7 +1,5 @@
package com.bluelinelabs.conductor;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
@@ -2,7 +2,6 @@ package com.bluelinelabs.conductor;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -1,270 +0,0 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ControllerLifecycleActivityReferenceTests {
private Router router;
private ActivityProxy activityProxy;
public void createActivityController(Bundle savedInstanceState, boolean includeStartAndResume) {
activityProxy = new ActivityProxy().create(savedInstanceState);
if (includeStartAndResume) {
activityProxy.start().resume();
}
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
router.setPopsLastView(true);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
}
@Before
public void setup() {
createActivityController(null, true);
}
@Test
public void testSingleControllerActivityOnPush() {
Controller controller = new TestController();
assertNull(controller.getActivity());
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
controller.addLifecycleListener(listener);
router.pushController(RouterTransaction.with(controller)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
assertEquals(Collections.emptyList(), listener.postDetachReferences);
assertEquals(Collections.emptyList(), listener.postDestroyViewReferences);
assertEquals(Collections.emptyList(), listener.postDestroyReferences);
}
@Test
public void testChildControllerActivityOnPush() {
Controller parent = new TestController();
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
assertNull(child.getActivity());
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
child.addLifecycleListener(listener);
Router childRouter = parent.getChildRouter((ViewGroup) parent.getView().findViewById(TestController.VIEW_ID));
childRouter.pushController(RouterTransaction.with(child)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
assertEquals(Collections.emptyList(), listener.postDetachReferences);
assertEquals(Collections.emptyList(), listener.postDestroyViewReferences);
assertEquals(Collections.emptyList(), listener.postDestroyReferences);
}
@Test
public void testSingleControllerActivityOnPop() {
Controller controller = new TestController();
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
controller.addLifecycleListener(listener);
router.pushController(RouterTransaction.with(controller)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
router.popCurrentController();
assertEquals(Arrays.asList(true, true), listener.changeEndReferences);
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
}
@Test
public void testChildControllerActivityOnPop() {
Controller parent = new TestController();
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
child.addLifecycleListener(listener);
Router childRouter = parent.getChildRouter((ViewGroup) parent.getView().findViewById(TestController.VIEW_ID));
childRouter.setPopsLastView(true);
childRouter.pushController(RouterTransaction.with(child)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
childRouter.popCurrentController();
assertEquals(Arrays.asList(true, true), listener.changeEndReferences);
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
}
@Test
public void testChildControllerActivityOnParentPop() {
Controller parent = new TestController();
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
child.addLifecycleListener(listener);
Router childRouter = parent.getChildRouter((ViewGroup) parent.getView().findViewById(TestController.VIEW_ID));
childRouter.setPopsLastView(true);
childRouter.pushController(RouterTransaction.with(child)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
router.popCurrentController();
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
}
@Test
public void testSingleControllerActivityOnDestroy() {
Controller controller = new TestController();
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
controller.addLifecycleListener(listener);
router.pushController(RouterTransaction.with(controller)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
activityProxy.pause().stop(false).destroy();
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
}
@Test
public void testChildControllerActivityOnDestroy() {
Controller parent = new TestController();
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
child.addLifecycleListener(listener);
Router childRouter = parent.getChildRouter((ViewGroup) parent.getView().findViewById(TestController.VIEW_ID));
childRouter.setPopsLastView(true);
childRouter.pushController(RouterTransaction.with(child)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
activityProxy.pause().stop(false).destroy();
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
}
static class ActivityReferencingLifecycleListener extends Controller.LifecycleListener {
final List<Boolean> changeEndReferences = new ArrayList<>();
final List<Boolean> postCreateViewReferences = new ArrayList<>();
final List<Boolean> postAttachReferences = new ArrayList<>();
final List<Boolean> postDetachReferences = new ArrayList<>();
final List<Boolean> postDestroyViewReferences = new ArrayList<>();
final List<Boolean> postDestroyReferences = new ArrayList<>();
@Override
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
changeEndReferences.add(controller.getActivity() != null);
}
@Override
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
postCreateViewReferences.add(controller.getActivity() != null);
}
@Override
public void postAttach(@NonNull Controller controller, @NonNull View view) {
postAttachReferences.add(controller.getActivity() != null);
}
@Override
public void postDetach(@NonNull Controller controller, @NonNull View view) {
postDetachReferences.add(controller.getActivity() != null);
}
@Override
public void postDestroyView(@NonNull Controller controller) {
postDestroyViewReferences.add(controller.getActivity() != null);
}
@Override
public void postDestroy(@NonNull Controller controller) {
postDestroyReferences.add(controller.getActivity() != null);
}
}
}
@@ -0,0 +1,255 @@
package com.bluelinelabs.conductor
import android.os.Looper.getMainLooper
import android.view.View
import com.bluelinelabs.conductor.Controller.LifecycleListener
import com.bluelinelabs.conductor.util.MockChangeHandler
import com.bluelinelabs.conductor.util.TestActivity
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class ControllerLifecycleActivityReferenceTests {
private val activityController = Robolectric.buildActivity(TestActivity::class.java).setup()
private val activity = activityController.get()
@Test
fun testSingleControllerActivityOnPush() {
val controller = TestController()
Assert.assertNull(controller.activity)
val listener = ActivityReferencingLifecycleListener()
controller.addLifecycleListener(listener)
activity.router.pushController(
controller.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertEquals(listOf(true), listener.changeEndReferences)
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
Assert.assertEquals(listOf(true), listener.postAttachReferences)
Assert.assertEquals(emptyList<Any>(), listener.postDetachReferences)
Assert.assertEquals(emptyList<Any>(), listener.postDestroyViewReferences)
Assert.assertEquals(emptyList<Any>(), listener.postDestroyReferences)
}
@Test
fun testChildControllerActivityOnPush() {
val parent = TestController()
activity.router.pushController(
parent.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val child = TestController()
Assert.assertNull(child.activity)
val listener = ActivityReferencingLifecycleListener()
child.addLifecycleListener(listener)
val childRouter = parent.getChildRouter((parent.view!!.findViewById(TestController.VIEW_ID)))
childRouter.pushController(
child.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertEquals(listOf(true), listener.changeEndReferences)
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
Assert.assertEquals(listOf(true), listener.postAttachReferences)
Assert.assertEquals(emptyList<Any>(), listener.postDetachReferences)
Assert.assertEquals(emptyList<Any>(), listener.postDestroyViewReferences)
Assert.assertEquals(emptyList<Any>(), listener.postDestroyReferences)
}
@Test
fun testSingleControllerActivityOnPop() {
val controller = TestController()
val listener = ActivityReferencingLifecycleListener()
controller.addLifecycleListener(listener)
activity.router.pushController(
controller.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
activity.router.popCurrentController()
Assert.assertEquals(listOf(true, true), listener.changeEndReferences)
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
Assert.assertEquals(listOf(true), listener.postAttachReferences)
Assert.assertEquals(listOf(true), listener.postDetachReferences)
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
}
@Test
fun testChildControllerActivityOnPop() {
val parent = TestController()
activity.router.pushController(
parent.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val child = TestController()
val listener = ActivityReferencingLifecycleListener()
child.addLifecycleListener(listener)
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
childRouter.setPopsLastView(true)
childRouter.pushController(
child.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
childRouter.popCurrentController()
shadowOf(getMainLooper()).idle()
Assert.assertEquals(listOf(true, true), listener.changeEndReferences)
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
Assert.assertEquals(listOf(true), listener.postAttachReferences)
Assert.assertEquals(listOf(true), listener.postDetachReferences)
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
}
@Test
fun testChildControllerActivityOnParentPop() {
val parent = TestController()
activity.router.pushController(
parent.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val child = TestController()
val listener = ActivityReferencingLifecycleListener()
child.addLifecycleListener(listener)
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
childRouter.setPopsLastView(true)
childRouter.pushController(
child.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
activity.router.popCurrentController()
Assert.assertEquals(listOf(true), listener.changeEndReferences)
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
Assert.assertEquals(listOf(true), listener.postAttachReferences)
Assert.assertEquals(listOf(true), listener.postDetachReferences)
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
}
@Test
fun testSingleControllerActivityOnDestroy() {
val controller = TestController()
val listener = ActivityReferencingLifecycleListener()
controller.addLifecycleListener(listener)
activity.router.pushController(
controller.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
activityController.pause().stop().destroy()
Assert.assertEquals(listOf(true), listener.changeEndReferences)
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
Assert.assertEquals(listOf(true), listener.postAttachReferences)
Assert.assertEquals(listOf(true), listener.postDetachReferences)
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
}
@Test
fun testChildControllerActivityOnDestroy() {
val parent = TestController()
activity.router.pushController(
parent.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val child = TestController()
val listener = ActivityReferencingLifecycleListener()
child.addLifecycleListener(listener)
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
childRouter.setPopsLastView(true)
childRouter.pushController(
child.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
activityController.pause().stop().destroy()
Assert.assertEquals(listOf(true), listener.changeEndReferences)
Assert.assertEquals(listOf(true), listener.postCreateViewReferences)
Assert.assertEquals(listOf(true), listener.postAttachReferences)
Assert.assertEquals(listOf(true), listener.postDetachReferences)
Assert.assertEquals(listOf(true), listener.postDestroyViewReferences)
Assert.assertEquals(listOf(true), listener.postDestroyReferences)
}
internal class ActivityReferencingLifecycleListener : LifecycleListener() {
val changeEndReferences = mutableListOf<Boolean>()
val postCreateViewReferences = mutableListOf<Boolean>()
val postAttachReferences = mutableListOf<Boolean>()
val postDetachReferences = mutableListOf<Boolean>()
val postDestroyViewReferences = mutableListOf<Boolean>()
val postDestroyReferences = mutableListOf<Boolean>()
override fun onChangeEnd(
controller: Controller,
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
changeEndReferences.add(controller.activity != null)
}
override fun postCreateView(controller: Controller, view: View) {
postCreateViewReferences.add(controller.activity != null)
}
override fun postAttach(controller: Controller, view: View) {
postAttachReferences.add(controller.activity != null)
}
override fun postDetach(controller: Controller, view: View) {
postDetachReferences.add(controller.activity != null)
}
override fun postDestroyView(controller: Controller) {
postDestroyViewReferences.add(controller.activity != null)
}
override fun postDestroy(controller: Controller) {
postDestroyReferences.add(controller.activity != null)
}
}
}
@@ -1,745 +0,0 @@
package com.bluelinelabs.conductor;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
import com.bluelinelabs.conductor.Controller.RetainViewMode;
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.CallState;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.MockChangeHandler.ChangeHandlerListener;
import com.bluelinelabs.conductor.util.TestController;
import com.bluelinelabs.conductor.util.ViewUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ControllerLifecycleCallbacksTests {
private Router router;
private ActivityProxy activityProxy;
private CallState currentCallState;
public void createActivityController(Bundle savedInstanceState, boolean includeStartAndResume) {
activityProxy = new ActivityProxy().create(savedInstanceState);
if (includeStartAndResume) {
activityProxy.start().resume();
}
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
}
@Before
public void setup() {
createActivityController(null, true);
currentCallState = new CallState(false);
}
@Test
public void testNormalLifecycle() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState(false);
assertCalls(expectedCallState, controller);
router.pushController(RouterTransaction.with(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.popChangeHandler(getPopHandler(expectedCallState, controller)));
assertCalls(expectedCallState, controller);
router.popCurrentController();
assertNull(controller.getView());
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleWithActivityStop() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState(false);
assertCalls(expectedCallState, controller);
router.pushController(RouterTransaction.with(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller)));
assertCalls(expectedCallState, controller);
activityProxy.getActivity().isDestroying = true;
activityProxy.pause();
assertCalls(expectedCallState, controller);
activityProxy.stop(false);
expectedCallState.detachCalls++;
assertCalls(expectedCallState, controller);
assertNotNull(controller.getView());
ViewUtils.reportAttached(controller.getView(), false);
expectedCallState.saveViewStateCalls++;
expectedCallState.destroyViewCalls++;
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleWithActivityDestroy() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState(false);
assertCalls(expectedCallState, controller);
router.pushController(RouterTransaction.with(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller)));
assertCalls(expectedCallState, controller);
activityProxy.getActivity().isDestroying = true;
activityProxy.pause();
assertCalls(expectedCallState, controller);
activityProxy.stop(true);
expectedCallState.saveViewStateCalls++;
expectedCallState.detachCalls++;
expectedCallState.destroyViewCalls++;
assertCalls(expectedCallState, controller);
activityProxy.destroy();
expectedCallState.contextUnavailableCalls++;
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleWithActivityConfigurationChange() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState(false);
assertCalls(expectedCallState, controller);
router.pushController(RouterTransaction.with(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.tag("root"));
assertCalls(expectedCallState, controller);
activityProxy.getActivity().isChangingConfigurations = true;
Bundle bundle = new Bundle();
activityProxy.saveInstanceState(bundle);
expectedCallState.saveViewStateCalls++;
expectedCallState.saveInstanceStateCalls++;
assertCalls(expectedCallState, controller);
activityProxy.pause();
assertCalls(expectedCallState, controller);
activityProxy.stop(true);
expectedCallState.detachCalls++;
expectedCallState.destroyViewCalls++;
assertCalls(expectedCallState, controller);
activityProxy.destroy();
expectedCallState.contextUnavailableCalls++;
assertCalls(expectedCallState, controller);
createActivityController(bundle, false);
controller = (TestController)router.getControllerWithTag("root");
expectedCallState.contextAvailableCalls++;
expectedCallState.restoreInstanceStateCalls++;
expectedCallState.restoreViewStateCalls++;
expectedCallState.changeStartCalls++;
expectedCallState.createViewCalls++;
// Lifecycle listener isn't attached during restore, grab the current views from the controller for this stuff...
currentCallState.restoreInstanceStateCalls = controller.currentCallState.restoreInstanceStateCalls;
currentCallState.restoreViewStateCalls = controller.currentCallState.restoreViewStateCalls;
currentCallState.changeStartCalls = controller.currentCallState.changeStartCalls;
currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls;
currentCallState.createViewCalls = controller.currentCallState.createViewCalls;
currentCallState.attachCalls = controller.currentCallState.attachCalls;
currentCallState.contextAvailableCalls = controller.currentCallState.contextAvailableCalls;
assertCalls(expectedCallState, controller);
activityProxy.start().resume();
currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls;
currentCallState.attachCalls = controller.currentCallState.attachCalls;
expectedCallState.changeEndCalls++;
expectedCallState.attachCalls++;
assertCalls(expectedCallState, controller);
activityProxy.resume();
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleWithActivityBackground() {
TestController controller = new TestController();
attachLifecycleListener(controller);
CallState expectedCallState = new CallState(false);
assertCalls(expectedCallState, controller);
router.pushController(RouterTransaction.with(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller)));
assertCalls(expectedCallState, controller);
activityProxy.pause();
Bundle bundle = new Bundle();
activityProxy.saveInstanceState(bundle);
expectedCallState.saveInstanceStateCalls++;
expectedCallState.saveViewStateCalls++;
assertCalls(expectedCallState, controller);
activityProxy.resume();
assertCalls(expectedCallState, controller);
}
@Test
public void testLifecycleCallOrder() {
final TestController testController = new TestController();
final CallState callState = new CallState(false);
testController.addLifecycleListener(new LifecycleListener() {
@Override
public void preCreateView(@NonNull Controller controller) {
callState.createViewCalls++;
assertEquals(1, callState.createViewCalls);
assertEquals(0, testController.currentCallState.createViewCalls);
assertEquals(0, callState.attachCalls);
assertEquals(0, testController.currentCallState.attachCalls);
assertEquals(0, callState.detachCalls);
assertEquals(0, testController.currentCallState.detachCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
callState.createViewCalls++;
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(0, callState.attachCalls);
assertEquals(0, testController.currentCallState.attachCalls);
assertEquals(0, callState.detachCalls);
assertEquals(0, testController.currentCallState.detachCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preAttach(@NonNull Controller controller, @NonNull View view) {
callState.attachCalls++;
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(1, callState.attachCalls);
assertEquals(0, testController.currentCallState.attachCalls);
assertEquals(0, callState.detachCalls);
assertEquals(0, testController.currentCallState.detachCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postAttach(@NonNull Controller controller, @NonNull View view) {
callState.attachCalls++;
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(0, callState.detachCalls);
assertEquals(0, testController.currentCallState.detachCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preDetach(@NonNull Controller controller, @NonNull View view) {
callState.detachCalls++;
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(1, callState.detachCalls);
assertEquals(0, testController.currentCallState.detachCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postDetach(@NonNull Controller controller, @NonNull View view) {
callState.detachCalls++;
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(1, testController.currentCallState.detachCalls);
assertEquals(0, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
callState.destroyViewCalls++;
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(1, testController.currentCallState.detachCalls);
assertEquals(1, callState.destroyViewCalls);
assertEquals(0, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postDestroyView(@NonNull Controller controller) {
callState.destroyViewCalls++;
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(1, testController.currentCallState.detachCalls);
assertEquals(2, callState.destroyViewCalls);
assertEquals(1, testController.currentCallState.destroyViewCalls);
assertEquals(0, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void preDestroy(@NonNull Controller controller) {
callState.destroyCalls++;
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(1, testController.currentCallState.detachCalls);
assertEquals(2, callState.destroyViewCalls);
assertEquals(1, testController.currentCallState.destroyViewCalls);
assertEquals(1, callState.destroyCalls);
assertEquals(0, testController.currentCallState.destroyCalls);
}
@Override
public void postDestroy(@NonNull Controller controller) {
callState.destroyCalls++;
assertEquals(2, callState.createViewCalls);
assertEquals(1, testController.currentCallState.createViewCalls);
assertEquals(2, callState.attachCalls);
assertEquals(1, testController.currentCallState.attachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(1, testController.currentCallState.detachCalls);
assertEquals(2, callState.destroyViewCalls);
assertEquals(1, testController.currentCallState.destroyViewCalls);
assertEquals(2, callState.destroyCalls);
assertEquals(1, testController.currentCallState.destroyCalls);
}
});
router.pushController(RouterTransaction.with(testController)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
router.popController(testController);
assertEquals(2, callState.createViewCalls);
assertEquals(2, callState.attachCalls);
assertEquals(2, callState.detachCalls);
assertEquals(2, callState.destroyViewCalls);
assertEquals(2, callState.destroyCalls);
}
@Test
public void testLifecycleWhenPopNonCurrentController() {
String controller1Tag = "controller1";
String controller2Tag = "controller2";
String controller3Tag = "controller3";
TestController controller1 = new TestController();
TestController controller2 = new TestController();
TestController controller3 = new TestController();
router.pushController(RouterTransaction.with(controller1)
.tag(controller1Tag));
router.pushController(RouterTransaction.with(controller2)
.tag(controller2Tag));
router.pushController(RouterTransaction.with(controller3)
.tag(controller3Tag));
router.popController(controller2);
assertEquals(1, controller2.currentCallState.attachCalls);
assertEquals(1, controller2.currentCallState.createViewCalls);
assertEquals(1, controller2.currentCallState.detachCalls);
assertEquals(1, controller2.currentCallState.destroyViewCalls);
assertEquals(1, controller2.currentCallState.destroyCalls);
assertEquals(1, controller2.currentCallState.contextAvailableCalls);
assertEquals(1, controller2.currentCallState.contextUnavailableCalls);
assertEquals(1, controller2.currentCallState.saveViewStateCalls);
assertEquals(0, controller2.currentCallState.restoreViewStateCalls);
}
@Test
public void testChildLifecycle() {
Controller parent = new TestController();
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
attachLifecycleListener(child);
CallState expectedCallState = new CallState(false);
assertCalls(expectedCallState, child);
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter
.setRoot(RouterTransaction.with(child)
.pushChangeHandler(getPushHandler(expectedCallState, child))
.popChangeHandler(getPopHandler(expectedCallState, child)));
assertCalls(expectedCallState, child);
parent.removeChildRouter(childRouter);
assertCalls(expectedCallState, child);
}
@Test
public void testChildLifecycle2() {
Controller parent = new TestController();
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
attachLifecycleListener(child);
CallState expectedCallState = new CallState(false);
assertCalls(expectedCallState, child);
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter
.setRoot(RouterTransaction.with(child)
.pushChangeHandler(getPushHandler(expectedCallState, child))
.popChangeHandler(getPopHandler(expectedCallState, child)));
assertCalls(expectedCallState, child);
router.popCurrentController();
expectedCallState.detachCalls++;
expectedCallState.destroyViewCalls++;
expectedCallState.contextUnavailableCalls++;
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, child);
}
@Test
public void testChildLifecycleOrderingAfterUnexpectedAttach() {
Controller parent = new TestController();
parent.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
child.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter
.setRoot(RouterTransaction.with(child)
.pushChangeHandler(new SimpleSwapChangeHandler())
.popChangeHandler(new SimpleSwapChangeHandler()));
assertTrue(parent.isAttached());
assertTrue(child.isAttached());
ViewUtils.reportAttached(parent.getView(), false, true);
assertFalse(parent.isAttached());
assertFalse(child.isAttached());
ViewUtils.reportAttached(child.getView(), true);
assertFalse(parent.isAttached());
assertFalse(child.isAttached());
ViewUtils.reportAttached(parent.getView(), true);
assertTrue(parent.isAttached());
assertTrue(child.isAttached());
}
@Test
public void testChildLifecycleAfterPushAndPop() {
Controller parent = new TestController();
parent.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter
.setRoot(RouterTransaction.with(child)
.pushChangeHandler(new SimpleSwapChangeHandler())
.popChangeHandler(new SimpleSwapChangeHandler()));
Controller nextController = new TestController();
router.pushController(RouterTransaction.with(nextController));
router.popCurrentController();
assertTrue(parent.isAttached());
assertTrue(child.isAttached());
}
@Test
public void testChildLifecycleAfterPushPopPush() {
Controller parent = new TestController();
parent.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter
.setRoot(RouterTransaction.with(child)
.pushChangeHandler(new SimpleSwapChangeHandler())
.popChangeHandler(new SimpleSwapChangeHandler()));
Controller nextController = new TestController();
router.pushController(RouterTransaction.with(nextController));
TestController child2 = new TestController();
childRouter.pushController(RouterTransaction.with(child2));
router.popCurrentController();
assertTrue(parent.isAttached());
assertFalse(child.isAttached());
assertTrue(child2.isAttached());
}
private MockChangeHandler getPushHandler(final CallState expectedCallState, final TestController controller) {
return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() {
@Override
public void willStartChange() {
expectedCallState.contextAvailableCalls++;
expectedCallState.changeStartCalls++;
expectedCallState.createViewCalls++;
assertCalls(expectedCallState, controller);
}
@Override
public void didAttachOrDetach() {
expectedCallState.attachCalls++;
assertCalls(expectedCallState, controller);
}
@Override
public void didEndChange() {
expectedCallState.changeEndCalls++;
assertCalls(expectedCallState, controller);
}
});
}
private MockChangeHandler getPopHandler(final CallState expectedCallState, final TestController controller) {
return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() {
@Override
public void willStartChange() {
expectedCallState.changeStartCalls++;
assertCalls(expectedCallState, controller);
}
@Override
public void didAttachOrDetach() {
expectedCallState.destroyViewCalls++;
expectedCallState.detachCalls++;
expectedCallState.contextUnavailableCalls++;
expectedCallState.destroyCalls++;
assertCalls(expectedCallState, controller);
}
@Override
public void didEndChange() {
expectedCallState.changeEndCalls++;
assertCalls(expectedCallState, controller);
}
});
}
private void assertCalls(CallState callState, TestController controller) {
assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
assertEquals("Expected call counts and lifecycle call counts do not match.", callState, currentCallState);
}
private void attachLifecycleListener(Controller controller) {
controller.addLifecycleListener(new LifecycleListener() {
@Override
public void onChangeStart(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
currentCallState.changeStartCalls++;
}
@Override
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
currentCallState.changeEndCalls++;
}
@Override
public void postContextAvailable(@NonNull Controller controller, @NonNull Context context) {
currentCallState.contextAvailableCalls++;
}
@Override
public void postContextUnavailable(@NonNull Controller controller) {
currentCallState.contextUnavailableCalls++;
}
@Override
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
currentCallState.createViewCalls++;
}
@Override
public void postAttach(@NonNull Controller controller, @NonNull View view) {
currentCallState.attachCalls++;
}
@Override
public void postDestroyView(@NonNull Controller controller) {
currentCallState.destroyViewCalls++;
}
@Override
public void postDetach(@NonNull Controller controller, @NonNull View view) {
currentCallState.detachCalls++;
}
@Override
public void postDestroy(@NonNull Controller controller) {
currentCallState.destroyCalls++;
}
@Override
public void onSaveInstanceState(@NonNull Controller controller, @NonNull Bundle outState) {
currentCallState.saveInstanceStateCalls++;
}
@Override
public void onRestoreInstanceState(@NonNull Controller controller, @NonNull Bundle savedInstanceState) {
currentCallState.restoreInstanceStateCalls++;
}
@Override
public void onSaveViewState(@NonNull Controller controller, @NonNull Bundle outState) {
currentCallState.saveViewStateCalls++;
}
@Override
public void onRestoreViewState(@NonNull Controller controller, @NonNull Bundle savedViewState) {
currentCallState.restoreViewStateCalls++;
}
});
}
}
@@ -0,0 +1,702 @@
package com.bluelinelabs.conductor
import android.content.Context
import android.os.Bundle
import android.os.Looper.getMainLooper
import android.view.View
import com.bluelinelabs.conductor.Controller.LifecycleListener
import com.bluelinelabs.conductor.Controller.RetainViewMode
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler
import com.bluelinelabs.conductor.util.CallState
import com.bluelinelabs.conductor.util.MockChangeHandler
import com.bluelinelabs.conductor.util.MockChangeHandler.ChangeHandlerListener
import com.bluelinelabs.conductor.util.TestActivity
import com.bluelinelabs.conductor.util.ViewUtils
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.android.controller.ActivityController
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class ControllerLifecycleCallbacksTests {
private lateinit var activityController: ActivityController<TestActivity>
private lateinit var currentCallState: CallState
private fun createActivityController(savedInstanceState: Bundle?, includeStartAndResume: Boolean) {
activityController = Robolectric.buildActivity(TestActivity::class.java)
activityController.create(savedInstanceState)
if (savedInstanceState != null) {
activityController.restoreInstanceState(savedInstanceState)
}
if (includeStartAndResume) {
activityController
.start()
.postCreate(savedInstanceState)
.resume()
.visible()
}
if (!activityController.get().router.hasRootController()) {
activityController.get().router.setRoot(TestController().asTransaction())
}
}
@Before
fun setup() {
createActivityController(null, true)
currentCallState = CallState(false)
}
@Test
fun testNormalLifecycle() {
val controller = TestController()
attachLifecycleListener(controller)
val expectedCallState = CallState(false)
assertCalls(expectedCallState, controller)
activityController.get().router.pushController(
controller.asTransaction(
pushChangeHandler = getPushHandler(expectedCallState, controller),
popChangeHandler = getPopHandler(expectedCallState, controller)
)
)
assertCalls(expectedCallState, controller)
activityController.get().router.popCurrentController()
Assert.assertNull(controller.view)
assertCalls(expectedCallState, controller)
}
@Test
fun testLifecycleWithActivityStop() {
val controller = TestController()
attachLifecycleListener(controller)
val expectedCallState = CallState(false)
assertCalls(expectedCallState, controller)
activityController.get().router.pushController(
controller.asTransaction(
pushChangeHandler = getPushHandler(expectedCallState, controller)
)
)
assertCalls(expectedCallState, controller)
activityController.get().destroying = true
activityController.pause()
assertCalls(expectedCallState, controller)
activityController.stop()
expectedCallState.detachCalls++
assertCalls(expectedCallState, controller)
Assert.assertNotNull(controller.view)
ViewUtils.reportAttached(controller.view, false)
expectedCallState.saveViewStateCalls++
expectedCallState.destroyViewCalls++
assertCalls(expectedCallState, controller)
}
@Test
fun testLifecycleWithActivityDestroy() {
val controller = TestController()
attachLifecycleListener(controller)
val expectedCallState = CallState(false)
assertCalls(expectedCallState, controller)
activityController.get().router.pushController(
controller.asTransaction(
pushChangeHandler = getPushHandler(expectedCallState, controller)
)
)
assertCalls(expectedCallState, controller)
activityController.get().destroying = true
activityController.pause()
assertCalls(expectedCallState, controller)
activityController.stop()
expectedCallState.detachCalls++
assertCalls(expectedCallState, controller)
activityController.destroy()
expectedCallState.destroyViewCalls++
expectedCallState.contextUnavailableCalls++
expectedCallState.destroyCalls++
assertCalls(expectedCallState, controller)
}
@Test
fun testLifecycleWithActivityConfigurationChange() {
var controller = TestController()
attachLifecycleListener(controller)
val expectedCallState = CallState(false)
assertCalls(expectedCallState, controller)
activityController.get().router.pushController(
RouterTransaction.with(controller)
.pushChangeHandler(getPushHandler(expectedCallState, controller))
.tag("root")
)
assertCalls(expectedCallState, controller)
activityController.get().changingConfigurations = true
val bundle = Bundle()
activityController.saveInstanceState(bundle)
expectedCallState.saveViewStateCalls++
expectedCallState.saveInstanceStateCalls++
assertCalls(expectedCallState, controller)
activityController.pause()
assertCalls(expectedCallState, controller)
activityController.stop()
expectedCallState.detachCalls++
assertCalls(expectedCallState, controller)
activityController.destroy()
expectedCallState.destroyViewCalls++
expectedCallState.contextUnavailableCalls++
assertCalls(expectedCallState, controller)
createActivityController(bundle, false)
controller = activityController.get().router.getControllerWithTag("root") as TestController
expectedCallState.contextAvailableCalls++
expectedCallState.restoreInstanceStateCalls++
expectedCallState.restoreViewStateCalls++
expectedCallState.changeStartCalls++
expectedCallState.createViewCalls++
// Lifecycle listener isn't attached during restore, grab the current views from the controller for this stuff...
currentCallState.restoreInstanceStateCalls = controller.currentCallState.restoreInstanceStateCalls
currentCallState.restoreViewStateCalls = controller.currentCallState.restoreViewStateCalls
currentCallState.changeStartCalls = controller.currentCallState.changeStartCalls
currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls
currentCallState.createViewCalls = controller.currentCallState.createViewCalls
currentCallState.attachCalls = controller.currentCallState.attachCalls
currentCallState.contextAvailableCalls = controller.currentCallState.contextAvailableCalls
assertCalls(expectedCallState, controller)
activityController
.start()
.postCreate(bundle)
.resume()
.visible()
currentCallState.changeEndCalls = controller.currentCallState.changeEndCalls
currentCallState.attachCalls = controller.currentCallState.attachCalls
expectedCallState.changeEndCalls++
expectedCallState.attachCalls++
assertCalls(expectedCallState, controller)
activityController.resume()
assertCalls(expectedCallState, controller)
}
@Test
fun testLifecycleWithActivityBackground() {
val controller = TestController()
attachLifecycleListener(controller)
val expectedCallState = CallState(false)
assertCalls(expectedCallState, controller)
activityController.get().router.pushController(
controller.asTransaction(
pushChangeHandler = getPushHandler(expectedCallState, controller)
)
)
assertCalls(expectedCallState, controller)
activityController.pause()
val bundle = Bundle()
activityController.saveInstanceState(bundle)
expectedCallState.saveInstanceStateCalls++
expectedCallState.saveViewStateCalls++
assertCalls(expectedCallState, controller)
activityController.resume()
assertCalls(expectedCallState, controller)
}
@Test
fun testLifecycleCallOrder() {
val testController = TestController()
val callState = CallState(false)
testController.addLifecycleListener(object : LifecycleListener() {
override fun preCreateView(controller: Controller) {
callState.createViewCalls++
Assert.assertEquals(1, callState.createViewCalls)
Assert.assertEquals(0, testController.currentCallState.createViewCalls)
Assert.assertEquals(0, callState.attachCalls)
Assert.assertEquals(0, testController.currentCallState.attachCalls)
Assert.assertEquals(0, callState.detachCalls)
Assert.assertEquals(0, testController.currentCallState.detachCalls)
Assert.assertEquals(0, callState.destroyViewCalls)
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
Assert.assertEquals(0, callState.destroyCalls)
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
}
override fun postCreateView(controller: Controller, view: View) {
callState.createViewCalls++
Assert.assertEquals(2, callState.createViewCalls)
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
Assert.assertEquals(0, callState.attachCalls)
Assert.assertEquals(0, testController.currentCallState.attachCalls)
Assert.assertEquals(0, callState.detachCalls)
Assert.assertEquals(0, testController.currentCallState.detachCalls)
Assert.assertEquals(0, callState.destroyViewCalls)
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
Assert.assertEquals(0, callState.destroyCalls)
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
}
override fun preAttach(controller: Controller, view: View) {
callState.attachCalls++
Assert.assertEquals(2, callState.createViewCalls)
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
Assert.assertEquals(1, callState.attachCalls)
Assert.assertEquals(0, testController.currentCallState.attachCalls)
Assert.assertEquals(0, callState.detachCalls)
Assert.assertEquals(0, testController.currentCallState.detachCalls)
Assert.assertEquals(0, callState.destroyViewCalls)
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
Assert.assertEquals(0, callState.destroyCalls)
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
}
override fun postAttach(controller: Controller, view: View) {
callState.attachCalls++
Assert.assertEquals(2, callState.createViewCalls)
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
Assert.assertEquals(2, callState.attachCalls)
Assert.assertEquals(1, testController.currentCallState.attachCalls)
Assert.assertEquals(0, callState.detachCalls)
Assert.assertEquals(0, testController.currentCallState.detachCalls)
Assert.assertEquals(0, callState.destroyViewCalls)
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
Assert.assertEquals(0, callState.destroyCalls)
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
}
override fun preDetach(controller: Controller, view: View) {
callState.detachCalls++
Assert.assertEquals(2, callState.createViewCalls)
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
Assert.assertEquals(2, callState.attachCalls)
Assert.assertEquals(1, testController.currentCallState.attachCalls)
Assert.assertEquals(1, callState.detachCalls)
Assert.assertEquals(0, testController.currentCallState.detachCalls)
Assert.assertEquals(0, callState.destroyViewCalls)
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
Assert.assertEquals(0, callState.destroyCalls)
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
}
override fun postDetach(controller: Controller, view: View) {
callState.detachCalls++
Assert.assertEquals(2, callState.createViewCalls)
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
Assert.assertEquals(2, callState.attachCalls)
Assert.assertEquals(1, testController.currentCallState.attachCalls)
Assert.assertEquals(2, callState.detachCalls)
Assert.assertEquals(1, testController.currentCallState.detachCalls)
Assert.assertEquals(0, callState.destroyViewCalls)
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
Assert.assertEquals(0, callState.destroyCalls)
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
}
override fun preDestroyView(controller: Controller, view: View) {
callState.destroyViewCalls++
Assert.assertEquals(2, callState.createViewCalls)
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
Assert.assertEquals(2, callState.attachCalls)
Assert.assertEquals(1, testController.currentCallState.attachCalls)
Assert.assertEquals(2, callState.detachCalls)
Assert.assertEquals(1, testController.currentCallState.detachCalls)
Assert.assertEquals(1, callState.destroyViewCalls)
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls)
Assert.assertEquals(0, callState.destroyCalls)
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
}
override fun postDestroyView(controller: Controller) {
callState.destroyViewCalls++
Assert.assertEquals(2, callState.createViewCalls)
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
Assert.assertEquals(2, callState.attachCalls)
Assert.assertEquals(1, testController.currentCallState.attachCalls)
Assert.assertEquals(2, callState.detachCalls)
Assert.assertEquals(1, testController.currentCallState.detachCalls)
Assert.assertEquals(2, callState.destroyViewCalls)
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls)
Assert.assertEquals(0, callState.destroyCalls)
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
}
override fun preDestroy(controller: Controller) {
callState.destroyCalls++
Assert.assertEquals(2, callState.createViewCalls)
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
Assert.assertEquals(2, callState.attachCalls)
Assert.assertEquals(1, testController.currentCallState.attachCalls)
Assert.assertEquals(2, callState.detachCalls)
Assert.assertEquals(1, testController.currentCallState.detachCalls)
Assert.assertEquals(2, callState.destroyViewCalls)
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls)
Assert.assertEquals(1, callState.destroyCalls)
Assert.assertEquals(0, testController.currentCallState.destroyCalls)
}
override fun postDestroy(controller: Controller) {
callState.destroyCalls++
Assert.assertEquals(2, callState.createViewCalls)
Assert.assertEquals(1, testController.currentCallState.createViewCalls)
Assert.assertEquals(2, callState.attachCalls)
Assert.assertEquals(1, testController.currentCallState.attachCalls)
Assert.assertEquals(2, callState.detachCalls)
Assert.assertEquals(1, testController.currentCallState.detachCalls)
Assert.assertEquals(2, callState.destroyViewCalls)
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls)
Assert.assertEquals(2, callState.destroyCalls)
Assert.assertEquals(1, testController.currentCallState.destroyCalls)
}
})
activityController.get().router.pushController(
testController.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
activityController.get().router.popController(testController)
Assert.assertEquals(2, callState.createViewCalls)
Assert.assertEquals(2, callState.attachCalls)
Assert.assertEquals(2, callState.detachCalls)
Assert.assertEquals(2, callState.destroyViewCalls)
Assert.assertEquals(2, callState.destroyCalls)
}
@Test
fun testLifecycleWhenPopNonCurrentController() {
val controller1Tag = "controller1"
val controller2Tag = "controller2"
val controller3Tag = "controller3"
val controller1 = TestController()
val controller2 = TestController()
val controller3 = TestController()
activityController.get().router.pushController(
RouterTransaction.with(controller1).tag(controller1Tag)
)
activityController.get().router.pushController(
RouterTransaction.with(controller2).tag(controller2Tag)
)
activityController.get().router.pushController(
RouterTransaction.with(controller3).tag(controller3Tag)
)
activityController.get().router.popController(controller2)
Assert.assertEquals(1, controller2.currentCallState.attachCalls)
Assert.assertEquals(1, controller2.currentCallState.createViewCalls)
Assert.assertEquals(1, controller2.currentCallState.detachCalls)
Assert.assertEquals(1, controller2.currentCallState.destroyViewCalls)
Assert.assertEquals(1, controller2.currentCallState.destroyCalls)
Assert.assertEquals(1, controller2.currentCallState.contextAvailableCalls)
Assert.assertEquals(1, controller2.currentCallState.contextUnavailableCalls)
Assert.assertEquals(1, controller2.currentCallState.saveViewStateCalls)
Assert.assertEquals(0, controller2.currentCallState.restoreViewStateCalls)
}
@Test
fun testChildLifecycle() {
val parent = TestController()
activityController.get().router.pushController(
parent.asTransaction(pushChangeHandler = MockChangeHandler.defaultHandler())
)
val child = TestController()
attachLifecycleListener(child)
val expectedCallState = CallState(false)
assertCalls(expectedCallState, child)
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
childRouter.setRoot(
child.asTransaction(
pushChangeHandler = getPushHandler(expectedCallState, child),
popChangeHandler = getPopHandler(expectedCallState, child)
)
)
assertCalls(expectedCallState, child)
parent.removeChildRouter(childRouter)
assertCalls(expectedCallState, child)
}
@Test
fun testChildLifecycle2() {
val parent = TestController()
activityController.get().router.pushController(
parent.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val child = TestController()
attachLifecycleListener(child)
val expectedCallState = CallState(false)
assertCalls(expectedCallState, child)
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
childRouter.setRoot(
child.asTransaction(
pushChangeHandler = getPushHandler(expectedCallState, child),
popChangeHandler = getPopHandler(expectedCallState, child)
)
)
assertCalls(expectedCallState, child)
activityController.get().router.popCurrentController()
expectedCallState.detachCalls++
expectedCallState.destroyViewCalls++
expectedCallState.contextUnavailableCalls++
expectedCallState.destroyCalls++
assertCalls(expectedCallState, child)
}
@Test
fun testChildLifecycleOrderingAfterUnexpectedAttach() {
val parent = TestController()
parent.retainViewMode = RetainViewMode.RETAIN_DETACH
activityController.get().router.pushController(
parent.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val child = TestController()
child.retainViewMode = RetainViewMode.RETAIN_DETACH
val childRouter = parent.getChildRouter(parent.getView()!!.findViewById(TestController.VIEW_ID))
childRouter.setRoot(
child.asTransaction(
pushChangeHandler = SimpleSwapChangeHandler(),
popChangeHandler = SimpleSwapChangeHandler()
)
)
Assert.assertTrue(parent.isAttached)
Assert.assertTrue(child.isAttached)
ViewUtils.reportAttached(parent.view, false, true)
Assert.assertFalse(parent.isAttached)
Assert.assertFalse(child.isAttached)
ViewUtils.reportAttached(child.view, true)
Assert.assertFalse(parent.isAttached)
Assert.assertFalse(child.isAttached)
ViewUtils.reportAttached(parent.view, true)
Assert.assertTrue(parent.isAttached)
Assert.assertTrue(child.isAttached)
}
@Test
fun testChildLifecycleAfterPushAndPop() {
val parent = TestController()
parent.retainViewMode = RetainViewMode.RETAIN_DETACH
activityController.get().router.pushController(
parent.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val child = TestController()
val childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
childRouter.setRoot(
child.asTransaction(
pushChangeHandler = SimpleSwapChangeHandler(),
popChangeHandler = SimpleSwapChangeHandler()
)
)
val nextController = TestController()
activityController.get().router.pushController(nextController.asTransaction())
activityController.get().router.popCurrentController()
shadowOf(getMainLooper()).idle()
Assert.assertTrue(parent.isAttached)
Assert.assertTrue(child.isAttached)
}
@Test
fun testChildLifecycleAfterPushPopPush() {
val parent = TestController()
parent.retainViewMode = RetainViewMode.RETAIN_DETACH
activityController.get().router.pushController(
parent.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val child = TestController()
val childRouter = parent.getChildRouter(parent.getView()!!.findViewById(TestController.VIEW_ID))
childRouter.setRoot(
child.asTransaction(
pushChangeHandler = SimpleSwapChangeHandler(),
popChangeHandler = SimpleSwapChangeHandler()
)
)
val nextController = TestController()
activityController.get().router.pushController(nextController.asTransaction())
val child2 = TestController()
childRouter.pushController(child2.asTransaction())
activityController.get().router.popCurrentController()
shadowOf(getMainLooper()).idle()
Assert.assertTrue(parent.isAttached)
Assert.assertFalse(child.isAttached)
Assert.assertTrue(child2.isAttached)
}
private fun getPushHandler(
expectedCallState: CallState,
controller: TestController
): MockChangeHandler {
return MockChangeHandler.listeningChangeHandler(object : ChangeHandlerListener() {
override fun willStartChange() {
expectedCallState.contextAvailableCalls++
expectedCallState.changeStartCalls++
expectedCallState.createViewCalls++
assertCalls(expectedCallState, controller)
}
override fun didAttachOrDetach() {
expectedCallState.attachCalls++
assertCalls(expectedCallState, controller)
}
override fun didEndChange() {
expectedCallState.changeEndCalls++
assertCalls(expectedCallState, controller)
}
})
}
private fun getPopHandler(
expectedCallState: CallState,
controller: TestController
): MockChangeHandler {
return MockChangeHandler.listeningChangeHandler(object : ChangeHandlerListener() {
override fun willStartChange() {
expectedCallState.changeStartCalls++
assertCalls(expectedCallState, controller)
}
override fun didAttachOrDetach() {
expectedCallState.destroyViewCalls++
expectedCallState.detachCalls++
expectedCallState.contextUnavailableCalls++
expectedCallState.destroyCalls++
assertCalls(expectedCallState, controller)
}
override fun didEndChange() {
expectedCallState.changeEndCalls++
assertCalls(expectedCallState, controller)
}
})
}
private fun assertCalls(callState: CallState, controller: TestController) {
shadowOf(getMainLooper()).idle()
Assert.assertEquals(
"Expected call counts and controller call counts do not match.",
callState,
controller.currentCallState
)
Assert.assertEquals(
"Expected call counts and lifecycle call counts do not match.",
callState,
currentCallState
)
}
private fun attachLifecycleListener(controller: Controller?) {
controller!!.addLifecycleListener(object : LifecycleListener() {
override fun onChangeStart(
controller: Controller,
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
currentCallState.changeStartCalls++
}
override fun onChangeEnd(
controller: Controller,
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
currentCallState.changeEndCalls++
}
override fun postContextAvailable(controller: Controller, context: Context) {
currentCallState.contextAvailableCalls++
}
override fun postContextUnavailable(controller: Controller) {
currentCallState.contextUnavailableCalls++
}
override fun postCreateView(controller: Controller, view: View) {
currentCallState.createViewCalls++
}
override fun postAttach(controller: Controller, view: View) {
currentCallState.attachCalls++
}
override fun postDestroyView(controller: Controller) {
currentCallState.destroyViewCalls++
}
override fun postDetach(controller: Controller, view: View) {
currentCallState.detachCalls++
}
override fun postDestroy(controller: Controller) {
currentCallState.destroyCalls++
}
override fun onSaveInstanceState(controller: Controller, outState: Bundle) {
currentCallState.saveInstanceStateCalls++
}
override fun onRestoreInstanceState(controller: Controller, savedInstanceState: Bundle) {
currentCallState.restoreInstanceStateCalls++
}
override fun onSaveViewState(controller: Controller, outState: Bundle) {
currentCallState.saveViewStateCalls++
}
override fun onRestoreViewState(controller: Controller, savedViewState: Bundle) {
currentCallState.restoreViewStateCalls++
}
})
}
}
@@ -1,412 +0,0 @@
package com.bluelinelabs.conductor;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller.RetainViewMode;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.CallState;
import com.bluelinelabs.conductor.util.TestController;
import com.bluelinelabs.conductor.util.ViewUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ControllerTests {
private ActivityProxy activityProxy;
private Router router;
public void createActivityController(Bundle savedInstanceState) {
activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
}
@Before
public void setup() {
createActivityController(null);
}
@Test
public void testViewRetention() {
Controller controller = new TestController();
controller.setRouter(router);
// Test View getting released w/ RELEASE_DETACH
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
assertNull(controller.getView());
View view = controller.inflate(router.container);
assertNotNull(controller.getView());
ViewUtils.reportAttached(view, true);
assertNotNull(controller.getView());
ViewUtils.reportAttached(view, false);
assertNull(controller.getView());
// Test View getting retained w/ RETAIN_DETACH
controller.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
view = controller.inflate(router.container);
assertNotNull(controller.getView());
ViewUtils.reportAttached(view, true);
assertNotNull(controller.getView());
ViewUtils.reportAttached(view, false);
assertNotNull(controller.getView());
// Ensure re-setting RELEASE_DETACH releases
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
assertNull(controller.getView());
}
@Test
public void testActivityResult() {
TestController controller = new TestController();
CallState expectedCallState = new CallState(true);
router.pushController(RouterTransaction.with(controller));
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
router.onActivityResult(1, Activity.RESULT_OK, null);
assertCalls(expectedCallState, controller);
// Ensure starting an activity for result gets us the result back
controller.startActivityForResult(new Intent("action"), 1);
router.onActivityResult(1, Activity.RESULT_OK, null);
expectedCallState.onActivityResultCalls++;
assertCalls(expectedCallState, controller);
// Ensure requesting a result w/o calling startActivityForResult works
controller.registerForActivityResult(2);
router.onActivityResult(2, Activity.RESULT_OK, null);
expectedCallState.onActivityResultCalls++;
assertCalls(expectedCallState, controller);
}
@Test
public void testActivityResultForChild() {
TestController parent = new TestController();
TestController child = new TestController();
router.pushController(RouterTransaction.with(parent));
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID))
.setRoot(RouterTransaction.with(child));
CallState childExpectedCallState = new CallState(true);
CallState parentExpectedCallState = new CallState(true);
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
router.onActivityResult(1, Activity.RESULT_OK, null);
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure starting an activity for result gets us the result back
child.startActivityForResult(new Intent("action"), 1);
router.onActivityResult(1, Activity.RESULT_OK, null);
childExpectedCallState.onActivityResultCalls++;
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure requesting a result w/o calling startActivityForResult works
child.registerForActivityResult(2);
router.onActivityResult(2, Activity.RESULT_OK, null);
childExpectedCallState.onActivityResultCalls++;
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
}
@Test
public void testPermissionResult() {
final String[] requestedPermissions = new String[] {"test"};
TestController controller = new TestController();
CallState expectedCallState = new CallState(true);
router.pushController(RouterTransaction.with(controller));
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, new int[] {1});
assertCalls(expectedCallState, controller);
// Ensure requesting the permission gets us the result back
try {
controller.requestPermissions(requestedPermissions, 1);
} catch (NoSuchMethodError ignored) { }
router.onRequestPermissionsResult(controller.getInstanceId(), 1, requestedPermissions, new int[] {1});
expectedCallState.onRequestPermissionsResultCalls++;
assertCalls(expectedCallState, controller);
}
@Test
public void testPermissionResultForChild() {
final String[] requestedPermissions = new String[] {"test"};
TestController parent = new TestController();
TestController child = new TestController();
router.pushController(RouterTransaction.with(parent));
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID))
.setRoot(RouterTransaction.with(child));
CallState childExpectedCallState = new CallState(true);
CallState parentExpectedCallState = new CallState(true);
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, new int[] {1});
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure requesting the permission gets us the result back
try {
child.requestPermissions(requestedPermissions, 1);
} catch (NoSuchMethodError ignored) { }
router.onRequestPermissionsResult(child.getInstanceId(), 1, requestedPermissions, new int[] {1});
childExpectedCallState.onRequestPermissionsResultCalls++;
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
}
@Test
public void testOptionsMenu() {
TestController controller = new TestController();
CallState expectedCallState = new CallState(true);
router.pushController(RouterTransaction.with(controller));
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
router.onCreateOptionsMenu(null, null);
assertCalls(expectedCallState, controller);
// Ensure calling onCreateOptionsMenu with a menu works
controller.setHasOptionsMenu(true);
// Ensure it'll still get called back next time onCreateOptionsMenu is called
router.onCreateOptionsMenu(null, null);
expectedCallState.createOptionsMenuCalls++;
assertCalls(expectedCallState, controller);
// Ensure we stop getting them when we hide it
controller.setOptionsMenuHidden(true);
router.onCreateOptionsMenu(null, null);
assertCalls(expectedCallState, controller);
// Ensure we get the callback them when we un-hide it
controller.setOptionsMenuHidden(false);
router.onCreateOptionsMenu(null, null);
expectedCallState.createOptionsMenuCalls++;
assertCalls(expectedCallState, controller);
// Ensure we don't get the callback when we no longer have a menu
controller.setHasOptionsMenu(false);
router.onCreateOptionsMenu(null, null);
assertCalls(expectedCallState, controller);
}
@Test
public void testOptionsMenuForChild() {
TestController parent = new TestController();
TestController child = new TestController();
router.pushController(RouterTransaction.with(parent));
parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID))
.setRoot(RouterTransaction.with(child));
CallState childExpectedCallState = new CallState(true);
CallState parentExpectedCallState = new CallState(true);
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
router.onCreateOptionsMenu(null, null);
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure calling onCreateOptionsMenu with a menu works
child.setHasOptionsMenu(true);
// Ensure it'll still get called back next time onCreateOptionsMenu is called
router.onCreateOptionsMenu(null, null);
childExpectedCallState.createOptionsMenuCalls++;
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure we stop getting them when we hide it
child.setOptionsMenuHidden(true);
router.onCreateOptionsMenu(null, null);
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure we get the callback them when we un-hide it
child.setOptionsMenuHidden(false);
router.onCreateOptionsMenu(null, null);
childExpectedCallState.createOptionsMenuCalls++;
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
// Ensure we don't get the callback when we no longer have a menu
child.setHasOptionsMenu(false);
router.onCreateOptionsMenu(null, null);
assertCalls(childExpectedCallState, child);
assertCalls(parentExpectedCallState, parent);
}
@Test
public void testAddRemoveChildControllers() {
TestController parent = new TestController();
TestController child1 = new TestController();
TestController child2 = new TestController();
router.pushController(RouterTransaction.with(parent));
assertEquals(0, parent.getChildRouters().size());
assertNull(child1.getParentController());
assertNull(child2.getParentController());
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter.setPopsLastView(true);
childRouter.setRoot(RouterTransaction.with(child1));
assertEquals(1, parent.getChildRouters().size());
assertEquals(childRouter, parent.getChildRouters().get(0));
assertEquals(1, childRouter.getBackstackSize());
assertEquals(child1, childRouter.getControllers().get(0));
assertEquals(parent, child1.getParentController());
assertNull(child2.getParentController());
childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter.pushController(RouterTransaction.with(child2));
assertEquals(1, parent.getChildRouters().size());
assertEquals(childRouter, parent.getChildRouters().get(0));
assertEquals(2, childRouter.getBackstackSize());
assertEquals(child1, childRouter.getControllers().get(0));
assertEquals(child2, childRouter.getControllers().get(1));
assertEquals(parent, child1.getParentController());
assertEquals(parent, child2.getParentController());
childRouter.popController(child2);
assertEquals(1, parent.getChildRouters().size());
assertEquals(childRouter, parent.getChildRouters().get(0));
assertEquals(1, childRouter.getBackstackSize());
assertEquals(child1, childRouter.getControllers().get(0));
assertEquals(parent, child1.getParentController());
assertNull(child2.getParentController());
childRouter.popController(child1);
assertEquals(1, parent.getChildRouters().size());
assertEquals(childRouter, parent.getChildRouters().get(0));
assertEquals(0, childRouter.getBackstackSize());
assertNull(child1.getParentController());
assertNull(child2.getParentController());
}
@Test
public void testAddRemoveChildRouters() {
TestController parent = new TestController();
TestController child1 = new TestController();
TestController child2 = new TestController();
router.pushController(RouterTransaction.with(parent));
assertEquals(0, parent.getChildRouters().size());
assertNull(child1.getParentController());
assertNull(child2.getParentController());
Router childRouter1 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
Router childRouter2 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_2));
childRouter1.setRoot(RouterTransaction.with(child1));
childRouter2.setRoot(RouterTransaction.with(child2));
assertEquals(2, parent.getChildRouters().size());
assertEquals(childRouter1, parent.getChildRouters().get(0));
assertEquals(childRouter2, parent.getChildRouters().get(1));
assertEquals(1, childRouter1.getBackstackSize());
assertEquals(1, childRouter2.getBackstackSize());
assertEquals(child1, childRouter1.getControllers().get(0));
assertEquals(child2, childRouter2.getControllers().get(0));
assertEquals(parent, child1.getParentController());
assertEquals(parent, child2.getParentController());
parent.removeChildRouter(childRouter2);
assertEquals(1, parent.getChildRouters().size());
assertEquals(childRouter1, parent.getChildRouters().get(0));
assertEquals(1, childRouter1.getBackstackSize());
assertEquals(0, childRouter2.getBackstackSize());
assertEquals(child1, childRouter1.getControllers().get(0));
assertEquals(parent, child1.getParentController());
assertNull(child2.getParentController());
parent.removeChildRouter(childRouter1);
assertEquals(0, parent.getChildRouters().size());
assertEquals(0, childRouter1.getBackstackSize());
assertEquals(0, childRouter2.getBackstackSize());
assertNull(child1.getParentController());
assertNull(child2.getParentController());
}
@Test
public void testRestoredChildRouterBackstack() {
TestController parent = new TestController();
router.pushController(RouterTransaction.with(parent));
ViewUtils.reportAttached(parent.getView(), true);
RouterTransaction childTransaction1 = RouterTransaction.with(new TestController());
RouterTransaction childTransaction2 = RouterTransaction.with(new TestController());
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
childRouter.setPopsLastView(true);
childRouter.setRoot(childTransaction1);
childRouter.pushController(childTransaction2);
Bundle savedState = new Bundle();
childRouter.saveInstanceState(savedState);
parent.removeChildRouter(childRouter);
childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
assertEquals(0, childRouter.getBackstackSize());
childRouter.restoreInstanceState(savedState);
childRouter.rebindIfNeeded();
assertEquals(2, childRouter.getBackstackSize());
RouterTransaction restoredChildTransaction1 = childRouter.getBackstack().get(0);
RouterTransaction restoredChildTransaction2 = childRouter.getBackstack().get(1);
assertEquals(childTransaction1.getTransactionIndex(), restoredChildTransaction1.getTransactionIndex());
assertEquals(childTransaction1.controller().getInstanceId(), restoredChildTransaction1.controller().getInstanceId());
assertEquals(childTransaction2.getTransactionIndex(), restoredChildTransaction2.getTransactionIndex());
assertEquals(childTransaction2.controller().getInstanceId(), restoredChildTransaction2.controller().getInstanceId());
assertTrue(parent.handleBack());
assertEquals(1, childRouter.getBackstackSize());
assertEquals(restoredChildTransaction1, childRouter.getBackstack().get(0));
assertTrue(parent.handleBack());
assertEquals(0, childRouter.getBackstackSize());
}
private void assertCalls(CallState callState, TestController controller) {
assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
}
}
@@ -0,0 +1,524 @@
package com.bluelinelabs.conductor
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Looper
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.SubMenu
import com.bluelinelabs.conductor.Controller.RetainViewMode
import com.bluelinelabs.conductor.util.AttachFakingFrameLayout
import com.bluelinelabs.conductor.util.CallState
import com.bluelinelabs.conductor.util.TestActivity
import com.bluelinelabs.conductor.util.ViewUtils
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class ControllerTests {
private val router = Robolectric.buildActivity(TestActivity::class.java)
.setup()
.get()
.router
@Test
fun testViewRetention() {
val controller = TestController()
controller.setRouter(router)
// Test View getting released w/ RELEASE_DETACH
controller.retainViewMode = RetainViewMode.RELEASE_DETACH
Assert.assertNull(controller.getView())
var view = controller.inflate(router.container)
Assert.assertNotNull(controller.getView())
ViewUtils.reportAttached(view, true)
Assert.assertNotNull(controller.getView())
ViewUtils.reportAttached(view, false)
Assert.assertNull(controller.getView())
// Test View getting retained w/ RETAIN_DETACH
controller.retainViewMode = RetainViewMode.RETAIN_DETACH
view = controller.inflate(router.container)
Assert.assertNotNull(controller.getView())
ViewUtils.reportAttached(view, true)
Assert.assertNotNull(controller.getView())
ViewUtils.reportAttached(view, false)
Assert.assertNotNull(controller.getView())
// Ensure re-setting RELEASE_DETACH releases
controller.retainViewMode = RetainViewMode.RELEASE_DETACH
Assert.assertNull(controller.getView())
}
@Test
fun testActivityResult() {
val controller = TestController()
val expectedCallState = CallState(true)
router.pushController(controller.asTransaction())
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
router.onActivityResult(1, Activity.RESULT_OK, null)
assertCalls(expectedCallState, controller)
// Ensure starting an activity for result gets us the result back
controller.startActivityForResult(Intent("action"), 1)
router.onActivityResult(1, Activity.RESULT_OK, null)
expectedCallState.onActivityResultCalls++
assertCalls(expectedCallState, controller)
// Ensure requesting a result w/o calling startActivityForResult works
controller.registerForActivityResult(2)
router.onActivityResult(2, Activity.RESULT_OK, null)
expectedCallState.onActivityResultCalls++
assertCalls(expectedCallState, controller)
}
@Test
fun testActivityResultForChild() {
val parent = TestController()
val child = TestController()
router.pushController(parent.asTransaction())
val childContainer = parent.view!!.findViewById<AttachFakingFrameLayout>(TestController.VIEW_ID)
childContainer.setAttached(true)
parent.getChildRouter(childContainer)
.setRoot(child.asTransaction())
val childExpectedCallState = CallState(true)
val parentExpectedCallState = CallState(true)
// Ensure that calling onActivityResult w/o requesting a result doesn't do anything
router.onActivityResult(1, Activity.RESULT_OK, null)
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
// Ensure starting an activity for result gets us the result back
child.startActivityForResult(Intent("action"), 1)
router.onActivityResult(1, Activity.RESULT_OK, null)
childExpectedCallState.onActivityResultCalls++
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
// Ensure requesting a result w/o calling startActivityForResult works
child.registerForActivityResult(2)
router.onActivityResult(2, Activity.RESULT_OK, null)
childExpectedCallState.onActivityResultCalls++
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
}
@Test
fun testPermissionResult() {
val requestedPermissions = arrayOf("test")
val controller = TestController()
val expectedCallState = CallState(true)
router.pushController(controller.asTransaction())
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, intArrayOf(1))
assertCalls(expectedCallState, controller)
// Ensure requesting the permission gets us the result back
try {
controller.requestPermissions(requestedPermissions, 1)
} catch (ignored: NoSuchMethodError) { }
router.onRequestPermissionsResult(
controller.instanceId,
1,
requestedPermissions,
intArrayOf(1)
)
expectedCallState.onRequestPermissionsResultCalls++
assertCalls(expectedCallState, controller)
}
@Test
fun testPermissionResultForChild() {
val requestedPermissions = arrayOf("test")
val parent = TestController()
val child = TestController()
router.pushController(parent.asTransaction())
val childContainer = parent.view!!.findViewById<AttachFakingFrameLayout>(TestController.VIEW_ID)
childContainer.setAttached(true)
parent.getChildRouter(childContainer)
.setRoot(child.asTransaction())
val childExpectedCallState = CallState(true)
val parentExpectedCallState = CallState(true)
// Ensure that calling handleRequestedPermission w/o requesting a result doesn't do anything
router.onRequestPermissionsResult("anotherId", 1, requestedPermissions, intArrayOf(1))
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
// Ensure requesting the permission gets us the result back
try {
child.requestPermissions(requestedPermissions, 1)
} catch (ignored: NoSuchMethodError) { }
router.onRequestPermissionsResult(child.instanceId, 1, requestedPermissions, intArrayOf(1))
childExpectedCallState.onRequestPermissionsResultCalls++
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
}
@Test
fun testOptionsMenu() {
val controller = TestController()
val expectedCallState = CallState(true)
router.pushController(controller.asTransaction())
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
assertCalls(expectedCallState, controller)
// Ensure calling onCreateOptionsMenu with a menu works
controller.setHasOptionsMenu(true)
expectedCallState.createOptionsMenuCalls++
assertCalls(expectedCallState, controller)
// Ensure it'll still get called back next time onCreateOptionsMenu is called
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
expectedCallState.createOptionsMenuCalls++
assertCalls(expectedCallState, controller)
// Ensure we stop getting them when we hide it
controller.setOptionsMenuHidden(true)
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
assertCalls(expectedCallState, controller)
// Ensure we get the callback them when we un-hide it
controller.setOptionsMenuHidden(false)
expectedCallState.createOptionsMenuCalls++
assertCalls(expectedCallState, controller)
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
expectedCallState.createOptionsMenuCalls++
assertCalls(expectedCallState, controller)
// Ensure we don't get the callback when we no longer have a menu
controller.setHasOptionsMenu(false)
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
assertCalls(expectedCallState, controller)
}
@Test
fun testOptionsMenuForChild() {
val parent = TestController()
val child = TestController()
router.pushController(parent.asTransaction())
val childContainer = parent.view!!.findViewById<AttachFakingFrameLayout>(TestController.VIEW_ID)
childContainer.setAttached(true)
parent.getChildRouter(childContainer)
.setRoot(child.asTransaction())
val childExpectedCallState = CallState(true)
val parentExpectedCallState = CallState(true)
// Ensure that calling onCreateOptionsMenu w/o declaring that we have one doesn't do anything
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
// Ensure calling onCreateOptionsMenu with a menu works
child.setHasOptionsMenu(true)
childExpectedCallState.createOptionsMenuCalls++
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
// Ensure it'll still get called back next time onCreateOptionsMenu is called
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
childExpectedCallState.createOptionsMenuCalls++
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
// Ensure we stop getting them when we hide it
child.setOptionsMenuHidden(true)
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
// Ensure we get the callback them when we un-hide it
child.setOptionsMenuHidden(false)
childExpectedCallState.createOptionsMenuCalls++
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
childExpectedCallState.createOptionsMenuCalls++
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
// Ensure we don't get the callback when we no longer have a menu
child.setHasOptionsMenu(false)
router.onCreateOptionsMenu(menu(), menuInflater(router.activity!!))
assertCalls(childExpectedCallState, child)
assertCalls(parentExpectedCallState, parent)
}
@Test
fun testAddRemoveChildControllers() {
val parent = TestController()
val child1 = TestController()
val child2 = TestController()
router.pushController(parent.asTransaction())
Assert.assertEquals(0, parent.childRouters.size)
Assert.assertNull(child1.parentController)
Assert.assertNull(child2.parentController)
var childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
childRouter.setPopsLastView(true)
childRouter.setRoot(child1.asTransaction())
Assert.assertEquals(1, parent.childRouters.size)
Assert.assertEquals(childRouter, parent.childRouters[0])
Assert.assertEquals(1, childRouter.backstackSize)
Assert.assertEquals(child1, childRouter.controllers[0])
Assert.assertEquals(parent, child1.parentController)
Assert.assertNull(child2.parentController)
childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.VIEW_ID))
childRouter.pushController(child2.asTransaction())
Assert.assertEquals(1, parent.childRouters.size)
Assert.assertEquals(childRouter, parent.childRouters[0])
Assert.assertEquals(2, childRouter.backstackSize)
Assert.assertEquals(child1, childRouter.controllers[0])
Assert.assertEquals(child2, childRouter.controllers[1])
Assert.assertEquals(parent, child1.parentController)
Assert.assertEquals(parent, child2.parentController)
childRouter.popController(child2)
Assert.assertEquals(1, parent.childRouters.size)
Assert.assertEquals(childRouter, parent.childRouters[0])
Assert.assertEquals(1, childRouter.backstackSize)
Assert.assertEquals(child1, childRouter.controllers[0])
Assert.assertEquals(parent, child1.parentController)
Assert.assertNull(child2.parentController)
childRouter.popController(child1)
shadowOf(Looper.getMainLooper()).idle()
Assert.assertEquals(1, parent.childRouters.size)
Assert.assertEquals(childRouter, parent.childRouters[0])
Assert.assertEquals(0, childRouter.backstackSize)
Assert.assertNull(child1.parentController)
Assert.assertNull(child2.parentController)
}
@Test
fun testAddRemoveChildRouters() {
val parent = TestController()
val child1 = TestController()
val child2 = TestController()
router.pushController(parent.asTransaction())
Assert.assertEquals(0, parent.childRouters.size)
Assert.assertNull(child1.parentController)
Assert.assertNull(child2.parentController)
val childRouter1 = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1))
val childRouter2 = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_2))
childRouter1.setRoot(child1.asTransaction())
childRouter2.setRoot(child2.asTransaction())
Assert.assertEquals(2, parent.childRouters.size)
Assert.assertEquals(childRouter1, parent.childRouters[0])
Assert.assertEquals(childRouter2, parent.childRouters[1])
Assert.assertEquals(1, childRouter1.backstackSize)
Assert.assertEquals(1, childRouter2.backstackSize)
Assert.assertEquals(child1, childRouter1.controllers[0])
Assert.assertEquals(child2, childRouter2.controllers[0])
Assert.assertEquals(parent, child1.parentController)
Assert.assertEquals(parent, child2.parentController)
parent.removeChildRouter(childRouter2)
shadowOf(Looper.getMainLooper()).idle()
Assert.assertEquals(1, parent.childRouters.size)
Assert.assertEquals(childRouter1, parent.childRouters[0])
Assert.assertEquals(1, childRouter1.backstackSize)
Assert.assertEquals(0, childRouter2.backstackSize)
Assert.assertEquals(child1, childRouter1.controllers[0])
Assert.assertEquals(parent, child1.parentController)
Assert.assertNull(child2.parentController)
parent.removeChildRouter(childRouter1)
Assert.assertEquals(0, parent.childRouters.size)
Assert.assertEquals(0, childRouter1.backstackSize)
Assert.assertEquals(0, childRouter2.backstackSize)
Assert.assertNull(child1.parentController)
Assert.assertNull(child2.parentController)
}
@Test
fun testRestoredChildRouterBackstack() {
val parent = TestController()
router.pushController(parent.asTransaction())
ViewUtils.reportAttached(parent.view, true)
val childTransaction1 = TestController().asTransaction()
val childTransaction2 = TestController().asTransaction()
var childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1))
childRouter.setPopsLastView(true)
childRouter.setRoot(childTransaction1)
childRouter.pushController(childTransaction2)
val savedState = Bundle()
childRouter.saveInstanceState(savedState)
parent.removeChildRouter(childRouter)
childRouter = parent.getChildRouter(parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1))
Assert.assertEquals(0, childRouter.backstackSize)
childRouter.restoreInstanceState(savedState)
childRouter.rebindIfNeeded()
Assert.assertEquals(2, childRouter.backstackSize)
val restoredChildTransaction1 = childRouter.getBackstack()[0]
val restoredChildTransaction2 = childRouter.getBackstack()[1]
Assert.assertEquals(
childTransaction1.transactionIndex,
restoredChildTransaction1.transactionIndex
)
Assert.assertEquals(
childTransaction1.controller.getInstanceId(),
restoredChildTransaction1.controller.getInstanceId()
)
Assert.assertEquals(
childTransaction2.transactionIndex,
restoredChildTransaction2.transactionIndex
)
Assert.assertEquals(
childTransaction2.controller.getInstanceId(),
restoredChildTransaction2.controller.getInstanceId()
)
Assert.assertTrue(parent.handleBack())
Assert.assertEquals(1, childRouter.backstackSize)
Assert.assertEquals(restoredChildTransaction1, childRouter.getBackstack()[0])
Assert.assertTrue(parent.handleBack())
Assert.assertEquals(0, childRouter.backstackSize)
}
private fun assertCalls(callState: CallState, controller: TestController) {
shadowOf(Looper.getMainLooper()).idle()
Assert.assertEquals(
"Expected call counts and controller call counts do not match.",
callState,
controller.currentCallState
)
}
private fun menu(): Menu {
return object : Menu {
override fun add(p0: CharSequence?): MenuItem {
TODO("Not yet implemented")
}
override fun add(p0: Int): MenuItem {
TODO("Not yet implemented")
}
override fun add(p0: Int, p1: Int, p2: Int, p3: CharSequence?): MenuItem {
TODO("Not yet implemented")
}
override fun add(p0: Int, p1: Int, p2: Int, p3: Int): MenuItem {
TODO("Not yet implemented")
}
override fun addSubMenu(p0: CharSequence?): SubMenu {
TODO("Not yet implemented")
}
override fun addSubMenu(p0: Int): SubMenu {
TODO("Not yet implemented")
}
override fun addSubMenu(p0: Int, p1: Int, p2: Int, p3: CharSequence?): SubMenu {
TODO("Not yet implemented")
}
override fun addSubMenu(p0: Int, p1: Int, p2: Int, p3: Int): SubMenu {
TODO("Not yet implemented")
}
override fun addIntentOptions(
p0: Int,
p1: Int,
p2: Int,
p3: ComponentName?,
p4: Array<out Intent>?,
p5: Intent?,
p6: Int,
p7: Array<out MenuItem>?
): Int {
TODO("Not yet implemented")
}
override fun removeItem(p0: Int) {
TODO("Not yet implemented")
}
override fun removeGroup(p0: Int) {
TODO("Not yet implemented")
}
override fun clear() {
TODO("Not yet implemented")
}
override fun setGroupCheckable(p0: Int, p1: Boolean, p2: Boolean) {
TODO("Not yet implemented")
}
override fun setGroupVisible(p0: Int, p1: Boolean) {
TODO("Not yet implemented")
}
override fun setGroupEnabled(p0: Int, p1: Boolean) {
TODO("Not yet implemented")
}
override fun hasVisibleItems(): Boolean {
TODO("Not yet implemented")
}
override fun findItem(p0: Int): MenuItem {
TODO("Not yet implemented")
}
override fun size(): Int {
TODO("Not yet implemented")
}
override fun getItem(p0: Int): MenuItem {
TODO("Not yet implemented")
}
override fun close() {
TODO("Not yet implemented")
}
override fun performShortcut(p0: Int, p1: KeyEvent?, p2: Int): Boolean {
TODO("Not yet implemented")
}
override fun isShortcutKey(p0: Int, p1: KeyEvent?): Boolean {
TODO("Not yet implemented")
}
override fun performIdentifierAction(p0: Int, p1: Int): Boolean {
TODO("Not yet implemented")
}
override fun setQwertyMode(p0: Boolean) {
TODO("Not yet implemented")
}
}
}
private fun menuInflater(context: Context): MenuInflater {
return MenuInflater(context)
}
}
@@ -4,7 +4,6 @@ import android.os.Bundle;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -1,406 +0,0 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.internal.LifecycleHandler;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.AttachFakingFrameLayout;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ReattachCaseTests {
private ActivityProxy activityProxy;
private Router router;
public void createActivityController(Bundle savedInstanceState) {
activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
}
@Before
public void setup() {
createActivityController(null);
}
@Test
public void testNeedsAttachingOnPauseAndOrientation() {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
sleepWakeDevice();
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
activityProxy.rotate();
router.rebindIfNeeded();
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
}
@Test
public void testChildNeedsAttachOnPauseAndOrientation() {
final Controller controllerA = new TestController();
final Controller childController = new TestController();
final Controller controllerB = new TestController();
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
Router childRouter = controllerA.getChildRouter((ViewGroup) controllerA.getView().findViewById(TestController.VIEW_ID));
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertTrue(controllerA.isAttached());
assertTrue(childController.isAttached());
assertFalse(controllerB.isAttached());
sleepWakeDevice();
assertTrue(controllerA.isAttached());
assertTrue(childController.isAttached());
assertFalse(controllerB.isAttached());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertFalse(controllerA.isAttached());
assertFalse(childController.isAttached());
assertTrue(controllerB.isAttached());
activityProxy.rotate();
router.rebindIfNeeded();
assertFalse(controllerA.isAttached());
assertFalse(childController.isAttached());
assertTrue(childController.getNeedsAttach());
assertTrue(controllerB.isAttached());
}
@Test
public void testChildHandleBackOnOrientation() {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
final TestController childController = new TestController();
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
assertFalse(childController.isAttached());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
Router childRouter = controllerB.getChildRouter((ViewGroup)controllerB.getView().findViewById(TestController.VIEW_ID));
childRouter.setPopsLastView(true);
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
activityProxy.rotate();
router.rebindIfNeeded();
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
router.handleBack();
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertFalse(childController.isAttached());
router.handleBack();
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
assertFalse(childController.isAttached());
}
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/86#issuecomment-231381271
@Test
public void testReusedChildRouterHandleBackOnOrientation() {
TestController controllerA = new TestController();
TestController controllerB = new TestController();
TestController childController = new TestController();
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
assertFalse(childController.isAttached());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
Router childRouter = controllerB.getChildRouter((ViewGroup)controllerB.getView().findViewById(TestController.VIEW_ID));
childRouter.setPopsLastView(true);
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
router.handleBack();
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertFalse(childController.isAttached());
childController = new TestController();
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
activityProxy.rotate();
router.rebindIfNeeded();
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
router.handleBack();
childController = new TestController();
childRouter.pushController(RouterTransaction.with(childController)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertTrue(childController.isAttached());
router.handleBack();
assertFalse(controllerA.isAttached());
assertTrue(controllerB.isAttached());
assertFalse(childController.isAttached());
router.handleBack();
assertTrue(controllerA.isAttached());
assertFalse(controllerB.isAttached());
assertFalse(childController.isAttached());
}
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/367
@Test
public void testViewIsAttachedAfterStartedActivityIsRecreated() {
Controller controller1 = new TestController();
Controller controller2 = new TestController();
router.setRoot(RouterTransaction.with(controller1));
assertTrue(controller1.isAttached());
// Lock screen
Bundle bundle = new Bundle();
activityProxy.pause().saveInstanceState(bundle).stop(false);
// Push a 2nd controller, which will rotate the screen once it unlocked
router.pushController(RouterTransaction.with(controller2));
assertTrue(controller2.isAttached());
assertTrue(controller2.getNeedsAttach());
// Unlock screen and rotate
activityProxy.start();
activityProxy.rotate();
assertTrue(controller2.isAttached());
}
@Test
public void testPopMiddleControllerAttaches() {
Controller controller1 = new TestController();
Controller controller2 = new TestController();
Controller controller3 = new TestController();
router.setRoot(RouterTransaction.with(controller1));
router.pushController(RouterTransaction.with(controller2));
router.pushController(RouterTransaction.with(controller3));
router.popController(controller2);
assertFalse(controller1.isAttached());
assertFalse(controller2.isAttached());
assertTrue(controller3.isAttached());
controller1 = new TestController();
controller2 = new TestController();
controller3 = new TestController();
router.setRoot(RouterTransaction.with(controller1));
router.pushController(RouterTransaction.with(controller2));
router.pushController(RouterTransaction.with(controller3).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()));
router.popController(controller2);
assertTrue(controller1.isAttached());
assertFalse(controller2.isAttached());
assertTrue(controller3.isAttached());
}
@Test
public void testPendingChanges() {
Controller controller1 = new TestController();
Controller controller2 = new TestController();
ActivityProxy activityProxy = new ActivityProxy().create(null);
AttachFakingFrameLayout container = new AttachFakingFrameLayout(activityProxy.getActivity());
container.setNeedDelayPost(true); // to simulate calling posts after resume
activityProxy.setView(container);
Router router = Conductor.attachRouter(activityProxy.getActivity(), container, null);
router.setRoot(RouterTransaction.with(controller1));
router.pushController(RouterTransaction.with(controller2));
activityProxy.start().resume();
container.setNeedDelayPost(false);
assertTrue(controller2.isAttached());
}
@Test
public void testPendingChangesAfterRotation() {
Controller controller1 = new TestController();
Controller controller2 = new TestController();
// first activity
ActivityProxy activityProxy = new ActivityProxy().create(null);
AttachFakingFrameLayout container1 = new AttachFakingFrameLayout(activityProxy.getActivity());
container1.setNeedDelayPost(true); // delay forever as view will be removed
activityProxy.setView(container1);
// first attachRouter: Conductor.attachRouter(activityProxy.getActivity(), container1, null)
LifecycleHandler lifecycleHandler = LifecycleHandler.install(activityProxy.getActivity());
Router router = lifecycleHandler.getRouter(container1, null);
router.setRoot(RouterTransaction.with(controller1));
// setup controllers
router.pushController(RouterTransaction.with(controller2));
// simulate setRequestedOrientation in activity onCreate
activityProxy.start().resume();
Bundle savedState = new Bundle();
activityProxy.saveInstanceState(savedState).pause().stop(true);
// recreate activity and view
activityProxy = new ActivityProxy().create(savedState);
AttachFakingFrameLayout container2 = new AttachFakingFrameLayout(activityProxy.getActivity());
activityProxy.setView(container2);
// second attach router with the same lifecycleHandler (do manually as Roboelectric recreates retained fragments)
// Conductor.attachRouter(activityProxy.getActivity(), container2, savedState);
router = lifecycleHandler.getRouter(container2, savedState);
router.rebindIfNeeded();
activityProxy.start().resume();
assertTrue(controller2.isAttached());
}
@Test
public void testHostAvailableDuringRotation() {
final Controller controllerA = new TestController();
final Controller childControllerA = new TestController();
final Controller controllerB = new TestController();
final Controller childControllerB = new TestController();
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
Router childRouterA = controllerA.getChildRouter((ViewGroup) controllerA.getView().findViewById(TestController.VIEW_ID));
childRouterA.pushController(RouterTransaction.with(childControllerA)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertNotNull(controllerA.getActivity());
assertNotNull(childControllerA.getActivity());
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
Router childRouterB = controllerB.getChildRouter((ViewGroup) controllerB.getView().findViewById(TestController.VIEW_ID));
childRouterB.pushController(RouterTransaction.with(childControllerB)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertNotNull(controllerA.getActivity());
assertNotNull(childControllerA.getActivity());
assertNotNull(controllerB.getActivity());
assertNotNull(childControllerB.getActivity());
activityProxy.rotate();
assertNotNull(controllerA.getActivity());
assertNotNull(childControllerA.getActivity());
assertNotNull(controllerB.getActivity());
assertNotNull(childControllerB.getActivity());
router.rebindIfNeeded();
assertNotNull(controllerA.getActivity());
assertNotNull(childControllerA.getActivity());
assertNotNull(controllerB.getActivity());
assertNotNull(childControllerB.getActivity());
}
private void sleepWakeDevice() {
activityProxy.saveInstanceState(new Bundle()).pause();
activityProxy.resume();
}
}
@@ -0,0 +1,400 @@
package com.bluelinelabs.conductor
import android.os.Bundle
import android.os.Looper
import com.bluelinelabs.conductor.Conductor.attachRouter
import com.bluelinelabs.conductor.internal.LifecycleHandler
import com.bluelinelabs.conductor.util.ActivityProxy
import com.bluelinelabs.conductor.util.AttachFakingFrameLayout
import com.bluelinelabs.conductor.util.MockChangeHandler
import com.bluelinelabs.conductor.util.TestActivity
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class ReattachCaseTests {
private val activityController = Robolectric.buildActivity(TestActivity::class.java).setup()
private val router = activityController.get().router
@Test
fun testNeedsAttachingOnPauseAndOrientation() {
val controllerA = TestController()
val controllerB = TestController()
router.pushController(
controllerA.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertTrue(controllerA.isAttached)
Assert.assertFalse(controllerB.isAttached)
sleepWakeDevice()
Assert.assertTrue(controllerA.isAttached)
Assert.assertFalse(controllerB.isAttached)
router.pushController(
controllerB.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
activityController.configurationChange()
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
}
@Test
fun testChildNeedsAttachOnPauseAndOrientation() {
val controllerA = TestController()
val childController = TestController()
val controllerB = TestController()
router.pushController(
controllerA.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val childRouter = controllerA.getChildRouter(
controllerA.view!!.findViewById(TestController.VIEW_ID)
)
childRouter.pushController(
childController.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertTrue(controllerA.isAttached)
Assert.assertTrue(childController.isAttached)
Assert.assertFalse(controllerB.isAttached)
sleepWakeDevice()
Assert.assertTrue(controllerA.isAttached)
Assert.assertTrue(childController.isAttached)
Assert.assertFalse(controllerB.isAttached)
router.pushController(
controllerB.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertFalse(controllerA.isAttached)
Assert.assertFalse(childController.isAttached)
Assert.assertTrue(controllerB.isAttached)
activityController.configurationChange()
Assert.assertFalse(controllerA.isAttached)
Assert.assertFalse(childController.isAttached)
Assert.assertTrue(childController.needsAttach)
Assert.assertTrue(controllerB.isAttached)
}
@Test
fun testChildHandleBackOnOrientation() {
val controllerA = TestController()
val controllerB = TestController()
val childController = TestController()
router.pushController(
controllerA.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertTrue(controllerA.isAttached)
Assert.assertFalse(controllerB.isAttached)
Assert.assertFalse(childController.isAttached)
router.pushController(
controllerB.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val childRouter = controllerB.getChildRouter(
controllerB.view!!.findViewById(TestController.VIEW_ID)
)
childRouter.setPopsLastView(true)
childRouter.pushController(
childController.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
Assert.assertTrue(childController.isAttached)
activityController.configurationChange()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
Assert.assertTrue(childController.isAttached)
router.handleBack()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
Assert.assertFalse(childController.isAttached)
router.handleBack()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertTrue(controllerA.isAttached)
Assert.assertFalse(controllerB.isAttached)
Assert.assertFalse(childController.isAttached)
}
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/86#issuecomment-231381271
@Test
fun testReusedChildRouterHandleBackOnOrientation() {
val controllerA = TestController()
val controllerB = TestController()
var childController = TestController()
router.pushController(
controllerA.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertTrue(controllerA.isAttached)
Assert.assertFalse(controllerB.isAttached)
Assert.assertFalse(childController.isAttached)
router.pushController(
controllerB.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val childRouter = controllerB.getChildRouter(
controllerB.view!!.findViewById(TestController.VIEW_ID)
)
childRouter.setPopsLastView(true)
childRouter.pushController(
childController.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
Assert.assertTrue(childController.isAttached)
router.handleBack()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
Assert.assertFalse(childController.isAttached)
childController = TestController()
childRouter.pushController(
childController.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
Assert.assertTrue(childController.isAttached)
activityController.configurationChange()
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
Assert.assertTrue(childController.isAttached)
router.handleBack()
childController = TestController()
childRouter.pushController(
childController.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
Assert.assertTrue(childController.isAttached)
router.handleBack()
Assert.assertFalse(controllerA.isAttached)
Assert.assertTrue(controllerB.isAttached)
Assert.assertFalse(childController.isAttached)
router.handleBack()
Assert.assertTrue(controllerA.isAttached)
Assert.assertFalse(controllerB.isAttached)
Assert.assertFalse(childController.isAttached)
}
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/367
@Test
fun testViewIsAttachedAfterStartedActivityIsRecreated() {
val controller1 = TestController()
val controller2 = TestController()
router.setRoot(controller1.asTransaction())
Assert.assertTrue(controller1.isAttached)
// Lock screen
val bundle = Bundle()
activityController.pause().saveInstanceState(bundle).stop()
// Push a 2nd controller, which will rotate the screen once it unlocked
router.pushController(controller2.asTransaction())
Assert.assertTrue(controller2.isAttached)
Assert.assertTrue(controller2.needsAttach)
// Unlock screen and rotate
activityController.start()
activityController.configurationChange()
Assert.assertTrue(controller2.isAttached)
}
@Test
fun testPopMiddleControllerAttaches() {
var controller1 = TestController()
var controller2 = TestController()
var controller3 = TestController()
router.setRoot(controller1.asTransaction())
router.pushController(controller2.asTransaction())
router.pushController(controller3.asTransaction())
router.popController(controller2)
Assert.assertFalse(controller1.isAttached)
Assert.assertFalse(controller2.isAttached)
Assert.assertTrue(controller3.isAttached)
controller1 = TestController()
controller2 = TestController()
controller3 = TestController()
router.setRoot(controller1.asTransaction())
router.pushController(controller2.asTransaction())
router.pushController(
controller3.asTransaction(
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
)
)
router.popController(controller2)
Assert.assertTrue(controller1.isAttached())
Assert.assertFalse(controller2.isAttached())
Assert.assertTrue(controller3.isAttached())
}
@Test
fun testPendingChanges() {
val controller1 = TestController()
val controller2 = TestController()
val activityProxy = ActivityProxy().create(null)
val container = AttachFakingFrameLayout(activityProxy.activity)
container.setNeedDelayPost(true) // to simulate calling posts after resume
activityProxy.view = container
val router = attachRouter(activityProxy.activity, container, null)
router.setRoot(controller1.asTransaction())
router.pushController(controller2.asTransaction())
activityProxy.start().resume()
container.setNeedDelayPost(false)
Assert.assertTrue(controller2.isAttached)
}
@Test
fun testPendingChangesAfterRotation() {
val controller1 = TestController()
val controller2 = TestController()
// first activity
var activityProxy = ActivityProxy().create(null)
val container1 = AttachFakingFrameLayout(activityProxy.activity)
container1.setNeedDelayPost(true) // delay forever as view will be removed
activityProxy.view = container1
// first attachRouter: Conductor.attachRouter(activityProxy.getActivity(), container1, null)
val lifecycleHandler = LifecycleHandler.install(activityProxy.activity)
var router = lifecycleHandler.getRouter(container1, null)
router.setRoot(controller1.asTransaction())
// setup controllers
router.pushController(controller2.asTransaction())
// simulate setRequestedOrientation in activity onCreate
activityProxy.start().resume()
val savedState = Bundle()
activityProxy.saveInstanceState(savedState).pause().stop(true)
// recreate activity and view
activityProxy = ActivityProxy().create(savedState)
val container2 = AttachFakingFrameLayout(activityProxy.activity)
activityProxy.view = container2
// second attach router with the same lifecycleHandler (do manually as robolectric recreates retained fragments)
// Conductor.attachRouter(activityProxy.getActivity(), container2, savedState);
router = lifecycleHandler.getRouter(container2, savedState)
router.rebindIfNeeded()
activityProxy.start().resume()
Assert.assertTrue(controller2.isAttached)
}
@Test
fun testHostAvailableDuringRotation() {
val controllerA = TestController()
val childControllerA = TestController()
val controllerB = TestController()
val childControllerB = TestController()
router.pushController(
controllerA.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val childRouterA = controllerA.getChildRouter(
controllerA.view!!.findViewById(TestController.VIEW_ID)
)
childRouterA.pushController(
childControllerA.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertNotNull(controllerA.activity)
Assert.assertNotNull(childControllerA.activity)
router.pushController(
controllerB.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
val childRouterB = controllerB.getChildRouter(
controllerB.view!!.findViewById(
TestController.VIEW_ID
)
)
childRouterB.pushController(
childControllerB.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertNotNull(controllerA.activity)
Assert.assertNotNull(childControllerA.activity)
Assert.assertNotNull(controllerB.activity)
Assert.assertNotNull(childControllerB.activity)
activityController.configurationChange()
Assert.assertNotNull(controllerA.activity)
Assert.assertNotNull(childControllerA.activity)
Assert.assertNotNull(controllerB.activity)
Assert.assertNotNull(childControllerB.activity)
}
private fun sleepWakeDevice() {
activityController.saveInstanceState(Bundle()).pause()
activityController.resume()
}
}
@@ -1,354 +0,0 @@
package com.bluelinelabs.conductor;
import android.view.View;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class RouterChangeHandlerTests {
private Router router;
@Before
public void setup() {
ActivityProxy activityProxy = new ActivityProxy().create(null).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), null);
}
@Test
public void testSetRootHandler() {
MockChangeHandler handler = MockChangeHandler.taggedHandler("root", true);
TestController rootController = new TestController();
router.setRoot(RouterTransaction.with(rootController).pushChangeHandler(handler));
assertTrue(rootController.changeHandlerHistory.isValidHistory);
assertNull(rootController.changeHandlerHistory.latestFromView());
assertNotNull(rootController.changeHandlerHistory.latestToView());
assertEquals(rootController.getView(), rootController.changeHandlerHistory.latestToView());
assertTrue(rootController.changeHandlerHistory.latestIsPush());
assertEquals(handler.tag, rootController.changeHandlerHistory.latestChangeHandler().tag);
}
@Test
public void testPushPopHandlers() {
TestController rootController = new TestController();
router.setRoot(RouterTransaction.with(rootController).pushChangeHandler(MockChangeHandler.defaultHandler()));
View rootView = rootController.getView();
MockChangeHandler pushHandler = MockChangeHandler.taggedHandler("push", true);
MockChangeHandler popHandler = MockChangeHandler.taggedHandler("pop", true);
TestController pushController = new TestController();
router.pushController(RouterTransaction.with(pushController).pushChangeHandler(pushHandler).popChangeHandler(popHandler));
assertTrue(rootController.changeHandlerHistory.isValidHistory);
assertTrue(pushController.changeHandlerHistory.isValidHistory);
assertNotNull(pushController.changeHandlerHistory.latestFromView());
assertNotNull(pushController.changeHandlerHistory.latestToView());
assertEquals(rootView, pushController.changeHandlerHistory.latestFromView());
assertEquals(pushController.getView(), pushController.changeHandlerHistory.latestToView());
assertTrue(pushController.changeHandlerHistory.latestIsPush());
assertEquals(pushHandler.tag, pushController.changeHandlerHistory.latestChangeHandler().tag);
View pushView = pushController.getView();
router.popController(pushController);
assertNotNull(pushController.changeHandlerHistory.latestFromView());
assertNotNull(pushController.changeHandlerHistory.latestToView());
assertEquals(pushView, pushController.changeHandlerHistory.fromViewAt(1));
assertEquals(rootController.getView(), pushController.changeHandlerHistory.latestToView());
assertFalse(pushController.changeHandlerHistory.latestIsPush());
assertEquals(popHandler.tag, pushController.changeHandlerHistory.latestChangeHandler().tag);
}
@Test
public void testResetRootHandlers() {
TestController initialController1 = new TestController();
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1));
TestController initialController2 = new TestController();
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2));
View initialView1 = initialController1.getView();
View initialView2 = initialController2.getView();
TestController newRootController = new TestController();
MockChangeHandler newRootHandler = MockChangeHandler.taggedHandler("newRootHandler", true);
router.setRoot(RouterTransaction.with(newRootController).pushChangeHandler(newRootHandler));
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
assertTrue(newRootController.changeHandlerHistory.isValidHistory);
assertEquals(3, initialController1.changeHandlerHistory.size());
assertEquals(2, initialController2.changeHandlerHistory.size());
assertEquals(1, newRootController.changeHandlerHistory.size());
assertNotNull(initialController1.changeHandlerHistory.latestToView());
assertEquals(newRootController.getView(), initialController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
assertEquals(newRootHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
assertNull(initialController2.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
assertEquals(newRootHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController2.changeHandlerHistory.latestIsPush());
assertNotNull(newRootController.changeHandlerHistory.latestToView());
assertEquals(newRootController.getView(), newRootController.changeHandlerHistory.latestToView());
assertEquals(initialView1, newRootController.changeHandlerHistory.latestFromView());
assertEquals(newRootHandler.tag, newRootController.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(newRootController.changeHandlerHistory.latestIsPush());
}
@Test
public void testSetBackstackHandlers() {
TestController initialController1 = new TestController();
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1));
TestController initialController2 = new TestController();
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2));
View initialView1 = initialController1.getView();
View initialView2 = initialController2.getView();
TestController newController1 = new TestController();
TestController newController2 = new TestController();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
List<RouterTransaction> newBackstack = Arrays.asList(
RouterTransaction.with(newController1),
RouterTransaction.with(newController2)
);
router.setBackstack(newBackstack, setBackstackHandler);
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
assertTrue(newController1.changeHandlerHistory.isValidHistory);
assertEquals(3, initialController1.changeHandlerHistory.size());
assertEquals(2, initialController2.changeHandlerHistory.size());
assertEquals(0, newController1.changeHandlerHistory.size());
assertEquals(1, newController2.changeHandlerHistory.size());
assertNotNull(initialController1.changeHandlerHistory.latestToView());
assertEquals(newController2.getView(), initialController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
assertNull(initialController2.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController2.changeHandlerHistory.latestIsPush());
assertNotNull(newController2.changeHandlerHistory.latestToView());
assertEquals(newController2.getView(), newController2.changeHandlerHistory.latestToView());
assertEquals(initialView1, newController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(newController2.changeHandlerHistory.latestIsPush());
}
@Test
public void testSetBackstackWithTwoVisibleHandlers() {
TestController initialController1 = new TestController();
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1));
TestController initialController2 = new TestController();
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2));
View initialView1 = initialController1.getView();
View initialView2 = initialController2.getView();
TestController newController1 = new TestController();
TestController newController2 = new TestController();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
MockChangeHandler pushController2Handler = MockChangeHandler.noRemoveViewOnPushHandler("pushController2");
List<RouterTransaction> newBackstack = Arrays.asList(
RouterTransaction.with(newController1),
RouterTransaction.with(newController2).pushChangeHandler(pushController2Handler)
);
router.setBackstack(newBackstack, setBackstackHandler);
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
assertTrue(newController1.changeHandlerHistory.isValidHistory);
assertEquals(3, initialController1.changeHandlerHistory.size());
assertEquals(2, initialController2.changeHandlerHistory.size());
assertEquals(2, newController1.changeHandlerHistory.size());
assertEquals(1, newController2.changeHandlerHistory.size());
assertNotNull(initialController1.changeHandlerHistory.latestToView());
assertEquals(newController1.getView(), initialController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
assertNull(initialController2.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController2.changeHandlerHistory.latestIsPush());
assertNotNull(newController1.changeHandlerHistory.latestToView());
assertEquals(newController1.getView(), newController1.changeHandlerHistory.toViewAt(0));
assertEquals(newController2.getView(), newController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, newController1.changeHandlerHistory.fromViewAt(0));
assertEquals(newController1.getView(), newController1.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, newController1.changeHandlerHistory.changeHandlerAt(0).tag);
assertEquals(pushController2Handler.tag, newController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(newController1.changeHandlerHistory.latestIsPush());
assertNotNull(newController2.changeHandlerHistory.latestToView());
assertEquals(newController2.getView(), newController2.changeHandlerHistory.latestToView());
assertEquals(newController1.getView(), newController2.changeHandlerHistory.latestFromView());
assertEquals(pushController2Handler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(newController2.changeHandlerHistory.latestIsPush());
}
@Test
public void testSetBackstackForPushHandlers() {
TestController initialController = new TestController();
MockChangeHandler initialPushHandler = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler = MockChangeHandler.taggedHandler("initialPop1", true);
RouterTransaction initialTransaction = RouterTransaction.with(initialController).pushChangeHandler(initialPushHandler).popChangeHandler(initialPopHandler);
router.setRoot(initialTransaction);
View initialView = initialController.getView();
TestController newController = new TestController();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
List<RouterTransaction> newBackstack = Arrays.asList(
initialTransaction,
RouterTransaction.with(newController)
);
router.setBackstack(newBackstack, setBackstackHandler);
assertTrue(initialController.changeHandlerHistory.isValidHistory);
assertTrue(newController.changeHandlerHistory.isValidHistory);
assertEquals(2, initialController.changeHandlerHistory.size());
assertEquals(1, newController.changeHandlerHistory.size());
assertNotNull(initialController.changeHandlerHistory.latestToView());
assertEquals(newController.getView(), initialController.changeHandlerHistory.latestToView());
assertEquals(initialView, initialController.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController.changeHandlerHistory.latestIsPush());
assertTrue(newController.changeHandlerHistory.latestIsPush());
}
@Test
public void testSetBackstackForInvertHandlersWithRemovesView() {
TestController initialController1 = new TestController();
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
RouterTransaction initialTransaction1 = RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1);
router.setRoot(initialTransaction1);
TestController initialController2 = new TestController();
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", true);
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", true);
RouterTransaction initialTransaction2 = RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2);
router.pushController(initialTransaction2);
View initialView2 = initialController2.getView();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
List<RouterTransaction> newBackstack = Arrays.asList(
initialTransaction2,
initialTransaction1
);
router.setBackstack(newBackstack, setBackstackHandler);
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
assertEquals(3, initialController1.changeHandlerHistory.size());
assertEquals(2, initialController2.changeHandlerHistory.size());
assertNotNull(initialController1.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController1.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
assertFalse(initialController1.changeHandlerHistory.latestIsPush());
assertNotNull(initialController2.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
assertFalse(initialController2.changeHandlerHistory.latestIsPush());
}
@Test
public void testSetBackstackForInvertHandlersWithoutRemovesView() {
TestController initialController1 = new TestController();
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
RouterTransaction initialTransaction1 = RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1);
router.setRoot(initialTransaction1);
TestController initialController2 = new TestController();
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
RouterTransaction initialTransaction2 = RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2);
router.pushController(initialTransaction2);
View initialView1 = initialController1.getView();
View initialView2 = initialController2.getView();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
List<RouterTransaction> newBackstack = Arrays.asList(
initialTransaction2,
initialTransaction1
);
router.setBackstack(newBackstack, setBackstackHandler);
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
assertEquals(2, initialController1.changeHandlerHistory.size());
assertEquals(2, initialController2.changeHandlerHistory.size());
assertNotNull(initialController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
assertEquals(initialPushHandler2.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
assertNull(initialController2.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
assertFalse(initialController2.changeHandlerHistory.latestIsPush());
}
}
@@ -0,0 +1,425 @@
package com.bluelinelabs.conductor
import com.bluelinelabs.conductor.util.MockChangeHandler
import com.bluelinelabs.conductor.util.TestActivity
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class RouterChangeHandlerTests {
private val router = Robolectric.buildActivity(TestActivity::class.java)
.setup()
.get()
.router
@Test
fun testSetRootHandler() {
val handler = MockChangeHandler.taggedHandler("root", true)
val rootController = TestController()
router.setRoot(
rootController.asTransaction(pushChangeHandler = handler)
)
Assert.assertTrue(rootController.changeHandlerHistory.isValidHistory)
Assert.assertNull(rootController.changeHandlerHistory.latestFromView())
Assert.assertNotNull(rootController.changeHandlerHistory.latestToView())
Assert.assertEquals(
rootController.view,
rootController.changeHandlerHistory.latestToView()
)
Assert.assertTrue(rootController.changeHandlerHistory.latestIsPush())
Assert.assertEquals(handler.tag, rootController.changeHandlerHistory.latestChangeHandler().tag)
}
@Test
fun testPushPopHandlers() {
val rootController = TestController()
router.setRoot(
rootController.asTransaction(pushChangeHandler = MockChangeHandler.defaultHandler())
)
val rootView = rootController.view
val pushHandler = MockChangeHandler.taggedHandler("push", true)
val popHandler = MockChangeHandler.taggedHandler("pop", true)
val pushController = TestController()
router.pushController(
pushController.asTransaction(
pushChangeHandler = pushHandler,
popChangeHandler = popHandler
)
)
Assert.assertTrue(rootController.changeHandlerHistory.isValidHistory)
Assert.assertTrue(pushController.changeHandlerHistory.isValidHistory)
Assert.assertNotNull(pushController.changeHandlerHistory.latestFromView())
Assert.assertNotNull(pushController.changeHandlerHistory.latestToView())
Assert.assertEquals(rootView, pushController.changeHandlerHistory.latestFromView())
Assert.assertEquals(
pushController.view,
pushController.changeHandlerHistory.latestToView()
)
Assert.assertTrue(pushController.changeHandlerHistory.latestIsPush())
Assert.assertEquals(
pushHandler.tag,
pushController.changeHandlerHistory.latestChangeHandler().tag
)
val pushView = pushController.view
router.popController(pushController)
Assert.assertNotNull(pushController.changeHandlerHistory.latestFromView())
Assert.assertNotNull(pushController.changeHandlerHistory.latestToView())
Assert.assertEquals(pushView, pushController.changeHandlerHistory.fromViewAt(1))
Assert.assertEquals(
rootController.view,
pushController.changeHandlerHistory.latestToView()
)
Assert.assertFalse(pushController.changeHandlerHistory.latestIsPush())
Assert.assertEquals(
popHandler.tag,
pushController.changeHandlerHistory.latestChangeHandler().tag
)
}
@Test
fun testResetRootHandlers() {
val initialController1 = TestController()
router.setRoot(
initialController1.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
)
)
val initialController2 = TestController()
router.pushController(
initialController2.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush2", false),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop2", false)
)
)
val initialView1 = initialController1.view
val initialView2 = initialController2.view
val newRootController = TestController()
val newRootHandlerTag = "newRootHandler"
router.setRoot(
newRootController.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler(newRootHandlerTag, true)
)
)
Assert.assertTrue(initialController1.changeHandlerHistory.isValidHistory)
Assert.assertTrue(initialController2.changeHandlerHistory.isValidHistory)
Assert.assertTrue(newRootController.changeHandlerHistory.isValidHistory)
Assert.assertEquals(3, initialController1.changeHandlerHistory.size().toLong())
Assert.assertEquals(2, initialController2.changeHandlerHistory.size().toLong())
Assert.assertEquals(1, newRootController.changeHandlerHistory.size().toLong())
Assert.assertNotNull(initialController1.changeHandlerHistory.latestToView())
Assert.assertEquals(
newRootController.view,
initialController1.changeHandlerHistory.latestToView()
)
Assert.assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView())
Assert.assertEquals(
newRootHandlerTag,
initialController1.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(initialController1.changeHandlerHistory.latestIsPush())
Assert.assertNull(initialController2.changeHandlerHistory.latestToView())
Assert.assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView())
Assert.assertEquals(
newRootHandlerTag,
initialController2.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(initialController2.changeHandlerHistory.latestIsPush())
Assert.assertNotNull(newRootController.changeHandlerHistory.latestToView())
Assert.assertEquals(
newRootController.view,
newRootController.changeHandlerHistory.latestToView()
)
Assert.assertEquals(initialView1, newRootController.changeHandlerHistory.latestFromView())
Assert.assertEquals(
newRootHandlerTag,
newRootController.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(newRootController.changeHandlerHistory.latestIsPush())
}
@Test
fun testSetBackstackHandlers() {
val initialController1 = TestController()
router.setRoot(
initialController1.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
)
)
val initialController2 = TestController()
router.pushController(
initialController2.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush2", false),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop2", false)
)
)
val initialView1 = initialController1.view
val initialView2 = initialController2.view
val newController1 = TestController()
val newController2 = TestController()
val setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true)
val newBackstack = listOf(newController1.asTransaction(), newController2.asTransaction())
router.setBackstack(newBackstack, setBackstackHandler)
Assert.assertTrue(initialController1.changeHandlerHistory.isValidHistory)
Assert.assertTrue(initialController2.changeHandlerHistory.isValidHistory)
Assert.assertTrue(newController1.changeHandlerHistory.isValidHistory)
Assert.assertEquals(3, initialController1.changeHandlerHistory.size().toLong())
Assert.assertEquals(2, initialController2.changeHandlerHistory.size().toLong())
Assert.assertEquals(0, newController1.changeHandlerHistory.size().toLong())
Assert.assertEquals(1, newController2.changeHandlerHistory.size().toLong())
Assert.assertNotNull(initialController1.changeHandlerHistory.latestToView())
Assert.assertEquals(
newController2.view,
initialController1.changeHandlerHistory.latestToView()
)
Assert.assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView())
Assert.assertEquals(
setBackstackHandler.tag,
initialController1.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(initialController1.changeHandlerHistory.latestIsPush())
Assert.assertNull(initialController2.changeHandlerHistory.latestToView())
Assert.assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView())
Assert.assertEquals(
setBackstackHandler.tag,
initialController2.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(initialController2.changeHandlerHistory.latestIsPush())
Assert.assertNotNull(newController2.changeHandlerHistory.latestToView())
Assert.assertEquals(
newController2.view,
newController2.changeHandlerHistory.latestToView()
)
Assert.assertEquals(initialView1, newController2.changeHandlerHistory.latestFromView())
Assert.assertEquals(
setBackstackHandler.tag,
newController2.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(newController2.changeHandlerHistory.latestIsPush())
}
@Test
fun testSetBackstackWithTwoVisibleHandlers() {
val initialController1 = TestController()
router.setRoot(
initialController1.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
)
)
val initialController2 = TestController()
router.pushController(
initialController2.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush2", false),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop2", false)
)
)
val initialView1 = initialController1.view
val initialView2 = initialController2.view
val newController1 = TestController()
val newController2 = TestController()
val setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true)
val pushController2Handler = MockChangeHandler.noRemoveViewOnPushHandler("pushController2")
val newBackstack = listOf(
newController1.asTransaction(),
newController2.asTransaction(pushChangeHandler = pushController2Handler)
)
router.setBackstack(newBackstack, setBackstackHandler)
Assert.assertTrue(initialController1.changeHandlerHistory.isValidHistory)
Assert.assertTrue(initialController2.changeHandlerHistory.isValidHistory)
Assert.assertTrue(newController1.changeHandlerHistory.isValidHistory)
Assert.assertEquals(3, initialController1.changeHandlerHistory.size().toLong())
Assert.assertEquals(2, initialController2.changeHandlerHistory.size().toLong())
Assert.assertEquals(2, newController1.changeHandlerHistory.size().toLong())
Assert.assertEquals(1, newController2.changeHandlerHistory.size().toLong())
Assert.assertNotNull(initialController1.changeHandlerHistory.latestToView())
Assert.assertEquals(
newController1.view,
initialController1.changeHandlerHistory.latestToView()
)
Assert.assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView())
Assert.assertEquals(
setBackstackHandler.tag,
initialController1.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(initialController1.changeHandlerHistory.latestIsPush())
Assert.assertNull(initialController2.changeHandlerHistory.latestToView())
Assert.assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView())
Assert.assertEquals(
setBackstackHandler.tag,
initialController2.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(initialController2.changeHandlerHistory.latestIsPush())
Assert.assertNotNull(newController1.changeHandlerHistory.latestToView())
Assert.assertEquals(newController1.view, newController1.changeHandlerHistory.toViewAt(0))
Assert.assertEquals(
newController2.view,
newController1.changeHandlerHistory.latestToView()
)
Assert.assertEquals(initialView1, newController1.changeHandlerHistory.fromViewAt(0))
Assert.assertEquals(
newController1.view,
newController1.changeHandlerHistory.latestFromView()
)
Assert.assertEquals(
setBackstackHandler.tag,
newController1.changeHandlerHistory.changeHandlerAt(0).tag
)
Assert.assertEquals(
pushController2Handler.tag,
newController1.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(newController1.changeHandlerHistory.latestIsPush())
Assert.assertNotNull(newController2.changeHandlerHistory.latestToView())
Assert.assertEquals(
newController2.view,
newController2.changeHandlerHistory.latestToView()
)
Assert.assertEquals(
newController1.view,
newController2.changeHandlerHistory.latestFromView()
)
Assert.assertEquals(
pushController2Handler.tag,
newController2.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(newController2.changeHandlerHistory.latestIsPush())
}
@Test
fun testSetBackstackForPushHandlers() {
val initialController = TestController()
val initialTransaction = initialController.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
)
router.setRoot(initialTransaction)
val initialView = initialController.view
val newController = TestController()
val setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true)
val newBackstack = listOf(initialTransaction, newController.asTransaction())
router.setBackstack(newBackstack, setBackstackHandler)
Assert.assertTrue(initialController.changeHandlerHistory.isValidHistory)
Assert.assertTrue(newController.changeHandlerHistory.isValidHistory)
Assert.assertEquals(2, initialController.changeHandlerHistory.size().toLong())
Assert.assertEquals(1, newController.changeHandlerHistory.size().toLong())
Assert.assertNotNull(initialController.changeHandlerHistory.latestToView())
Assert.assertEquals(
newController.view,
initialController.changeHandlerHistory.latestToView()
)
Assert.assertEquals(initialView, initialController.changeHandlerHistory.latestFromView())
Assert.assertEquals(
setBackstackHandler.tag,
initialController.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(initialController.changeHandlerHistory.latestIsPush())
Assert.assertTrue(newController.changeHandlerHistory.latestIsPush())
}
@Test
fun testSetBackstackForInvertHandlersWithRemovesView() {
val initialController1 = TestController()
val initialTransaction1 = initialController1.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
)
router.setRoot(initialTransaction1)
val initialController2 = TestController()
val initialTransaction2 = initialController2.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush2", true),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop2", true)
)
router.pushController(initialTransaction2)
val initialView2 = initialController2.view
val setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true)
val newBackstack = listOf(initialTransaction2, initialTransaction1)
router.setBackstack(newBackstack, setBackstackHandler)
Assert.assertTrue(initialController1.changeHandlerHistory.isValidHistory)
Assert.assertTrue(initialController2.changeHandlerHistory.isValidHistory)
Assert.assertEquals(3, initialController1.changeHandlerHistory.size().toLong())
Assert.assertEquals(2, initialController2.changeHandlerHistory.size().toLong())
Assert.assertNotNull(initialController1.changeHandlerHistory.latestToView())
Assert.assertEquals(initialView2, initialController1.changeHandlerHistory.latestFromView())
Assert.assertEquals(
setBackstackHandler.tag,
initialController1.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertFalse(initialController1.changeHandlerHistory.latestIsPush())
Assert.assertNotNull(initialController2.changeHandlerHistory.latestToView())
Assert.assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView())
Assert.assertEquals(
setBackstackHandler.tag,
initialController2.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertFalse(initialController2.changeHandlerHistory.latestIsPush())
}
@Test
fun testSetBackstackForInvertHandlersWithoutRemovesView() {
val initialController1 = TestController()
val initialTransaction1 = initialController1.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler("initialPush1", true),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop1", true)
)
router.setRoot(initialTransaction1)
val initialController2 = TestController()
val initialPushHandler2Tag = "initialPush2"
val initialTransaction2 = initialController2.asTransaction(
pushChangeHandler = MockChangeHandler.taggedHandler(initialPushHandler2Tag, false),
popChangeHandler = MockChangeHandler.taggedHandler("initialPop2", false)
)
router.pushController(initialTransaction2)
val initialView1 = initialController1.view
val initialView2 = initialController2.view
val setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true)
val newBackstack = listOf(initialTransaction2, initialTransaction1)
router.setBackstack(newBackstack, setBackstackHandler)
Assert.assertTrue(initialController1.changeHandlerHistory.isValidHistory)
Assert.assertTrue(initialController2.changeHandlerHistory.isValidHistory)
Assert.assertEquals(2, initialController1.changeHandlerHistory.size().toLong())
Assert.assertEquals(2, initialController2.changeHandlerHistory.size().toLong())
Assert.assertNotNull(initialController1.changeHandlerHistory.latestToView())
Assert.assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView())
Assert.assertEquals(
initialPushHandler2Tag,
initialController1.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertTrue(initialController1.changeHandlerHistory.latestIsPush())
Assert.assertNull(initialController2.changeHandlerHistory.latestToView())
Assert.assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView())
Assert.assertEquals(
setBackstackHandler.tag,
initialController2.changeHandlerHistory.latestChangeHandler().tag
)
Assert.assertFalse(initialController2.changeHandlerHistory.latestIsPush())
}
}
@@ -1,496 +0,0 @@
package com.bluelinelabs.conductor;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class RouterTests {
private Router router;
@Before
public void setup() {
ActivityProxy activityProxy = new ActivityProxy().create(null).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), null);
}
@Test
public void testSetRoot() {
String rootTag = "root";
Controller rootController = new TestController();
assertFalse(router.hasRootController());
router.setRoot(RouterTransaction.with(rootController).tag(rootTag));
assertTrue(router.hasRootController());
assertEquals(rootController, router.getControllerWithTag(rootTag));
}
@Test
public void testSetNewRoot() {
String oldRootTag = "oldRoot";
String newRootTag = "newRoot";
Controller oldRootController = new TestController();
Controller newRootController = new TestController();
router.setRoot(RouterTransaction.with(oldRootController).tag(oldRootTag));
router.setRoot(RouterTransaction.with(newRootController).tag(newRootTag));
assertNull(router.getControllerWithTag(oldRootTag));
assertEquals(newRootController, router.getControllerWithTag(newRootTag));
}
@Test
public void testGetByInstanceId() {
Controller controller = new TestController();
router.pushController(RouterTransaction.with(controller));
assertEquals(controller, router.getControllerWithInstanceId(controller.getInstanceId()));
assertNull(router.getControllerWithInstanceId("fake id"));
}
@Test
public void testGetByTag() {
String controller1Tag = "controller1";
String controller2Tag = "controller2";
Controller controller1 = new TestController();
Controller controller2 = new TestController();
router.pushController(RouterTransaction.with(controller1)
.tag(controller1Tag));
router.pushController(RouterTransaction.with(controller2)
.tag(controller2Tag));
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
assertEquals(controller2, router.getControllerWithTag(controller2Tag));
}
@Test
public void testPushPopControllers() {
String controller1Tag = "controller1";
String controller2Tag = "controller2";
Controller controller1 = new TestController();
Controller controller2 = new TestController();
router.pushController(RouterTransaction.with(controller1)
.tag(controller1Tag));
assertEquals(1, router.getBackstackSize());
router.pushController(RouterTransaction.with(controller2)
.tag(controller2Tag));
assertEquals(2, router.getBackstackSize());
router.popCurrentController();
assertEquals(1, router.getBackstackSize());
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
assertNull(router.getControllerWithTag(controller2Tag));
router.popCurrentController();
assertEquals(0, router.getBackstackSize());
assertNull(router.getControllerWithTag(controller1Tag));
assertNull(router.getControllerWithTag(controller2Tag));
}
@Test
public void testPopControllerConcurrentModificationException() {
int step = 1;
for (int i = 0; i < 10; i++, step++) {
router.pushController(RouterTransaction.with(new TestController()).tag("1"));
router.pushController(RouterTransaction.with(new TestController()).tag("2"));
router.pushController(RouterTransaction.with(new TestController()).tag("3"));
String tag;
if (step == 1) {
tag = "1";
} else if (step == 2) {
tag = "2";
} else {
tag = "3";
step = 0;
}
Controller controller = router.getControllerWithTag(tag);
if (controller != null) {
router.popController(controller);
}
router.popToRoot();
}
}
@Test
public void testPopToTag() {
String controller1Tag = "controller1";
String controller2Tag = "controller2";
String controller3Tag = "controller3";
String controller4Tag = "controller4";
Controller controller1 = new TestController();
Controller controller2 = new TestController();
Controller controller3 = new TestController();
Controller controller4 = new TestController();
router.pushController(RouterTransaction.with(controller1)
.tag(controller1Tag));
router.pushController(RouterTransaction.with(controller2)
.tag(controller2Tag));
router.pushController(RouterTransaction.with(controller3)
.tag(controller3Tag));
router.pushController(RouterTransaction.with(controller4)
.tag(controller4Tag));
router.popToTag(controller2Tag);
assertEquals(2, router.getBackstackSize());
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
assertEquals(controller2, router.getControllerWithTag(controller2Tag));
assertNull(router.getControllerWithTag(controller3Tag));
assertNull(router.getControllerWithTag(controller4Tag));
}
@Test
public void testPopNonCurrent() {
String controller1Tag = "controller1";
String controller2Tag = "controller2";
String controller3Tag = "controller3";
Controller controller1 = new TestController();
Controller controller2 = new TestController();
Controller controller3 = new TestController();
router.pushController(RouterTransaction.with(controller1)
.tag(controller1Tag));
router.pushController(RouterTransaction.with(controller2)
.tag(controller2Tag));
router.pushController(RouterTransaction.with(controller3)
.tag(controller3Tag));
router.popController(controller2);
assertEquals(2, router.getBackstackSize());
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
assertNull(router.getControllerWithTag(controller2Tag));
assertEquals(controller3, router.getControllerWithTag(controller3Tag));
}
@Test
public void testSetBackstack() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction middleTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction);
router.setBackstack(backstack, null);
assertEquals(3, router.getBackstackSize());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(middleTransaction, fetchedBackstack.get(1));
assertEquals(topTransaction, fetchedBackstack.get(2));
}
@Test
public void testNewSetBackstack() {
router.setRoot(RouterTransaction.with(new TestController()));
assertEquals(1, router.getBackstackSize());
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction middleTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction);
router.setBackstack(backstack, null);
assertEquals(3, router.getBackstackSize());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(middleTransaction, fetchedBackstack.get(1));
assertEquals(topTransaction, fetchedBackstack.get(2));
assertEquals(router, rootTransaction.controller().getRouter());
assertEquals(router, middleTransaction.controller().getRouter());
assertEquals(router, topTransaction.controller().getRouter());
}
@Test
public void testNewSetBackstackWithNoRemoveViewOnPush() {
RouterTransaction oldRootTransaction = RouterTransaction.with(new TestController());
RouterTransaction oldTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
router.setRoot(oldRootTransaction);
router.pushController(oldTopTransaction);
assertEquals(2, router.getBackstackSize());
assertTrue(oldRootTransaction.controller().isAttached());
assertTrue(oldTopTransaction.controller().isAttached());
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction);
router.setBackstack(backstack, null);
assertEquals(3, router.getBackstackSize());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(middleTransaction, fetchedBackstack.get(1));
assertEquals(topTransaction, fetchedBackstack.get(2));
assertFalse(oldRootTransaction.controller().isAttached());
assertFalse(oldTopTransaction.controller().isAttached());
assertTrue(rootTransaction.controller().isAttached());
assertTrue(middleTransaction.controller().isAttached());
assertTrue(topTransaction.controller().isAttached());
}
@Test
public void testPopToRoot() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, transaction1, transaction2);
router.setBackstack(backstack, null);
assertEquals(3, router.getBackstackSize());
router.popToRoot();
assertEquals(1, router.getBackstackSize());
assertEquals(rootTransaction, router.getBackstack().get(0));
assertTrue(rootTransaction.controller().isAttached());
assertFalse(transaction1.controller().isAttached());
assertFalse(transaction2.controller().isAttached());
}
@Test
public void testPopToRootWithNoRemoveViewOnPush() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new HorizontalChangeHandler(false));
RouterTransaction transaction1 = RouterTransaction.with(new TestController()).pushChangeHandler(new HorizontalChangeHandler(false));
RouterTransaction transaction2 = RouterTransaction.with(new TestController()).pushChangeHandler(new HorizontalChangeHandler(false));
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, transaction1, transaction2);
router.setBackstack(backstack, null);
assertEquals(3, router.getBackstackSize());
router.popToRoot();
assertEquals(1, router.getBackstackSize());
assertEquals(rootTransaction, router.getBackstack().get(0));
assertTrue(rootTransaction.controller().isAttached());
assertFalse(transaction1.controller().isAttached());
assertFalse(transaction2.controller().isAttached());
}
@Test
public void testReplaceTopController() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, topTransaction);
router.setBackstack(backstack, null);
assertEquals(2, router.getBackstackSize());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(topTransaction, fetchedBackstack.get(1));
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController());
router.replaceTopController(newTopTransaction);
assertEquals(2, router.getBackstackSize());
fetchedBackstack = router.getBackstack();
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(newTopTransaction, fetchedBackstack.get(1));
}
@Test
public void testReplaceTopControllerWithNoRemoveViewOnPush() {
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, topTransaction);
router.setBackstack(backstack, null);
assertEquals(2, router.getBackstackSize());
assertTrue(rootTransaction.controller().isAttached());
assertTrue(topTransaction.controller().isAttached());
List<RouterTransaction> fetchedBackstack = router.getBackstack();
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(topTransaction, fetchedBackstack.get(1));
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
router.replaceTopController(newTopTransaction);
newTopTransaction.pushChangeHandler().completeImmediately();
assertEquals(2, router.getBackstackSize());
fetchedBackstack = router.getBackstack();
assertEquals(rootTransaction, fetchedBackstack.get(0));
assertEquals(newTopTransaction, fetchedBackstack.get(1));
assertTrue(rootTransaction.controller().isAttached());
assertFalse(topTransaction.controller().isAttached());
assertTrue(newTopTransaction.controller().isAttached());
}
@Test
public void testRearrangeTransactionBackstack() {
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = Arrays.asList(transaction1, transaction2);
router.setBackstack(backstack, null);
assertEquals(1, transaction1.getTransactionIndex());
assertEquals(2, transaction2.getTransactionIndex());
backstack = Arrays.asList(transaction2, transaction1);
router.setBackstack(backstack, null);
assertEquals(1, transaction2.getTransactionIndex());
assertEquals(2, transaction1.getTransactionIndex());
router.handleBack();
assertEquals(1, router.getBackstackSize());
assertEquals(transaction2, router.getBackstack().get(0));
router.handleBack();
assertEquals(0, router.getBackstackSize());
}
@Test
public void testChildRouterRearrangeTransactionBackstack() {
Controller parent = new TestController();
router.setRoot(RouterTransaction.with(parent));
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
List<RouterTransaction> backstack = Arrays.asList(transaction1, transaction2);
childRouter.setBackstack(backstack, null);
assertEquals(2, transaction1.getTransactionIndex());
assertEquals(3, transaction2.getTransactionIndex());
backstack = Arrays.asList(transaction2, transaction1);
childRouter.setBackstack(backstack, null);
assertEquals(2, transaction2.getTransactionIndex());
assertEquals(3, transaction1.getTransactionIndex());
childRouter.handleBack();
assertEquals(1, childRouter.getBackstackSize());
assertEquals(transaction2, childRouter.getBackstack().get(0));
childRouter.handleBack();
assertEquals(0, childRouter.getBackstackSize());
}
@Test
public void testRemovesAllViewsOnDestroy() {
Controller controller1 = new TestController();
Controller controller2 = new TestController();
router.setRoot(RouterTransaction.with(controller1));
router.pushController(RouterTransaction.with(controller2)
.pushChangeHandler(new FadeChangeHandler(false)));
assertEquals(2, router.container.getChildCount());
router.destroy(true);
assertEquals(0, router.container.getChildCount());
}
@Test
public void testIsBeingDestroyed() {
final LifecycleListener lifecycleListener = new LifecycleListener() {
@Override
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
assertTrue(controller.isBeingDestroyed());
}
};
Controller controller1 = new TestController();
Controller controller2 = new TestController();
controller2.addLifecycleListener(lifecycleListener);
router.setRoot(RouterTransaction.with(controller1));
router.pushController(RouterTransaction.with(controller2));
assertFalse(controller1.isBeingDestroyed());
assertFalse(controller2.isBeingDestroyed());
router.popCurrentController();
assertFalse(controller1.isBeingDestroyed());
assertTrue(controller2.isBeingDestroyed());
Controller controller3 = new TestController();
controller3.addLifecycleListener(lifecycleListener);
router.pushController(RouterTransaction.with(controller3));
assertFalse(controller1.isBeingDestroyed());
assertFalse(controller3.isBeingDestroyed());
router.popToRoot();
assertFalse(controller1.isBeingDestroyed());
assertTrue(controller3.isBeingDestroyed());
}
}
@@ -0,0 +1,436 @@
package com.bluelinelabs.conductor
import android.view.View
import com.bluelinelabs.conductor.Controller.LifecycleListener
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.util.MockChangeHandler
import com.bluelinelabs.conductor.util.TestActivity
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class RouterTests {
private val router = Robolectric.buildActivity(TestActivity::class.java)
.setup()
.get()
.router
@Test
fun testSetRoot() {
val rootTag = "root"
val rootController = TestController()
Assert.assertFalse(router.hasRootController())
router.setRoot(RouterTransaction.with(rootController).tag(rootTag))
Assert.assertTrue(router.hasRootController())
Assert.assertEquals(rootController, router.getControllerWithTag(rootTag))
}
@Test
fun testSetNewRoot() {
val oldRootTag = "oldRoot"
val newRootTag = "newRoot"
val oldRootController = TestController()
val newRootController = TestController()
router.setRoot(RouterTransaction.with(oldRootController).tag(oldRootTag))
router.setRoot(RouterTransaction.with(newRootController).tag(newRootTag))
Assert.assertNull(router.getControllerWithTag(oldRootTag))
Assert.assertEquals(newRootController, router.getControllerWithTag(newRootTag))
}
@Test
fun testGetByInstanceId() {
val controller = TestController()
router.pushController(controller.asTransaction())
Assert.assertEquals(
controller,
router.getControllerWithInstanceId(controller.getInstanceId())
)
Assert.assertNull(router.getControllerWithInstanceId("fake id"))
}
@Test
fun testGetByTag() {
val controller1Tag = "controller1"
val controller2Tag = "controller2"
val controller1 = TestController()
val controller2 = TestController()
router.pushController(
RouterTransaction.with(controller1).tag(controller1Tag)
)
router.pushController(
RouterTransaction.with(controller2).tag(controller2Tag)
)
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag))
Assert.assertEquals(controller2, router.getControllerWithTag(controller2Tag))
}
@Test
fun testPushPopControllers() {
val controller1Tag = "controller1"
val controller2Tag = "controller2"
val controller1 = TestController()
val controller2 = TestController()
router.pushController(
RouterTransaction.with(controller1).tag(controller1Tag)
)
Assert.assertEquals(1, router.backstackSize.toLong())
router.pushController(
RouterTransaction.with(controller2).tag(controller2Tag)
)
Assert.assertEquals(2, router.backstackSize.toLong())
router.popCurrentController()
Assert.assertEquals(1, router.backstackSize.toLong())
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag))
Assert.assertNull(router.getControllerWithTag(controller2Tag))
router.popCurrentController()
Assert.assertEquals(0, router.backstackSize.toLong())
Assert.assertNull(router.getControllerWithTag(controller1Tag))
Assert.assertNull(router.getControllerWithTag(controller2Tag))
}
@Test
fun testPopControllerConcurrentModificationException() {
var step = 1
var i = 0
while (i < 10) {
router.pushController(RouterTransaction.with(TestController()).tag("1"))
router.pushController(RouterTransaction.with(TestController()).tag("2"))
router.pushController(RouterTransaction.with(TestController()).tag("3"))
val tag = when (step) {
1 -> "1"
2 -> "2"
else -> {
step = 0
"3"
}
}
val controller = router.getControllerWithTag(tag)
if (controller != null) {
router.popController(controller)
}
router.popToRoot()
i++
step++
}
}
@Test
fun testPopToTag() {
val controller1Tag = "controller1"
val controller2Tag = "controller2"
val controller3Tag = "controller3"
val controller4Tag = "controller4"
val controller1 = TestController()
val controller2 = TestController()
val controller3 = TestController()
val controller4 = TestController()
router.pushController(
RouterTransaction.with(controller1).tag(controller1Tag)
)
router.pushController(
RouterTransaction.with(controller2).tag(controller2Tag)
)
router.pushController(
RouterTransaction.with(controller3).tag(controller3Tag)
)
router.pushController(
RouterTransaction.with(controller4).tag(controller4Tag)
)
router.popToTag(controller2Tag)
Assert.assertEquals(2, router.backstackSize.toLong())
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag))
Assert.assertEquals(controller2, router.getControllerWithTag(controller2Tag))
Assert.assertNull(router.getControllerWithTag(controller3Tag))
Assert.assertNull(router.getControllerWithTag(controller4Tag))
}
@Test
fun testPopNonCurrent() {
val controller1Tag = "controller1"
val controller2Tag = "controller2"
val controller3Tag = "controller3"
val controller1 = TestController()
val controller2 = TestController()
val controller3 = TestController()
router.pushController(
RouterTransaction.with(controller1).tag(controller1Tag)
)
router.pushController(
RouterTransaction.with(controller2).tag(controller2Tag)
)
router.pushController(
RouterTransaction.with(controller3).tag(controller3Tag)
)
router.popController(controller2)
Assert.assertEquals(2, router.backstackSize.toLong())
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag))
Assert.assertNull(router.getControllerWithTag(controller2Tag))
Assert.assertEquals(controller3, router.getControllerWithTag(controller3Tag))
}
@Test
fun testSetBackstack() {
val rootTransaction = TestController().asTransaction()
val middleTransaction = TestController().asTransaction()
val topTransaction = TestController().asTransaction()
val backstack = listOf(rootTransaction, middleTransaction, topTransaction)
router.setBackstack(backstack, null)
Assert.assertEquals(3, router.backstackSize.toLong())
val fetchedBackstack = router.getBackstack()
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
Assert.assertEquals(middleTransaction, fetchedBackstack[1])
Assert.assertEquals(topTransaction, fetchedBackstack[2])
}
@Test
fun testNewSetBackstack() {
router.setRoot(TestController().asTransaction())
Assert.assertEquals(1, router.backstackSize.toLong())
val rootTransaction = TestController().asTransaction()
val middleTransaction = TestController().asTransaction()
val topTransaction = TestController().asTransaction()
val backstack = listOf(rootTransaction, middleTransaction, topTransaction)
router.setBackstack(backstack, null)
Assert.assertEquals(3, router.backstackSize.toLong())
val fetchedBackstack = router.getBackstack()
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
Assert.assertEquals(middleTransaction, fetchedBackstack[1])
Assert.assertEquals(topTransaction, fetchedBackstack[2])
Assert.assertEquals(router, rootTransaction.controller.getRouter())
Assert.assertEquals(router, middleTransaction.controller.getRouter())
Assert.assertEquals(router, topTransaction.controller.getRouter())
}
@Test
fun testNewSetBackstackWithNoRemoveViewOnPush() {
val oldRootTransaction = TestController().asTransaction()
val oldTopTransaction = TestController().asTransaction(
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
)
router.setRoot(oldRootTransaction)
router.pushController(oldTopTransaction)
Assert.assertEquals(2, router.backstackSize.toLong())
Assert.assertTrue(oldRootTransaction.controller.isAttached)
Assert.assertTrue(oldTopTransaction.controller.isAttached)
val rootTransaction = TestController().asTransaction()
val middleTransaction = TestController().asTransaction(
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
)
val topTransaction = TestController().asTransaction(
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
)
val backstack = listOf(rootTransaction, middleTransaction, topTransaction)
router.setBackstack(backstack, null)
Assert.assertEquals(3, router.backstackSize.toLong())
val fetchedBackstack = router.getBackstack()
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
Assert.assertEquals(middleTransaction, fetchedBackstack[1])
Assert.assertEquals(topTransaction, fetchedBackstack[2])
Assert.assertFalse(oldRootTransaction.controller.isAttached)
Assert.assertFalse(oldTopTransaction.controller.isAttached)
Assert.assertTrue(rootTransaction.controller.isAttached)
Assert.assertTrue(middleTransaction.controller.isAttached)
Assert.assertTrue(topTransaction.controller.isAttached)
}
@Test
fun testPopToRoot() {
val rootTransaction = TestController().asTransaction()
val transaction1 = TestController().asTransaction()
val transaction2 = TestController().asTransaction()
val backstack = listOf(rootTransaction, transaction1, transaction2)
router.setBackstack(backstack, null)
Assert.assertEquals(3, router.backstackSize.toLong())
router.popToRoot()
Assert.assertEquals(1, router.backstackSize.toLong())
Assert.assertEquals(rootTransaction, router.getBackstack()[0])
Assert.assertTrue(rootTransaction.controller.isAttached)
Assert.assertFalse(transaction1.controller.isAttached)
Assert.assertFalse(transaction2.controller.isAttached)
}
@Test
fun testPopToRootWithNoRemoveViewOnPush() {
val rootTransaction = TestController().asTransaction(
pushChangeHandler = HorizontalChangeHandler(false)
)
val transaction1 = TestController().asTransaction(
pushChangeHandler = HorizontalChangeHandler(false)
)
val transaction2 = TestController().asTransaction(
pushChangeHandler = HorizontalChangeHandler(false)
)
val backstack = listOf(rootTransaction, transaction1, transaction2)
router.setBackstack(backstack, null)
Assert.assertEquals(3, router.backstackSize.toLong())
router.popToRoot()
Assert.assertEquals(1, router.backstackSize.toLong())
Assert.assertEquals(rootTransaction, router.getBackstack()[0])
Assert.assertTrue(rootTransaction.controller.isAttached)
Assert.assertFalse(transaction1.controller.isAttached)
Assert.assertFalse(transaction2.controller.isAttached)
}
@Test
fun testReplaceTopController() {
val rootTransaction = TestController().asTransaction()
val topTransaction = TestController().asTransaction()
val backstack = listOf(rootTransaction, topTransaction)
router.setBackstack(backstack, null)
Assert.assertEquals(2, router.backstackSize.toLong())
var fetchedBackstack = router.getBackstack()
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
Assert.assertEquals(topTransaction, fetchedBackstack[1])
val newTopTransaction = TestController().asTransaction()
router.replaceTopController(newTopTransaction)
Assert.assertEquals(2, router.backstackSize.toLong())
fetchedBackstack = router.getBackstack()
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
Assert.assertEquals(newTopTransaction, fetchedBackstack[1])
}
@Test
fun testReplaceTopControllerWithNoRemoveViewOnPush() {
val rootTransaction = TestController().asTransaction()
val topTransaction = TestController().asTransaction(
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
)
val backstack = listOf(rootTransaction, topTransaction)
router.setBackstack(backstack, null)
Assert.assertEquals(2, router.backstackSize.toLong())
Assert.assertTrue(rootTransaction.controller.isAttached)
Assert.assertTrue(topTransaction.controller.isAttached)
var fetchedBackstack = router.getBackstack()
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
Assert.assertEquals(topTransaction, fetchedBackstack[1])
val newTopTransaction = TestController().asTransaction(
pushChangeHandler = MockChangeHandler.noRemoveViewOnPushHandler()
)
router.replaceTopController(newTopTransaction)
newTopTransaction.pushChangeHandler()!!.completeImmediately()
Assert.assertEquals(2, router.backstackSize.toLong())
fetchedBackstack = router.getBackstack()
Assert.assertEquals(rootTransaction, fetchedBackstack[0])
Assert.assertEquals(newTopTransaction, fetchedBackstack[1])
Assert.assertTrue(rootTransaction.controller.isAttached)
Assert.assertFalse(topTransaction.controller.isAttached)
Assert.assertTrue(newTopTransaction.controller.isAttached)
}
@Test
fun testRearrangeTransactionBackstack() {
val transaction1 = TestController().asTransaction()
val transaction2 = TestController().asTransaction()
var backstack = listOf(transaction1, transaction2)
router.setBackstack(backstack, null)
Assert.assertEquals(1, transaction1.transactionIndex.toLong())
Assert.assertEquals(2, transaction2.transactionIndex.toLong())
backstack = listOf(transaction2, transaction1)
router.setBackstack(backstack, null)
Assert.assertEquals(1, transaction2.transactionIndex.toLong())
Assert.assertEquals(2, transaction1.transactionIndex.toLong())
router.handleBack()
Assert.assertEquals(1, router.backstackSize.toLong())
Assert.assertEquals(transaction2, router.getBackstack()[0])
router.handleBack()
Assert.assertEquals(0, router.backstackSize.toLong())
}
@Test
fun testChildRouterRearrangeTransactionBackstack() {
val parent = TestController()
router.setRoot(parent.asTransaction())
val childRouter = parent.getChildRouter(
parent.view!!.findViewById(TestController.CHILD_VIEW_ID_1)
)
val transaction1 = TestController().asTransaction()
val transaction2 = TestController().asTransaction()
var backstack = listOf(transaction1, transaction2)
childRouter.setBackstack(backstack, null)
Assert.assertEquals(2, transaction1.transactionIndex.toLong())
Assert.assertEquals(3, transaction2.transactionIndex.toLong())
backstack = listOf(transaction2, transaction1)
childRouter.setBackstack(backstack, null)
Assert.assertEquals(2, transaction2.transactionIndex.toLong())
Assert.assertEquals(3, transaction1.transactionIndex.toLong())
childRouter.handleBack()
Assert.assertEquals(1, childRouter.backstackSize.toLong())
Assert.assertEquals(transaction2, childRouter.getBackstack()[0])
childRouter.handleBack()
Assert.assertEquals(0, childRouter.backstackSize.toLong())
}
@Test
fun testRemovesAllViewsOnDestroy() {
router.setRoot(TestController().asTransaction())
router.pushController(
TestController().asTransaction(
pushChangeHandler = FadeChangeHandler(false)
)
)
Assert.assertEquals(2, router.container.childCount.toLong())
router.destroy(true)
Assert.assertEquals(0, router.container.childCount.toLong())
}
@Test
fun testIsBeingDestroyed() {
val lifecycleListener: LifecycleListener = object : LifecycleListener() {
override fun preDestroyView(controller: Controller, view: View) {
Assert.assertTrue(controller.isBeingDestroyed())
}
}
val controller1 = TestController()
val controller2 = TestController()
controller2.addLifecycleListener(lifecycleListener)
router.setRoot(controller1.asTransaction())
router.pushController(controller2.asTransaction())
Assert.assertFalse(controller1.isBeingDestroyed())
Assert.assertFalse(controller2.isBeingDestroyed())
router.popCurrentController()
Assert.assertFalse(controller1.isBeingDestroyed())
Assert.assertTrue(controller2.isBeingDestroyed())
val controller3 = TestController()
controller3.addLifecycleListener(lifecycleListener)
router.pushController(controller3.asTransaction())
Assert.assertFalse(controller1.isBeingDestroyed())
Assert.assertFalse(controller3.isBeingDestroyed())
router.popToRoot()
Assert.assertFalse(controller1.isBeingDestroyed())
Assert.assertTrue(controller3.isBeingDestroyed())
}
}
@@ -1,106 +0,0 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TargetControllerTests {
private Router router;
public void createActivityController(Bundle savedInstanceState) {
ActivityProxy activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
}
@Before
public void setup() {
createActivityController(null);
}
@Test
public void testSiblingTarget() {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
assertNull(controllerA.getTargetController());
assertNull(controllerB.getTargetController());
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
controllerB.setTargetController(controllerA);
router.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertNull(controllerA.getTargetController());
assertEquals(controllerA, controllerB.getTargetController());
}
@Test
public void testParentChildTarget() {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
assertNull(controllerA.getTargetController());
assertNull(controllerB.getTargetController());
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
controllerB.setTargetController(controllerA);
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID));
childRouter.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertNull(controllerA.getTargetController());
assertEquals(controllerA, controllerB.getTargetController());
}
@Test
public void testChildParentTarget() {
final TestController controllerA = new TestController();
final TestController controllerB = new TestController();
assertNull(controllerA.getTargetController());
assertNull(controllerB.getTargetController());
router.pushController(RouterTransaction.with(controllerA)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
controllerA.setTargetController(controllerB);
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID));
childRouter.pushController(RouterTransaction.with(controllerB)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
assertNull(controllerB.getTargetController());
assertEquals(controllerB, controllerA.getTargetController());
}
}
@@ -0,0 +1,98 @@
package com.bluelinelabs.conductor
import com.bluelinelabs.conductor.util.MockChangeHandler
import com.bluelinelabs.conductor.util.TestActivity
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class TargetControllerTests {
private val router = Robolectric.buildActivity(TestActivity::class.java)
.setup()
.get()
.router
@Test
fun testSiblingTarget() {
val controllerA = TestController()
val controllerB = TestController()
Assert.assertNull(controllerA.targetController)
Assert.assertNull(controllerB.targetController)
router.pushController(
controllerA.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
controllerB.targetController = controllerA
router.pushController(
controllerB.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertNull(controllerA.targetController)
Assert.assertEquals(controllerA, controllerB.targetController)
}
@Test
fun testParentChildTarget() {
val controllerA = TestController()
val controllerB = TestController()
Assert.assertNull(controllerA.targetController)
Assert.assertNull(controllerB.targetController)
router.pushController(
controllerA.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
controllerB.targetController = controllerA
val childRouter = controllerA.getChildRouter(
controllerA.view!!.findViewById(TestController.VIEW_ID)
)
childRouter.pushController(
controllerB.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertNull(controllerA.targetController)
Assert.assertEquals(controllerA, controllerB.targetController)
}
@Test
fun testChildParentTarget() {
val controllerA = TestController()
val controllerB = TestController()
Assert.assertNull(controllerA.targetController)
Assert.assertNull(controllerB.targetController)
router.pushController(
controllerA.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
controllerA.targetController = controllerB
val childRouter = controllerA.getChildRouter(
controllerA.view!!.findViewById(TestController.VIEW_ID)
)
childRouter.pushController(
controllerB.asTransaction(
pushChangeHandler = MockChangeHandler.defaultHandler(),
popChangeHandler = MockChangeHandler.defaultHandler()
)
)
Assert.assertNull(controllerB.targetController)
Assert.assertEquals(controllerB, controllerA.targetController)
}
}
@@ -1,4 +1,4 @@
package com.bluelinelabs.conductor.util;
package com.bluelinelabs.conductor;
import android.content.Context;
import android.content.Intent;
@@ -14,9 +14,10 @@ import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.util.AttachFakingFrameLayout;
import com.bluelinelabs.conductor.util.CallState;
import com.bluelinelabs.conductor.util.ChangeHandlerHistory;
import com.bluelinelabs.conductor.util.MockChangeHandler;
public class TestController extends Controller {
@@ -1,153 +0,0 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.TestController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ViewLeakTests {
private ActivityProxy activityProxy;
private Router router;
public void createActivityController(Bundle savedInstanceState) {
activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new TestController()));
}
}
@Before
public void setup() {
createActivityController(null);
}
@Test
public void testPop() {
Controller controller = new TestController();
router.pushController(RouterTransaction.with(controller));
assertNotNull(controller.getView());
router.popCurrentController();
assertNull(controller.getView());
}
@Test
public void testPopWhenPushNeverAdded() {
Controller controller = new TestController();
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverAddChangeHandler()));
assertNotNull(controller.getView());
router.popCurrentController();
assertNull(controller.getView());
}
@Test
public void testPopWhenPushNeverCompleted() {
Controller controller = new TestController();
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverCompleteChangeHandler()));
assertNotNull(controller.getView());
router.popCurrentController();
assertNull(controller.getView());
}
@Test
public void testActivityStop() {
Controller controller = new TestController();
router.pushController(RouterTransaction.with(controller));
assertNotNull(controller.getView());
activityProxy.stop(true);
assertNull(controller.getView());
}
@Test
public void testActivityStopWhenPushNeverCompleted() {
Controller controller = new TestController();
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverCompleteChangeHandler()));
assertNotNull(controller.getView());
activityProxy.stop(true);
assertNull(controller.getView());
}
@Test
public void testActivityDestroyWhenPushNeverAdded() {
Controller controller = new TestController();
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverAddChangeHandler()));
assertNotNull(controller.getView());
activityProxy.stop(true).destroy();
assertNull(controller.getView());
}
@Test
public void testViewRemovedIfLayeredNotRemovesFromViewOnPush() {
Controller controller = new TestController();
router.pushController(RouterTransaction.with(controller));
router.pushController(RouterTransaction.with(new TestController()).pushChangeHandler(new SimpleSwapChangeHandler(false)));
View view = controller.view;
assertNotNull(view.getParent());
router.pushController(RouterTransaction.with(new TestController()));
assertNotNull(view.getParent());
router.popToRoot();
assertNull(view.getParent());
}
public static class NeverAddChangeHandler extends ControllerChangeHandler {
@Override
public void performChange(@NonNull final ViewGroup container, @Nullable View from, @Nullable final View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
if (from != null) {
container.removeView(from);
}
}
}
public static class NeverCompleteChangeHandler extends ControllerChangeHandler {
@Override
public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
if (from != null) {
container.removeView(from);
}
container.addView(to);
}
}
}
@@ -0,0 +1,143 @@
package com.bluelinelabs.conductor
import android.os.Looper
import android.view.View
import android.view.ViewGroup
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler
import com.bluelinelabs.conductor.util.TestActivity
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class ViewLeakTests {
private val activityController = Robolectric.buildActivity(TestActivity::class.java).setup()
private val router = activityController.get().router
@Before
fun setup() {
if (!router.hasRootController()) {
router.setRoot(TestController().asTransaction())
}
}
@Test
fun testPop() {
val controller = TestController()
router.pushController(controller.asTransaction())
Assert.assertNotNull(controller.getView())
router.popCurrentController()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertNull(controller.getView())
}
@Test
fun testPopWhenPushNeverAdded() {
val controller = TestController()
router.pushController(controller.asTransaction(pushChangeHandler = NeverAddChangeHandler()))
Assert.assertNotNull(controller.getView())
router.popCurrentController()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertNull(controller.getView())
}
@Test
fun testPopWhenPushNeverCompleted() {
val controller = TestController()
router.pushController(controller.asTransaction(pushChangeHandler = NeverCompleteChangeHandler()))
Assert.assertNotNull(controller.getView())
router.popCurrentController()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertNull(controller.getView())
}
@Test
fun testActivityDestroy() {
val controller = TestController()
router.pushController(controller.asTransaction())
Assert.assertNotNull(controller.getView())
activityController.stop().destroy()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertNull(controller.getView())
}
@Test
fun testActivityDestroyWhenPushNeverCompleted() {
val controller = TestController()
router.pushController(controller.asTransaction(pushChangeHandler = NeverCompleteChangeHandler()))
Assert.assertNotNull(controller.getView())
activityController.stop().destroy()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertNull(controller.getView())
}
@Test
fun testActivityDestroyWhenPushNeverAdded() {
val controller = TestController()
router.pushController(controller.asTransaction(pushChangeHandler = NeverAddChangeHandler()))
Assert.assertNotNull(controller.getView())
activityController.stop().destroy()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertNull(controller.getView())
}
@Test
fun testViewRemovedIfLayeredNotRemovesFromViewOnPush() {
val controller = TestController()
router.pushController(controller.asTransaction())
router.pushController(TestController().asTransaction(pushChangeHandler = SimpleSwapChangeHandler(false)))
val view = controller.view
shadowOf(Looper.getMainLooper()).idle()
Assert.assertNotNull(view.parent)
router.pushController(TestController().asTransaction())
shadowOf(Looper.getMainLooper()).idle()
Assert.assertNotNull(view.parent)
router.popToRoot()
shadowOf(Looper.getMainLooper()).idle()
Assert.assertNull(view.parent)
}
class NeverAddChangeHandler : ControllerChangeHandler() {
override fun performChange(
container: ViewGroup,
from: View?,
to: View?,
isPush: Boolean,
changeListener: ControllerChangeCompletedListener
) {
if (from != null) {
container.removeView(from)
}
}
}
class NeverCompleteChangeHandler : ControllerChangeHandler() {
override fun performChange(
container: ViewGroup,
from: View?,
to: View?,
isPush: Boolean,
changeListener: ControllerChangeCompletedListener
) {
if (from != null) {
container.removeView(from)
}
container.addView(to)
}
}
}
@@ -1,19 +0,0 @@
package com.bluelinelabs.conductor.util;
import android.app.Activity;
public class TestActivity extends Activity {
public boolean isChangingConfigurations = false;
public boolean isDestroying = false;
@Override
public boolean isChangingConfigurations() {
return isChangingConfigurations;
}
@Override
public boolean isDestroyed() {
return isDestroying || super.isDestroyed();
}
}
@@ -0,0 +1,32 @@
package com.bluelinelabs.conductor.util
import android.app.Activity
import android.os.Bundle
import com.bluelinelabs.conductor.Conductor
import com.bluelinelabs.conductor.Router
class TestActivity : Activity() {
lateinit var router: Router
var changingConfigurations = false
var destroying = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
router = Conductor.attachRouter(
this,
findViewById(android.R.id.content),
savedInstanceState
)
}
override fun isChangingConfigurations(): Boolean {
return changingConfigurations
}
override fun isDestroyed(): Boolean {
return destroying || super.isDestroyed()
}
}
+14
View File
@@ -24,6 +24,7 @@ android {
buildFeatures {
viewBinding = true
compose = true
}
compileSdkVersion rootProject.ext.compileSdkVersion
@@ -36,6 +37,10 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
composeOptions {
kotlinCompilerExtensionVersion composeVersion
}
}
dependencies {
@@ -58,6 +63,15 @@ dependencies {
implementation project(':conductor-modules:arch-components-lifecycle')
implementation project(':conductor-modules:androidx-transition')
implementation "androidx.compose.ui:ui:$composeVersion"
implementation "androidx.compose.ui:ui-tooling:$composeVersion"
implementation "androidx.compose.foundation:foundation:$composeVersion"
implementation "androidx.compose.material:material:$composeVersion"
implementation "androidx.compose.material:material-icons-core:$composeVersion"
implementation "androidx.compose.material:material-icons-extended:$composeVersion"
implementation "androidx.activity:activity-compose:1.3.0-beta02"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07"
debugImplementation rootProject.ext.leakCanary
releaseImplementation rootProject.ext.leakCanaryNoOp
testImplementation rootProject.ext.leakCanaryNoOp
@@ -0,0 +1,85 @@
package com.bluelinelabs.conductor.demo.controllers
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.asTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.demo.ToolbarProvider
class ComposeController : Controller() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedViewState: Bundle?
): View {
return ComposeView(container.context).apply {
setContent {
LongNumberList {
router.pushController(
ComposeController().asTransaction(
pushChangeHandler = HorizontalChangeHandler(),
popChangeHandler = HorizontalChangeHandler()
)
)
}
}
}
}
override fun onAttach(view: View) {
super.onAttach(view)
(activity as? ToolbarProvider)?.toolbar?.apply {
title = "Jetpack Compose Demo"
menu.clear()
}
}
}
@Composable
fun LongNumberList(onClick: () -> Unit) {
MaterialTheme {
LazyColumn {
item {
Spacer(modifier = Modifier.height(8.dp))
}
items((0..100).toList()) {
Box(
modifier = Modifier
.height(48.dp)
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp)
) {
Text(
modifier = Modifier
.align(Alignment.CenterStart),
text = "Line $it"
)
}
}
item {
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
@@ -18,6 +18,7 @@ import androidx.core.text.buildSpannedString
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.asTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.demo.R
@@ -117,12 +118,24 @@ class HomeController : BaseController(R.layout.controller_home) {
)
}
DemoModel.SHARED_ELEMENT_TRANSITIONS -> {
val titleSharedElementName = resources!!.getString(R.string.transition_tag_title_indexed, position)
val dotSharedElementName = resources!!.getString(R.string.transition_tag_dot_indexed, position)
val titleSharedElementName =
resources!!.getString(R.string.transition_tag_title_indexed, position)
val dotSharedElementName =
resources!!.getString(R.string.transition_tag_dot_indexed, position)
router.pushController(
RouterTransaction.with(CityGridController(model.title, model.color, position))
.pushChangeHandler(ArcFadeMoveChangeHandler(titleSharedElementName, dotSharedElementName))
.popChangeHandler(ArcFadeMoveChangeHandler(titleSharedElementName, dotSharedElementName))
.pushChangeHandler(
ArcFadeMoveChangeHandler(
titleSharedElementName,
dotSharedElementName
)
)
.popChangeHandler(
ArcFadeMoveChangeHandler(
titleSharedElementName,
dotSharedElementName
)
)
)
}
DemoModel.DRAG_DISMISS -> {
@@ -153,13 +166,19 @@ class HomeController : BaseController(R.layout.controller_home) {
.popChangeHandler(FadeChangeHandler())
)
}
DemoModel.COMPOSE -> {
router.pushController(
ComposeController().asTransaction(FadeChangeHandler(), FadeChangeHandler())
)
}
}
}
private fun showAboutDialog(fromFab: Boolean) {
val details = SpannableString("A small, yet full-featured framework that allows building View-based Android applications").apply {
setSpan(AbsoluteSizeSpan(16, true), 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
val details =
SpannableString("A small, yet full-featured framework that allows building View-based Android applications").apply {
setSpan(AbsoluteSizeSpan(16, true), 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
val link = SpannableString(CONDUCTOR_URL).apply {
setSpan(object : URLSpan(CONDUCTOR_URL) {
@@ -175,7 +194,8 @@ class HomeController : BaseController(R.layout.controller_home) {
append(link)
}
val pushHandler = if (fromFab) FabToDialogTransitionChangeHandler() else FadeChangeHandler(false)
val pushHandler =
if (fromFab) FabToDialogTransitionChangeHandler() else FadeChangeHandler(false)
val popHandler = if (fromFab) FabToDialogTransitionChangeHandler() else FadeChangeHandler()
router.pushController(
RouterTransaction.with(DialogController("Conductor", description))
@@ -191,6 +211,7 @@ class HomeController : BaseController(R.layout.controller_home) {
}
private enum class DemoModel(val title: String, @ColorRes val color: Int) {
COMPOSE("Jetpack Compose", R.color.amber_500),
NAVIGATION("Navigation Demos", R.color.red_300),
TRANSITIONS("Transition Demos", R.color.blue_grey_300),
SHARED_ELEMENT_TRANSITIONS("Shared Element Demos", R.color.purple_300),
+1
View File
@@ -27,6 +27,7 @@
<color name="cyan_300">#4dd0e1</color>
<color name="grey_300">#e0e0e0</color>
<color name="grey_400">#78909C</color>
<color name="amber_500">#ffc107</color>
<color name="dark_icon">#99000000</color>
<color name="dialog_background">#fafafa</color>
+9 -6
View File
@@ -10,23 +10,24 @@ ext {
rxLifecycleVersion = '1.0'
rxLifecycle2Version = '2.2.1'
autodisposeVersion = '1.0.0'
archComponentsVersion = '2.0.0'
archComponentsVersion = '2.3.1'
junitVersion = '4.12'
mvnPublishVersion = '0.13.0'
dokkaVersion = '1.4.10.2'
dokkaVersion = '1.4.32'
composeVersion = "1.0.0-beta09"
agpVersion = "4.1.0"
agpVersion = "7.0.0-beta04"
lintVersion = agpVersion.replaceFirst(~/\d*/) { version ->
// the major version of lint is always 23 version higher than the major version of agp
version.toInteger() + 23
}
kotlinVersion = '1.3.72'
kotlinVersion = '1.5.10'
kotlinStd = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
material = "com.google.android.material:material:1.1.0"
androidxAnnotations = "androidx.annotation:annotation:1.1.0"
androidxAppCompat = "androidx.appcompat:appcompat:1.0.0"
androidxAppCompat = "androidx.appcompat:appcompat:1.3.0"
androidxTransition = "androidx.transition:transition:1.3.1"
androidxCollection = "androidx.collection:collection:1.1.0"
androidxViewPager2 = "androidx.viewpager2:viewpager2:1.0.0"
@@ -50,8 +51,10 @@ ext {
archComponentsLifecycle = "androidx.lifecycle:lifecycle-runtime:$archComponentsVersion"
archComponentsLiveDataCore = "androidx.lifecycle:lifecycle-livedata-core:$archComponentsVersion"
savedState = "androidx.savedstate:savedstate-ktx:1.1.0"
junit = "junit:junit:$junitVersion"
roboelectric = "org.robolectric:robolectric:4.3.1"
robolectric = "org.robolectric:robolectric:4.5.1"
lintapi = "com.android.tools.lint:lint-api:$lintVersion"
lintchecks = "com.android.tools.lint:lint-checks:$lintVersion"
+1 -1
View File
@@ -16,7 +16,7 @@ org.gradle.jvmargs=-Xmx1536m
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
VERSION_NAME=3.0.1-SNAPSHOT
VERSION_NAME=3.1.1-SNAPSHOT
VERSION_CODE=3
GROUP=com.bluelinelabs
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+1 -1
View File
@@ -72,7 +72,7 @@ case "`uname`" in
Darwin* )
darwin=true
;;
MINGW* )
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )