summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jordan Demeulenaere <jdemeulenaere@google.com> 2025-01-09 13:39:15 +0100
committer Jordan Demeulenaere <jdemeulenaere@google.com> 2025-01-09 15:05:10 +0100
commit0f16c025183924a529647d8732b4deb75a964ffb (patch)
treed47e0b18286edf60dbc0a0822d90137de14d23dd
parentacc993fd2b0e3a073cd7b176a20fa59e0ca798b7 (diff)
Make nested scrolling tests depend less on implementation
This CL migrates the nested scroll tests in DraggableHandler to higher level tests that use the Compose testing API. This will allow to replace the implementation of the nested scroll implementation witout having to change the tests, and is the last step towards submitting ag/30549444. Bug: 378470603 Test: atest SwipeToSceneTest Flag: EXEMPT test refactor Change-Id: I09a9ffb7fad410cef246b167e6b91560f79ea9fb
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt105
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt163
2 files changed, 163 insertions, 105 deletions
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index dbac62ffb713..9c1a14dec8fa 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -137,13 +137,6 @@ class DraggableHandlerTest {
var pointerInfoOwner: () -> PointersInfo = { pointersDown() }
- fun nestedScrollConnection() =
- NestedScrollHandlerImpl(
- draggableHandler = draggableHandler,
- pointersInfoOwner = { pointerInfoOwner() },
- )
- .connection
-
val velocityThreshold = draggableHandler.velocityThreshold
fun down(fractionOfScreen: Float) =
@@ -607,57 +600,6 @@ class DraggableHandlerTest {
}
@Test
- fun nestedScrollUseFromSourceInfo() = runGestureTest {
- // Start at scene C.
- navigateToSceneC()
- val nestedScroll = nestedScrollConnection()
-
- // Drag from the **top** of the screen
- pointerInfoOwner = { pointersDown() }
- assertIdle(currentScene = SceneC)
-
- nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- // userAction: Swipe.Up to SceneB
- toScene = SceneB,
- progress = 0.1f,
- )
-
- // Reset to SceneC
- nestedScroll.preFling(Velocity.Zero)
- advanceUntilIdle()
-
- // Drag from the **bottom** of the screen
- pointerInfoOwner = { pointersDown(startedPosition = Offset(0f, SCREEN_SIZE)) }
- assertIdle(currentScene = SceneC)
-
- nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- // userAction: Swipe.Up(fromSource = Edge.Bottom) to SceneA
- toScene = SceneA,
- progress = 0.1f,
- )
- }
-
- @Test
- fun ignoreMouseWheel() = runGestureTest {
- // Start at scene C.
- navigateToSceneC()
- val nestedScroll = nestedScrollConnection()
-
- // Use mouse wheel
- pointerInfoOwner = { PointersInfo.MouseWheel }
- assertIdle(currentScene = SceneC)
-
- nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
- assertIdle(currentScene = SceneC)
- }
-
- @Test
fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
// Swipe up from the middle to transition to scene B.
val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
@@ -689,24 +631,6 @@ class DraggableHandlerTest {
}
@Test
- fun scrollKeepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() = runGestureTest {
- val nestedScroll = nestedScrollConnection()
-
- // Overscroll is disabled, it will scroll up to 100%
- nestedScroll.scroll(available = upOffset(fractionOfScreen = 2f))
- assertTransition(fromScene = SceneA, toScene = SceneB, progress = 1f)
-
- // We need to maintain scroll priority even if the scene transition can no longer consume
- // the scroll gesture.
- nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
- assertTransition(fromScene = SceneA, toScene = SceneB, progress = 1f)
-
- // A scroll gesture in the opposite direction allows us to return to the previous scene.
- nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.5f))
- assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.5f)
- }
-
- @Test
fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
// Make scene B overscrollable.
layoutState.transitions = transitions {
@@ -944,33 +868,4 @@ class DraggableHandlerTest {
assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB)
}
-
- @Test
- fun replaceOverlayNestedScroll() = runGestureTest {
- layoutState.showOverlay(OverlayA, animationScope = testScope)
- advanceUntilIdle()
-
- // Initial state.
- assertThat(layoutState.transitionState).isIdle()
- assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
- assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayA)
-
- // Swipe down to replace overlay A by overlay B.
-
- val nestedScroll = nestedScrollConnection()
- nestedScroll.scroll(downOffset(0.1f))
- val transition = assertThat(layoutState.transitionState).isReplaceOverlayTransition()
- assertThat(transition).hasCurrentScene(SceneA)
- assertThat(transition).hasFromOverlay(OverlayA)
- assertThat(transition).hasToOverlay(OverlayB)
- assertThat(transition).hasCurrentOverlays(OverlayA)
- assertThat(transition).hasProgress(0.1f)
-
- nestedScroll.preFling(Velocity(0f, velocityThreshold))
- advanceUntilIdle()
- // Commit the gesture. The overlays are instantly swapped in the set of current overlays.
- assertThat(layoutState.transitionState).isIdle()
- assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
- assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB)
- }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index e80805a4e374..0355a30d5c73 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -40,6 +40,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ScrollWheel
+import androidx.compose.ui.test.TouchInjectionScope
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.createComposeRule
@@ -55,6 +56,8 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestOverlays.OverlayA
+import com.android.compose.animation.scene.TestOverlays.OverlayB
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
@@ -977,4 +980,164 @@ class SwipeToSceneTest {
rule.waitForIdle()
assertThat(state.transitionState).isSceneTransition()
}
+
+ @Test
+ fun nestedScroll_useFromSourceInfo() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state) {
+ scene(
+ SceneA,
+ userActions =
+ mapOf(Swipe.Down to SceneB, Swipe.Down(fromSource = Edge.Top) to SceneC),
+ ) {
+ // Use a fullscreen nested scrollable to use the nested scroll connection.
+ Box(
+ Modifier.fillMaxSize()
+ .scrollable(rememberScrollableState { 0f }, Orientation.Vertical)
+ )
+ }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ // Swiping down from the middle of the screen leads to B.
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(Offset(0f, touchSlop + 1f))
+ }
+
+ var transition = assertThat(state.transitionState).isSceneTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+
+ // Release finger and wait to settle back to A.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+ // Swiping down from the top of the screen leads to B.
+ rule.onRoot().performTouchInput {
+ down(center.copy(y = 0f))
+ moveBy(Offset(0f, touchSlop + 1f))
+ }
+
+ transition = assertThat(state.transitionState).isSceneTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneC)
+ }
+
+ @Test
+ fun nestedScroll_ignoreMouseWheel() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ // Use a fullscreen nested scrollable to use the nested scroll connection.
+ Box(
+ Modifier.fillMaxSize()
+ .scrollable(rememberScrollableState { 0f }, Orientation.Vertical)
+ )
+ }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ rule.onRoot().performMouseInput {
+ scroll(-touchSlop - 1f, scrollWheel = ScrollWheel.Vertical)
+ }
+ assertThat(state.transitionState).isIdle()
+ }
+
+ @Test
+ fun nestedScroll_keepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ // Use a fullscreen nested scrollable to use the nested scroll connection.
+ Box(
+ Modifier.fillMaxSize()
+ .scrollable(rememberScrollableState { 0f }, Orientation.Vertical)
+ )
+ }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ fun TouchInjectionScope.height() = bottom
+ fun TouchInjectionScope.halfHeight() = height() / 2f
+
+ rule.onRoot().performTouchInput {
+ down(center.copy(y = 0f))
+ moveBy(Offset(0f, touchSlop + halfHeight()))
+ }
+ val transition = assertThat(state.transitionState).isSceneTransition()
+ assertThat(transition).hasProgress(0.5f, tolerance = 0.01f)
+
+ // The progress should never go above 100%.
+ rule.onRoot().performTouchInput { moveBy(Offset(0f, height())) }
+ assertThat(transition).hasProgress(1f, tolerance = 0.01f)
+
+ // Because the overscroll effect of scene B is not attached, swiping in the opposite
+ // direction will directly decrease the progress.
+ rule.onRoot().performTouchInput { moveBy(Offset(0f, -halfHeight())) }
+ assertThat(transition).hasProgress(0.5f, tolerance = 0.01f)
+ }
+
+ @Test
+ fun nestedScroll_replaceOverlay() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ }
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) }
+ overlay(
+ OverlayA,
+ mapOf(Swipe.Down to UserActionResult.ReplaceByOverlay(OverlayB)),
+ ) {
+ Box(
+ Modifier.fillMaxSize()
+ .scrollable(rememberScrollableState { 0f }, Orientation.Vertical)
+ )
+ }
+ overlay(OverlayB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ // Swipe down 100% to replace A by B.
+ rule.onRoot().performTouchInput {
+ down(center.copy(y = 0f))
+ moveBy(Offset(0f, touchSlop + bottom))
+ }
+
+ val transition = assertThat(state.transitionState).isReplaceOverlayTransition()
+ assertThat(transition).hasCurrentScene(SceneA)
+ assertThat(transition).hasFromOverlay(OverlayA)
+ assertThat(transition).hasToOverlay(OverlayB)
+ assertThat(transition).hasCurrentOverlays(OverlayA)
+ assertThat(transition).hasProgress(1f, tolerance = 0.01f)
+
+ // Commit the gesture. The overlays are instantly swapped in the set of current overlays.
+ rule.onRoot().performTouchInput { up() }
+ assertThat(transition).hasCurrentScene(SceneA)
+ assertThat(transition).hasCurrentOverlays(OverlayB)
+
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+ assertThat(state.transitionState).hasCurrentOverlays(OverlayB)
+ }
}