1 /* <lambda>null2 * Copyright (C) 2022 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.constraintlayout.compose 18 19 import androidx.compose.foundation.background 20 import androidx.compose.foundation.layout.Box 21 import androidx.compose.foundation.layout.size 22 import androidx.compose.runtime.Composable 23 import androidx.compose.ui.Modifier 24 import androidx.compose.ui.graphics.Color 25 import androidx.compose.ui.layout.layoutId 26 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled 27 import androidx.compose.ui.platform.testTag 28 import androidx.compose.ui.test.assertPositionInRootIsEqualTo 29 import androidx.compose.ui.test.junit4.createComposeRule 30 import androidx.compose.ui.test.onNodeWithTag 31 import androidx.compose.ui.unit.Dp 32 import androidx.compose.ui.unit.dp 33 import androidx.test.ext.junit.runners.AndroidJUnit4 34 import androidx.test.filters.MediumTest 35 import org.junit.After 36 import org.junit.Before 37 import org.junit.Rule 38 import org.junit.Test 39 import org.junit.runner.RunWith 40 41 /** Tests for the Grid Helper */ 42 @MediumTest 43 @RunWith(AndroidJUnit4::class) 44 class GridTest { 45 @get:Rule val rule = createComposeRule() 46 47 @Before 48 fun setup() { 49 isDebugInspectorInfoEnabled = true 50 } 51 52 @After 53 fun tearDown() { 54 isDebugInspectorInfoEnabled = false 55 } 56 57 @Test 58 fun testTwoByTwo() { 59 val rootSize = 200.dp 60 val boxesCount = 4 61 val rows = 2 62 val columns = 2 63 rule.setContent { 64 gridComposableTest( 65 modifier = Modifier.size(rootSize), 66 width = "'parent'", 67 height = "'parent'", 68 boxesCount = boxesCount, 69 orientation = 0, 70 rows = rows, 71 columns = columns, 72 hGap = 0, 73 vGap = 0, 74 spans = "''", 75 skips = "''", 76 rowWeights = "''", 77 columnWeights = "''", 78 flags = "''" 79 ) 80 } 81 var leftX = 0.dp 82 var topY = 0.dp 83 var rightX: Dp 84 var bottomY: Dp 85 86 // 10.dp is the size of a singular box 87 val gapSize = (rootSize - (10.dp * 2f)) / (columns * 2f) 88 rule.waitForIdle() 89 leftX += gapSize 90 topY += gapSize 91 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(leftX, topY) 92 rightX = leftX + 10.dp + gapSize + gapSize 93 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(rightX, topY) 94 bottomY = topY + 10.dp + gapSize + gapSize 95 rule.onNodeWithTag("box2").assertPositionInRootIsEqualTo(leftX, bottomY) 96 rule.onNodeWithTag("box3").assertPositionInRootIsEqualTo(rightX, bottomY) 97 } 98 99 @Test 100 fun testOrientation() { 101 val rootSize = 200.dp 102 val boxesCount = 4 103 val rows = 2 104 val columns = 2 105 rule.setContent { 106 gridComposableTest( 107 modifier = Modifier.size(rootSize), 108 width = "'parent'", 109 height = "'parent'", 110 boxesCount = boxesCount, 111 orientation = 1, 112 rows = rows, 113 columns = columns, 114 hGap = 0, 115 vGap = 0, 116 spans = "''", 117 skips = "''", 118 rowWeights = "''", 119 columnWeights = "''", 120 flags = "''" 121 ) 122 } 123 var leftX = 0.dp 124 var topY = 0.dp 125 var rightX: Dp 126 var bottomY: Dp 127 128 // 10.dp is the size of a singular box 129 val gapSize = (rootSize - (10.dp * 2f)) / (columns * 2f) 130 rule.waitForIdle() 131 leftX += gapSize 132 topY += gapSize 133 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(leftX, topY) 134 rightX = leftX + 10.dp + gapSize + gapSize 135 rule.onNodeWithTag("box2").assertPositionInRootIsEqualTo(rightX, topY) 136 bottomY = topY + 10.dp + gapSize + gapSize 137 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(leftX, bottomY) 138 rule.onNodeWithTag("box3").assertPositionInRootIsEqualTo(rightX, bottomY) 139 } 140 141 @Test 142 fun testRows() { 143 val rootSize = 200.dp 144 val boxesCount = 4 145 val rows = 0 146 val columns = 1 147 rule.setContent { 148 gridComposableTest( 149 modifier = Modifier.size(rootSize), 150 width = "'parent'", 151 height = "'parent'", 152 boxesCount = boxesCount, 153 orientation = 0, 154 rows = rows, 155 columns = columns, 156 hGap = 0, 157 vGap = 0, 158 spans = "''", 159 skips = "''", 160 rowWeights = "''", 161 columnWeights = "''", 162 flags = "''" 163 ) 164 } 165 var expectedX = 0.dp 166 var expectedY = 0.dp 167 168 // 10.dp is the size of a singular box 169 val hGapSize = (rootSize - 10.dp) / 2f 170 val vGapSize = (rootSize - (10.dp * 4f)) / (boxesCount * 2f) 171 rule.waitForIdle() 172 expectedX += hGapSize 173 expectedY += vGapSize 174 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(expectedX, expectedY) 175 expectedY += vGapSize + vGapSize + 10.dp 176 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(expectedX, expectedY) 177 expectedY += vGapSize + vGapSize + 10.dp 178 rule.onNodeWithTag("box2").assertPositionInRootIsEqualTo(expectedX, expectedY) 179 expectedY += vGapSize + vGapSize + 10.dp 180 rule.onNodeWithTag("box3").assertPositionInRootIsEqualTo(expectedX, expectedY) 181 } 182 183 @Test 184 fun testColumns() { 185 val rootSize = 200.dp 186 val boxesCount = 4 187 val rows = 1 188 val columns = 0 189 rule.setContent { 190 gridComposableTest( 191 modifier = Modifier.size(rootSize), 192 width = "'parent'", 193 height = "'parent'", 194 boxesCount = boxesCount, 195 orientation = 0, 196 rows = rows, 197 columns = columns, 198 hGap = 0, 199 vGap = 0, 200 spans = "''", 201 skips = "''", 202 rowWeights = "''", 203 columnWeights = "''", 204 flags = "''" 205 ) 206 } 207 var expectedX = 0.dp 208 var expectedY = 0.dp 209 210 // 10.dp is the size of a singular box 211 val hGapSize = (rootSize - (10.dp * 4f)) / (boxesCount * 2f) 212 val vGapSize = (rootSize - 10.dp) / 2f 213 rule.waitForIdle() 214 expectedX += hGapSize 215 expectedY += vGapSize 216 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(expectedX, expectedY) 217 expectedX += hGapSize + hGapSize + 10.dp 218 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(expectedX, expectedY) 219 expectedX += hGapSize + hGapSize + 10.dp 220 rule.onNodeWithTag("box2").assertPositionInRootIsEqualTo(expectedX, expectedY) 221 expectedX += hGapSize + hGapSize + 10.dp 222 rule.onNodeWithTag("box3").assertPositionInRootIsEqualTo(expectedX, expectedY) 223 } 224 225 @Test 226 fun testSkips() { 227 val rootSize = 200.dp 228 val boxesCount = 3 229 val rows = 2 230 val columns = 2 231 rule.setContent { 232 gridComposableTest( 233 modifier = Modifier.size(rootSize), 234 width = "'parent'", 235 height = "'parent'", 236 boxesCount = boxesCount, 237 orientation = 0, 238 rows = rows, 239 columns = columns, 240 hGap = 0, 241 vGap = 0, 242 spans = "''", 243 skips = "'0:1x1'", 244 rowWeights = "''", 245 columnWeights = "''", 246 flags = "''" 247 ) 248 } 249 var leftX = 0.dp 250 var topY = 0.dp 251 var rightX: Dp 252 var bottomY: Dp 253 254 // 10.dp is the size of a singular box 255 val gapSize = (rootSize - (10.dp * 2f)) / (columns * 2f) 256 rule.waitForIdle() 257 leftX += gapSize 258 topY += gapSize 259 rightX = leftX + 10.dp + gapSize + gapSize 260 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(rightX, topY) 261 bottomY = topY + 10.dp + gapSize + gapSize 262 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(leftX, bottomY) 263 rule.onNodeWithTag("box2").assertPositionInRootIsEqualTo(rightX, bottomY) 264 } 265 266 @Test 267 fun testReversedDirectionSkips() { 268 val rootSize = 200.dp 269 val boxesCount = 2 270 val rows = 2 271 val columns = 2 272 rule.setContent { 273 gridComposableTest( 274 modifier = Modifier.size(rootSize), 275 width = "'parent'", 276 height = "'parent'", 277 boxesCount = boxesCount, 278 orientation = 0, 279 rows = rows, 280 columns = columns, 281 hGap = 0, 282 vGap = 0, 283 spans = "''", 284 skips = "'0:2x1'", 285 rowWeights = "''", 286 columnWeights = "''", 287 flags = "'SubGridByColRow'" 288 ) 289 } 290 var leftX = 0.dp 291 var topY = 0.dp 292 var rightX: Dp 293 var bottomY: Dp 294 295 // 10.dp is the size of a singular box 296 val gapSize = (rootSize - (10.dp * 2f)) / (columns * 2f) 297 rule.waitForIdle() 298 leftX += gapSize 299 topY += gapSize 300 rightX = leftX + 10.dp + gapSize + gapSize 301 bottomY = topY + 10.dp + gapSize + gapSize 302 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(leftX, bottomY) 303 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(rightX, bottomY) 304 } 305 306 @Test 307 fun testSpans() { 308 val rootSize = 200.dp 309 val boxesCount = 3 310 val rows = 2 311 val columns = 2 312 rule.setContent { 313 gridComposableTest( 314 modifier = Modifier.size(rootSize), 315 width = "'parent'", 316 height = "'parent'", 317 boxesCount = boxesCount, 318 orientation = 0, 319 rows = rows, 320 columns = columns, 321 hGap = 0, 322 vGap = 0, 323 spans = "'0:1x2'", 324 skips = "''", 325 rowWeights = "''", 326 columnWeights = "''", 327 flags = "''" 328 ) 329 } 330 var leftX = 0.dp 331 var topY = 0.dp 332 var rightX: Dp 333 var bottomY: Dp 334 335 // 10.dp is the size of a singular box 336 var spanLeft = (rootSize - 10.dp) / 2f 337 val gapSize = (rootSize - (10.dp * 2f)) / (columns * 2f) 338 rule.waitForIdle() 339 leftX += gapSize 340 topY += gapSize 341 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(spanLeft, topY) 342 rightX = leftX + 10.dp + gapSize + gapSize 343 bottomY = topY + 10.dp + gapSize + gapSize 344 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(leftX, bottomY) 345 rule.onNodeWithTag("box2").assertPositionInRootIsEqualTo(rightX, bottomY) 346 } 347 348 @Test 349 fun testMultipleSpans() { 350 val rootSize = 200.dp 351 val boxesCount = 2 352 val rows = 2 353 val columns = 2 354 rule.setContent { 355 gridComposableTest( 356 modifier = Modifier.size(rootSize), 357 width = "'parent'", 358 height = "'parent'", 359 boxesCount = boxesCount, 360 orientation = 0, 361 rows = rows, 362 columns = columns, 363 hGap = 0, 364 vGap = 0, 365 spans = "'2:1x2,0:1x2'", 366 skips = "''", 367 rowWeights = "''", 368 columnWeights = "''", 369 flags = "'SpansRespectWidgetOrder'" 370 ) 371 } 372 var topY = 0.dp 373 var bottomY: Dp 374 375 // 10.dp is the size of a singular box 376 var spanLeft = (rootSize - 10.dp) / 2f 377 val gapSize = (rootSize - (10.dp * 2f)) / (columns * 2f) 378 rule.waitForIdle() 379 topY += gapSize 380 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(spanLeft, topY) 381 bottomY = topY + 10.dp + gapSize + gapSize 382 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(spanLeft, bottomY) 383 } 384 385 @Test 386 fun testOrderFirstSpans() { 387 val rootSize = 200.dp 388 val boxesCount = 3 389 val rows = 2 390 val columns = 2 391 rule.setContent { 392 gridComposableTest( 393 modifier = Modifier.size(rootSize), 394 width = "'parent'", 395 height = "'parent'", 396 boxesCount = boxesCount, 397 orientation = 0, 398 rows = rows, 399 columns = columns, 400 hGap = 0, 401 vGap = 0, 402 spans = "'1:2x1'", 403 skips = "''", 404 rowWeights = "''", 405 columnWeights = "''", 406 flags = "'SpansRespectWidgetOrder'" 407 ) 408 } 409 var leftX = 0.dp 410 var topY = 0.dp 411 var rightX: Dp 412 var bottomY: Dp 413 414 // 10.dp is the size of a singular box 415 var spanTop = (rootSize - 10.dp) / 2f 416 val gapSize = (rootSize - (10.dp * 2f)) / (columns * 2f) 417 rule.waitForIdle() 418 leftX += gapSize 419 topY += gapSize 420 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(leftX, topY) 421 rightX = leftX + 10.dp + gapSize + gapSize 422 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(rightX, spanTop) 423 bottomY = topY + 10.dp + gapSize + gapSize 424 rule.onNodeWithTag("box2").assertPositionInRootIsEqualTo(leftX, bottomY) 425 } 426 427 @Test 428 fun testReversedDirectionSpans() { 429 val rootSize = 200.dp 430 val boxesCount = 3 431 val rows = 2 432 val columns = 2 433 rule.setContent { 434 gridComposableTest( 435 modifier = Modifier.size(rootSize), 436 width = "'parent'", 437 height = "'parent'", 438 boxesCount = boxesCount, 439 orientation = 0, 440 rows = rows, 441 columns = columns, 442 hGap = 0, 443 vGap = 0, 444 spans = "'0:2x1'", 445 skips = "''", 446 rowWeights = "''", 447 columnWeights = "''", 448 flags = "'SubGridByColRow'" 449 ) 450 } 451 var leftX = 0.dp 452 var topY = 0.dp 453 var rightX: Dp 454 var bottomY: Dp 455 456 // 10.dp is the size of a singular box 457 var spanLeft = (rootSize - 10.dp) / 2f 458 val gapSize = (rootSize - (10.dp * 2f)) / (columns * 2f) 459 rule.waitForIdle() 460 leftX += gapSize 461 topY += gapSize 462 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(spanLeft, topY) 463 rightX = leftX + 10.dp + gapSize + gapSize 464 bottomY = topY + 10.dp + gapSize + gapSize 465 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(leftX, bottomY) 466 rule.onNodeWithTag("box2").assertPositionInRootIsEqualTo(rightX, bottomY) 467 } 468 469 @Test 470 fun testRowWeights() { 471 val rootSize = 200.dp 472 val boxesCount = 2 473 val rows = 0 474 val columns = 1 475 rule.setContent { 476 gridComposableTest( 477 modifier = Modifier.size(rootSize), 478 width = "'parent'", 479 height = "'parent'", 480 boxesCount = boxesCount, 481 orientation = 0, 482 rows = rows, 483 columns = columns, 484 hGap = 0, 485 vGap = 0, 486 spans = "''", 487 skips = "''", 488 rowWeights = "'1,3'", 489 columnWeights = "''", 490 flags = "''" 491 ) 492 } 493 var expectedLeft = (rootSize - 10.dp) / 2f 494 var expectedTop = 0.dp 495 496 // 10.dp is the size of a singular box 497 // first box takes the 1/4 of the height 498 val firstGapSize = (rootSize / 4 - 10.dp) / 2 499 // second box takes the 3/4 of the height 500 val secondGapSize = ((rootSize * 3 / 4) - 10.dp) / 2 501 rule.waitForIdle() 502 expectedTop += firstGapSize 503 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(expectedLeft, expectedTop) 504 expectedTop += 10.dp + firstGapSize + secondGapSize 505 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(expectedLeft, expectedTop) 506 } 507 508 @Test 509 fun testColumnWeights() { 510 val rootSize = 200.dp 511 val boxesCount = 2 512 val rows = 1 513 val columns = 0 514 rule.setContent { 515 gridComposableTest( 516 modifier = Modifier.size(rootSize), 517 width = "'parent'", 518 height = "'parent'", 519 boxesCount = boxesCount, 520 orientation = 0, 521 rows = rows, 522 columns = columns, 523 hGap = 0, 524 vGap = 0, 525 spans = "''", 526 skips = "''", 527 rowWeights = "''", 528 columnWeights = "'1,3'", 529 flags = "''" 530 ) 531 } 532 var expectedLeft = 0.dp 533 var expectedTop = (rootSize - 10.dp) / 2f 534 535 // 10.dp is the size of a singular box 536 // first box takes the 1/4 of the width 537 val firstGapSize = (rootSize / 4 - 10.dp) / 2 538 // second box takes the 3/4 of the width 539 val secondGapSize = ((rootSize * 3 / 4) - 10.dp) / 2 540 rule.waitForIdle() 541 expectedLeft += firstGapSize 542 543 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(expectedLeft, expectedTop) 544 expectedLeft += 10.dp + firstGapSize + secondGapSize 545 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(expectedLeft, expectedTop) 546 } 547 548 @Test 549 fun testGaps() { 550 val rootSize = 200.dp 551 val hGap = 10.dp 552 val vGap = 20.dp 553 rule.setContent { 554 gridComposableGapTest( 555 modifier = Modifier.size(rootSize), 556 width = "'parent'", 557 height = "'parent'", 558 hGap = Math.round(hGap.value), 559 vGap = Math.round(vGap.value), 560 ) 561 } 562 var expectedLeft = 0.dp 563 var expectedTop = 0.dp 564 565 val boxWidth = (rootSize - hGap) / 2f 566 val boxHeight = (rootSize - vGap) / 2f 567 568 rule.waitForIdle() 569 rule.onNodeWithTag("box0").assertPositionInRootIsEqualTo(0.dp, 0.dp) 570 expectedLeft += boxWidth + hGap 571 rule.onNodeWithTag("box1").assertPositionInRootIsEqualTo(expectedLeft, 0.dp) 572 expectedTop += boxHeight + vGap 573 rule.onNodeWithTag("box2").assertPositionInRootIsEqualTo(0.dp, expectedTop) 574 rule.onNodeWithTag("box3").assertPositionInRootIsEqualTo(expectedLeft, expectedTop) 575 } 576 577 @Composable 578 private fun gridComposableTest( 579 modifier: Modifier = Modifier, 580 width: String, 581 height: String, 582 rows: Int, 583 columns: Int, 584 spans: String, 585 skips: String, 586 rowWeights: String, 587 columnWeights: String, 588 boxesCount: Int, 589 orientation: Int, 590 vGap: Int, 591 hGap: Int, 592 flags: String, 593 ) { 594 val ids = (0 until boxesCount).map { "box$it" }.toTypedArray() 595 val gridContains = ids.joinToString(separator = ", ") { "'$it'" } 596 ConstraintLayout( 597 modifier = modifier, 598 constraintSet = 599 ConstraintSet( 600 """ 601 { 602 grid: { 603 width: $width, 604 height: $height, 605 type: 'grid', 606 rows: $rows, 607 columns $columns, 608 vGap: $vGap, 609 hGap: $hGap, 610 spans: $spans, 611 skips: $skips, 612 rowWeights: $rowWeights, 613 columnWeights: $columnWeights 614 orientation: $orientation, 615 contains: [$gridContains], 616 flags: $flags, 617 } 618 } 619 """ 620 .trimIndent() 621 ) 622 ) { 623 ids.forEach { id -> 624 Box(Modifier.layoutId(id).size(10.dp).background(Color.Red).testTag(id)) 625 } 626 } 627 } 628 629 @Composable 630 private fun gridComposableGapTest( 631 modifier: Modifier = Modifier, 632 width: String, 633 height: String, 634 vGap: Int, 635 hGap: Int, 636 ) { 637 val ids = (0 until 4).map { "box$it" }.toTypedArray() 638 val gridContains = ids.joinToString(separator = ", ") { "'$it'" } 639 640 ConstraintLayout( 641 modifier = modifier, 642 constraintSet = 643 ConstraintSet( 644 """ 645 { 646 grid: { 647 width: $width, 648 height: $height, 649 type: 'grid', 650 rows: 2, 651 columns: 2, 652 vGap: $vGap, 653 hGap: $hGap, 654 contains: [$gridContains], 655 }, 656 box0: { 657 width: 'spread', 658 height: 'spread', 659 }, 660 box1: { 661 width: 'spread', 662 height: 'spread', 663 }, 664 box2: { 665 width: 'spread', 666 height: 'spread', 667 }, 668 box3: { 669 width: 'spread', 670 height: 'spread', 671 } 672 } 673 """ 674 .trimIndent() 675 ) 676 ) { 677 ids.forEach { id -> Box(Modifier.layoutId(id).background(Color.Red).testTag(id)) } 678 } 679 } 680 } 681