using PLib.Domain.Videos; using Shouldly; namespace PLib.Tests.Domain; public sealed class WatchProgressTests { private static VideoItem CreateHourLongVideo() { var item = new VideoItem(@"C:\videos\film.mp4", "film", 1_000, DateTimeOffset.UnixEpoch); item.ApplyTechnicalInfo(new VideoTechnicalInfo(TimeSpan.FromHours(1), 1920, 1080, "h264")); return item; } [Fact] public void A_position_worth_returning_to_is_remembered() { var item = CreateHourLongVideo(); item.RememberProgress(TimeSpan.FromMinutes(20)); item.ResumePosition.ShouldBe(TimeSpan.FromMinutes(20)); item.WatchedFraction.ShouldBe(1.0 / 3, 0.01); item.PlayCount.ShouldBe(0); item.LastPlayedAt.ShouldNotBeNull(); } [Fact] public void Stopping_in_the_first_seconds_leaves_nothing_to_resume() { var item = CreateHourLongVideo(); item.RememberProgress(TimeSpan.FromSeconds(5)); item.ResumePosition.ShouldBeNull(); item.PlayCount.ShouldBe(0); // It still counts as opened, which is what recently-played ordering uses. item.LastPlayedAt.ShouldNotBeNull(); } [Fact] public void Reaching_the_credits_counts_as_watched_rather_than_as_a_resume_point() { var item = CreateHourLongVideo(); // Inside the end-of-playback slack: the viewer is done, not paused. item.RememberProgress(TimeSpan.FromMinutes(60) - TimeSpan.FromSeconds(5)); item.ResumePosition.ShouldBeNull(); item.PlayCount.ShouldBe(1); item.WatchedFraction.ShouldBe(0); } [Fact] public void Watching_again_after_finishing_starts_a_fresh_resume_point() { var item = CreateHourLongVideo(); item.RememberProgress(TimeSpan.FromMinutes(60)); item.RememberProgress(TimeSpan.FromMinutes(3)); item.PlayCount.ShouldBe(1); item.ResumePosition.ShouldBe(TimeSpan.FromMinutes(3)); } [Fact] public void A_video_of_unknown_length_still_remembers_where_it_stopped() { var item = new VideoItem(@"C:\videos\odd.mkv", "odd", 1_000, DateTimeOffset.UnixEpoch); item.RememberProgress(TimeSpan.FromMinutes(5)); item.ResumePosition.ShouldBe(TimeSpan.FromMinutes(5)); // Without a duration there is no fraction to draw. item.WatchedFraction.ShouldBe(0); } }