Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.chamika.dashtune

import android.net.Uri
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.io.File

@RunWith(RobolectricTestRunner::class)
class AlbumArtContentProviderExtendedTest {

@After
fun tearDown() {
AlbumArtContentProvider.clearCache(File(System.getProperty("java.io.tmpdir")))
}

// --- URI format edge cases ---

@Test
fun `mapUri handles URI with port number`() {
val httpUri = Uri.parse("http://192.168.1.100:8096/Items/abc123/Images/Primary")

val contentUri = AlbumArtContentProvider.mapUri(httpUri)

assertEquals("content", contentUri.scheme)
assertEquals("com.chamika.dashtune", contentUri.authority)
assertNotNull(contentUri.path)
}

@Test
fun `mapUri handles https URI`() {
val httpsUri = Uri.parse("https://jellyfin.example.com/Items/abc123/Images/Primary")

val contentUri = AlbumArtContentProvider.mapUri(httpsUri)

assertEquals("content", contentUri.scheme)
assertNotNull(contentUri.path)
}

@Test
fun `mapUri handles URI with query parameters`() {
val httpUri = Uri.parse("http://example.com/Items/abc123/Images/Primary?quality=90&maxWidth=256")

val contentUri = AlbumArtContentProvider.mapUri(httpUri)

assertEquals("content", contentUri.scheme)
// Path should be derived from encoded path portion only
assertNotNull(contentUri.path)
}

@Test
fun `originalUri correctly maps back after mapUri with port number`() {
val httpUri = Uri.parse("http://192.168.1.100:8096/Items/abc123/Images/Primary")
val contentUri = AlbumArtContentProvider.mapUri(httpUri)

val result = AlbumArtContentProvider.originalUri(contentUri)

assertEquals(httpUri, result)
}

@Test
fun `mapUri handles long nested paths`() {
val httpUri = Uri.parse("http://example.com/Items/abc123/Images/Primary/Extra/Deep")

val contentUri = AlbumArtContentProvider.mapUri(httpUri)

assertEquals("content", contentUri.scheme)
// Path should replace all / with :
val path = contentUri.path
assertNotNull(path)
assertTrue(path!!.contains(":"))
}

// --- clearCache file deletion tests ---

@Test
fun `clearCache removes entries from uriMap`() {
val httpUri1 = Uri.parse("http://example.com/Items/item1/Images/Primary")
val httpUri2 = Uri.parse("http://example.com/Items/item2/Images/Primary")
val contentUri1 = AlbumArtContentProvider.mapUri(httpUri1)
val contentUri2 = AlbumArtContentProvider.mapUri(httpUri2)

AlbumArtContentProvider.clearCache(File(System.getProperty("java.io.tmpdir")))

assertNull(AlbumArtContentProvider.originalUri(contentUri1))
assertNull(AlbumArtContentProvider.originalUri(contentUri2))
}

// --- Multiple mappings tests ---

@Test
fun `multiple different URIs can coexist in the map`() {
val uris = (1..10).map { Uri.parse("http://example.com/Items/item$it/Images/Primary") }
val contentUris = uris.map { AlbumArtContentProvider.mapUri(it) }

// All mappings should resolve back to original
uris.forEachIndexed { index, httpUri ->
assertEquals(httpUri, AlbumArtContentProvider.originalUri(contentUris[index]))
}
}

@Test
fun `content URI paths are unique for different source URIs`() {
val httpUri1 = Uri.parse("http://example.com/Items/abc/Images/Primary")
val httpUri2 = Uri.parse("http://example.com/Items/def/Images/Primary")

val contentUri1 = AlbumArtContentProvider.mapUri(httpUri1)
val contentUri2 = AlbumArtContentProvider.mapUri(httpUri2)

assertTrue(contentUri1.path != contentUri2.path)
}

// --- Empty path tests ---

@Test
fun `mapUri returns EMPTY for URI with empty encoded path`() {
// A URI whose encodedPath is "/" produces an empty path after substring(1).
// mapUri returns Uri.EMPTY in that case (the ?: branch).
val rootPathUri = Uri.parse("http://example.com/")

val contentUri = AlbumArtContentProvider.mapUri(rootPathUri)

assertNotNull(contentUri)
// path "/" → substring(1) → "" → the resulting content URI has an empty path
assertEquals("", contentUri.path ?: "")
}
}
147 changes: 147 additions & 0 deletions automotive/src/test/java/com/chamika/dashtune/CommandButtonsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.chamika.dashtune

import androidx.annotation.OptIn
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@OptIn(UnstableApi::class)
@RunWith(RobolectricTestRunner::class)
class CommandButtonsTest {

private fun playerWith(repeatMode: Int, shuffleEnabled: Boolean): Player {
val player = mockk<Player>(relaxed = true)
every { player.repeatMode } returns repeatMode
every { player.shuffleModeEnabled } returns shuffleEnabled
return player
}

// --- Button list structure tests ---

@Test
fun `createButtons returns exactly 3 buttons`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_OFF, false))
assertEquals(3, buttons.size)
}

@Test
fun `createButtons returns shuffle then repeat then sync`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_OFF, false))
assertEquals("Toggle Shuffle", buttons[0].displayName.toString())
assertEquals("Toggle repeat", buttons[1].displayName.toString())
assertEquals("Sync Library", buttons[2].displayName.toString())
}

// --- Repeat mode icon tests ---

@Test
fun `repeat button uses ICON_REPEAT_OFF when repeat mode is OFF`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_OFF, false))
val repeatButton = buttons[1]
assertEquals(CommandButton.ICON_REPEAT_OFF, repeatButton.icon)
}

