fix: clean up flaky, duplicated, and misplaced tests; remove redundant deps (#5048)

This commit is contained in:
James Rich 2026-04-10 14:46:45 -05:00 committed by GitHub
parent e70dabe94d
commit 02f6fd67b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 11 additions and 676 deletions

View file

@ -294,8 +294,6 @@ jobs:
tasks+=(
"app:connectedFdroidDebugAndroidTest"
"app:connectedGoogleDebugAndroidTest"
"core:barcode:connectedFdroidDebugAndroidTest"
"core:barcode:connectedGoogleDebugAndroidTest"
)
fi

View file

@ -53,8 +53,5 @@ dependencies {
testRuntimeOnly(libs.junit.vintage.engine)
testImplementation(libs.robolectric)
testImplementation(libs.androidx.compose.ui.test.junit4)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.test.manifest)
}

View file

@ -1,29 +0,0 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.barcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BarcodeScannerTest {
@Test
fun placeholder() {
// Placeholder for AndroidTest
}
}

View file

@ -1,49 +0,0 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.common.util
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class CommonUriTest {
@Test
fun testParse() {
val uri = CommonUri.parse("https://meshtastic.org/path/to/page?param1=value1&param2=true#fragment")
assertEquals("meshtastic.org", uri.host)
assertEquals("fragment", uri.fragment)
assertEquals(listOf("path", "to", "page"), uri.pathSegments)
assertEquals("value1", uri.getQueryParameter("param1"))
assertTrue(uri.getBooleanQueryParameter("param2", false))
}
@Test
fun testBooleanParameters() {
val uri = CommonUri.parse("meshtastic://test?t1=true&t2=1&t3=yes&f1=false&f2=0")
assertTrue(uri.getBooleanQueryParameter("t1", false))
assertTrue(uri.getBooleanQueryParameter("t2", false))
assertTrue(uri.getBooleanQueryParameter("t3", false))
assertTrue(!uri.getBooleanQueryParameter("f1", true))
assertTrue(!uri.getBooleanQueryParameter("f2", true))
}
}

View file

