Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2020 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 | */ |
| 16 | |
| 17 | package androidx.compose.foundation |
| 18 | |
| 19 | import androidx.compose.foundation.layout.Box |
Andrey Kulikov | 05c18b2 | 2020-12-14 14:26:44 +0000 | [diff] [blame] | 20 | import androidx.compose.foundation.layout.Column |
Igor Demin | 3bc059e | 2020-11-25 21:19:35 +0300 | [diff] [blame] | 21 | import androidx.compose.foundation.layout.ColumnScope |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 22 | import androidx.compose.foundation.layout.fillMaxHeight |
| 23 | import androidx.compose.foundation.layout.fillMaxSize |
| 24 | import androidx.compose.foundation.layout.size |
| 25 | import androidx.compose.foundation.layout.width |
Andrey Kulikov | 1e8ebd3 | 2020-12-08 22:12:16 +0000 | [diff] [blame] | 26 | import androidx.compose.foundation.lazy.LazyColumn |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 27 | import androidx.compose.foundation.lazy.LazyListState |
Andrey Kulikov | bb05cd7 | 2020-12-11 19:39:21 +0000 | [diff] [blame] | 28 | import androidx.compose.foundation.lazy.items |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 29 | import androidx.compose.foundation.lazy.rememberLazyListState |
| 30 | import androidx.compose.runtime.Composable |
Leland Richardson | 0f99bf1 | 2021-02-02 20:56:41 -0800 | [diff] [blame] | 31 | import androidx.compose.runtime.CompositionLocalProvider |
Igor Demin | 3bc059e | 2020-11-25 21:19:35 +0300 | [diff] [blame] | 32 | import androidx.compose.runtime.mutableStateOf |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 33 | import androidx.compose.ui.Modifier |
| 34 | import androidx.compose.ui.geometry.Offset |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 35 | import androidx.compose.ui.graphics.Color |
| 36 | import androidx.compose.ui.graphics.RectangleShape |
| 37 | import androidx.compose.ui.input.mouse.MouseScrollEvent |
| 38 | import androidx.compose.ui.input.mouse.MouseScrollUnit |
Matvei Malkov | 693e3cc | 2021-02-05 19:18:12 +0000 | [diff] [blame^] | 39 | import androidx.compose.ui.input.mouse.MouseScrollOrientation |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 40 | import androidx.compose.ui.platform.DesktopPlatform |
| 41 | import androidx.compose.ui.platform.DesktopPlatformAmbient |
| 42 | import androidx.compose.ui.platform.testTag |
Jelle Fresen | 8e55c4f | 2020-12-16 13:15:53 +0000 | [diff] [blame] | 43 | import androidx.compose.ui.test.ExperimentalTestApi |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 44 | import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo |
| 45 | import androidx.compose.ui.test.down |
| 46 | import androidx.compose.ui.test.junit4.ComposeTestRule |
| 47 | import androidx.compose.ui.test.junit4.DesktopComposeTestRule |
| 48 | import androidx.compose.ui.test.junit4.createComposeRule |
| 49 | import androidx.compose.ui.test.onNodeWithTag |
| 50 | import androidx.compose.ui.test.performGesture |
| 51 | import androidx.compose.ui.test.swipe |
| 52 | import androidx.compose.ui.unit.Dp |
| 53 | import androidx.compose.ui.unit.dp |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 54 | import kotlinx.coroutines.Dispatchers |
| 55 | import kotlinx.coroutines.delay |
| 56 | import kotlinx.coroutines.runBlocking |
| 57 | import org.jetbrains.skija.Surface |
| 58 | import org.junit.Assert.assertEquals |
| 59 | import org.junit.Ignore |
| 60 | import org.junit.Rule |
| 61 | import org.junit.Test |
| 62 | |
| 63 | @Suppress("WrapUnaryOperator") |
Jelle Fresen | 8e55c4f | 2020-12-16 13:15:53 +0000 | [diff] [blame] | 64 | @OptIn(ExperimentalTestApi::class) |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 65 | class ScrollbarTest { |
| 66 | @get:Rule |
| 67 | val rule = createComposeRule() |
Igor Demin | 67e4aa2 | 2020-11-19 11:12:43 +0300 | [diff] [blame] | 68 | |
| 69 | // don't inline, surface controls canvas life time |
Igor Demin | d4827cb | 2021-01-18 15:30:47 +0300 | [diff] [blame] | 70 | private val surface = Surface.makeRasterN32Premul(100, 100) |
Igor Demin | 67e4aa2 | 2020-11-19 11:12:43 +0300 | [diff] [blame] | 71 | private val canvas = surface.canvas |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 72 | |
| 73 | @Test |
| 74 | fun `drag slider to the middle`() { |
| 75 | runBlocking(Dispatchers.Main) { |
| 76 | rule.setContent { |
| 77 | TestBox(size = 100.dp, childSize = 20.dp, childCount = 10, scrollbarWidth = 10.dp) |
| 78 | } |
| 79 | rule.awaitIdle() |
| 80 | |
| 81 | rule.onNodeWithTag("scrollbar").performGesture { |
| 82 | swipe(start = Offset(0f, 25f), end = Offset(0f, 50f)) |
| 83 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 84 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 85 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(-50.dp) |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | @Test |
| 90 | fun `drag slider to the edges`() { |
| 91 | runBlocking(Dispatchers.Main) { |
| 92 | rule.setContent { |
| 93 | TestBox(size = 100.dp, childSize = 20.dp, childCount = 10, scrollbarWidth = 10.dp) |
| 94 | } |
| 95 | rule.awaitIdle() |
| 96 | |
| 97 | rule.onNodeWithTag("scrollbar").performGesture { |
| 98 | swipe(start = Offset(0f, 25f), end = Offset(0f, 500f)) |
| 99 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 100 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 101 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(-100.dp) |
| 102 | |
| 103 | rule.onNodeWithTag("scrollbar").performGesture { |
| 104 | swipe(start = Offset(0f, 99f), end = Offset(0f, -500f)) |
| 105 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 106 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 107 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(0.dp) |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | @Test |
| 112 | fun `drag outside slider`() { |
| 113 | runBlocking(Dispatchers.Main) { |
| 114 | rule.setContent { |
| 115 | TestBox(size = 100.dp, childSize = 20.dp, childCount = 10, scrollbarWidth = 10.dp) |
| 116 | } |
| 117 | rule.awaitIdle() |
| 118 | |
| 119 | rule.onNodeWithTag("scrollbar").performGesture { |
| 120 | swipe(start = Offset(10f, 25f), end = Offset(0f, 50f)) |
| 121 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 122 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 123 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(0.dp) |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | // TODO(demin): enable after we resolve b/171889442 |
| 128 | @Ignore("Enable after we resolve b/171889442") |
| 129 | @Test |
| 130 | fun `mouseScroll over slider`() { |
| 131 | runBlocking(Dispatchers.Main) { |
| 132 | rule.setContent { |
| 133 | TestBox(size = 100.dp, childSize = 20.dp, childCount = 10, scrollbarWidth = 10.dp) |
| 134 | } |
| 135 | rule.awaitIdle() |
| 136 | |
| 137 | rule.performMouseScroll(0, 25, 1f) |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 138 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 139 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(-10.dp) |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // TODO(demin): enable after we resolve b/171889442 |
| 144 | @Ignore("Enable after we resolve b/171889442") |
| 145 | @Test |
| 146 | fun `mouseScroll over scrollbar outside slider`() { |
| 147 | runBlocking(Dispatchers.Main) { |
| 148 | rule.setContent { |
| 149 | TestBox(size = 100.dp, childSize = 20.dp, childCount = 10, scrollbarWidth = 10.dp) |
| 150 | } |
| 151 | rule.awaitIdle() |
| 152 | |
| 153 | rule.performMouseScroll(0, 99, 1f) |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 154 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 155 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(-10.dp) |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | // TODO(demin): enable after we resolve b/171889442 |
| 160 | @Ignore("Enable after we resolve b/171889442") |
| 161 | @Test |
| 162 | fun `vertical mouseScroll over horizontal scrollbar `() { |
| 163 | runBlocking(Dispatchers.Main) { |
| 164 | // TODO(demin): write tests for vertical mouse scrolling over |
| 165 | // horizontalScrollbar for the case when we have two-way scrollable content: |
| 166 | // Modifier.verticalScrollbar(...).horizontalScrollbar(...) |
| 167 | // Content should scroll vertically. |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | @Test |
| 172 | fun `mouseScroll over column then drag to the beginning`() { |
| 173 | runBlocking(Dispatchers.Main) { |
| 174 | rule.setContent { |
| 175 | TestBox(size = 100.dp, childSize = 20.dp, childCount = 10, scrollbarWidth = 10.dp) |
| 176 | } |
| 177 | rule.awaitIdle() |
| 178 | |
| 179 | rule.performMouseScroll(20, 25, 10f) |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 180 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 181 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(-100.dp) |
| 182 | |
| 183 | rule.onNodeWithTag("scrollbar").performGesture { |
| 184 | swipe(start = Offset(0f, 99f), end = Offset(0f, -500f)) |
| 185 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 186 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 187 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(0.dp) |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | @Test(timeout = 3000) |
| 192 | fun `press on scrollbar outside slider`() { |
| 193 | runBlocking(Dispatchers.Main) { |
| 194 | rule.setContent { |
| 195 | TestBox(size = 100.dp, childSize = 20.dp, childCount = 20, scrollbarWidth = 10.dp) |
| 196 | } |
| 197 | rule.awaitIdle() |
| 198 | |
| 199 | rule.onNodeWithTag("scrollbar").performGesture { |
| 200 | down(Offset(0f, 26f)) |
| 201 | } |
| 202 | |
| 203 | tryUntilSucceeded { |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 204 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 205 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(-100.dp) |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | @Test(timeout = 3000) |
| 211 | fun `press on the end of scrollbar outside slider`() { |
| 212 | runBlocking(Dispatchers.Main) { |
| 213 | rule.setContent { |
| 214 | TestBox(size = 100.dp, childSize = 20.dp, childCount = 20, scrollbarWidth = 10.dp) |
| 215 | } |
| 216 | rule.awaitIdle() |
| 217 | |
| 218 | rule.onNodeWithTag("scrollbar").performGesture { |
| 219 | down(Offset(0f, 99f)) |
| 220 | } |
| 221 | |
| 222 | tryUntilSucceeded { |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 223 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 224 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(-300.dp) |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | |
Igor Demin | 3bc059e | 2020-11-25 21:19:35 +0300 | [diff] [blame] | 229 | @Test(timeout = 3000) |
| 230 | fun `dynamically change content then drag slider to the end`() { |
| 231 | runBlocking(Dispatchers.Main) { |
| 232 | val isContentVisible = mutableStateOf(false) |
| 233 | rule.setContent { |
| 234 | TestBox( |
| 235 | size = 100.dp, |
| 236 | scrollbarWidth = 10.dp |
| 237 | ) { |
| 238 | if (isContentVisible.value) { |
| 239 | repeat(10) { |
| 240 | Box(Modifier.size(20.dp).testTag("box$it")) |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 245 | rule.awaitIdle() |
Igor Demin | 3bc059e | 2020-11-25 21:19:35 +0300 | [diff] [blame] | 246 | |
| 247 | isContentVisible.value = true |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 248 | rule.awaitIdle() |
Igor Demin | 3bc059e | 2020-11-25 21:19:35 +0300 | [diff] [blame] | 249 | |
| 250 | rule.onNodeWithTag("scrollbar").performGesture { |
| 251 | swipe(start = Offset(0f, 25f), end = Offset(0f, 500f)) |
| 252 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 253 | rule.awaitIdle() |
Igor Demin | 3bc059e | 2020-11-25 21:19:35 +0300 | [diff] [blame] | 254 | rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(-100.dp) |
| 255 | } |
| 256 | } |
| 257 | |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 258 | @Suppress("SameParameterValue") |
| 259 | @OptIn(ExperimentalFoundationApi::class) |
| 260 | @Test(timeout = 3000) |
| 261 | fun `scroll by less than one page in lazy list`() { |
| 262 | runBlocking(Dispatchers.Main) { |
| 263 | lateinit var state: LazyListState |
| 264 | |
| 265 | rule.setContent { |
| 266 | state = rememberLazyListState() |
| 267 | LazyTestBox( |
| 268 | state, |
| 269 | size = 100.dp, |
| 270 | childSize = 20.dp, |
| 271 | childCount = 20, |
| 272 | scrollbarWidth = 10.dp |
| 273 | ) |
| 274 | } |
| 275 | rule.awaitIdle() |
| 276 | |
| 277 | rule.onNodeWithTag("scrollbar").performGesture { |
George Mount | 8c41a23 | 2021-01-11 21:13:04 +0000 | [diff] [blame] | 278 | swipe(start = Offset(0f, 0f), end = Offset(0f, 11f), durationMillis = 1) |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 279 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 280 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 281 | assertEquals(2, state.firstVisibleItemIndex) |
| 282 | assertEquals(4, state.firstVisibleItemScrollOffset) |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | @Suppress("SameParameterValue") |
| 287 | @OptIn(ExperimentalFoundationApi::class) |
| 288 | @Test(timeout = 3000) |
| 289 | fun `scroll by more than one page in lazy list`() { |
| 290 | runBlocking(Dispatchers.Main) { |
| 291 | lateinit var state: LazyListState |
| 292 | |
| 293 | rule.setContent { |
| 294 | state = rememberLazyListState() |
| 295 | LazyTestBox( |
| 296 | state, |
| 297 | size = 100.dp, |
| 298 | childSize = 20.dp, |
| 299 | childCount = 20, |
| 300 | scrollbarWidth = 10.dp |
| 301 | ) |
| 302 | } |
| 303 | rule.awaitIdle() |
| 304 | |
| 305 | rule.onNodeWithTag("scrollbar").performGesture { |
George Mount | 8c41a23 | 2021-01-11 21:13:04 +0000 | [diff] [blame] | 306 | swipe(start = Offset(0f, 0f), end = Offset(0f, 26f), durationMillis = 1) |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 307 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 308 | rule.awaitIdle() |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 309 | assertEquals(5, state.firstVisibleItemIndex) |
| 310 | assertEquals(4, state.firstVisibleItemScrollOffset) |
| 311 | } |
| 312 | } |
| 313 | |
Igor Demin | 67e4aa2 | 2020-11-19 11:12:43 +0300 | [diff] [blame] | 314 | @Suppress("SameParameterValue") |
| 315 | @OptIn(ExperimentalFoundationApi::class) |
| 316 | @Test(timeout = 3000) |
| 317 | fun `scroll outside of scrollbar bounds in lazy list`() { |
| 318 | runBlocking(Dispatchers.Main) { |
| 319 | lateinit var state: LazyListState |
| 320 | |
| 321 | rule.setContent { |
| 322 | state = rememberLazyListState() |
| 323 | LazyTestBox( |
| 324 | state, |
| 325 | size = 100.dp, |
| 326 | childSize = 20.dp, |
| 327 | childCount = 20, |
| 328 | scrollbarWidth = 10.dp |
| 329 | ) |
| 330 | } |
| 331 | rule.awaitIdle() |
| 332 | |
| 333 | rule.onNodeWithTag("scrollbar").performGesture { |
George Mount | 8c41a23 | 2021-01-11 21:13:04 +0000 | [diff] [blame] | 334 | swipe(start = Offset(0f, 0f), end = Offset(0f, 10000f), durationMillis = 1) |
Igor Demin | 67e4aa2 | 2020-11-19 11:12:43 +0300 | [diff] [blame] | 335 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 336 | rule.awaitIdle() |
Igor Demin | 67e4aa2 | 2020-11-19 11:12:43 +0300 | [diff] [blame] | 337 | assertEquals(15, state.firstVisibleItemIndex) |
| 338 | assertEquals(0, state.firstVisibleItemScrollOffset) |
| 339 | |
| 340 | rule.onNodeWithTag("scrollbar").performGesture { |
George Mount | 8c41a23 | 2021-01-11 21:13:04 +0000 | [diff] [blame] | 341 | swipe(start = Offset(0f, 99f), end = Offset(0f, -10000f), durationMillis = 1) |
Igor Demin | 67e4aa2 | 2020-11-19 11:12:43 +0300 | [diff] [blame] | 342 | } |
Igor Demin | 3dfcd04 | 2020-12-03 17:08:19 +0300 | [diff] [blame] | 343 | rule.awaitIdle() |
Igor Demin | 67e4aa2 | 2020-11-19 11:12:43 +0300 | [diff] [blame] | 344 | assertEquals(0, state.firstVisibleItemIndex) |
| 345 | assertEquals(0, state.firstVisibleItemScrollOffset) |
| 346 | } |
| 347 | } |
| 348 | |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 349 | private suspend fun tryUntilSucceeded(block: suspend () -> Unit) { |
| 350 | while (true) { |
| 351 | try { |
| 352 | block() |
| 353 | break |
| 354 | } catch (e: Throwable) { |
| 355 | delay(10) |
| 356 | } |
| 357 | } |
| 358 | } |
| 359 | |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 360 | private fun ComposeTestRule.performMouseScroll(x: Int, y: Int, delta: Float) { |
Igor Demin | d49c014 | 2021-01-22 15:59:54 +0300 | [diff] [blame] | 361 | (this as DesktopComposeTestRule).window.onMouseScroll( |
Matvei Malkov | 693e3cc | 2021-02-05 19:18:12 +0000 | [diff] [blame^] | 362 | x, y, MouseScrollEvent(MouseScrollUnit.Line(delta), MouseScrollOrientation.Vertical) |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 363 | ) |
| 364 | } |
| 365 | |
| 366 | @Composable |
| 367 | private fun TestBox( |
| 368 | size: Dp, |
| 369 | childSize: Dp, |
| 370 | childCount: Int, |
| 371 | scrollbarWidth: Dp, |
| 372 | ) = withTestEnvironment { |
| 373 | Box(Modifier.size(size)) { |
| 374 | val state = rememberScrollState() |
| 375 | |
Andrey Kulikov | 05c18b2 | 2020-12-14 14:26:44 +0000 | [diff] [blame] | 376 | Column( |
| 377 | Modifier.fillMaxSize().testTag("column").verticalScroll(state) |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 378 | ) { |
| 379 | repeat(childCount) { |
| 380 | Box(Modifier.size(childSize).testTag("box$it")) |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | VerticalScrollbar( |
| 385 | adapter = rememberScrollbarAdapter(state), |
| 386 | modifier = Modifier |
| 387 | .width(scrollbarWidth) |
| 388 | .fillMaxHeight() |
| 389 | .testTag("scrollbar") |
| 390 | ) |
| 391 | } |
| 392 | } |
| 393 | |
Igor Demin | 3bc059e | 2020-11-25 21:19:35 +0300 | [diff] [blame] | 394 | @Composable |
| 395 | private fun TestBox( |
| 396 | size: Dp, |
| 397 | scrollbarWidth: Dp, |
| 398 | scrollableContent: @Composable ColumnScope.() -> Unit |
| 399 | ) = withTestEnvironment { |
| 400 | Box(Modifier.size(size)) { |
| 401 | val state = rememberScrollState() |
| 402 | |
Andrey Kulikov | 05c18b2 | 2020-12-14 14:26:44 +0000 | [diff] [blame] | 403 | Column( |
| 404 | Modifier.fillMaxSize().testTag("column").verticalScroll(state), |
Igor Demin | 3bc059e | 2020-11-25 21:19:35 +0300 | [diff] [blame] | 405 | content = scrollableContent |
| 406 | ) |
| 407 | |
| 408 | VerticalScrollbar( |
| 409 | adapter = rememberScrollbarAdapter(state), |
| 410 | modifier = Modifier |
| 411 | .width(scrollbarWidth) |
| 412 | .fillMaxHeight() |
| 413 | .testTag("scrollbar") |
| 414 | ) |
| 415 | } |
| 416 | } |
| 417 | |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 418 | @Suppress("SameParameterValue") |
| 419 | @OptIn(ExperimentalFoundationApi::class) |
| 420 | @Composable |
| 421 | private fun LazyTestBox( |
| 422 | state: LazyListState, |
| 423 | size: Dp, |
| 424 | childSize: Dp, |
| 425 | childCount: Int, |
| 426 | scrollbarWidth: Dp, |
| 427 | ) = withTestEnvironment { |
| 428 | Box(Modifier.size(size)) { |
Andrey Kulikov | 1e8ebd3 | 2020-12-08 22:12:16 +0000 | [diff] [blame] | 429 | LazyColumn( |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 430 | Modifier.fillMaxSize().testTag("column"), |
| 431 | state |
| 432 | ) { |
Andrey Kulikov | 1e8ebd3 | 2020-12-08 22:12:16 +0000 | [diff] [blame] | 433 | items((0 until childCount).toList()) { |
| 434 | Box(Modifier.size(childSize).testTag("box$it")) |
| 435 | } |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 436 | } |
| 437 | |
| 438 | VerticalScrollbar( |
| 439 | adapter = rememberScrollbarAdapter(state, childCount, childSize), |
| 440 | modifier = Modifier |
| 441 | .width(scrollbarWidth) |
| 442 | .fillMaxHeight() |
| 443 | .testTag("scrollbar") |
| 444 | ) |
| 445 | } |
| 446 | } |
| 447 | |
| 448 | @Composable |
Leland Richardson | 0f99bf1 | 2021-02-02 20:56:41 -0800 | [diff] [blame] | 449 | private fun withTestEnvironment(content: @Composable () -> Unit) = CompositionLocalProvider( |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 450 | ScrollbarStyleAmbient provides ScrollbarStyle( |
| 451 | minimalHeight = 16.dp, |
| 452 | thickness = 8.dp, |
| 453 | shape = RectangleShape, |
| 454 | hoverDurationMillis = 300, |
| 455 | unhoverColor = Color.Black, |
| 456 | hoverColor = Color.Red |
| 457 | ), |
| 458 | DesktopPlatformAmbient provides DesktopPlatform.MacOS, |
Igor Demin | d3e0f02 | 2020-11-18 13:20:30 +0300 | [diff] [blame] | 459 | content = content |
Igor Demin | d7332f8 | 2020-10-23 21:45:51 +0300 | [diff] [blame] | 460 | ) |
Jelle Fresen | 8e55c4f | 2020-12-16 13:15:53 +0000 | [diff] [blame] | 461 | } |