mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-01 18:11:37 -05:00
refactor(desktop): move Document and undo recording into OpenNest.Desktop
Document and Undo/ (ChangeScope, NestFingerprint, NestUndoState) use only Core, Data, IO and Engine, so they move into the toolkit-neutral library both desktop front ends share. Namespaces are unchanged, so the WinForms app compiles as before; it keeps using the internal scope factories and fingerprint through InternalsVisibleTo. The scope, fingerprint and SaveAs/save-point rules now have Linux tests in OpenNest.Desktop.Tests; before, only the Windows-only WinForms suite covered them.
This commit is contained in:
1 parent
0719476618
commit
38de7ab3a3
10 files changed
+202
-2
No files matched your search
@@ -0,0 +1,54 @@
|
||||
using OpenNest.IO;
|
||||
using OpenNest.Undo;
|
||||
|
||||
namespace OpenNest.Desktop.Tests;
|
||||
|
||||
public sealed class DocumentTests : IDisposable
|
||||
{
|
||||
private readonly string directory =
|
||||
Directory.CreateTempSubdirectory("opennest-desktop-document-").FullName;
|
||||
|
||||
public void Dispose() => Directory.Delete(directory, recursive: true);
|
||||
|
||||
[Fact]
|
||||
public void SaveAs_WritesAReadableNestNamedAfterTheFile()
|
||||
{
|
||||
var document = new Document { Nest = TestNests.OneSquare() };
|
||||
var path = Path.Combine(directory, "Renamed Job.nest");
|
||||
|
||||
document.SaveAs(path);
|
||||
|
||||
Assert.Equal("Renamed Job", document.Name);
|
||||
Assert.Equal(path, document.LastSavePath);
|
||||
Assert.True(document.HasSavePath);
|
||||
var read = new NestReader(path).Read();
|
||||
Assert.Equal("Renamed Job", read.Name);
|
||||
Assert.Single(read.Plates);
|
||||
Assert.Single(read.Plates[0].Parts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveAs_MarksTheCurrentHistoryStateSaved()
|
||||
{
|
||||
var document = new Document { Nest = TestNests.OneSquare() };
|
||||
document.History.Reset(new NestUndoState(NestSnapshot.Capture(document.Nest)));
|
||||
document.Nest.Plates[0].Parts[0].Offset(1, 0);
|
||||
document.History.Commit("Move", new NestUndoState(NestSnapshot.Capture(document.Nest)));
|
||||
Assert.False(document.History.IsAtSavePoint);
|
||||
|
||||
document.SaveAs(Path.Combine(directory, "saved.nest"));
|
||||
|
||||
Assert.True(document.History.IsAtSavePoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_RoundTripsThroughTheNestReader()
|
||||
{
|
||||
var document = new Document { Nest = TestNests.OneSquare() };
|
||||
|
||||
using var stream = new MemoryStream(document.Serialize());
|
||||
var read = new NestReader(stream).Read();
|
||||
|
||||
Assert.Equal(document.Nest.Plates[0].Parts.Count, read.Plates[0].Parts.Count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Desktop.Tests;
|
||||
|
||||
internal static class TestNests
|
||||
{
|
||||
/// <summary>A nest with one 48x96 plate holding one 10x10 square part.</summary>
|
||||
public static Nest OneSquare()
|
||||
{
|
||||
var program = new Program();
|
||||
program.Codes.Add(new RapidMove(new Vector(0, 0)));
|
||||
program.Codes.Add(new LinearMove(new Vector(10, 0)));
|
||||
program.Codes.Add(new LinearMove(new Vector(10, 10)));
|
||||
program.Codes.Add(new LinearMove(new Vector(0, 10)));
|
||||
program.Codes.Add(new LinearMove(new Vector(0, 0)));
|
||||
|
||||
var drawing = new Drawing("square", program);
|
||||
drawing.Quantity.Required = 1;
|
||||
|
||||
var plate = new Plate(new Size(48, 96));
|
||||
plate.Parts.Add(new Part(drawing));
|
||||
|
||||
var nest = new Nest { Name = "test" };
|
||||
nest.Drawings.Add(drawing);
|
||||
nest.Plates.Add(plate);
|
||||
return nest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using OpenNest.Undo;
|
||||
|
||||
namespace OpenNest.Desktop.Tests.Undo;
|
||||
|
||||
public class ChangeScopeTests
|
||||
{
|
||||
[Fact]
|
||||
public void RootScope_EndsOnceAndCarriesChangesMergedFromNestedScopes()
|
||||
{
|
||||
var ends = new List<ChangeScope>();
|
||||
var root = ChangeScope.Root("Rotate", "key", ends.Add);
|
||||
|
||||
using (var nested = ChangeScope.Nested(root))
|
||||
{
|
||||
nested.MarkChanged();
|
||||
nested.Complete();
|
||||
Assert.Equal("Rotate", nested.Label);
|
||||
Assert.Null(nested.CoalesceKey);
|
||||
}
|
||||
|
||||
Assert.Empty(ends);
|
||||
root.Complete();
|
||||
root.Dispose();
|
||||
root.Dispose();
|
||||
|
||||
var ended = Assert.Single(ends);
|
||||
Assert.Same(root, ended);
|
||||
Assert.True(ended.IsChanged);
|
||||
Assert.True(ended.Succeeded);
|
||||
Assert.Equal("key", ended.CoalesceKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IncompleteNestedScope_FailsTheWholeEntry()
|
||||
{
|
||||
ChangeScope ended = null!;
|
||||
var root = ChangeScope.Root("Fill", null, scope => ended = scope);
|
||||
|
||||
using (ChangeScope.Nested(root))
|
||||
{
|
||||
// Left incomplete, as when the nested edit throws or is cancelled.
|
||||
}
|
||||
|
||||
root.MarkChanged();
|
||||
root.Complete();
|
||||
root.Dispose();
|
||||
|
||||
Assert.True(ended.IsChanged);
|
||||
Assert.False(ended.Succeeded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RootScope_DisposedWithoutComplete_DoesNotSucceed()
|
||||
{
|
||||
ChangeScope ended = null!;
|
||||
using (var root = ChangeScope.Root("Move", null, scope => ended = scope))
|
||||
root.MarkChanged();
|
||||
|
||||
Assert.False(ended.Succeeded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetachedScope_RecordsNothingAndToleratesDispose()
|
||||
{
|
||||
var scope = ChangeScope.Detached();
|
||||
Assert.False(scope.IsChanged);
|
||||
|
||||
scope.MarkChanged();
|
||||
scope.Complete();
|
||||
scope.Dispose();
|
||||
|
||||
Assert.True(scope.IsChanged);
|
||||
Assert.Null(scope.Label);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using OpenNest.Undo;
|
||||
|
||||
namespace OpenNest.Desktop.Tests.Undo;
|
||||
|
||||
public class NestFingerprintTests
|
||||
{
|
||||
[Fact]
|
||||
public void UnchangedNest_KeepsItsFingerprint()
|
||||
{
|
||||
var nest = TestNests.OneSquare();
|
||||
|
||||
Assert.Equal(NestFingerprint.Compute(nest), NestFingerprint.Compute(nest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MovingAPart_ChangesTheFingerprint()
|
||||
{
|
||||
var nest = TestNests.OneSquare();
|
||||
var before = NestFingerprint.Compute(nest);
|
||||
|
||||
nest.Plates[0].Parts[0].Offset(1, 0);
|
||||
|
||||
Assert.NotEqual(before, NestFingerprint.Compute(nest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangingARequiredQuantity_ChangesTheFingerprint()
|
||||
{
|
||||
var nest = TestNests.OneSquare();
|
||||
var before = NestFingerprint.Compute(nest);
|
||||
|
||||
nest.Drawings.First().Quantity.Required = 5;
|
||||
|
||||
Assert.NotEqual(before, NestFingerprint.Compute(nest));
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// The WinForms app drives undo recording through ChangeScope's internal root/nested factories
|
||||
// and NestFingerprint.
|
||||
[assembly: InternalsVisibleTo("OpenNest")]
|
||||
[assembly: InternalsVisibleTo("OpenNest.Desktop.Tests")]
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -10,10 +10,10 @@ once its entry point records it.
|
||||
|---|---|
|
||||
| History engine (states, cursor, limits, coalescing, save point) | `OpenNest.Data/UndoHistory.cs` |
|
||||
| State codec | `OpenNest.IO/NestSnapshot.cs` |
|
||||
| Recording scope, fingerprint, history state | `OpenNest/Undo/` |
|
||||
| Recording scope, fingerprint, history state | `OpenNest.Desktop/Undo/` (shared with `OpenNest.Avalonia`) |
|
||||
| Recording, restore, safety net | `OpenNest/Forms/EditNestForm.Undo.cs` |
|
||||
| Menu, keys, guards | `OpenNest/Forms/MainForm.Undo.cs`, `MainForm.Guards.cs` (`IsUndoBusy`) |
|
||||
| Tests | `OpenNest.WinForms.Tests/Forms/UndoRedoTests.cs`, `UndoRecordingTests.cs` |
|
||||
| Tests | `OpenNest.WinForms.Tests/Forms/UndoRedoTests.cs`, `UndoRecordingTests.cs`; scope and fingerprint on Linux in `OpenNest.Desktop.Tests/Undo/` |
|
||||
|
||||
## What is undoable
|
||||
|
||||
|
||||
Reference in new issue
Block a user