mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-10 08:44:25 -04:00
test(ui): cover shown BOM import and nest info persistence
This commit is contained in:
1 parent
b591528aa2
commit
71f41e23e4
3 files changed
+364
No files matched your search
@@ -1,13 +1,211 @@
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Forms;
|
||||
using OpenNest.IO.Bom;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Forms;
|
||||
|
||||
[Collection("Fill operation lifetime")]
|
||||
public class BomImportFormTests
|
||||
{
|
||||
[Fact]
|
||||
public void FileNameWithDxfExtension_RealRowBuilderShowsReady()
|
||||
{
|
||||
RunSta(() =>
|
||||
{
|
||||
using var files = new WindowsAcceptanceFiles();
|
||||
var path = files.WriteSquare("acceptance-square");
|
||||
var row = Assert.Single(BomImportRows.Build(
|
||||
new List<BomItem> { Item("acceptance-square.dxf", qty: 2) }, files.Folder));
|
||||
using var form = Show(out var parts);
|
||||
|
||||
form.LoadRows(new[] { row });
|
||||
|
||||
Assert.True(form.Visible);
|
||||
Assert.Equal(path, row.DxfPath);
|
||||
Assert.Same(row, parts.Rows[0].DataBoundItem);
|
||||
Assert.Equal("acceptance-square.dxf", parts.Rows[0].Cells["colFileName"].Value);
|
||||
Assert.Equal("Ready", parts.Rows[0].Cells["colStatus"].Value);
|
||||
Assert.Equal(BomRowStatus.Ready, row.Status);
|
||||
Assert.Single(GroupsTable(form).Rows);
|
||||
Assert.Equal(2, GroupTotal(form, 0));
|
||||
Assert.True(Field<Button>(form, "btnCreateNests").Enabled);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BlankThickness_RealRowBuilderResolvesDrawingAndGridEditMakesReadyGroup()
|
||||
{
|
||||
RunSta(() =>
|
||||
{
|
||||
using var files = new WindowsAcceptanceFiles();
|
||||
var path = files.WriteSquare("acceptance-square");
|
||||
var item = Item("acceptance-square", qty: 3);
|
||||
item.Thickness = null;
|
||||
var row = Assert.Single(BomImportRows.Build(new List<BomItem> { item }, files.Folder));
|
||||
using var form = Show(out var parts);
|
||||
form.LoadRows(new[] { row });
|
||||
|
||||
Assert.True(form.Visible);
|
||||
Assert.Equal(path, row.DxfPath);
|
||||
Assert.Null(row.Thickness);
|
||||
Assert.True(row.IsEditable);
|
||||
Assert.Equal("Needs thickness", parts.Rows[0].Cells["colStatus"].Value);
|
||||
Assert.Empty(GroupsTable(form).Rows);
|
||||
Assert.False(Field<Button>(form, "btnCreateNests").Enabled);
|
||||
|
||||
Assert.True(Edit(parts, 0, "colThickness", 0.25.ToString()));
|
||||
|
||||
Assert.Equal(0.25, row.Thickness);
|
||||
Assert.Equal(BomRowStatus.Ready, row.Status);
|
||||
Assert.Equal("Ready", parts.Rows[0].Cells["colStatus"].Value);
|
||||
var group = Assert.Single(GroupsTable(form).Rows.Cast<DataRow>());
|
||||
Assert.Equal("Stainless", group["Material"]);
|
||||
Assert.Equal(0.25, group["Thickness"]);
|
||||
Assert.Equal(1, group["Parts"]);
|
||||
Assert.Equal(3, group["Total Qty"]);
|
||||
Assert.Equal("1 ready", Field<Label>(form, "lblSummary").Text);
|
||||
Assert.True(Field<Button>(form, "btnCreateNests").Enabled);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DuplicateRows_CreateNestsButtonImportsOneDrawingWithCombinedQuantity()
|
||||
{
|
||||
RunSta(() =>
|
||||
{
|
||||
using var files = new WindowsAcceptanceFiles();
|
||||
var path = files.WriteSquare("acceptance-square");
|
||||
var rows = BomImportRows.Build(
|
||||
new List<BomItem>
|
||||
{
|
||||
Item("acceptance-square", qty: 2),
|
||||
Item("acceptance-square.dxf", qty: 3),
|
||||
}, files.Folder);
|
||||
using var host = new Form { IsMdiContainer = true };
|
||||
host.Show();
|
||||
using var form = Show(out var parts);
|
||||
form.MdiParentForm = host;
|
||||
Field<TextBox>(form, "txtJobName").Text = "Acceptance job";
|
||||
Field<TextBox>(form, "txtPlateWidth").Text = "10";
|
||||
Field<TextBox>(form, "txtPlateLength").Text = "20";
|
||||
form.LoadRows(rows);
|
||||
|
||||
Assert.Equal(2, parts.Rows.Count);
|
||||
Assert.All(rows, row => Assert.Equal(path, row.DxfPath));
|
||||
Assert.Equal(5, GroupTotal(form, 0));
|
||||
var create = Field<Button>(form, "btnCreateNests");
|
||||
Assert.True(create.Enabled);
|
||||
|
||||
// The production event ends in a native MessageBox. A timer in
|
||||
// its modal loop clicks that thread's real OK button, retaining
|
||||
// the actual result text; no builder or success result is stubbed.
|
||||
var dialogTitle = "";
|
||||
var dialogText = "";
|
||||
var okPosted = false;
|
||||
var deadline = DateTime.UtcNow.AddSeconds(10);
|
||||
using var dismiss = new System.Windows.Forms.Timer { Interval = 20 };
|
||||
dismiss.Tick += (_, _) =>
|
||||
{
|
||||
EnumThreadWindows(GetCurrentThreadId(), (window, _) =>
|
||||
{
|
||||
var className = new StringBuilder(256);
|
||||
GetClassName(window, className, className.Capacity);
|
||||
if (className.ToString() != "#32770")
|
||||
return true;
|
||||
|
||||
dialogTitle = WindowText(window);
|
||||
var text = FindWindowEx(window, IntPtr.Zero, "Static", null);
|
||||
while (text != IntPtr.Zero)
|
||||
{
|
||||
var value = WindowText(text);
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
dialogText += value;
|
||||
text = FindWindowEx(window, text, "Static", null);
|
||||
}
|
||||
var ok = GetDlgItem(window, 1);
|
||||
okPosted = ok != IntPtr.Zero && PostMessage(ok, 0x00F5, IntPtr.Zero, IntPtr.Zero);
|
||||
dismiss.Stop();
|
||||
return false;
|
||||
}, IntPtr.Zero);
|
||||
if (DateTime.UtcNow >= deadline && !okPosted)
|
||||
throw new TimeoutException("The Create Nests result dialog could not be acknowledged.");
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
dismiss.Start();
|
||||
create.PerformClick();
|
||||
dismiss.Stop();
|
||||
|
||||
Assert.True(okPosted, "Create Nests did not show its result dialog.");
|
||||
Assert.Equal("Import Complete", dialogTitle);
|
||||
Assert.Equal("1 nest created.", dialogText);
|
||||
Assert.False(form.Visible);
|
||||
var editor = Assert.IsType<EditNestForm>(Assert.Single(host.MdiChildren));
|
||||
Assert.True(editor.Visible);
|
||||
var nest = editor.Nest;
|
||||
var drawing = Assert.Single(nest.Drawings);
|
||||
Assert.Equal("acceptance-square", drawing.Name);
|
||||
Assert.Equal(5, drawing.Quantity.Required);
|
||||
Assert.NotEmpty(drawing.Program.Codes);
|
||||
Assert.Equal(4, drawing.Area, precision: 6);
|
||||
Assert.Equal($"Acceptance job - {0.25:0.####} Stainless", nest.Name);
|
||||
Assert.Equal("Stainless", nest.Material.Name);
|
||||
Assert.Equal(0.25, nest.Thickness);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dismiss.Stop();
|
||||
foreach (var child in host.MdiChildren)
|
||||
child.Dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static BomItem Item(string fileName, int qty) => new()
|
||||
{
|
||||
FileName = fileName,
|
||||
Material = "Stainless",
|
||||
Thickness = 0.25,
|
||||
Qty = qty,
|
||||
};
|
||||
|
||||
private static string WindowText(IntPtr window)
|
||||
{
|
||||
var text = new StringBuilder(1024);
|
||||
GetWindowText(window, text, text.Capacity);
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
private delegate bool EnumThreadWindowCallback(IntPtr window, IntPtr parameter);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint GetCurrentThreadId();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool EnumThreadWindows(uint threadId, EnumThreadWindowCallback callback, IntPtr parameter);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int GetClassName(IntPtr window, StringBuilder className, int capacity);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int GetWindowText(IntPtr window, StringBuilder text, int capacity);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr FindWindowEx(IntPtr parent, IntPtr after, string className, string? title);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetDlgItem(IntPtr dialog, int id);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool PostMessage(IntPtr window, uint message, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[Fact]
|
||||
public void PartColumns_KeepBomOrderAndOnlyMaterialThicknessAndQtyAreEditable()
|
||||
{
|
||||
|
||||
@@ -1,9 +1,127 @@
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Forms;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Forms;
|
||||
|
||||
[Collection("Fill operation lifetime")]
|
||||
public class EditNestInfoFormTests
|
||||
{
|
||||
[Fact]
|
||||
public void ShownEditorOk_FileRoundTripPreservesUnchangedMaterialGradeAndDensity()
|
||||
{
|
||||
RunSta(() =>
|
||||
{
|
||||
using var files = new WindowsAcceptanceFiles();
|
||||
var nest = AcceptanceNest();
|
||||
|
||||
AcceptShownEditor(nest, materialName: null);
|
||||
|
||||
Assert.Equal("Stainless", nest.Material.Name);
|
||||
Assert.Equal("304", nest.Material.Grade);
|
||||
Assert.Equal(0.289, nest.Material.Density);
|
||||
var restored = files.RoundTrip(nest);
|
||||
Assert.Equal("Stainless", restored.Material.Name);
|
||||
Assert.Equal("304", restored.Material.Grade);
|
||||
Assert.Equal(0.289, restored.Material.Density);
|
||||
Assert.Equal("Acceptance nest", restored.Name);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShownEditorOk_FileRoundTripClearsOldMetadataWhenMaterialNameChanges()
|
||||
{
|
||||
RunSta(() =>
|
||||
{
|
||||
using var files = new WindowsAcceptanceFiles();
|
||||
var nest = AcceptanceNest();
|
||||
|
||||
AcceptShownEditor(nest, materialName: "Aluminum");
|
||||
|
||||
Assert.Equal("Aluminum", nest.Material.Name);
|
||||
Assert.True(string.IsNullOrEmpty(nest.Material.Grade));
|
||||
Assert.Equal(0, nest.Material.Density);
|
||||
var restored = files.RoundTrip(nest);
|
||||
Assert.Equal("Aluminum", restored.Material.Name);
|
||||
Assert.True(string.IsNullOrEmpty(restored.Material.Grade));
|
||||
Assert.Equal(0, restored.Material.Density);
|
||||
Assert.Equal("Acceptance nest", restored.Name);
|
||||
});
|
||||
}
|
||||
|
||||
private static Nest AcceptanceNest()
|
||||
{
|
||||
var nest = new Nest("Acceptance nest")
|
||||
{
|
||||
Material = new Material("Stainless", "304", 0.289),
|
||||
Thickness = 0.25,
|
||||
Units = Units.Inches,
|
||||
};
|
||||
nest.PlateDefaults.Size = new Geometry.Size(10, 20);
|
||||
nest.CreatePlate();
|
||||
return nest;
|
||||
}
|
||||
|
||||
private static void AcceptShownEditor(Nest nest, string? materialName)
|
||||
{
|
||||
using var editor = new EditNestForm(nest);
|
||||
editor.Show();
|
||||
Assert.True(editor.Visible);
|
||||
EditNestInfoForm? info = null;
|
||||
Exception? failure = null;
|
||||
var deadline = DateTime.UtcNow.AddSeconds(10);
|
||||
using var accept = new System.Windows.Forms.Timer { Interval = 20 };
|
||||
accept.Tick += (_, _) =>
|
||||
{
|
||||
info = Application.OpenForms.OfType<EditNestInfoForm>().SingleOrDefault();
|
||||
if (info == null)
|
||||
{
|
||||
if (DateTime.UtcNow >= deadline)
|
||||
throw new TimeoutException("The Nest Info editor was not shown.");
|
||||
return;
|
||||
}
|
||||
|
||||
accept.Stop();
|
||||
try
|
||||
{
|
||||
Assert.True(info.Visible);
|
||||
Assert.True(info.Modal);
|
||||
if (materialName != null)
|
||||
Assert.IsType<ComboBox>(info.Controls.Find("materialBox", true).Single()).Text = materialName;
|
||||
info.EnableCheck();
|
||||
var ok = Assert.IsType<Button>(info.Controls.Find("applyButton", true).Single());
|
||||
Assert.True(ok.Enabled);
|
||||
Assert.Equal(DialogResult.OK, ok.DialogResult);
|
||||
ok.PerformClick();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failure = ex;
|
||||
info.DialogResult = DialogResult.Cancel;
|
||||
info.Close();
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
accept.Start();
|
||||
// This is the live Nest Info entry point; it owns LoadNestInfo,
|
||||
// ShowDialog and SaveNestInfo after the actual OK button accepts.
|
||||
editor.ShowNestInfoEditor();
|
||||
accept.Stop();
|
||||
if (failure != null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
Assert.NotNull(info);
|
||||
Assert.Equal(DialogResult.OK, info.DialogResult);
|
||||
Assert.False(info.Visible);
|
||||
}
|
||||
finally
|
||||
{
|
||||
accept.Stop();
|
||||
info?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveWithUnchangedMaterial_KeepsGradeAndDensity()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.IO;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Forms;
|
||||
|
||||
/// <summary>Private, synthetic files only; removed after each acceptance case.</summary>
|
||||
internal sealed class WindowsAcceptanceFiles : IDisposable
|
||||
{
|
||||
public string Folder { get; } = Path.Combine(Path.GetTempPath(), "opennest-acceptance-" + Guid.NewGuid().ToString("N"));
|
||||
|
||||
public WindowsAcceptanceFiles()
|
||||
{
|
||||
Directory.CreateDirectory(Folder);
|
||||
}
|
||||
|
||||
public string WriteSquare(string name)
|
||||
{
|
||||
var shape = new Shape();
|
||||
shape.Entities.Add(new Line(new Vector(0, 0), new Vector(2, 0)));
|
||||
shape.Entities.Add(new Line(new Vector(2, 0), new Vector(2, 2)));
|
||||
shape.Entities.Add(new Line(new Vector(2, 2), new Vector(0, 2)));
|
||||
shape.Entities.Add(new Line(new Vector(0, 2), new Vector(0, 0)));
|
||||
var path = Path.Combine(Folder, name + ".dxf");
|
||||
Dxf.ExportProgram(ConvertGeometry.ToProgram(shape), path);
|
||||
Assert.True(File.Exists(path));
|
||||
return path;
|
||||
}
|
||||
|
||||
public Nest RoundTrip(Nest nest)
|
||||
{
|
||||
var path = Path.Combine(Folder, "acceptance.nest");
|
||||
Assert.True(new NestWriter(nest).Write(path));
|
||||
Assert.True(File.Exists(path));
|
||||
Assert.True(new FileInfo(path).Length > 0);
|
||||
using var stream = File.OpenRead(path);
|
||||
var reader = new NestReader(stream);
|
||||
var restored = reader.Read();
|
||||
Assert.Empty(reader.Warnings);
|
||||
Assert.NotSame(nest, restored);
|
||||
return restored;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Directory.Delete(Folder, recursive: true);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user