androidTest: Add tests for reusable composable
* Add semantics wherever required to make composable easy to test Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
29
app/src/androidTest/java/com/aurora/store/IsolatedTest.kt
Normal file
29
app/src/androidTest/java/com/aurora/store/IsolatedTest.kt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
|
import com.aurora.store.compose.theme.AuroraTheme
|
||||||
|
import org.junit.Rule
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that provides helper methods to test isolated composable
|
||||||
|
*/
|
||||||
|
abstract class IsolatedTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets given composable as content with default theme
|
||||||
|
*/
|
||||||
|
fun setContent(content: @Composable () -> Unit) {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
AuroraTheme(content = content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.composable
|
||||||
|
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.test.assertHasClickAction
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import com.aurora.store.IsolatedTest
|
||||||
|
import com.aurora.store.R
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ErrorTest : IsolatedTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testErrorWithoutActionHandling() {
|
||||||
|
setContent {
|
||||||
|
Error(
|
||||||
|
painter = painterResource(R.drawable.ic_apps_outage),
|
||||||
|
message = "An error occurred!",
|
||||||
|
actionMessage = "Retry"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText("Retry")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
.assertHasClickAction()
|
||||||
|
.assertIsNotEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.composable
|
||||||
|
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.test.assertHasClickAction
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import com.aurora.store.IsolatedTest
|
||||||
|
import com.aurora.store.R
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class InfoTest: IsolatedTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testInfoWithoutClickHandling() {
|
||||||
|
setContent {
|
||||||
|
Info(title = AnnotatedString(text = stringResource(R.string.app_name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText("Aurora Store")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
.assertHasClickAction()
|
||||||
|
.assertIsNotEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.composable
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.assertIsEnabled
|
||||||
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import com.aurora.store.IsolatedTest
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.data.model.Permission
|
||||||
|
import com.aurora.store.data.model.PermissionType
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class PermissionListItemTest : IsolatedTest() {
|
||||||
|
|
||||||
|
private val permission: Permission
|
||||||
|
@Composable
|
||||||
|
get() = Permission(
|
||||||
|
PermissionType.STORAGE_MANAGER,
|
||||||
|
stringResource(R.string.onboarding_permission_esm),
|
||||||
|
stringResource(R.string.onboarding_permission_esa_desc),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPermissionNotGranted() {
|
||||||
|
setContent {
|
||||||
|
PermissionListItem(permission = permission.copy(isGranted = false))
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText("Grant")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
.assertIsEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPermissionGranted() {
|
||||||
|
setContent {
|
||||||
|
PermissionListItem(permission = permission.copy(isGranted = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText("Granted")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
.assertIsNotEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.composable
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import com.aurora.store.IsolatedTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class TopAppBarTest : IsolatedTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTitleWithNoNavigationAction() {
|
||||||
|
setContent {
|
||||||
|
TopAppBar(title = "About", onNavigateUp = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText("About")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithContentDescription("Back")
|
||||||
|
.assertDoesNotExist()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.composable.app
|
||||||
|
|
||||||
|
import androidx.compose.ui.semantics.ProgressBarRangeInfo
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.assertIsNotDisplayed
|
||||||
|
import androidx.compose.ui.test.assertRangeInfoEquals
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
import com.aurora.store.IsolatedTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class AnimatedAppIconTest: IsolatedTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAnimatedAppIconNoProgress() {
|
||||||
|
setContent {
|
||||||
|
AnimatedAppIcon(
|
||||||
|
iconUrl = "https://example.com/icon.png",
|
||||||
|
inProgress = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag("progressIndicator")
|
||||||
|
.assertIsNotDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAnimatedAppIconProgressAt0() {
|
||||||
|
setContent {
|
||||||
|
AnimatedAppIcon(
|
||||||
|
iconUrl = "https://example.com/icon.png",
|
||||||
|
progress = 0F,
|
||||||
|
inProgress = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag("progressIndicator")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
.assertRangeInfoEquals(ProgressBarRangeInfo.Indeterminate)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAnimatedAppIconProgressAt50() {
|
||||||
|
setContent {
|
||||||
|
AnimatedAppIcon(
|
||||||
|
iconUrl = "https://example.com/icon.png",
|
||||||
|
progress = 50F,
|
||||||
|
inProgress = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag("progressIndicator")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
.assertRangeInfoEquals(ProgressBarRangeInfo(0.5F, 0.00F..1.00F))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.dimensionResource
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.semantics.stateDescription
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.compose.preview.PreviewTemplate
|
import com.aurora.store.compose.preview.PreviewTemplate
|
||||||
@@ -30,8 +33,11 @@ fun ContainedLoadingIndicator(modifier: Modifier = Modifier) {
|
|||||||
.padding(dimensionResource(R.dimen.padding_small)),
|
.padding(dimensionResource(R.dimen.padding_small)),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
|
val description = stringResource(R.string.loading)
|
||||||
ContainedLoadingIndicator(
|
ContainedLoadingIndicator(
|
||||||
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_small))
|
modifier = Modifier
|
||||||
|
.requiredSize(dimensionResource(R.dimen.icon_size_small))
|
||||||
|
.semantics { stateDescription = description }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ fun Error(
|
|||||||
painter: Painter,
|
painter: Painter,
|
||||||
message: String,
|
message: String,
|
||||||
actionMessage: String? = null,
|
actionMessage: String? = null,
|
||||||
onAction: () -> Unit = {}
|
onAction: (() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -65,7 +65,7 @@ fun Error(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (actionMessage != null) {
|
if (actionMessage != null) {
|
||||||
Button(onClick = onAction) {
|
Button(onClick = { if (onAction != null) onAction() }, enabled = onAction != null) {
|
||||||
Text(
|
Text(
|
||||||
text = actionMessage,
|
text = actionMessage,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.dimensionResource
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.semantics.stateDescription
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.compose.preview.PreviewTemplate
|
import com.aurora.store.compose.preview.PreviewTemplate
|
||||||
@@ -44,6 +47,7 @@ fun PageIndicator(modifier: Modifier = Modifier, totalPages: Int, currentPage: I
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
repeat(totalPages) { iteration ->
|
repeat(totalPages) { iteration ->
|
||||||
|
val page = stringResource(R.string.page, iteration)
|
||||||
val isSelected = currentPage == iteration
|
val isSelected = currentPage == iteration
|
||||||
val color by animateColorAsState(
|
val color by animateColorAsState(
|
||||||
targetValue = if (isSelected) {
|
targetValue = if (isSelected) {
|
||||||
@@ -67,6 +71,7 @@ fun PageIndicator(modifier: Modifier = Modifier, totalPages: Int, currentPage: I
|
|||||||
.size(size)
|
.size(size)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(color = color)
|
.background(color = color)
|
||||||
|
.semantics { stateDescription = page }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import androidx.compose.ui.graphics.graphicsLayer
|
|||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.dimensionResource
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.semantics.testTag
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
@@ -65,7 +67,9 @@ fun AnimatedAppIcon(
|
|||||||
|
|
||||||
Box(modifier = modifier, contentAlignment = Alignment.Center) {
|
Box(modifier = modifier, contentAlignment = Alignment.Center) {
|
||||||
if (inProgress) {
|
if (inProgress) {
|
||||||
val indicatorModifier = Modifier.fillMaxSize()
|
val indicatorModifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.semantics { testTag = "progressIndicator" }
|
||||||
if (animatedProgress > 0) {
|
if (animatedProgress > 0) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = indicatorModifier,
|
modifier = indicatorModifier,
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ import com.aurora.store.compose.theme.AuroraTheme
|
|||||||
@Composable
|
@Composable
|
||||||
fun PreviewTemplate(content : @Composable () -> Unit) {
|
fun PreviewTemplate(content : @Composable () -> Unit) {
|
||||||
AuroraTheme {
|
AuroraTheme {
|
||||||
CompositionLocalProvider(LocalAsyncImagePreviewHandler provides coilPreviewProvider) {
|
CompositionLocalProvider(
|
||||||
content()
|
value = LocalAsyncImagePreviewHandler provides coilPreviewProvider,
|
||||||
}
|
content = content
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -546,4 +546,8 @@
|
|||||||
<string name="select_app_for_details">Select an app for more details</string>
|
<string name="select_app_for_details">Select an app for more details</string>
|
||||||
<string name="action_filter_no_ads">No advertisements</string>
|
<string name="action_filter_no_ads">No advertisements</string>
|
||||||
<string name="action_filter_no_gms">No play services</string>
|
<string name="action_filter_no_gms">No play services</string>
|
||||||
|
|
||||||
|
<!-- Misc -->
|
||||||
|
<string name="loading">Loading in progress</string>
|
||||||
|
<string name="page">Page <xliff:g id="number">%1$d</xliff:g></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user