From a98ba8b5bab6b786663b1ffc8d8f06d68ede03ab Mon Sep 17 00:00:00 2001 From: Philip Porto Schiffer Date: Fri, 9 Oct 2026 22:53:02 +0200 Subject: [PATCH] Show dialog and filter button labels in sentence case The package filter dialogs and the profile editor's add-filter buttons upper-cased their labels in code ("CANCEL", "ADD", "YES", "CLOSE", "PACKAGE NAME", "APP SIGNATURE"), while the rest of the app uses the string resources as written. They now use the strings unchanged, and add_package_name reads "Package name" instead of "package name". Compose UI tests also run as Robolectric unit tests now, so the labels are covered by DialogButtonLabelTest and FilterTypeLabelTest. Co-Authored-By: Claude Opus 5.5 --- app/build.gradle | 4 + .../AddAppSignaturePackageFilterDialog.kt | 6 +- .../AddPackageNamePackageFilterDialog.kt | 8 +- .../ui/editor/DeletePackageFilterDialog.kt | 6 +- .../ui/editor/PackageFilterInfoDialog.kt | 4 +- .../ui/editor/PackageFilterPreviewDialog.kt | 6 +- .../profiles/ui/editor/WidgetProfileEditor.kt | 8 +- app/src/main/res/values/strings.xml | 2 +- .../ui/editor/DialogButtonLabelTest.kt | 108 ++++++++++++++++++ .../profiles/ui/editor/FilterTypeLabelTest.kt | 73 ++++++++++++ gradle/libs.versions.toml | 2 + 11 files changed, 201 insertions(+), 26 deletions(-) create mode 100644 app/src/test/java/de/psdev/devdrawer/profiles/ui/editor/DialogButtonLabelTest.kt create mode 100644 app/src/test/java/de/psdev/devdrawer/profiles/ui/editor/FilterTypeLabelTest.kt diff --git a/app/build.gradle b/app/build.gradle index d656fcc3..d46273f1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -159,6 +159,7 @@ dependencies { // Compose def composeBom = platform(libs.androidx.compose.bom) implementation(composeBom) + testImplementation(composeBom) // Material Design 3 implementation libs.androidx.material3 @@ -167,6 +168,9 @@ dependencies { // Android Studio Preview support implementation libs.androidx.ui.tooling.preview debugImplementation libs.androidx.ui.tooling + // UI tests, run as Robolectric unit tests + testImplementation libs.androidx.ui.test.junit4 + debugImplementation libs.androidx.ui.test.manifest // Optional - Add full set of material icons implementation libs.androidx.material.icons.extended // Optional - Integration with activities diff --git a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/AddAppSignaturePackageFilterDialog.kt b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/AddAppSignaturePackageFilterDialog.kt index 416835d5..66d4694d 100644 --- a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/AddAppSignaturePackageFilterDialog.kt +++ b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/AddAppSignaturePackageFilterDialog.kt @@ -32,8 +32,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.graphics.createBitmap @@ -66,7 +64,7 @@ fun AddAppSignaturePackageFilterDialog( @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun AddAppSignaturePackageFilterDialog( +internal fun AddAppSignaturePackageFilterDialog( viewState: ViewState, closeDialog: () -> Unit = {}, appSelected: (AppInfo) -> Unit = {}, @@ -111,7 +109,7 @@ private fun AddAppSignaturePackageFilterDialog( modifier = Modifier .align(Alignment.End), onClick = closeDialog ) { - Text(text = stringResource(id = R.string.cancel).toUpperCase(Locale.current)) + Text(text = stringResource(id = R.string.cancel)) } } ) { diff --git a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/AddPackageNamePackageFilterDialog.kt b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/AddPackageNamePackageFilterDialog.kt index d39c6d7a..ce5b472e 100644 --- a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/AddPackageNamePackageFilterDialog.kt +++ b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/AddPackageNamePackageFilterDialog.kt @@ -25,8 +25,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog @@ -58,7 +56,7 @@ fun AddPackageNamePackageFilterDialog( } @Composable -private fun AddPackageNamePackageFilterDialog( +internal fun AddPackageNamePackageFilterDialog( viewState: AddPackageNamePackageFilterDialogViewModel.ViewState, closeDialog: () -> Unit = {}, addFilter: (String) -> Unit = {} @@ -98,10 +96,10 @@ private fun AddPackageNamePackageFilterDialog( Spacer(modifier = Modifier.size(8.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { TextButton(onClick = closeDialog) { - Text(text = stringResource(id = R.string.cancel).toUpperCase(Locale.current)) + Text(text = stringResource(id = R.string.cancel)) } TextButton(onClick = { addFilter(text) }, enabled = text.isNotBlank()) { - Text(text = stringResource(id = R.string.add).toUpperCase(Locale.current)) + Text(text = stringResource(id = R.string.add)) } } } diff --git a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/DeletePackageFilterDialog.kt b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/DeletePackageFilterDialog.kt index 20fba914..971a781b 100644 --- a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/DeletePackageFilterDialog.kt +++ b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/DeletePackageFilterDialog.kt @@ -5,8 +5,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.toUpperCase import de.psdev.devdrawer.R @Composable @@ -21,12 +19,12 @@ fun DeletePackageFilterDialog( }, dismissButton = { TextButton(onClick = onDismiss) { - Text(text = stringResource(id = R.string.cancel).toUpperCase(Locale.current)) + Text(text = stringResource(id = R.string.cancel)) } }, confirmButton = { TextButton(onClick = onConfirm) { - Text(text = stringResource(id = R.string.yes).toUpperCase(Locale.current)) + Text(text = stringResource(id = R.string.yes)) } } ) diff --git a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/PackageFilterInfoDialog.kt b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/PackageFilterInfoDialog.kt index cccd81c9..9ee054ff 100644 --- a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/PackageFilterInfoDialog.kt +++ b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/PackageFilterInfoDialog.kt @@ -19,8 +19,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import de.psdev.devdrawer.R @@ -65,7 +63,7 @@ fun PackageFilterInfoDialog( modifier = Modifier.align(Alignment.End), onClick = onDismiss ) { - Text(text = stringResource(id = R.string.close).toUpperCase(Locale.current)) + Text(text = stringResource(id = R.string.close)) } } ) { diff --git a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/PackageFilterPreviewDialog.kt b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/PackageFilterPreviewDialog.kt index de34bb92..5ee16f6b 100644 --- a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/PackageFilterPreviewDialog.kt +++ b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/PackageFilterPreviewDialog.kt @@ -20,8 +20,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.content.res.ResourcesCompat @@ -49,7 +47,7 @@ fun PackageFilterPreviewDialog( } @Composable -private fun PackageFilterPreviewDialog( +internal fun PackageFilterPreviewDialog( viewState: PackageFilterPreviewDialogViewModel.ViewState, closeDialog: () -> Unit = {} ) { @@ -67,7 +65,7 @@ private fun PackageFilterPreviewDialog( }, bottomContent = { TextButton(modifier = Modifier.align(Alignment.End), onClick = closeDialog) { - Text(text = stringResource(id = R.string.close).toUpperCase(Locale.current)) + Text(text = stringResource(id = R.string.close)) } } ) { diff --git a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/WidgetProfileEditor.kt b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/WidgetProfileEditor.kt index 35b1b676..f0acb450 100644 --- a/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/WidgetProfileEditor.kt +++ b/app/src/main/java/de/psdev/devdrawer/profiles/ui/editor/WidgetProfileEditor.kt @@ -43,8 +43,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -223,7 +221,7 @@ private sealed class WidgetProfileEditorDialogs { @OptIn(ExperimentalAnimationApi::class) @Composable -private fun WidgetProfileEditor( +internal fun WidgetProfileEditor( viewState: WidgetProfileEditorViewState, modifier: Modifier = Modifier, onNameChange: (String) -> Unit = {}, @@ -266,7 +264,7 @@ private fun WidgetProfileEditor( contentDescription = stringResource(id = R.string.add_package_name) ) Spacer(Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(id = R.string.add_package_name).toUpperCase(Locale.current)) + Text(text = stringResource(id = R.string.add_package_name)) } Button( modifier = Modifier.weight(1f), @@ -278,7 +276,7 @@ private fun WidgetProfileEditor( contentDescription = stringResource(id = R.string.add_app_signature) ) Spacer(Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(id = R.string.add_app_signature).toUpperCase(Locale.current)) + Text(text = stringResource(id = R.string.add_app_signature)) } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bbea42ca..e1d47c85 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,7 +62,7 @@ Uninstall App details App signature - package name + Package name App icon Enter package name filter Select signature from app diff --git a/app/src/test/java/de/psdev/devdrawer/profiles/ui/editor/DialogButtonLabelTest.kt b/app/src/test/java/de/psdev/devdrawer/profiles/ui/editor/DialogButtonLabelTest.kt new file mode 100644 index 00000000..8a80edc2 --- /dev/null +++ b/app/src/test/java/de/psdev/devdrawer/profiles/ui/editor/DialogButtonLabelTest.kt @@ -0,0 +1,108 @@ +package de.psdev.devdrawer.profiles.ui.editor + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import de.psdev.devdrawer.database.FilterType +import de.psdev.devdrawer.database.PackageFilter +import de.psdev.devdrawer.ui.theme.DevDrawerTheme +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** + * Dialog buttons use the string resources' sentence case ("Cancel"), not upper-cased labels ("CANCEL"). + */ +@RunWith(RobolectricTestRunner::class) +// SDK 36 requires Java 21; use SDK 33 which is compatible with the project's Java 17 toolchain. +@Config(sdk = [33]) +class DialogButtonLabelTest { + + @get:Rule + val composeTestRule = createComposeRule() + + private val packageFilter = PackageFilter( + type = FilterType.SIGNATURE, + filter = "4f8d2c9e", + description = "Example App", + profileId = "profile" + ) + + @Test + fun `given the delete filter dialog, when shown, then its buttons are in sentence case`() { + // Given / When + composeTestRule.setContent { + DevDrawerTheme { DeletePackageFilterDialog(onDismiss = {}, onConfirm = {}) } + } + + // Then + assertSentenceCase("Cancel", "Yes") + } + + @Test + fun `given the add package name filter dialog, when loaded, then its buttons are in sentence case`() { + // Given / When + composeTestRule.setContent { + DevDrawerTheme { + AddPackageNamePackageFilterDialog( + viewState = AddPackageNamePackageFilterDialogViewModel.ViewState.Loaded(listOf("com.example.app")) + ) + } + } + + // Then + assertSentenceCase("Cancel", "Add") + } + + @Test + fun `given the add app signature filter dialog, when shown, then its button is in sentence case`() { + // Given / When + composeTestRule.setContent { + DevDrawerTheme { + AddAppSignaturePackageFilterDialog( + viewState = AddAppSignaturePackageFilterDialogViewModel.ViewState.Loaded( + data = emptyList(), + showSystemApps = false + ) + ) + } + } + + // Then + assertSentenceCase("Cancel") + } + + @Test + fun `given the filter info dialog, when shown, then its button is in sentence case`() { + // Given / When + composeTestRule.setContent { + DevDrawerTheme { PackageFilterInfoDialog(packageFilter = packageFilter, onDismiss = {}) } + } + + // Then + assertSentenceCase("Close") + } + + @Test + fun `given the filter preview dialog, when loaded, then its button is in sentence case`() { + // Given / When + composeTestRule.setContent { + DevDrawerTheme { + PackageFilterPreviewDialog( + viewState = PackageFilterPreviewDialogViewModel.ViewState.Loaded(emptyList()) + ) + } + } + + // Then + assertSentenceCase("Close") + } + + private fun assertSentenceCase(vararg labels: String) { + labels.forEach { label -> + composeTestRule.onNodeWithText(label).assertExists() + composeTestRule.onNodeWithText(label.uppercase()).assertDoesNotExist() + } + } +} diff --git a/app/src/test/java/de/psdev/devdrawer/profiles/ui/editor/FilterTypeLabelTest.kt b/app/src/test/java/de/psdev/devdrawer/profiles/ui/editor/FilterTypeLabelTest.kt new file mode 100644 index 00000000..9d261044 --- /dev/null +++ b/app/src/test/java/de/psdev/devdrawer/profiles/ui/editor/FilterTypeLabelTest.kt @@ -0,0 +1,73 @@ +package de.psdev.devdrawer.profiles.ui.editor + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import de.psdev.devdrawer.database.FilterType +import de.psdev.devdrawer.database.PackageFilter +import de.psdev.devdrawer.database.WidgetProfile +import de.psdev.devdrawer.ui.theme.DevDrawerTheme +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** + * The filter type labels ("Package name", "App signature") are shown in sentence case, + * neither upper-cased ("PACKAGE NAME") nor lower-cased ("package name"). + */ +@RunWith(RobolectricTestRunner::class) +// SDK 36 requires Java 21; use SDK 33 which is compatible with the project's Java 17 toolchain. +@Config(sdk = [33]) +class FilterTypeLabelTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun `given the profile editor, when loaded, then the add filter buttons are in sentence case`() { + // Given + val widgetProfile = WidgetProfile(name = "Test profile") + + // When + composeTestRule.setContent { + DevDrawerTheme { + WidgetProfileEditor( + viewState = WidgetProfileEditorViewState( + widgetProfile = widgetProfile, + widgetName = widgetProfile.name + ) + ) + } + } + + // Then + assertSentenceCase("Package name", "App signature") + } + + @Test + fun `given a package name filter, when showing its info, then the title is in sentence case`() { + // Given + val packageFilter = PackageFilter( + type = FilterType.PACKAGE_NAME, + filter = "com.example.*", + profileId = "profile" + ) + + // When + composeTestRule.setContent { + DevDrawerTheme { PackageFilterInfoDialog(packageFilter = packageFilter, onDismiss = {}) } + } + + // Then + assertSentenceCase("Package name") + } + + private fun assertSentenceCase(vararg labels: String) { + labels.forEach { label -> + composeTestRule.onNodeWithText(label).assertExists() + composeTestRule.onNodeWithText(label.uppercase()).assertDoesNotExist() + composeTestRule.onNodeWithText(label.lowercase()).assertDoesNotExist() + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 63887820..e89e1f86 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,6 +53,8 @@ androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" } androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } +androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" } +androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" } app-update-ktx = { module = "com.google.android.play:app-update-ktx", version.ref = "playUpdate" } androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3" }