1 /* <lambda>null2 * Copyright (C) 2024 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 com.android.compose.animation 18 19 import androidx.compose.foundation.gestures.Orientation 20 import androidx.compose.foundation.layout.Box 21 import androidx.compose.foundation.layout.Column 22 import androidx.compose.foundation.layout.Row 23 import androidx.compose.foundation.layout.fillMaxHeight 24 import androidx.compose.foundation.layout.fillMaxWidth 25 import androidx.compose.foundation.layout.size 26 import androidx.compose.runtime.getValue 27 import androidx.compose.runtime.mutableStateOf 28 import androidx.compose.runtime.setValue 29 import androidx.compose.ui.Modifier 30 import androidx.compose.ui.platform.testTag 31 import androidx.compose.ui.test.assertHeightIsEqualTo 32 import androidx.compose.ui.test.assertPositionInRootIsEqualTo 33 import androidx.compose.ui.test.assertWidthIsEqualTo 34 import androidx.compose.ui.test.junit4.createComposeRule 35 import androidx.compose.ui.test.onNodeWithTag 36 import androidx.compose.ui.unit.Dp 37 import androidx.compose.ui.unit.dp 38 import androidx.compose.ui.unit.times 39 import androidx.test.ext.junit.runners.AndroidJUnit4 40 import org.junit.Rule 41 import org.junit.Test 42 import org.junit.runner.RunWith 43 44 @RunWith(AndroidJUnit4::class) 45 class BounceableTest { 46 @get:Rule val rule = createComposeRule() 47 48 @Test 49 fun bounceable_horizontal() { 50 var bounceables by mutableStateOf(List(4) { bounceable(0.dp) }) 51 52 rule.setContent { 53 Row(Modifier.size(100.dp, 50.dp)) { 54 repeat(bounceables.size) { i -> 55 Box( 56 Modifier.weight(1f) 57 .fillMaxHeight() 58 .bounceable(bounceables, i, orientation = Orientation.Horizontal) 59 ) 60 } 61 } 62 } 63 64 // All bounceables have a width of (100dp / bounceables.size) = 25dp and height of 50dp. 65 repeat(bounceables.size) { i -> 66 rule 67 .onNodeWithTag(bounceableTag(i)) 68 .assertWidthIsEqualTo(25.dp) 69 .assertHeightIsEqualTo(50.dp) 70 .assertPositionInRootIsEqualTo(i * 25.dp, 0.dp) 71 } 72 73 // If all bounceables have the same bounce, it's the same as if they didn't have any. 74 bounceables = List(4) { bounceable(10.dp) } 75 repeat(bounceables.size) { i -> 76 rule 77 .onNodeWithTag(bounceableTag(i)) 78 .assertWidthIsEqualTo(25.dp) 79 .assertHeightIsEqualTo(50.dp) 80 .assertPositionInRootIsEqualTo(i * 25.dp, 0.dp) 81 } 82 83 // Bounce the first and third one. 84 bounceables = 85 listOf( 86 bounceable(bounce = 5.dp), 87 bounceable(bounce = 0.dp), 88 bounceable(bounce = 10.dp), 89 bounceable(bounce = 0.dp), 90 ) 91 92 // First one has a width of 25dp + 5dp, located in (0, 0). 93 rule 94 .onNodeWithTag(bounceableTag(0)) 95 .assertWidthIsEqualTo(30.dp) 96 .assertHeightIsEqualTo(50.dp) 97 .assertPositionInRootIsEqualTo(0.dp, 0.dp) 98 99 // Second one has a width of 25dp - 5dp - 10dp, located in (30, 0). 100 rule 101 .onNodeWithTag(bounceableTag(1)) 102 .assertWidthIsEqualTo(10.dp) 103 .assertHeightIsEqualTo(50.dp) 104 .assertPositionInRootIsEqualTo(30.dp, 0.dp) 105 106 // Third one has a width of 25 + 2 * 10dp, located in (40, 0). 107 rule 108 .onNodeWithTag(bounceableTag(2)) 109 .assertWidthIsEqualTo(45.dp) 110 .assertHeightIsEqualTo(50.dp) 111 .assertPositionInRootIsEqualTo(40.dp, 0.dp) 112 113 // First one has a width of 25dp - 10dp, located in (85, 0). 114 rule 115 .onNodeWithTag(bounceableTag(3)) 116 .assertWidthIsEqualTo(15.dp) 117 .assertHeightIsEqualTo(50.dp) 118 .assertPositionInRootIsEqualTo(85.dp, 0.dp) 119 } 120 121 @Test 122 fun bounceable_vertical() { 123 var bounceables by mutableStateOf(List(4) { bounceable(0.dp) }) 124 125 rule.setContent { 126 Column(Modifier.size(50.dp, 100.dp)) { 127 repeat(bounceables.size) { i -> 128 Box( 129 Modifier.weight(1f) 130 .fillMaxWidth() 131 .bounceable(bounceables, i, Orientation.Vertical) 132 ) 133 } 134 } 135 } 136 137 // All bounceables have a height of (100dp / bounceables.size) = 25dp and width of 50dp. 138 repeat(bounceables.size) { i -> 139 rule 140 .onNodeWithTag(bounceableTag(i)) 141 .assertWidthIsEqualTo(50.dp) 142 .assertHeightIsEqualTo(25.dp) 143 .assertPositionInRootIsEqualTo(0.dp, i * 25.dp) 144 } 145 146 // If all bounceables have the same bounce, it's the same as if they didn't have any. 147 bounceables = List(4) { bounceable(10.dp) } 148 repeat(bounceables.size) { i -> 149 rule 150 .onNodeWithTag(bounceableTag(i)) 151 .assertWidthIsEqualTo(50.dp) 152 .assertHeightIsEqualTo(25.dp) 153 .assertPositionInRootIsEqualTo(0.dp, i * 25.dp) 154 } 155 156 // Bounce the first and third one. 157 bounceables = 158 listOf( 159 bounceable(bounce = 5.dp), 160 bounceable(bounce = 0.dp), 161 bounceable(bounce = 10.dp), 162 bounceable(bounce = 0.dp), 163 ) 164 165 // First one has a height of 25dp + 5dp, located in (0, 0). 166 rule 167 .onNodeWithTag(bounceableTag(0)) 168 .assertWidthIsEqualTo(50.dp) 169 .assertHeightIsEqualTo(30.dp) 170 .assertPositionInRootIsEqualTo(0.dp, 0.dp) 171 172 // Second one has a height of 25dp - 5dp - 10dp, located in (0, 30). 173 rule 174 .onNodeWithTag(bounceableTag(1)) 175 .assertWidthIsEqualTo(50.dp) 176 .assertHeightIsEqualTo(10.dp) 177 .assertPositionInRootIsEqualTo(0.dp, 30.dp) 178 179 // Third one has a height of 25 + 2 * 10dp, located in (0, 40). 180 rule 181 .onNodeWithTag(bounceableTag(2)) 182 .assertWidthIsEqualTo(50.dp) 183 .assertHeightIsEqualTo(45.dp) 184 .assertPositionInRootIsEqualTo(0.dp, 40.dp) 185 186 // First one has a height of 25dp - 10dp, located in (0, 85). 187 rule 188 .onNodeWithTag(bounceableTag(3)) 189 .assertWidthIsEqualTo(50.dp) 190 .assertHeightIsEqualTo(15.dp) 191 .assertPositionInRootIsEqualTo(0.dp, 85.dp) 192 } 193 194 private fun bounceable(bounce: Dp): Bounceable { 195 return object : Bounceable { 196 override val bounce: Dp = bounce 197 } 198 } 199 200 private fun Modifier.bounceable( 201 bounceables: List<Bounceable>, 202 i: Int, 203 orientation: Orientation, 204 ): Modifier { 205 val previous = if (i > 0) bounceables[i - 1] else null 206 val next = if (i < bounceables.lastIndex) bounceables[i + 1] else null 207 return this.bounceable(bounceables[i], previous, next, orientation) 208 .testTag(bounceableTag(i)) 209 } 210 211 private fun bounceableTag(i: Int) = "bounceable$i" 212 } 213