1 /*
2  * Copyright 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 package androidx.wear.compose.material
17 
18 import android.os.Build
19 import androidx.compose.foundation.background
20 import androidx.compose.foundation.layout.Arrangement
21 import androidx.compose.foundation.layout.Box
22 import androidx.compose.foundation.layout.BoxScope
23 import androidx.compose.foundation.layout.Row
24 import androidx.compose.foundation.layout.Spacer
25 import androidx.compose.foundation.layout.fillMaxSize
26 import androidx.compose.foundation.layout.fillMaxWidth
27 import androidx.compose.foundation.layout.height
28 import androidx.compose.foundation.layout.offset
29 import androidx.compose.foundation.layout.size
30 import androidx.compose.foundation.layout.width
31 import androidx.compose.foundation.layout.wrapContentSize
32 import androidx.compose.runtime.Composable
33 import androidx.compose.runtime.CompositionLocalProvider
34 import androidx.compose.testutils.assertAgainstGolden
35 import androidx.compose.ui.Alignment
36 import androidx.compose.ui.Modifier
37 import androidx.compose.ui.graphics.Color
38 import androidx.compose.ui.platform.LocalLayoutDirection
39 import androidx.compose.ui.platform.testTag
40 import androidx.compose.ui.test.captureToImage
41 import androidx.compose.ui.test.junit4.createComposeRule
42 import androidx.compose.ui.test.onNodeWithTag
43 import androidx.compose.ui.unit.Dp
44 import androidx.compose.ui.unit.LayoutDirection
45 import androidx.compose.ui.unit.dp
46 import androidx.test.ext.junit.runners.AndroidJUnit4
47 import androidx.test.filters.MediumTest
48 import androidx.test.filters.SdkSuppress
49 import androidx.test.screenshot.AndroidXScreenshotTestRule
50 import org.junit.Rule
51 import org.junit.Test
52 import org.junit.rules.TestName
53 import org.junit.runner.RunWith
54 
55 @MediumTest
56 @RunWith(AndroidJUnit4::class)
57 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
58 class PickerScreenshotTest {
59 
60     @get:Rule val rule = createComposeRule()
61 
62     @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
63 
64     @get:Rule val testName = TestName()
65 
66     private val screenHeight = 150.dp
67 
<lambda>null68     @Test fun picker() = verifyScreenshot { samplePicker() }
69 
picker_without_gradientnull70     @Test fun picker_without_gradient() = verifyScreenshot { samplePicker(gradientRatio = 0f) }
71 
<lambda>null72     @Test fun picker_negative_separation() = verifyScreenshot { samplePicker(separation = -8.dp) }
73 
<lambda>null74     @Test fun dual_picker() = verifyScreenshot { dualPicker() }
75 
76     @Test
<lambda>null77     fun dual_picker_with_readonlylabel() = verifyScreenshot { dualPicker(readOnlyLabel = "Min") }
78 
79     @Composable
samplePickernull80     private fun samplePicker(
81         gradientRatio: Float = PickerDefaults.DefaultGradientRatio,
82         separation: Dp = 0.dp,
83     ) {
84         Box(
85             modifier =
86                 Modifier.height(screenHeight)
87                     .fillMaxWidth()
88                     .background(MaterialTheme.colors.background),
89             contentAlignment = Alignment.Center
90         ) {
91             val items = listOf("One", "Two", "Three", "Four", "Five")
92             val state = rememberPickerState(items.size)
93             Picker(
94                 modifier = Modifier.fillMaxSize().testTag(TEST_TAG),
95                 state = state,
96                 gradientRatio = gradientRatio,
97                 separation = separation,
98                 contentDescription = "",
99             ) {
100                 Text(items[it])
101             }
102         }
103     }
104 
105     @Composable
dualPickernull106     private fun dualPicker(readOnlyLabel: String? = null) {
107         // This test verifies read-only mode alongside an 'editable' Picker.
108         val textStyle = MaterialTheme.typography.display1
109 
110         @Composable
111         fun Option(color: Color, text: String) =
112             Box(modifier = Modifier.fillMaxSize()) {
113                 Text(
114                     text = text,
115                     style = textStyle,
116                     color = color,
117                     modifier = Modifier.align(Alignment.Center).wrapContentSize()
118                 )
119             }
120 
121         Row(
122             modifier =
123                 Modifier.height(screenHeight)
124                     .fillMaxWidth()
125                     .background(MaterialTheme.colors.background)
126                     .testTag(TEST_TAG),
127             verticalAlignment = Alignment.CenterVertically,
128             horizontalArrangement = Arrangement.Center,
129         ) {
130             Picker(
131                 state =
132                     rememberPickerState(initialNumberOfOptions = 100, initiallySelectedOption = 11),
133                 contentDescription = "",
134                 readOnly = false,
135                 modifier = Modifier.size(64.dp, 100.dp),
136                 option = { Option(MaterialTheme.colors.secondary, "%2d".format(it)) }
137             )
138             Spacer(Modifier.width(8.dp))
139             Text(text = ":", style = textStyle, color = MaterialTheme.colors.onBackground)
140             Spacer(Modifier.width(8.dp))
141             Picker(
142                 state =
143                     rememberPickerState(
144                         initialNumberOfOptions = 100,
145                         initiallySelectedOption = 100
146                     ),
147                 contentDescription = "",
148                 readOnly = true,
149                 readOnlyLabel = { if (readOnlyLabel != null) LabelText(readOnlyLabel) },
150                 modifier = Modifier.size(64.dp, 100.dp),
151                 option = { Option(MaterialTheme.colors.onBackground, "%02d".format(it)) }
152             )
153         }
154     }
155 
156     @Composable
LabelTextnull157     private fun BoxScope.LabelText(text: String) {
158         Text(
159             text = text,
160             style = MaterialTheme.typography.caption1,
161             color = MaterialTheme.colors.onSurfaceVariant,
162             modifier = Modifier.align(Alignment.TopCenter).offset(y = 8.dp)
163         )
164     }
165 
verifyScreenshotnull166     private fun verifyScreenshot(
167         layoutDirection: LayoutDirection = LayoutDirection.Ltr,
168         content: @Composable () -> Unit
169     ) {
170         rule.setContentWithTheme {
171             CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) { content() }
172         }
173 
174         rule
175             .onNodeWithTag(TEST_TAG)
176             .captureToImage()
177             .assertAgainstGolden(screenshotRule, testName.methodName)
178     }
179 }
180