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" }