using Explorer.Application; namespace Explorer.Application.Tests; public class ThumbnailSchedulerTests { [Fact] public void Only_window_items_are_queued_not_the_whole_directory() { var scheduler = new ThumbnailScheduler(); scheduler.Reset(1); scheduler.SetEnabled(true); var window = Enumerable.Range(0, 40) .Select(i => Job($@"C:\v{i}.jpg", rank: 0)) .Concat(Enumerable.Range(0, 24).Select(i => Job($@"C:\p{i}.jpg", rank: 1))) .ToList(); scheduler.SetWindow(1, window); var taken = Drain(scheduler); Assert.Equal(64, taken.Count); Assert.DoesNotContain(taken, j => j.Key.Path.Contains(@"\rest", StringComparison.Ordinal)); Assert.True(taken.Take(40).All(j => j.Rank == 0)); Assert.Equal(0, scheduler.InFlight); } [Fact] public void Visible_jobs_are_taken_before_prefetch() { var scheduler = new ThumbnailScheduler(); scheduler.Reset(1); scheduler.SetEnabled(true); scheduler.SetWindow(1, [ Job(@"C:\far.jpg", rank: 1), Job(@"C:\near.jpg", rank: 0) ]); Assert.True(scheduler.TryTake(out var first)); Assert.Equal(@"C:\near.jpg", first.Key.Path); Assert.True(scheduler.TryTake(out var second)); Assert.Equal(@"C:\far.jpg", second.Key.Path); scheduler.Complete(first, generated: true, failed: false); scheduler.Complete(second, generated: true, failed: false); } [Fact] public void Scrolling_away_drops_obsolete_pending_work() { var scheduler = new ThumbnailScheduler(); scheduler.Reset(1); scheduler.SetEnabled(true); scheduler.SetWindow(1, [Job(@"C:\old.jpg")]); scheduler.SetWindow(1, [Job(@"C:\new.jpg")]); Assert.True(scheduler.TryTake(out var job)); Assert.Equal(@"C:\new.jpg", job.Key.Path); Assert.False(scheduler.TryTake(out _)); Assert.True(scheduler.Snapshot().Cancelled > 0); scheduler.Complete(job, generated: true, failed: false); } [Fact] public void Navigation_generation_invalidates_previous_jobs() { var scheduler = new ThumbnailScheduler(); scheduler.Reset(1); scheduler.SetEnabled(true); scheduler.SetWindow(1, [Job(@"C:\a.jpg")]); Assert.True(scheduler.TryTake(out var stale)); scheduler.Reset(2); Assert.False(scheduler.IsCurrent(stale)); Assert.False(scheduler.TryTake(out _)); scheduler.SetWindow(2, [Job(@"C:\b.jpg", generation: 2)]); Assert.True(scheduler.TryTake(out var next)); Assert.Equal(@"C:\b.jpg", next.Key.Path); scheduler.Complete(next, generated: true, failed: false); } [Fact] public void Cache_hit_skips_regeneration() { var scheduler = new ThumbnailScheduler(); scheduler.Reset(1); scheduler.SetEnabled(true); var key = ThumbnailKey.From(@"C:\hit.jpg", DateTimeOffset.UnixEpoch, 128); scheduler.MarkCached(key); scheduler.SetWindow(1, [new ThumbnailJob(key, 0, 1)]); Assert.False(scheduler.TryTake(out _)); scheduler.RecordCacheHit(); Assert.Equal(1, scheduler.Snapshot().CacheHits); } [Fact] public void Window_is_capped_so_the_queue_cannot_hold_the_whole_directory() { var scheduler = new ThumbnailScheduler(); scheduler.Reset(1); scheduler.SetEnabled(true); var huge = Enumerable.Range(0, 500).Select(i => Job($@"C:\{i}.jpg")).ToList(); scheduler.SetWindow(1, huge); Assert.True(scheduler.Snapshot().Window <= ThumbnailScheduler.MaxPending); var taken = Drain(scheduler); Assert.Equal(ThumbnailScheduler.MaxPending, taken.Count); } [Fact] public void In_flight_work_is_tracked_until_complete() { var scheduler = new ThumbnailScheduler(); scheduler.Reset(1); scheduler.SetEnabled(true); scheduler.SetWindow(1, Enumerable.Range(0, 8).Select(i => Job($@"C:\{i}.jpg")).ToList()); Assert.True(scheduler.TryTake(out var a)); Assert.True(scheduler.TryTake(out var b)); Assert.Equal(2, scheduler.InFlight); scheduler.Complete(a, generated: true, failed: false); Assert.Equal(1, scheduler.InFlight); scheduler.Complete(b, generated: true, failed: false); Assert.Equal(0, scheduler.InFlight); } [Fact] public void Preview_disabled_cancels_window() { var scheduler = new ThumbnailScheduler(); scheduler.Reset(1); scheduler.SetEnabled(true); scheduler.SetWindow(1, [Job(@"C:\x.jpg")]); scheduler.SetEnabled(false); Assert.False(scheduler.TryTake(out _)); } [Fact] public void Cache_key_changes_when_file_is_modified() { var first = ThumbnailKey.From(@"C:\a.jpg", DateTimeOffset.UnixEpoch, 128); var second = ThumbnailKey.From(@"C:\a.jpg", DateTimeOffset.UnixEpoch.AddHours(1), 128); Assert.NotEqual(first, second); } [Fact] public void Lru_cache_evicts_oldest_when_full() { var cache = new LruCache(2); cache.Set("a", 1); cache.Set("b", 2); cache.Set("c", 3); Assert.False(cache.TryGet("a", out _)); Assert.True(cache.TryGet("b", out var b)); Assert.Equal(2, b); cache.Set("d", 4); Assert.False(cache.TryGet("c", out _)); Assert.True(cache.TryGet("b", out _)); } private static ThumbnailJob Job(string path, int rank = 0, int generation = 1) => new(ThumbnailKey.From(path, DateTimeOffset.UnixEpoch, 128), rank, generation); private static List Drain(ThumbnailScheduler scheduler) { var taken = new List(); while (scheduler.TryTake(out var job)) { taken.Add(job); scheduler.Complete(job, generated: true, failed: false); } return taken; } }