Files
OpenNest/OpenNest.WinForms.Tests/Forms/MainFormReleaseNestingTests.cs
T
ajandClaude Opus 5.5 223bfea47c feat(desktop): undo fills, pattern tiles, auto nest and clear nest
Placement edits were only caught by the fingerprint safety net, so they
undid as an anonymous "Change" or folded into the next edit. Each now
records one labelled step: Fill (at the commit, so a stopped fill that
keeps nothing records nothing), Fill Area, Pattern Tile, Auto Nest (from
the kept proposal only; Make Nests tabs too), Clear Nest, Remove Empty
Plates and Center Parts. Clear Nest no longer warns that it cannot be
undone.

Pattern Tile onto a new plate created a plate beside the empty sentinel,
which the plate manager dropped at once, so the pattern landed on a
detached plate; it now fills the sentinel.

The undo tests share one harness that compares a text description of the
nest before an edit and after Undo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:11:47 -04:00

118 lines
4.5 KiB
C#

using System.Windows.Forms;
using OpenNest.Controls;
using OpenNest.Data;
using OpenNest.Engine;
using OpenNest.Engine.Jobs;
using OpenNest.Forms;
using OpenNest.Geometry;
using OpenNest.Shapes;
namespace OpenNest.WinForms.Tests.Forms;
// Make Nests nesting live in the main window. Reads Application.OpenForms through MainForm,
// so it must not overlap fill-lifetime tests.
[Collection("Fill operation lifetime")]
public class MainFormReleaseNestingTests
{
private static readonly TimeSpan TestTimeout = TimeSpan.FromMinutes(1);
[Fact]
public void EachNestNestsInItsOwnTab_ASavedOneIsBoundNotReopened_AndAnUnsavedOneCloses() => StaTestThread.Run(() =>
{
using var main = new Host();
main.SolveReleasedNest = (_, request, cts) =>
Task.Run(() => NestPipeline.Run(NestingEngineRegistry.Create("Rectangles"), "Rectangles", request, null, cts.Token));
main.Show();
IReleaseNestingHost host = main;
var saved = NestOf("N26-0001", 6);
var unsaved = NestOf("N26-0002", 2);
host.Begin();
var first = Run(host.RenestAsync(saved, CancellationToken.None));
var tab = Assert.Single(Tabs(main));
Assert.Same(saved, tab.Nest);
Assert.True(first.Nested, first.Note);
Assert.StartsWith("Nested on 1 sheet", first.Note);
Assert.Equal(6, saved.Plates.Sum(p => p.Parts.Count * p.Quantity));
Assert.Equal(Guid.Empty, tab.Document.RemoteId);
// The kept layout is one undoable step in the tab; undo waits for the release.
Assert.Equal(1, tab.History.UndoCount);
Assert.Equal("Auto Nest", tab.History.UndoLabel);
Assert.False(main.TryUndo(tab));
Run(host.RenestAsync(unsaved, CancellationToken.None));
Assert.Equal(2, Tabs(main).Count);
// Make Nests opens what it saved: the tab already showing it is bound instead.
var record = new NestRecord { Id = Guid.NewGuid(), Revision = 1, Name = saved.Name };
main.OpenServerNest(saved, record);
Assert.Equal(2, Tabs(main).Count);
Assert.Equal((record.Id, 1), (tab.Document.RemoteId, tab.Document.RemoteRevision));
host.End(null!);
Application.DoEvents();
Assert.Same(tab, Assert.Single(Tabs(main)));
}, TestTimeout, "The live release nesting test did not complete.");
[Fact]
public void StoppingTheAutoNestStopsTheRelease_AndLeavesNoLayout() => StaTestThread.Run(() =>
{
using var main = new Host();
main.SolveReleasedNest = (_, _, cts) =>
{
// The progress window's Stop.
cts.Cancel();
return Task.FromException<NestPipelineResult>(new OperationCanceledException(cts.Token));
};
main.Show();
IReleaseNestingHost host = main;
var nest = NestOf("N26-0003", 4);
host.Begin();
var renest = host.RenestAsync(nest, CancellationToken.None);
PumpUntil(() => renest.IsCompleted);
Assert.True(renest.IsCanceled || renest.Exception?.InnerException is OperationCanceledException);
Assert.DoesNotContain(nest.Plates, p => p.Parts.Count > 0);
host.End(null!);
Application.DoEvents();
Assert.Empty(Tabs(main));
}, TestTimeout, "The live release stop test did not complete.");
private static Nest NestOf(string name, int quantity)
{
var nest = new Nest(name) { Units = Units.Inches };
nest.PlateDefaults.Size = new Size(48, 96);
var drawing = new RectangleShape { Name = name + "-A", Length = 4, Width = 2 }.GetDrawing();
drawing.Quantity.Required = quantity;
nest.Drawings.Add(drawing);
return nest;
}
private static List<EditNestForm> Tabs(MainForm main) =>
main.MdiChildren.OfType<EditNestForm>().Where(f => !f.IsDisposed).ToList();
private static T Run<T>(Task<T> task)
{
PumpUntil(() => task.IsCompleted);
return task.GetAwaiter().GetResult();
}
private static void PumpUntil(Func<bool> condition)
{
var until = DateTime.UtcNow.AddSeconds(30);
while (!condition() && DateTime.UtcNow < until)
{
Application.DoEvents();
Thread.Sleep(10);
}
Assert.True(condition(), "Timed out.");
}
private sealed class Host : MainForm
{
// No startup migrations/automatic new document, and no settings save at close.
protected override void OnLoad(EventArgs e) { }
protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { }
}
}