You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
Music/MusicTests/PlaylistBarIdentityTests.swift

54 lines
2.6 KiB

import Foundation
import Testing
@testable import Music
// Reproduces the playlist-bar duplication bug.
//
// Regular playlists and smart playlists live in two separate SQLite tables, each
// with its own `autoIncrementedPrimaryKey("id")`. The two id sequences are
// independent, so a regular playlist and a smart playlist routinely share the
// same `id` value (both start at 1, 2, 3, ...). PlaylistViewModel.allPlaylists
// merges the two kinds into one [any PlaylistRepresentable] collection, and
// PlaylistBarView's `ForEach(playlists, id: \.id)` keyed off the bare `id`.
//
// When two items share an id, SwiftUI collapses them into a single identity:
// it renders one row twice and ties both buttons to the same view, so selecting
// or updating one leaks to the other (the reported "shown twice / name changes
// on both buttons" symptom). The fix is a type-disambiguated `listIdentity` that
// stays unique across the merged collection.
struct PlaylistBarIdentityTests {
// Step 1: build a regular playlist and a smart playlist that share id == 1,
// exactly as the two independent autoincrement tables would produce.
// Step 2: collect the identities the playlist bar uses to key its ForEach.
// Step 3: assert the two identities are distinct, so SwiftUI keeps two rows.
@Test func regularAndSmartPlaylistWithSameIdHaveDistinctListIdentity() {
let regular = Playlist.fixture(id: 1, name: "Rock")
let smart = SmartPlaylist.fixture(id: 1, name: "Recently Added")
let items: [any PlaylistRepresentable] = [regular, smart]
let identities = items.map(\.listIdentity)
#expect(Set(identities).count == items.count)
}
// Step 1: build the merged collection the way allPlaylists does, with regular
// and smart playlists whose ids overlap across the two tables.
// Step 2: map every item to its list identity.
// Step 3: assert all identities are unique across the whole collection the
// invariant SwiftUI ForEach needs to avoid duplicate/leaking rows.
@Test func mergedPlaylistsHaveUniqueListIdentities() {
let regulars: [any PlaylistRepresentable] = [
Playlist.fixture(id: 1, name: "Rock"),
Playlist.fixture(id: 2, name: "Jazz"),
]
let smarts: [any PlaylistRepresentable] = [
SmartPlaylist.fixture(id: 1, name: "Recently Added"),
SmartPlaylist.fixture(id: 2, name: "Top Rated"),
]
let all = regulars + smarts
let identities = all.map(\.listIdentity)
#expect(Set(identities).count == all.count)
}
}