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