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.
54 lines
2.6 KiB
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)
|
|
}
|
|
}
|
|
|