@Test
fun `repeat button uses ICON_REPEAT_ALL when repeat mode is ALL`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_ALL, false))
val repeatButton = buttons[1]
assertEquals(CommandButton.ICON_REPEAT_ALL, repeatButton.icon)
}

@Test
fun `repeat button uses ICON_REPEAT_ONE when repeat mode is ONE`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_ONE, false))
val repeatButton = buttons[1]
assertEquals(CommandButton.ICON_REPEAT_ONE, repeatButton.icon)
}

@Test(expected = IllegalStateException::class)
fun `createButtons throws IllegalStateException for invalid repeat mode`() {
CommandButtons.createButtons(playerWith(99, false))
}

// --- Shuffle mode icon tests ---

@Test
fun `shuffle button uses ICON_SHUFFLE_OFF when shuffle is disabled`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_OFF, false))
val shuffleButton = buttons[0]
assertEquals(CommandButton.ICON_SHUFFLE_OFF, shuffleButton.icon)
}

@Test
fun `shuffle button uses ICON_SHUFFLE_ON when shuffle is enabled`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_OFF, true))
val shuffleButton = buttons[0]
assertEquals(CommandButton.ICON_SHUFFLE_ON, shuffleButton.icon)
}

// --- Sync button tests ---

@Test
fun `sync button uses ICON_SYNC`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_OFF, false))
val syncButton = buttons[2]
assertEquals(CommandButton.ICON_SYNC, syncButton.icon)
}

// --- Session command tests ---

@Test
fun `repeat button has REPEAT_COMMAND session command`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_OFF, false))
val repeatButton = buttons[1]
assertNotNull(repeatButton.sessionCommand)
assertEquals(DashTuneSessionCallback.REPEAT_COMMAND, repeatButton.sessionCommand?.customAction)
}

@Test
fun `shuffle button has SHUFFLE_COMMAND session command`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_OFF, false))
val shuffleButton = buttons[0]
assertNotNull(shuffleButton.sessionCommand)
assertEquals(DashTuneSessionCallback.SHUFFLE_COMMAND, shuffleButton.sessionCommand?.customAction)
}

@Test
fun `sync button has SYNC_COMMAND session command`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_OFF, false))
val syncButton = buttons[2]
assertNotNull(syncButton.sessionCommand)
assertEquals(DashTuneSessionCallback.SYNC_COMMAND, syncButton.sessionCommand?.customAction)
}

// --- Slot tests ---

@Test
fun `all buttons are assigned to SLOT_OVERFLOW`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_OFF, false))
buttons.forEach { button ->
assertTrue(button.slots.contains(CommandButton.SLOT_OVERFLOW))
}
}

// --- Combined state tests ---

@Test
fun `createButtons with shuffle on and repeat all produces correct icons`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_ALL, true))
assertEquals(CommandButton.ICON_SHUFFLE_ON, buttons[0].icon)
assertEquals(CommandButton.ICON_REPEAT_ALL, buttons[1].icon)
}

@Test
fun `createButtons with shuffle off and repeat one produces correct icons`() {
val buttons = CommandButtons.createButtons(playerWith(Player.REPEAT_MODE_ONE, false))
assertEquals(CommandButton.ICON_SHUFFLE_OFF, buttons[0].icon)
assertEquals(CommandButton.ICON_REPEAT_ONE, buttons[1].icon)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.chamika.dashtune

import org.junit.Assert.assertEquals
import org.junit.Test

class DashTuneSessionCallbackTest {

// --- Companion constant tests ---

@Test
fun `LOGIN_COMMAND uses correct namespace`() {
assertEquals("com.chamika.dashtune.COMMAND.LOGIN", DashTuneSessionCallback.LOGIN_COMMAND)
}

@Test
fun `REPEAT_COMMAND uses correct namespace`() {
assertEquals("com.chamika.dashtune.COMMAND.REPEAT", DashTuneSessionCallback.REPEAT_COMMAND)
}

@Test
fun `SHUFFLE_COMMAND uses correct namespace`() {
assertEquals("com.chamika.dashtune.COMMAND.SHUFFLE", DashTuneSessionCallback.SHUFFLE_COMMAND)
}

@Test
fun `SYNC_COMMAND uses correct namespace`() {
assertEquals("com.chamika.dashtune.COMMAND.SYNC", DashTuneSessionCallback.SYNC_COMMAND)
}

@Test
fun `PLAYLIST_IDS_PREF has expected value`() {
assertEquals("playlistIds", DashTuneSessionCallback.PLAYLIST_IDS_PREF)
}

@Test
fun `PLAYLIST_INDEX_PREF has expected value`() {
assertEquals("playlistIndex", DashTuneSessionCallback.PLAYLIST_INDEX_PREF)
}

@Test
fun `PLAYLIST_TRACK_POSITON_MS_PREF has expected value`() {
assertEquals("playlistTrackPositionMs", DashTuneSessionCallback.PLAYLIST_TRACK_POSITON_MS_PREF)
}

@Test
fun `all command constants are unique`() {
val commands = setOf(
DashTuneSessionCallback.LOGIN_COMMAND,
DashTuneSessionCallback.REPEAT_COMMAND,
DashTuneSessionCallback.SHUFFLE_COMMAND,
DashTuneSessionCallback.SYNC_COMMAND
)
assertEquals(4, commands.size)
}

@Test
fun `all pref key constants are unique`() {
val keys = setOf(
DashTuneSessionCallback.PLAYLIST_IDS_PREF,
DashTuneSessionCallback.PLAYLIST_INDEX_PREF,
DashTuneSessionCallback.PLAYLIST_TRACK_POSITON_MS_PREF
)
assertEquals(3, keys.size)
}
}
Loading
Loading