Skip to content

Commit 002883f

Browse files
committed
Updates ComposeViewTreeIntegrationTest to cover ComposeScreen
`ComposeViewTreeIntegrationTest` was built around its own `ScreenViewFactory`, and so did not cover the production code that normally builds `ComposeView`. Now we use that thing only for a few spots where we cover the effects of specific `ViewCompositionStrategy` implementations.
1 parent 89d3a0f commit 002883f

File tree

1 file changed

+131
-40
lines changed

1 file changed

+131
-40
lines changed

workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/ComposeViewTreeIntegrationTest.kt

Lines changed: 131 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import androidx.compose.runtime.setValue
1515
import androidx.compose.ui.Modifier
1616
import androidx.compose.ui.platform.ComposeView
1717
import androidx.compose.ui.platform.ViewCompositionStrategy
18+
import androidx.compose.ui.platform.ViewCompositionStrategy.Companion
1819
import androidx.compose.ui.platform.ViewCompositionStrategy.DisposeOnDetachedFromWindow
1920
import androidx.compose.ui.platform.ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
2021
import androidx.compose.ui.platform.testTag
@@ -65,15 +66,16 @@ internal class ComposeViewTreeIntegrationTest {
6566

6667
@Before fun setUp() {
6768
scenario.onActivity {
68-
it.viewEnvironment = ViewEnvironment.EMPTY + ViewRegistry(NoTransitionBackStackContainer)
69+
it.viewEnvironment = (ViewEnvironment.EMPTY + ViewRegistry(NoTransitionBackStackContainer))
70+
.withComposeInteropSupport()
6971
}
7072
}
7173

7274
@Test fun compose_view_assertions_work() {
73-
val firstScreen = TestComposeRendering("first") {
75+
val firstScreen = VanillaComposeRendering("first") {
7476
BasicText("First Screen")
7577
}
76-
val secondScreen = TestComposeRendering("second") {}
78+
val secondScreen = VanillaComposeRendering("second") {}
7779

7880
scenario.onActivity {
7981
it.setBackstack(firstScreen)
@@ -89,18 +91,87 @@ internal class ComposeViewTreeIntegrationTest {
8991
composeRule.onNodeWithText("First Screen").assertDoesNotExist()
9092
}
9193

92-
@Test fun composition_is_disposed_when_navigated_away_dispose_on_detach_strategy() {
94+
@Test fun composition_is_disposed_when_navigated_away_stock_class() {
9395
var composedCount = 0
9496
var disposedCount = 0
95-
val firstScreen = TestComposeRendering("first", disposeStrategy = DisposeOnDetachedFromWindow) {
96-
DisposableEffect(Unit) {
97-
composedCount++
98-
onDispose {
99-
disposedCount++
97+
val firstScreen =
98+
VanillaComposeRendering("first") {
99+
DisposableEffect(Unit) {
100+
composedCount++
101+
onDispose {
102+
disposedCount++
103+
}
104+
}
105+
}
106+
val secondScreen = VanillaComposeRendering("second") {}
107+
108+
scenario.onActivity {
109+
it.setBackstack(firstScreen)
110+
}
111+
112+
composeRule.runOnIdle {
113+
assertThat(composedCount).isEqualTo(1)
114+
assertThat(disposedCount).isEqualTo(0)
115+
}
116+
117+
// Navigate away.
118+
scenario.onActivity {
119+
it.setBackstack(firstScreen, secondScreen)
120+
}
121+
122+
composeRule.runOnIdle {
123+
assertThat(composedCount).isEqualTo(1)
124+
assertThat(disposedCount).isEqualTo(1)
125+
}
126+
}
127+
128+
@Test fun composition_is_disposed_when_navigated_away_default_strategy() {
129+
var composedCount = 0
130+
var disposedCount = 0
131+
val firstScreen =
132+
BespokeComposeRendering("first", disposeStrategy = ViewCompositionStrategy.Default) {
133+
DisposableEffect(Unit) {
134+
composedCount++
135+
onDispose {
136+
disposedCount++
137+
}
100138
}
101139
}
140+
val secondScreen = VanillaComposeRendering("second") {}
141+
142+
scenario.onActivity {
143+
it.setBackstack(firstScreen)
144+
}
145+
146+
composeRule.runOnIdle {
147+
assertThat(composedCount).isEqualTo(1)
148+
assertThat(disposedCount).isEqualTo(0)
102149
}
103-
val secondScreen = TestComposeRendering("second") {}
150+
151+
// Navigate away.
152+
scenario.onActivity {
153+
it.setBackstack(firstScreen, secondScreen)
154+
}
155+
156+
composeRule.runOnIdle {
157+
assertThat(composedCount).isEqualTo(1)
158+
assertThat(disposedCount).isEqualTo(1)
159+
}
160+
}
161+
162+
@Test fun composition_is_disposed_when_navigated_away_dispose_on_detach_strategy() {
163+
var composedCount = 0
164+
var disposedCount = 0
165+
val firstScreen =
166+
BespokeComposeRendering("first", disposeStrategy = DisposeOnDetachedFromWindow) {
167+
DisposableEffect(Unit) {
168+
composedCount++
169+
onDispose {
170+
disposedCount++
171+
}
172+
}
173+
}
174+
val secondScreen = VanillaComposeRendering("second") {}
104175

105176
scenario.onActivity {
106177
it.setBackstack(firstScreen)
@@ -126,15 +197,15 @@ internal class ComposeViewTreeIntegrationTest {
126197
var composedCount = 0
127198
var disposedCount = 0
128199
val firstScreen =
129-
TestComposeRendering("first", disposeStrategy = DisposeOnViewTreeLifecycleDestroyed) {
200+
BespokeComposeRendering("first", disposeStrategy = DisposeOnViewTreeLifecycleDestroyed) {
130201
DisposableEffect(Unit) {
131202
composedCount++
132203
onDispose {
133204
disposedCount++
134205
}
135206
}
136207
}
137-
val secondScreen = TestComposeRendering("second") {}
208+
val secondScreen = VanillaComposeRendering("second") {}
138209

139210
scenario.onActivity {
140211
it.setBackstack(firstScreen)
@@ -157,7 +228,7 @@ internal class ComposeViewTreeIntegrationTest {
157228
}
158229

159230
@Test fun composition_state_is_restored_after_config_change() {
160-
val firstScreen = TestComposeRendering("first") {
231+
val firstScreen = VanillaComposeRendering("first") {
161232
var counter by rememberSaveable { mutableStateOf(0) }
162233
BasicText(
163234
"Counter: $counter",
@@ -184,7 +255,7 @@ internal class ComposeViewTreeIntegrationTest {
184255
}
185256

186257
@Test fun composition_state_is_restored_after_navigating_back() {
187-
val firstScreen = TestComposeRendering("first") {
258+
val firstScreen = VanillaComposeRendering("first") {
188259
var counter by rememberSaveable { mutableStateOf(0) }
189260
BasicText(
190261
"Counter: $counter",
@@ -193,7 +264,7 @@ internal class ComposeViewTreeIntegrationTest {
193264
.testTag(CounterTag)
194265
)
195266
}
196-
val secondScreen = TestComposeRendering("second") {
267+
val secondScreen = VanillaComposeRendering("second") {
197268
BasicText("nothing to see here")
198269
}
199270

@@ -225,7 +296,7 @@ internal class ComposeViewTreeIntegrationTest {
225296

226297
@Test
227298
fun composition_state_is_restored_after_config_change_then_navigating_back() {
228-
val firstScreen = TestComposeRendering("first") {
299+
val firstScreen = VanillaComposeRendering("first") {
229300
var counter by rememberSaveable { mutableStateOf(0) }
230301
BasicText(
231302
"Counter: $counter",
@@ -234,7 +305,7 @@ internal class ComposeViewTreeIntegrationTest {
234305
.testTag(CounterTag)
235306
)
236307
}
237-
val secondScreen = TestComposeRendering("second") {
308+
val secondScreen = VanillaComposeRendering("second") {
238309
BasicText("nothing to see here")
239310
}
240311

@@ -267,7 +338,7 @@ internal class ComposeViewTreeIntegrationTest {
267338
}
268339

269340
@Test fun composition_state_is_not_restored_after_screen_is_removed_from_backstack() {
270-
val firstScreen = TestComposeRendering("first") {
341+
val firstScreen = VanillaComposeRendering("first") {
271342
var counter by rememberSaveable { mutableStateOf(0) }
272343
BasicText(
273344
"Counter: $counter",
@@ -276,7 +347,7 @@ internal class ComposeViewTreeIntegrationTest {
276347
.testTag(CounterTag)
277348
)
278349
}
279-
val secondScreen = TestComposeRendering("second") {
350+
val secondScreen = VanillaComposeRendering("second") {
280351
BasicText("nothing to see here")
281352
}
282353

@@ -312,7 +383,7 @@ internal class ComposeViewTreeIntegrationTest {
312383

313384
@Test
314385
fun composition_state_is_not_restored_after_screen_is_removed_and_replaced_from_backstack() {
315-
val firstScreen = TestComposeRendering("first") {
386+
val firstScreen = VanillaComposeRendering("first") {
316387
var counter by rememberSaveable { mutableStateOf(0) }
317388
BasicText(
318389
"Counter: $counter",
@@ -321,7 +392,7 @@ internal class ComposeViewTreeIntegrationTest {
321392
.testTag(CounterTag)
322393
)
323394
}
324-
val secondScreen = TestComposeRendering("second") {
395+
val secondScreen = VanillaComposeRendering("second") {
325396
BasicText("nothing to see here")
326397
}
327398

@@ -360,8 +431,8 @@ internal class ComposeViewTreeIntegrationTest {
360431
.assertTextEquals("Counter: 0")
361432
}
362433

363-
@Test fun composition_is_restored_in_modal_after_config_change() {
364-
val firstScreen: Screen = TestComposeRendering(compatibilityKey = "") {
434+
@Test fun composition_is_restored_in_overlay_after_config_change() {
435+
val firstScreen: Screen = VanillaComposeRendering(compatibilityKey = "") {
365436
var counter by rememberSaveable { mutableStateOf(0) }
366437
BasicText(
367438
"Counter: $counter",
@@ -392,8 +463,8 @@ internal class ComposeViewTreeIntegrationTest {
392463
.assertTextEquals("Counter: 1")
393464
}
394465

395-
@Test fun composition_is_restored_in_multiple_modals_after_config_change() {
396-
val firstScreen: Screen = TestComposeRendering(compatibilityKey = "0") {
466+
@Test fun composition_is_restored_in_multiple_overlays_after_config_change() {
467+
val firstScreen: Screen = VanillaComposeRendering(compatibilityKey = "0") {
397468
var counter by rememberSaveable { mutableStateOf(0) }
398469
BasicText(
399470
"Counter: $counter",
@@ -403,7 +474,7 @@ internal class ComposeViewTreeIntegrationTest {
403474
)
404475
}
405476

406-
val secondScreen: Screen = TestComposeRendering(compatibilityKey = "1") {
477+
val secondScreen: Screen = VanillaComposeRendering(compatibilityKey = "1") {
407478
var counter by rememberSaveable { mutableStateOf(0) }
408479
BasicText(
409480
"Counter2: $counter",
@@ -413,7 +484,7 @@ internal class ComposeViewTreeIntegrationTest {
413484
)
414485
}
415486

416-
val thirdScreen: Screen = TestComposeRendering(compatibilityKey = "2") {
487+
val thirdScreen: Screen = VanillaComposeRendering(compatibilityKey = "2") {
417488
var counter by rememberSaveable { mutableStateOf(0) }
418489
BasicText(
419490
"Counter3: $counter",
@@ -464,12 +535,12 @@ internal class ComposeViewTreeIntegrationTest {
464535
.assertTextEquals("Counter3: 1")
465536
}
466537

467-
@Test fun composition_is_restored_in_multiple_modals_backstacks_after_config_change() {
538+
@Test fun composition_is_restored_in_multiple_overlays_backstacks_after_config_change() {
468539
fun createRendering(
469540
layer: Int,
470541
screen: Int
471-
) = TestComposeRendering(
472-
// Use the same compatibility key across layers – these screens are in different modals, so
542+
) = VanillaComposeRendering(
543+
// Use the same compatibility key across layers – these screens are in different overlays, so
473544
// they won't conflict.
474545
compatibilityKey = screen.toString()
475546
) {
@@ -494,7 +565,7 @@ internal class ComposeViewTreeIntegrationTest {
494565
EmptyRendering,
495566
listOf(
496567
TestOverlay(BackStackScreen(EmptyRendering, layer0Screen0)),
497-
// A SavedStateRegistry is set up for each modal. Each registry needs a unique name,
568+
// A SavedStateRegistry is set up for each overlay. Each registry needs a unique name,
498569
// and these names default to their `Compatible.keyFor` value. When we show two
499570
// of the same type at the same time, we need to give them unique names.
500571
TestOverlay(NamedScreen(BackStackScreen(EmptyRendering, layer1Screen0), "another"))
@@ -522,7 +593,7 @@ internal class ComposeViewTreeIntegrationTest {
522593
EmptyRendering,
523594
listOf(
524595
TestOverlay(BackStackScreen(EmptyRendering, layer0Screen0, layer0Screen1)),
525-
// A SavedStateRegistry is set up for each modal. Each registry needs a unique name,
596+
// A SavedStateRegistry is set up for each overlay. Each registry needs a unique name,
526597
// and these names default to their `Compatible.keyFor` value. When we show two
527598
// of the same type at the same time, we need to give them unique names.
528599
TestOverlay(
@@ -566,7 +637,7 @@ internal class ComposeViewTreeIntegrationTest {
566637
EmptyRendering,
567638
listOf(
568639
TestOverlay(BackStackScreen(EmptyRendering, layer0Screen0)),
569-
// A SavedStateRegistry is set up for each modal. Each registry needs a unique name,
640+
// A SavedStateRegistry is set up for each overlay. Each registry needs a unique name,
570641
// and these names default to their `Compatible.keyFor` value. When we show two
571642
// of the same type at the same time, we need to give them unique names.
572643
TestOverlay(NamedScreen(BackStackScreen(EmptyRendering, layer1Screen0), "another"))
@@ -581,7 +652,7 @@ internal class ComposeViewTreeIntegrationTest {
581652
.assertIsDisplayed()
582653
}
583654

584-
private fun WorkflowUiTestActivity.setBackstack(vararg backstack: TestComposeRendering) {
655+
private fun WorkflowUiTestActivity.setBackstack(vararg backstack: Screen) {
585656
setRendering(
586657
BackStackScreen.fromList(listOf<AndroidScreen<*>>(EmptyRendering) + backstack.asList())
587658
)
@@ -598,20 +669,27 @@ internal class ComposeViewTreeIntegrationTest {
598669
}
599670
}
600671

601-
data class TestComposeRendering(
672+
/**
673+
* This is our own custom lovingly handcrafted implementation that creates [ComposeView]
674+
* itself, bypassing [ScreenComposableFactory] entirely. Allows us to mess with alternative
675+
* [ViewCompositionStrategy] approaches.
676+
*/
677+
data class BespokeComposeRendering(
602678
override val compatibilityKey: String,
603679
val disposeStrategy: ViewCompositionStrategy? = null,
604680
val content: @Composable () -> Unit
605-
) : Compatible, AndroidScreen<TestComposeRendering>, ScreenViewFactory<TestComposeRendering> {
606-
override val type: KClass<in TestComposeRendering> = TestComposeRendering::class
607-
override val viewFactory: ScreenViewFactory<TestComposeRendering> get() = this
681+
) : Compatible,
682+
AndroidScreen<BespokeComposeRendering>,
683+
ScreenViewFactory<BespokeComposeRendering> {
684+
override val type: KClass<in BespokeComposeRendering> = BespokeComposeRendering::class
685+
override val viewFactory: ScreenViewFactory<BespokeComposeRendering> get() = this
608686

609687
override fun buildView(
610-
initialRendering: TestComposeRendering,
688+
initialRendering: BespokeComposeRendering,
611689
initialEnvironment: ViewEnvironment,
612690
context: Context,
613691
container: ViewGroup?
614-
): ScreenViewHolder<TestComposeRendering> {
692+
): ScreenViewHolder<BespokeComposeRendering> {
615693
var lastCompositionStrategy = initialRendering.disposeStrategy
616694

617695
return ComposeView(context).let { view ->
@@ -629,6 +707,19 @@ internal class ComposeViewTreeIntegrationTest {
629707
}
630708
}
631709

710+
/**
711+
* Bog standard [ComposeScreen], as opposed to [BespokeComposeRendering].
712+
* Requires [ViewEnvironment.withComposeInteropSupport].
713+
*/
714+
data class VanillaComposeRendering(
715+
override val compatibilityKey: String,
716+
val content: @Composable () -> Unit
717+
) : Compatible, ComposeScreen {
718+
@Composable override fun Content() {
719+
content()
720+
}
721+
}
722+
632723
object EmptyRendering : AndroidScreen<EmptyRendering> {
633724
override val viewFactory: ScreenViewFactory<EmptyRendering>
634725
get() = ScreenViewFactory.fromCode { _, e, c, _ ->

0 commit comments

Comments
 (0)