@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* 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.util.Log
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||||
|
import androidx.compose.material3.windowsizeclass.WindowSizeClass
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onRoot
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.work.Configuration
|
||||||
|
import androidx.work.testing.SynchronousExecutor
|
||||||
|
import androidx.work.testing.WorkManagerTestInitHelper
|
||||||
|
import com.github.takahirom.roborazzi.captureRoboImage
|
||||||
|
import com.google.accompanist.testharness.TestHarness
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
|
||||||
|
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.flow.first
|
||||||
|
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 navigation UI is rendered correctly on different screen sizes.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
|
||||||
|
@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", sdk = [33])
|
||||||
|
@LooperMode(LooperMode.Mode.PAUSED)
|
||||||
|
@HiltAndroidTest
|
||||||
|
class NiaAppScreenSizesScreenshotTests {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 userDataRepository: UserDataRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var topicsRepository: TopicsRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var userNewsResourceRepository: UserNewsResourceRepository
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
val config = Configuration.Builder()
|
||||||
|
.setMinimumLoggingLevel(Log.DEBUG)
|
||||||
|
.setExecutor(SynchronousExecutor())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// Initialize WorkManager for instrumentation tests.
|
||||||
|
WorkManagerTestInitHelper.initializeTestWorkManager(
|
||||||
|
InstrumentationRegistry.getInstrumentation().context,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testNiaAppScreenshotWithSize(width: Dp, height: Dp, screenshotName: String) {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalInspectionMode provides true,
|
||||||
|
) {
|
||||||
|
TestHarness(size = DpSize(width, height)) {
|
||||||
|
BoxWithConstraints {
|
||||||
|
NiaApp(
|
||||||
|
windowSizeClass = WindowSizeClass.calculateFromSize(
|
||||||
|
DpSize(maxWidth, maxHeight),
|
||||||
|
),
|
||||||
|
networkMonitor = networkMonitor,
|
||||||
|
userNewsResourceRepository = userNewsResourceRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onRoot()
|
||||||
|
.captureRoboImage(
|
||||||
|
"src/testDemo/screenshots/$screenshotName.png",
|
||||||
|
roborazziOptions = DefaultRoborazziOptions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compactWidth_compactHeight_showsNavigationBar() {
|
||||||
|
testNiaAppScreenshotWithSize(
|
||||||
|
610.dp,
|
||||||
|
400.dp,
|
||||||
|
"compactWidth_compactHeight_showsNavigationBar",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mediumWidth_compactHeight_showsNavigationRail() {
|
||||||
|
testNiaAppScreenshotWithSize(
|
||||||
|
610.dp,
|
||||||
|
400.dp,
|
||||||
|
"mediumWidth_compactHeight_showsNavigationRail",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun expandedWidth_compactHeight_showsNavigationRail() {
|
||||||
|
testNiaAppScreenshotWithSize(
|
||||||
|
900.dp,
|
||||||
|
400.dp,
|
||||||
|
"expandedWidth_compactHeight_showsNavigationRail",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compactWidth_mediumHeight_showsNavigationBar() {
|
||||||
|
testNiaAppScreenshotWithSize(
|
||||||
|
400.dp,
|
||||||
|
500.dp,
|
||||||
|
"compactWidth_mediumHeight_showsNavigationBar",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mediumWidth_mediumHeight_showsNavigationRail() {
|
||||||
|
testNiaAppScreenshotWithSize(
|
||||||
|
610.dp,
|
||||||
|
500.dp,
|
||||||
|
"mediumWidth_mediumHeight_showsNavigationRail",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun expandedWidth_mediumHeight_showsNavigationRail() {
|
||||||
|
testNiaAppScreenshotWithSize(
|
||||||
|
900.dp,
|
||||||
|
500.dp,
|
||||||
|
"expandedWidth_mediumHeight_showsNavigationRail",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compactWidth_expandedHeight_showsNavigationBar() {
|
||||||
|
testNiaAppScreenshotWithSize(
|
||||||
|
400.dp,
|
||||||
|
1000.dp,
|
||||||
|
"compactWidth_expandedHeight_showsNavigationBar",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mediumWidth_expandedHeight_showsNavigationRail() {
|
||||||
|
testNiaAppScreenshotWithSize(
|
||||||
|
610.dp,
|
||||||
|
1000.dp,
|
||||||
|
"mediumWidth_expandedHeight_showsNavigationRail",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun expandedWidth_expandedHeight_showsNavigationRail() {
|
||||||
|
testNiaAppScreenshotWithSize(
|
||||||
|
900.dp,
|
||||||
|
1000.dp,
|
||||||
|
"expandedWidth_expandedHeight_showsNavigationRail",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 202 KiB |
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 83 KiB |
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.android.build.api.dsl.ApplicationExtension
|
||||||
|
import com.android.build.api.dsl.LibraryExtension
|
||||||
|
import com.android.build.api.dsl.Lint
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.kotlin.dsl.configure
|
||||||
|
|
||||||
|
class AndroidLintConventionPlugin : Plugin<Project> {
|
||||||
|
override fun apply(target: Project) {
|
||||||
|
with(target) {
|
||||||
|
when {
|
||||||
|
pluginManager.hasPlugin("com.android.application") ->
|
||||||
|
configure<ApplicationExtension> { lint(Lint::configure) }
|
||||||
|
|
||||||
|
pluginManager.hasPlugin("com.android.library") ->
|
||||||
|
configure<LibraryExtension> { lint(Lint::configure) }
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
pluginManager.apply("com.android.lint")
|
||||||
|
configure<Lint>(Lint::configure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Lint.configure() {
|
||||||
|
xmlReport = true
|
||||||
|
checkDependencies = true
|
||||||
|
}
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
* 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.core.network.model.util
|
|
||||||
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import kotlinx.datetime.toInstant
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.PrimitiveKind.STRING
|
|
||||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
|
|
||||||
object InstantSerializer : KSerializer<Instant> {
|
|
||||||
override fun deserialize(decoder: Decoder): Instant =
|
|
||||||
decoder.decodeString().toInstant()
|
|
||||||
|
|
||||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(
|
|
||||||
serialName = "Instant",
|
|
||||||
kind = STRING,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: Instant) =
|
|
||||||
encoder.encodeString(value.toString())
|
|
||||||
}
|
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.testing.util
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import androidx.compose.ui.test.onRoot
|
||||||
|
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||||
|
import com.github.takahirom.roborazzi.RoborazziOptions
|
||||||
|
import com.github.takahirom.roborazzi.RoborazziOptions.CompareOptions
|
||||||
|
import com.github.takahirom.roborazzi.RoborazziOptions.RecordOptions
|
||||||
|
import com.github.takahirom.roborazzi.captureRoboImage
|
||||||
|
import com.google.accompanist.testharness.TestHarness
|
||||||
|
import org.robolectric.RuntimeEnvironment
|
||||||
|
|
||||||
|
val DefaultRoborazziOptions =
|
||||||
|
RoborazziOptions(
|
||||||
|
compareOptions = CompareOptions(changeThreshold = 0f), // Pixel-perfect matching
|
||||||
|
recordOptions = RecordOptions(resizeScale = 0.5), // Reduce the size of the PNGs
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class DefaultTestDevices(val description: String, val spec: String) {
|
||||||
|
PHONE("phone", "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480"),
|
||||||
|
FOLDABLE("foldable", "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480"),
|
||||||
|
TABLET("tablet", "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480"),
|
||||||
|
}
|
||||||
|
fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.captureMultiDevice(
|
||||||
|
screenshotName: String,
|
||||||
|
body: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
DefaultTestDevices.values().forEach {
|
||||||
|
this.captureForDevice(it.description, it.spec, screenshotName, body = body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.captureForDevice(
|
||||||
|
deviceName: String,
|
||||||
|
deviceSpec: String,
|
||||||
|
screenshotName: String,
|
||||||
|
roborazziOptions: RoborazziOptions = DefaultRoborazziOptions,
|
||||||
|
darkMode: Boolean = false,
|
||||||
|
body: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
val (width, height, dpi) = extractSpecs(deviceSpec)
|
||||||
|
|
||||||
|
// Set qualifiers from specs
|
||||||
|
RuntimeEnvironment.setQualifiers("w${width}dp-h${height}dp-${dpi}dpi")
|
||||||
|
|
||||||
|
this.activity.setContent {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalInspectionMode provides true,
|
||||||
|
) {
|
||||||
|
TestHarness(darkMode = darkMode) {
|
||||||
|
body()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.onRoot()
|
||||||
|
.captureRoboImage(
|
||||||
|
"src/test/screenshots/${screenshotName}_$deviceName.png",
|
||||||
|
roborazziOptions = roborazziOptions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts some properties from the spec string. Note that this function is not exhaustive.
|
||||||
|
*/
|
||||||
|
private fun extractSpecs(deviceSpec: String): TestDeviceSpecs {
|
||||||
|
val specs = deviceSpec.substringAfter("spec:")
|
||||||
|
.split(",").map { it.split("=") }.associate { it[0] to it[1] }
|
||||||
|
val width = specs["width"]?.toInt() ?: 640
|
||||||
|
val height = specs["height"]?.toInt() ?: 480
|
||||||
|
val dpi = specs["dpi"]?.toInt() ?: 480
|
||||||
|
return TestDeviceSpecs(width, height, dpi)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class TestDeviceSpecs(val width: Int, val height: Int, val dpi: Int)
|
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* 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.feature.foryou
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
|
||||||
|
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
|
||||||
|
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultTestDevices
|
||||||
|
import com.google.samples.apps.nowinandroid.core.testing.util.captureForDevice
|
||||||
|
import com.google.samples.apps.nowinandroid.core.testing.util.captureMultiDevice
|
||||||
|
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
|
||||||
|
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Success
|
||||||
|
import com.google.samples.apps.nowinandroid.core.ui.UserNewsResourcePreviewParameterProvider
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.NotShown
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.Shown
|
||||||
|
import dagger.hilt.android.testing.HiltTestApplication
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screenshot tests for the [ForYouScreen].
|
||||||
|
*/
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||||
|
@Config(application = HiltTestApplication::class, sdk = [33])
|
||||||
|
@LooperMode(LooperMode.Mode.PAUSED)
|
||||||
|
class ForYouScreenScreenshotTests {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a test activity to set the content on.
|
||||||
|
*/
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
|
private val userNewsResources = UserNewsResourcePreviewParameterProvider().values.first()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setTimeZone() {
|
||||||
|
// Make time zone deterministic in tests
|
||||||
|
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forYouScreenPopulatedFeed() {
|
||||||
|
composeTestRule.captureMultiDevice("ForYouScreenPopulatedFeed") {
|
||||||
|
NiaTheme {
|
||||||
|
ForYouScreen(
|
||||||
|
isSyncing = false,
|
||||||
|
onboardingUiState = NotShown,
|
||||||
|
feedState = Success(
|
||||||
|
feed = userNewsResources,
|
||||||
|
),
|
||||||
|
onTopicCheckedChanged = { _, _ -> },
|
||||||
|
saveFollowedTopics = {},
|
||||||
|
onNewsResourcesCheckedChanged = { _, _ -> },
|
||||||
|
onNewsResourceViewed = {},
|
||||||
|
onTopicClick = {},
|
||||||
|
deepLinkedUserNewsResource = null,
|
||||||
|
onDeepLinkOpened = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forYouScreenLoading() {
|
||||||
|
composeTestRule.captureMultiDevice("ForYouScreenLoading") {
|
||||||
|
NiaTheme {
|
||||||
|
ForYouScreen(
|
||||||
|
isSyncing = false,
|
||||||
|
onboardingUiState = Loading,
|
||||||
|
feedState = NewsFeedUiState.Loading,
|
||||||
|
onTopicCheckedChanged = { _, _ -> },
|
||||||
|
saveFollowedTopics = {},
|
||||||
|
onNewsResourcesCheckedChanged = { _, _ -> },
|
||||||
|
onNewsResourceViewed = {},
|
||||||
|
onTopicClick = {},
|
||||||
|
deepLinkedUserNewsResource = null,
|
||||||
|
onDeepLinkOpened = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forYouScreenTopicSelection() {
|
||||||
|
composeTestRule.captureMultiDevice("ForYouScreenTopicSelection") {
|
||||||
|
ForYouScreenTopicSelection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forYouScreenTopicSelection_dark() {
|
||||||
|
composeTestRule.captureForDevice(
|
||||||
|
deviceName = "phone_dark",
|
||||||
|
deviceSpec = DefaultTestDevices.PHONE.spec,
|
||||||
|
screenshotName = "ForYouScreenTopicSelection",
|
||||||
|
darkMode = true,
|
||||||
|
) {
|
||||||
|
ForYouScreenTopicSelection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forYouScreenPopulatedAndLoading() {
|
||||||
|
composeTestRule.captureMultiDevice("ForYouScreenPopulatedAndLoading") {
|
||||||
|
ForYouScreenPopulatedAndLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forYouScreenPopulatedAndLoading_dark() {
|
||||||
|
composeTestRule.captureForDevice(
|
||||||
|
deviceName = "phone_dark",
|
||||||
|
deviceSpec = DefaultTestDevices.PHONE.spec,
|
||||||
|
screenshotName = "ForYouScreenPopulatedAndLoading",
|
||||||
|
darkMode = true,
|
||||||
|
) {
|
||||||
|
ForYouScreenPopulatedAndLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ForYouScreenTopicSelection() {
|
||||||
|
NiaTheme {
|
||||||
|
NiaBackground {
|
||||||
|
ForYouScreen(
|
||||||
|
isSyncing = false,
|
||||||
|
onboardingUiState = Shown(
|
||||||
|
topics = userNewsResources.flatMap { news -> news.followableTopics }
|
||||||
|
.distinctBy { it.topic.id },
|
||||||
|
),
|
||||||
|
feedState = Success(
|
||||||
|
feed = userNewsResources,
|
||||||
|
),
|
||||||
|
onTopicCheckedChanged = { _, _ -> },
|
||||||
|
saveFollowedTopics = {},
|
||||||
|
onNewsResourcesCheckedChanged = { _, _ -> },
|
||||||
|
onNewsResourceViewed = {},
|
||||||
|
onTopicClick = {},
|
||||||
|
deepLinkedUserNewsResource = null,
|
||||||
|
onDeepLinkOpened = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ForYouScreenPopulatedAndLoading() {
|
||||||
|
NiaTheme {
|
||||||
|
NiaBackground {
|
||||||
|
NiaTheme {
|
||||||
|
ForYouScreen(
|
||||||
|
isSyncing = true,
|
||||||
|
onboardingUiState = Loading,
|
||||||
|
feedState = Success(
|
||||||
|
feed = userNewsResources,
|
||||||
|
),
|
||||||
|
onTopicCheckedChanged = { _, _ -> },
|
||||||
|
saveFollowedTopics = {},
|
||||||
|
onNewsResourcesCheckedChanged = { _, _ -> },
|
||||||
|
onNewsResourceViewed = {},
|
||||||
|
onTopicClick = {},
|
||||||
|
deepLinkedUserNewsResource = null,
|
||||||
|
onDeepLinkOpened = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 180 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 221 KiB |
After Width: | Height: | Size: 176 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 216 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 94 KiB |