Adds testTag modifiers to key interactive elements across the core
composables, enabling Espresso/Compose UI tests to locate them:
Navigation.kt — 'nav_*' on bottom bar items, 'drawer_*' on drawer items
HermesScaffold — 'back_button', 'menu_button', 'refresh_button'
StateViews.kt — 'loading_state', 'loading_spinner', 'error_retry_button',
'empty_state_action'
ChatScreen.kt — 'chat_input', 'send_button', 'interrupt_button'
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
cc52442be3
commit
abcec14cb5
4 files changed
+38
-8
No files matched your search
@@ -56,6 +56,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation3.runtime.NavKey
|
import androidx.navigation3.runtime.NavKey
|
||||||
@@ -279,7 +280,10 @@ fun MainNavigation() {
|
|||||||
selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(horizontal = 12.dp, vertical = 2.dp)
|
||||||
|
.testTag("drawer_${entry.label.lowercase().replace(' ', '_')}"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,6 +306,7 @@ fun MainNavigation() {
|
|||||||
onClick = { NavigationController.navigateTo(item.key) },
|
onClick = { NavigationController.navigateTo(item.key) },
|
||||||
icon = { Icon(item.icon, contentDescription = item.label) },
|
icon = { Icon(item.icon, contentDescription = item.label) },
|
||||||
label = { Text(item.label) },
|
label = { Text(item.label) },
|
||||||
|
modifier = Modifier.testTag("nav_${item.label.lowercase().replace(' ', '_')}"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -650,7 +651,8 @@ private fun ChatInputBar(
|
|||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.heightIn(max = 140.dp),
|
.heightIn(max = 140.dp)
|
||||||
|
.testTag("chat_input"),
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(
|
Text(
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
@@ -679,7 +681,10 @@ private fun ChatInputBar(
|
|||||||
// Interrupt button
|
// Interrupt button
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
onClick = onInterrupt,
|
onClick = onInterrupt,
|
||||||
modifier = Modifier.size(48.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.testTag("interrupt_button"),
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
contentPadding = PaddingValues(0.dp),
|
contentPadding = PaddingValues(0.dp),
|
||||||
) {
|
) {
|
||||||
@@ -694,7 +699,10 @@ private fun ChatInputBar(
|
|||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
onClick = onSend,
|
onClick = onSend,
|
||||||
enabled = canSend,
|
enabled = canSend,
|
||||||
modifier = Modifier.size(48.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.testTag("send_button"),
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
contentPadding = PaddingValues(0.dp),
|
contentPadding = PaddingValues(0.dp),
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared Scaffold wrapper that standardizes the TopAppBar.
|
* Shared Scaffold wrapper that standardizes the TopAppBar.
|
||||||
@@ -69,6 +70,7 @@ fun HermesScaffold(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = "Back",
|
contentDescription = "Back",
|
||||||
|
modifier = Modifier.testTag("back_button"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,6 +80,7 @@ fun HermesScaffold(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Menu,
|
imageVector = Icons.Filled.Menu,
|
||||||
contentDescription = "Open navigation drawer",
|
contentDescription = "Open navigation drawer",
|
||||||
|
modifier = Modifier.testTag("menu_button"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,6 +92,7 @@ fun HermesScaffold(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Refresh,
|
imageVector = Icons.Filled.Refresh,
|
||||||
contentDescription = "Refresh",
|
contentDescription = "Refresh",
|
||||||
|
modifier = Modifier.testTag("refresh_button"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ 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.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.m57.hermescontrol.theme.LocalSpacing
|
import com.m57.hermescontrol.theme.LocalSpacing
|
||||||
@@ -34,13 +35,19 @@ import com.m57.hermescontrol.theme.LocalSpacing
|
|||||||
@Composable
|
@Composable
|
||||||
fun LoadingState(modifier: Modifier = Modifier) {
|
fun LoadingState(modifier: Modifier = Modifier) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier =
|
||||||
|
modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.testTag("loading_state"),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
strokeWidth = 3.dp,
|
strokeWidth = 3.dp,
|
||||||
modifier = Modifier.size(40.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.testTag("loading_spinner"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +84,10 @@ fun ErrorState(
|
|||||||
)
|
)
|
||||||
if (onRetry != null) {
|
if (onRetry != null) {
|
||||||
Spacer(modifier = Modifier.height(spacing.md))
|
Spacer(modifier = Modifier.height(spacing.md))
|
||||||
Button(onClick = onRetry) {
|
Button(
|
||||||
|
onClick = onRetry,
|
||||||
|
modifier = Modifier.testTag("error_retry_button"),
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Refresh,
|
imageVector = Icons.Filled.Refresh,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
@@ -133,7 +143,10 @@ fun EmptyState(
|
|||||||
}
|
}
|
||||||
if (actionLabel != null && onAction != null) {
|
if (actionLabel != null && onAction != null) {
|
||||||
Spacer(modifier = Modifier.height(spacing.md))
|
Spacer(modifier = Modifier.height(spacing.md))
|
||||||
OutlinedButton(onClick = onAction) {
|
OutlinedButton(
|
||||||
|
onClick = onAction,
|
||||||
|
modifier = Modifier.testTag("empty_state_action"),
|
||||||
|
) {
|
||||||
Text(actionLabel)
|
Text(actionLabel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user