Genres fix

This commit is contained in:
2026-04-20 13:02:51 +02:00
parent 7e25a4f978
commit dae9da7084
4 changed files with 265 additions and 37 deletions
@@ -114,6 +114,185 @@ struct FavoriteURICollectionTests {
}
}
// MARK: - Genre Deduplication
@Suite("MALibraryManager Genre Deduplication")
struct GenreDeduplicationTests {
private func uniqueByName(_ genres: [MAGenre]) -> [MAGenre] {
var seen = Set<String>()
return genres.filter { seen.insert($0.name.lowercased()).inserted }
}
private func genre(_ name: String, provider: String) -> MAGenre {
MAGenre(uri: "genre://\(name.lowercased())@\(provider)", name: name, metadata: nil)
}
@Test("Single genre per provider is unchanged")
func singleProviderUnchanged() {
let genres = [genre("Rock", provider: "spotify"), genre("Pop", provider: "spotify")]
#expect(uniqueByName(genres).count == 2)
}
@Test("Duplicate names across providers collapse to one entry")
func duplicatesCollapse() {
let genres = [
genre("Rock", provider: "spotify"),
genre("Rock", provider: "local"),
genre("Rock", provider: "tidal"),
]
let unique = uniqueByName(genres)
#expect(unique.count == 1)
#expect(unique[0].name == "Rock")
}
@Test("Mixed duplicates and unique names are handled correctly")
func mixedList() {
let genres = [
genre("Jazz", provider: "spotify"),
genre("Rock", provider: "spotify"),
genre("Rock", provider: "local"),
genre("Blues", provider: "local"),
]
let unique = uniqueByName(genres)
#expect(unique.count == 3)
#expect(unique.map(\.name).sorted() == ["Blues", "Jazz", "Rock"])
}
@Test("Case-insensitive deduplication treats rock and Rock as the same")
func caseInsensitive() {
let genres = [genre("rock", provider: "local"), genre("Rock", provider: "spotify")]
#expect(uniqueByName(genres).count == 1)
}
@Test("browseGenresByName on manager with no genres returns empty without service crash")
func emptyGenresNoService() {
let manager = MALibraryManager(service: nil)
#expect(manager.genres.isEmpty)
// Without genres loaded, matching set is empty no service call is attempted.
}
}
// MARK: - Genre URI Parsing
@Suite("MALibraryManager Genre URI Parsing")
struct GenreURIParsingTests {
/// Replicates the ID extraction used in browseGenresByName and filterNonEmptyGenres.
private func genreId(from uri: String) -> Int? {
Int(uri.components(separatedBy: "/").last ?? "")
}
@Test("Standard library genre URI yields correct integer ID")
func standardURI() {
#expect(genreId(from: "library://genre/26") == 26)
}
@Test("Small genre ID parses correctly")
func smallId() {
#expect(genreId(from: "library://genre/1") == 1)
}
@Test("Large genre ID parses correctly")
func largeId() {
#expect(genreId(from: "library://genre/99999") == 99999)
}
@Test("Non-integer last path component returns nil")
func nonIntegerComponent() {
#expect(genreId(from: "genre://Rock@local") == nil)
}
@Test("URI with trailing slash returns nil for empty last component")
func trailingSlash() {
#expect(genreId(from: "library://genre/") == nil)
}
@Test("Empty URI string returns nil")
func emptyURI() {
#expect(genreId(from: "") == nil)
}
}
// MARK: - Genre ID Aggregation
@Suite("MALibraryManager Genre ID Aggregation")
struct GenreIDAggregationTests {
/// Replicates the ID collection used in browseGenresByName and filterNonEmptyGenres.
private func idsForName(_ name: String, in genres: [MAGenre]) -> [Int] {
genres
.filter { $0.name.caseInsensitiveCompare(name) == .orderedSame }
.compactMap { Int($0.uri.components(separatedBy: "/").last ?? "") }
}
private func genre(_ name: String, id: Int) -> MAGenre {
MAGenre(uri: "library://genre/\(id)", name: name, metadata: nil)
}
@Test("Single genre yields its ID")
func singleGenreId() {
let genres = [genre("Rock", id: 26)]
#expect(idsForName("Rock", in: genres) == [26])
}
@Test("Multiple providers for the same genre name aggregate all IDs")
func duplicateNamesAggregateIds() {
let genres = [
genre("Rock", id: 26),
genre("Rock", id: 7),
genre("Rock", id: 14),
]
let ids = idsForName("Rock", in: genres)
#expect(Set(ids) == Set([26, 7, 14]))
#expect(ids.count == 3)
}
@Test("Different genre names yield independent ID sets")
func separateNames() {
let genres = [genre("Rock", id: 26), genre("Jazz", id: 5)]
#expect(idsForName("Rock", in: genres) == [26])
#expect(idsForName("Jazz", in: genres) == [5])
}
@Test("Name matching is case-insensitive")
func caseInsensitiveMatch() {
let genres = [genre("rock", id: 1), genre("Rock", id: 2), genre("ROCK", id: 3)]
#expect(Set(idsForName("Rock", in: genres)) == Set([1, 2, 3]))
}
@Test("Genre with non-integer URI is skipped by compactMap")
func invalidURISkipped() {
let bad = MAGenre(uri: "genre://Rock@local", name: "Rock", metadata: nil)
let good = genre("Rock", id: 26)
#expect(idsForName("Rock", in: [bad, good]) == [26])
}
@Test("No matching genre name yields empty ID list")
func noMatchReturnsEmpty() {
let genres = [genre("Jazz", id: 5)]
#expect(idsForName("Rock", in: genres).isEmpty)
}
}
// MARK: - Display Genres State
@Suite("MALibraryManager Display Genres")
struct DisplayGenresTests {
@Test("displayGenres starts empty before any load")
func displayGenresStartEmpty() {
#expect(MALibraryManager(service: nil).displayGenres.isEmpty)
}
@Test("displayGenres and genres are independent collections")
func displayGenresIsSeparate() {
let manager = MALibraryManager(service: nil)
#expect(manager.genres.isEmpty)
#expect(manager.displayGenres.isEmpty)
}
}
// MARK: - MALibraryManager Initial State
@Suite("MALibraryManager Initial State")