Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/DiffEngineViewer.Windows.Tests/FormsHeadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,57 @@ public async Task EveryPictureEverDrawnStaysDecoded()
await Assert.That(count).IsLessThanOrEqualTo(2);
}

/// <summary>
/// Through the real canvas: a pair of pictures painted twice at one size is composed once, and
/// the second paint copies it. Both paints show the pictures.
/// </summary>
[Test]
public async Task RepaintingAPictureComposesItOnce()
{
using var host = new CanvasHost();
var screen = ScreenBuilder.Build(ViewerSession.Resize(Fixtures.Images(), columns, rows));
host.Canvas.Draw(screen);
host.Canvas.LoadPictures();

var first = host.Draw(screen);
var second = host.Draw(screen);

await Assert.That(host.Canvas.Composed()).IsEqualTo(2);
// The left picture's colour, which only a drawn picture puts on the canvas
var red = Bounds(second, _ => _.R == 198 && _.G == 64 && _.B == 64);
await Assert.That(red).IsNotNull();
await Assert.That(Bounds(first, _ => _.R == 198 && _.G == 64 && _.B == 64)).IsEqualTo(red);
}

/// <summary>
/// The footer's buttons are pooled and relabelled as the screen changes, and each is sized to
/// the label it has now. At WinForms' default, GrowOnly, a button stayed as wide as the longest
/// label it had ever held, so the footer was the history of the session - and the pixel
/// baselines the history of the test run, which moved whenever a test was added.
/// </summary>
[Test]
public async Task AFooterButtonIsSizedToItsCurrentLabel()
{
using var host = new FormHost(Fixtures.File(Fixtures.Long(true), Fixtures.Long(false)));
host.Frame();
var button = FooterButtons(host.Form)[4];
var before = (button.Text, button.Width);

host.Form.Apply(ScreenBuilder.Build(ViewerSession.Apply(host.State, CommandKind.ToggleMinimal)));
var after = (button.Text, button.Width);

Console.WriteLine($"{before} then {after}");
await Assert.That(before.Text).IsEqualTo("Changes only");
await Assert.That(after.Text).IsEqualTo("All lines");
await Assert.That(after.Width).IsLessThan(before.Width);
await Assert.That(after.Width).IsGreaterThanOrEqualTo(button.MinimumSize.Width);
}

static List<System.Windows.Forms.Button> FooterButtons(ViewerForm form) =>
((IList) typeof(ViewerForm).GetField("pool", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(form)!)
.Cast<System.Windows.Forms.Button>()
.ToList();

/// <summary>
/// The default 1100 by 700 window at the common scales, through the canvas's own layout
/// code with the cell MonoFont measures at that scale and the chrome the form takes there: the
Expand Down Expand Up @@ -918,6 +969,9 @@ public static int BodyTop(this ViewerCanvas canvas) =>
public static (int Left, int Half, int Width) Panes(this ViewerCanvas canvas) =>
((int, int, int)) typeof(ViewerCanvas).GetMethod("Panes", flags)!.Invoke(canvas, null)!;

public static int Composed(this ViewerCanvas canvas) =>
((ImageCache) typeof(ViewerCanvas).GetField("images", flags)!.GetValue(canvas)!).Composed;

public static (int Count, long Bytes) CachedImages(this ViewerCanvas canvas)
{
var cache = typeof(ViewerCanvas).GetField("images", flags)!.GetValue(canvas)!;
Expand Down
67 changes: 67 additions & 0 deletions src/DiffEngineViewer.Windows.Tests/ImageCacheTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Concurrent;

/// <summary>
/// The decode the WinForms head puts under an image pane's rows.
/// <para>
Expand Down Expand Up @@ -68,6 +70,71 @@ public async Task RemembersAFailure()
await Assert.That(cache.Get(path, null)).IsNull();
}

/// <summary>
/// The window's decode runs on the pool and comes back through the post the window gives the
/// cache, which is BeginInvoke there and a queue here. Until it does, the pane has no picture
/// and the window is free to paint its rows.
/// </summary>
[Test]
public async Task ADecodeWithSomewhereToPostItIsHandedBack()
{
var path = Write("posted.png", SamplePng.Build(8, 6, 200, 40, 40));
using var posted = new BlockingCollection<Action>();
using var cache = new ImageCache(posted.Add);
var loaded = 0;

await Assert.That(cache.Get(path, null, () => loaded++)).IsNull();
await Assert.That(posted.TryTake(out var handBack, TimeSpan.FromSeconds(10))).IsTrue();
handBack!();

await Assert.That(loaded).IsEqualTo(1);
await Assert.That(cache.Get(path, null, () => loaded++)!.Width).IsEqualTo(8);
}

/// <summary>
/// A decode that finishes after its picture left the screen is thrown away rather than cached,
/// or navigating quickly through a queue of pictures would hold every one of them.
/// </summary>
[Test]
public async Task ADecodeForAPictureNoLongerOnScreenIsDropped()
{
var path = Write("left-behind.png", SamplePng.Build(8, 6, 200, 40, 40));
using var posted = new BlockingCollection<Action>();
using var cache = new ImageCache(posted.Add);
var loaded = 0;
cache.Keep([path]);

cache.Get(path, null, () => loaded++);
await Assert.That(posted.TryTake(out var handBack, TimeSpan.FromSeconds(10))).IsTrue();
cache.Keep([]);
handBack!();

await Assert.That(loaded).IsEqualTo(0);
await Assert.That(cache.Composite(path, new(8, 6), (_, size) => new(size.Width, size.Height))).IsNull();
}

/// <summary>
/// A pane paints the picture over its checkerboard, scaled, once per size, and copies that on
/// every paint after. Scaling it on every paint cost 46 to 66 ms a paint for a pair of 2000 by
/// 1500 pictures, on every wheel notch.
/// </summary>
[Test]
public async Task APictureIsComposedOncePerSize()
{
var path = Write("composed.png", SamplePng.Build(8, 6, 200, 40, 40));
using var cache = new ImageCache();
await Assert.That(cache.Get(path, null)).IsNotNull();
Func<Image, Size, Bitmap> build = (_, size) => new(size.Width, size.Height);

var first = cache.Composite(path, new(4, 3), build);
var again = cache.Composite(path, new(4, 3), build);
var resized = cache.Composite(path, new(6, 4), build);

await Assert.That(ReferenceEquals(first, again)).IsTrue();
await Assert.That(resized!.Size).IsEqualTo(new Size(6, 4));
await Assert.That(cache.Composed).IsEqualTo(2);
}

[Test]
public async Task MissingFile()
{
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/DiffEngineViewer.Windows/FormsViewerWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ public bool Capture(Screen screen, int width, int height, string pngPath)
{
form.ClientSize = new(width, height);
form.Apply(screen);
form.LoadPictures();
form.PerformLayout();
// Invalidate only marks dirty; the paint has to have happened before the bitmap.
form.Surface.Refresh();
Expand Down
Loading
Loading