From 82a9a5ea16ba868e152ff45ab5d5308efeaafdce Mon Sep 17 00:00:00 2001 From: Isaac Udy Date: Mon, 16 Oct 2023 21:51:38 +1300 Subject: [PATCH] Added tests to verify EnroTest rule clean up issues, and updated EnroTest/ConfigureNavigationHandleForPlugins to solve the reported bug --- .../ConfigureNavigationHandleForPlugins.kt | 8 +++-- .../src/main/java/dev/enro/test/EnroTest.kt | 18 +++------- .../application/SelectDestinationRobot.kt | 10 ++++++ .../activity/SimpleActivityRobot.kt | 23 +++++++++++++ .../ruleinterop/FirstTestWithEnroRule.kt | 28 ++++++++++++++++ .../ruleinterop/FirstTestWithoutEnroRule.kt | 19 +++++++++++ .../test/application/ruleinterop/README.md | 6 ++++ .../ruleinterop/SecondTestWithEnroRule.kt | 27 +++++++++++++++ .../ruleinterop/SecondTestWithoutEnroRule.kt | 19 +++++++++++ .../application/src/main/AndroidManifest.xml | 1 + .../application/activity/SimpleActivity.kt | 33 +++++++++++++++++++ 11 files changed, 177 insertions(+), 15 deletions(-) create mode 100644 tests/application/src/androidTest/java/dev/enro/test/application/activity/SimpleActivityRobot.kt create mode 100644 tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/FirstTestWithEnroRule.kt create mode 100644 tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/FirstTestWithoutEnroRule.kt create mode 100644 tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/README.md create mode 100644 tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/SecondTestWithEnroRule.kt create mode 100644 tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/SecondTestWithoutEnroRule.kt create mode 100644 tests/application/src/main/java/dev/enro/tests/application/activity/SimpleActivity.kt diff --git a/enro-core/src/main/java/dev/enro/core/controller/usecase/ConfigureNavigationHandleForPlugins.kt b/enro-core/src/main/java/dev/enro/core/controller/usecase/ConfigureNavigationHandleForPlugins.kt index 91baf310..5cd17c15 100644 --- a/enro-core/src/main/java/dev/enro/core/controller/usecase/ConfigureNavigationHandleForPlugins.kt +++ b/enro-core/src/main/java/dev/enro/core/controller/usecase/ConfigureNavigationHandleForPlugins.kt @@ -4,9 +4,13 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.coroutineScope -import dev.enro.core.* +import dev.enro.core.NavigationContext +import dev.enro.core.NavigationHandle import dev.enro.core.controller.repository.PluginRepository +import dev.enro.core.getNavigationHandle import dev.enro.core.internal.handle.NavigationHandleViewModel +import dev.enro.core.leafContext +import dev.enro.core.rootContext import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import java.lang.ref.WeakReference @@ -34,7 +38,7 @@ internal class ConfigureNavigationHandleForPlugins( // in which case, we just ignore the exception runCatching { val active = context.rootContext().leafContext().getNavigationHandle() - if (!active.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) return@runCatching + if (!active.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) return@runCatching activeNavigationHandle = WeakReference(active) } } diff --git a/enro-test/src/main/java/dev/enro/test/EnroTest.kt b/enro-test/src/main/java/dev/enro/test/EnroTest.kt index 9dd9afde..8d99860e 100644 --- a/enro-test/src/main/java/dev/enro/test/EnroTest.kt +++ b/enro-test/src/main/java/dev/enro/test/EnroTest.kt @@ -20,10 +20,6 @@ object EnroTest { if (navigationController != null) { uninstallNavigationController() } - navigationController = createUnattachedNavigationController() - .apply { - isInTest = true - } if (isInstrumented()) { val application = ApplicationProvider.getApplicationContext() @@ -35,7 +31,11 @@ object EnroTest { } navigationController?.apply { install(application) } } else { - navigationController?.installForJvmTests() + navigationController = createUnattachedNavigationController() + .apply { + isInTest = true + installForJvmTests() + } } } @@ -44,15 +44,7 @@ object EnroTest { navigationController?.apply { isInTest = false } - - val uninstallNavigationController = navigationController navigationController = null - - if (isInstrumented()) { - val application = ApplicationProvider.getApplicationContext() - if (application is NavigationApplication) return - uninstallNavigationController?.uninstall(application) - } } fun getCurrentNavigationController(): NavigationController { diff --git a/tests/application/src/androidTest/java/dev/enro/test/application/SelectDestinationRobot.kt b/tests/application/src/androidTest/java/dev/enro/test/application/SelectDestinationRobot.kt index 9fe44397..ed57188a 100644 --- a/tests/application/src/androidTest/java/dev/enro/test/application/SelectDestinationRobot.kt +++ b/tests/application/src/androidTest/java/dev/enro/test/application/SelectDestinationRobot.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.onSiblings import androidx.compose.ui.test.performClick +import dev.enro.test.application.activity.SimpleActivityRobot import dev.enro.test.application.compose.BottomSheetChangeSizeRobot import dev.enro.test.application.compose.BottomSheetCloseAndPresentRobot import dev.enro.test.application.compose.LegacyBottomSheetsRobot @@ -46,4 +47,13 @@ class SelectDestinationRobot( return LegacyBottomSheetsRobot(composeRule) } + + fun openSimpleActivity() : SimpleActivityRobot { + composeRule.onNode(hasText("Simple Activity")) + .onSiblings() + .filterToOne(hasText("Present")) + .performClick() + + return SimpleActivityRobot(composeRule) + } } \ No newline at end of file diff --git a/tests/application/src/androidTest/java/dev/enro/test/application/activity/SimpleActivityRobot.kt b/tests/application/src/androidTest/java/dev/enro/test/application/activity/SimpleActivityRobot.kt new file mode 100644 index 00000000..3e0fcea5 --- /dev/null +++ b/tests/application/src/androidTest/java/dev/enro/test/application/activity/SimpleActivityRobot.kt @@ -0,0 +1,23 @@ +package dev.enro.test.application.activity + +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.performClick +import dev.enro.test.application.waitForNavigationHandle +import dev.enro.tests.application.activity.SimpleActivity + +class SimpleActivityRobot( + private val composeRule: ComposeTestRule +) { + + init { + composeRule.waitForNavigationHandle { + it.key is SimpleActivity + } + } + + fun close() { + composeRule.onNode(hasText("Close Activity")) + .performClick() + } +} \ No newline at end of file diff --git a/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/FirstTestWithEnroRule.kt b/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/FirstTestWithEnroRule.kt new file mode 100644 index 00000000..0c6d489e --- /dev/null +++ b/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/FirstTestWithEnroRule.kt @@ -0,0 +1,28 @@ +package dev.enro.test.application.ruleinterop + +import android.content.Intent +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import dev.enro.core.NavigationInstruction +import dev.enro.core.addOpenInstruction +import dev.enro.core.close +import dev.enro.test.assertClosed +import dev.enro.test.extensions.getTestNavigationHandle +import dev.enro.test.runEnroTest +import dev.enro.tests.application.activity.SimpleActivity +import dev.enro.tests.application.activity.SimpleActivityImpl +import org.junit.Test + +class FirstTestWithEnroRule { + @Test + fun test() = runEnroTest { + val scenario = ActivityScenario.launch( + Intent(ApplicationProvider.getApplicationContext(), SimpleActivityImpl::class.java) + .addOpenInstruction(NavigationInstruction.Present(SimpleActivity)) + ) + val navigationHandle = scenario.getTestNavigationHandle() + navigationHandle.close() + navigationHandle.assertClosed() + } +} + diff --git a/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/FirstTestWithoutEnroRule.kt b/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/FirstTestWithoutEnroRule.kt new file mode 100644 index 00000000..5040351e --- /dev/null +++ b/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/FirstTestWithoutEnroRule.kt @@ -0,0 +1,19 @@ +package dev.enro.test.application.ruleinterop + +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import dev.enro.test.application.SelectDestinationRobot +import dev.enro.tests.application.TestActivity +import org.junit.Rule +import org.junit.Test + +class FirstTestWithoutEnroRule() { + @get:Rule + val composeRule = createAndroidComposeRule() + + @Test + fun test() { + SelectDestinationRobot(composeRule) + .openSimpleActivity() + .close() + } +} \ No newline at end of file diff --git a/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/README.md b/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/README.md new file mode 100644 index 00000000..a3f248e0 --- /dev/null +++ b/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/README.md @@ -0,0 +1,6 @@ +# Rule Interop Tests +`EnroTest` (`EnroTestRule` or `runEnroTest`) are designed for testing screens in isolation. When using `EnroTest` functionality in instrumented tests (i.e. those in `src/androidTest`), the NavigationController configures NavigationHandles so that they don't launch any NavigationInstructions, but instead record the NavigationInstructions that were executed against a NavigationHandle, which can be verified against expected state without performing the navigation action. When the test is finished, the `EnroTest` functionality will revert this behaviour. + +When running instrumented tests without using `EnroTest` functionality, navigation is expected to occur as normal. + +The tests in this package are designed to be ran as a package (not as individual tests). A bug was reported where packages that mix isolated single screen tests using `EnroTest` and tests that do not use `EnroTest` (i.e. tests that perform "real" navigation). This bug would cause the tests not using `EnroTest` to fail, due to the clean up process of `EnroTest` being overeager and clearing NavigationBindings that should not have been cleared and needed to be accessed by the tests. Running the tests in this package together as a whole verifies that this bug is no longer present. \ No newline at end of file diff --git a/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/SecondTestWithEnroRule.kt b/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/SecondTestWithEnroRule.kt new file mode 100644 index 00000000..32d99b5e --- /dev/null +++ b/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/SecondTestWithEnroRule.kt @@ -0,0 +1,27 @@ +package dev.enro.test.application.ruleinterop + +import android.content.Intent +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import dev.enro.core.NavigationInstruction +import dev.enro.core.addOpenInstruction +import dev.enro.core.close +import dev.enro.test.assertClosed +import dev.enro.test.extensions.getTestNavigationHandle +import dev.enro.test.runEnroTest +import dev.enro.tests.application.activity.SimpleActivity +import dev.enro.tests.application.activity.SimpleActivityImpl +import org.junit.Test + +class SecondTestWithEnroRule { + @Test + fun test() = runEnroTest { + val scenario = ActivityScenario.launch( + Intent(ApplicationProvider.getApplicationContext(), SimpleActivityImpl::class.java) + .addOpenInstruction(NavigationInstruction.Present(SimpleActivity)) + ) + val navigationHandle = scenario.getTestNavigationHandle() + navigationHandle.close() + navigationHandle.assertClosed() + } +} \ No newline at end of file diff --git a/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/SecondTestWithoutEnroRule.kt b/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/SecondTestWithoutEnroRule.kt new file mode 100644 index 00000000..12b8c6f0 --- /dev/null +++ b/tests/application/src/androidTest/java/dev/enro/test/application/ruleinterop/SecondTestWithoutEnroRule.kt @@ -0,0 +1,19 @@ +package dev.enro.test.application.ruleinterop + +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import dev.enro.test.application.SelectDestinationRobot +import dev.enro.tests.application.TestActivity +import org.junit.Rule +import org.junit.Test + +class SecondTestWithoutEnroRule() { + @get:Rule + val composeRule = createAndroidComposeRule() + + @Test + fun test() { + SelectDestinationRobot(composeRule) + .openSimpleActivity() + .close() + } +} \ No newline at end of file diff --git a/tests/application/src/main/AndroidManifest.xml b/tests/application/src/main/AndroidManifest.xml index 696ebbcb..fe6e50cc 100644 --- a/tests/application/src/main/AndroidManifest.xml +++ b/tests/application/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ + \ No newline at end of file diff --git a/tests/application/src/main/java/dev/enro/tests/application/activity/SimpleActivity.kt b/tests/application/src/main/java/dev/enro/tests/application/activity/SimpleActivity.kt new file mode 100644 index 00000000..aa0384fc --- /dev/null +++ b/tests/application/src/main/java/dev/enro/tests/application/activity/SimpleActivity.kt @@ -0,0 +1,33 @@ +package dev.enro.tests.application.activity + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material.Button +import androidx.compose.material.Text +import dev.enro.annotations.NavigationDestination +import dev.enro.core.NavigationKey +import dev.enro.core.navigationHandle +import dev.enro.core.requestClose +import dev.enro.tests.application.compose.common.TitledColumn +import kotlinx.parcelize.Parcelize + +@Parcelize +object SimpleActivity : NavigationKey.SupportsPresent + +@NavigationDestination(SimpleActivity::class) +class SimpleActivityImpl : ComponentActivity() { + + private val navigation by navigationHandle() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + TitledColumn(title = "Simple Activity") { + Button(onClick = { navigation.requestClose() }) { + Text(text = "Close Activity") + } + } + } + } +} \ No newline at end of file