From f96159a30e37ee7572bd9ea4c511b492d9e5ed97 Mon Sep 17 00:00:00 2001 From: svastven Date: Thu, 9 Jul 2026 00:29:13 +0200 Subject: [PATCH 1/6] Add DialogSwipeBackTest test suite --- .../swipeback/DialogSwipeBackTest.kt | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt new file mode 100644 index 0000000000000..1bf29a7e2d38f --- /dev/null +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt @@ -0,0 +1,227 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.ui.interaction.swipeback + +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.background +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.UIKitInstrumentedTest +import androidx.compose.ui.test.findNodeWithTag +import androidx.compose.ui.test.findNodeWithTagOrNull +import androidx.compose.ui.test.runUIKitInstrumentedTest +import androidx.compose.ui.test.utils.hold +import androidx.compose.ui.test.utils.up +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.navigationevent.NavigationEventInfo +import androidx.navigationevent.NavigationEventTransitionState +import androidx.navigationevent.NavigationEventTransitionState.InProgress +import androidx.navigationevent.compose.NavigationBackHandler +import androidx.navigationevent.compose.rememberNavigationEventState +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse + +internal class DialogSwipeBackInHostingViewTest : DialogSwipeBackTest( + runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = true, it) } +) + +internal class DialogSwipeBackInHostingViewControllerTest : DialogSwipeBackTest( + runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = false, it) } +) + +internal abstract class DialogSwipeBackTest( + private val runUIKitInstrumentedTest: (UIKitInstrumentedTest.() -> Unit) -> Unit +) { + @Test + fun testEdgeBackSwipeOverDialogDoesNotDispatchHorizontalDragToCompose() = runUIKitInstrumentedTest { + var dragDistance = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent { + DialogBackGestureContent( + onDragDistanceChanged = { dragDistance = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } + + val backSwipe = swipeRightFromEdge().hold() + + waitForIdle() + + assertFalse( + transitionState is InProgress, + "Edge swipe over Dialog should not start root back navigation" + ) + assertEquals( + expected = 0f, + actual = dragDistance, + absoluteTolerance = 0.01f, + message = "Edge back swipe over Dialog should not dispatch horizontal drag deltas to Compose" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Back gesture over Dialog should not complete before release" + ) + + backSwipe.up() + + waitForIdle() + + assertFalse( + transitionState is InProgress, + "Releasing edge swipe over Dialog should still not start root back navigation" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Edge swipe over Dialog should not complete root back navigation" + ) + } + + @Test + fun testInnerSwipeOverDialogDispatchesHorizontalDragWithoutStartingBack() = runUIKitInstrumentedTest { + var dragDistance = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent { + DialogBackGestureContent( + onDragDistanceChanged = { dragDistance = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } + + findNodeWithTag(OVERLAY_SURFACE).swipeRight() + + waitUntil("Inner swipe should dispatch drag deltas over Dialog") { + dragDistance > 0f + } + + assertFalse( + transitionState is InProgress, + "Inner swipe over Dialog should not start back navigation" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Inner swipe over Dialog should not complete back navigation" + ) + } +} + +@Composable +private fun DialogBackGestureContent( + onDragDistanceChanged: (Float) -> Unit, + onTransitionStateChanged: (NavigationEventTransitionState) -> Unit, + onBackCompletedCountChanged: (Int) -> Unit, +) { + BackGestureHost( + onTransitionStateChanged = onTransitionStateChanged, + onBackCompletedCountChanged = onBackCompletedCountChanged + ) { + Dialog( + onDismissRequest = {}, + properties = DialogProperties( + dismissOnBackPress = false, + usePlatformDefaultWidth = false, + usePlatformInsets = false + ) + ) { + DraggableSurface(onDragDistanceChanged = onDragDistanceChanged) + } + } +} + +@Composable +private fun DraggableSurface( + onDragDistanceChanged: (Float) -> Unit, +) { + var dragDistance by remember { mutableFloatStateOf(0f) } + + onDragDistanceChanged(dragDistance) + + Box( + modifier = Modifier + .background(Color.Red) + .fillMaxSize() + .testTag(OVERLAY_SURFACE) + .draggable( + state = rememberDraggableState { delta -> + dragDistance += delta + }, + orientation = Orientation.Horizontal, + ) + ) +} + +@Composable +private fun BackGestureHost( + onTransitionStateChanged: (NavigationEventTransitionState) -> Unit, + onBackCompletedCountChanged: (Int) -> Unit, + content: @Composable () -> Unit, +) { + var backCompletedCount by remember { mutableIntStateOf(0) } + val navigationEventState = rememberNavigationEventState( + currentInfo = NavigationEventInfo.None, + backInfo = listOf(NavigationEventInfo.None) + ) + + onTransitionStateChanged(navigationEventState.transitionState) + onBackCompletedCountChanged(backCompletedCount) + + NavigationBackHandler( + state = navigationEventState, + onBackCompleted = { + backCompletedCount += 1 + } + ) + + Box(modifier = Modifier.fillMaxSize()) { + content() + } +} + +private fun UIKitInstrumentedTest.waitUntilReady( + otherConditions: () -> Boolean, +) { + waitUntil("$OVERLAY_SURFACE should be ready") { + findNodeWithTagOrNull(OVERLAY_SURFACE) != null && otherConditions() + } +} + +private const val OVERLAY_SURFACE = "overlaySurface" From 7a0c2cbcd4796c12b644d265a350b9f8c90d146d Mon Sep 17 00:00:00 2001 From: svastven Date: Thu, 9 Jul 2026 00:29:44 +0200 Subject: [PATCH 2/6] Add HorizontalScrollSwipeBackTest test suite --- .../HorizontalScrollSwipeBackTest.kt | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt new file mode 100644 index 0000000000000..ac0915302aae8 --- /dev/null +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.ui.interaction.swipeback + +import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.UIKitInstrumentedTest +import androidx.compose.ui.test.findNodeWithTag +import androidx.compose.ui.test.findNodeWithTagOrNull +import androidx.compose.ui.test.runUIKitInstrumentedTest +import androidx.compose.ui.test.utils.hold +import androidx.compose.ui.test.utils.up +import androidx.compose.ui.unit.dp +import androidx.navigationevent.NavigationEventInfo +import androidx.navigationevent.NavigationEventTransitionState +import androidx.navigationevent.NavigationEventTransitionState.InProgress +import androidx.navigationevent.compose.NavigationBackHandler +import androidx.navigationevent.compose.rememberNavigationEventState +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse + +internal class HorizontalScrollSwipeBackInHostingViewTest : HorizontalScrollSwipeBackTest( + runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = true, it) } +) + +internal class HorizontalScrollSwipeBackInHostingViewControllerTest : HorizontalScrollSwipeBackTest( + runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = false, it) } +) + +internal abstract class HorizontalScrollSwipeBackTest( + private val runUIKitInstrumentedTest: (UIKitInstrumentedTest.() -> Unit) -> Unit +) { + @Test + fun testEdgeBackSwipeOverHorizontalScrollDoesNotScrollComposeContent() = runUIKitInstrumentedTest { + var scrollOffset = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent { + HorizontalScrollBackGestureContent( + onScrollOffsetChanged = { scrollOffset = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !scrollOffset.isNaN() && backCompletedCount == 0 } + + val backSwipe = swipeRightFromEdge().hold() + waitUntil("Back swipe over horizontal scroll content should start") { + transitionState is InProgress + } + + assertEquals( + expected = 0f, + actual = scrollOffset, + absoluteTolerance = 0.01f, + message = "Edge back swipe should not scroll horizontal Compose content" + ) + + backSwipe.up() + + waitUntil("Back swipe over horizontal scroll content should complete") { + backCompletedCount == 1 + } + } + + @Test + fun testInnerSwipeOverHorizontalScrollScrollsComposeContentWithoutStartingBack() = runUIKitInstrumentedTest { + var scrollOffset = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent { + HorizontalScrollBackGestureContent( + onScrollOffsetChanged = { scrollOffset = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !scrollOffset.isNaN() && backCompletedCount == 0 } + + findNodeWithTag(SCROLL_SURFACE).swipeLeft() + + waitUntil("Inner swipe should scroll horizontal Compose content") { + scrollOffset > 0f + } + + assertFalse( + transitionState is InProgress, + "Inner swipe over horizontal scroll content should not start back navigation" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Inner swipe over horizontal scroll content should not complete back navigation" + ) + } +} + +@Composable +private fun HorizontalScrollBackGestureContent( + onScrollOffsetChanged: (Float) -> Unit, + onTransitionStateChanged: (NavigationEventTransitionState) -> Unit, + onBackCompletedCountChanged: (Int) -> Unit, +) { + var scrollOffset by remember { mutableFloatStateOf(0f) } + + onScrollOffsetChanged(scrollOffset) + + BackGestureHost( + onTransitionStateChanged = onTransitionStateChanged, + onBackCompletedCountChanged = onBackCompletedCountChanged + ) { + val scrollState = rememberScrollState() + + scrollOffset = scrollState.value.toFloat() + + Row( + modifier = Modifier + .fillMaxWidth() + .height(160.dp) + .testTag(SCROLL_SURFACE) + .horizontalScroll(scrollState) + ) { + repeat(10) { + Box( + modifier = Modifier + .size(width = 200.dp, height = 160.dp) + .background(if (it % 2 == 0) Color.Red else Color.Blue) + ) + } + } + } +} + +@Composable +private fun BackGestureHost( + onTransitionStateChanged: (NavigationEventTransitionState) -> Unit, + onBackCompletedCountChanged: (Int) -> Unit, + content: @Composable () -> Unit, +) { + var backCompletedCount by remember { mutableIntStateOf(0) } + val navigationEventState = rememberNavigationEventState( + currentInfo = NavigationEventInfo.None, + backInfo = listOf(NavigationEventInfo.None) + ) + + onTransitionStateChanged(navigationEventState.transitionState) + onBackCompletedCountChanged(backCompletedCount) + + NavigationBackHandler( + state = navigationEventState, + onBackCompleted = { + backCompletedCount += 1 + } + ) + + Box(modifier = Modifier.fillMaxSize()) { + content() + } +} + +private fun UIKitInstrumentedTest.waitUntilReady( + otherConditions: () -> Boolean, +) { + waitUntil("$SCROLL_SURFACE should be ready") { + findNodeWithTagOrNull(SCROLL_SURFACE) != null && otherConditions() + } +} + +private const val SCROLL_SURFACE = "scrollSurface" From 4486d6c3e8c54604dee054395ee9c343dc59dbf7 Mon Sep 17 00:00:00 2001 From: svastven Date: Thu, 9 Jul 2026 00:30:08 +0200 Subject: [PATCH 3/6] Add PopupSwipeBackTest test suite --- .../swipeback/PopupSwipeBackTest.kt | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt new file mode 100644 index 0000000000000..8ae431ac08d26 --- /dev/null +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt @@ -0,0 +1,227 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.ui.interaction.swipeback + +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.UIKitInstrumentedTest +import androidx.compose.ui.test.findNodeWithTag +import androidx.compose.ui.test.findNodeWithTagOrNull +import androidx.compose.ui.test.runUIKitInstrumentedTest +import androidx.compose.ui.test.utils.hold +import androidx.compose.ui.test.utils.up +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import androidx.navigationevent.NavigationEventInfo +import androidx.navigationevent.NavigationEventTransitionState +import androidx.navigationevent.NavigationEventTransitionState.InProgress +import androidx.navigationevent.compose.NavigationBackHandler +import androidx.navigationevent.compose.rememberNavigationEventState +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse + +internal class PopupSwipeBackInHostingViewTest : PopupSwipeBackTest( + runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = true, it) } +) + +internal class PopupSwipeBackInHostingViewControllerTest : PopupSwipeBackTest( + runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = false, it) } +) + +internal abstract class PopupSwipeBackTest( + private val runUIKitInstrumentedTest: (UIKitInstrumentedTest.() -> Unit) -> Unit +) { + @Test + fun testEdgeBackSwipeOverPopupDoesNotDispatchHorizontalDragToCompose() = runComposeContainerTest { + var dragDistance = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent { + PopupBackGestureContent( + onDragDistanceChanged = { dragDistance = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } + + val backSwipe = swipeRightFromEdge().hold() + waitForIdle() + + assertFalse( + transitionState is InProgress, + "Edge swipe over Popup should not start root back navigation" + ) + assertEquals( + expected = 0f, + actual = dragDistance, + absoluteTolerance = 0.01f, + message = "Edge back swipe over Popup should not dispatch horizontal drag deltas to Compose" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Back gesture over Popup should not complete before release" + ) + + backSwipe.up() + waitForIdle() + + assertFalse( + transitionState is InProgress, + "Releasing edge swipe over Popup should still not start root back navigation" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Edge swipe over Popup should not complete root back navigation" + ) + } + + @Test + fun testInnerSwipeOverPopupDispatchesHorizontalDragWithoutStartingBack() = runComposeContainerTest { + var dragDistance = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent { + PopupBackGestureContent( + onDragDistanceChanged = { dragDistance = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } + + findNodeWithTag(OVERLAY_SURFACE).swipeRight() + + waitUntil("Inner swipe should dispatch drag deltas over Popup") { + dragDistance > 0f + } + + assertFalse( + transitionState is InProgress, + "Inner swipe over Popup should not start back navigation" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Inner swipe over Popup should not complete back navigation" + ) + } + + private fun runComposeContainerTest(testBlock: UIKitInstrumentedTest.() -> Unit) { + runUIKitInstrumentedTest(testBlock) + } +} + +@Composable +private fun PopupBackGestureContent( + onDragDistanceChanged: (Float) -> Unit, + onTransitionStateChanged: (NavigationEventTransitionState) -> Unit, + onBackCompletedCountChanged: (Int) -> Unit, +) { + BackGestureHost( + onTransitionStateChanged = onTransitionStateChanged, + onBackCompletedCountChanged = onBackCompletedCountChanged + ) { + Popup( + properties = PopupProperties( + dismissOnClickOutside = false, + dismissOnBackPress = false, + focusable = true, + usePlatformDefaultWidth = false, + usePlatformInsets = false + ) + ) { + DraggableSurface(onDragDistanceChanged = onDragDistanceChanged) + } + } +} + +@Composable +private fun DraggableSurface( + onDragDistanceChanged: (Float) -> Unit, +) { + var dragDistance by remember { mutableFloatStateOf(0f) } + + onDragDistanceChanged(dragDistance) + + Box( + modifier = Modifier + .fillMaxSize() + .testTag(OVERLAY_SURFACE) + .draggable( + state = rememberDraggableState { delta -> + dragDistance += delta + }, + orientation = Orientation.Horizontal, + ) + ) +} + +@Composable +private fun BackGestureHost( + onTransitionStateChanged: (NavigationEventTransitionState) -> Unit, + onBackCompletedCountChanged: (Int) -> Unit, + content: @Composable () -> Unit, +) { + var backCompletedCount by remember { mutableIntStateOf(0) } + val navigationEventState = rememberNavigationEventState( + currentInfo = NavigationEventInfo.None, + backInfo = listOf(NavigationEventInfo.None) + ) + + onTransitionStateChanged(navigationEventState.transitionState) + onBackCompletedCountChanged(backCompletedCount) + + NavigationBackHandler( + state = navigationEventState, + onBackCompleted = { + backCompletedCount += 1 + } + ) + + Box(modifier = Modifier.fillMaxSize()) { + content() + } +} + +private fun UIKitInstrumentedTest.waitUntilReady( + otherConditions: () -> Boolean, +) { + waitUntil("$OVERLAY_SURFACE should be ready") { + findNodeWithTagOrNull(OVERLAY_SURFACE) != null && otherConditions() + } +} + +private const val OVERLAY_SURFACE = "overlaySurface" From 53ac1918081ad469403a4caf3853a63d00911cd7 Mon Sep 17 00:00:00 2001 From: svastven Date: Thu, 9 Jul 2026 00:36:28 +0200 Subject: [PATCH 4/6] Fix renamed function --- .../compose/ui/interaction/swipeback/DialogSwipeBackTest.kt | 2 +- .../ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt | 2 +- .../compose/ui/interaction/swipeback/PopupSwipeBackTest.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt index 1bf29a7e2d38f..c152940362b2d 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt @@ -75,7 +75,7 @@ internal abstract class DialogSwipeBackTest( waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } - val backSwipe = swipeRightFromEdge().hold() + val backSwipe = swipeFromLeftEdge().hold() waitForIdle() diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt index ac0915302aae8..963dd718646fa 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt @@ -77,7 +77,7 @@ internal abstract class HorizontalScrollSwipeBackTest( waitUntilReady { !scrollOffset.isNaN() && backCompletedCount == 0 } - val backSwipe = swipeRightFromEdge().hold() + val backSwipe = swipeFromLeftEdge().hold() waitUntil("Back swipe over horizontal scroll content should start") { transitionState is InProgress } diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt index 8ae431ac08d26..5121ddff72d17 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt @@ -73,7 +73,7 @@ internal abstract class PopupSwipeBackTest( waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } - val backSwipe = swipeRightFromEdge().hold() + val backSwipe = swipeFromLeftEdge().hold() waitForIdle() assertFalse( From 9d36c6adb7ae2b2cf02d5013f7c97fe45bdbfb2a Mon Sep 17 00:00:00 2001 From: svastven Date: Thu, 9 Jul 2026 10:55:36 +0200 Subject: [PATCH 5/6] Consolidate iOS swipe back instrumented tests --- .../swipeback/DialogSwipeBackTest.kt | 94 ++++++++++- .../HorizontalScrollSwipeBackTest.kt | 53 +++++- .../swipeback/ModalContainerSwipeBackTest.kt | 157 ++++++++++++++++++ .../NonFullscreenContainerSwipeBackTest.kt | 141 ++++++++++++++++ .../swipeback/PopupSwipeBackTest.kt | 92 +++++++++- .../{ => swipeback}/SwipeBackTest.kt | 24 ++- .../compose/ui/test/UIKitInstrumentedTest.kt | 2 +- 7 files changed, 549 insertions(+), 14 deletions(-) create mode 100644 compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/ModalContainerSwipeBackTest.kt create mode 100644 compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/NonFullscreenContainerSwipeBackTest.kt rename compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/{ => swipeback}/SwipeBackTest.kt (96%) diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt index c152940362b2d..b0127e8cc3561 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt @@ -47,6 +47,8 @@ import androidx.navigationevent.compose.rememberNavigationEventState import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import platform.UIKit.UITraitEnvironmentLayoutDirectionLeftToRight +import platform.UIKit.UITraitEnvironmentLayoutDirectionRightToLeft internal class DialogSwipeBackInHostingViewTest : DialogSwipeBackTest( runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = true, it) } @@ -60,12 +62,12 @@ internal abstract class DialogSwipeBackTest( private val runUIKitInstrumentedTest: (UIKitInstrumentedTest.() -> Unit) -> Unit ) { @Test - fun testEdgeBackSwipeOverDialogDoesNotDispatchHorizontalDragToCompose() = runUIKitInstrumentedTest { + fun testEdgeBackSwipeOverDialogDoesNotDispatchHorizontalDragToComposeLtr() = runUIKitInstrumentedTest { var dragDistance = Float.NaN var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle var backCompletedCount = -1 - setContent { + setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { DialogBackGestureContent( onDragDistanceChanged = { dragDistance = it }, onTransitionStateChanged = { transitionState = it }, @@ -111,12 +113,63 @@ internal abstract class DialogSwipeBackTest( } @Test - fun testInnerSwipeOverDialogDispatchesHorizontalDragWithoutStartingBack() = runUIKitInstrumentedTest { + fun testEdgeBackSwipeOverDialogDoesNotDispatchHorizontalDragToComposeRtl() = runUIKitInstrumentedTest { var dragDistance = Float.NaN var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle var backCompletedCount = -1 - setContent { + setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { + DialogBackGestureContent( + onDragDistanceChanged = { dragDistance = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } + + val backSwipe = swipeFromRightEdge().hold() + + waitForIdle() + + assertFalse( + transitionState is InProgress, + "Edge swipe over Dialog should not start root back navigation" + ) + assertEquals( + expected = 0f, + actual = dragDistance, + absoluteTolerance = 0.01f, + message = "Edge back swipe over Dialog should not dispatch horizontal drag deltas to Compose" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Back gesture over Dialog should not complete before release" + ) + + backSwipe.up() + + waitForIdle() + + assertFalse( + transitionState is InProgress, + "Releasing edge swipe over Dialog should still not start root back navigation" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Edge swipe over Dialog should not complete root back navigation" + ) + } + + @Test + fun testInnerSwipeOverDialogDispatchesHorizontalDragWithoutStartingBackLtr() = runUIKitInstrumentedTest { + var dragDistance = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { DialogBackGestureContent( onDragDistanceChanged = { dragDistance = it }, onTransitionStateChanged = { transitionState = it }, @@ -142,6 +195,39 @@ internal abstract class DialogSwipeBackTest( message = "Inner swipe over Dialog should not complete back navigation" ) } + + @Test + fun testInnerSwipeOverDialogDispatchesHorizontalDragWithoutStartingBackRtl() = runUIKitInstrumentedTest { + var dragDistance = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { + DialogBackGestureContent( + onDragDistanceChanged = { dragDistance = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } + + findNodeWithTag(OVERLAY_SURFACE).swipeLeft() + + waitUntil("Inner swipe should dispatch drag deltas over Dialog") { + dragDistance < 0f + } + + assertFalse( + transitionState is InProgress, + "Inner swipe over Dialog should not start back navigation" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Inner swipe over Dialog should not complete back navigation" + ) + } } @Composable diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt index 963dd718646fa..784561bd2dfed 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.test.runUIKitInstrumentedTest import androidx.compose.ui.test.utils.hold import androidx.compose.ui.test.utils.up import androidx.compose.ui.unit.dp +import androidx.navigationevent.NavigationEvent import androidx.navigationevent.NavigationEventInfo import androidx.navigationevent.NavigationEventTransitionState import androidx.navigationevent.NavigationEventTransitionState.InProgress @@ -49,6 +50,8 @@ import androidx.navigationevent.compose.rememberNavigationEventState import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import platform.UIKit.UITraitEnvironmentLayoutDirectionLeftToRight +import platform.UIKit.UITraitEnvironmentLayoutDirectionRightToLeft internal class HorizontalScrollSwipeBackInHostingViewTest : HorizontalScrollSwipeBackTest( runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = true, it) } @@ -62,12 +65,12 @@ internal abstract class HorizontalScrollSwipeBackTest( private val runUIKitInstrumentedTest: (UIKitInstrumentedTest.() -> Unit) -> Unit ) { @Test - fun testEdgeBackSwipeOverHorizontalScrollDoesNotScrollComposeContent() = runUIKitInstrumentedTest { + fun testEdgeBackSwipeOverHorizontalScrollDoesNotScrollComposeContentLtr() = runUIKitInstrumentedTest { var scrollOffset = Float.NaN var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle var backCompletedCount = -1 - setContent { + setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { HorizontalScrollBackGestureContent( onScrollOffsetChanged = { scrollOffset = it }, onTransitionStateChanged = { transitionState = it }, @@ -82,6 +85,51 @@ internal abstract class HorizontalScrollSwipeBackTest( transitionState is InProgress } + assertEquals( + expected = NavigationEvent.EDGE_LEFT, + actual = (transitionState as InProgress).latestEvent.swipeEdge, + message = "Back swipe over horizontal scroll content should report the expected edge" + ) + assertEquals( + expected = 0f, + actual = scrollOffset, + absoluteTolerance = 0.01f, + message = "Edge back swipe should not scroll horizontal Compose content" + ) + + backSwipe.up() + + waitUntil("Back swipe over horizontal scroll content should complete") { + backCompletedCount == 1 + } + } + + @Test + fun testEdgeBackSwipeOverHorizontalScrollDoesNotScrollComposeContentRtl() = runUIKitInstrumentedTest { + var scrollOffset = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { + HorizontalScrollBackGestureContent( + onScrollOffsetChanged = { scrollOffset = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !scrollOffset.isNaN() && backCompletedCount == 0 } + + val backSwipe = swipeFromRightEdge().hold() + waitUntil("Back swipe over horizontal scroll content should start") { + transitionState is InProgress + } + + assertEquals( + expected = NavigationEvent.EDGE_RIGHT, + actual = (transitionState as InProgress).latestEvent.swipeEdge, + message = "Back swipe over horizontal scroll content should report the expected edge" + ) assertEquals( expected = 0f, actual = scrollOffset, @@ -128,6 +176,7 @@ internal abstract class HorizontalScrollSwipeBackTest( message = "Inner swipe over horizontal scroll content should not complete back navigation" ) } + } @Composable diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/ModalContainerSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/ModalContainerSwipeBackTest.kt new file mode 100644 index 0000000000000..216c3e95370af --- /dev/null +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/ModalContainerSwipeBackTest.kt @@ -0,0 +1,157 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.ui.interaction.swipeback + +import androidx.compose.ui.test.UIKitInstrumentedTest +import androidx.compose.ui.test.findNodeWithTagOrNull +import androidx.compose.ui.test.runUIKitInstrumentedTest +import androidx.compose.ui.test.setLayoutDirection +import androidx.compose.ui.test.utils.hold +import androidx.compose.ui.test.utils.up +import androidx.navigationevent.NavigationEvent +import androidx.navigationevent.NavigationEventTransitionState +import androidx.navigationevent.NavigationEventTransitionState.InProgress +import kotlin.test.Test +import kotlin.test.assertEquals +import platform.UIKit.UIModalPresentationFullScreen +import platform.UIKit.UITraitEnvironmentLayoutDirectionLeftToRight +import platform.UIKit.UITraitEnvironmentLayoutDirectionRightToLeft +import platform.UIKit.UIViewController + +internal class ModalContainerSwipeBackInHostingViewTest : ModalContainerSwipeBackTest( + runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = true, it) } +) + +internal class ModalContainerSwipeBackInHostingViewControllerTest : ModalContainerSwipeBackTest( + runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = false, it) } +) + +internal abstract class ModalContainerSwipeBackTest( + private val runUIKitInstrumentedTest: (UIKitInstrumentedTest.() -> Unit) -> Unit +) { + @Test + fun testBackSwipeCompletesInModalContainerLtr() = runUIKitInstrumentedTest { + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + val rootViewController = UIViewController().apply { + setLayoutDirection(UITraitEnvironmentLayoutDirectionLeftToRight) + } + setupWindow { rootViewController } + + waitUntil("root view controller should be attached before presenting modal") { + rootViewController.view.window != null + } + + var presented = false + rootViewController.presentViewController( + viewControllerToPresent = createViewControllerHostingCompose { + TestContent( + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + }.apply { + modalPresentationStyle = UIModalPresentationFullScreen + setLayoutDirection(UITraitEnvironmentLayoutDirectionLeftToRight) + }, + animated = false + ) { + presented = true + } + + waitUntil("modal container should be presented") { presented } + + val swipeBack = swipeFromLeftEdge().hold() + + waitUntil("back swipe should be in progress in a modal container") { + transitionState is InProgress + } + + assertEquals( + expected = NavigationEvent.EDGE_LEFT, + actual = (transitionState as InProgress).latestEvent.swipeEdge, + message = "back swipe should report the expected edge in a modal container" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "back swipe should not complete before release in a modal container" + ) + + swipeBack.up() + + waitUntil("back swipe should complete in a modal container") { + backCompletedCount == 1 + } + } + + @Test + fun testBackSwipeCompletesInModalContainerRtl() = runUIKitInstrumentedTest { + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + val rootViewController = UIViewController().apply { + setLayoutDirection(UITraitEnvironmentLayoutDirectionRightToLeft) + } + setupWindow { rootViewController } + + waitUntil("root view controller should be attached before presenting modal") { + rootViewController.view.window != null + } + + var presented = false + rootViewController.presentViewController( + viewControllerToPresent = createViewControllerHostingCompose { + TestContent( + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + }.apply { + modalPresentationStyle = UIModalPresentationFullScreen + setLayoutDirection(UITraitEnvironmentLayoutDirectionRightToLeft) + }, + animated = false + ) { + presented = true + } + + waitUntil("modal container should be presented") { presented } + + val swipeBack = swipeFromRightEdge().hold() + + waitUntil("back swipe should be in progress in a modal container") { + transitionState is InProgress + } + + assertEquals( + expected = NavigationEvent.EDGE_RIGHT, + actual = (transitionState as InProgress).latestEvent.swipeEdge, + message = "back swipe should report the expected edge in a modal container" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "back swipe should not complete before release in a modal container" + ) + + swipeBack.up() + + waitUntil("back swipe should complete in a modal container") { + backCompletedCount == 1 + } + } +} diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/NonFullscreenContainerSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/NonFullscreenContainerSwipeBackTest.kt new file mode 100644 index 0000000000000..f77ddfb6edcc6 --- /dev/null +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/NonFullscreenContainerSwipeBackTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.ui.interaction.swipeback + +import androidx.compose.ui.test.UIKitInstrumentedTest +import androidx.compose.ui.test.findNodeWithTagOrNull +import androidx.compose.ui.test.runUIKitInstrumentedTest +import androidx.compose.ui.test.setLayoutDirection +import androidx.compose.ui.test.utils.hold +import androidx.compose.ui.test.utils.up +import androidx.navigationevent.NavigationEvent +import androidx.navigationevent.NavigationEventTransitionState +import androidx.navigationevent.NavigationEventTransitionState.InProgress +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlinx.cinterop.ExperimentalForeignApi +import platform.CoreGraphics.CGRectMake +import platform.UIKit.UITraitEnvironmentLayoutDirectionLeftToRight +import platform.UIKit.UITraitEnvironmentLayoutDirectionRightToLeft +import platform.UIKit.UIViewController +import platform.UIKit.addChildViewController +import platform.UIKit.didMoveToParentViewController + +internal class NonFullscreenContainerSwipeBackInHostingViewTest : NonFullscreenContainerSwipeBackTest( + runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = true, it) } +) + +internal class NonFullscreenContainerSwipeBackInHostingViewControllerTest : + NonFullscreenContainerSwipeBackTest( + runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = false, it) } + ) + +internal abstract class NonFullscreenContainerSwipeBackTest( + private val runUIKitInstrumentedTest: (UIKitInstrumentedTest.() -> Unit) -> Unit +) { + @OptIn(ExperimentalForeignApi::class) + @Test + fun testBackSwipeCompletesInNonFullscreenContainerLtr() = runUIKitInstrumentedTest { + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setupWindow { + val composeViewController = createViewControllerHostingCompose { + TestContent( + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + }.apply { setLayoutDirection(UITraitEnvironmentLayoutDirectionLeftToRight) } + + UIViewController().also { hostViewController -> + hostViewController.addChildViewController(composeViewController) + hostViewController.view.addSubview(composeViewController.view) + composeViewController.view.setFrame(CGRectMake(80.0, 160.0, 220.0, 260.0)) + composeViewController.didMoveToParentViewController(hostViewController) + } + } + + val swipeBack = swipeFromLeftEdge().hold() + + waitUntil("back swipe should be in progress in a non-fullscreen container") { + transitionState is InProgress + } + + assertEquals( + expected = NavigationEvent.EDGE_LEFT, + actual = (transitionState as InProgress).latestEvent.swipeEdge, + message = "back swipe should report the expected edge in a non-fullscreen container" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "back swipe should not complete before release in a non-fullscreen container" + ) + + swipeBack.up() + + waitUntil("back swipe should complete in a non-fullscreen container") { + backCompletedCount == 1 + } + } + + @OptIn(ExperimentalForeignApi::class) + @Test + fun testBackSwipeCompletesInNonFullscreenContainerRtl() = runUIKitInstrumentedTest { + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setupWindow { + val composeViewController = createViewControllerHostingCompose { + TestContent( + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + }.apply { setLayoutDirection(UITraitEnvironmentLayoutDirectionRightToLeft) } + + UIViewController().also { hostViewController -> + hostViewController.addChildViewController(composeViewController) + hostViewController.view.addSubview(composeViewController.view) + composeViewController.view.setFrame(CGRectMake(80.0, 160.0, 220.0, 260.0)) + composeViewController.didMoveToParentViewController(hostViewController) + } + } + + val swipeBack = swipeFromRightEdge().hold() + + waitUntil("back swipe should be in progress in a non-fullscreen container") { + transitionState is InProgress + } + + assertEquals( + expected = NavigationEvent.EDGE_RIGHT, + actual = (transitionState as InProgress).latestEvent.swipeEdge, + message = "back swipe should report the expected edge in a non-fullscreen container" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "back swipe should not complete before release in a non-fullscreen container" + ) + + swipeBack.up() + + waitUntil("back swipe should complete in a non-fullscreen container") { + backCompletedCount == 1 + } + } +} diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt index 5121ddff72d17..ae60a81f052e3 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt @@ -45,6 +45,8 @@ import androidx.navigationevent.compose.rememberNavigationEventState import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import platform.UIKit.UITraitEnvironmentLayoutDirectionLeftToRight +import platform.UIKit.UITraitEnvironmentLayoutDirectionRightToLeft internal class PopupSwipeBackInHostingViewTest : PopupSwipeBackTest( runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = true, it) } @@ -58,12 +60,12 @@ internal abstract class PopupSwipeBackTest( private val runUIKitInstrumentedTest: (UIKitInstrumentedTest.() -> Unit) -> Unit ) { @Test - fun testEdgeBackSwipeOverPopupDoesNotDispatchHorizontalDragToCompose() = runComposeContainerTest { + fun testEdgeBackSwipeOverPopupDoesNotDispatchHorizontalDragToComposeLtr() = runComposeContainerTest { var dragDistance = Float.NaN var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle var backCompletedCount = -1 - setContent { + setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { PopupBackGestureContent( onDragDistanceChanged = { dragDistance = it }, onTransitionStateChanged = { transitionState = it }, @@ -107,12 +109,61 @@ internal abstract class PopupSwipeBackTest( } @Test - fun testInnerSwipeOverPopupDispatchesHorizontalDragWithoutStartingBack() = runComposeContainerTest { + fun testEdgeBackSwipeOverPopupDoesNotDispatchHorizontalDragToComposeRtl() = runComposeContainerTest { var dragDistance = Float.NaN var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle var backCompletedCount = -1 - setContent { + setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { + PopupBackGestureContent( + onDragDistanceChanged = { dragDistance = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } + + val backSwipe = swipeFromRightEdge().hold() + waitForIdle() + + assertFalse( + transitionState is InProgress, + "Edge swipe over Popup should not start root back navigation" + ) + assertEquals( + expected = 0f, + actual = dragDistance, + absoluteTolerance = 0.01f, + message = "Edge back swipe over Popup should not dispatch horizontal drag deltas to Compose" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Back gesture over Popup should not complete before release" + ) + + backSwipe.up() + waitForIdle() + + assertFalse( + transitionState is InProgress, + "Releasing edge swipe over Popup should still not start root back navigation" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Edge swipe over Popup should not complete root back navigation" + ) + } + + @Test + fun testInnerSwipeOverPopupDispatchesHorizontalDragWithoutStartingBackLtr() = runComposeContainerTest { + var dragDistance = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { PopupBackGestureContent( onDragDistanceChanged = { dragDistance = it }, onTransitionStateChanged = { transitionState = it }, @@ -139,6 +190,39 @@ internal abstract class PopupSwipeBackTest( ) } + @Test + fun testInnerSwipeOverPopupDispatchesHorizontalDragWithoutStartingBackRtl() = runComposeContainerTest { + var dragDistance = Float.NaN + var transitionState: NavigationEventTransitionState = NavigationEventTransitionState.Idle + var backCompletedCount = -1 + + setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { + PopupBackGestureContent( + onDragDistanceChanged = { dragDistance = it }, + onTransitionStateChanged = { transitionState = it }, + onBackCompletedCountChanged = { backCompletedCount = it } + ) + } + + waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } + + findNodeWithTag(OVERLAY_SURFACE).swipeLeft() + + waitUntil("Inner swipe should dispatch drag deltas over Popup") { + dragDistance < 0f + } + + assertFalse( + transitionState is InProgress, + "Inner swipe over Popup should not start back navigation" + ) + assertEquals( + expected = 0, + actual = backCompletedCount, + message = "Inner swipe over Popup should not complete back navigation" + ) + } + private fun runComposeContainerTest(testBlock: UIKitInstrumentedTest.() -> Unit) { runUIKitInstrumentedTest(testBlock) } diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/SwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/SwipeBackTest.kt similarity index 96% rename from compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/SwipeBackTest.kt rename to compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/SwipeBackTest.kt index ac7de9848cad1..873076d76fdef 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/SwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/SwipeBackTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.compose.ui.interaction +package androidx.compose.ui.interaction.swipeback import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable @@ -34,10 +34,12 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.UIKitInstrumentedTest import androidx.compose.ui.test.findNodeWithTag import androidx.compose.ui.test.runUIKitInstrumentedTest +import androidx.compose.ui.test.setLayoutDirection import androidx.compose.ui.test.utils.hold import androidx.compose.ui.test.utils.up import androidx.compose.ui.uikit.EndEdgePanGestureBehavior import androidx.compose.ui.unit.LayoutDirection +import androidx.navigationevent.NavigationEvent import androidx.navigationevent.NavigationEventInfo import androidx.navigationevent.NavigationEventTransitionState import androidx.navigationevent.NavigationEventTransitionState.InProgress @@ -47,8 +49,11 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import platform.UIKit.UISemanticContentAttributeForceLeftToRight +import platform.UIKit.UISemanticContentAttributeForceRightToLeft import platform.UIKit.UITraitEnvironmentLayoutDirectionLeftToRight import platform.UIKit.UITraitEnvironmentLayoutDirectionRightToLeft +import platform.UIKit.UIView internal class SwipeBackInHostingViewTest : SwipeBackTest( runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = true, it) } @@ -119,6 +124,12 @@ internal abstract class SwipeBackTest( transitionState is InProgress } + assertEquals( + expected = NavigationEvent.EDGE_LEFT, + actual = (transitionState as InProgress).latestEvent.swipeEdge, + message = "left edge swipe back should report EDGE_LEFT in LTR" + ) + swipeBack.up() waitUntil("left edge back swipe should complete in LTR") { @@ -142,6 +153,12 @@ internal abstract class SwipeBackTest( assertTrue(transitionState is InProgress, message = "right edge swipe back should be in progress in RTL") + assertEquals( + expected = NavigationEvent.EDGE_RIGHT, + actual = (transitionState as InProgress).latestEvent.swipeEdge, + message = "right edge swipe back should report EDGE_RIGHT in RTL" + ) + swipeBack.up() waitForIdle() @@ -598,10 +615,11 @@ internal abstract class SwipeBackTest( message = "left edge swipe back should complete in LTR" ) } + } @Composable -private fun TestContent( +internal fun TestContent( onDragDistanceChanged: (Float) -> Unit = {}, onTransitionStateChanged: (NavigationEventTransitionState) -> Unit = {}, onBackCompletedCountChanged: (Int) -> Unit = {}, @@ -643,4 +661,4 @@ private fun TestContent( ) } -private const val DRAG_SURFACE = "dragSurface" +internal const val DRAG_SURFACE = "dragSurface" diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/test/UIKitInstrumentedTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/test/UIKitInstrumentedTest.kt index a160f3131a5c2..5d9c41b296dab 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/test/UIKitInstrumentedTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/test/UIKitInstrumentedTest.kt @@ -910,7 +910,7 @@ internal fun UIKitInstrumentedTest.waitForContextMenu() { delay(500) // wait for toolbar animation } -private fun UIViewController.setLayoutDirection( +internal fun UIViewController.setLayoutDirection( layoutDirection: UITraitEnvironmentLayoutDirection ) { if (available(OS.Ios to OSVersion(major = 17))) { From 637cabb490b3b1a126e6fe70694315d1dead54af Mon Sep 17 00:00:00 2001 From: svastven Date: Thu, 9 Jul 2026 11:05:26 +0200 Subject: [PATCH 6/6] Remove redundant waitUntilReady and rename TestContent to SwipeBackTestContent --- .../swipeback/DialogSwipeBackTest.kt | 16 ------ .../HorizontalScrollSwipeBackTest.kt | 14 ------ .../swipeback/ModalContainerSwipeBackTest.kt | 5 +- .../NonFullscreenContainerSwipeBackTest.kt | 5 +- .../swipeback/PopupSwipeBackTest.kt | 16 ------ .../ui/interaction/swipeback/SwipeBackTest.kt | 50 +++++++++---------- 6 files changed, 27 insertions(+), 79 deletions(-) diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt index b0127e8cc3561..359cd06bc73c8 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/DialogSwipeBackTest.kt @@ -75,8 +75,6 @@ internal abstract class DialogSwipeBackTest( ) } - waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } - val backSwipe = swipeFromLeftEdge().hold() waitForIdle() @@ -126,8 +124,6 @@ internal abstract class DialogSwipeBackTest( ) } - waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } - val backSwipe = swipeFromRightEdge().hold() waitForIdle() @@ -177,8 +173,6 @@ internal abstract class DialogSwipeBackTest( ) } - waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } - findNodeWithTag(OVERLAY_SURFACE).swipeRight() waitUntil("Inner swipe should dispatch drag deltas over Dialog") { @@ -210,8 +204,6 @@ internal abstract class DialogSwipeBackTest( ) } - waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } - findNodeWithTag(OVERLAY_SURFACE).swipeLeft() waitUntil("Inner swipe should dispatch drag deltas over Dialog") { @@ -302,12 +294,4 @@ private fun BackGestureHost( } } -private fun UIKitInstrumentedTest.waitUntilReady( - otherConditions: () -> Boolean, -) { - waitUntil("$OVERLAY_SURFACE should be ready") { - findNodeWithTagOrNull(OVERLAY_SURFACE) != null && otherConditions() - } -} - private const val OVERLAY_SURFACE = "overlaySurface" diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt index 784561bd2dfed..65e2101c5a6a3 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/HorizontalScrollSwipeBackTest.kt @@ -78,8 +78,6 @@ internal abstract class HorizontalScrollSwipeBackTest( ) } - waitUntilReady { !scrollOffset.isNaN() && backCompletedCount == 0 } - val backSwipe = swipeFromLeftEdge().hold() waitUntil("Back swipe over horizontal scroll content should start") { transitionState is InProgress @@ -118,8 +116,6 @@ internal abstract class HorizontalScrollSwipeBackTest( ) } - waitUntilReady { !scrollOffset.isNaN() && backCompletedCount == 0 } - val backSwipe = swipeFromRightEdge().hold() waitUntil("Back swipe over horizontal scroll content should start") { transitionState is InProgress @@ -158,8 +154,6 @@ internal abstract class HorizontalScrollSwipeBackTest( ) } - waitUntilReady { !scrollOffset.isNaN() && backCompletedCount == 0 } - findNodeWithTag(SCROLL_SURFACE).swipeLeft() waitUntil("Inner swipe should scroll horizontal Compose content") { @@ -242,12 +236,4 @@ private fun BackGestureHost( } } -private fun UIKitInstrumentedTest.waitUntilReady( - otherConditions: () -> Boolean, -) { - waitUntil("$SCROLL_SURFACE should be ready") { - findNodeWithTagOrNull(SCROLL_SURFACE) != null && otherConditions() - } -} - private const val SCROLL_SURFACE = "scrollSurface" diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/ModalContainerSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/ModalContainerSwipeBackTest.kt index 216c3e95370af..d0bcdc080a661 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/ModalContainerSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/ModalContainerSwipeBackTest.kt @@ -17,7 +17,6 @@ package androidx.compose.ui.interaction.swipeback import androidx.compose.ui.test.UIKitInstrumentedTest -import androidx.compose.ui.test.findNodeWithTagOrNull import androidx.compose.ui.test.runUIKitInstrumentedTest import androidx.compose.ui.test.setLayoutDirection import androidx.compose.ui.test.utils.hold @@ -60,7 +59,7 @@ internal abstract class ModalContainerSwipeBackTest( var presented = false rootViewController.presentViewController( viewControllerToPresent = createViewControllerHostingCompose { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -116,7 +115,7 @@ internal abstract class ModalContainerSwipeBackTest( var presented = false rootViewController.presentViewController( viewControllerToPresent = createViewControllerHostingCompose { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/NonFullscreenContainerSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/NonFullscreenContainerSwipeBackTest.kt index f77ddfb6edcc6..b814603151f10 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/NonFullscreenContainerSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/NonFullscreenContainerSwipeBackTest.kt @@ -17,7 +17,6 @@ package androidx.compose.ui.interaction.swipeback import androidx.compose.ui.test.UIKitInstrumentedTest -import androidx.compose.ui.test.findNodeWithTagOrNull import androidx.compose.ui.test.runUIKitInstrumentedTest import androidx.compose.ui.test.setLayoutDirection import androidx.compose.ui.test.utils.hold @@ -55,7 +54,7 @@ internal abstract class NonFullscreenContainerSwipeBackTest( setupWindow { val composeViewController = createViewControllerHostingCompose { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -101,7 +100,7 @@ internal abstract class NonFullscreenContainerSwipeBackTest( setupWindow { val composeViewController = createViewControllerHostingCompose { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt index ae60a81f052e3..baa71a05e45f8 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/PopupSwipeBackTest.kt @@ -73,8 +73,6 @@ internal abstract class PopupSwipeBackTest( ) } - waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } - val backSwipe = swipeFromLeftEdge().hold() waitForIdle() @@ -122,8 +120,6 @@ internal abstract class PopupSwipeBackTest( ) } - waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } - val backSwipe = swipeFromRightEdge().hold() waitForIdle() @@ -171,8 +167,6 @@ internal abstract class PopupSwipeBackTest( ) } - waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } - findNodeWithTag(OVERLAY_SURFACE).swipeRight() waitUntil("Inner swipe should dispatch drag deltas over Popup") { @@ -204,8 +198,6 @@ internal abstract class PopupSwipeBackTest( ) } - waitUntilReady { !dragDistance.isNaN() && backCompletedCount == 0 } - findNodeWithTag(OVERLAY_SURFACE).swipeLeft() waitUntil("Inner swipe should dispatch drag deltas over Popup") { @@ -300,12 +292,4 @@ private fun BackGestureHost( } } -private fun UIKitInstrumentedTest.waitUntilReady( - otherConditions: () -> Boolean, -) { - waitUntil("$OVERLAY_SURFACE should be ready") { - findNodeWithTagOrNull(OVERLAY_SURFACE) != null && otherConditions() - } -} - private const val OVERLAY_SURFACE = "overlaySurface" diff --git a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/SwipeBackTest.kt b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/SwipeBackTest.kt index 873076d76fdef..a3db42e3c8e1b 100644 --- a/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/SwipeBackTest.kt +++ b/compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/interaction/swipeback/SwipeBackTest.kt @@ -34,7 +34,6 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.UIKitInstrumentedTest import androidx.compose.ui.test.findNodeWithTag import androidx.compose.ui.test.runUIKitInstrumentedTest -import androidx.compose.ui.test.setLayoutDirection import androidx.compose.ui.test.utils.hold import androidx.compose.ui.test.utils.up import androidx.compose.ui.uikit.EndEdgePanGestureBehavior @@ -49,11 +48,8 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -import platform.UIKit.UISemanticContentAttributeForceLeftToRight -import platform.UIKit.UISemanticContentAttributeForceRightToLeft import platform.UIKit.UITraitEnvironmentLayoutDirectionLeftToRight import platform.UIKit.UITraitEnvironmentLayoutDirectionRightToLeft -import platform.UIKit.UIView internal class SwipeBackInHostingViewTest : SwipeBackTest( runUIKitInstrumentedTest = { runUIKitInstrumentedTest(useHostingView = true, it) } @@ -71,7 +67,7 @@ internal abstract class SwipeBackTest( var dragDistance = Float.NaN setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { - TestContent( + SwipeBackTestContent( onDragDistanceChanged = { dragDistance = it } ) } @@ -91,7 +87,7 @@ internal abstract class SwipeBackTest( var dragDistance = Float.NaN setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { - TestContent( + SwipeBackTestContent( onDragDistanceChanged = { dragDistance = it } ) } @@ -112,7 +108,7 @@ internal abstract class SwipeBackTest( var backCompletedCount = -1 setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -143,7 +139,7 @@ internal abstract class SwipeBackTest( var backCompletedCount = -1 setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -172,7 +168,7 @@ internal abstract class SwipeBackTest( var backCompletedCount = -1 setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -195,7 +191,7 @@ internal abstract class SwipeBackTest( var backCompletedCount = -1 setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -217,7 +213,7 @@ internal abstract class SwipeBackTest( var dragDistance = Float.NaN setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { - TestContent( + SwipeBackTestContent( onDragDistanceChanged = { dragDistance = it } ) } @@ -234,7 +230,7 @@ internal abstract class SwipeBackTest( var dragDistance = Float.NaN setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { - TestContent( + SwipeBackTestContent( onDragDistanceChanged = { dragDistance = it } ) } @@ -251,7 +247,7 @@ internal abstract class SwipeBackTest( var dragDistance = Float.NaN setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { - TestContent( + SwipeBackTestContent( onDragDistanceChanged = { dragDistance = it } ) } @@ -268,7 +264,7 @@ internal abstract class SwipeBackTest( var dragDistance = Float.NaN setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { - TestContent( + SwipeBackTestContent( onDragDistanceChanged = { dragDistance = it } ) } @@ -285,7 +281,7 @@ internal abstract class SwipeBackTest( var dragDistance = Float.NaN setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { - TestContent( + SwipeBackTestContent( onDragDistanceChanged = { dragDistance = it } ) } @@ -302,7 +298,7 @@ internal abstract class SwipeBackTest( var dragDistance = Float.NaN setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { - TestContent( + SwipeBackTestContent( onDragDistanceChanged = { dragDistance = it } ) } @@ -320,7 +316,7 @@ internal abstract class SwipeBackTest( var backCompletedCount = -1 setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -343,7 +339,7 @@ internal abstract class SwipeBackTest( var backCompletedCount = -1 setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -366,7 +362,7 @@ internal abstract class SwipeBackTest( var backCompletedCount = -1 setContent(layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -389,7 +385,7 @@ internal abstract class SwipeBackTest( var backCompletedCount = -1 setContent(layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -415,7 +411,7 @@ internal abstract class SwipeBackTest( configure = { endEdgePanGestureBehavior = EndEdgePanGestureBehavior.Back }, layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft ) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -444,7 +440,7 @@ internal abstract class SwipeBackTest( configure = { endEdgePanGestureBehavior = EndEdgePanGestureBehavior.Back }, layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight ) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -473,7 +469,7 @@ internal abstract class SwipeBackTest( configure = { endEdgePanGestureBehavior = EndEdgePanGestureBehavior.Back }, layoutDirection = UITraitEnvironmentLayoutDirectionLeftToRight ) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -509,7 +505,7 @@ internal abstract class SwipeBackTest( configure = { endEdgePanGestureBehavior = EndEdgePanGestureBehavior.Back }, layoutDirection = UITraitEnvironmentLayoutDirectionRightToLeft ) { - TestContent( + SwipeBackTestContent( onTransitionStateChanged = { transitionState = it }, onBackCompletedCountChanged = { backCompletedCount = it } ) @@ -548,7 +544,7 @@ internal abstract class SwipeBackTest( composeLayoutDirection = currentLayoutDirection } - TestContent( + SwipeBackTestContent( onBackCompletedCountChanged = { backCompletedCount = it } ) } @@ -588,7 +584,7 @@ internal abstract class SwipeBackTest( composeLayoutDirection = currentLayoutDirection } - TestContent( + SwipeBackTestContent( onBackCompletedCountChanged = { backCompletedCount = it } ) } @@ -619,7 +615,7 @@ internal abstract class SwipeBackTest( } @Composable -internal fun TestContent( +internal fun SwipeBackTestContent( onDragDistanceChanged: (Float) -> Unit = {}, onTransitionStateChanged: (NavigationEventTransitionState) -> Unit = {}, onBackCompletedCountChanged: (Int) -> Unit = {},