Files
deepdrft/DeepDrftTests/FramePlayerReleaseResolutionTests.cs
T
daniel-c-harvey 912256d99a Add whole-release embeds to FramePlayer and un-gate the release embed share affordance
The queue gains an armed-but-idle state (Arm/Start) so a release embed stages track 0 prerender-safe, then queues the full release on first play and auto-advances.
2026-06-19 12:05:35 -04:00

160 lines
6.1 KiB
C#

using DeepDrftModels.DTOs;
using DeepDrftPublic.Client.Services;
using DeepDrftPublic.Client.ViewModels;
using Models.Common;
using NetBlocks.Models;
namespace DeepDrftTests;
/// <summary>
/// Unit tests for the release-embed track resolution (<see cref="FramePlayerViewModel"/>). The VM is
/// the data half of FramePlayer's release path: resolve a release EntryKey to its ordered track list,
/// which the page then stages (track 0) and arms into the queue. It is pure async logic over the two
/// data-service seams, so it is exercised here against recording fakes — no browser, no JS, no HTTP.
/// Coverage: ordered non-empty resolution, single-track release, release-not-found, and a release
/// that resolves to no tracks (the "leave the bar idle, don't throw" path).
/// </summary>
[TestFixture]
public class FramePlayerReleaseResolutionTests
{
private FakeReleaseData _releaseData = null!;
private FakeTrackData _trackData = null!;
private FramePlayerViewModel _vm = null!;
private const string ReleaseKey = "release-key-1";
private const long ReleaseId = 42;
[SetUp]
public void SetUp()
{
_releaseData = new FakeReleaseData();
_trackData = new FakeTrackData();
_vm = new FramePlayerViewModel(_releaseData, _trackData);
}
private static ReleaseDto Release() => new() { Id = ReleaseId, EntryKey = ReleaseKey, Title = "Album" };
private static List<TrackDto> Tracks(int count) =>
Enumerable.Range(1, count)
.Select(i => new TrackDto { EntryKey = $"track-{i}", TrackName = $"Track {i}", TrackNumber = i })
.ToList();
[Test]
public async Task Load_ResolvesOrderedNonEmptyTrackList_AndQueriesByResolvedReleaseId()
{
_releaseData.Release = Release();
_trackData.Page = Tracks(3);
await _vm.Load(ReleaseKey);
Assert.Multiple(() =>
{
Assert.That(_vm.Tracks.Select(t => t.EntryKey),
Is.EqualTo(new[] { "track-1", "track-2", "track-3" }));
// The track page must be narrowed by the resolved release.Id (the int FK join), sorted by
// the explicit TrackNumber ordinal — the same resolution the Cut detail page uses.
Assert.That(_trackData.LastReleaseId, Is.EqualTo(ReleaseId));
Assert.That(_trackData.LastSortColumn, Is.EqualTo("TrackNumber"));
});
}
[Test]
public async Task Load_WithSingleTrackRelease_ResolvesAOneItemList()
{
_releaseData.Release = Release();
_trackData.Page = Tracks(1);
await _vm.Load(ReleaseKey);
Assert.That(_vm.Tracks, Has.Count.EqualTo(1));
Assert.That(_vm.Tracks[0].EntryKey, Is.EqualTo("track-1"));
}
[Test]
public async Task Load_WhenReleaseNotFound_LeavesTracksEmptyAndDoesNotQueryTracks()
{
_releaseData.Release = null; // GetByEntryKey returns a fail result.
await _vm.Load(ReleaseKey);
Assert.Multiple(() =>
{
Assert.That(_vm.Tracks, Is.Empty);
Assert.That(_trackData.WasQueried, Is.False);
});
}
[Test]
public async Task Load_WhenReleaseResolvesToNoTracks_LeavesTracksEmptyWithoutThrowing()
{
_releaseData.Release = Release();
_trackData.Page = new List<TrackDto>(); // empty page
await _vm.Load(ReleaseKey);
Assert.That(_vm.Tracks, Is.Empty);
}
[Test]
public async Task Load_ResetsTracksFromAPriorLoad()
{
_releaseData.Release = Release();
_trackData.Page = Tracks(3);
await _vm.Load(ReleaseKey);
Assert.That(_vm.Tracks, Has.Count.EqualTo(3));
// A second load whose release is not found must not retain the prior album's tracks.
_releaseData.Release = null;
await _vm.Load("another-key");
Assert.That(_vm.Tracks, Is.Empty);
}
private sealed class FakeReleaseData : IReleaseDataService
{
public ReleaseDto? Release { get; set; }
public Task<ApiResult<ReleaseDto>> GetByEntryKey(string entryKey)
=> Task.FromResult(Release is null
? ApiResult<ReleaseDto>.CreateFailResult("not found")
: ApiResult<ReleaseDto>.CreatePassResult(Release));
public Task<ApiResult<PagedResult<ReleaseDto>>> GetPaged(
string? medium, int page, int pageSize, string? sortColumn = null,
bool sortDescending = false, string? search = null, string? genre = null)
=> throw new NotSupportedException("FramePlayerViewModel does not page releases.");
}
private sealed class FakeTrackData : ITrackDataService
{
public List<TrackDto> Page { get; set; } = new();
public bool WasQueried { get; private set; }
public long? LastReleaseId { get; private set; }
public string? LastSortColumn { get; private set; }
public Task<ApiResult<PagedResult<TrackDto>>> GetPage(
int pageNumber, int pageSize, string? sortColumn = null, bool sortDescending = false,
string? searchText = null, string? album = null, string? genre = null, long? releaseId = null)
{
WasQueried = true;
LastReleaseId = releaseId;
LastSortColumn = sortColumn;
var paged = new PagedResult<TrackDto>
{
Items = Page,
TotalCount = Page.Count,
Page = pageNumber,
PageSize = pageSize,
};
return Task.FromResult(ApiResult<PagedResult<TrackDto>>.CreatePassResult(paged));
}
// Inert remainder — FramePlayerViewModel only calls GetPage.
public Task<ApiResult<List<ReleaseDto>>> GetAlbums() => throw new NotSupportedException();
public Task<ApiResult<List<GenreSummaryDto>>> GetGenres() => throw new NotSupportedException();
public Task<ApiResult<TrackDto>> GetTrack(string trackId) => throw new NotSupportedException();
public Task<ApiResult<WaveformProfileDto?>> GetTrackWaveform(string trackEntryKey) => throw new NotSupportedException();
public Task<ApiResult<TrackDto?>> GetRandomTrack() => throw new NotSupportedException();
}
}