[go: nahoru, domu]

blob: 73a523304dcadac306d5be03c2e71b760a06ad9d [file] [log] [blame]
Matvei Malkov2d37cbd2019-07-04 15:15:20 +01001/*
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 */
16
17package androidx.ui.material
18
Jelle Fresen11fc7062020-01-13 16:53:55 +000019import android.os.SystemClock.sleep
Matvei Malkov7aa7fd22019-08-16 16:52:59 +010020import androidx.compose.Model
Louis Pullen-Freiliche46bcf02020-02-12 16:06:21 +000021import androidx.compose.emptyContent
Matvei Malkov2d37cbd2019-07-04 15:15:20 +010022import androidx.test.filters.MediumTest
Adam Powell999a89b2020-03-11 09:08:07 -070023import androidx.ui.core.LayoutCoordinates
24import androidx.ui.core.Modifier
Matvei Malkov7aa7fd22019-08-16 16:52:59 +010025import androidx.ui.core.TestTag
George Mountd02af602020-03-06 16:41:40 -080026import androidx.ui.core.onPositioned
Matvei Malkov201726d2020-03-13 14:03:54 +000027import androidx.ui.foundation.Box
Matvei Malkov7aa7fd22019-08-16 16:52:59 +010028import androidx.ui.foundation.Clickable
Adam Powell999a89b2020-03-11 09:08:07 -070029import androidx.ui.layout.fillMaxSize
Matvei Malkov7aa7fd22019-08-16 16:52:59 +010030import androidx.ui.semantics.Semantics
Matvei Malkov2d37cbd2019-07-04 15:15:20 +010031import androidx.ui.test.createComposeRule
Jelle Fresen11fc7062020-01-13 16:53:55 +000032import androidx.ui.test.doGesture
Matvei Malkov7aa7fd22019-08-16 16:52:59 +010033import androidx.ui.test.findByTag
Jelle Fresen11fc7062020-01-13 16:53:55 +000034import androidx.ui.test.globalBounds
Filip Pavlis6fdb2502020-04-02 14:41:32 +010035import androidx.ui.test.runOnIdleCompose
36import androidx.ui.test.runOnUiThread
Jelle Fresen11fc7062020-01-13 16:53:55 +000037import androidx.ui.test.sendClick
George Mount8baef7a2020-01-21 13:40:57 -080038import androidx.ui.unit.IntPx
39import androidx.ui.unit.IntPxSize
George Mount842c8c12020-01-08 16:03:42 -080040import androidx.ui.unit.PxPosition
George Mount842c8c12020-01-08 16:03:42 -080041import androidx.ui.unit.dp
Ryan Mentley7865a632019-08-20 20:10:29 -070042import androidx.ui.unit.height
Jelle Fresen11fc7062020-01-13 16:53:55 +000043import androidx.ui.unit.px
George Mount842c8c12020-01-08 16:03:42 -080044import androidx.ui.unit.round
Ryan Mentley7865a632019-08-20 20:10:29 -070045import androidx.ui.unit.width
Filip Pavlise63b30c2020-01-09 16:21:07 +000046import com.google.common.truth.Truth.assertThat
Matvei Malkov886ec6a2020-02-03 01:45:29 +000047import org.junit.Ignore
Matvei Malkov2d37cbd2019-07-04 15:15:20 +010048import org.junit.Rule
49import org.junit.Test
50import org.junit.runner.RunWith
51import org.junit.runners.JUnit4
Jelle Fresen4c566312020-01-16 10:32:40 +000052import java.util.concurrent.CountDownLatch
53import java.util.concurrent.TimeUnit
Matvei Malkov2d37cbd2019-07-04 15:15:20 +010054import kotlin.math.roundToInt
55
Matvei Malkov7aa7fd22019-08-16 16:52:59 +010056@Model
57data class DrawerStateHolder(var state: DrawerState)
58
Matvei Malkov2d37cbd2019-07-04 15:15:20 +010059@MediumTest
60@RunWith(JUnit4::class)
61class DrawerTest {
62
63 @get:Rule
Matvei Malkov7aa7fd22019-08-16 16:52:59 +010064 val composeTestRule = createComposeRule(disableTransitions = true)
Matvei Malkov2d37cbd2019-07-04 15:15:20 +010065
66 @Test
67 fun modalDrawer_testOffset_whenOpened() {
68 var position: PxPosition? = null
69 composeTestRule.setMaterialContent {
Matvei Malkov7aa7fd22019-08-16 16:52:59 +010070 ModalDrawerLayout(DrawerState.Opened, {}, drawerContent = {
Adam Powell999a89b2020-03-11 09:08:07 -070071 Box(Modifier.fillMaxSize().onPositioned { coords: LayoutCoordinates ->
Matvei Malkov201726d2020-03-13 14:03:54 +000072 position = coords.localToGlobal(PxPosition.Origin)
73 })
Louis Pullen-Freiliche46bcf02020-02-12 16:06:21 +000074 }, bodyContent = emptyContent())
Matvei Malkov2d37cbd2019-07-04 15:15:20 +010075 }
Filip Pavlis6fdb2502020-04-02 14:41:32 +010076 runOnIdleCompose {
Filip Pavlise63b30c2020-01-09 16:21:07 +000077 assertThat(position!!.x.value).isEqualTo(0f)
78 }
Matvei Malkov2d37cbd2019-07-04 15:15:20 +010079 }
80
81 @Test
82 fun modalDrawer_testOffset_whenClosed() {
83 var position: PxPosition? = null
84 composeTestRule.setMaterialContent {
Matvei Malkov7aa7fd22019-08-16 16:52:59 +010085 ModalDrawerLayout(DrawerState.Closed, {}, drawerContent = {
Adam Powell999a89b2020-03-11 09:08:07 -070086 Box(Modifier.fillMaxSize().onPositioned { coords: LayoutCoordinates ->
Matvei Malkov201726d2020-03-13 14:03:54 +000087 position = coords.localToGlobal(PxPosition.Origin)
88 })
Louis Pullen-Freiliche46bcf02020-02-12 16:06:21 +000089 }, bodyContent = emptyContent())
Matvei Malkov2d37cbd2019-07-04 15:15:20 +010090 }
91 val width = composeTestRule.displayMetrics.widthPixels
Filip Pavlis6fdb2502020-04-02 14:41:32 +010092 runOnIdleCompose {
Filip Pavlise63b30c2020-01-09 16:21:07 +000093 assertThat(position!!.x.round().value).isEqualTo(-width)
94 }
Matvei Malkov2d37cbd2019-07-04 15:15:20 +010095 }
96
97 @Test
98 fun modalDrawer_testEndPadding_whenOpened() {
George Mount8baef7a2020-01-21 13:40:57 -080099 var size: IntPxSize? = null
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100100 composeTestRule.setMaterialContent {
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100101 ModalDrawerLayout(DrawerState.Opened, {}, drawerContent = {
Adam Powell999a89b2020-03-11 09:08:07 -0700102 Box(Modifier.fillMaxSize().onPositioned { coords: LayoutCoordinates ->
103 size = coords.size
104 })
Louis Pullen-Freiliche46bcf02020-02-12 16:06:21 +0000105 }, bodyContent = emptyContent())
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100106 }
107
108 val width = composeTestRule.displayMetrics.widthPixels
Filip Pavlise63b30c2020-01-09 16:21:07 +0000109 composeTestRule.runOnIdleComposeWithDensity {
George Mount8baef7a2020-01-21 13:40:57 -0800110 assertThat(size!!.width.value)
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100111 .isEqualTo(width - 56.dp.toPx().round().value)
112 }
113 }
114
115 @Test
116 fun bottomDrawer_testOffset_whenOpened() {
117 var position: PxPosition? = null
118 composeTestRule.setMaterialContent {
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100119 BottomDrawerLayout(DrawerState.Opened, {}, drawerContent = {
Adam Powell999a89b2020-03-11 09:08:07 -0700120 Box(Modifier.fillMaxSize().onPositioned { coords: LayoutCoordinates ->
Matvei Malkov201726d2020-03-13 14:03:54 +0000121 position = coords.localToGlobal(PxPosition.Origin)
122 })
Louis Pullen-Freiliche46bcf02020-02-12 16:06:21 +0000123 }, bodyContent = emptyContent())
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100124 }
Filip Pavlise63b30c2020-01-09 16:21:07 +0000125
Matvei Malkov2270be12019-07-08 16:59:45 +0100126 val width = composeTestRule.displayMetrics.widthPixels
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100127 val height = composeTestRule.displayMetrics.heightPixels
Matvei Malkov2270be12019-07-08 16:59:45 +0100128 // temporary calculation of landscape screen
Matvei Malkovb5e3d8a2020-03-09 15:17:20 +0000129 val expectedHeight = if (width > height) 0 else (height / 2f).roundToInt()
Filip Pavlis6fdb2502020-04-02 14:41:32 +0100130 runOnIdleCompose {
Filip Pavlise63b30c2020-01-09 16:21:07 +0000131 assertThat(position!!.y.round().value).isEqualTo(expectedHeight)
132 }
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100133 }
134
135 @Test
136 fun bottomDrawer_testOffset_whenClosed() {
137 var position: PxPosition? = null
138 composeTestRule.setMaterialContent {
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100139 BottomDrawerLayout(DrawerState.Closed, {}, drawerContent = {
Adam Powell999a89b2020-03-11 09:08:07 -0700140 Box(Modifier.fillMaxSize().onPositioned { coords: LayoutCoordinates ->
Matvei Malkov201726d2020-03-13 14:03:54 +0000141 position = coords.localToGlobal(PxPosition.Origin)
142 })
Louis Pullen-Freiliche46bcf02020-02-12 16:06:21 +0000143 }, bodyContent = emptyContent())
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100144 }
145 val height = composeTestRule.displayMetrics.heightPixels
Filip Pavlis6fdb2502020-04-02 14:41:32 +0100146 runOnIdleCompose {
Filip Pavlise63b30c2020-01-09 16:21:07 +0000147 assertThat(position!!.y.round().value).isEqualTo(height)
148 }
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100149 }
150
151 @Test
152 fun staticDrawer_testWidth_whenOpened() {
153 composeTestRule
Matvei Malkov804bee42019-07-16 16:24:06 +0100154 .setMaterialContentAndCollectSizes {
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100155 StaticDrawer {
Adam Powell999a89b2020-03-11 09:08:07 -0700156 Box(Modifier.fillMaxSize())
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100157 }
158 }
159 .assertWidthEqualsTo(256.dp)
160 }
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100161
162 @Test
Matvei Malkov886ec6a2020-02-03 01:45:29 +0000163 @Ignore("failing in postsubmit, fix in b/148751721")
Jelle Fresen4c566312020-01-16 10:32:40 +0000164 fun modalDrawer_openAndClose() {
George Mount8baef7a2020-01-21 13:40:57 -0800165 var contentWidth: IntPx? = null
Jelle Fresen4c566312020-01-16 10:32:40 +0000166 var openedLatch: CountDownLatch? = null
167 var closedLatch: CountDownLatch? = CountDownLatch(1)
168 val drawerState = DrawerStateHolder(DrawerState.Closed)
169 composeTestRule.setMaterialContent {
170 TestTag("Drawer") {
171 Semantics(container = true) {
172 ModalDrawerLayout(drawerState.state, { drawerState.state = it },
173 drawerContent = {
Matvei Malkov201726d2020-03-13 14:03:54 +0000174 Box(
Adam Powell999a89b2020-03-11 09:08:07 -0700175 Modifier.fillMaxSize().onPositioned { info: LayoutCoordinates ->
George Mountb47c3b02020-03-10 23:26:42 -0700176 val pos = info.localToGlobal(PxPosition.Origin)
177 if (pos.x == 0.px) {
178 // If fully opened, mark the openedLatch if present
179 openedLatch?.countDown()
180 } else if (-pos.x.round() == contentWidth) {
181 // If fully closed, mark the closedLatch if present
182 closedLatch?.countDown()
183 }
Jelle Fresen4c566312020-01-16 10:32:40 +0000184 }
George Mountb47c3b02020-03-10 23:26:42 -0700185 )
Jelle Fresen4c566312020-01-16 10:32:40 +0000186 },
187 bodyContent = {
Adam Powell999a89b2020-03-11 09:08:07 -0700188 Box(Modifier.fillMaxSize()
189 .onPositioned { contentWidth = it.size.width })
Jelle Fresen4c566312020-01-16 10:32:40 +0000190 })
191 }
192 }
193 }
194 // Drawer should start in closed state
195 assertThat(closedLatch!!.await(5, TimeUnit.SECONDS)).isTrue()
196
197 // When the drawer state is set to Opened
198 openedLatch = CountDownLatch(1)
Filip Pavlis6fdb2502020-04-02 14:41:32 +0100199 runOnIdleCompose {
Jelle Fresen4c566312020-01-16 10:32:40 +0000200 drawerState.state = DrawerState.Opened
201 }
202 // Then the drawer should be opened
203 assertThat(openedLatch.await(5, TimeUnit.SECONDS)).isTrue()
204
205 // When the drawer state is set to Closed
206 closedLatch = CountDownLatch(1)
Filip Pavlis6fdb2502020-04-02 14:41:32 +0100207 runOnIdleCompose {
Jelle Fresen4c566312020-01-16 10:32:40 +0000208 drawerState.state = DrawerState.Closed
209 }
210 // Then the drawer should be closed
211 assertThat(closedLatch.await(5, TimeUnit.SECONDS)).isTrue()
212 }
213
214 @Test
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100215 fun modalDrawer_bodyContent_clickable() {
216 var drawerClicks = 0
217 var bodyClicks = 0
218 val drawerState = DrawerStateHolder(DrawerState.Closed)
219 composeTestRule.setMaterialContent {
Aurimas Liutikas7a828d32019-10-07 17:16:05 -0700220 // emulate click on the screen
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100221 TestTag("Drawer") {
222 Semantics(container = true) {
223 ModalDrawerLayout(drawerState.state, { drawerState.state = it },
224 drawerContent = {
225 Clickable(onClick = { drawerClicks += 1 }) {
Adam Powell999a89b2020-03-11 09:08:07 -0700226 Box(Modifier.fillMaxSize(), children = emptyContent())
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100227 }
228 },
229 bodyContent = {
230 Clickable(onClick = { bodyClicks += 1 }) {
Adam Powell999a89b2020-03-11 09:08:07 -0700231 Box(Modifier.fillMaxSize(), children = emptyContent())
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100232 }
233 })
234 }
235 }
236 }
237
Jelle Fresen11fc7062020-01-13 16:53:55 +0000238 // Click in the middle of the drawer (which is the middle of the body)
239 findByTag("Drawer").doGesture { sendClick() }
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100240
Filip Pavlis6fdb2502020-04-02 14:41:32 +0100241 runOnIdleCompose {
Filip Pavlise63b30c2020-01-09 16:21:07 +0000242 assertThat(drawerClicks).isEqualTo(0)
243 assertThat(bodyClicks).isEqualTo(1)
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100244
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100245 drawerState.state = DrawerState.Opened
246 }
Jelle Fresen11fc7062020-01-13 16:53:55 +0000247 sleep(100) // TODO(147586311): remove this sleep when opening the drawer triggers a wait
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100248
Jelle Fresen11fc7062020-01-13 16:53:55 +0000249 // Click on the left-center pixel of the drawer
250 findByTag("Drawer").doGesture {
Ryan Mentley7865a632019-08-20 20:10:29 -0700251 val left = 1.px
Jelle Fresen11fc7062020-01-13 16:53:55 +0000252 val centerY = globalBounds.height / 2
Ryan Mentley7865a632019-08-20 20:10:29 -0700253 sendClick(PxPosition(left, centerY))
Jelle Fresen11fc7062020-01-13 16:53:55 +0000254 }
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100255
Filip Pavlis6fdb2502020-04-02 14:41:32 +0100256 runOnIdleCompose {
Filip Pavlise63b30c2020-01-09 16:21:07 +0000257 assertThat(drawerClicks).isEqualTo(1)
258 assertThat(bodyClicks).isEqualTo(1)
Filip Pavlis2b161e42019-11-22 17:40:08 +0000259 }
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100260 }
261
262 @Test
Matvei Malkov886ec6a2020-02-03 01:45:29 +0000263 @Ignore("failing in postsubmit, fix in b/148751721")
Jelle Fresen4c566312020-01-16 10:32:40 +0000264 fun bottomDrawer_openAndClose() {
George Mount8baef7a2020-01-21 13:40:57 -0800265 var contentHeight: IntPx? = null
266 var openedHeight: IntPx? = null
Jelle Fresen4c566312020-01-16 10:32:40 +0000267 var openedLatch: CountDownLatch? = null
268 var closedLatch: CountDownLatch? = CountDownLatch(1)
269 val drawerState = DrawerStateHolder(DrawerState.Closed)
270 composeTestRule.setMaterialContent {
271 TestTag("Drawer") {
272 Semantics(container = true) {
273 BottomDrawerLayout(drawerState.state, { drawerState.state = it },
274 drawerContent = {
Adam Powell999a89b2020-03-11 09:08:07 -0700275 Box(Modifier.fillMaxSize().onPositioned { info: LayoutCoordinates ->
Matvei Malkov201726d2020-03-13 14:03:54 +0000276 val pos = info.localToGlobal(PxPosition.Origin)
277 if (pos.y.round() == openedHeight) {
278 // If fully opened, mark the openedLatch if present
279 openedLatch?.countDown()
280 } else if (pos.y.round() == contentHeight) {
281 // If fully closed, mark the closedLatch if present
282 closedLatch?.countDown()
Jelle Fresen4c566312020-01-16 10:32:40 +0000283 }
Matvei Malkov201726d2020-03-13 14:03:54 +0000284 })
Jelle Fresen4c566312020-01-16 10:32:40 +0000285 },
286 bodyContent = {
Adam Powell999a89b2020-03-11 09:08:07 -0700287 Box(Modifier.fillMaxSize().onPositioned {
Matvei Malkov201726d2020-03-13 14:03:54 +0000288 contentHeight = it.size.height
289 openedHeight = it.size.height * BottomDrawerOpenFraction
290 })
George Mountb47c3b02020-03-10 23:26:42 -0700291 }
292 )
Jelle Fresen4c566312020-01-16 10:32:40 +0000293 }
294 }
295 }
296 // Drawer should start in closed state
297 assertThat(closedLatch!!.await(5, TimeUnit.SECONDS)).isTrue()
298
299 // When the drawer state is set to Opened
300 openedLatch = CountDownLatch(1)
Filip Pavlis6fdb2502020-04-02 14:41:32 +0100301 runOnIdleCompose {
Jelle Fresen4c566312020-01-16 10:32:40 +0000302 drawerState.state = DrawerState.Opened
303 }
304 // Then the drawer should be opened
305 assertThat(openedLatch.await(5, TimeUnit.SECONDS)).isTrue()
306
307 // When the drawer state is set to Closed
308 closedLatch = CountDownLatch(1)
Filip Pavlis6fdb2502020-04-02 14:41:32 +0100309 runOnIdleCompose {
Jelle Fresen4c566312020-01-16 10:32:40 +0000310 drawerState.state = DrawerState.Closed
311 }
312 // Then the drawer should be closed
313 assertThat(closedLatch.await(5, TimeUnit.SECONDS)).isTrue()
314 }
315
316 @Test
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100317 fun bottomDrawer_bodyContent_clickable() {
318 var drawerClicks = 0
319 var bodyClicks = 0
320 val drawerState = DrawerStateHolder(DrawerState.Closed)
321 composeTestRule.setMaterialContent {
Aurimas Liutikas7a828d32019-10-07 17:16:05 -0700322 // emulate click on the screen
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100323 TestTag("Drawer") {
324 Semantics(container = true) {
325 BottomDrawerLayout(drawerState.state, { drawerState.state = it },
326 drawerContent = {
327 Clickable(onClick = { drawerClicks += 1 }) {
Adam Powell999a89b2020-03-11 09:08:07 -0700328 Box(Modifier.fillMaxSize(), children = emptyContent())
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100329 }
330 },
331 bodyContent = {
332 Clickable(onClick = { bodyClicks += 1 }) {
Adam Powell999a89b2020-03-11 09:08:07 -0700333 Box(Modifier.fillMaxSize(), children = emptyContent())
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100334 }
335 })
336 }
337 }
338 }
339
Jelle Fresen11fc7062020-01-13 16:53:55 +0000340 // Click in the middle of the drawer (which is the middle of the body)
341 findByTag("Drawer").doGesture { sendClick() }
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100342
Filip Pavlis6fdb2502020-04-02 14:41:32 +0100343 runOnIdleCompose {
Filip Pavlise63b30c2020-01-09 16:21:07 +0000344 assertThat(drawerClicks).isEqualTo(0)
345 assertThat(bodyClicks).isEqualTo(1)
346 }
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100347
Filip Pavlis6fdb2502020-04-02 14:41:32 +0100348 runOnUiThread {
Jelle Fresen11fc7062020-01-13 16:53:55 +0000349 drawerState.state = DrawerState.Opened
350 }
351 sleep(100) // TODO(147586311): remove this sleep when opening the drawer triggers a wait
352
353 // Click on the bottom-center pixel of the drawer
354 findByTag("Drawer").doGesture {
355 val bounds = globalBounds
356 val centerX = bounds.width / 2
Ryan Mentley7865a632019-08-20 20:10:29 -0700357 val bottom = bounds.height - 1.px
358 sendClick(PxPosition(centerX, bottom))
Jelle Fresen11fc7062020-01-13 16:53:55 +0000359 }
360
361 assertThat(drawerClicks).isEqualTo(1)
362 assertThat(bodyClicks).isEqualTo(1)
Matvei Malkov7aa7fd22019-08-16 16:52:59 +0100363 }
Matvei Malkov2d37cbd2019-07-04 15:15:20 +0100364}