db: keep playlist owner and sharing in filtered lists

The hand-written playlist projection used by the Downloaded filter and
offline mode did not select the new owner and shared columns, so the
playlist Info dialog lost them there while unfiltered lists had them.
Select both columns and pin the filtered path in LibraryDaoTest.

Also share the artist-name extraction between the subtitle and the
plural label so duplicate or blank refs cannot make them disagree,
accept the XML boolean form "1" for the public flag, and strengthen the
date-adjacency and pluralisation tests.
This commit is contained in:
austinried
2026-10-21 14:04:46 +00:00
parent f46f43b6cd
commit d8ac7fefc7
7 changed files with 55 additions and 11 deletions
@@ -1016,7 +1016,12 @@ class LibraryDaoTest {
runTest {
val sourceId = source()
val dao = db.libraryDao()
dao.upsertPlaylists(listOf(playlist(sourceId, "pl-1", "List", created = 0, changed = 0).copy(songCount = 5)))
dao.upsertPlaylists(
listOf(
playlist(sourceId, "pl-1", "List", created = 0, changed = 0)
.copy(songCount = 5, owner = "austin", shared = true),
),
)
dao.upsertSongs(
listOf(
song(sourceId, "s1", "One", starred = null),
@@ -1034,6 +1039,8 @@ class LibraryDaoTest {
val filtered = dao.playlistsByName(sourceId, "", downloadedFilter = 1).page()
assertEquals(1, filtered.size)
assertEquals(1L, filtered.first().songCount)
assertEquals("austin", filtered.first().owner)
assertEquals(true, filtered.first().shared)
assertEquals(
5L,
@@ -754,6 +754,7 @@ class SubsonicXmlTest {
<playlists>
<playlist id="pl1" name="Shared" owner="austin" public="true" songCount="2" duration="120"/>
<playlist id="pl2" name="Private" owner="austin" public="false" songCount="1" duration="60"/>
<playlist id="pl3" name="Numeric" owner="austin" public="1" songCount="1" duration="60"/>
</playlists>
</subsonic-response>
""".trimIndent(),
@@ -764,6 +765,7 @@ class SubsonicXmlTest {
assertEquals("austin", playlists[0].owner)
assertTrue(playlists[0].shared)
assertEquals(false, playlists[1].shared)
assertTrue(playlists[2].shared)
}
private class CountingInputStream(
@@ -11,6 +11,7 @@ import com.subtracks.data.model.ArtistRef
import com.subtracks.data.model.Playlist
import com.subtracks.data.model.Song
import com.subtracks.ui.theme.SubtracksTheme
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
@@ -171,6 +172,12 @@ class LibraryInfoDialogTest {
val plays = composeRule.onNodeWithText("Plays").fetchSemanticsNode().boundsInRoot
assertTrue("last played must follow added", lastPlayed.top >= added.bottom)
assertTrue("plays must not sit between the dates", plays.top >= lastPlayed.bottom)
assertEquals(
"no row may sit between added and last played",
lastPlayed.top - added.top,
plays.top - lastPlayed.top,
0.5f,
)
}
@Test
@@ -228,6 +235,29 @@ class LibraryInfoDialogTest {
composeRule.onNodeWithText("Artists sort").assertDoesNotExist()
}
@Test
fun duplicateAndBlankArtistRefsStaySingular() {
composeRule.setContent {
SubtracksTheme {
SongInfoDialog(
song =
song(
artists = listOf(ArtistRef("ar1", "Alpha"), ArtistRef("ar2", "Alpha"), ArtistRef("ar3", "")),
artistSort = "Beta",
),
download = null,
localFile = null,
streamEncoding = null,
onDismiss = {},
genres = emptyList(),
)
}
}
composeRule.onNodeWithText("Artist").assertIsDisplayed()
composeRule.onNodeWithText("Artists").assertDoesNotExist()
}
@Test
fun aMixedArtistTrackUsesPluralLabelsForBothRows() {
composeRule.setContent {