settings: exercise the keep-screen-on toggle in tests

The toggle's behaviour was untested: deleting the Modifier.keepScreenOn(),
hard-coding the row's request or emptying the Switch handler all left the
suite green, so none of the three new code paths were actually pinned.

- NowPlayingKeepScreenOnTest renders Now Playing and asserts a view in the
  activity tree holds the screen on while the flag is set and releases it
  once the flag clears.
- SettingsScreenTest now covers off->on and on->off on the row, and the
  Switch itself, instead of only the off->on row click.
- Remember the keep-screen-on flow so NowPlayingRoute stops tearing down
  and rebuilding its DataStore collector on every position tick.
This commit is contained in:
austinried
2026-10-17 01:14:48 +00:00
parent 538cb4177a
commit a562e4b316
3 changed files with 127 additions and 7 deletions
@@ -196,7 +196,7 @@ fun NowPlayingRoute(
userPreferences: UserPreferences = koinInject(),
) {
val state by controller.state.collectAsStateWithLifecycle()
val keepScreenOn by userPreferences.keepScreenOn().collectAsStateWithLifecycle(initialValue = false)
val keepScreenOn by remember { userPreferences.keepScreenOn() }.collectAsStateWithLifecycle(initialValue = false)
val positionMs by controller.positionMs.collectAsStateWithLifecycle()
val sourceId = state.context?.sourceId
val songId = state.item?.id
@@ -0,0 +1,85 @@
package com.subtracks.ui.playback
import android.view.View
import android.view.ViewGroup
import androidx.activity.ComponentActivity
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.model.CoverArtRef
import com.subtracks.playback.PlaybackState
import com.subtracks.playback.QueueItem
import com.subtracks.ui.theme.SubtracksTheme
import com.subtracks.ui.theme.artworkColorsFromSeed
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.GraphicsMode
@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class NowPlayingKeepScreenOnTest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()
private val state =
PlaybackState(
item =
QueueItem(
id = "s-eiirp",
title = "Everything In Its Right Place",
artist = "Radiohead",
album = "Kid A",
coverArtId = "art-al-kid-a",
),
isPlaying = true,
durationMs = 251_000,
hasNext = true,
hasPrevious = false,
)
@Test
fun theScreenOnFlagTracksTheSetting() {
var keepScreenOn by mutableStateOf(true)
composeRule.setContent {
SubtracksTheme {
NowPlayingScreen(
state = state,
positionMs = 62_000,
title = "Kid A",
coverArt = CoverArtRef("art-al-kid-a", "test:art-al-kid-a"),
artwork = artworkColorsFromSeed(android.graphics.Color.rgb(120, 80, 200)),
onBack = {},
onQueue = {},
onPlayPause = {},
onNext = {},
onPrevious = {},
onSeek = {},
keepScreenOn = keepScreenOn,
)
}
}
composeRule.waitForIdle()
assertNotNull("now playing should hold the screen on", viewHoldingTheScreenOn())
composeRule.runOnIdle { keepScreenOn = false }
composeRule.waitForIdle()
assertNull("clearing the setting should release the screen-on flag", viewHoldingTheScreenOn())
}
private fun viewHoldingTheScreenOn(): View? = findKeepingScreenOn(composeRule.activity.window.decorView)
private fun findKeepingScreenOn(view: View): View? {
if (view.keepScreenOn) return view
if (view is ViewGroup) {
for (index in 0 until view.childCount) {
findKeepingScreenOn(view.getChildAt(index))?.let { return it }
}
}
return null
}
}
@@ -1,8 +1,10 @@
package com.subtracks.ui.settings
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.hasAnySibling
import androidx.compose.ui.test.hasScrollToNodeAction
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isToggleable
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
@@ -27,8 +29,44 @@ class SettingsScreenTest {
val composeRule = createAndroidComposeRule<ComponentActivity>()
@Test
fun theKeepScreenOnRowToggles() {
fun theKeepScreenOnRowTurnsTheSettingOn() {
var requested: Boolean? = null
renderSettings(keepScreenOn = false) { requested = it }
composeRule.onNodeWithText("Keep screen on").performClick()
composeRule.waitForIdle()
assertEquals(true, requested)
}
@Test
fun theKeepScreenOnRowTurnsTheSettingOff() {
var requested: Boolean? = null
renderSettings(keepScreenOn = true) { requested = it }
composeRule.onNodeWithText("Keep screen on").performClick()
composeRule.waitForIdle()
assertEquals(false, requested)
}
@Test
fun theKeepScreenOnSwitchTurnsTheSettingOn() {
var requested: Boolean? = null
renderSettings(keepScreenOn = false) { requested = it }
composeRule
.onNode(isToggleable() and hasAnySibling(hasText("Keep screen on")), useUnmergedTree = true)
.performClick()
composeRule.waitForIdle()
assertEquals(true, requested)
}
private fun renderSettings(
keepScreenOn: Boolean,
onKeepScreenOnChange: (Boolean) -> Unit,
) {
composeRule.setContent {
SubtracksTheme {
SettingsScreen(
@@ -40,7 +78,7 @@ class SettingsScreenTest {
downloadQuality = StreamQuality(),
downloadOverMetered = false,
scrobbling = false,
keepScreenOn = false,
keepScreenOn = keepScreenOn,
onSelectSource = {},
onEditServer = {},
onWifiQualityChange = {},
@@ -49,7 +87,7 @@ class SettingsScreenTest {
onDownloadQualityChange = {},
onDownloadOverMeteredChange = {},
onScrobblingChange = {},
onKeepScreenOnChange = { requested = it },
onKeepScreenOnChange = onKeepScreenOnChange,
onAddServer = {},
onOpenDownloads = {},
onOpenLicenses = {},
@@ -60,9 +98,6 @@ class SettingsScreenTest {
}
awaitText("Add server")
composeRule.onNode(hasScrollToNodeAction()).performScrollToNode(hasText("Keep screen on"))
composeRule.onNodeWithText("Keep screen on").performClick()
composeRule.waitForIdle()
assertEquals(true, requested)
}
private fun awaitText(text: String) {