import CoreGraphics import Foundation @main struct PlaybackQueueChecks { static func require(_ condition: @autoclosure () -> Bool, _ message: String) { if !condition() { fatalError("FAIL: " + message) } } static func file(_ name: String) -> URL { URL(fileURLWithPath: "/queue-check/" + name + ".mp4") } static func dragReordering() { // Unequal heights catch snapping to a row's old center instead of the vacated slot. let frames = [ CGRect(x: 0, y: 52, width: 260, height: 60), CGRect(x: 0, y: 112, width: 260, height: 80), CGRect(x: 0, y: 192, width: 260, height: 60), ] var queue = PlaybackQueue() queue.replace([file("A"), file("B"), file("C")]) let current = queue.currentID var drag = PlaybackQueueDrag(source: 0, frames: frames, scrollOffset: 0) drag.update(translation: 39, scrollOffset: 0) require(drag.destination == 0, "release before the nearest-slot midpoint keeps the original position") drag.update(translation: 41, scrollOffset: 0) require(drag.destination == 1 && drag.offset(at: 1) == -60 && drag.offset(at: 2) == 0, "crossing the midpoint slides only the displaced neighbor") drag.update(translation: 140, scrollOffset: 0) require(drag.destination == 2 && drag.offset(at: 0) == 140, "dragged row follows the pointer continuously to the final slot") // No further pointer update: release consumes the retained gesture state. queue.move(fromOffsets: IndexSet(integer: drag.source), toOffset: drag.insertionOffset) require(queue.items.map(\.url) == [file("B"), file("C"), file("A")] && queue.currentID == current, "stationary release commits the nearest slot without changing playback identity") drag = PlaybackQueueDrag(source: 2, frames: frames, scrollOffset: 100) drag.update(translation: -20, scrollOffset: 0) require(drag.destination == 0 && drag.offset(at: 0) == 60 && drag.offset(at: 1) == 60, "scrolling under a stationary pointer moves the row and shifts earlier neighbors down") drag.update(translation: 0, scrollOffset: 100) require(drag.destination == 2 && drag.offset(at: 1) == 0, "reversing a drag restores the original gap") drag.update(translation: -1_000, scrollOffset: 100) require(drag.destination == 0 && drag.insertionOffset == 0, "release above the list selects the first slot") drag.update(translation: 1_000, scrollOffset: 100) require(drag.destination == 2 && drag.insertionOffset == 2, "release below the list selects the last slot") } static func main() { dragReordering() let first = file("Episode1"), second = file("Episode2"), tenth = file("Episode10") var queue = PlaybackQueue() require(queue.items.isEmpty && queue.currentURL == nil && !queue.canPrevious && !queue.canNext, "empty queue has no selection or navigation") require(queue.advance(1) == nil && queue.advance(-1) == nil, "empty navigation is harmless") queue.replace([tenth, second, first]) require(queue.items.map(\.url) == [first, second, tenth], "replace uses natural Episode2/Episode10 filename order") require(queue.currentURL == first && !queue.canPrevious && queue.canNext, "replace selects first item") let firstID = queue.currentID require(queue.advance(-1) == nil && queue.currentID == firstID, "previous at start does not wrap or deselect") require(queue.advance(1) == second && queue.canPrevious && queue.canNext, "next selects middle item") require(queue.advance(1) == tenth && queue.canPrevious && !queue.canNext, "next selects final item") let tenthID = queue.currentID require(queue.advance(1) == nil && queue.currentID == tenthID, "next at end stops without wrapping") require(queue.advance(-1) == second, "previous moves back one item") let selected = queue.currentID! queue.append([file("Episode20"), file("Episode3")]) require(queue.items.map(\.url) == [first, second, tenth, file("Episode3"), file("Episode20")], "append naturally sorts the new batch without reordering existing items") require(queue.currentID == selected && queue.currentURL == second, "append does not interrupt selection") queue.move(fromOffsets: IndexSet(integer: 1), toOffset: queue.items.count) require(queue.items.last?.id == selected && queue.currentID == selected && queue.currentURL == second, "reordering preserves current item identity") require(queue.canPrevious && !queue.canNext, "navigation follows reordered positions") queue.move(fromOffsets: IndexSet([0, 1]), toOffset: queue.items.count) require(queue.currentID == selected && queue.currentURL == second, "moving other items preserves current") queue.replace([tenth, second, first]) queue.select(queue.items[1].id) require(queue.currentURL == second, "explicit selection jumps to an item") let current = queue.currentID let unrelated = queue.items[0].id require(queue.remove(unrelated) == nil && queue.currentID == current && queue.currentURL == second, "removing another item does not request playback or change selection") require(queue.remove(current!) == tenth && queue.currentURL == tenth, "removing current selects the next remaining item") require(queue.remove(queue.currentID!) == nil && queue.currentURL == nil && queue.items.isEmpty, "removing final item stops and empties queue") queue.replace([first, second, tenth]) queue.select(queue.items.last!.id) require(queue.remove(queue.currentID!) == nil && queue.currentID == nil && queue.items.count == 2, "removing current at end stops rather than jumping backward") require(!queue.canPrevious && !queue.canNext, "stopped queue has no implicit selection") queue.select(queue.items[0].id) require(queue.currentURL == first, "remaining items can be explicitly selected after stopping") queue.append([first]) require(Set(queue.items.map(\.id)).count == queue.items.count, "duplicate file entries have independent identities") queue.replace([tenth]) require(queue.items.map(\.url) == [tenth] && queue.currentURL == tenth && !queue.canPrevious && !queue.canNext, "replace discards previous queue and starts new selection") queue.replace([]) require(queue.items.isEmpty && queue.currentID == nil, "empty replacement clears queue") print("playback queue checks passed") } }