feat(player): docked queue overlay with reorder, remove, jump, and clear-upcoming

Add a Queue toggle to the docked player bar opening a centered editable queue
overlay. New additive QueueService.ClearUpcoming keeps the playing track while
dropping the rest. Current track is non-removable.
This commit is contained in:
daniel-c-harvey
2026-06-19 15:18:25 -04:00
parent 4317a2f9e7
commit fe3819f378
10 changed files with 413 additions and 5 deletions
+69
View File
@@ -678,6 +678,75 @@ public class QueueServiceTests
});
}
// --- ClearUpcoming (OQ5: keep the current track, drop the up-next) — Phase 17 wave 17.2 ---
[Test]
public async Task ClearUpcoming_KeepsCurrentTrack_DropsTheRest_WithoutStopping()
{
// Current = track-2; ClearUpcoming leaves only track-2 at index 0 and does not stop the player.
await _queue.PlayRelease(Tracks(4), startIndex: 1);
var streamedBefore = _player.SelectedTracks.Count;
_queue.ClearUpcoming();
Assert.Multiple(() =>
{
Assert.That(_queue.Items.Select(t => t.EntryKey), Is.EqualTo(new[] { "track-2" }));
Assert.That(_queue.CurrentIndex, Is.EqualTo(0));
Assert.That(_queue.Current!.EntryKey, Is.EqualTo("track-2"));
Assert.That(_queue.HasNext, Is.False);
Assert.That(_queue.HasPrevious, Is.False);
Assert.That(_player.StopCount, Is.EqualTo(0), "ClearUpcoming must not stop playback (C2)");
Assert.That(_player.SelectedTracks, Has.Count.EqualTo(streamedBefore),
"ClearUpcoming must not re-stream");
});
}
[Test]
public async Task ClearUpcoming_RaisesQueueChangedOnce()
{
await _queue.PlayRelease(Tracks(3));
var changed = 0;
_queue.QueueChanged += () => changed++;
_queue.ClearUpcoming();
Assert.That(changed, Is.EqualTo(1));
}
[Test]
public async Task ClearUpcoming_WhenOnlyCurrentRemains_IsNoOpAndDoesNotRaiseQueueChanged()
{
await _queue.PlayRelease(Tracks(1));
var raised = false;
_queue.QueueChanged += () => raised = true;
_queue.ClearUpcoming();
Assert.Multiple(() =>
{
Assert.That(_queue.Items, Has.Count.EqualTo(1));
Assert.That(_queue.CurrentIndex, Is.EqualTo(0));
Assert.That(raised, Is.False);
});
}
[Test]
public void ClearUpcoming_OnEmptyQueue_IsNoOpAndDoesNotRaiseQueueChanged()
{
var raised = false;
_queue.QueueChanged += () => raised = true;
_queue.ClearUpcoming();
Assert.Multiple(() =>
{
Assert.That(_queue.Items, Is.Empty);
Assert.That(_queue.CurrentIndex, Is.EqualTo(-1));
Assert.That(raised, Is.False);
});
}
// --- QueueChanged notifications ---
[Test]