drop the songs tab and the whole-library song queue

This commit is contained in:
austinried
2026-10-04 00:29:59 +00:00
parent 9cf2c99fb5
commit e3af5ed0ac
25 changed files with 36 additions and 927 deletions
@@ -9,6 +9,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.model.Album
import com.subtracks.data.model.Song
import com.subtracks.data.model.Source
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
@@ -63,29 +64,6 @@ class LibraryDaoTest {
assertEquals(listOf("Alpha"), dao.albumsByName(sourceId, starredFilter = 1, search = "").page().map { it.name })
}
@Test
fun songSortsByTitleAndStarredFilter() =
runTest {
val sourceId = source()
val dao = db.libraryDao()
dao.upsertSongs(
listOf(
song(sourceId, "s1", "Zebra", starred = null),
song(sourceId, "s2", "Apple", starred = 7),
),
)
assertEquals(
listOf("Apple", "Zebra"),
dao.songsByTitle(sourceId, starredFilter = 0, search = "").page().map { it.song.title },
)
assertEquals(
listOf("Zebra", "Apple"),
dao.songsByTitleReversed(sourceId, starredFilter = 0, search = "").page().map { it.song.title },
)
assertEquals(listOf("Apple"), dao.songsByTitle(sourceId, starredFilter = 1, search = "").page().map { it.song.title })
}
@Test
fun albumAddedAndStarredSortsAndSearch() =
runTest {
@@ -112,24 +90,21 @@ class LibraryDaoTest {
}
@Test
fun songAddedSortOrdersByCreated() =
fun albumSongsAreOrderedByDiscAndTrack() =
runTest {
val sourceId = source()
val dao = db.libraryDao()
dao.upsertSongs(
listOf(
song(sourceId, "s1", "Zebra", starred = null, created = 100),
song(sourceId, "s2", "Apple", starred = null, created = 200),
song(sourceId, "s2", "Second", starred = null, track = 2),
song(sourceId, "s3", "Third", starred = null, track = 1, disc = 2),
song(sourceId, "s1", "First", starred = null, track = 1),
),
)
assertEquals(
listOf("Apple", "Zebra"),
dao.songsByAdded(sourceId, starredFilter = 0, search = "").page().map { it.song.title },
)
assertEquals(
listOf("Zebra", "Apple"),
dao.songsByAddedReversed(sourceId, starredFilter = 0, search = "").page().map { it.song.title },
listOf("s1", "s2", "s3"),
dao.songsByAlbum(sourceId, "al-1").first().map { it.id },
)
}
@@ -170,6 +145,8 @@ class LibraryDaoTest {
title: String,
starred: Long?,
created: Long = 0,
track: Long = 1,
disc: Long = 1,
) = Song(
sourceId = sourceId,
id = id,
@@ -179,8 +156,8 @@ class LibraryDaoTest {
album = "Album",
artist = "Artist",
duration = 200,
track = 1,
disc = 1,
track = track,
disc = disc,
starred = starred,
genre = null,
created = created,
@@ -21,15 +21,19 @@ class UserPreferencesTest {
val prefs = UserPreferences(store)
assertEquals(ListQuery("Name"), prefs.listQuery(LibraryListTab.Albums).first())
assertEquals(ListQuery("Album"), prefs.listQuery(LibraryListTab.Songs).first())
assertEquals(ListQuery("Name"), prefs.listQuery(LibraryListTab.Artists).first())
prefs.setListQuery(LibraryListTab.Albums, ListQuery("Added", descending = true, starred = StarredFilter.Starred))
prefs.setListQuery(LibraryListTab.Artists, ListQuery("AlbumCount", descending = true))
assertEquals(
ListQuery("Added", descending = true, starred = StarredFilter.Starred),
prefs.listQuery(LibraryListTab.Albums).first(),
)
assertEquals(ListQuery("Album"), prefs.listQuery(LibraryListTab.Songs).first())
assertEquals(
ListQuery("AlbumCount", descending = true),
prefs.listQuery(LibraryListTab.Artists).first(),
)
file.delete()
}
@@ -12,8 +12,6 @@ import com.subtracks.data.model.QueueEntry
import com.subtracks.data.model.QueueKind
import com.subtracks.data.model.Song
import com.subtracks.data.model.Source
import com.subtracks.data.prefs.SongSort
import com.subtracks.data.prefs.StarredFilter
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
@@ -75,19 +73,6 @@ class QueueRepositoryTest {
assertEquals(listOf("s2", "s3"), resolveAll(snapshot))
}
@Test
fun theSongsEntryResolvesTheWholeLibraryInListOrder() =
runTest {
seedLibrary()
db.libraryDao().upsertAlbums(listOf(album("al0", "Zeroth Album", "Aardvark")))
db.libraryDao().upsertSongs(listOf(song("s0", "al0", track = 1, album = "Zeroth Album")))
val snapshot = repository.snapshotAfter(listOf(repository.songsEntry(1)))
assertEquals(6, snapshot.size)
assertEquals(listOf("s0", "s1", "s2", "s3", "s4", "s5"), resolveAll(snapshot))
}
@Test
fun windowSpansEntryBoundaries() =
runTest {
@@ -314,39 +299,6 @@ class QueueRepositoryTest {
assertEquals(listOf("s2", "s3", "s1"), resolveAll(repository.snapshot()))
}
@Test
fun theSongsEntryFollowsItsSortAndStarredFilter() =
runTest {
seedLibrary()
db.libraryDao().upsertSongs(
listOf(
song("s1", "al1", track = 1, album = "First Album", title = "Charlie", starred = 5),
song("s2", "al1", track = 2, album = "First Album", title = "Alpha"),
song("s3", "al1", track = 3, album = "First Album", title = "Bravo", starred = 7),
song("s4", "al2", track = 1, album = "Second Album", title = "Echo"),
song("s5", "al2", track = 2, album = "Second Album", title = "Delta", starred = 3),
),
)
val byTitle =
repository.snapshotAfter(
listOf(repository.songsEntry(1, QueueSongQuery(SongSort.Title, descending = false, StarredFilter.Any))),
)
assertEquals(listOf("s2", "s3", "s1", "s5", "s4"), resolveAll(byTitle))
val starredOnly =
repository.snapshotAfter(
listOf(repository.songsEntry(1, QueueSongQuery(SongSort.Starred, descending = false, StarredFilter.Starred))),
)
assertEquals(listOf("s3", "s1", "s5"), resolveAll(starredOnly))
val unstarred =
repository.snapshotAfter(
listOf(repository.songsEntry(1, QueueSongQuery(SongSort.Album, descending = false, StarredFilter.NotStarred))),
)
assertEquals(listOf("s2", "s4"), resolveAll(unstarred))
}
private suspend fun QueueRepository.snapshotAfter(entries: List<QueueEntry>): QueueSnapshot {
replace(entries)
return snapshot()
@@ -69,14 +69,7 @@ class SyncServiceTest {
.allRows()
.size,
)
assertEquals(
2,
db
.libraryDao()
.songs(1, 0, "")
.allRows()
.size,
)
assertEquals(2, db.libraryDao().songIds(1).size)
assertEquals(
1,
db
@@ -99,14 +92,7 @@ class SyncServiceTest {
.allRows()
.map { it.id },
)
assertEquals(
listOf("s1"),
db
.libraryDao()
.songs(1, 0, "")
.allRows()
.map { it.song.id },
)
assertEquals(listOf("s1"), db.libraryDao().songIds(1))
}
@Test
@@ -106,19 +106,6 @@ class PlaybackControllerTest {
assertEquals("cover-al1", runBlocking { controller.upcomingItem()?.coverArtId })
}
@Test
fun playingTheSongsListQueuesTheWholeLibrary() {
seedAlbum(60, sourceId = 1)
controller.playSongs(1, 30)
await { handle.operations.contains("play") }
assertEquals(51, handle.itemCount)
assertEquals(QueueContext(QueueKind.Songs, 1, ""), controller.state.value.context)
assertTrue(controller.state.value.hasNext)
assertTrue(controller.state.value.hasPrevious)
}
@Test
fun advancingShiftsTheWindowAndKeepsItBounded() {
seedAlbum(100, sourceId = 1)
@@ -107,13 +107,11 @@ class AppScreenshotTest {
onTabSelected = {},
albums = remember { flowOf(PagingData.from(albums)) },
artists = remember { flowOf(PagingData.empty<Artist>()) },
songs = remember { flowOf(PagingData.empty<SongListItem>()) },
playlists = remember { flowOf(PagingData.empty<Playlist>()) },
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = {},
onOpenSettings = {},
artwork = artworkColorsFromSeed(Color.rgb(120, 80, 200)),
@@ -133,13 +131,6 @@ class AppScreenshotTest {
composeRule.onRoot().captureRoboImage("src/test/screenshots/library_artists.png")
}
@Test
fun librarySongs() {
setLibraryContent(LibraryTab.Songs)
awaitText("Everything In Its Right Place")
composeRule.onRoot().captureRoboImage("src/test/screenshots/library_songs.png")
}
@Test
fun libraryPlaylists() {
setLibraryContent(LibraryTab.Playlists)
@@ -166,13 +157,11 @@ class AppScreenshotTest {
onTabSelected = {},
albums = remember { flowOf(PagingData.from(Fixtures.albums)) },
artists = remember { flowOf(PagingData.from(Fixtures.artists)) },
songs = remember { flowOf(PagingData.from(Fixtures.songItems)) },
playlists = remember { flowOf(PagingData.from(Fixtures.playlists)) },
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = {},
onOpenSettings = {},
artwork = artwork,
@@ -473,13 +462,11 @@ class AppScreenshotTest {
onTabSelected = {},
albums = remember { flowOf(PagingData.from(Fixtures.albums)) },
artists = remember { flowOf(PagingData.from(Fixtures.artists)) },
songs = remember { flowOf(PagingData.from(Fixtures.songItems)) },
playlists = remember { flowOf(PagingData.from(Fixtures.playlists)) },
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = {},
onOpenSettings = {},
artwork = artworkColorsFromSeed(Color.rgb(120, 80, 200)),
@@ -14,7 +14,6 @@ import com.github.takahirom.roborazzi.captureRoboImage
import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist
import com.subtracks.data.model.Playlist
import com.subtracks.data.model.SongListItem
import com.subtracks.data.prefs.ListQuery
import com.subtracks.ui.library.ALBUM_COVER_TAG
import com.subtracks.ui.library.LibraryScreen
@@ -44,13 +43,11 @@ class LibraryOptionsScreenshotTest {
onTabSelected = {},
albums = remember { flowOf(PagingData.from(albums())) },
artists = remember { flowOf(PagingData.empty<Artist>()) },
songs = remember { flowOf(PagingData.empty<SongListItem>()) },
playlists = remember { flowOf(PagingData.empty<Playlist>()) },
coverArt = { _, _ -> null },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = {},
onOpenSettings = {},
listQuery = ListQuery("Name"),
@@ -17,7 +17,6 @@ import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers
import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist
import com.subtracks.data.model.Playlist
import com.subtracks.data.model.SongListItem
import com.subtracks.data.prefs.ListQuery
import com.subtracks.data.prefs.StarredFilter
import com.subtracks.ui.library.ALBUM_COVER_TAG
@@ -50,13 +49,11 @@ class LibraryOptionsTest {
onTabSelected = {},
albums = remember { flowOf(PagingData.from(albums())) },
artists = remember { flowOf(PagingData.empty<Artist>()) },
songs = remember { flowOf(PagingData.empty<SongListItem>()) },
playlists = remember { flowOf(PagingData.empty<Playlist>()) },
coverArt = { _, _ -> null },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = {},
onOpenSettings = {},
listQuery = query,
@@ -23,7 +23,6 @@ import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers
import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist
import com.subtracks.data.model.Playlist
import com.subtracks.data.model.SongListItem
import com.subtracks.ui.library.ALBUM_COVER_TAG
import com.subtracks.ui.library.LibraryScreen
import com.subtracks.ui.library.LibraryTab
@@ -60,13 +59,11 @@ class LibraryScreenTest {
onTabSelected = {},
albums = remember { flowOf(PagingData.from(albums())) },
artists = remember { flowOf(PagingData.empty<Artist>()) },
songs = remember { flowOf(PagingData.empty<SongListItem>()) },
playlists = remember { flowOf(PagingData.empty<Playlist>()) },
coverArt = { _, _ -> null },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = { synced = true },
onOpenSettings = { settingsOpened = true },
)
@@ -95,13 +92,11 @@ class LibraryScreenTest {
onTabSelected = {},
albums = remember { flowOf(PagingData.empty<Album>(settledLoadStates)) },
artists = remember { flowOf(PagingData.empty<Artist>()) },
songs = remember { flowOf(PagingData.empty<SongListItem>()) },
playlists = remember { flowOf(PagingData.empty<Playlist>()) },
coverArt = { _, _ -> null },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = { synced = true },
onOpenSettings = {},
)
@@ -129,11 +124,6 @@ class LibraryScreenTest {
collected += "artists"
emit(PagingData.empty())
}
val songs =
flow<PagingData<SongListItem>> {
collected += "songs"
emit(PagingData.empty())
}
val playlists =
flow<PagingData<Playlist>> {
collected += "playlists"
@@ -146,13 +136,11 @@ class LibraryScreenTest {
onTabSelected = {},
albums = albums,
artists = artists,
songs = songs,
playlists = playlists,
coverArt = { _, _ -> null },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = {},
onOpenSettings = {},
)
@@ -178,11 +166,6 @@ class LibraryScreenTest {
collected += "artists"
emit(PagingData.from(artists()))
}
val songs =
flow<PagingData<SongListItem>> {
collected += "songs"
emit(PagingData.empty())
}
val playlists =
flow<PagingData<Playlist>> {
collected += "playlists"
@@ -196,13 +179,11 @@ class LibraryScreenTest {
onTabSelected = { selectedTab.value = it },
albums = albums,
artists = artists,
songs = songs,
playlists = playlists,
coverArt = { _, _ -> null },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = {},
onOpenSettings = {},
)
@@ -223,8 +204,7 @@ class LibraryScreenTest {
assertTrue("returning to a tab should render its content", collected.contains("albums"))
assertTrue("the tab that was shown should collect its flow", collected.contains("artists"))
assertFalse("inactive tabs should not collect their flows", collected.contains("songs"))
assertFalse(collected.contains("playlists"))
assertFalse("inactive tabs should not collect their flows", collected.contains("playlists"))
}
@Test
@@ -247,7 +227,7 @@ class LibraryScreenTest {
composeRule.runOnIdle { pagerState.requestScrollToPage(1, step * 0.05f) }
composeRule.waitForIdle()
composeRule
.onNodeWithContentDescription(LibraryTab.Songs.label)
.onNodeWithContentDescription(LibraryTab.Playlists.label)
.fetchSemanticsNode()
.boundsInRoot.left
}
@@ -15,7 +15,6 @@ import com.github.takahirom.roborazzi.captureRoboImage
import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist
import com.subtracks.data.model.Playlist
import com.subtracks.data.model.SongListItem
import com.subtracks.data.prefs.ListQuery
import com.subtracks.ui.library.ALBUM_COVER_TAG
import com.subtracks.ui.library.LibraryScreen
@@ -45,13 +44,11 @@ class LibrarySearchScreenshotTest {
onTabSelected = {},
albums = remember { flowOf(PagingData.from(albums())) },
artists = remember { flowOf(PagingData.empty<Artist>()) },
songs = remember { flowOf(PagingData.empty<SongListItem>()) },
playlists = remember { flowOf(PagingData.empty<Playlist>()) },
coverArt = { _, _ -> null },
onAlbumClick = {},
onArtistClick = {},
onPlaylistClick = {},
onSongClick = {},
onSync = {},
onOpenSettings = {},
listQuery = ListQuery("Name"),
@@ -66,7 +66,7 @@ class QueueViewModelTest {
@Test
fun openingLoadsAWindowAtTheCursorAndExtendingGrowsIt() {
runBlocking { seedSongs(200) }
controller.playSongs(1, 100)
controller.playAlbum(1, "al1", 100)
await { controller.state.value.position == 100L }
viewModel.open()
@@ -87,7 +87,7 @@ class QueueViewModelTest {
@Test
fun openingAtTheEndLoadsAWindowAndExtendsOlder() {
runBlocking { seedSongs(200) }
controller.playSongs(1, 199)
controller.playAlbum(1, "al1", 199)
await { controller.state.value.position == 199L }
viewModel.open()
@@ -111,7 +111,7 @@ class QueueViewModelTest {
@Test
fun reopeningReusesRowIdentitySoComposeDoesNotCrossFadeTwoLists() {
runBlocking { seedSongs(200) }
controller.playSongs(1, 100)
controller.playAlbum(1, "al1", 100)
await { controller.state.value.position == 100L }
viewModel.open()
@@ -128,7 +128,7 @@ class QueueViewModelTest {
@Test
fun reorderingMovesTheRowLocally() {
runBlocking { seedSongs(200) }
controller.playSongs(1, 100)
controller.playAlbum(1, "al1", 100)
await { controller.state.value.position == 100L }
viewModel.open()