Add Git overlay, operation tools, and virtualized preview so large folders stay responsive.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-24 15:04:04 +02:00
parent 9bf451932f
commit a3c54bbb03
127 changed files with 14748 additions and 633 deletions

View File

@@ -0,0 +1,170 @@
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<string, int>(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<ThumbnailJob> Drain(ThumbnailScheduler scheduler)
{
var taken = new List<ThumbnailJob>();
while (scheduler.TryTake(out var job))
{
taken.Add(job);
scheduler.Complete(job, generated: true, failed: false);
}
return taken;
}
}