121 lines
4.4 KiB
Swift
121 lines
4.4 KiB
Swift
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]))
|
||
}
|
||
}
|