From 124a48a07cee9b79795ac5d5bffea03233c1a0eb Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 26 May 2026 00:14:51 +0200 Subject: [PATCH] feat: add SmartPlaylist model with PlaylistRepresentable conformance Also fixes unit test infrastructure: create shared scheme including MusicTests, fix TEST_HOST path (Mumu.app), add PRODUCT_MODULE_NAME=Music, add get-task-allow entitlement, and skip NSOpenPanel in test runs. --- Music.xcodeproj/project.pbxproj | 22 +-- .../xcshareddata/xcschemes/Music.xcscheme | 128 ++++++++++++++++++ Music/Models/SmartPlaylist.swift | 34 +++++ Music/Music.entitlements | 2 + Music/MusicApp.swift | 6 +- MusicTests/SmartPlaylistTests.swift | 18 +++ 6 files changed, 200 insertions(+), 10 deletions(-) create mode 100644 Music.xcodeproj/xcshareddata/xcschemes/Music.xcscheme create mode 100644 Music/Models/SmartPlaylist.swift create mode 100644 MusicTests/SmartPlaylistTests.swift diff --git a/Music.xcodeproj/project.pbxproj b/Music.xcodeproj/project.pbxproj index 261c04e..a8ed464 100644 --- a/Music.xcodeproj/project.pbxproj +++ b/Music.xcodeproj/project.pbxproj @@ -29,7 +29,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - C46B2C8D2FC2448700F95A24 /* Music.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Music.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C46B2C8D2FC2448700F95A24 /* Mumu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mumu.app; sourceTree = BUILT_PRODUCTS_DIR; }; C46B2C9A2FC2448800F95A24 /* MusicTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MusicTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C46B2CA42FC2448800F95A24 /* MusicUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MusicUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -108,7 +108,7 @@ C46B2C8E2FC2448700F95A24 /* Products */ = { isa = PBXGroup; children = ( - C46B2C8D2FC2448700F95A24 /* Music.app */, + C46B2C8D2FC2448700F95A24 /* Mumu.app */, C46B2C9A2FC2448800F95A24 /* MusicTests.xctest */, C46B2CA42FC2448800F95A24 /* MusicUITests.xctest */, ); @@ -138,7 +138,7 @@ C46B2CBF2FC2449900F95A24 /* GRDB */, ); productName = Music; - productReference = C46B2C8D2FC2448700F95A24 /* Music.app */; + productReference = C46B2C8D2FC2448700F95A24 /* Mumu.app */; productType = "com.apple.product-type.application"; }; C46B2C992FC2448800F95A24 /* MusicTests */ = { @@ -426,13 +426,14 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 8; DEVELOPMENT_TEAM = 526E96RFNP; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = Mumu; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSMicrophoneUsageDescription = "Music uses the microphone to identify songs with Shazam."; @@ -442,7 +443,8 @@ ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.mu; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_MODULE_NAME = Music; + PRODUCT_NAME = Mumu; REGISTER_APP_GROUPS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES; @@ -462,13 +464,14 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 8; DEVELOPMENT_TEAM = 526E96RFNP; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = Mumu; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSMicrophoneUsageDescription = "Music uses the microphone to identify songs with Shazam."; @@ -478,7 +481,8 @@ ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.mu; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_MODULE_NAME = Music; + PRODUCT_NAME = Mumu; REGISTER_APP_GROUPS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES; @@ -506,7 +510,7 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Music.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Music"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mumu.app/Contents/MacOS/Mumu"; }; name = Debug; }; @@ -527,7 +531,7 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Music.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Music"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mumu.app/Contents/MacOS/Mumu"; }; name = Release; }; diff --git a/Music.xcodeproj/xcshareddata/xcschemes/Music.xcscheme b/Music.xcodeproj/xcshareddata/xcschemes/Music.xcscheme new file mode 100644 index 0000000..3bccbe6 --- /dev/null +++ b/Music.xcodeproj/xcshareddata/xcschemes/Music.xcscheme @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Music/Models/SmartPlaylist.swift b/Music/Models/SmartPlaylist.swift new file mode 100644 index 0000000..62f54e6 --- /dev/null +++ b/Music/Models/SmartPlaylist.swift @@ -0,0 +1,34 @@ +import Foundation +import GRDB + +nonisolated struct SmartPlaylist: Codable, Identifiable, Equatable, Hashable, Sendable { + var id: Int64? + var name: String + var searchQuery: String + var createdAt: Date +} + +nonisolated extension SmartPlaylist: FetchableRecord, MutablePersistableRecord { + static let databaseTableName = "smart_playlists" + + mutating func didInsert(_ inserted: InsertionSuccess) { + id = inserted.rowID + } +} + +extension SmartPlaylist: PlaylistRepresentable { + var isSmartPlaylist: Bool { true } +} + +#if DEBUG +extension SmartPlaylist { + static func fixture( + id: Int64? = nil, + name: String = "Test Smart Playlist", + searchQuery: String = "test query", + createdAt: Date = Date() + ) -> SmartPlaylist { + SmartPlaylist(id: id, name: name, searchQuery: searchQuery, createdAt: createdAt) + } +} +#endif diff --git a/Music/Music.entitlements b/Music/Music.entitlements index 65c81d1..ff87a79 100644 --- a/Music/Music.entitlements +++ b/Music/Music.entitlements @@ -4,6 +4,8 @@ com.apple.security.app-sandbox + com.apple.security.get-task-allow + com.apple.security.files.user-selected.read-only com.apple.security.files.bookmarks.app-scope diff --git a/Music/MusicApp.swift b/Music/MusicApp.swift index 0020b33..587fd16 100644 --- a/Music/MusicApp.swift +++ b/Music/MusicApp.swift @@ -78,7 +78,7 @@ struct MusicApp: App { Task { await scanner.rescan(url) } - } else { + } else if !isRunningTests { DispatchQueue.main.async { pickFolder() } @@ -88,6 +88,10 @@ struct MusicApp: App { } } + private var isRunningTests: Bool { + ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil + } + private func pickFolder() { let panel = NSOpenPanel() panel.canChooseFiles = false diff --git a/MusicTests/SmartPlaylistTests.swift b/MusicTests/SmartPlaylistTests.swift new file mode 100644 index 0000000..5d1ce8c --- /dev/null +++ b/MusicTests/SmartPlaylistTests.swift @@ -0,0 +1,18 @@ +import Foundation +import Testing +@testable import Music + +struct SmartPlaylistTests { + // Creates a SmartPlaylist in memory and verifies its properties. + @Test func smartPlaylistProperties() throws { + let sp = SmartPlaylist( + id: nil, + name: "Miles Davis", + searchQuery: "miles davis", + createdAt: Date() + ) + #expect(sp.name == "Miles Davis") + #expect(sp.searchQuery == "miles davis") + #expect(sp.isSmartPlaylist == true) + } +}