* main: (58 commits) Instrumented tests for InterestsListDetailScreen (#1518) Remove Firebase AD_SERVICE_CONFIG property updateProdReleaseBadging Merge Android & JVM plugins into a single `HiltConventionPlugin` Update dependency guard 🤖 Updates baselines for Dependency Guard Update to Lifecycle 2.8.3 Add android.adservices.AD_SERVICES_CONFIG to badges Apply suggestions from code review Update app/src/main/AndroidManifest.xml generateModuleGraphs dependencyGuardBaseline updateProdReleaseBadging Fix spotless. Change set to `=` operator Cleanup unused reference in `libs.versions.toml` Regenerate SVG graphs Update comment wording Use stdin to avoid repeating the output file name Replace bash commands with proper svgo cli ... Change-Id: I1d804dcb6ac0ca857cb303b8c8bff5704b5d27a5pull/1837/head
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.ui
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import kotlin.properties.ReadOnlyProperty
|
||||||
|
|
||||||
|
fun AndroidComposeTestRule<*, *>.stringResource(
|
||||||
|
@StringRes resId: Int,
|
||||||
|
): ReadOnlyProperty<Any, String> =
|
||||||
|
ReadOnlyProperty { _, _ -> activity.getString(resId) }
|
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.ui.interests2pane
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.material3.adaptive.Posture
|
||||||
|
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||||
|
import androidx.compose.ui.test.DeviceConfigurationOverride
|
||||||
|
import androidx.compose.ui.test.ForcedSize
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.assertIsNotDisplayed
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.test.espresso.Espresso
|
||||||
|
import androidx.window.core.layout.WindowSizeClass
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
|
||||||
|
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.Topic
|
||||||
|
import com.google.samples.apps.nowinandroid.ui.stringResource
|
||||||
|
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
|
||||||
|
import dagger.hilt.android.testing.BindValue
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidRule
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.topic.R as FeatureTopicR
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class InterestsListDetailScreenTest {
|
||||||
|
@get:Rule(order = 0)
|
||||||
|
val hiltRule = HiltAndroidRule(this)
|
||||||
|
|
||||||
|
@BindValue
|
||||||
|
@get:Rule(order = 1)
|
||||||
|
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
|
||||||
|
|
||||||
|
@get:Rule(order = 2)
|
||||||
|
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var topicsRepository: TopicsRepository
|
||||||
|
|
||||||
|
// The strings used for matching in these tests.
|
||||||
|
private val placeholderText by composeTestRule.stringResource(FeatureTopicR.string.feature_topic_select_an_interest)
|
||||||
|
private val listPaneTag = "interests:topics"
|
||||||
|
|
||||||
|
private val Topic.testTag
|
||||||
|
get() = "topic:${this.id}"
|
||||||
|
|
||||||
|
// Overrides for device sizes.
|
||||||
|
private enum class TestDeviceConfig(widthDp: Float, heightDp: Float) {
|
||||||
|
Compact(412f, 915f),
|
||||||
|
Expanded(1200f, 840f),
|
||||||
|
;
|
||||||
|
|
||||||
|
val sizeOverride = DeviceConfigurationOverride.ForcedSize(DpSize(widthDp.dp, heightDp.dp))
|
||||||
|
val adaptiveInfo = WindowAdaptiveInfo(
|
||||||
|
windowSizeClass = WindowSizeClass.compute(widthDp, heightDp),
|
||||||
|
windowPosture = Posture(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
hiltRule.inject()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience function for getting all topics during tests, */
|
||||||
|
private fun getTopics(): List<Topic> = runBlocking {
|
||||||
|
topicsRepository.getTopics().first()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun expandedWidth_initialState_showsTwoPanesWithPlaceholder() {
|
||||||
|
composeTestRule.apply {
|
||||||
|
setContent {
|
||||||
|
with(TestDeviceConfig.Expanded) {
|
||||||
|
DeviceConfigurationOverride(override = sizeOverride) {
|
||||||
|
NiaTheme {
|
||||||
|
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(listPaneTag).assertIsDisplayed()
|
||||||
|
onNodeWithText(placeholderText).assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compactWidth_initialState_showsListPane() {
|
||||||
|
composeTestRule.apply {
|
||||||
|
setContent {
|
||||||
|
with(TestDeviceConfig.Compact) {
|
||||||
|
DeviceConfigurationOverride(override = sizeOverride) {
|
||||||
|
NiaTheme {
|
||||||
|
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(listPaneTag).assertIsDisplayed()
|
||||||
|
onNodeWithText(placeholderText).assertIsNotDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun expandedWidth_topicSelected_updatesDetailPane() {
|
||||||
|
composeTestRule.apply {
|
||||||
|
setContent {
|
||||||
|
with(TestDeviceConfig.Expanded) {
|
||||||
|
DeviceConfigurationOverride(override = sizeOverride) {
|
||||||
|
NiaTheme {
|
||||||
|
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val firstTopic = getTopics().first()
|
||||||
|
onNodeWithText(firstTopic.name).performClick()
|
||||||
|
|
||||||
|
onNodeWithTag(listPaneTag).assertIsDisplayed()
|
||||||
|
onNodeWithText(placeholderText).assertIsNotDisplayed()
|
||||||
|
onNodeWithTag(firstTopic.testTag).assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compactWidth_topicSelected_showsTopicDetailPane() {
|
||||||
|
composeTestRule.apply {
|
||||||
|
setContent {
|
||||||
|
with(TestDeviceConfig.Compact) {
|
||||||
|
DeviceConfigurationOverride(override = sizeOverride) {
|
||||||
|
NiaTheme {
|
||||||
|
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val firstTopic = getTopics().first()
|
||||||
|
onNodeWithText(firstTopic.name).performClick()
|
||||||
|
|
||||||
|
onNodeWithTag(listPaneTag).assertIsNotDisplayed()
|
||||||
|
onNodeWithText(placeholderText).assertIsNotDisplayed()
|
||||||
|
onNodeWithTag(firstTopic.testTag).assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun expandedWidth_backPressFromTopicDetail_leavesInterests() {
|
||||||
|
var unhandledBackPress = false
|
||||||
|
composeTestRule.apply {
|
||||||
|
setContent {
|
||||||
|
with(TestDeviceConfig.Expanded) {
|
||||||
|
DeviceConfigurationOverride(override = sizeOverride) {
|
||||||
|
NiaTheme {
|
||||||
|
// Back press should not be handled by the two pane layout, and thus
|
||||||
|
// "fall through" to this BackHandler.
|
||||||
|
BackHandler {
|
||||||
|
unhandledBackPress = true
|
||||||
|
}
|
||||||
|
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val firstTopic = getTopics().first()
|
||||||
|
onNodeWithText(firstTopic.name).performClick()
|
||||||
|
|
||||||
|
Espresso.pressBack()
|
||||||
|
|
||||||
|
assertTrue(unhandledBackPress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compactWidth_backPressFromTopicDetail_showsListPane() {
|
||||||
|
composeTestRule.apply {
|
||||||
|
setContent {
|
||||||
|
with(TestDeviceConfig.Compact) {
|
||||||
|
DeviceConfigurationOverride(override = sizeOverride) {
|
||||||
|
NiaTheme {
|
||||||
|
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val firstTopic = getTopics().first()
|
||||||
|
onNodeWithText(firstTopic.name).performClick()
|
||||||
|
|
||||||
|
Espresso.pressBack()
|
||||||
|
|
||||||
|
onNodeWithTag(listPaneTag).assertIsDisplayed()
|
||||||
|
onNodeWithText(placeholderText).assertIsNotDisplayed()
|
||||||
|
onNodeWithTag(firstTopic.testTag).assertIsNotDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.ui
|
||||||
|
|
||||||
|
import android.view.WindowInsets
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import androidx.compose.ui.test.DeviceConfigurationOverride
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.children
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [DeviceConfigurationOverride] that allows overriding the [windowInsets] available
|
||||||
|
* to the content under test.
|
||||||
|
*/
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
|
fun DeviceConfigurationOverride.Companion.WindowInsets(
|
||||||
|
windowInsets: WindowInsetsCompat,
|
||||||
|
): DeviceConfigurationOverride = DeviceConfigurationOverride { contentUnderTest ->
|
||||||
|
val currentContentUnderTest by rememberUpdatedState(contentUnderTest)
|
||||||
|
val currentWindowInsets by rememberUpdatedState(windowInsets)
|
||||||
|
AndroidView(
|
||||||
|
factory = { context ->
|
||||||
|
object : FrameLayout(context) {
|
||||||
|
override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets {
|
||||||
|
children.forEach {
|
||||||
|
it.dispatchApplyWindowInsets(currentWindowInsets.toWindowInsets())
|
||||||
|
}
|
||||||
|
return WindowInsetsCompat.CONSUMED.toWindowInsets()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated, but intercept the `requestApplyInsets` call via the deprecated
|
||||||
|
* method.
|
||||||
|
*/
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
|
override fun requestFitSystemWindows() {
|
||||||
|
dispatchApplyWindowInsets(currentWindowInsets.toWindowInsets()!!)
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
addView(
|
||||||
|
ComposeView(context).apply {
|
||||||
|
setContent {
|
||||||
|
currentContentUnderTest()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,349 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.only
|
||||||
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsEndWidth
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsStartWidth
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsTopHeight
|
||||||
|
import androidx.compose.material3.SnackbarDuration.Indefinite
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||||
|
import androidx.compose.material3.adaptive.Posture
|
||||||
|
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toAndroidRect
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.test.DeviceConfigurationOverride
|
||||||
|
import androidx.compose.ui.test.ForcedSize
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.DpRect
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.roundToIntRect
|
||||||
|
import androidx.core.graphics.Insets
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.window.core.layout.WindowSizeClass
|
||||||
|
import com.github.takahirom.roborazzi.captureRoboImage
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeUserDataRepository
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
|
||||||
|
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
|
||||||
|
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
|
||||||
|
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
|
||||||
|
import dagger.hilt.android.testing.BindValue
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidRule
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import dagger.hilt.android.testing.HiltTestApplication
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
import org.robolectric.annotation.GraphicsMode
|
||||||
|
import org.robolectric.annotation.LooperMode
|
||||||
|
import java.util.TimeZone
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the Snackbar is correctly displayed on different screen sizes.
|
||||||
|
*/
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||||
|
// Configure Robolectric to use a very large screen size that can fit all of the test sizes.
|
||||||
|
// This allows enough room to render the content under test without clipping or scaling.
|
||||||
|
@Config(application = HiltTestApplication::class, qualifiers = "w1000dp-h1000dp-480dpi")
|
||||||
|
@LooperMode(LooperMode.Mode.PAUSED)
|
||||||
|
@HiltAndroidTest
|
||||||
|
class SnackbarInsetsScreenshotTests {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the components' state and is used to perform injection on your test
|
||||||
|
*/
|
||||||
|
@get:Rule(order = 0)
|
||||||
|
val hiltRule = HiltAndroidRule(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a temporary folder used to create a Data Store file. This guarantees that
|
||||||
|
* the file is removed in between each test, preventing a crash.
|
||||||
|
*/
|
||||||
|
@BindValue
|
||||||
|
@get:Rule(order = 1)
|
||||||
|
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a test activity to set the content on.
|
||||||
|
*/
|
||||||
|
@get:Rule(order = 2)
|
||||||
|
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var networkMonitor: NetworkMonitor
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var timeZoneMonitor: TimeZoneMonitor
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var userDataRepository: FakeUserDataRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var topicsRepository: TopicsRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var userNewsResourceRepository: UserNewsResourceRepository
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
hiltRule.inject()
|
||||||
|
|
||||||
|
// Configure user data
|
||||||
|
runBlocking {
|
||||||
|
userDataRepository.setShouldHideOnboarding(true)
|
||||||
|
|
||||||
|
userDataRepository.setFollowedTopicIds(
|
||||||
|
setOf(topicsRepository.getTopics().first().first().id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setTimeZone() {
|
||||||
|
// Make time zone deterministic in tests
|
||||||
|
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun phone_noSnackbar() {
|
||||||
|
val snackbarHostState = SnackbarHostState()
|
||||||
|
testSnackbarScreenshotWithSize(
|
||||||
|
snackbarHostState,
|
||||||
|
400.dp,
|
||||||
|
500.dp,
|
||||||
|
"insets_snackbar_compact_medium_noSnackbar",
|
||||||
|
action = { },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun snackbarShown_phone() {
|
||||||
|
val snackbarHostState = SnackbarHostState()
|
||||||
|
testSnackbarScreenshotWithSize(
|
||||||
|
snackbarHostState,
|
||||||
|
400.dp,
|
||||||
|
500.dp,
|
||||||
|
"insets_snackbar_compact_medium",
|
||||||
|
) {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
"This is a test snackbar message",
|
||||||
|
actionLabel = "Action Label",
|
||||||
|
duration = Indefinite,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun snackbarShown_foldable() {
|
||||||
|
val snackbarHostState = SnackbarHostState()
|
||||||
|
testSnackbarScreenshotWithSize(
|
||||||
|
snackbarHostState,
|
||||||
|
600.dp,
|
||||||
|
600.dp,
|
||||||
|
"insets_snackbar_medium_medium",
|
||||||
|
) {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
"This is a test snackbar message",
|
||||||
|
actionLabel = "Action Label",
|
||||||
|
duration = Indefinite,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun snackbarShown_tablet() {
|
||||||
|
val snackbarHostState = SnackbarHostState()
|
||||||
|
testSnackbarScreenshotWithSize(
|
||||||
|
snackbarHostState,
|
||||||
|
900.dp,
|
||||||
|
900.dp,
|
||||||
|
"insets_snackbar_expanded_expanded",
|
||||||
|
) {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
"This is a test snackbar message",
|
||||||
|
actionLabel = "Action Label",
|
||||||
|
duration = Indefinite,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||||
|
private fun testSnackbarScreenshotWithSize(
|
||||||
|
snackbarHostState: SnackbarHostState,
|
||||||
|
width: Dp,
|
||||||
|
height: Dp,
|
||||||
|
screenshotName: String,
|
||||||
|
action: suspend () -> Unit,
|
||||||
|
) {
|
||||||
|
lateinit var scope: CoroutineScope
|
||||||
|
composeTestRule.setContent {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
// Replaces images with placeholders
|
||||||
|
LocalInspectionMode provides true,
|
||||||
|
) {
|
||||||
|
scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
DeviceConfigurationOverride(
|
||||||
|
DeviceConfigurationOverride.ForcedSize(DpSize(width, height)),
|
||||||
|
) {
|
||||||
|
DeviceConfigurationOverride(
|
||||||
|
DeviceConfigurationOverride.WindowInsets(
|
||||||
|
WindowInsetsCompat.Builder()
|
||||||
|
.setInsets(
|
||||||
|
WindowInsetsCompat.Type.statusBars(),
|
||||||
|
DpRect(
|
||||||
|
left = 0.dp,
|
||||||
|
top = 64.dp,
|
||||||
|
right = 0.dp,
|
||||||
|
bottom = 0.dp,
|
||||||
|
).toInsets(),
|
||||||
|
)
|
||||||
|
.setInsets(
|
||||||
|
WindowInsetsCompat.Type.navigationBars(),
|
||||||
|
DpRect(
|
||||||
|
left = 64.dp,
|
||||||
|
top = 0.dp,
|
||||||
|
right = 64.dp,
|
||||||
|
bottom = 64.dp,
|
||||||
|
).toInsets(),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
BoxWithConstraints(Modifier.testTag("root")) {
|
||||||
|
NiaTheme {
|
||||||
|
val appState = rememberNiaAppState(
|
||||||
|
networkMonitor = networkMonitor,
|
||||||
|
userNewsResourceRepository = userNewsResourceRepository,
|
||||||
|
timeZoneMonitor = timeZoneMonitor,
|
||||||
|
)
|
||||||
|
NiaApp(
|
||||||
|
appState = appState,
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
|
showSettingsDialog = false,
|
||||||
|
onSettingsDismissed = {},
|
||||||
|
onTopAppBarActionClick = {},
|
||||||
|
windowAdaptiveInfo = WindowAdaptiveInfo(
|
||||||
|
windowSizeClass = WindowSizeClass.compute(
|
||||||
|
maxWidth.value,
|
||||||
|
maxHeight.value,
|
||||||
|
),
|
||||||
|
windowPosture = Posture(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
DebugVisibleWindowInsets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag("root")
|
||||||
|
.captureRoboImage(
|
||||||
|
"src/testDemo/screenshots/$screenshotName.png",
|
||||||
|
roborazziOptions = DefaultRoborazziOptions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DebugVisibleWindowInsets(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
debugColor: Color = Color.Magenta.copy(alpha = 0.5f),
|
||||||
|
) {
|
||||||
|
Box(modifier = modifier.fillMaxSize()) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterStart)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.windowInsetsStartWidth(WindowInsets.safeDrawing)
|
||||||
|
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Vertical))
|
||||||
|
.background(debugColor),
|
||||||
|
)
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.windowInsetsEndWidth(WindowInsets.safeDrawing)
|
||||||
|
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Vertical))
|
||||||
|
.background(debugColor),
|
||||||
|
)
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.windowInsetsTopHeight(WindowInsets.safeDrawing)
|
||||||
|
.background(debugColor),
|
||||||
|
)
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.windowInsetsBottomHeight(WindowInsets.safeDrawing)
|
||||||
|
.background(debugColor),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DpRect.toInsets() = toInsets(LocalDensity.current)
|
||||||
|
|
||||||
|
private fun DpRect.toInsets(density: Density) =
|
||||||
|
Insets.of(with(density) { toRect() }.roundToIntRect().toAndroidRect())
|
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 77 KiB |
@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright 2022 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<manifest />
|
|
Before Width: | Height: | Size: 373 B After Width: | Height: | Size: 373 B |
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 265 B |
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 478 B |
Before Width: | Height: | Size: 673 B After Width: | Height: | Size: 673 B |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 784 B After Width: | Height: | Size: 459 B |
Before Width: | Height: | Size: 778 B After Width: | Height: | Size: 456 B |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 920 B |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 796 B After Width: | Height: | Size: 465 B |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 790 B After Width: | Height: | Size: 462 B |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 776 B After Width: | Height: | Size: 455 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 944 B |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 6.2 KiB |