[go: nahoru, domu]

blob: c7f16efdab26a46de222c97a67aa97f5e1348ad5 [file] [log] [blame]
George Mounte872c332019-04-12 15:03:59 -07001/*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Louis Pullen-Freilichddda7be2020-07-17 18:28:12 +010016package androidx.compose.foundation
George Mounte872c332019-04-12 15:03:59 -070017
Matvei Malkov1a41de32019-08-15 16:08:40 +010018import android.os.Handler
Jelle Fresen2c776c62019-11-15 16:52:39 +000019import android.os.Looper
Matvei Malkov2ff160f2020-08-06 16:42:22 +010020import androidx.annotation.RequiresApi
Jelle Fresenb40ab8e2020-09-08 14:18:41 +010021import androidx.compose.animation.core.FloatExponentialDecaySpec
Louis Pullen-Freilich5bb0c4712020-07-20 22:01:15 +010022import androidx.compose.animation.core.ManualAnimationClock
Louis Pullen-Freilichddda7be2020-07-17 18:28:12 +010023import androidx.compose.foundation.animation.FlingConfig
Ryan Mentley4ac84982021-01-14 13:45:24 -080024import androidx.compose.foundation.animation.scrollBy
Ryan Mentleybc9c8bb2020-12-09 08:08:53 -080025import androidx.compose.foundation.animation.smoothScrollBy
26import androidx.compose.foundation.gestures.Scrollable
Mihai Popa60a90cc2020-09-15 12:17:41 +010027import androidx.compose.foundation.layout.Box
Andrey Kulikov05c18b22020-12-14 14:26:44 +000028import androidx.compose.foundation.layout.Column
29import androidx.compose.foundation.layout.Row
Louis Pullen-Freilich623e4052020-07-19 20:24:03 +010030import androidx.compose.foundation.layout.preferredHeight
31import androidx.compose.foundation.layout.preferredSize
Louis Pullen-Freilich051800b2020-10-30 19:26:32 +000032import androidx.compose.foundation.text.BasicText
Matvei Malkov2ff160f2020-08-06 16:42:22 +010033import androidx.compose.runtime.Composable
34import androidx.compose.runtime.Providers
35import androidx.compose.runtime.mutableStateOf
Jelle Fresen3ac4b802021-01-06 11:22:38 +000036import androidx.compose.testutils.MockAnimationClock
Filip Pavlis591e17a2020-11-02 18:47:42 +000037import androidx.compose.testutils.assertPixels
Matvei Malkov2ff160f2020-08-06 16:42:22 +010038import androidx.compose.ui.Modifier
39import androidx.compose.ui.graphics.Color
Jens Ole Lauridsencb1ca652020-10-28 14:43:30 -070040import androidx.compose.ui.platform.InspectableValue
Louis Pullen-Freilichd42e1072021-01-27 18:20:02 +000041import androidx.compose.ui.platform.LocalLayoutDirection
Jens Ole Lauridsencb1ca652020-10-28 14:43:30 -070042import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
Matvei Malkov2ff160f2020-08-06 16:42:22 +010043import androidx.compose.ui.platform.testTag
Ryan Mentleybc9c8bb2020-12-09 08:08:53 -080044import androidx.compose.ui.test.ExperimentalTestApi
Filip Pavlisce1489432020-10-29 12:18:06 +000045import androidx.compose.ui.test.GestureScope
46import androidx.compose.ui.test.SemanticsNodeInteraction
47import androidx.compose.ui.test.assertIsDisplayed
48import androidx.compose.ui.test.assertIsNotDisplayed
Filip Pavlis591e17a2020-11-02 18:47:42 +000049import androidx.compose.ui.test.captureToImage
Jelle Fresen3ac4b802021-01-06 11:22:38 +000050import androidx.compose.ui.test.center
51import androidx.compose.ui.test.down
Filip Pavlisce1489432020-10-29 12:18:06 +000052import androidx.compose.ui.test.junit4.StateRestorationTester
53import androidx.compose.ui.test.junit4.createComposeRule
54import androidx.compose.ui.test.onNodeWithTag
55import androidx.compose.ui.test.onNodeWithText
56import androidx.compose.ui.test.performGesture
57import androidx.compose.ui.test.performScrollTo
58import androidx.compose.ui.test.swipeDown
59import androidx.compose.ui.test.swipeLeft
60import androidx.compose.ui.test.swipeRight
61import androidx.compose.ui.test.swipeUp
Matvei Malkov2ff160f2020-08-06 16:42:22 +010062import androidx.compose.ui.unit.Dp
63import androidx.compose.ui.unit.IntSize
64import androidx.compose.ui.unit.LayoutDirection
65import androidx.compose.ui.unit.dp
Jelle Fresen53dd7b72020-09-25 10:02:27 +010066import androidx.test.ext.junit.runners.AndroidJUnit4
67import androidx.test.filters.LargeTest
68import androidx.test.filters.MediumTest
Matvei Malkov2ff160f2020-08-06 16:42:22 +010069import androidx.test.filters.SdkSuppress
Jelle Fresen2c776c62019-11-15 16:52:39 +000070import com.google.common.truth.Truth.assertThat
Jelle Fresen7ce06d62020-02-25 11:38:59 +000071import com.google.common.truth.Truth.assertWithMessage
Ryan Mentleybc9c8bb2020-12-09 08:08:53 -080072import kotlinx.coroutines.runBlocking
Jens Ole Lauridsencb1ca652020-10-28 14:43:30 -070073import org.junit.After
George Mounte872c332019-04-12 15:03:59 -070074import org.junit.Assert.assertEquals
George Mounte872c332019-04-12 15:03:59 -070075import org.junit.Assert.assertTrue
Jens Ole Lauridsencb1ca652020-10-28 14:43:30 -070076import org.junit.Before
Matvei Malkov0ed3ed52020-05-12 20:28:47 +010077import org.junit.Ignore
Matvei Malkov1a41de32019-08-15 16:08:40 +010078import org.junit.Rule
George Mounte872c332019-04-12 15:03:59 -070079import org.junit.Test
80import org.junit.runner.RunWith
George Mounte872c332019-04-12 15:03:59 -070081import java.util.concurrent.CountDownLatch
Jelle Fresen7ce06d62020-02-25 11:38:59 +000082import java.util.concurrent.TimeUnit
George Mounte872c332019-04-12 15:03:59 -070083
Jelle Fresen53dd7b72020-09-25 10:02:27 +010084@MediumTest
Jelle Fresen17628d72020-09-24 16:23:51 +010085@RunWith(AndroidJUnit4::class)
Matvei Malkov235b4fa2020-07-07 21:14:49 +010086class ScrollTest {
Matvei Malkov1a41de32019-08-15 16:08:40 +010087
88 @get:Rule
Filip Pavlis3d2d0392020-09-03 14:43:36 +010089 val rule = createComposeRule()
Matvei Malkov1a41de32019-08-15 16:08:40 +010090
Filip Pavlis582a2be2020-01-07 17:50:32 +000091 private val scrollerTag = "ScrollerTest"
Matvei Malkov1a41de32019-08-15 16:08:40 +010092
Matvei Malkovc09bc562020-02-05 18:13:34 -080093 private val defaultCrossAxisSize = 45
94 private val defaultMainAxisSize = 40
95 private val defaultCellSize = 5
Jelle Fresen389cb602019-11-14 16:54:28 +000096
97 private val colors = listOf(
Jelle Fresen3ee94402019-11-14 15:00:41 +000098 Color(red = 0xFF, green = 0, blue = 0, alpha = 0xFF),
99 Color(red = 0xFF, green = 0xA5, blue = 0, alpha = 0xFF),
100 Color(red = 0xFF, green = 0xFF, blue = 0, alpha = 0xFF),
101 Color(red = 0xA5, green = 0xFF, blue = 0, alpha = 0xFF),
102 Color(red = 0, green = 0xFF, blue = 0, alpha = 0xFF),
103 Color(red = 0, green = 0xFF, blue = 0xA5, alpha = 0xFF),
104 Color(red = 0, green = 0, blue = 0xFF, alpha = 0xFF),
105 Color(red = 0xA5, green = 0, blue = 0xFF, alpha = 0xFF)
Louis Pullen-Freilich541e3c02019-05-02 21:17:19 +0100106 )
George Mounte872c332019-04-12 15:03:59 -0700107
Jens Ole Lauridsencb1ca652020-10-28 14:43:30 -0700108 @Before
109 fun before() {
110 isDebugInspectorInfoEnabled = true
111 }
112
113 @After
114 fun after() {
115 isDebugInspectorInfoEnabled = false
116 }
117
Jelle Fresen389cb602019-11-14 16:54:28 +0000118 @SdkSuppress(minSdkVersion = 26)
George Mounte872c332019-04-12 15:03:59 -0700119 @Test
120 fun verticalScroller_SmallContent() {
Matvei Malkovc09bc562020-02-05 18:13:34 -0800121 val height = 40
George Mounte872c332019-04-12 15:03:59 -0700122
Jelle Fresen389cb602019-11-14 16:54:28 +0000123 composeVerticalScroller(height = height)
124
125 validateVerticalScroller(height = height)
George Mounte872c332019-04-12 15:03:59 -0700126 }
127
128 @Test
Matvei Malkovcc0ea422019-09-25 14:24:02 +0100129 fun verticalScroller_SmallContent_Unscrollable() {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100130 val scrollState = ScrollState(
131 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100132 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Jelle Fresen0186e462020-02-20 11:35:14 +0000133 animationClock = ManualAnimationClock(0)
Matvei Malkovc09bc562020-02-05 18:13:34 -0800134 )
Matvei Malkovcc0ea422019-09-25 14:24:02 +0100135
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100136 composeVerticalScroller(scrollState)
Jelle Fresen389cb602019-11-14 16:54:28 +0000137
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100138 rule.runOnIdle {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100139 assertTrue(scrollState.maxValue == 0f)
Matvei Malkov985dcfa2019-09-26 11:37:27 +0100140 }
Matvei Malkovcc0ea422019-09-25 14:24:02 +0100141 }
142
Jelle Fresen389cb602019-11-14 16:54:28 +0000143 @SdkSuppress(minSdkVersion = 26)
Matvei Malkovcc0ea422019-09-25 14:24:02 +0100144 @Test
George Mounte872c332019-04-12 15:03:59 -0700145 fun verticalScroller_LargeContent_NoScroll() {
Matvei Malkovc09bc562020-02-05 18:13:34 -0800146 val height = 30
George Mounte872c332019-04-12 15:03:59 -0700147
Jelle Fresen389cb602019-11-14 16:54:28 +0000148 composeVerticalScroller(height = height)
149
150 validateVerticalScroller(height = height)
George Mounte872c332019-04-12 15:03:59 -0700151 }
152
Ryan Mentley4ac84982021-01-14 13:45:24 -0800153 @OptIn(ExperimentalTestApi::class)
Jelle Fresen389cb602019-11-14 16:54:28 +0000154 @SdkSuppress(minSdkVersion = 26)
George Mounte872c332019-04-12 15:03:59 -0700155 @Test
Ryan Mentley4ac84982021-01-14 13:45:24 -0800156 fun verticalScroller_LargeContent_ScrollToEnd() = runBlocking {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100157 val scrollState = ScrollState(
158 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100159 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Jelle Fresen0186e462020-02-20 11:35:14 +0000160 animationClock = ManualAnimationClock(0)
Matvei Malkovc09bc562020-02-05 18:13:34 -0800161 )
162 val height = 30
163 val scrollDistance = 10
George Mounte872c332019-04-12 15:03:59 -0700164
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100165 composeVerticalScroller(scrollState, height = height)
George Mounte872c332019-04-12 15:03:59 -0700166
Jelle Fresen389cb602019-11-14 16:54:28 +0000167 validateVerticalScroller(height = height)
George Mounte872c332019-04-12 15:03:59 -0700168
Ryan Mentley4ac84982021-01-14 13:45:24 -0800169 rule.awaitIdle()
170 assertEquals(scrollDistance.toFloat(), scrollState.maxValue)
171 scrollState.scrollTo(scrollDistance.toFloat())
Filip Pavlis2b161e42019-11-22 17:40:08 +0000172
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100173 rule.runOnIdle {} // Just so the block below is correct
Jelle Fresen389cb602019-11-14 16:54:28 +0000174 validateVerticalScroller(offset = scrollDistance, height = height)
George Mounte872c332019-04-12 15:03:59 -0700175 }
176
Jelle Fresen389cb602019-11-14 16:54:28 +0000177 @SdkSuppress(minSdkVersion = 26)
George Mountbc7a78f2019-08-02 14:20:03 -0700178 @Test
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100179 fun verticalScroller_Reversed() {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100180 val scrollState = ScrollState(
181 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100182 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100183 animationClock = ManualAnimationClock(0)
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100184 )
185 val height = 30
186 val expectedOffset = defaultCellSize * colors.size - height
187
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100188 composeVerticalScroller(scrollState, height = height, isReversed = true)
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100189
190 validateVerticalScroller(offset = expectedOffset, height = height)
191 }
192
Ryan Mentley4ac84982021-01-14 13:45:24 -0800193 @OptIn(ExperimentalTestApi::class)
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100194 @SdkSuppress(minSdkVersion = 26)
195 @Test
Ryan Mentley4ac84982021-01-14 13:45:24 -0800196 fun verticalScroller_LargeContent_Reversed_ScrollToEnd() = runBlocking {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100197 val scrollState = ScrollState(
198 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100199 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100200 animationClock = ManualAnimationClock(0)
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100201 )
202 val height = 20
203 val scrollDistance = 10
204 val expectedOffset = defaultCellSize * colors.size - height - scrollDistance
205
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100206 composeVerticalScroller(scrollState, height = height, isReversed = true)
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100207
Ryan Mentley4ac84982021-01-14 13:45:24 -0800208 rule.awaitIdle()
209 scrollState.scrollTo(scrollDistance.toFloat())
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100210
Ryan Mentley4ac84982021-01-14 13:45:24 -0800211 rule.awaitIdle()
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100212 validateVerticalScroller(offset = expectedOffset, height = height)
213 }
214
215 @SdkSuppress(minSdkVersion = 26)
216 @Test
George Mountbc7a78f2019-08-02 14:20:03 -0700217 fun horizontalScroller_SmallContent() {
Matvei Malkovc09bc562020-02-05 18:13:34 -0800218 val width = 40
George Mountbc7a78f2019-08-02 14:20:03 -0700219
Jelle Fresen389cb602019-11-14 16:54:28 +0000220 composeHorizontalScroller(width = width)
221
222 validateHorizontalScroller(width = width)
George Mountbc7a78f2019-08-02 14:20:03 -0700223 }
224
Jelle Fresen389cb602019-11-14 16:54:28 +0000225 @SdkSuppress(minSdkVersion = 26)
George Mountbc7a78f2019-08-02 14:20:03 -0700226 @Test
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100227 fun horizontalScroller_rtl_SmallContent() {
228 val width = 40
229
230 composeHorizontalScroller(width = width, isRtl = true)
231
232 validateHorizontalScroller(width = width, checkInRtl = true)
233 }
234
235 @SdkSuppress(minSdkVersion = 26)
236 @Test
George Mountbc7a78f2019-08-02 14:20:03 -0700237 fun horizontalScroller_LargeContent_NoScroll() {
Matvei Malkovc09bc562020-02-05 18:13:34 -0800238 val width = 30
George Mountbc7a78f2019-08-02 14:20:03 -0700239
Jelle Fresen389cb602019-11-14 16:54:28 +0000240 composeHorizontalScroller(width = width)
241
242 validateHorizontalScroller(width = width)
George Mountbc7a78f2019-08-02 14:20:03 -0700243 }
244
Jelle Fresen389cb602019-11-14 16:54:28 +0000245 @SdkSuppress(minSdkVersion = 26)
George Mountbc7a78f2019-08-02 14:20:03 -0700246 @Test
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100247 fun horizontalScroller_rtl_LargeContent_NoScroll() {
248 val width = 30
249
250 composeHorizontalScroller(width = width, isRtl = true)
251
252 validateHorizontalScroller(width = width, checkInRtl = true)
253 }
254
Ryan Mentley4ac84982021-01-14 13:45:24 -0800255 @OptIn(ExperimentalTestApi::class)
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100256 @SdkSuppress(minSdkVersion = 26)
257 @Test
Ryan Mentley4ac84982021-01-14 13:45:24 -0800258 fun horizontalScroller_LargeContent_ScrollToEnd() = runBlocking {
Matvei Malkovc09bc562020-02-05 18:13:34 -0800259 val width = 30
260 val scrollDistance = 10
Jelle Fresen389cb602019-11-14 16:54:28 +0000261
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100262 val scrollState = ScrollState(
263 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100264 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Jelle Fresen0186e462020-02-20 11:35:14 +0000265 animationClock = ManualAnimationClock(0)
Matvei Malkovc09bc562020-02-05 18:13:34 -0800266 )
George Mountbc7a78f2019-08-02 14:20:03 -0700267
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100268 composeHorizontalScroller(scrollState, width = width)
George Mountbc7a78f2019-08-02 14:20:03 -0700269
Jelle Fresen389cb602019-11-14 16:54:28 +0000270 validateHorizontalScroller(width = width)
George Mountbc7a78f2019-08-02 14:20:03 -0700271
Ryan Mentley4ac84982021-01-14 13:45:24 -0800272 rule.awaitIdle()
273 assertEquals(scrollDistance.toFloat(), scrollState.maxValue)
274 scrollState.scrollTo(scrollDistance.toFloat())
Filip Pavlis2b161e42019-11-22 17:40:08 +0000275
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100276 rule.runOnIdle {} // Just so the block below is correct
Jelle Fresen389cb602019-11-14 16:54:28 +0000277 validateHorizontalScroller(offset = scrollDistance, width = width)
George Mountbc7a78f2019-08-02 14:20:03 -0700278 }
279
Ryan Mentley4ac84982021-01-14 13:45:24 -0800280 @OptIn(ExperimentalTestApi::class)
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100281 @SdkSuppress(minSdkVersion = 26)
282 @Test
Ryan Mentley4ac84982021-01-14 13:45:24 -0800283 fun horizontalScroller_rtl_LargeContent_ScrollToEnd() = runBlocking {
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100284 val width = 30
285 val scrollDistance = 10
286
287 val scrollState = ScrollState(
288 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100289 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100290 animationClock = ManualAnimationClock(0)
291 )
292
293 composeHorizontalScroller(scrollState, width = width, isRtl = true)
294
295 validateHorizontalScroller(width = width, checkInRtl = true)
296
Ryan Mentley4ac84982021-01-14 13:45:24 -0800297 rule.awaitIdle()
298 assertEquals(scrollDistance.toFloat(), scrollState.maxValue)
299 scrollState.scrollTo(scrollDistance.toFloat())
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100300
Ryan Mentley4ac84982021-01-14 13:45:24 -0800301 rule.awaitIdle()
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100302 validateHorizontalScroller(offset = scrollDistance, width = width, checkInRtl = true)
303 }
304
305 @SdkSuppress(minSdkVersion = 26)
306 @Test
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100307 fun horizontalScroller_reversed() {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100308 val scrollState = ScrollState(
309 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100310 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100311 animationClock = ManualAnimationClock(0)
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100312 )
313 val width = 30
314 val expectedOffset = defaultCellSize * colors.size - width
315
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100316 composeHorizontalScroller(scrollState, width = width, isReversed = true)
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100317
318 validateHorizontalScroller(offset = expectedOffset, width = width)
319 }
320
321 @SdkSuppress(minSdkVersion = 26)
322 @Test
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100323 fun horizontalScroller_rtl_reversed() {
324 val scrollState = ScrollState(
325 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100326 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100327 animationClock = ManualAnimationClock(0)
328 )
329 val width = 30
330 val expectedOffset = defaultCellSize * colors.size - width
331
332 composeHorizontalScroller(scrollState, width = width, isReversed = true, isRtl = true)
333
334 validateHorizontalScroller(offset = expectedOffset, width = width, checkInRtl = true)
335 }
336
Ryan Mentley4ac84982021-01-14 13:45:24 -0800337 @OptIn(ExperimentalTestApi::class)
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100338 @SdkSuppress(minSdkVersion = 26)
339 @Test
Ryan Mentley4ac84982021-01-14 13:45:24 -0800340 fun horizontalScroller_LargeContent_Reversed_ScrollToEnd() = runBlocking {
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100341 val width = 30
342 val scrollDistance = 10
343
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100344 val scrollState = ScrollState(
345 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100346 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100347 animationClock = ManualAnimationClock(0)
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100348 )
349
350 val expectedOffset = defaultCellSize * colors.size - width - scrollDistance
351
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100352 composeHorizontalScroller(scrollState, width = width, isReversed = true)
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100353
Ryan Mentley4ac84982021-01-14 13:45:24 -0800354 rule.awaitIdle()
355 scrollState.scrollTo(scrollDistance.toFloat())
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100356
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100357 rule.runOnIdle {} // Just so the block below is correct
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100358 validateHorizontalScroller(offset = expectedOffset, width = width)
359 }
360
Ryan Mentley4ac84982021-01-14 13:45:24 -0800361 @OptIn(ExperimentalTestApi::class)
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100362 @SdkSuppress(minSdkVersion = 26)
363 @Test
Ryan Mentley4ac84982021-01-14 13:45:24 -0800364 fun horizontalScroller_rtl_LargeContent_Reversed_ScrollToEnd() = runBlocking {
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100365 val width = 30
366 val scrollDistance = 10
367
368 val scrollState = ScrollState(
369 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100370 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100371 animationClock = ManualAnimationClock(0)
372 )
373
374 val expectedOffset = defaultCellSize * colors.size - width - scrollDistance
375
376 composeHorizontalScroller(scrollState, width = width, isReversed = true, isRtl = true)
377
Ryan Mentley4ac84982021-01-14 13:45:24 -0800378 rule.awaitIdle()
379
380 scrollState.scrollTo(scrollDistance.toFloat())
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100381
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100382 rule.runOnIdle {} // Just so the block below is correct
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100383 validateHorizontalScroller(offset = expectedOffset, width = width, checkInRtl = true)
384 }
385
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000386 @Test
387 fun verticalScroller_scrollTo_scrollForward() {
388 createScrollableContent(isVertical = true)
389
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100390 rule.onNodeWithText("50")
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000391 .assertIsNotDisplayed()
Filip Pavlis659ea722020-07-13 14:14:32 +0100392 .performScrollTo()
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000393 .assertIsDisplayed()
394 }
Jelle Fresen389cb602019-11-14 16:54:28 +0000395
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000396 @Test
397 fun horizontalScroller_scrollTo_scrollForward() {
398 createScrollableContent(isVertical = false)
399
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100400 rule.onNodeWithText("50")
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000401 .assertIsNotDisplayed()
Filip Pavlis659ea722020-07-13 14:14:32 +0100402 .performScrollTo()
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000403 .assertIsDisplayed()
404 }
405
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100406 @Ignore("Unignore when b/156389287 is fixed for proper reverse and rtl delegation")
407 @Test
408 fun horizontalScroller_rtl_scrollTo_scrollForward() {
409 createScrollableContent(isVertical = false, isRtl = true)
410
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100411 rule.onNodeWithText("50")
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100412 .assertIsNotDisplayed()
413 .performScrollTo()
414 .assertIsDisplayed()
415 }
416
417 @Ignore("Unignore when b/156389287 is fixed for proper reverse delegation")
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100418 @Test
419 fun verticalScroller_reversed_scrollTo_scrollForward() {
420 createScrollableContent(
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100421 isVertical = true,
422 scrollState = ScrollState(
423 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100424 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100425 animationClock = ManualAnimationClock(0)
426 ),
427 isReversed = true
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100428 )
429
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100430 rule.onNodeWithText("50")
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100431 .assertIsNotDisplayed()
Filip Pavlis659ea722020-07-13 14:14:32 +0100432 .performScrollTo()
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100433 .assertIsDisplayed()
434 }
435
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100436 @Ignore("Unignore when b/156389287 is fixed for proper reverse and rtl delegation")
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100437 @Test
438 fun horizontalScroller_reversed_scrollTo_scrollForward() {
439 createScrollableContent(
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100440 isVertical = false,
441 scrollState = ScrollState(
442 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100443 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100444 animationClock = ManualAnimationClock(0)
445 ),
446 isReversed = true
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100447 )
448
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100449 rule.onNodeWithText("50")
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100450 .assertIsNotDisplayed()
Filip Pavlis659ea722020-07-13 14:14:32 +0100451 .performScrollTo()
Matvei Malkov0ed3ed52020-05-12 20:28:47 +0100452 .assertIsDisplayed()
453 }
454
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000455 @Test
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100456 @Ignore("When b/157687898 is fixed, performScrollTo must be adjusted to use semantic bounds")
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000457 fun verticalScroller_scrollTo_scrollBack() {
458 createScrollableContent(isVertical = true)
459
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100460 rule.onNodeWithText("50")
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000461 .assertIsNotDisplayed()
Filip Pavlis659ea722020-07-13 14:14:32 +0100462 .performScrollTo()
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000463 .assertIsDisplayed()
464
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100465 rule.onNodeWithText("20")
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000466 .assertIsNotDisplayed()
Filip Pavlis659ea722020-07-13 14:14:32 +0100467 .performScrollTo()
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000468 .assertIsDisplayed()
469 }
Jelle Fresen389cb602019-11-14 16:54:28 +0000470
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000471 @Test
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100472 @Ignore("When b/157687898 is fixed, performScrollTo must be adjusted to use semantic bounds")
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000473 fun horizontalScroller_scrollTo_scrollBack() {
474 createScrollableContent(isVertical = false)
475
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100476 rule.onNodeWithText("50")
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000477 .assertIsNotDisplayed()
Filip Pavlis659ea722020-07-13 14:14:32 +0100478 .performScrollTo()
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000479 .assertIsDisplayed()
480
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100481 rule.onNodeWithText("20")
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000482 .assertIsNotDisplayed()
Filip Pavlis659ea722020-07-13 14:14:32 +0100483 .performScrollTo()
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000484 .assertIsDisplayed()
485 }
486
Jelle Fresen2c776c62019-11-15 16:52:39 +0000487 @Test
Jelle Fresen53dd7b72020-09-25 10:02:27 +0100488 @LargeTest
Jelle Fresen2c776c62019-11-15 16:52:39 +0000489 fun verticalScroller_swipeUp_swipeDown() {
Filip Pavlis659ea722020-07-13 14:14:32 +0100490 swipeScrollerAndBack(true, GestureScope::swipeUp, GestureScope::swipeDown)
Jelle Fresen2c776c62019-11-15 16:52:39 +0000491 }
492
493 @Test
Jelle Fresen53dd7b72020-09-25 10:02:27 +0100494 @LargeTest
Jelle Fresen2c776c62019-11-15 16:52:39 +0000495 fun horizontalScroller_swipeLeft_swipeRight() {
Filip Pavlis659ea722020-07-13 14:14:32 +0100496 swipeScrollerAndBack(false, GestureScope::swipeLeft, GestureScope::swipeRight)
Jelle Fresen2c776c62019-11-15 16:52:39 +0000497 }
498
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000499 @Test
Jelle Fresen53dd7b72020-09-25 10:02:27 +0100500 @LargeTest
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100501 fun horizontalScroller_rtl_swipeLeft_swipeRight() {
502 swipeScrollerAndBack(
503 false,
504 GestureScope::swipeRight,
505 GestureScope::swipeLeft,
506 isRtl = true
507 )
508 }
509
Ryan Mentley4ac84982021-01-14 13:45:24 -0800510 @OptIn(ExperimentalTestApi::class)
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100511 @Test
Ryan Mentley4ac84982021-01-14 13:45:24 -0800512 fun scroller_coerce_whenScrollTo() = runBlocking {
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000513 val clock = ManualAnimationClock(0)
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100514 val scrollState = ScrollState(
515 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100516 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000517 animationClock = clock
518 )
519
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100520 createScrollableContent(isVertical = true, scrollState = scrollState)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000521
Ryan Mentley4ac84982021-01-14 13:45:24 -0800522 rule.awaitIdle()
523
524 assertThat(scrollState.value).isEqualTo(0f)
525 assertThat(scrollState.maxValue).isGreaterThan(0f)
526
527 scrollState.scrollTo(-100f)
528 assertThat(scrollState.value).isEqualTo(0f)
529
530 (scrollState as Scrollable).scrollBy(-100f)
531 assertThat(scrollState.value).isEqualTo(0f)
532
533 scrollState.scrollTo(scrollState.maxValue)
534 assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
535
536 scrollState.scrollTo(scrollState.maxValue + 1000)
537 assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
538
539 (scrollState as Scrollable).scrollBy(100f)
540 assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000541 }
542
Ryan Mentley4ac84982021-01-14 13:45:24 -0800543 @OptIn(ExperimentalTestApi::class)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000544 @Test
Ryan Mentley4ac84982021-01-14 13:45:24 -0800545 fun verticalScroller_LargeContent_coerceWhenMaxChanges() = runBlocking {
Matvei Malkovdff83912020-04-08 18:25:15 +0100546 val clock = ManualAnimationClock(0)
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100547 val scrollState = ScrollState(
548 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100549 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkovdff83912020-04-08 18:25:15 +0100550 animationClock = clock
551 )
552 val itemCount = mutableStateOf(100)
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100553 rule.setContent {
Mihai Popa60a90cc2020-09-15 12:17:41 +0100554 Box {
Andrey Kulikov05c18b22020-12-14 14:26:44 +0000555 Column(
556 Modifier
557 .preferredSize(100.dp)
558 .testTag(scrollerTag)
559 .verticalScroll(scrollState)
Matvei Malkov1da47fe2020-05-15 12:40:16 +0100560 ) {
Matvei Malkov9fb69242020-05-19 14:07:14 +0100561 for (i in 0..itemCount.value) {
Louis Pullen-Freilich051800b2020-10-30 19:26:32 +0000562 BasicText(i.toString())
Matvei Malkovdff83912020-04-08 18:25:15 +0100563 }
564 }
565 }
566 }
567
Ryan Mentley4ac84982021-01-14 13:45:24 -0800568 rule.awaitIdle()
Matvei Malkovdff83912020-04-08 18:25:15 +0100569
Ryan Mentley4ac84982021-01-14 13:45:24 -0800570 assertThat(scrollState.value).isEqualTo(0f)
571 assertThat(scrollState.maxValue).isGreaterThan(0f)
572 val max = scrollState.maxValue
573
574 scrollState.scrollTo(max)
575 itemCount.value -= 2
576
577 rule.awaitIdle()
578 val newMax = scrollState.maxValue
579 assertThat(newMax).isLessThan(max)
580 assertThat(scrollState.value).isEqualTo(newMax)
Matvei Malkovdff83912020-04-08 18:25:15 +0100581 }
582
Ryan Mentleybc9c8bb2020-12-09 08:08:53 -0800583 @OptIn(ExperimentalTestApi::class)
Matvei Malkovdff83912020-04-08 18:25:15 +0100584 @Test
Ryan Mentleybc9c8bb2020-12-09 08:08:53 -0800585 fun scroller_coerce_whenScrollSmoothTo() = runBlocking {
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000586 val clock = ManualAnimationClock(0)
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100587 val scrollState = ScrollState(
588 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100589 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000590 animationClock = clock
591 )
592
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100593 createScrollableContent(isVertical = true, scrollState = scrollState)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000594
Ryan Mentleybc9c8bb2020-12-09 08:08:53 -0800595 rule.awaitIdle()
596 assertThat(scrollState.value).isEqualTo(0f)
597 assertThat(scrollState.maxValue).isGreaterThan(0f)
598 val max = scrollState.maxValue
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000599
Ryan Mentleybc9c8bb2020-12-09 08:08:53 -0800600 scrollState.smoothScrollTo(-100f)
601 assertThat(scrollState.value).isEqualTo(0f)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000602
Ryan Mentleybc9c8bb2020-12-09 08:08:53 -0800603 (scrollState as Scrollable).smoothScrollBy(-100f)
604 assertThat(scrollState.value).isEqualTo(0f)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000605
Ryan Mentleybc9c8bb2020-12-09 08:08:53 -0800606 scrollState.smoothScrollTo(scrollState.maxValue)
607 assertThat(scrollState.value).isEqualTo(max)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000608
Ryan Mentleybc9c8bb2020-12-09 08:08:53 -0800609 scrollState.smoothScrollTo(scrollState.maxValue + 1000)
610 assertThat(scrollState.value).isEqualTo(max)
611
612 (scrollState as Scrollable).smoothScrollBy(100f)
613 assertThat(scrollState.value).isEqualTo(max)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000614 }
615
616 @Test
617 fun scroller_whenFling_stopsByTouchDown() {
618 val clock = ManualAnimationClock(0)
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100619 val scrollState = ScrollState(
620 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100621 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000622 animationClock = clock
623 )
624
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100625 createScrollableContent(isVertical = true, scrollState = scrollState)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000626
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100627 rule.runOnIdle {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100628 assertThat(scrollState.value).isEqualTo(0f)
629 assertThat(scrollState.isAnimationRunning).isEqualTo(false)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000630 }
631
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100632 rule.onNodeWithTag(scrollerTag)
Filip Pavlis659ea722020-07-13 14:14:32 +0100633 .performGesture { swipeUp() }
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000634
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100635 rule.runOnIdle {
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000636 clock.clockTimeMillis += 100
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100637 assertThat(scrollState.isAnimationRunning).isEqualTo(true)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000638 }
639
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100640 rule.onNodeWithTag(scrollerTag)
Jelle Fresen3ac4b802021-01-06 11:22:38 +0000641 .performGesture { down(center) }
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000642
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100643 rule.runOnIdle {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100644 assertThat(scrollState.isAnimationRunning).isEqualTo(false)
Matvei Malkovcbdfa052020-03-11 11:39:20 +0000645 }
646 }
647
Ryan Mentley4ac84982021-01-14 13:45:24 -0800648 @OptIn(ExperimentalTestApi::class)
Andrey Kulikovfe8be4d2020-05-04 20:05:40 +0100649 @Test
Ryan Mentley4ac84982021-01-14 13:45:24 -0800650 fun scroller_restoresScrollerPosition() = runBlocking {
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100651 val restorationTester = StateRestorationTester(rule)
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100652 var scrollState: ScrollState? = null
Andrey Kulikovfe8be4d2020-05-04 20:05:40 +0100653
654 restorationTester.setContent {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100655 scrollState = rememberScrollState()
Andrey Kulikov05c18b22020-12-14 14:26:44 +0000656 Column(Modifier.verticalScroll(scrollState!!)) {
Matvei Malkov9fb69242020-05-19 14:07:14 +0100657 repeat(50) {
658 Box(Modifier.preferredHeight(100.dp))
Andrey Kulikovfe8be4d2020-05-04 20:05:40 +0100659 }
660 }
661 }
662
Ryan Mentley4ac84982021-01-14 13:45:24 -0800663 rule.awaitIdle()
664 scrollState!!.scrollTo(70f)
665 scrollState = null
Andrey Kulikovfe8be4d2020-05-04 20:05:40 +0100666
667 restorationTester.emulateSavedInstanceStateRestore()
668
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100669 rule.runOnIdle {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100670 assertThat(scrollState!!.value).isEqualTo(70f)
Andrey Kulikovfe8be4d2020-05-04 20:05:40 +0100671 }
672 }
673
Jelle Fresen2c776c62019-11-15 16:52:39 +0000674 private fun swipeScrollerAndBack(
675 isVertical: Boolean,
676 firstSwipe: GestureScope.() -> Unit,
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100677 secondSwipe: GestureScope.() -> Unit,
678 isRtl: Boolean = false
Jelle Fresen2c776c62019-11-15 16:52:39 +0000679 ) {
Jelle Fresen0186e462020-02-20 11:35:14 +0000680 val clock = ManualAnimationClock(0)
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100681 val scrollState = ScrollState(
682 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100683 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Jelle Fresen0186e462020-02-20 11:35:14 +0000684 animationClock = clock
Matvei Malkovc09bc562020-02-05 18:13:34 -0800685 )
Jelle Fresen2c776c62019-11-15 16:52:39 +0000686
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100687 createScrollableContent(isVertical, scrollState = scrollState, isRtl = isRtl)
Jelle Fresen2c776c62019-11-15 16:52:39 +0000688
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100689 rule.runOnIdle {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100690 assertThat(scrollState.value).isEqualTo(0f)
Filip Pavlis582a2be2020-01-07 17:50:32 +0000691 }
692
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100693 rule.onNodeWithTag(scrollerTag)
Filip Pavlis659ea722020-07-13 14:14:32 +0100694 .performGesture { firstSwipe() }
Jelle Fresen0186e462020-02-20 11:35:14 +0000695
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100696 rule.runOnIdle {
Jelle Fresen0186e462020-02-20 11:35:14 +0000697 clock.clockTimeMillis += 5000
698 }
699
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100700 rule.onNodeWithTag(scrollerTag)
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100701 .awaitScrollAnimation(scrollState)
Jelle Fresen2c776c62019-11-15 16:52:39 +0000702
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100703 val scrolledValue = rule.runOnIdle {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100704 scrollState.value
Filip Pavlis582a2be2020-01-07 17:50:32 +0000705 }
Matvei Malkovc09bc562020-02-05 18:13:34 -0800706 assertThat(scrolledValue).isGreaterThan(0f)
Jelle Fresen2c776c62019-11-15 16:52:39 +0000707
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100708 rule.onNodeWithTag(scrollerTag)
Filip Pavlis659ea722020-07-13 14:14:32 +0100709 .performGesture { secondSwipe() }
Jelle Fresen0186e462020-02-20 11:35:14 +0000710
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100711 rule.runOnIdle {
Jelle Fresen0186e462020-02-20 11:35:14 +0000712 clock.clockTimeMillis += 5000
713 }
714
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100715 rule.onNodeWithTag(scrollerTag)
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100716 .awaitScrollAnimation(scrollState)
Jelle Fresen2c776c62019-11-15 16:52:39 +0000717
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100718 rule.runOnIdle {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100719 assertThat(scrollState.value).isLessThan(scrolledValue)
Jelle Fresen2c776c62019-11-15 16:52:39 +0000720 }
Jelle Fresen2c776c62019-11-15 16:52:39 +0000721 }
722
George Mountbc7a78f2019-08-02 14:20:03 -0700723 private fun composeVerticalScroller(
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100724 scrollState: ScrollState = ScrollState(
725 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100726 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Jelle Fresen0186e462020-02-20 11:35:14 +0000727 animationClock = ManualAnimationClock(0)
Matvei Malkovc09bc562020-02-05 18:13:34 -0800728 ),
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100729 isReversed: Boolean = false,
Matvei Malkovc09bc562020-02-05 18:13:34 -0800730 width: Int = defaultCrossAxisSize,
731 height: Int = defaultMainAxisSize,
732 rowHeight: Int = defaultCellSize
George Mounte872c332019-04-12 15:03:59 -0700733 ) {
734 // We assume that the height of the device is more than 45 px
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100735 with(rule.density) {
736 rule.setContent {
Mihai Popa60a90cc2020-09-15 12:17:41 +0100737 Box {
Andrey Kulikov05c18b22020-12-14 14:26:44 +0000738 Column(
Matvei Malkov1da47fe2020-05-15 12:40:16 +0100739 modifier = Modifier
740 .preferredSize(width.toDp(), height.toDp())
741 .testTag(scrollerTag)
Andrey Kulikov05c18b22020-12-14 14:26:44 +0000742 .verticalScroll(scrollState, reverseScrolling = isReversed)
Matvei Malkov1da47fe2020-05-15 12:40:16 +0100743 ) {
Matvei Malkov9fb69242020-05-19 14:07:14 +0100744 colors.forEach { color ->
745 Box(
Matvei Malkov0de3d282020-09-03 18:01:03 +0100746 Modifier
747 .preferredSize(width.toDp(), rowHeight.toDp())
748 .background(color)
Matvei Malkov9fb69242020-05-19 14:07:14 +0100749 )
Anastasia Sobolevad70e6702019-12-16 13:53:41 +0000750 }
George Mounte872c332019-04-12 15:03:59 -0700751 }
752 }
753 }
George Mounte872c332019-04-12 15:03:59 -0700754 }
755 }
756
George Mountbc7a78f2019-08-02 14:20:03 -0700757 private fun composeHorizontalScroller(
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100758 scrollState: ScrollState = ScrollState(
759 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100760 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Jelle Fresen0186e462020-02-20 11:35:14 +0000761 animationClock = ManualAnimationClock(0)
Matvei Malkovc09bc562020-02-05 18:13:34 -0800762 ),
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100763 isReversed: Boolean = false,
Matvei Malkovc09bc562020-02-05 18:13:34 -0800764 width: Int = defaultMainAxisSize,
765 height: Int = defaultCrossAxisSize,
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100766 isRtl: Boolean = false
George Mountbc7a78f2019-08-02 14:20:03 -0700767 ) {
768 // We assume that the height of the device is more than 45 px
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100769 with(rule.density) {
770 rule.setContent {
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100771 val direction = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
Louis Pullen-Freilichd42e1072021-01-27 18:20:02 +0000772 Providers(LocalLayoutDirection provides direction) {
Mihai Popa60a90cc2020-09-15 12:17:41 +0100773 Box {
Andrey Kulikov05c18b22020-12-14 14:26:44 +0000774 Row(
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100775 modifier = Modifier
776 .preferredSize(width.toDp(), height.toDp())
777 .testTag(scrollerTag)
Andrey Kulikov05c18b22020-12-14 14:26:44 +0000778 .horizontalScroll(scrollState, reverseScrolling = isReversed)
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100779 ) {
780 colors.forEach { color ->
781 Box(
Matvei Malkov0de3d282020-09-03 18:01:03 +0100782 Modifier
783 .preferredSize(defaultCellSize.toDp(), height.toDp())
784 .background(color)
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100785 )
786 }
Anastasia Sobolevad70e6702019-12-16 13:53:41 +0000787 }
George Mountbc7a78f2019-08-02 14:20:03 -0700788 }
789 }
790 }
George Mountbc7a78f2019-08-02 14:20:03 -0700791 }
792 }
793
Jelle Fresen389cb602019-11-14 16:54:28 +0000794 @RequiresApi(api = 26)
George Mountbc7a78f2019-08-02 14:20:03 -0700795 private fun validateVerticalScroller(
Matvei Malkovc09bc562020-02-05 18:13:34 -0800796 offset: Int = 0,
797 width: Int = 45,
798 height: Int = 40,
799 rowHeight: Int = 5
George Mounte872c332019-04-12 15:03:59 -0700800 ) {
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100801 rule.onNodeWithTag(scrollerTag)
Filip Pavlis591e17a2020-11-02 18:47:42 +0000802 .captureToImage()
George Mount8f237572020-04-30 12:08:30 -0700803 .assertPixels(expectedSize = IntSize(width, height)) { pos ->
804 val colorIndex = (offset + pos.y) / rowHeight
Filip Pavlis582a2be2020-01-07 17:50:32 +0000805 colors[colorIndex]
George Mounte872c332019-04-12 15:03:59 -0700806 }
George Mounte872c332019-04-12 15:03:59 -0700807 }
808
Jelle Fresen389cb602019-11-14 16:54:28 +0000809 @RequiresApi(api = 26)
George Mountbc7a78f2019-08-02 14:20:03 -0700810 private fun validateHorizontalScroller(
Matvei Malkovc09bc562020-02-05 18:13:34 -0800811 offset: Int = 0,
812 width: Int = 40,
813 height: Int = 45,
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100814 checkInRtl: Boolean = false
George Mountbc7a78f2019-08-02 14:20:03 -0700815 ) {
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100816 val scrollerWidth = colors.size * defaultCellSize
817 val absoluteOffset = if (checkInRtl) scrollerWidth - width - offset else offset
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100818 rule.onNodeWithTag(scrollerTag)
Filip Pavlis591e17a2020-11-02 18:47:42 +0000819 .captureToImage()
George Mount8f237572020-04-30 12:08:30 -0700820 .assertPixels(expectedSize = IntSize(width, height)) { pos ->
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100821 val colorIndex = (absoluteOffset + pos.x) / defaultCellSize
822 if (checkInRtl) colors[colors.size - 1 - colorIndex] else colors[colorIndex]
George Mountbc7a78f2019-08-02 14:20:03 -0700823 }
George Mountbc7a78f2019-08-02 14:20:03 -0700824 }
825
Jelle Fresen389cb602019-11-14 16:54:28 +0000826 private fun createScrollableContent(
827 isVertical: Boolean,
Jelle Fresen2c776c62019-11-15 16:52:39 +0000828 itemCount: Int = 100,
829 width: Dp = 100.dp,
830 height: Dp = 100.dp,
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100831 isReversed: Boolean = false,
832 scrollState: ScrollState = ScrollState(
833 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100834 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Jelle Fresen0186e462020-02-20 11:35:14 +0000835 animationClock = ManualAnimationClock(0)
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100836 ),
837 isRtl: Boolean = false
Jelle Fresen389cb602019-11-14 16:54:28 +0000838 ) {
Filip Pavlis3d2d0392020-09-03 14:43:36 +0100839 rule.setContent {
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000840 val content = @Composable {
Jelle Fresen389cb602019-11-14 16:54:28 +0000841 repeat(itemCount) {
Louis Pullen-Freilich051800b2020-10-30 19:26:32 +0000842 BasicText(text = "$it")
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000843 }
844 }
Mihai Popa60a90cc2020-09-15 12:17:41 +0100845 Box {
Matvei Malkov2b055a72020-06-12 17:26:17 +0100846 Box(
Matvei Malkov0de3d282020-09-03 18:01:03 +0100847 Modifier.preferredSize(width, height).background(Color.White)
Matvei Malkov2b055a72020-06-12 17:26:17 +0100848 ) {
849 if (isVertical) {
Andrey Kulikov05c18b22020-12-14 14:26:44 +0000850 Column(
851 Modifier
852 .testTag(scrollerTag)
853 .verticalScroll(scrollState, reverseScrolling = isReversed)
Jelle Fresen33c83a32020-08-27 15:39:59 +0100854 ) {
855 content()
Matvei Malkov2b055a72020-06-12 17:26:17 +0100856 }
857 } else {
Matvei Malkov2ff160f2020-08-06 16:42:22 +0100858 val direction = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
Louis Pullen-Freilichd42e1072021-01-27 18:20:02 +0000859 Providers(LocalLayoutDirection provides direction) {
Andrey Kulikov05c18b22020-12-14 14:26:44 +0000860 Row(
861 Modifier.testTag(scrollerTag)
862 .horizontalScroll(scrollState, reverseScrolling = isReversed)
Jelle Fresen33c83a32020-08-27 15:39:59 +0100863 ) {
864 content()
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100865 }
Jelle Fresen2c776c62019-11-15 16:52:39 +0000866 }
Matvei Malkovc09bc562020-02-05 18:13:34 -0800867 }
Cătălin Tudor1ff2b582019-11-12 13:38:32 +0000868 }
869 }
870 }
871 }
872
Filip Pavlis582a2be2020-01-07 17:50:32 +0000873 // TODO(b/147291885): This should not be needed in the future.
Jelle Fresen2c776c62019-11-15 16:52:39 +0000874 private fun SemanticsNodeInteraction.awaitScrollAnimation(
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100875 scroller: ScrollState
Jelle Fresen2c776c62019-11-15 16:52:39 +0000876 ): SemanticsNodeInteraction {
Jelle Fresen2c776c62019-11-15 16:52:39 +0000877 val latch = CountDownLatch(1)
878 val handler = Handler(Looper.getMainLooper())
879 handler.post(object : Runnable {
880 override fun run() {
Matvei Malkov235b4fa2020-07-07 21:14:49 +0100881 if (scroller.isAnimationRunning) {
Jelle Fresen2c776c62019-11-15 16:52:39 +0000882 handler.post(this)
883 } else {
884 latch.countDown()
885 }
886 }
887 })
Jelle Fresen7ce06d62020-02-25 11:38:59 +0000888 assertWithMessage("Scroll didn't finish after 20 seconds")
889 .that(latch.await(20, TimeUnit.SECONDS)).isTrue()
Jelle Fresen2c776c62019-11-15 16:52:39 +0000890 return this
891 }
Jens Ole Lauridsencb1ca652020-10-28 14:43:30 -0700892
893 @Test
894 fun testInspectorValue() {
895 val state = ScrollState(
896 initial = 0f,
Jelle Fresenb40ab8e2020-09-08 14:18:41 +0100897 flingConfig = FlingConfig(FloatExponentialDecaySpec()),
Jelle Fresen3ac4b802021-01-06 11:22:38 +0000898 animationClock = MockAnimationClock()
Jens Ole Lauridsencb1ca652020-10-28 14:43:30 -0700899 )
900 rule.setContent {
901 val modifier = Modifier.verticalScroll(state) as InspectableValue
902 assertThat(modifier.nameFallback).isEqualTo("scroll")
903 assertThat(modifier.valueOverride).isNull()
904 assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
905 "state",
906 "reverseScrolling",
907 "isScrollable",
908 "isVertical"
909 )
910 }
911 }
George Mounte872c332019-04-12 15:03:59 -0700912}