@ -57,10 +57,8 @@ kotlin {
dependencies {
implementation(libs.androidx.sqlite.bundled)
implementation(libs.androidx.room.testing)
implementation(libs.androidx.test.core)
implementation(libs.androidx.test.ext.junit)
implementation(libs.junit)
implementation(libs.robolectric)
}
}
val androidDeviceTest by getting {

View file

@ -1,49 +0,0 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.model
import org.meshtastic.proto.HardwareModel
import kotlin.test.Test
import kotlin.test.assertEquals
class NodeTest {
@Test
fun `createFallback produces expected node data`() {
val nodeNum = 0x12345678
val prefix = "Node"
val node = Node.createFallback(nodeNum, prefix)
assertEquals(nodeNum, node.num)
assertEquals("!12345678", node.user.id)
assertEquals("Node 5678", node.user.long_name)
assertEquals("5678", node.user.short_name)
assertEquals(HardwareModel.UNSET, node.user.hw_model)
}
@Test
fun `createFallback pads short IDs with zeros`() {
val nodeNum = 0x1
val prefix = "Node"
val node = Node.createFallback(nodeNum, prefix)
assertEquals(nodeNum, node.num)
assertEquals("!00000001", node.user.id)
assertEquals("Node 0001", node.user.long_name)
assertEquals("0001", node.user.short_name)
}
}

View file

@ -52,13 +52,6 @@ kotlin {
api(libs.androidx.annotation)
api(libs.androidx.core.ktx)
}
val androidHostTest by getting {
dependencies {
implementation(libs.junit)
implementation(libs.robolectric)
implementation(libs.androidx.test.ext.junit)
}
}
val androidDeviceTest by getting {
dependencies {
implementation(libs.androidx.test.ext.junit)

View file

@ -60,8 +60,6 @@ kotlin {
val androidHostTest by getting {
dependencies {
implementation(projects.core.testing)
implementation(libs.robolectric)
implementation(libs.androidx.test.core)
implementation(libs.androidx.test.ext.junit)
implementation(libs.androidx.work.testing)
}

View file

@ -16,12 +16,17 @@
*/
package org.meshtastic.core.service
import org.junit.runner.RunWith
import org.meshtastic.core.service.testing.FakeIMeshService
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
/** Test to verify that the AIDL contract is correctly implemented by our test harness. */
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class IMeshServiceContractTest {
@Test

View file

@ -1,143 +0,0 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.service
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.IInterface
import dev.mokkery.MockMode
import dev.mokkery.every
import dev.mokkery.matcher.any
import dev.mokkery.matcher.capture.Capture
import dev.mokkery.matcher.capture.capture
import dev.mokkery.mock
import dev.mokkery.verify
import dev.mokkery.verify.exactly
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.fail
@OptIn(ExperimentalCoroutinesApi::class)
class ServiceClientTest {
interface MyInterface : IInterface
private val stubFactory: (IBinder) -> MyInterface = { _ -> mock<MyInterface>() }
private val client = ServiceClient(stubFactory)
private val context = mock<Context>(MockMode.autofill)
private val intent = mock<Intent>()
private val binder = mock<IBinder>()
@Test
fun `connect binds service successfully`() = runTest {
val slot = Capture.slot<ServiceConnection>()
every { context.bindService(any(), capture(slot), any()) } returns true
client.connect(context, intent, 0)
verify { context.bindService(intent, any(), 0) }
// Simulate connection
try {
slot.get().onServiceConnected(ComponentName("pkg", "cls"), binder)
assertNotNull(client.serviceP)
} catch (e: NoSuchElementException) {
fail("ServiceConnection was not captured")
}
}
@Test
fun `connect retries on failure`() = runTest {
val slot = Capture.slot<ServiceConnection>()
// First attempt fails, second succeeds
every { context.bindService(any(), capture(slot), any()) } sequentially
{
returns(false)
returns(true)
}
client.connect(context, intent, 0)
verify(exactly(2)) { context.bindService(intent, any(), 0) }
}
@Test
fun `connect throws exception after two failures`() = runTest {
every { context.bindService(any(), any(), any()) } returns false
assertFailsWith<BindFailedException> { client.connect(context, intent, 0) }
}
@Test
fun `waitConnect blocks until connected`() {
val slot = Capture.slot<ServiceConnection>()
every { context.bindService(any(), capture(slot), any()) } returns true
// Run connect in a coroutine scope (it's suspend)
runTest { client.connect(context, intent, 0) }
val latch = CountDownLatch(1)
thread {
client.waitConnect()
latch.countDown()
}
// Verify it's blocked (wait a bit)
if (latch.await(100, TimeUnit.MILLISECONDS)) {
fail("waitConnect should block until connected")
}
// Simulate connection
try {
slot.get().onServiceConnected(ComponentName("pkg", "cls"), binder)
} catch (e: NoSuchElementException) {
fail("ServiceConnection was not captured")
}
// Verify it unblocks
if (!latch.await(1, TimeUnit.SECONDS)) {
fail("waitConnect should unblock after connection")
}
assertNotNull(client.serviceP)
}
@Test
fun `close unbinds service`() = runTest {
val slot = Capture.slot<ServiceConnection>()
every { context.bindService(any(), capture(slot), any()) } returns true
client.connect(context, intent, 0)
try {
client.close()
verify { context.unbindService(slot.get()) }
assertNull(client.serviceP)
} catch (e: NoSuchElementException) {
fail("ServiceConnection was not captured")
}
}
}

View file

@ -1,55 +0,0 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.timezone
import kotlinx.datetime.TimeZone
import org.meshtastic.core.model.util.toPosixString
import kotlin.test.Test
import kotlin.test.assertEquals
class ZoneIdExtensionsTest {
@Test
fun `test POSIX string generation`() {
val zoneMap =
mapOf(
"US/Hawaii" to "HST10",
"US/Alaska" to "AKST9AKDT,M3.2.0,M11.1.0",
"US/Pacific" to "PST8PDT,M3.2.0,M11.1.0",
"US/Arizona" to "MST7",
"US/Mountain" to "MST7MDT,M3.2.0,M11.1.0",
"US/Central" to "CST6CDT,M3.2.0,M11.1.0",
"US/Eastern" to "EST5EDT,M3.2.0,M11.1.0",
"America/Sao_Paulo" to "BRT3",
"UTC" to "UTC0",
"Europe/London" to "GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Lisbon" to "WET0WEST,M3.5.0/1,M10.5.0",
"Europe/Budapest" to "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Kiev" to "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Africa/Cairo" to "EET-2EEST,M4.5.5/0,M10.5.5/0",
"Asia/Kolkata" to "IST-5:30",
"Asia/Hong_Kong" to "HKT-8",
"Asia/Tokyo" to "JST-9",
"Australia/Perth" to "AWST-8",
"Australia/Adelaide" to "ACST-9:30ACDT,M10.1.0,M4.1.0/3",
"Australia/Sydney" to "AEST-10AEDT,M10.1.0,M4.1.0/3",
"Pacific/Auckland" to "NZST-12NZDT,M9.5.0,M4.1.0/3",
)
zoneMap.forEach { (tz, expected) -> assertEquals(expected, TimeZone.of(tz).toPosixString()) }
}
}

View file

@ -49,12 +49,5 @@ kotlin {
}
androidMain.dependencies { implementation(libs.usb.serial.android) }
val androidHostTest by getting {
dependencies {
implementation(libs.androidx.test.core)
implementation(libs.robolectric)
}
}
}
}

View file

@ -58,16 +58,9 @@ kotlin {
androidMain.dependencies { implementation(libs.markdown.renderer.android) }
commonTest.dependencies {
implementation(projects.core.testing)
implementation(libs.turbine)
}
val androidHostTest by getting {
dependencies {
implementation(libs.junit)
implementation(libs.robolectric)
implementation(libs.turbine)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.androidx.compose.ui.test.junit4)
implementation(libs.androidx.test.ext.junit)

View file

@ -42,9 +42,7 @@ kotlin {
val androidHostTest by getting {
dependencies {
implementation(libs.junit)
implementation(libs.robolectric)
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.androidx.test.core)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.androidx.compose.ui.test.junit4)
}

View file

@ -47,10 +47,8 @@ kotlin {
val androidHostTest by getting {
dependencies {
implementation(libs.junit)
implementation(libs.robolectric)
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.kotlinx.coroutines.test)
implementation(libs.androidx.test.core)
}
}
}

View file

@ -56,12 +56,6 @@ kotlin {
androidMain.dependencies { implementation(libs.androidx.work.runtime.ktx) }
val androidHostTest by getting {
dependencies {
implementation(libs.androidx.work.testing)
implementation(libs.androidx.test.core)
implementation(libs.robolectric)
}
}
val androidHostTest by getting { dependencies { implementation(libs.androidx.work.testing) } }
}
}

View file

@ -27,8 +27,8 @@ class HomoglyphCharacterTransformTest {
fun `optimizeUtf8StringWithHomoglyphs shrinks binary size of cyrillic text containing some homoglyphs`() {
val testString = "Мештастик - это проект с открытым исходным кодом"
val transformedTestString = HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(testString)
val testStringBytes = testString.toByteArray(charset = Charsets.UTF_8)
val transformedTestStringBytes = transformedTestString.toByteArray(charset = Charsets.UTF_8)
val testStringBytes = testString.encodeToByteArray()
val transformedTestStringBytes = transformedTestString.encodeToByteArray()
val transformedStringBinarySizeShrinked = transformedTestStringBytes.size < testStringBytes.size
assertTrue(transformedStringBinarySizeShrinked)
}
@ -37,8 +37,8 @@ class HomoglyphCharacterTransformTest {
fun `optimizeUtf8StringWithHomoglyphs shrinks binary size in half of cyrillic text containing only homoglyphs`() {
val testString = "Косуха"
val transformedTestString = HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(testString)
val testStringBytes = testString.toByteArray(charset = Charsets.UTF_8)
val transformedTestStringBytes = transformedTestString.toByteArray(charset = Charsets.UTF_8)
val testStringBytes = testString.encodeToByteArray()
val transformedTestStringBytes = transformedTestString.encodeToByteArray()
assertEquals(transformedTestStringBytes.size, testStringBytes.size / 2)
}

View file

@ -66,8 +66,6 @@ kotlin {
val androidHostTest by getting {
dependencies {
implementation(libs.junit)
implementation(libs.robolectric)
implementation(libs.turbine)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.androidx.compose.ui.test.junit4)
implementation(libs.androidx.test.ext.junit)

View file

@ -1,95 +0,0 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.node.metrics
import androidx.compose.material3.Text
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.meshtastic.core.model.TelemetryType
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.device_metrics_log
import org.meshtastic.core.ui.theme.AppTheme
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.assertTrue
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class BaseMetricScreenTest {
@get:Rule val composeTestRule = createComposeRule()
@Test
fun baseMetricScreen_displaysTitleAndNodeName() {
val nodeName = "Test Node 123"
val testData = listOf("Item 1", "Item 2")
composeTestRule.setContent {
AppTheme {
BaseMetricScreen(
onNavigateUp = {},
telemetryType = TelemetryType.DEVICE,
titleRes = Res.string.device_metrics_log,
nodeName = nodeName,
data = testData,
timeProvider = { 0.0 },
chartPart = { _, _, _, _ -> Text("Chart Placeholder") },
listPart = { _, _, _, _ -> Text("List Placeholder") },
)
}
}
// Verify Node Name is displayed (MainAppBar title)
composeTestRule.onNodeWithText(nodeName).assertIsDisplayed()
// Verify Placeholders are displayed
composeTestRule.onNodeWithText("Chart Placeholder").assertIsDisplayed()
composeTestRule.onNodeWithText("List Placeholder").assertIsDisplayed()
}
@Test
fun baseMetricScreen_refreshButtonTriggersCallback() {
var refreshClicked = false
val testData = emptyList<String>()
composeTestRule.setContent {
AppTheme {
BaseMetricScreen(
onNavigateUp = {},
telemetryType = TelemetryType.DEVICE,
titleRes = Res.string.device_metrics_log,
nodeName = "Node",
data = testData,
timeProvider = { 0.0 },
onRequestTelemetry = { refreshClicked = true },
chartPart = { _, _, _, _ -> },
listPart = { _, _, _, _ -> },
)
}
}
composeTestRule.onNodeWithTag("refresh_button").performClick()
assertTrue("Refresh callback should be triggered", refreshClicked)
}
}

View file

@ -57,17 +57,12 @@ kotlin {
implementation(libs.androidx.appcompat)
}
commonTest.dependencies {
implementation(project(":core:testing"))
implementation(project(":core:datastore"))
}
commonTest.dependencies { implementation(project(":core:datastore")) }
val androidHostTest by getting {
dependencies {
implementation(project(":core:datastore"))
implementation(libs.junit)
implementation(libs.robolectric)
implementation(libs.turbine)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.androidx.compose.ui.test.junit4)
implementation(libs.androidx.compose.ui.test.manifest)

View file

@ -1,134 +0,0 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.settings.debugging
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.v2.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.debug_active_filters
import org.meshtastic.core.resources.debug_filters
import org.meshtastic.core.resources.getString
import org.meshtastic.feature.settings.debugging.DebugViewModel.UiMeshLog
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
@Config(sdk = [34])
class DebugFiltersTest {
@get:Rule val composeTestRule = createComposeRule()
@Test
fun debugFilterBar_showsFilterButtonAndMenu() {
val filterLabel = getString(Res.string.debug_filters)
composeTestRule.setContent {
var filterTexts by remember { mutableStateOf(listOf<String>()) }
var customFilterText by remember { mutableStateOf("") }
val presetFilters = listOf("Error", "Warning", "Info")
val logs =
listOf(
UiMeshLog(
uuid = "1",
messageType = "Info",
formattedReceivedDate = "2024-01-01 12:00:00",
logMessage = "Sample log message",
),
)
DebugFilterBar(
filterTexts = filterTexts,
onFilterTextsChange = { filterTexts = it },
customFilterText = customFilterText,
onCustomFilterTextChange = { customFilterText = it },
presetFilters = presetFilters,
logs = logs,
)
}
// The filter button should be visible
composeTestRule.onNodeWithText(filterLabel).assertIsDisplayed()
}
@Test
fun debugFilterBar_addCustomFilter_displaysActiveFilter() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val activeFiltersLabel = getString(Res.string.debug_active_filters)
composeTestRule.setContent {
var filterTexts by remember { mutableStateOf(listOf<String>()) }
var customFilterText by remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp)) {
DebugActiveFilters(
filterTexts = filterTexts,
onFilterTextsChange = { filterTexts = it },
filterMode = FilterMode.OR,
onFilterModeChange = {},
)
DebugCustomFilterInput(
customFilterText = customFilterText,
onCustomFilterTextChange = { customFilterText = it },
filterTexts = filterTexts,
onFilterTextsChange = { filterTexts = it },
)
}
}
with(composeTestRule) {
// Add a custom filter
onNodeWithText("Add custom filter").performTextInput("MyFilter")
onNodeWithContentDescription("Add filter").performClick()
// The active filters label and the filter chip should be visible
onNodeWithText(activeFiltersLabel).assertIsDisplayed()
onNodeWithText("MyFilter").assertIsDisplayed()
}
}
@Test
fun debugActiveFilters_clearAllFilters_removesFilters() {
val activeFiltersLabel = getString(Res.string.debug_active_filters)
composeTestRule.setContent {
var filterTexts by remember { mutableStateOf(listOf("A", "B")) }
DebugActiveFilters(
filterTexts = filterTexts,
onFilterTextsChange = { filterTexts = it },
filterMode = FilterMode.OR,
onFilterModeChange = {},
)
}
// The active filters label and chips should be visible
composeTestRule.onNodeWithText(activeFiltersLabel).assertIsDisplayed()
composeTestRule.onNodeWithText("A").assertIsDisplayed()
composeTestRule.onNodeWithText("B").assertIsDisplayed()
// Click the clear all filters button
composeTestRule.onNodeWithContentDescription("Clear all filters").performClick()
// The filter chips should no longer be visible
composeTestRule.onNodeWithText("A").assertDoesNotExist()
composeTestRule.onNodeWithText("B").assertDoesNotExist()
}
}

View file

@ -1,69 +0,0 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.settings
import android.content.res.Configuration
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.v2.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.getString
import org.meshtastic.core.resources.use_homoglyph_characters_encoding
import org.meshtastic.feature.settings.component.HomoglyphSetting
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import java.util.Locale
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class HomoglyphSettingTest {
@get:Rule val composeTestRule = createComposeRule()
@Test
fun homoglyphSetting_isVisible_forRussianLocale() {
val russianConfig = Configuration().apply { setLocale(Locale.forLanguageTag("ru")) }
composeTestRule.setContent {
CompositionLocalProvider(LocalConfiguration provides russianConfig) {
HomoglyphSetting(homoglyphEncodingEnabled = false, onToggle = {})
}
}
val expectedText = getString(Res.string.use_homoglyph_characters_encoding)
composeTestRule.onNodeWithText(expectedText).assertIsDisplayed()
}
@Test
fun homoglyphSetting_isNotVisible_forEnglishLocale() {
val englishConfig = Configuration().apply { setLocale(Locale.forLanguageTag("en")) }
composeTestRule.setContent {
CompositionLocalProvider(LocalConfiguration provides englishConfig) {
HomoglyphSetting(homoglyphEncodingEnabled = false, onToggle = {})
}
}
val expectedText = getString(Res.string.use_homoglyph_characters_encoding)
composeTestRule.onNodeWithText(expectedText).assertDoesNotExist()
}
}