library: fall back to the plural artist names for display

Keep the server's combined legacy artist/albumArtist string when it is
present, prefer the OpenSubsonic displayArtist when it is not, and otherwise
join the plural artist refs with Navidrome's separator. The normalized value
is stored in the display columns, so song rows, album cards, now playing,
menu subtitles, downloads, search and artist ordering all get it.
This commit is contained in:
austinried
2026-10-18 02:58:59 +00:00
parent 6c3028eeb2
commit 97d33a781a
2 changed files with 106 additions and 8 deletions
@@ -240,6 +240,80 @@ class SubsonicXmlTest {
)
}
@Test
fun fallsBackToThePluralNamesWhenTheLegacyDisplayArtistIsBlank() {
val albums =
albums(
"""
<subsonic-response status="ok">
<albumList2>
<album id="al1" name="Split" songCount="1">
<artists id="ar1" name="A"/>
<artists id="ar2" name="B"/>
</album>
</albumList2>
</subsonic-response>
""".trimIndent(),
)
assertEquals("A • B", albums.single().albumArtist)
assertEquals(listOf(ArtistRef("ar1", "A"), ArtistRef("ar2", "B")), albums.single().artists)
}
@Test
fun prefersTheOpenSubsonicDisplayArtistOverThePluralNames() {
val albums =
albums(
"""
<subsonic-response status="ok">
<albumList2>
<album id="al1" name="Split" displayArtist="A feat. B" songCount="1">
<artists id="ar1" name="A"/>
<artists id="ar2" name="B"/>
</album>
</albumList2>
</subsonic-response>
""".trimIndent(),
)
assertEquals("A feat. B", albums.single().albumArtist)
}
@Test
fun fallsBackToThePluralSongArtistsWhenTheLegacyNameIsBlank() {
val songs =
songs(
"""
<subsonic-response status="ok">
<song id="s1" title="Collab" albumId="al1">
<artists id="ar1" name="A"/>
<artists id="ar2" name="B"/>
</song>
</subsonic-response>
""".trimIndent(),
)
assertEquals("A • B", songs.single().artist)
assertEquals(listOf(ArtistRef("ar1", "A"), ArtistRef("ar2", "B")), songs.single().artists)
}
@Test
fun prefersTheOpenSubsonicDisplayArtistForSongs() {
val songs =
songs(
"""
<subsonic-response status="ok">
<song id="s1" title="Collab" albumId="al1" displayArtist="A feat. B">
<artists id="ar1" name="A"/>
<artists id="ar2" name="B"/>
</song>
</subsonic-response>
""".trimIndent(),
)
assertEquals("A feat. B", songs.single().artist)
}
@Test
fun parsesTimestampsWithFractionsAndOffsets() {
assertEquals(1789906657L, IsoDate.parse("2026-09-20T12:17:37.370827413Z"))