mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
ui: cover the artist section split in tests
Assert the combined meta line while loading, cover the downloaded partition and the plural album/song credit branches, share the album-credit id query with its plan test, and put the meta line through a translatable string.
This commit is contained in:
@@ -118,6 +118,10 @@ internal const val ALBUMS_FOR_ARTIST =
|
|||||||
"JOIN albums ON albums.sourceId = :sourceId AND albums.id = credits.albumId " +
|
"JOIN albums ON albums.sourceId = :sourceId AND albums.id = credits.albumId " +
|
||||||
"ORDER BY year DESC, sortName COLLATE NOCASE, id"
|
"ORDER BY year DESC, sortName COLLATE NOCASE, id"
|
||||||
|
|
||||||
|
internal const val ALBUM_CREDIT_ALBUM_IDS =
|
||||||
|
"SELECT DISTINCT albumId FROM ($CREDITED_ALBUM_ROWS) " +
|
||||||
|
"WHERE artistId = :artistId AND albumCredit = 1"
|
||||||
|
|
||||||
// Download visibility follows the credit path: an album credit includes the album when any song is
|
// Download visibility follows the credit path: an album credit includes the album when any song is
|
||||||
// downloaded, while a track credit includes it only when the credited track is downloaded.
|
// downloaded, while a track credit includes it only when the credited track is downloaded.
|
||||||
private const val DOWNLOADED_ARTIST_ALBUMS =
|
private const val DOWNLOADED_ARTIST_ALBUMS =
|
||||||
@@ -1066,10 +1070,7 @@ interface LibraryDao {
|
|||||||
artistId: String,
|
artistId: String,
|
||||||
): Flow<List<Album>>
|
): Flow<List<Album>>
|
||||||
|
|
||||||
@Query(
|
@Query(ALBUM_CREDIT_ALBUM_IDS)
|
||||||
"SELECT DISTINCT albumId FROM ($CREDITED_ALBUM_ROWS) " +
|
|
||||||
"WHERE artistId = :artistId AND albumCredit = 1",
|
|
||||||
)
|
|
||||||
fun albumCreditAlbumIds(
|
fun albumCreditAlbumIds(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
artistId: String,
|
artistId: String,
|
||||||
|
|||||||
@@ -387,10 +387,8 @@ class LibraryRepository(
|
|||||||
}
|
}
|
||||||
return combine(albums, db.libraryDao().albumCreditAlbumIds(sourceId, artistId)) { list, creditIds ->
|
return combine(albums, db.libraryDao().albumCreditAlbumIds(sourceId, artistId)) { list, creditIds ->
|
||||||
val credits = creditIds.toHashSet()
|
val credits = creditIds.toHashSet()
|
||||||
ArtistAlbums(
|
val (albumCredits, appearances) = list.partition { it.id in credits }
|
||||||
albumCredits = list.filter { it.id in credits },
|
ArtistAlbums(albumCredits = albumCredits, appearances = appearances)
|
||||||
appearances = list.filterNot { it.id in credits },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -252,13 +252,8 @@ fun ArtistDetailScreen(
|
|||||||
stringResource(
|
stringResource(
|
||||||
if (albums.albumCredits.isNotEmpty()) R.string.artist_type_album_artist else R.string.artist_type_artist,
|
if (albums.albumCredits.isNotEmpty()) R.string.artist_type_album_artist else R.string.artist_type_artist,
|
||||||
)
|
)
|
||||||
val albumCountText =
|
val albumCountText = pluralStringResource(R.plurals.resources_album_count, albumCount.toInt(), albumCount)
|
||||||
if (albumsLoaded) {
|
val metaText = if (albumsLoaded) stringResource(R.string.artist_meta, artistType, albumCountText) else ""
|
||||||
pluralStringResource(R.plurals.resources_album_count, albumCount.toInt(), albumCount)
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
val metaText = if (albumsLoaded) "$artistType · $albumCountText" else ""
|
|
||||||
var titleHeightPx by remember { mutableFloatStateOf(0f) }
|
var titleHeightPx by remember { mutableFloatStateOf(0f) }
|
||||||
val titleHeight = if (titleHeightPx > 0f) with(density) { titleHeightPx.toDp() } else lineHeight
|
val titleHeight = if (titleHeightPx > 0f) with(density) { titleHeightPx.toDp() } else lineHeight
|
||||||
val titleMaxLines =
|
val titleMaxLines =
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
<string name="artist_type_album_artist">Album artist</string>
|
<string name="artist_type_album_artist">Album artist</string>
|
||||||
<string name="artist_section_albums">Albums (%1$d)</string>
|
<string name="artist_section_albums">Albums (%1$d)</string>
|
||||||
<string name="artist_section_appearances">Appears on (%1$d)</string>
|
<string name="artist_section_appearances">Appears on (%1$d)</string>
|
||||||
|
<string name="artist_meta">%1$s · %2$s</string>
|
||||||
<string name="resources_filter_genre">Genre</string>
|
<string name="resources_filter_genre">Genre</string>
|
||||||
<string name="resources_filter_starred">Starred</string>
|
<string name="resources_filter_starred">Starred</string>
|
||||||
<string name="resources_song_list_delete_all_content">%1$s\n\nDeleting will free %2$s</string>
|
<string name="resources_song_list_delete_all_content">%1$s\n\nDeleting will free %2$s</string>
|
||||||
|
|||||||
@@ -548,17 +548,18 @@ class LibraryDaoTest {
|
|||||||
@Test
|
@Test
|
||||||
fun albumCreditIdsFindCreditsThroughArtistIndexes() =
|
fun albumCreditIdsFindCreditsThroughArtistIndexes() =
|
||||||
runTest {
|
runTest {
|
||||||
val plan =
|
val plan = plan(ALBUM_CREDIT_ALBUM_IDS)
|
||||||
plan(
|
|
||||||
"SELECT DISTINCT albumId FROM ($CREDITED_ALBUM_ROWS) " +
|
|
||||||
"WHERE artistId = :artistId AND albumCredit = 1",
|
|
||||||
)
|
|
||||||
|
|
||||||
assertTrue(plan, plan.contains("USING INDEX index_albums_sourceId_artistId (sourceId=? AND artistId=?)"))
|
assertTrue(plan, plan.contains("USING INDEX index_albums_sourceId_artistId (sourceId=? AND artistId=?)"))
|
||||||
assertTrue(
|
assertTrue(
|
||||||
plan,
|
plan,
|
||||||
plan.contains("USING INDEX index_album_artists_sourceId_artistId (sourceId=? AND artistId=?)"),
|
plan.contains("USING INDEX index_album_artists_sourceId_artistId (sourceId=? AND artistId=?)"),
|
||||||
)
|
)
|
||||||
|
assertTrue(plan, plan.contains("USING INDEX index_songs_sourceId_artistId (sourceId=? AND artistId=?)"))
|
||||||
|
assertTrue(
|
||||||
|
plan,
|
||||||
|
plan.contains("USING INDEX index_song_artists_sourceId_artistId (sourceId=? AND artistId=?)"),
|
||||||
|
)
|
||||||
assertFalse(plan, plan.contains("SCAN albums"))
|
assertFalse(plan, plan.contains("SCAN albums"))
|
||||||
assertFalse(plan, plan.contains("SCAN songs"))
|
assertFalse(plan, plan.contains("SCAN songs"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import com.subtracks.cancelAndJoinBlocking
|
|||||||
import com.subtracks.data.db.SubtracksDatabase
|
import com.subtracks.data.db.SubtracksDatabase
|
||||||
import com.subtracks.data.download.ArtworkStore
|
import com.subtracks.data.download.ArtworkStore
|
||||||
import com.subtracks.data.model.Album
|
import com.subtracks.data.model.Album
|
||||||
|
import com.subtracks.data.model.DownloadStatus
|
||||||
import com.subtracks.data.model.Song
|
import com.subtracks.data.model.Song
|
||||||
|
import com.subtracks.data.model.SongDownload
|
||||||
import com.subtracks.data.model.Source
|
import com.subtracks.data.model.Source
|
||||||
import com.subtracks.data.prefs.AlbumSort
|
import com.subtracks.data.prefs.AlbumSort
|
||||||
import com.subtracks.data.prefs.StarredFilter
|
import com.subtracks.data.prefs.StarredFilter
|
||||||
@@ -231,6 +233,37 @@ class LibraryRepositoryTest {
|
|||||||
assertEquals(2, albums.count)
|
assertEquals(2, albums.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun downloadedArtistAlbumsKeepPerCreditVisibility() =
|
||||||
|
runBlocking {
|
||||||
|
val sourceId = 1L
|
||||||
|
db.sourcesDao().upsertSource(
|
||||||
|
Source(id = sourceId, name = "test", address = "http://localhost", isActive = true, createdAt = 0),
|
||||||
|
)
|
||||||
|
db.libraryDao().upsertAlbums(
|
||||||
|
listOf(
|
||||||
|
seedAlbum(sourceId, 1),
|
||||||
|
seedAlbum(sourceId, 2).copy(artistId = "ar2"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
db.libraryDao().upsertSongs(
|
||||||
|
listOf(
|
||||||
|
seedSong(sourceId).copy(albumId = "al-0001"),
|
||||||
|
seedSong(sourceId).copy(id = "s2", albumId = "al-0002"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
db.downloadDao().upsert(SongDownload(sourceId, "s1", DownloadStatus.Completed))
|
||||||
|
val before = repository.artistAlbums(sourceId, "ar1", downloaded = true).first()
|
||||||
|
assertEquals(listOf("al-0001"), before.albumCredits.map { it.id })
|
||||||
|
assertEquals(emptyList<String>(), before.appearances.map { it.id })
|
||||||
|
|
||||||
|
db.downloadDao().upsert(SongDownload(sourceId, "s2", DownloadStatus.Completed))
|
||||||
|
val after = repository.artistAlbums(sourceId, "ar1", downloaded = true).first()
|
||||||
|
assertEquals(listOf("al-0001"), after.albumCredits.map { it.id })
|
||||||
|
assertEquals(listOf("al-0002"), after.appearances.map { it.id })
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun awaitRefreshes(
|
private suspend fun awaitRefreshes(
|
||||||
presenter: RecordingPresenter,
|
presenter: RecordingPresenter,
|
||||||
target: Int,
|
target: Int,
|
||||||
|
|||||||
@@ -1640,6 +1640,71 @@ class SyncServiceTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun albumCreditIdsFollowPluralCredits() =
|
||||||
|
runTest {
|
||||||
|
insertSource()
|
||||||
|
val source =
|
||||||
|
FakeMusicSource(
|
||||||
|
artists = listOf(artist("a1", "A"), artist("a2", "B")),
|
||||||
|
albums =
|
||||||
|
listOf(
|
||||||
|
album("al1").copy(artistId = "a1", artists = listOf(ArtistRef("a1", "A"))),
|
||||||
|
album("al2").copy(artistId = "a2", artists = listOf(ArtistRef("a2", "B"))),
|
||||||
|
album("al3").copy(
|
||||||
|
artistId = "a2",
|
||||||
|
artists = listOf(ArtistRef("a1", "A"), ArtistRef("a2", "B")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
songs =
|
||||||
|
listOf(
|
||||||
|
song("s1").copy(
|
||||||
|
albumId = "al1",
|
||||||
|
artistId = "a1",
|
||||||
|
artists = listOf(ArtistRef("a1", "A")),
|
||||||
|
),
|
||||||
|
song("s2").copy(
|
||||||
|
albumId = "al2",
|
||||||
|
artistId = "a2",
|
||||||
|
artists = listOf(ArtistRef("a2", "B"), ArtistRef("a1", "A")),
|
||||||
|
),
|
||||||
|
song("s3").copy(
|
||||||
|
albumId = "al3",
|
||||||
|
artistId = "a2",
|
||||||
|
artists = listOf(ArtistRef("a1", "A"), ArtistRef("a2", "B")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
SyncService(db, source).sync()
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf("al1", "al3"),
|
||||||
|
db
|
||||||
|
.libraryDao()
|
||||||
|
.albumCreditAlbumIds(1, "a1")
|
||||||
|
.first()
|
||||||
|
.sorted(),
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("al2", "al3"),
|
||||||
|
db
|
||||||
|
.libraryDao()
|
||||||
|
.albumCreditAlbumIds(1, "a2")
|
||||||
|
.first()
|
||||||
|
.sorted(),
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("al1", "al2", "al3"),
|
||||||
|
db
|
||||||
|
.libraryDao()
|
||||||
|
.albumsForArtist(1, "a1")
|
||||||
|
.first()
|
||||||
|
.map { it.id }
|
||||||
|
.sorted(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun everyArtistConsumerAgreesOnTheCreditedSet() =
|
fun everyArtistConsumerAgreesOnTheCreditedSet() =
|
||||||
runTest {
|
runTest {
|
||||||
|
|||||||
@@ -104,9 +104,8 @@ class ArtistDetailScreenTest {
|
|||||||
albumsLoaded = false,
|
albumsLoaded = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
composeRule.onNodeWithText("Artist").assertDoesNotExist()
|
composeRule.onNodeWithText("Artist · 0 albums").assertDoesNotExist()
|
||||||
composeRule.onNodeWithText("Album artist").assertDoesNotExist()
|
composeRule.onNodeWithText("Album artist · 0 albums").assertDoesNotExist()
|
||||||
composeRule.onNodeWithText("0 albums").assertDoesNotExist()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun album(
|
private fun album(
|
||||||
|
|||||||
Reference in New Issue
Block a user