mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
test(overview): Cover the background-monitoring-off signal
Adds monitoringStatus decision tests to OverviewViewModelTest plus Compose tests for the new dashboard card and the missing-paired-device banner.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
package eu.darken.capod.main.ui.overview
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.ui.semantics.SemanticsActions
|
||||
import androidx.compose.ui.test.assertCountEquals
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performSemanticsAction
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.compose.PreviewWrapper
|
||||
import eu.darken.capod.main.ui.overview.cards.components.MissingPairedDeviceBanner
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import testhelpers.compose.BaseComposeRobolectricTest
|
||||
|
||||
class MissingPairedDeviceBannerTest : BaseComposeRobolectricTest() {
|
||||
|
||||
private val context: Context
|
||||
get() = ApplicationProvider.getApplicationContext()
|
||||
|
||||
@Test
|
||||
fun `states the missing paired device and what it costs`() {
|
||||
composeRule.setContent {
|
||||
PreviewWrapper {
|
||||
MissingPairedDeviceBanner(onClick = {})
|
||||
}
|
||||
}
|
||||
|
||||
composeRule.onAllNodesWithText(context.getString(R.string.overview_card_missing_paired_device))
|
||||
.assertCountEquals(1)
|
||||
composeRule.onAllNodesWithText(context.getString(R.string.overview_card_missing_paired_device_consequence))
|
||||
.assertCountEquals(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tapping the banner invokes the callback`() {
|
||||
var clicked = false
|
||||
|
||||
composeRule.setContent {
|
||||
PreviewWrapper {
|
||||
MissingPairedDeviceBanner(onClick = { clicked = true })
|
||||
}
|
||||
}
|
||||
|
||||
composeRule.onNodeWithText(context.getString(R.string.overview_card_missing_paired_device))
|
||||
.performSemanticsAction(SemanticsActions.OnClick)
|
||||
|
||||
composeRule.runOnIdle {
|
||||
assertTrue(clicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package eu.darken.capod.main.ui.overview
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.ui.semantics.SemanticsActions
|
||||
import androidx.compose.ui.test.assertCountEquals
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performSemanticsAction
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.compose.PreviewWrapper
|
||||
import eu.darken.capod.common.compose.preview.MOCK_NOW
|
||||
import eu.darken.capod.common.compose.preview.MockPodDataProvider
|
||||
import eu.darken.capod.main.core.MonitorMode
|
||||
import eu.darken.capod.pods.core.apple.PodModel
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import testhelpers.compose.BaseComposeRobolectricTest
|
||||
|
||||
class OverviewScreenMonitoringTest : BaseComposeRobolectricTest() {
|
||||
|
||||
private val context: Context
|
||||
get() = ApplicationProvider.getApplicationContext()
|
||||
|
||||
private fun state(
|
||||
effectiveMode: MonitorMode,
|
||||
address: String?,
|
||||
) = OverviewViewModel.State(
|
||||
now = MOCK_NOW,
|
||||
permissions = emptySet(),
|
||||
devices = emptyList(),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = effectiveMode,
|
||||
profiles = listOf(
|
||||
MockPodDataProvider.profile("My Headphones", PodModel.AIRPODS_PRO2, address = address),
|
||||
),
|
||||
upgradeInfo = MockPodDataProvider.fossInfo(),
|
||||
showUnmatchedDevices = false,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `MANUAL mode states that background monitoring is off`() {
|
||||
composeRule.setContent {
|
||||
PreviewWrapper {
|
||||
OverviewScreen(
|
||||
state = state(effectiveMode = MonitorMode.MANUAL, address = null),
|
||||
onRequestPermission = {},
|
||||
onBluetoothSettings = {},
|
||||
onManageDevices = {},
|
||||
onSettings = {},
|
||||
onUpgrade = {},
|
||||
onToggleUnmatched = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeRule.onAllNodesWithText(context.getString(R.string.overview_monitoring_off_label))
|
||||
.assertCountEquals(1)
|
||||
composeRule.onAllNodesWithText(context.getString(R.string.overview_monitoring_active_label))
|
||||
.assertCountEquals(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the setup action invokes the callback`() {
|
||||
var clicked = false
|
||||
|
||||
composeRule.setContent {
|
||||
PreviewWrapper {
|
||||
OverviewScreen(
|
||||
state = state(effectiveMode = MonitorMode.MANUAL, address = null),
|
||||
onRequestPermission = {},
|
||||
onBluetoothSettings = {},
|
||||
onManageDevices = {},
|
||||
onSettings = {},
|
||||
onUpgrade = {},
|
||||
onToggleUnmatched = {},
|
||||
onSetupPairedDevice = { clicked = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeRule.onNodeWithText(context.getString(R.string.overview_monitoring_off_action))
|
||||
.performSemanticsAction(SemanticsActions.OnClick)
|
||||
|
||||
composeRule.runOnIdle {
|
||||
assertTrue(clicked)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AUTOMATIC mode without devices keeps the searching card`() {
|
||||
composeRule.setContent {
|
||||
PreviewWrapper {
|
||||
OverviewScreen(
|
||||
state = state(effectiveMode = MonitorMode.AUTOMATIC, address = "AA:BB:CC:DD:EE:FF"),
|
||||
onRequestPermission = {},
|
||||
onBluetoothSettings = {},
|
||||
onManageDevices = {},
|
||||
onSettings = {},
|
||||
onUpgrade = {},
|
||||
onToggleUnmatched = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeRule.onAllNodesWithText(context.getString(R.string.overview_monitoring_active_label))
|
||||
.assertCountEquals(1)
|
||||
composeRule.onAllNodesWithText(context.getString(R.string.overview_monitoring_off_label))
|
||||
.assertCountEquals(0)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import eu.darken.capod.monitor.core.battery.BatteryEstimate
|
||||
import eu.darken.capod.monitor.core.battery.BatteryEstimator
|
||||
import eu.darken.capod.monitor.core.worker.MonitorControl
|
||||
import eu.darken.capod.pods.core.apple.PodModel
|
||||
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
|
||||
import eu.darken.capod.profiles.core.AppleDeviceProfile
|
||||
import eu.darken.capod.profiles.core.DeviceProfile
|
||||
import eu.darken.capod.profiles.core.DeviceProfilesRepo
|
||||
@@ -208,6 +209,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(profiled, unmatched),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = mockk(relaxed = true),
|
||||
showUnmatchedDevices = false,
|
||||
@@ -235,6 +237,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(profiled, unmatched),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = mockk(relaxed = true),
|
||||
showUnmatchedDevices = false,
|
||||
@@ -257,6 +260,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = emptyList(),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = upgradeInfo,
|
||||
showUnmatchedDevices = false,
|
||||
@@ -281,6 +285,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(profiled),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = upgradeInfo,
|
||||
showUnmatchedDevices = false,
|
||||
@@ -307,6 +312,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(device1, device2, device3),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = upgradeInfo,
|
||||
showUnmatchedDevices = false,
|
||||
@@ -333,6 +339,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(device1, device2, device3),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = upgradeInfo,
|
||||
showUnmatchedDevices = false,
|
||||
@@ -361,6 +368,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(device1, device2, device3),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = upgradeInfo,
|
||||
showUnmatchedDevices = false,
|
||||
@@ -387,6 +395,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(device1, device2),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = upgradeInfo,
|
||||
showUnmatchedDevices = false,
|
||||
@@ -413,6 +422,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(profiled1, profiled2, unmatched),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = upgradeInfo,
|
||||
showUnmatchedDevices = false,
|
||||
@@ -450,6 +460,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(unmatched),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = mockk(relaxed = true),
|
||||
showUnmatchedDevices = false,
|
||||
@@ -475,6 +486,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(unmatched),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = mockk(relaxed = true),
|
||||
showUnmatchedDevices = false,
|
||||
@@ -486,6 +498,127 @@ class OverviewViewModelTest : BaseTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class MonitoringStatusTests {
|
||||
|
||||
private fun addressLessProfile(): DeviceProfile = AppleDeviceProfile(
|
||||
label = "My Headphones",
|
||||
model = PodModel.UNKNOWN,
|
||||
)
|
||||
|
||||
private fun addressedProfile(): DeviceProfile = AppleDeviceProfile(
|
||||
label = "My AirPods",
|
||||
model = PodModel.AIRPODS_PRO2,
|
||||
address = "AA:BB:CC:DD:EE:FF",
|
||||
)
|
||||
|
||||
private fun monitoringState(
|
||||
effectiveMode: MonitorMode,
|
||||
profiles: List<DeviceProfile>,
|
||||
devices: List<PodDevice> = emptyList(),
|
||||
permissions: Set<Permission> = emptySet(),
|
||||
isBluetoothEnabled: Boolean = true,
|
||||
) = OverviewViewModel.State(
|
||||
now = java.time.Instant.now(),
|
||||
permissions = permissions,
|
||||
devices = devices,
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = isBluetoothEnabled,
|
||||
effectiveMode = effectiveMode,
|
||||
profiles = profiles,
|
||||
upgradeInfo = mockk(relaxed = true),
|
||||
showUnmatchedDevices = false,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `MANUAL with an address-less profile reports background monitoring off`() {
|
||||
val state = monitoringState(
|
||||
effectiveMode = MonitorMode.MANUAL,
|
||||
profiles = listOf(addressLessProfile()),
|
||||
)
|
||||
|
||||
state.monitoringStatus shouldBe OverviewViewModel.MonitoringStatus.BACKGROUND_OFF
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MANUAL still reports background monitoring off while an unknown device card shows`() {
|
||||
// The auto-created wildcard profile can match an unrecognised Apple payload, and that
|
||||
// card carries neither the missing-paired-device banner nor an edit action.
|
||||
val unknownDevice = PodDevice(
|
||||
profileId = "auto-created",
|
||||
ble = mockk<BlePodSnapshot>(relaxed = true) {
|
||||
every { model } returns PodModel.UNKNOWN
|
||||
},
|
||||
aap = null,
|
||||
)
|
||||
val state = monitoringState(
|
||||
effectiveMode = MonitorMode.MANUAL,
|
||||
profiles = listOf(addressLessProfile()),
|
||||
devices = listOf(unknownDevice),
|
||||
)
|
||||
|
||||
state.monitoringStatus shouldBe OverviewViewModel.MonitoringStatus.BACKGROUND_OFF
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AUTOMATIC without devices reports searching`() {
|
||||
val state = monitoringState(
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = listOf(addressedProfile()),
|
||||
)
|
||||
|
||||
state.monitoringStatus shouldBe OverviewViewModel.MonitoringStatus.SEARCHING
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hidden while a scan-blocking permission is missing`() {
|
||||
val state = monitoringState(
|
||||
effectiveMode = MonitorMode.MANUAL,
|
||||
profiles = listOf(addressLessProfile()),
|
||||
permissions = setOf(Permission.BLUETOOTH_SCAN),
|
||||
)
|
||||
|
||||
state.monitoringStatus shouldBe OverviewViewModel.MonitoringStatus.HIDDEN
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hidden while bluetooth is disabled`() {
|
||||
val state = monitoringState(
|
||||
effectiveMode = MonitorMode.MANUAL,
|
||||
profiles = listOf(addressLessProfile()),
|
||||
isBluetoothEnabled = false,
|
||||
)
|
||||
|
||||
state.monitoringStatus shouldBe OverviewViewModel.MonitoringStatus.HIDDEN
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hidden when there are no profiles at all`() {
|
||||
val state = monitoringState(
|
||||
effectiveMode = MonitorMode.MANUAL,
|
||||
profiles = emptyList(),
|
||||
)
|
||||
|
||||
state.monitoringStatus shouldBe OverviewViewModel.MonitoringStatus.HIDDEN
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `effectiveMode tracks the resolver`() = runTest(testDispatcher) {
|
||||
effectiveModeFlow.value = MonitorMode.MANUAL
|
||||
val vm = createViewModel()
|
||||
var latest: OverviewViewModel.State? = null
|
||||
backgroundScope.launch { vm.state.collect { latest = it } }
|
||||
|
||||
advanceTimeBy(1_000)
|
||||
latest!!.effectiveMode shouldBe MonitorMode.MANUAL
|
||||
|
||||
effectiveModeFlow.value = MonitorMode.ALWAYS
|
||||
|
||||
advanceTimeBy(1_000)
|
||||
latest!!.effectiveMode shouldBe MonitorMode.ALWAYS
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WorkerAutolaunchTests {
|
||||
|
||||
@@ -751,6 +884,7 @@ class OverviewViewModelTest : BaseTest() {
|
||||
devices = listOf(device),
|
||||
isDebug = false,
|
||||
isBluetoothEnabled = true,
|
||||
effectiveMode = MonitorMode.AUTOMATIC,
|
||||
profiles = emptyList(),
|
||||
upgradeInfo = mockk(relaxed = true),
|
||||
showUnmatchedDevices = false,
|
||||
|
||||
Reference in New Issue
Block a user