v2026.06.03 — Media3 like button + Bluetooth/UPnP picker + system playlist daily rotation #77
@@ -80,19 +80,6 @@ android {
|
|||||||
buildConfig = true
|
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 {
|
packaging {
|
||||||
resources.excludes +=
|
resources.excludes +=
|
||||||
setOf(
|
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