1 /* <lambda>null2 * 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 * https://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.google.accompanist.testharness 18 19 import android.content.res.Configuration 20 import android.content.res.Configuration.UI_MODE_NIGHT_MASK 21 import android.content.res.Configuration.UI_MODE_NIGHT_NO 22 import android.content.res.Configuration.UI_MODE_NIGHT_YES 23 import androidx.activity.ComponentActivity 24 import androidx.compose.foundation.background 25 import androidx.compose.foundation.layout.Box 26 import androidx.compose.foundation.layout.requiredSize 27 import androidx.compose.foundation.layout.size 28 import androidx.compose.foundation.text.BasicText 29 import androidx.compose.runtime.Composable 30 import androidx.compose.runtime.CompositionLocalProvider 31 import androidx.compose.ui.Modifier 32 import androidx.compose.ui.graphics.Color 33 import androidx.compose.ui.layout.LayoutCoordinates 34 import androidx.compose.ui.layout.onGloballyPositioned 35 import androidx.compose.ui.platform.LocalConfiguration 36 import androidx.compose.ui.platform.LocalDensity 37 import androidx.compose.ui.platform.LocalLayoutDirection 38 import androidx.compose.ui.res.stringResource 39 import androidx.compose.ui.test.junit4.createAndroidComposeRule 40 import androidx.compose.ui.test.onNodeWithText 41 import androidx.compose.ui.unit.Dp 42 import androidx.compose.ui.unit.DpSize 43 import androidx.compose.ui.unit.LayoutDirection 44 import androidx.compose.ui.unit.dp 45 import androidx.core.os.LocaleListCompat 46 import androidx.test.ext.junit.runners.AndroidJUnit4 47 import androidx.test.filters.SdkSuppress 48 import com.google.accompanist.testharness.test.R 49 import org.junit.Assert.assertEquals 50 import org.junit.Assert.assertNotEquals 51 import org.junit.Rule 52 import org.junit.Test 53 import org.junit.runner.RunWith 54 import java.util.Locale 55 56 @RunWith(AndroidJUnit4::class) 57 class TestHarnessTest { 58 @get:Rule 59 val composeTestRule = createAndroidComposeRule<ComponentActivity>() 60 61 @Test 62 fun size_SmallerThanOuterBox_measuredWidthIsCorrect() { 63 var width = 0.dp 64 composeTestRule.setContent { 65 Box(Modifier.requiredSize(300.dp)) { 66 TestHarness(size = DpSize(200.dp, 200.dp)) { 67 BoxOfSize(200.dp, onWidth = { width = it }) 68 } 69 } 70 } 71 composeTestRule.waitForIdle() 72 73 val ratio = width / 200.dp 74 assertEquals(ratio, 1f, 0.01f) 75 } 76 77 @Test 78 fun size_BiggerThanOuterBox_measuredWidthIsCorrect() { 79 var width = 0.dp 80 composeTestRule.setContent { 81 Box(Modifier.requiredSize(100.dp)) { 82 TestHarness(size = DpSize(200.dp, 200.dp)) { 83 BoxOfSize(200.dp, onWidth = { width = it }) 84 } 85 } 86 } 87 composeTestRule.waitForIdle() 88 89 val ratio = width / 200.dp 90 assertEquals(ratio, 1f, 0.01f) 91 } 92 93 @Test 94 fun size_ExtremelyBig_measuredWidthIsCorrect() { 95 var width = 0.dp 96 composeTestRule.setContent { 97 TestHarness(size = DpSize(10000.dp, 10000.dp)) { 98 BoxOfSize(10000.dp, onWidth = { width = it }) 99 } 100 } 101 composeTestRule.waitForIdle() 102 103 val ratio = width / 10000.dp 104 assertEquals(ratio, 1f, 0.01f) 105 } 106 107 @Test 108 fun darkMode_enabled() { 109 var darkMode: Int = -1 110 composeTestRule.setContent { 111 TestHarness(darkMode = true) { 112 darkMode = LocalConfiguration.current.uiMode 113 } 114 } 115 composeTestRule.waitForIdle() 116 117 assertEquals(darkMode and UI_MODE_NIGHT_MASK, UI_MODE_NIGHT_YES) 118 } 119 120 @Test 121 fun darkMode_disabled() { 122 var darkMode: Int = -1 123 composeTestRule.setContent { 124 TestHarness(darkMode = false) { 125 darkMode = LocalConfiguration.current.uiMode 126 } 127 } 128 composeTestRule.waitForIdle() 129 130 assertEquals(darkMode and UI_MODE_NIGHT_MASK, UI_MODE_NIGHT_NO) 131 } 132 133 @Test 134 @SdkSuppress(minSdkVersion = 24) 135 fun locales_api24_allLocalesApplied() { 136 val expectedLocales = LocaleListCompat.create(Locale.CANADA, Locale.ITALY) 137 lateinit var locales: LocaleListCompat 138 composeTestRule.setContent { 139 TestHarness(locales = expectedLocales) { 140 locales = LocaleListCompat.wrap(LocalConfiguration.current.locales) 141 } 142 } 143 144 composeTestRule.waitForIdle() 145 146 // All locales are expected in Sdk>=24 147 assertEquals(expectedLocales, locales) 148 } 149 150 @Test 151 fun usLocale_usesCorrectResource() { 152 composeTestRule.setContent { 153 TestHarness(locales = LocaleListCompat.forLanguageTags("us")) { 154 BasicText(text = stringResource(R.string.this_is_content, "abc")) 155 } 156 } 157 composeTestRule.onNodeWithText("This is content\nabc").assertExists() 158 } 159 160 @Test 161 fun arLocale_usesCorrectResource() { 162 composeTestRule.setContent { 163 TestHarness(locales = LocaleListCompat.forLanguageTags("ar")) { 164 BasicText(text = stringResource(R.string.this_is_content, "abc")) 165 } 166 } 167 composeTestRule.onNodeWithText("هذا مضمون \nabc").assertExists() 168 } 169 170 @Test 171 fun layoutDirection_RtlLocale_usesOverride() { 172 lateinit var direction: LayoutDirection 173 val initialLocale = LocaleListCompat.create(Locale("ar")) // Arabic 174 val initialLayoutDirection = LayoutDirection.Ltr 175 176 // Given test harness setting an RTL Locale but it also forcing the opposite 177 // layout direction 178 composeTestRule.setContent { 179 TestHarness( 180 layoutDirection = initialLayoutDirection, 181 locales = initialLocale 182 ) { 183 direction = LocalLayoutDirection.current 184 } 185 } 186 composeTestRule.waitForIdle() 187 188 // The used locale should be the one overriden with the test harness, ignoring the Locale's. 189 assertEquals(initialLayoutDirection, direction) 190 } 191 192 @Test 193 fun layoutDirection_default_RtlLocale() { 194 lateinit var direction: LayoutDirection 195 val initialLocale = LocaleListCompat.create(Locale("ar")) // Arabic 196 197 // Given an initial layout direction, when the test harness sets an RTL Locale and doesn't 198 // force the layout direction 199 composeTestRule.setContent { 200 TestHarness( 201 layoutDirection = null, 202 locales = initialLocale 203 ) { 204 direction = LocalLayoutDirection.current 205 } 206 } 207 composeTestRule.waitForIdle() 208 209 // The used locale should be the Locale's. 210 assertEquals(LayoutDirection.Rtl, direction) 211 } 212 213 @Test 214 fun layoutDirection_default_usesLocales() { 215 lateinit var direction: LayoutDirection 216 val initialLocale = LocaleListCompat.create(Locale("ar")) // Arabic 217 val initialLayoutDirection = LayoutDirection.Ltr 218 219 // Given no layout direction, when the test harness sets an RTL Locale with an initial 220 // LTR direction 221 composeTestRule.setContent { 222 CompositionLocalProvider( 223 LocalLayoutDirection provides initialLayoutDirection 224 ) { 225 TestHarness( 226 layoutDirection = null, 227 locales = initialLocale 228 ) { 229 direction = LocalLayoutDirection.current 230 } 231 } 232 } 233 composeTestRule.waitForIdle() 234 235 // The default should be the one provided by the Locale 236 assertNotEquals(initialLayoutDirection, direction) 237 } 238 239 @Test 240 fun layoutDirection_setLtr() { 241 lateinit var direction: LayoutDirection 242 val initialLayoutDirection = LayoutDirection.Rtl 243 val expected = LayoutDirection.Ltr 244 245 // Given a content with an initial RTL layout direction, when the test harness overrides it 246 composeTestRule.setContent { 247 CompositionLocalProvider( 248 LocalLayoutDirection provides initialLayoutDirection 249 ) { 250 TestHarness(layoutDirection = expected) { 251 direction = LocalLayoutDirection.current 252 } 253 } 254 } 255 composeTestRule.waitForIdle() 256 257 // The direction should be the one forced by the test harness 258 assertEquals(expected, direction) 259 } 260 261 @Test 262 fun layoutDirection_setRtl() { 263 lateinit var direction: LayoutDirection 264 val initialLayoutDirection = LayoutDirection.Ltr 265 val expected = LayoutDirection.Rtl 266 267 // Given a content with an initial RTL layout direction, when the test harness overrides it 268 composeTestRule.setContent { 269 CompositionLocalProvider( 270 LocalLayoutDirection provides initialLayoutDirection 271 ) { 272 TestHarness(layoutDirection = expected) { 273 direction = LocalLayoutDirection.current 274 } 275 } 276 } 277 composeTestRule.waitForIdle() 278 279 // The direction should be the one forced by the test harness 280 assertEquals(expected, direction) 281 } 282 283 @Test 284 fun layoutDirection_default_followsLocaleLtr() { 285 lateinit var direction: LayoutDirection 286 287 // Given an initial layout direction and no overrides 288 composeTestRule.setContent { 289 CompositionLocalProvider( 290 LocalConfiguration provides Configuration().apply { 291 setLocale(Locale.ENGLISH) 292 } 293 ) { 294 TestHarness(layoutDirection = null) { 295 direction = LocalLayoutDirection.current 296 } 297 } 298 } 299 composeTestRule.waitForIdle() 300 301 // The direction should be set by the Locale 302 assertEquals(LayoutDirection.Ltr, direction) 303 } 304 305 @Test 306 fun layoutDirection_default_followsLocaleRtl() { 307 lateinit var direction: LayoutDirection 308 309 // Given an initial layout direction and no overrides 310 composeTestRule.setContent { 311 CompositionLocalProvider( 312 LocalConfiguration provides Configuration().apply { 313 setLocale(Locale("ar")) 314 } 315 ) { 316 TestHarness(layoutDirection = null) { 317 direction = LocalLayoutDirection.current 318 } 319 } 320 } 321 composeTestRule.waitForIdle() 322 323 // The direction should be set by the Locale 324 assertEquals(LayoutDirection.Rtl, direction) 325 } 326 327 @Test 328 fun fontScale() { 329 val expectedFontScale = 5f 330 var fontScale = 0f 331 // Given 332 composeTestRule.setContent { 333 TestHarness(fontScale = expectedFontScale) { 334 fontScale = LocalConfiguration.current.fontScale 335 } 336 } 337 338 composeTestRule.waitForIdle() 339 340 assertEquals(expectedFontScale, fontScale) 341 } 342 343 @Test 344 @SdkSuppress(minSdkVersion = 31) 345 fun fontWeightAdjustment() { 346 val expectedFontWeightAdjustment = 10 347 var fontWeightAdjustment = 0 348 composeTestRule.setContent { 349 TestHarness(fontWeightAdjustment = expectedFontWeightAdjustment) { 350 fontWeightAdjustment = LocalConfiguration.current.fontWeightAdjustment 351 } 352 } 353 354 composeTestRule.waitForIdle() 355 356 assertEquals(expectedFontWeightAdjustment, fontWeightAdjustment) 357 } 358 359 @Test 360 @SdkSuppress(minSdkVersion = 23) // SCREENLAYOUT_ROUND_YES supported on API 23+ 361 fun isScreenRound() { 362 var defaultRound: Boolean? = null 363 var forcedRound: Boolean? = null 364 var forcedNotRound: Boolean? = null 365 366 composeTestRule.setContent { 367 defaultRound = LocalConfiguration.current.isScreenRound 368 TestHarness(isScreenRound = false) { 369 TestHarness(isScreenRound = true) { 370 forcedRound = LocalConfiguration.current.isScreenRound 371 TestHarness(isScreenRound = false) { 372 forcedNotRound = LocalConfiguration.current.isScreenRound 373 } 374 } 375 } 376 } 377 378 assertEquals( 379 composeTestRule.activity.resources.configuration.isScreenRound, 380 defaultRound 381 ) 382 assertEquals(true, forcedRound) 383 assertEquals(false, forcedNotRound) 384 } 385 386 @Composable 387 private fun BoxOfSize(size: Dp, onWidth: (Dp) -> Unit) { 388 val localDensity = LocalDensity.current 389 Box( 390 Modifier 391 .size(size) 392 .background(color = Color.Black) 393 .onGloballyPositioned { it: LayoutCoordinates -> 394 onWidth(with(localDensity) { it.size.width.toDp() }) 395 } 396 ) 397 } 398 } 399