mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 16:11:07 -04:00
Replace the plain-text report with a colored status banner, per-check count tiles and a findings list grouped by check type, with the selected finding shown in full below it. A Copy Report button keeps the full text report available. Consent, cancellation and failure blocking are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
216 lines
8.7 KiB
C#
216 lines
8.7 KiB
C#
using System.Reflection;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Windows.Forms;
|
|
using OpenNest.CNC;
|
|
using OpenNest.Diagnostics;
|
|
using OpenNest.Forms;
|
|
|
|
namespace OpenNest.WinForms.Tests.Forms;
|
|
|
|
public class PostVerificationFormTests
|
|
{
|
|
#pragma warning disable xUnit1031 // Dedicated STA; only await completed tasks, or explicitly pump until completion.
|
|
[Fact]
|
|
public void WarningsRequireFreshAcknowledgmentAndUncheckingRevokesPermission()
|
|
{
|
|
RunSta(() =>
|
|
{
|
|
var nest = WarningNest();
|
|
using var form = new PostVerificationForm(nest,
|
|
(_, _) => Task.FromResult(PostVerificationAnalyzer.Analyze(nest)));
|
|
form.VerifyAsync().GetAwaiter().GetResult();
|
|
var consent = Control<CheckBox>(form, "acknowledgeBox");
|
|
var post = Control<Button>(form, "postButton");
|
|
Assert.False(consent.Checked);
|
|
Assert.False(post.Enabled);
|
|
var list = Control<ListView>(form, "findingsList");
|
|
Assert.NotEmpty(list.Items);
|
|
Assert.All(list.Items.Cast<ListViewItem>(), item => Assert.NotNull(item.Group));
|
|
Invoke(form, "PostButton_Click", null, EventArgs.Empty);
|
|
Assert.NotEqual(DialogResult.OK, form.DialogResult);
|
|
consent.Checked = true;
|
|
Assert.True(post.Enabled);
|
|
consent.Checked = false;
|
|
Assert.False(post.Enabled);
|
|
consent.Checked = true;
|
|
Invoke(form, "PostButton_Click", null, EventArgs.Empty);
|
|
Assert.Equal(DialogResult.OK, form.DialogResult);
|
|
|
|
using var retry = new PostVerificationForm(nest,
|
|
(_, _) => Task.FromResult(PostVerificationAnalyzer.Analyze(nest)));
|
|
retry.VerifyAsync().GetAwaiter().GetResult();
|
|
Assert.False(Control<CheckBox>(retry, "acknowledgeBox").Checked);
|
|
Assert.False(Control<Button>(retry, "postButton").Enabled);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void ClearReportIsDisplayedAndCancelIsDefault()
|
|
{
|
|
RunSta(() =>
|
|
{
|
|
var nest = WarningNest();
|
|
foreach (var move in nest.Plates[0].Parts[0].Program.Codes.OfType<LinearMove>())
|
|
move.Layer = LayerType.Scribe;
|
|
foreach (var move in nest.Plates[0].Parts[0].BaseDrawing.Program.Codes.OfType<LinearMove>())
|
|
move.Layer = LayerType.Scribe;
|
|
using var form = new PostVerificationForm(nest,
|
|
(_, _) => Task.FromResult(PostVerificationAnalyzer.Analyze(nest)));
|
|
form.VerifyAsync().GetAwaiter().GetResult();
|
|
Assert.Equal("No warnings found", Control<Label>(form, "headlineLabel").Text);
|
|
Assert.Empty(Control<ListView>(form, "findingsList").Items);
|
|
Assert.True(Control<Button>(form, "postButton").Enabled);
|
|
Assert.False(Control<CheckBox>(form, "acknowledgeBox").Enabled);
|
|
Assert.Equal("Continue Posting", Control<Button>(form, "postButton").Text);
|
|
Assert.Same(Control<Button>(form, "cancelButton"), form.AcceptButton);
|
|
Assert.Same(Control<Button>(form, "cancelButton"), form.CancelButton);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void UnexpectedFailureCannotBeOverridden()
|
|
{
|
|
RunSta(() =>
|
|
{
|
|
using var form = new PostVerificationForm(WarningNest(),
|
|
(_, _) => Task.FromException<PostVerificationReport>(new InvalidOperationException("test failure")));
|
|
form.VerifyAsync().GetAwaiter().GetResult();
|
|
Control<CheckBox>(form, "acknowledgeBox").Checked = true;
|
|
Assert.False(Control<Button>(form, "postButton").Enabled);
|
|
Assert.Contains("test failure", Control<Label>(form, "detailLabel").Text);
|
|
Invoke(form, "PostButton_Click", null, EventArgs.Empty);
|
|
Assert.NotEqual(DialogResult.OK, form.DialogResult);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void DisposalIsIdempotent()
|
|
{
|
|
RunSta(() =>
|
|
{
|
|
var form = new PostVerificationForm(WarningNest());
|
|
form.Dispose();
|
|
form.Dispose();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void ClosingDuringVerificationWaitsForWorkerAndDiscardsLateResult()
|
|
{
|
|
RunSta(() =>
|
|
{
|
|
var pending = new TaskCompletionSource<PostVerificationReport>();
|
|
var nest = WarningNest();
|
|
var token = CancellationToken.None;
|
|
using var form = new PostVerificationForm(nest, (_, cancellation) =>
|
|
{
|
|
token = cancellation;
|
|
return pending.Task;
|
|
});
|
|
var work = form.VerifyAsync();
|
|
try
|
|
{
|
|
var closing = new FormClosingEventArgs(CloseReason.UserClosing, false);
|
|
Invoke(form, "OnFormClosing", closing);
|
|
Assert.True(closing.Cancel);
|
|
Assert.True(token.IsCancellationRequested);
|
|
Assert.False(work.IsCompleted);
|
|
pending.SetResult(PostVerificationAnalyzer.Analyze(nest));
|
|
var until = DateTime.UtcNow.AddSeconds(10);
|
|
while (!work.IsCompleted && DateTime.UtcNow < until)
|
|
Application.DoEvents();
|
|
Assert.True(work.IsCompleted);
|
|
work.GetAwaiter().GetResult();
|
|
Assert.NotEqual(DialogResult.OK, form.DialogResult);
|
|
}
|
|
finally
|
|
{
|
|
pending.TrySetCanceled();
|
|
}
|
|
});
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Cancel")]
|
|
[InlineData("Escape")]
|
|
[InlineData("Close")]
|
|
public void RealModalCancellationKeepsOwnerDisabledUntilWorkerStops(string action)
|
|
{
|
|
RunSta(() =>
|
|
{
|
|
var pending = new TaskCompletionSource<PostVerificationReport>();
|
|
using var owner = new Form();
|
|
using var form = new PostVerificationForm(WarningNest(), (_, _) => pending.Task);
|
|
Exception? failure = null;
|
|
owner.Show();
|
|
using var timeout = new System.Windows.Forms.Timer { Interval = 10000 };
|
|
timeout.Tick += (_, _) =>
|
|
{
|
|
failure ??= new TimeoutException("Modal cancellation did not finish.");
|
|
pending.TrySetCanceled();
|
|
form.Close();
|
|
};
|
|
form.Shown += (_, _) => form.BeginInvoke(new System.Action(() =>
|
|
{
|
|
try
|
|
{
|
|
Assert.False(IsWindowEnabled(owner.Handle));
|
|
if (action == "Cancel")
|
|
Control<Button>(form, "cancelButton").PerformClick();
|
|
else if (action == "Escape")
|
|
Invoke(form, "ProcessDialogKey", Keys.Escape);
|
|
else
|
|
form.Close();
|
|
|
|
// ShowDialog must stay active while the worker ignores cancellation.
|
|
Assert.True(form.Visible);
|
|
Assert.False(IsWindowEnabled(owner.Handle));
|
|
Assert.False(Control<Button>(form, "postButton").Enabled);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
failure = ex;
|
|
}
|
|
finally
|
|
{
|
|
pending.TrySetCanceled();
|
|
}
|
|
}));
|
|
timeout.Start();
|
|
var result = form.ShowDialog(owner);
|
|
timeout.Stop();
|
|
Assert.Equal(DialogResult.Cancel, result);
|
|
Assert.True(IsWindowEnabled(owner.Handle));
|
|
if (failure != null)
|
|
ExceptionDispatchInfo.Capture(failure).Throw();
|
|
});
|
|
}
|
|
|
|
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
|
[return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]
|
|
private static extern bool IsWindowEnabled(IntPtr window);
|
|
|
|
private static Nest WarningNest()
|
|
{
|
|
var nest = new Nest("verification");
|
|
var program = new CNC.Program();
|
|
program.Codes.Add(new RapidMove(0, 0));
|
|
program.Codes.Add(new LinearMove(5, 0));
|
|
program.Codes.Add(new LinearMove(5, 5));
|
|
program.Codes.Add(new LinearMove(0, 5));
|
|
program.Codes.Add(new LinearMove(0, 0));
|
|
nest.CreatePlate().Parts.Add(new Part(new Drawing("square", program)));
|
|
return nest;
|
|
}
|
|
|
|
private static T Control<T>(Form form, string name) where T : Control =>
|
|
Assert.IsType<T>(form.Controls.Find(name, true).Single());
|
|
|
|
private static void Invoke(Form form, string method, params object?[] arguments) =>
|
|
form.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.NonPublic)!
|
|
.Invoke(form, arguments);
|
|
|
|
private static void RunSta(System.Action action) =>
|
|
StaTestThread.Run(action, TimeSpan.FromSeconds(30), "STA verification test timed out.");
|
|
}
|