From abcec14cb5676de01202c63bc23bfddd2c276d58 Mon Sep 17 00:00:00 2001 From: M57 <41645758+Hy4ri@users.noreply.github.com> Date: Fri, 19 Jun 2026 22:15:53 +0300 Subject: [PATCH] feat(#76): add testTag identifiers to composable elements for UI test targeting (#98) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../java/com/m57/hermescontrol/Navigation.kt | 7 ++++++- .../m57/hermescontrol/ui/chat/ChatScreen.kt | 14 ++++++++++--- .../hermescontrol/ui/common/HermesScaffold.kt | 4 ++++ .../m57/hermescontrol/ui/common/StateViews.kt | 21 +++++++++++++++---- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/m57/hermescontrol/Navigation.kt b/app/src/main/java/com/m57/hermescontrol/Navigation.kt index c1e1f16f..9641c574 100644 --- a/app/src/main/java/com/m57/hermescontrol/Navigation.kt +++ b/app/src/main/java/com/m57/hermescontrol/Navigation.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.navigation3.runtime.NavKey @@ -279,7 +280,10 @@ fun MainNavigation() { selectedTextColor = 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) }, icon = { Icon(item.icon, contentDescription = item.label) }, label = { Text(item.label) }, + modifier = Modifier.testTag("nav_${item.label.lowercase().replace(' ', '_')}"), ) } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt index 6cbe392a..abdac91f 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt @@ -84,6 +84,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp @@ -650,7 +651,8 @@ private fun ChatInputBar( modifier = Modifier .weight(1f) - .heightIn(max = 140.dp), + .heightIn(max = 140.dp) + .testTag("chat_input"), placeholder = { Text( if (!isConnected) { @@ -679,7 +681,10 @@ private fun ChatInputBar( // Interrupt button FilledTonalButton( onClick = onInterrupt, - modifier = Modifier.size(48.dp), + modifier = + Modifier + .size(48.dp) + .testTag("interrupt_button"), shape = CircleShape, contentPadding = PaddingValues(0.dp), ) { @@ -694,7 +699,10 @@ private fun ChatInputBar( FilledTonalButton( onClick = onSend, enabled = canSend, - modifier = Modifier.size(48.dp), + modifier = + Modifier + .size(48.dp) + .testTag("send_button"), shape = CircleShape, contentPadding = PaddingValues(0.dp), ) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/common/HermesScaffold.kt b/app/src/main/java/com/m57/hermescontrol/ui/common/HermesScaffold.kt index 7b9128c0..5a463193 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/common/HermesScaffold.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/common/HermesScaffold.kt @@ -20,6 +20,7 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.testTag /** * Shared Scaffold wrapper that standardizes the TopAppBar. @@ -69,6 +70,7 @@ fun HermesScaffold( Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", + modifier = Modifier.testTag("back_button"), ) } } @@ -78,6 +80,7 @@ fun HermesScaffold( Icon( imageVector = Icons.Filled.Menu, contentDescription = "Open navigation drawer", + modifier = Modifier.testTag("menu_button"), ) } } @@ -89,6 +92,7 @@ fun HermesScaffold( Icon( imageVector = Icons.Filled.Refresh, contentDescription = "Refresh", + modifier = Modifier.testTag("refresh_button"), ) } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/common/StateViews.kt b/app/src/main/java/com/m57/hermescontrol/ui/common/StateViews.kt index 590ff5cc..7f85f11b 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/common/StateViews.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/common/StateViews.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.m57.hermescontrol.theme.LocalSpacing @@ -34,13 +35,19 @@ import com.m57.hermescontrol.theme.LocalSpacing @Composable fun LoadingState(modifier: Modifier = Modifier) { Box( - modifier = modifier.fillMaxSize(), + modifier = + modifier + .fillMaxSize() + .testTag("loading_state"), contentAlignment = Alignment.Center, ) { CircularProgressIndicator( color = MaterialTheme.colorScheme.primary, 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) { Spacer(modifier = Modifier.height(spacing.md)) - Button(onClick = onRetry) { + Button( + onClick = onRetry, + modifier = Modifier.testTag("error_retry_button"), + ) { Icon( imageVector = Icons.Filled.Refresh, contentDescription = null, @@ -133,7 +143,10 @@ fun EmptyState( } if (actionLabel != null && onAction != null) { Spacer(modifier = Modifier.height(spacing.md)) - OutlinedButton(onClick = onAction) { + OutlinedButton( + onClick = onAction, + modifier = Modifier.testTag("empty_state_action"), + ) { Text(actionLabel) } }