package com.doublesymmetry.trackplayer.models import org.junit.Test import org.junit.Assert.* import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class BrowseNavigationTest { // Helper to build a tree with two categories, each having different item types private fun buildTree(): BrowseTree { return BrowseTree(listOf( BrowseCategory( mediaId = "cat-music", title = "Music", items = listOf( BrowseMediaItem( mediaId = "track-1", url = "https://example.com/track1.mp3", title = "Track 1", artist = "Artist A", artworkUrl = "https://example.com/art.jpg", ), BrowseMediaItem( mediaId = "album-1", title = "Album", children = listOf( BrowseMediaItem( mediaId = "track-2", url = "https://example.com/track2.mp3", title = "Track 2", artist = "Artist B", ) ), ), ), ), BrowseCategory( mediaId = "cat-podcasts", title = "Podcasts", items = listOf( BrowseMediaItem( mediaId = "episode-1", url = "https://example.com/ep1.mp3", title = "Episode 1", ) ), ), )) } // --- Simulating onGetChildren root logic --- // Root: return categories mapped to browsable MediaItems @Test fun `root returns all categories as browsable items`() { val tree = buildTree() // Simulate what onGetChildren("root") does: map categories to MediaItems val items = tree.categories.map { cat -> BrowseMediaItem( mediaId = cat.mediaId, title = cat.title, children = cat.items, // presence of children makes it browsable ).toMediaItem() } assertEquals(2, items.size) items.forEach { item -> assertEquals(true, item.mediaMetadata.isBrowsable) assertEquals(false, item.mediaMetadata.isPlayable) } assertEquals("cat-music", items[0].mediaId) assertEquals("cat-podcasts", items[1].mediaId) assertEquals("Music", items[0].mediaMetadata.title.toString()) } // --- Simulating onGetChildren for a category parentId --- @Test fun `category returns its direct children`() { val tree = buildTree() val category = tree.findCategory("cat-music") assertNotNull(category) val items = category!!.items.map { it.toMediaItem() } assertEquals(2, items.size) // First child is a playable track val track = items[0] assertEquals("track-1", track.mediaId) assertEquals(true, track.mediaMetadata.isPlayable) assertEquals(false, track.mediaMetadata.isBrowsable) // Second child is a browsable album val album = items[1] assertEquals("album-1", album.mediaId) assertEquals(false, album.mediaMetadata.isPlayable) assertEquals(true, album.mediaMetadata.isBrowsable) } // --- Simulating onGetChildren for a browsable item parentId --- @Test fun `browsable item returns its children`() { val tree = buildTree() val item = tree.findItem("album-1") assertNotNull(item) assertNotNull(item!!.children) val children = item.children!!.map { it.toMediaItem() } assertEquals(1, children.size) assertEquals("track-2", children[0].mediaId) assertEquals(true, children[0].mediaMetadata.isPlayable) } @Test fun `unknown parentId returns null for both lookups`() { val tree = buildTree() val category = tree.findCategory("nonexistent") val item = tree.findItem("nonexistent") assertNull(category) assertNull(item) } @Test fun `playable items in category have correct metadata`() { val tree = buildTree() val category = tree.findCategory("cat-music")!! val playableItem = category.items.first { it.isPlayable } val mediaItem = playableItem.toMediaItem() assertEquals("track-1", mediaItem.mediaId) assertEquals("Track 1", mediaItem.mediaMetadata.title.toString()) assertEquals("Artist A", mediaItem.mediaMetadata.artist.toString()) assertNotNull(mediaItem.localConfiguration?.uri) assertEquals("https://example.com/track1.mp3", mediaItem.localConfiguration!!.uri.toString()) assertEquals(true, mediaItem.mediaMetadata.isPlayable) assertEquals(false, mediaItem.mediaMetadata.isBrowsable) } @Test fun `browsable items have no URI and isBrowsable is true`() { val tree = buildTree() val category = tree.findCategory("cat-music")!! val browsableItem = category.items.first { it.isBrowsable } val mediaItem = browsableItem.toMediaItem() assertEquals("album-1", mediaItem.mediaId) assertEquals(true, mediaItem.mediaMetadata.isBrowsable) assertEquals(false, mediaItem.mediaMetadata.isPlayable) // Browsable items have no local configuration (no URI set) assertNull(mediaItem.localConfiguration) } @Test fun `category lookup works across multiple categories`() { val tree = buildTree() val podcasts = tree.findCategory("cat-podcasts") assertNotNull(podcasts) assertEquals("Podcasts", podcasts!!.title) assertEquals(1, podcasts.items.size) assertEquals("episode-1", podcasts.items[0].mediaId) } @Test fun `toMediaItem sets correct artwork URI for playable items`() { val item = BrowseMediaItem( mediaId = "t1", url = "https://example.com/t.mp3", artworkUrl = "https://example.com/art.jpg", ) val mediaItem = item.toMediaItem() assertNotNull(mediaItem.mediaMetadata.artworkUri) assertEquals("https://example.com/art.jpg", mediaItem.mediaMetadata.artworkUri.toString()) } @Test fun `toMediaItem stores duration and isLive in extras`() { val item = BrowseMediaItem( mediaId = "live-1", url = "https://example.com/live.mp3", duration = 3600.0, isLive = true, ) val mediaItem = item.toMediaItem() val extras = mediaItem.mediaMetadata.extras assertNotNull(extras) assertEquals(3600.0, extras!!.getDouble("duration"), 0.001) assertTrue(extras.getBoolean("isLive")) // Live items get a LiveConfiguration assertNotNull(mediaItem.liveConfiguration) } @Test fun `empty category returns empty children list`() { val tree = BrowseTree(listOf( BrowseCategory(mediaId = "empty-cat", title = "Empty", items = emptyList()) )) val category = tree.findCategory("empty-cat") assertNotNull(category) assertTrue(category!!.items.isEmpty()) } }