Spiked #150 against the AOSP Settings source (not just the reference docs): no public, non-hidden Settings action opens the "Unrestricted/Optimized/Restricted" screen directly for a specific package. ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL would, but it's @hide/non-SDK; the other public battery action, ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS, isn't package-scoped and is a worse landing for one known app. So ACTION_APPLICATION_DETAILS_SETTINGS (one tap from the target via "Battery" on stock/Pixel/AOSP) stays the primary target. BatteryOptimizationManager.settingsIntent() is restructured into a verified, never-dead-end fallback chain: try app-details, and if it doesn't resolve on some device, fall back to the battery-optimization list rather than nothing. The ordering/selection logic is extracted into a small Android-free helper so it's directly unit-testable; a new instrumented test checks the real candidate intents/order against a real PackageManager. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
1.9 KiB
Kotlin
50 lines
1.9 KiB
Kotlin
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
package org.libremail.push
|
|
|
|
import org.junit.Test
|
|
import kotlin.test.assertEquals
|
|
import kotlin.test.assertFailsWith
|
|
|
|
/**
|
|
* Unit tests for the `firstResolvableOrElseLast` fallback-selection helper that backs
|
|
* [BatteryOptimizationManager.settingsIntent] (#150). Deliberately exercised with plain `String`
|
|
* candidates rather than real `Intent`s: `Intent`/`Uri`/`PackageManager` are unmocked Android SDK stubs
|
|
* in a JVM unit test and throw on use, so the selection/ordering logic is kept generic and Android-free
|
|
* specifically so it's testable here (see its KDoc). The real candidate intents and end-to-end
|
|
* resolution are covered by the instrumented `BatteryOptimizationStepTest`.
|
|
*/
|
|
class BatteryOptimizationManagerTest {
|
|
|
|
@Test
|
|
fun `returns the first candidate that resolves`() {
|
|
assertEquals("b", firstResolvableOrElseLast(listOf("a", "b", "c")) { it == "b" })
|
|
}
|
|
|
|
@Test
|
|
fun `returns the only candidate when it is the first checked, even if it would not resolve`() {
|
|
assertEquals("only", firstResolvableOrElseLast(listOf("only")) { it == "only" })
|
|
}
|
|
|
|
@Test
|
|
fun `falls back to the last candidate when none resolve, rather than dead-ending`() {
|
|
assertEquals("c", firstResolvableOrElseLast(listOf("a", "b", "c")) { false })
|
|
}
|
|
|
|
@Test
|
|
fun `falls back to the last candidate even when it is the only one and it does not resolve`() {
|
|
assertEquals("only", firstResolvableOrElseLast(listOf("only")) { false })
|
|
}
|
|
|
|
@Test
|
|
fun `prefers an earlier resolving candidate over a later one, even if both resolve`() {
|
|
assertEquals("first", firstResolvableOrElseLast(listOf("first", "second")) { true })
|
|
}
|
|
|
|
@Test
|
|
fun `rejects an empty candidate list rather than silently returning nothing`() {
|
|
assertFailsWith<IllegalArgumentException> {
|
|
firstResolvableOrElseLast(emptyList<String>()) { true }
|
|
}
|
|
}
|
|
}
|