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) } }