v2026.06.03 — Media3 like button + Bluetooth/UPnP picker + system playlist daily rotation #77
@@ -80,19 +80,6 @@ android {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
// JVM unit tests run against android.jar's stub methods,
|
||||
// which throw `RuntimeException("Method ... not mocked")`
|
||||
// when touched (including statics like Bundle.EMPTY).
|
||||
// Return defaults instead so tests that pass Bundles
|
||||
// through without inspecting them (e.g. SessionCommand
|
||||
// construction in LikeMediaCallbackTest) just work,
|
||||
// without dragging in Robolectric.
|
||||
isReturnDefaultValues = true
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes +=
|
||||
setOf(
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
package com.fabledsword.minstrel.player
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.session.SessionCommand
|
||||
import androidx.media3.session.SessionResult
|
||||
import com.fabledsword.minstrel.likes.data.LikesRepository
|
||||
import com.fabledsword.minstrel.likes.data.LikesRepository.Companion.ENTITY_TRACK
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class LikeMediaCallbackTest {
|
||||
|
||||
private val scope = TestScope(UnconfinedTestDispatcher())
|
||||
private val likes = mockk<LikesRepository>(relaxed = true)
|
||||
private val session = mockk<MediaSession>()
|
||||
private val controllerInfo = mockk<MediaSession.ControllerInfo>()
|
||||
private val callback = LikeMediaCallback(likes, scope)
|
||||
|
||||
@Test
|
||||
fun `onConnect adds CMD_TOGGLE_LIKE to available session commands`() {
|
||||
val result = callback.onConnect(session, controllerInfo)
|
||||
assertTrue(
|
||||
result.availableSessionCommands.contains(
|
||||
SessionCommand(LikeMediaCallback.CMD_TOGGLE_LIKE, Bundle.EMPTY),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onCustomCommand routes toggle when current track has a mediaId`() {
|
||||
val item = MediaItem.Builder().setMediaId("track-abc").build()
|
||||
val player = mockk<Player> { every { currentMediaItem } returns item }
|
||||
every { session.player } returns player
|
||||
coEvery { likes.observeIsLiked(ENTITY_TRACK, "track-abc") } returns flowOf(false)
|
||||
|
||||
val future = callback.onCustomCommand(
|
||||
session,
|
||||
controllerInfo,
|
||||
SessionCommand(LikeMediaCallback.CMD_TOGGLE_LIKE, Bundle.EMPTY),
|
||||
Bundle.EMPTY,
|
||||
)
|
||||
|
||||
assertEquals(SessionResult.RESULT_SUCCESS, future.get().resultCode)
|
||||
coVerify(exactly = 1) { likes.toggleLike(ENTITY_TRACK, "track-abc", true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onCustomCommand inverts current liked state`() {
|
||||
val item = MediaItem.Builder().setMediaId("track-xyz").build()
|
||||
val player = mockk<Player> { every { currentMediaItem } returns item }
|
||||
every { session.player } returns player
|
||||
coEvery { likes.observeIsLiked(ENTITY_TRACK, "track-xyz") } returns flowOf(true)
|
||||
|
||||
callback.onCustomCommand(
|
||||
session,
|
||||
controllerInfo,
|
||||
SessionCommand(LikeMediaCallback.CMD_TOGGLE_LIKE, Bundle.EMPTY),
|
||||
Bundle.EMPTY,
|
||||
)
|
||||
|
||||
coVerify(exactly = 1) { likes.toggleLike(ENTITY_TRACK, "track-xyz", false) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onCustomCommand is no-op when no currentMediaItem`() {
|
||||
val player = mockk<Player> { every { currentMediaItem } returns null }
|
||||
every { session.player } returns player
|
||||
|
||||
val future = callback.onCustomCommand(
|
||||
session,
|
||||
controllerInfo,
|
||||
SessionCommand(LikeMediaCallback.CMD_TOGGLE_LIKE, Bundle.EMPTY),
|
||||
Bundle.EMPTY,
|
||||
)
|
||||
|
||||
assertEquals(SessionResult.RESULT_INFO_SKIPPED, future.get().resultCode)
|
||||
coVerify(exactly = 0) { likes.toggleLike(any(), any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onCustomCommand rejects unknown actions`() {
|
||||
val future = callback.onCustomCommand(
|
||||
session,
|
||||
controllerInfo,
|
||||
SessionCommand("garbage", Bundle.EMPTY),
|
||||
Bundle.EMPTY,
|
||||
)
|
||||
assertEquals(SessionResult.RESULT_ERROR_NOT_SUPPORTED, future.get().resultCode)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user