import Testing import Foundation @testable import Mobile_Music_Assistant // MARK: - Live Activity Player Selection @Suite("MAPlayerManager – Live Activity Selection") struct LiveActivitySelectionTests { /// Build a minimal MAPlayer from JSON for testing. private func makePlayer( id: String, state: String = "playing", currentItem: String? = nil ) throws -> MAPlayer { var json = """ {"player_id":"\(id)","name":"Player \(id)","state":"\(state)"} """ if let item = currentItem { json = """ {"player_id":"\(id)","name":"Player \(id)","state":"\(state)","current_item":{"queue_item_id":"qi1","name":"\(item)"}} """ } return try JSONDecoder().decode(MAPlayer.self, from: Data(json.utf8)) } // The selection logic is internal to MAPlayerManager; we test it via // a whitebox helper that replicates the exact algorithm. private func selectBestPlayer( from players: [MAPlayer], queues: [String: MAPlayerQueue] ) -> MAPlayer? { players .filter { $0.state == .playing } .first { p in p.currentItem != nil || queues[p.playerId]?.currentItem != nil } ?? players.first { $0.state == .playing } } @Test("Selects the playing player with a known currentItem") func selectsPlayingWithItem() throws { let playing = try makePlayer(id: "1", state: "playing", currentItem: "Song A") let idle = try makePlayer(id: "2", state: "idle") let selected = selectBestPlayer(from: [idle, playing], queues: [:]) #expect(selected?.playerId == "1") } @Test("Falls back to any playing player when no current item is set") func fallsBackToAnyPlayingPlayer() throws { let playing = try makePlayer(id: "1", state: "playing") let idle = try makePlayer(id: "2", state: "idle") let selected = selectBestPlayer(from: [idle, playing], queues: [:]) #expect(selected?.playerId == "1") } @Test("Returns nil when no player is playing") func returnsNilWhenNobodyPlaying() throws { let p1 = try makePlayer(id: "1", state: "idle") let p2 = try makePlayer(id: "2", state: "paused") let selected = selectBestPlayer(from: [p1, p2], queues: [:]) #expect(selected == nil) } @Test("Prefers player whose queue has a currentItem") func prefersPlayerWithQueueItem() throws { let p1 = try makePlayer(id: "1", state: "playing") // no direct item let p2 = try makePlayer(id: "2", state: "playing") // no direct item let queue2: MAPlayerQueue = try { let json = """ {"queue_id":"q2","current_item":{"queue_item_id":"qi2","name":"Track"}} """ return try JSONDecoder().decode(MAPlayerQueue.self, from: Data(json.utf8)) }() // p1 has no item in players or queues; p2 has item in queue let selected = selectBestPlayer(from: [p1, p2], queues: ["2": queue2]) #expect(selected?.playerId == "2") } @Test("Returns nil with empty player list") func emptyPlayerList() { let selected = selectBestPlayer(from: [], queues: [:]) #expect(selected == nil) } } // MARK: - ResizeAndEncode @Suite("MAPlayerManager – resizeAndEncode") struct ResizeAndEncodeTests { @Test("Encodes UIImage to JPEG data under 4 KB") func encodedDataIsBelowActivityKitLimit() { let size = CGSize(width: 400, height: 400) let renderer = UIGraphicsImageRenderer(size: size) let image = renderer.image { ctx in UIColor.systemBlue.setFill() ctx.fill(CGRect(origin: .zero, size: size)) } let data = MAPlayerManager.testResizeAndEncode(image) #expect(data != nil) // ActivityKit limit is 4 KB; we target <1 KB for the image #expect((data?.count ?? Int.max) < 4096) } @Test("Returns valid JPEG data") func returnsValidJpeg() { let renderer = UIGraphicsImageRenderer(size: CGSize(width: 100, height: 100)) let image = renderer.image { ctx in UIColor.red.setFill() ctx.fill(CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) } guard let data = MAPlayerManager.testResizeAndEncode(image) else { Issue.record("resizeAndEncode returned nil") return } // JPEG magic bytes: FF D8 #expect(data.prefix(2) == Data([0xFF, 0xD8])) } }