Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates ComposeViewTreeIntegrationTest to cover ComposeScreen #1212

Merged
merged 2 commits into from
Jul 23, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,16 @@ internal class ComposeViewTreeIntegrationTest {

@Before fun setUp() {
scenario.onActivity {
it.viewEnvironment = ViewEnvironment.EMPTY + ViewRegistry(NoTransitionBackStackContainer)
it.viewEnvironment = (ViewEnvironment.EMPTY + ViewRegistry(NoTransitionBackStackContainer))
.withComposeInteropSupport()
}
}

@Test fun compose_view_assertions_work() {
val firstScreen = TestComposeRendering("first") {
val firstScreen = VanillaComposeRendering("first") {
BasicText("First Screen")
}
val secondScreen = TestComposeRendering("second") {}
val secondScreen = VanillaComposeRendering("second") {}

scenario.onActivity {
it.setBackstack(firstScreen)
Expand All @@ -89,18 +90,87 @@ internal class ComposeViewTreeIntegrationTest {
composeRule.onNodeWithText("First Screen").assertDoesNotExist()
}

@Test fun composition_is_disposed_when_navigated_away_dispose_on_detach_strategy() {
@Test fun composition_is_disposed_when_navigated_away_stock_class() {
var composedCount = 0
var disposedCount = 0
val firstScreen = TestComposeRendering("first", disposeStrategy = DisposeOnDetachedFromWindow) {
DisposableEffect(Unit) {
composedCount++
onDispose {
disposedCount++
val firstScreen =
VanillaComposeRendering("first") {
DisposableEffect(Unit) {
composedCount++
onDispose {
disposedCount++
}
}
}
val secondScreen = VanillaComposeRendering("second") {}

scenario.onActivity {
it.setBackstack(firstScreen)
}

composeRule.runOnIdle {
assertThat(composedCount).isEqualTo(1)
assertThat(disposedCount).isEqualTo(0)
}

// Navigate away.
scenario.onActivity {
it.setBackstack(firstScreen, secondScreen)
}

composeRule.runOnIdle {
assertThat(composedCount).isEqualTo(1)
assertThat(disposedCount).isEqualTo(1)
}
}

@Test fun composition_is_disposed_when_navigated_away_default_strategy() {
var composedCount = 0
var disposedCount = 0
val firstScreen =
BespokeComposeRendering("first", disposeStrategy = ViewCompositionStrategy.Default) {
DisposableEffect(Unit) {
composedCount++
onDispose {
disposedCount++
}
}
}
val secondScreen = VanillaComposeRendering("second") {}

scenario.onActivity {
it.setBackstack(firstScreen)
}

composeRule.runOnIdle {
assertThat(composedCount).isEqualTo(1)
assertThat(disposedCount).isEqualTo(0)
}
val secondScreen = TestComposeRendering("second") {}

// Navigate away.
scenario.onActivity {
it.setBackstack(firstScreen, secondScreen)
}

composeRule.runOnIdle {
assertThat(composedCount).isEqualTo(1)
assertThat(disposedCount).isEqualTo(1)
}
}

@Test fun composition_is_disposed_when_navigated_away_dispose_on_detach_strategy() {
var composedCount = 0
var disposedCount = 0
val firstScreen =
BespokeComposeRendering("first", disposeStrategy = DisposeOnDetachedFromWindow) {
DisposableEffect(Unit) {
composedCount++
onDispose {
disposedCount++
}
}
}
val secondScreen = VanillaComposeRendering("second") {}

scenario.onActivity {
it.setBackstack(firstScreen)
Expand All @@ -126,15 +196,15 @@ internal class ComposeViewTreeIntegrationTest {
var composedCount = 0
var disposedCount = 0
val firstScreen =
TestComposeRendering("first", disposeStrategy = DisposeOnViewTreeLifecycleDestroyed) {
BespokeComposeRendering("first", disposeStrategy = DisposeOnViewTreeLifecycleDestroyed) {
DisposableEffect(Unit) {
composedCount++
onDispose {
disposedCount++
}
}
}
val secondScreen = TestComposeRendering("second") {}
val secondScreen = VanillaComposeRendering("second") {}

scenario.onActivity {
it.setBackstack(firstScreen)
Expand All @@ -157,7 +227,7 @@ internal class ComposeViewTreeIntegrationTest {
}

@Test fun composition_state_is_restored_after_config_change() {
val firstScreen = TestComposeRendering("first") {
val firstScreen = VanillaComposeRendering("first") {
var counter by rememberSaveable { mutableStateOf(0) }
BasicText(
"Counter: $counter",
Expand All @@ -184,7 +254,7 @@ internal class ComposeViewTreeIntegrationTest {
}

@Test fun composition_state_is_restored_after_navigating_back() {
val firstScreen = TestComposeRendering("first") {
val firstScreen = VanillaComposeRendering("first") {
var counter by rememberSaveable { mutableStateOf(0) }
BasicText(
"Counter: $counter",
Expand All @@ -193,7 +263,7 @@ internal class ComposeViewTreeIntegrationTest {
.testTag(CounterTag)
)
}
val secondScreen = TestComposeRendering("second") {
val secondScreen = VanillaComposeRendering("second") {
BasicText("nothing to see here")
}

Expand Down Expand Up @@ -225,7 +295,7 @@ internal class ComposeViewTreeIntegrationTest {

@Test
fun composition_state_is_restored_after_config_change_then_navigating_back() {
val firstScreen = TestComposeRendering("first") {
val firstScreen = VanillaComposeRendering("first") {
var counter by rememberSaveable { mutableStateOf(0) }
BasicText(
"Counter: $counter",
Expand All @@ -234,7 +304,7 @@ internal class ComposeViewTreeIntegrationTest {
.testTag(CounterTag)
)
}
val secondScreen = TestComposeRendering("second") {
val secondScreen = VanillaComposeRendering("second") {
BasicText("nothing to see here")
}

Expand Down Expand Up @@ -267,7 +337,7 @@ internal class ComposeViewTreeIntegrationTest {
}

@Test fun composition_state_is_not_restored_after_screen_is_removed_from_backstack() {
val firstScreen = TestComposeRendering("first") {
val firstScreen = VanillaComposeRendering("first") {
var counter by rememberSaveable { mutableStateOf(0) }
BasicText(
"Counter: $counter",
Expand All @@ -276,7 +346,7 @@ internal class ComposeViewTreeIntegrationTest {
.testTag(CounterTag)
)
}
val secondScreen = TestComposeRendering("second") {
val secondScreen = VanillaComposeRendering("second") {
BasicText("nothing to see here")
}

Expand Down Expand Up @@ -312,7 +382,7 @@ internal class ComposeViewTreeIntegrationTest {

@Test
fun composition_state_is_not_restored_after_screen_is_removed_and_replaced_from_backstack() {
val firstScreen = TestComposeRendering("first") {
val firstScreen = VanillaComposeRendering("first") {
var counter by rememberSaveable { mutableStateOf(0) }
BasicText(
"Counter: $counter",
Expand All @@ -321,7 +391,7 @@ internal class ComposeViewTreeIntegrationTest {
.testTag(CounterTag)
)
}
val secondScreen = TestComposeRendering("second") {
val secondScreen = VanillaComposeRendering("second") {
BasicText("nothing to see here")
}

Expand Down Expand Up @@ -360,8 +430,8 @@ internal class ComposeViewTreeIntegrationTest {
.assertTextEquals("Counter: 0")
}

@Test fun composition_is_restored_in_modal_after_config_change() {
val firstScreen: Screen = TestComposeRendering(compatibilityKey = "") {
@Test fun composition_is_restored_in_overlay_after_config_change() {
val firstScreen: Screen = VanillaComposeRendering(compatibilityKey = "") {
var counter by rememberSaveable { mutableStateOf(0) }
BasicText(
"Counter: $counter",
Expand Down Expand Up @@ -392,8 +462,8 @@ internal class ComposeViewTreeIntegrationTest {
.assertTextEquals("Counter: 1")
}

@Test fun composition_is_restored_in_multiple_modals_after_config_change() {
val firstScreen: Screen = TestComposeRendering(compatibilityKey = "0") {
@Test fun composition_is_restored_in_multiple_overlays_after_config_change() {
val firstScreen: Screen = VanillaComposeRendering(compatibilityKey = "0") {
var counter by rememberSaveable { mutableStateOf(0) }
BasicText(
"Counter: $counter",
Expand All @@ -403,7 +473,7 @@ internal class ComposeViewTreeIntegrationTest {
)
}

val secondScreen: Screen = TestComposeRendering(compatibilityKey = "1") {
val secondScreen: Screen = VanillaComposeRendering(compatibilityKey = "1") {
var counter by rememberSaveable { mutableStateOf(0) }
BasicText(
"Counter2: $counter",
Expand All @@ -413,7 +483,7 @@ internal class ComposeViewTreeIntegrationTest {
)
}

val thirdScreen: Screen = TestComposeRendering(compatibilityKey = "2") {
val thirdScreen: Screen = VanillaComposeRendering(compatibilityKey = "2") {
var counter by rememberSaveable { mutableStateOf(0) }
BasicText(
"Counter3: $counter",
Expand Down Expand Up @@ -464,12 +534,12 @@ internal class ComposeViewTreeIntegrationTest {
.assertTextEquals("Counter3: 1")
}

@Test fun composition_is_restored_in_multiple_modals_backstacks_after_config_change() {
@Test fun composition_is_restored_in_multiple_overlays_backstacks_after_config_change() {
fun createRendering(
layer: Int,
screen: Int
) = TestComposeRendering(
// Use the same compatibility key across layers – these screens are in different modals, so
) = VanillaComposeRendering(
// Use the same compatibility key across layers – these screens are in different overlays, so
// they won't conflict.
compatibilityKey = screen.toString()
) {
Expand All @@ -494,7 +564,7 @@ internal class ComposeViewTreeIntegrationTest {
EmptyRendering,
listOf(
TestOverlay(BackStackScreen(EmptyRendering, layer0Screen0)),
// A SavedStateRegistry is set up for each modal. Each registry needs a unique name,
// A SavedStateRegistry is set up for each overlay. Each registry needs a unique name,
// and these names default to their `Compatible.keyFor` value. When we show two
// of the same type at the same time, we need to give them unique names.
TestOverlay(NamedScreen(BackStackScreen(EmptyRendering, layer1Screen0), "another"))
Expand Down Expand Up @@ -522,7 +592,7 @@ internal class ComposeViewTreeIntegrationTest {
EmptyRendering,
listOf(
TestOverlay(BackStackScreen(EmptyRendering, layer0Screen0, layer0Screen1)),
// A SavedStateRegistry is set up for each modal. Each registry needs a unique name,
// A SavedStateRegistry is set up for each overlay. Each registry needs a unique name,
// and these names default to their `Compatible.keyFor` value. When we show two
// of the same type at the same time, we need to give them unique names.
TestOverlay(
Expand Down Expand Up @@ -566,7 +636,7 @@ internal class ComposeViewTreeIntegrationTest {
EmptyRendering,
listOf(
TestOverlay(BackStackScreen(EmptyRendering, layer0Screen0)),
// A SavedStateRegistry is set up for each modal. Each registry needs a unique name,
// A SavedStateRegistry is set up for each overlay. Each registry needs a unique name,
// and these names default to their `Compatible.keyFor` value. When we show two
// of the same type at the same time, we need to give them unique names.
TestOverlay(NamedScreen(BackStackScreen(EmptyRendering, layer1Screen0), "another"))
Expand All @@ -581,7 +651,7 @@ internal class ComposeViewTreeIntegrationTest {
.assertIsDisplayed()
}

private fun WorkflowUiTestActivity.setBackstack(vararg backstack: TestComposeRendering) {
private fun WorkflowUiTestActivity.setBackstack(vararg backstack: Screen) {
setRendering(
BackStackScreen.fromList(listOf<AndroidScreen<*>>(EmptyRendering) + backstack.asList())
)
Expand All @@ -598,20 +668,27 @@ internal class ComposeViewTreeIntegrationTest {
}
}

data class TestComposeRendering(
/**
* This is our own custom lovingly handcrafted implementation that creates [ComposeView]
* itself, bypassing [ScreenComposableFactory] entirely. Allows us to mess with alternative
* [ViewCompositionStrategy] approaches.
*/
data class BespokeComposeRendering(
override val compatibilityKey: String,
val disposeStrategy: ViewCompositionStrategy? = null,
val content: @Composable () -> Unit
) : Compatible, AndroidScreen<TestComposeRendering>, ScreenViewFactory<TestComposeRendering> {
override val type: KClass<in TestComposeRendering> = TestComposeRendering::class
override val viewFactory: ScreenViewFactory<TestComposeRendering> get() = this
) : Compatible,
AndroidScreen<BespokeComposeRendering>,
ScreenViewFactory<BespokeComposeRendering> {
override val type: KClass<in BespokeComposeRendering> = BespokeComposeRendering::class
override val viewFactory: ScreenViewFactory<BespokeComposeRendering> get() = this

override fun buildView(
initialRendering: TestComposeRendering,
initialRendering: BespokeComposeRendering,
initialEnvironment: ViewEnvironment,
context: Context,
container: ViewGroup?
): ScreenViewHolder<TestComposeRendering> {
): ScreenViewHolder<BespokeComposeRendering> {
var lastCompositionStrategy = initialRendering.disposeStrategy

return ComposeView(context).let { view ->
Expand All @@ -629,6 +706,19 @@ internal class ComposeViewTreeIntegrationTest {
}
}

/**
* Bog standard [ComposeScreen], as opposed to [BespokeComposeRendering].
* Requires [ViewEnvironment.withComposeInteropSupport].
*/
data class VanillaComposeRendering(
override val compatibilityKey: String,
val content: @Composable () -> Unit
) : Compatible, ComposeScreen {
@Composable override fun Content() {
content()
}
}

object EmptyRendering : AndroidScreen<EmptyRendering> {
override val viewFactory: ScreenViewFactory<EmptyRendering>
get() = ScreenViewFactory.fromCode { _, e, c, _ ->
Expand Down
Loading