using System.Windows.Forms;
using OpenNest.CNC;
using OpenNest.Data;
using OpenNest.Engine;
using OpenNest.Engine.Jobs;
using OpenNest.Engine.Jobs.Remote;
using OpenNest.Forms;
using OpenNest.Forms.Commands;
using OpenNest.Geometry;
namespace OpenNest.WinForms.Tests.Forms;
///
/// A server Auto Nest's progress window: the streamed stage text and the worker's best plan so
/// far in the same strip a local run fills, and the notice shown when a job ends while
/// OpenNest is not in front.
///
public class ServerAutoNestTests
{
private sealed class Collect : IProgress
{
public readonly List Reports = new();
public void Report(NestJobProgress value)
{
lock (Reports)
Reports.Add(value);
}
}
private static NestPipelineRequest Request()
{
var program = new Program();
program.MoveTo(0, 0);
program.LineTo(10, 0);
program.LineTo(10, 20);
program.LineTo(0, 20);
program.LineTo(0, 0);
var template = new Plate(48, 96) { EdgeSpacing = new Spacing(0.25, 0.25, 0.25, 0.25), PartSpacing = 0.25 };
return new NestPipelineRequest("Rectangles",
new[] { new NestItem { Drawing = new Drawing("bracket", program), Quantity = 9 } },
NestStockBuilder.FromTemplate(template, null),
new NestJobOptions(maxPlates: 5));
}
[Fact]
public void TheProgressWindowShowsTheStreamedStageAndPlan()
{
var request = Request();
var tap = new Collect();
NestPipeline.Run(new RecordingNestingEngine(NestingEngineRegistry.Create(request.EngineName)) { Progress = tap },
request.EngineName, request);
var plan = tap.Reports.Last(r => r.Stage == NestJobStage.PlanImproved).Plan;
var job = new NestJobRecord
{
Id = Guid.NewGuid(),
EngineName = "Rectangles",
State = NestJobState.Running,
Revision = 3,
Progress = RemoteNestProgress.From(new NestJobProgress(NestJobStage.EvaluatingCandidate, "s0", 1, 1, 9)).ToJson(),
};
StaTestThread.Run(() =>
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
using var cts = new CancellationTokenSource();
using var form = new NestProgressForm(cts, showPlateRow: false) { AllowAccept = false, HoldOpenUntilCompleted = true };
form.ShowPlanStrip(new ColorScheme());
_ = form.Handle;
var view = new ServerAutoNest.LiveView(form, request, cts.Token);
var description = form.Controls.Find("descriptionValue", true).Single();
view.ShowJob(job);
Assert.Equal("On the nest server: Rectangles: evaluating plate 2 on stock s0", description.Text);
view.ShowProblem("Waiting for the nest server: connection refused");
Assert.StartsWith("Waiting for the nest server", description.Text);
view.ShowProblem("");
Assert.StartsWith("On the nest server", description.Text);
view.ShowPlan(RemoteNestPlan.From(plan).ToJson());
var deadline = DateTime.UtcNow.AddSeconds(10);
while (!view.Drawn.IsCompleted && DateTime.UtcNow < deadline)
Application.DoEvents();
Assert.True(view.Drawn.IsCompleted);
var strip = Descendants(form).OfType().Single();
Assert.Equal(9, strip.PartCount);
// Updates posted after the run ended change nothing.
view.Stop();
view.ShowJob(new NestJobRecord { State = NestJobState.Queued });
Assert.StartsWith("On the nest server", description.Text);
}, TimeSpan.FromSeconds(30), "The STA test did not complete.");
}
[Fact]
public void TheNoticeShowsOnlyWhileOpenNestIsAwayAndGoesOnceItIsBack()
{
StaTestThread.Run(() =>
{
using var main = new Form { ShowInTaskbar = false, WindowState = FormWindowState.Minimized };
_ = main.Handle;
// Minimized counts as away even when one of OpenNest's windows is active.
Assert.True(ServerNestNotice.IsAway(main));
using var notice = ServerNestNotice.ShowIfAway(main, "Server nest finished", "N26-0042 is ready to review.");
Assert.NotNull(notice);
Assert.True(notice!.IsShown);
// Clicking it restores the main window and removes the icon.
notice.BringForward();
Assert.Equal(FormWindowState.Normal, main.WindowState);
Assert.False(notice.IsShown);
}, TimeSpan.FromSeconds(15), "The STA test did not complete.");
}
private static IEnumerable Descendants(Control parent) =>
parent.Controls.Cast().SelectMany(c => Descendants(c).Prepend(c));
}