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