Skip to content

Commit 040ece4

Browse files
authored
Merge pull request #1212 from square/ray/more-complete-compose-integration-tests
Updates `ComposeViewTreeIntegrationTest` to cover `ComposeScreen`
2 parents 89d3a0f + 3cc0485 commit 040ece4

File tree

1 file changed

+130
-40
lines changed

1 file changed

+130
-40
lines changed

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

Lines changed: 130 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,16 @@ internal class ComposeViewTreeIntegrationTest {
6565

6666
@Before fun setUp() {
6767
scenario.onActivity {
68-
it.viewEnvironment = ViewEnvironment.EMPTY + ViewRegistry(NoTransitionBackStackContainer)
68+
it.viewEnvironment = (ViewEnvironment.EMPTY + ViewRegistry(NoTransitionBackStackContainer))
69+
.withComposeInteropSupport()
6970
}
7071
}
7172

7273
@Test fun compose_view_assertions_work() {
73-
val firstScreen = TestComposeRendering("first") {
74+
val firstScreen = VanillaComposeRendering("first") {
7475
BasicText("First Screen")
7576
}
76-
val secondScreen = TestComposeRendering("second") {}
77+
val secondScreen = VanillaComposeRendering("second") {}
7778

7879
scenario.onActivity {
7980
it.setBackstack(firstScreen)
@@ -89,18 +90,87 @@ internal class ComposeViewTreeIntegrationTest {
8990
composeRule.onNodeWithText("First Screen").assertDoesNotExist()
9091
}
9192

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

105175
scenario.onActivity {
106176
it.setBackstack(firstScreen)
@@ -126,15 +196,15 @@ internal class ComposeViewTreeIntegrationTest {
126196
var composedCount = 0
127197
var disposedCount = 0
128198
val firstScreen =
129-
TestComposeRendering("first", disposeStrategy = DisposeOnViewTreeLifecycleDestroyed) {
199+
BespokeComposeRendering("first", disposeStrategy = DisposeOnViewTreeLifecycleDestroyed) {
130200
DisposableEffect(Unit) {
131201
composedCount++
132202
onDispose {
133203
disposedCount++
134204
}
135205
}
136206
}
137-
val secondScreen = TestComposeRendering("second") {}
207+
val secondScreen = VanillaComposeRendering("second") {}
138208

139209
scenario.onActivity {
140210
it.setBackstack(firstScreen)
@@ -157,7 +227,7 @@ internal class ComposeViewTreeIntegrationTest {
157227
}
158228

159229
@Test fun composition_state_is_restored_after_config_change() {
160-
val firstScreen = TestComposeRendering("first") {
230+
val firstScreen = VanillaComposeRendering("first") {
161231
var counter by rememberSaveable { mutableStateOf(0) }
162232
BasicText(
163233
"Counter: $counter",
@@ -184,7 +254,7 @@ internal class ComposeViewTreeIntegrationTest {
184254
}
185255

186256
@Test fun composition_state_is_restored_after_navigating_back() {
187-
val firstScreen = TestComposeRendering("first") {
257+
val firstScreen = VanillaComposeRendering("first") {
188258
var counter by rememberSaveable { mutableStateOf(0) }
189259
BasicText(
190260
"Counter: $counter",
@@ -193,7 +263,7 @@ internal class ComposeViewTreeIntegrationTest {
193263
.testTag(CounterTag)
194264
)
195265
}
196-
val secondScreen = TestComposeRendering("second") {
266+
val secondScreen = VanillaComposeRendering("second") {
197267
BasicText("nothing to see here")
198268
}
199269

@@ -225,7 +295,7 @@ internal class ComposeViewTreeIntegrationTest {
225295

226296
@Test
227297
fun composition_state_is_restored_after_config_change_then_navigating_back() {
228-
val firstScreen = TestComposeRendering("first") {
298+
val firstScreen = VanillaComposeRendering("first") {
229299
var counter by rememberSaveable { mutableStateOf(0) }
230300
BasicText(
231301
"Counter: $counter",
@@ -234,7 +304,7 @@ internal class ComposeViewTreeIntegrationTest {
234304
.testTag(CounterTag)
235305
)
236306
}
237-
val secondScreen = TestComposeRendering("second") {
307+
val secondScreen = VanillaComposeRendering("second") {
238308
BasicText("nothing to see here")
239309
}
240310

@@ -267,7 +337,7 @@ internal class ComposeViewTreeIntegrationTest {
267337
}
268338

269339
@Test fun composition_state_is_not_restored_after_screen_is_removed_from_backstack() {
270-
val firstScreen = TestComposeRendering("first") {
340+
val firstScreen = VanillaComposeRendering("first") {
271341
var counter by rememberSaveable { mutableStateOf(0) }
272342
BasicText(
273343
"Counter: $counter",
@@ -276,7 +346,7 @@ internal class ComposeViewTreeIntegrationTest {
276346
.testTag(CounterTag)
277347
)
278348
}
279-
val secondScreen = TestComposeRendering("second") {
349+
val secondScreen = VanillaComposeRendering("second") {
280350
BasicText("nothing to see here")
281351
}
282352

@@ -312,7 +382,7 @@ internal class ComposeViewTreeIntegrationTest {
312382

313383
@Test
314384
fun composition_state_is_not_restored_after_screen_is_removed_and_replaced_from_backstack() {
315-
val firstScreen = TestComposeRendering("first") {
385+
val firstScreen = VanillaComposeRendering("first") {
316386
var counter by rememberSaveable { mutableStateOf(0) }
317387
BasicText(
318388
"Counter: $counter",
@@ -321,7 +391,7 @@ internal class ComposeViewTreeIntegrationTest {
321391
.testTag(CounterTag)
322392
)
323393
}
324-
val secondScreen = TestComposeRendering("second") {
394+
val secondScreen = VanillaComposeRendering("second") {
325395
BasicText("nothing to see here")
326396
}
327397

@@ -360,8 +430,8 @@ internal class ComposeViewTreeIntegrationTest {
360430
.assertTextEquals("Counter: 0")
361431
}
362432

363-
@Test fun composition_is_restored_in_modal_after_config_change() {
364-
val firstScreen: Screen = TestComposeRendering(compatibilityKey = "") {
433+
@Test fun composition_is_restored_in_overlay_after_config_change() {
434+
val firstScreen: Screen = VanillaComposeRendering(compatibilityKey = "") {
365435
var counter by rememberSaveable { mutableStateOf(0) }
366436
BasicText(
367437
"Counter: $counter",
@@ -392,8 +462,8 @@ internal class ComposeViewTreeIntegrationTest {
392462
.assertTextEquals("Counter: 1")
393463
}
394464

395-
@Test fun composition_is_restored_in_multiple_modals_after_config_change() {
396-
val firstScreen: Screen = TestComposeRendering(compatibilityKey = "0") {
465+
@Test fun composition_is_restored_in_multiple_overlays_after_config_change() {
466+
val firstScreen: Screen = VanillaComposeRendering(compatibilityKey = "0") {
397467
var counter by rememberSaveable { mutableStateOf(0) }
398468
BasicText(
399469
"Counter: $counter",
@@ -403,7 +473,7 @@ internal class ComposeViewTreeIntegrationTest {
403473
)
404474
}
405475

406-
val secondScreen: Screen = TestComposeRendering(compatibilityKey = "1") {
476+
val secondScreen: Screen = VanillaComposeRendering(compatibilityKey = "1") {
407477
var counter by rememberSaveable { mutableStateOf(0) }
408478
BasicText(
409479
"Counter2: $counter",
@@ -413,7 +483,7 @@ internal class ComposeViewTreeIntegrationTest {
413483
)
414484
}
415485

416-
val thirdScreen: Screen = TestComposeRendering(compatibilityKey = "2") {
486+
val thirdScreen: Screen = VanillaComposeRendering(compatibilityKey = "2") {
417487
var counter by rememberSaveable { mutableStateOf(0) }
418488
BasicText(
419489
"Counter3: $counter",
@@ -464,12 +534,12 @@ internal class ComposeViewTreeIntegrationTest {
464534
.assertTextEquals("Counter3: 1")
465535
}
466536

467-
@Test fun composition_is_restored_in_multiple_modals_backstacks_after_config_change() {
537+
@Test fun composition_is_restored_in_multiple_overlays_backstacks_after_config_change() {
468538
fun createRendering(
469539
layer: Int,
470540
screen: Int
471-
) = TestComposeRendering(
472-
// Use the same compatibility key across layers – these screens are in different modals, so
541+
) = VanillaComposeRendering(
542+
// Use the same compatibility key across layers – these screens are in different overlays, so
473543
// they won't conflict.
474544
compatibilityKey = screen.toString()
475545
) {
@@ -494,7 +564,7 @@ internal class ComposeViewTreeIntegrationTest {
494564
EmptyRendering,
495565
listOf(
496566
TestOverlay(BackStackScreen(EmptyRendering, layer0Screen0)),
497-
// A SavedStateRegistry is set up for each modal. Each registry needs a unique name,
567+
// A SavedStateRegistry is set up for each overlay. Each registry needs a unique name,
498568
// and these names default to their `Compatible.keyFor` value. When we show two
499569
// of the same type at the same time, we need to give them unique names.
500570
TestOverlay(NamedScreen(BackStackScreen(EmptyRendering, layer1Screen0), "another"))
@@ -522,7 +592,7 @@ internal class ComposeViewTreeIntegrationTest {
522592
EmptyRendering,
523593
listOf(
524594
TestOverlay(BackStackScreen(EmptyRendering, layer0Screen0, layer0Screen1)),
525-
// A SavedStateRegistry is set up for each modal. Each registry needs a unique name,
595+
// A SavedStateRegistry is set up for each overlay. Each registry needs a unique name,
526596
// and these names default to their `Compatible.keyFor` value. When we show two
527597
// of the same type at the same time, we need to give them unique names.
528598
TestOverlay(
@@ -566,7 +636,7 @@ internal class ComposeViewTreeIntegrationTest {
566636
EmptyRendering,
567637
listOf(
568638
TestOverlay(BackStackScreen(EmptyRendering, layer0Screen0)),
569-
// A SavedStateRegistry is set up for each modal. Each registry needs a unique name,
639+
// A SavedStateRegistry is set up for each overlay. Each registry needs a unique name,
570640
// and these names default to their `Compatible.keyFor` value. When we show two
571641
// of the same type at the same time, we need to give them unique names.
572642
TestOverlay(NamedScreen(BackStackScreen(EmptyRendering, layer1Screen0), "another"))
@@ -581,7 +651,7 @@ internal class ComposeViewTreeIntegrationTest {
581651
.assertIsDisplayed()
582652
}
583653

584-
private fun WorkflowUiTestActivity.setBackstack(vararg backstack: TestComposeRendering) {
654+
private fun WorkflowUiTestActivity.setBackstack(vararg backstack: Screen) {
585655
setRendering(
586656
BackStackScreen.fromList(listOf<AndroidScreen<*>>(EmptyRendering) + backstack.asList())
587657
)
@@ -598,20 +668,27 @@ internal class ComposeViewTreeIntegrationTest {
598668
}
599669
}
600670

601-
data class TestComposeRendering(
671+
/**
672+
* This is our own custom lovingly handcrafted implementation that creates [ComposeView]
673+
* itself, bypassing [ScreenComposableFactory] entirely. Allows us to mess with alternative
674+
* [ViewCompositionStrategy] approaches.
675+
*/
676+
data class BespokeComposeRendering(
602677
override val compatibilityKey: String,
603678
val disposeStrategy: ViewCompositionStrategy? = null,
604679
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
680+
) : Compatible,
681+
AndroidScreen<BespokeComposeRendering>,
682+
ScreenViewFactory<BespokeComposeRendering> {
683+
override val type: KClass<in BespokeComposeRendering> = BespokeComposeRendering::class
684+
override val viewFactory: ScreenViewFactory<BespokeComposeRendering> get() = this
608685

609686
override fun buildView(
610-
initialRendering: TestComposeRendering,
687+
initialRendering: BespokeComposeRendering,
611688
initialEnvironment: ViewEnvironment,
612689
context: Context,
613690
container: ViewGroup?
614-
): ScreenViewHolder<TestComposeRendering> {
691+
): ScreenViewHolder<BespokeComposeRendering> {
615692
var lastCompositionStrategy = initialRendering.disposeStrategy
616693

617694
return ComposeView(context).let { view ->
@@ -629,6 +706,19 @@ internal class ComposeViewTreeIntegrationTest {
629706
}
630707
}
631708

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

0 commit comments

Comments
 (0)