@@ -3,6 +3,7 @@ package com.squareup.workflow1.ui.compose
3
3
import android.content.Context
4
4
import android.view.View
5
5
import android.view.ViewGroup
6
+ import android.widget.FrameLayout
6
7
import androidx.activity.ComponentDialog
7
8
import androidx.compose.foundation.clickable
8
9
import androidx.compose.foundation.text.BasicText
@@ -30,10 +31,13 @@ import com.squareup.workflow1.ui.Compatible
30
31
import com.squareup.workflow1.ui.NamedScreen
31
32
import com.squareup.workflow1.ui.Screen
32
33
import com.squareup.workflow1.ui.ScreenViewFactory
34
+ import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromCode
33
35
import com.squareup.workflow1.ui.ScreenViewHolder
34
36
import com.squareup.workflow1.ui.ViewEnvironment
35
37
import com.squareup.workflow1.ui.ViewRegistry
36
38
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
39
+ import com.squareup.workflow1.ui.WorkflowViewStub
40
+ import com.squareup.workflow1.ui.Wrapper
37
41
import com.squareup.workflow1.ui.internal.test.IdleAfterTestRule
38
42
import com.squareup.workflow1.ui.internal.test.IdlingDispatcherRule
39
43
import com.squareup.workflow1.ui.internal.test.WorkflowUiTestActivity
@@ -651,6 +655,162 @@ internal class ComposeViewTreeIntegrationTest {
651
655
.assertIsDisplayed()
652
656
}
653
657
658
+ @Test fun composition_handles_overlay_reordering () {
659
+ val composeA: Screen = VanillaComposeRendering (
660
+ compatibilityKey = " 0" ,
661
+ ) {
662
+ var counter by rememberSaveable { mutableStateOf(0 ) }
663
+ BasicText (
664
+ " Counter: $counter " ,
665
+ Modifier
666
+ .clickable { counter++ }
667
+ .testTag(CounterTag )
668
+ )
669
+ }
670
+
671
+ val composeB: Screen = VanillaComposeRendering (
672
+ compatibilityKey = " 1" ,
673
+ ) {
674
+ var counter by rememberSaveable { mutableStateOf(0 ) }
675
+ BasicText (
676
+ " Counter2: $counter " ,
677
+ Modifier
678
+ .clickable { counter++ }
679
+ .testTag(CounterTag2 )
680
+ )
681
+ }
682
+
683
+ scenario.onActivity {
684
+ it.setRendering(
685
+ BodyAndOverlaysScreen (
686
+ EmptyRendering ,
687
+ listOf (
688
+ TestOverlay (composeA),
689
+ TestOverlay (composeB),
690
+ // When we move this to the front, both of the other previously-upstream-
691
+ // now-downstream dialogs will be dismissed and re-shown.
692
+ TestOverlay (EmptyRendering )
693
+ )
694
+ )
695
+ )
696
+ }
697
+
698
+ composeRule.onNodeWithTag(CounterTag )
699
+ .assertTextEquals(" Counter: 0" )
700
+ .performClick()
701
+ .assertTextEquals(" Counter: 1" )
702
+
703
+ composeRule.onNodeWithTag(CounterTag2 )
704
+ .assertTextEquals(" Counter2: 0" )
705
+ .performClick()
706
+ .assertTextEquals(" Counter2: 1" )
707
+
708
+ // Reorder the overlays, dialogs will be dismissed and re-shown to preserve order.
709
+
710
+ scenario.onActivity {
711
+ it.setRendering(
712
+ BodyAndOverlaysScreen (
713
+ EmptyRendering ,
714
+ listOf (
715
+ TestOverlay (EmptyRendering ),
716
+ TestOverlay (composeB),
717
+ TestOverlay (composeA),
718
+ )
719
+ )
720
+ )
721
+ }
722
+
723
+ // Are they still responsive?
724
+
725
+ composeRule.onNodeWithTag(CounterTag )
726
+ .assertTextEquals(" Counter: 1" )
727
+ .performClick()
728
+ .assertTextEquals(" Counter: 2" )
729
+
730
+ composeRule.onNodeWithTag(CounterTag2 )
731
+ .assertTextEquals(" Counter2: 1" )
732
+ .performClick()
733
+ .assertTextEquals(" Counter2: 2" )
734
+ }
735
+
736
+ @Test fun composition_under_view_stub_handles_overlay_reordering () {
737
+ val composeA: Screen = VanillaComposeRendering (
738
+ compatibilityKey = " 0" ,
739
+ ) {
740
+ var counter by rememberSaveable { mutableStateOf(0 ) }
741
+ BasicText (
742
+ " Counter: $counter " ,
743
+ Modifier
744
+ .clickable { counter++ }
745
+ .testTag(CounterTag )
746
+ )
747
+ }
748
+
749
+ val composeB: Screen = VanillaComposeRendering (
750
+ compatibilityKey = " 1" ,
751
+ ) {
752
+ var counter by rememberSaveable { mutableStateOf(0 ) }
753
+ BasicText (
754
+ " Counter2: $counter " ,
755
+ Modifier
756
+ .clickable { counter++ }
757
+ .testTag(CounterTag2 )
758
+ )
759
+ }
760
+
761
+ scenario.onActivity {
762
+ it.setRendering(
763
+ BodyAndOverlaysScreen (
764
+ EmptyRendering ,
765
+ listOf (
766
+ TestOverlay (ViewStubWrapper (composeA)),
767
+ TestOverlay (ViewStubWrapper (composeB)),
768
+ // When we move this to the front, both of the other previously-upstream-
769
+ // now-downstream dialogs will be dismissed and re-shown.
770
+ TestOverlay (EmptyRendering )
771
+ )
772
+ )
773
+ )
774
+ }
775
+
776
+ composeRule.onNodeWithTag(CounterTag )
777
+ .assertTextEquals(" Counter: 0" )
778
+ .performClick()
779
+ .assertTextEquals(" Counter: 1" )
780
+
781
+ composeRule.onNodeWithTag(CounterTag2 )
782
+ .assertTextEquals(" Counter2: 0" )
783
+ .performClick()
784
+ .assertTextEquals(" Counter2: 1" )
785
+
786
+ // Reorder the overlays, dialogs will be dismissed and re-shown to preserve order.
787
+
788
+ scenario.onActivity {
789
+ it.setRendering(
790
+ BodyAndOverlaysScreen (
791
+ EmptyRendering ,
792
+ listOf (
793
+ TestOverlay (EmptyRendering ),
794
+ TestOverlay (ViewStubWrapper (composeB)),
795
+ TestOverlay (ViewStubWrapper (composeA)),
796
+ )
797
+ )
798
+ )
799
+ }
800
+
801
+ // Are they still responsive?
802
+
803
+ composeRule.onNodeWithTag(CounterTag )
804
+ .assertTextEquals(" Counter: 1" )
805
+ .performClick()
806
+ .assertTextEquals(" Counter: 2" )
807
+
808
+ composeRule.onNodeWithTag(CounterTag2 )
809
+ .assertTextEquals(" Counter2: 1" )
810
+ .performClick()
811
+ .assertTextEquals(" Counter2: 2" )
812
+ }
813
+
654
814
private fun WorkflowUiTestActivity.setBackstack (vararg backstack : Screen ) {
655
815
setRendering(
656
816
BackStackScreen .fromList(listOf<AndroidScreen <* >>(EmptyRendering ) + backstack.asList())
@@ -668,6 +828,26 @@ internal class ComposeViewTreeIntegrationTest {
668
828
}
669
829
}
670
830
831
+ data class ViewStubWrapper <C : Screen >(
832
+ override val content : C
833
+ ) : Screen, Wrapper<Screen, C>, AndroidScreen<ViewStubWrapper<C>> {
834
+ override fun <D : Screen > map (transform : (C ) -> D ) = ViewStubWrapper (transform(content))
835
+
836
+ override val viewFactory: ScreenViewFactory <ViewStubWrapper <C >> =
837
+ fromCode { _, initialEnvironment, context, _ ->
838
+ val stub = WorkflowViewStub (context)
839
+
840
+ FrameLayout (context)
841
+ .apply {
842
+ this .addView(stub)
843
+ }.let {
844
+ ScreenViewHolder (initialEnvironment, it) { r, e ->
845
+ stub.show(r.content, e)
846
+ }
847
+ }
848
+ }
849
+ }
850
+
671
851
/* *
672
852
* This is our own custom lovingly handcrafted implementation that creates [ComposeView]
673
853
* itself, bypassing [ScreenComposableFactory] entirely. Allows us to mess with alternative
0 commit comments