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.
feat/music-streaming
Laurent 1 month ago
parent 7024be1cba
commit 124a48a07c
  1. 22
      Music.xcodeproj/project.pbxproj
  2. 128
      Music.xcodeproj/xcshareddata/xcschemes/Music.xcscheme
  3. 34
      Music/Models/SmartPlaylist.swift
  4. 2
      Music/Music.entitlements
  5. 6
      Music/MusicApp.swift
  6. 18
      MusicTests/SmartPlaylistTests.swift

@ -29,7 +29,7 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference 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; }; 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; }; C46B2CA42FC2448800F95A24 /* MusicUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MusicUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -108,7 +108,7 @@
C46B2C8E2FC2448700F95A24 /* Products */ = { C46B2C8E2FC2448700F95A24 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C46B2C8D2FC2448700F95A24 /* Music.app */, C46B2C8D2FC2448700F95A24 /* Mumu.app */,
C46B2C9A2FC2448800F95A24 /* MusicTests.xctest */, C46B2C9A2FC2448800F95A24 /* MusicTests.xctest */,
C46B2CA42FC2448800F95A24 /* MusicUITests.xctest */, C46B2CA42FC2448800F95A24 /* MusicUITests.xctest */,
); );
@ -138,7 +138,7 @@
C46B2CBF2FC2449900F95A24 /* GRDB */, C46B2CBF2FC2449900F95A24 /* GRDB */,
); );
productName = Music; productName = Music;
productReference = C46B2C8D2FC2448700F95A24 /* Music.app */; productReference = C46B2C8D2FC2448700F95A24 /* Mumu.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
C46B2C992FC2448800F95A24 /* MusicTests */ = { C46B2C992FC2448800F95A24 /* MusicTests */ = {
@ -426,13 +426,14 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_TEAM = 526E96RFNP; DEVELOPMENT_TEAM = 526E96RFNP;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly; ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = Mumu;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Music uses the microphone to identify songs with Shazam."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "Music uses the microphone to identify songs with Shazam.";
@ -442,7 +443,8 @@
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.mu; PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.mu;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_MODULE_NAME = Music;
PRODUCT_NAME = Mumu;
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
@ -462,13 +464,14 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_TEAM = 526E96RFNP; DEVELOPMENT_TEAM = 526E96RFNP;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly; ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = Mumu;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Music uses the microphone to identify songs with Shazam."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "Music uses the microphone to identify songs with Shazam.";
@ -478,7 +481,8 @@
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.mu; PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.mu;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_MODULE_NAME = Music;
PRODUCT_NAME = Mumu;
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
@ -506,7 +510,7 @@
SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; 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; name = Debug;
}; };
@ -527,7 +531,7 @@
SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; 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; name = Release;
}; };

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C46B2C8C2FC2448700F95A24"
BuildableName = "Mumu.app"
BlueprintName = "Music"
ReferencedContainer = "container:Music.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C46B2C992FC2448800F95A24"
BuildableName = "MusicTests.xctest"
BlueprintName = "MusicTests"
ReferencedContainer = "container:Music.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C46B2CA32FC2448800F95A24"
BuildableName = "MusicUITests.xctest"
BlueprintName = "MusicUITests"
ReferencedContainer = "container:Music.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C46B2C992FC2448800F95A24"
BuildableName = "MusicTests.xctest"
BlueprintName = "MusicTests"
ReferencedContainer = "container:Music.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C46B2CA32FC2448800F95A24"
BuildableName = "MusicUITests.xctest"
BlueprintName = "MusicUITests"
ReferencedContainer = "container:Music.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C46B2C8C2FC2448700F95A24"
BuildableName = "Mumu.app"
BlueprintName = "Music"
ReferencedContainer = "container:Music.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C46B2C8C2FC2448700F95A24"
BuildableName = "Mumu.app"
BlueprintName = "Music"
ReferencedContainer = "container:Music.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -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

@ -4,6 +4,8 @@
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.get-task-allow</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key> <key>com.apple.security.files.user-selected.read-only</key>
<true/> <true/>
<key>com.apple.security.files.bookmarks.app-scope</key> <key>com.apple.security.files.bookmarks.app-scope</key>

@ -78,7 +78,7 @@ struct MusicApp: App {
Task { Task {
await scanner.rescan(url) await scanner.rescan(url)
} }
} else { } else if !isRunningTests {
DispatchQueue.main.async { DispatchQueue.main.async {
pickFolder() pickFolder()
} }
@ -88,6 +88,10 @@ struct MusicApp: App {
} }
} }
private var isRunningTests: Bool {
ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}
private func pickFolder() { private func pickFolder() {
let panel = NSOpenPanel() let panel = NSOpenPanel()
panel.canChooseFiles = false panel.canChooseFiles = false

@ -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)
}
}
Loading…
Cancel
Save