mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 09:21:05 -04:00
Operators can now pull shared library drawings into a nest. Nest > Add from Drawing Library... and a Library button above the drawing list open a paged browser (name, customer, material, grade, thickness, cut type, revision, saved) with a notes preview and a quantity. It starts filtered to the nest's material, grade and thickness, with Show all to lift that. Add downloads the listed revision's immutable package, not the current one, so a revision saved meanwhile can never be what lands in the nest. The insert is planned first and every warning or refusal is shown before the nest changes; the dialog is modal, so the nest cannot move between plan and apply. The dialog stays open for further adds and the drawing list refreshes on close. Both entries are Database mode only: the library lives on the nest server, so in File mode they are disabled with a "Database mode only" tooltip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
203 lines
8.2 KiB
C#
203 lines
8.2 KiB
C#
using System.Reflection;
|
|
using System.Security.Cryptography;
|
|
using System.Windows.Forms;
|
|
using OpenNest.Data;
|
|
using OpenNest.Forms;
|
|
using OpenNest.IO;
|
|
using OpenNest.Shapes;
|
|
|
|
namespace OpenNest.WinForms.Tests.Forms;
|
|
|
|
public class DrawingLibraryFormTests
|
|
{
|
|
private const string Server = "http://library.test:8090";
|
|
private static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(60);
|
|
|
|
[Fact]
|
|
public void ListsTheNestFilteredPage_AndAddThenAddAgainRaisesTheQuantity()
|
|
{
|
|
StaTestThread.Run(
|
|
() =>
|
|
{
|
|
var library = new FakeLibrary();
|
|
var record = library.Add("SYN-101", "Synthetic Steel", "Grade A", 0.25, Units.Inches, "Deburr both sides");
|
|
var nest = MatchingNest();
|
|
var messages = new List<string>();
|
|
|
|
using var form = new DrawingLibraryForm(library, Server, nest);
|
|
form.ShowMessage = (text, _, _) => { messages.Add(text); return DialogResult.OK; };
|
|
form.Show();
|
|
var grid = Field<DataGridView>(form, "drawingGrid");
|
|
var notes = Field<TextBox>(form, "notesBox");
|
|
var quantity = Field<NumericUpDown>(form, "quantityBox");
|
|
|
|
PumpUntil(() => grid.Rows.Count == 1, "the library page");
|
|
var query = Assert.Single(library.Queries);
|
|
Assert.Equal(("Synthetic Steel", "Grade A", (double?)0.25), (query.MaterialName, query.MaterialGrade, query.Thickness));
|
|
Assert.Equal("SYN-101", grid.Rows[0].Cells["nameColumn"].Value);
|
|
Assert.Equal("Laser", grid.Rows[0].Cells["cutTypeColumn"].Value);
|
|
Assert.Equal("Deburr both sides", notes.Text);
|
|
|
|
quantity.Value = 3;
|
|
Run(form.AddSelectedAsync());
|
|
quantity.Value = 2;
|
|
Run(form.AddSelectedAsync());
|
|
|
|
Assert.Empty(messages);
|
|
var drawing = Assert.Single(nest.Drawings);
|
|
Assert.Equal(5, drawing.Quantity.Required);
|
|
Assert.Equal((Server, record.Id, record.Revision), (drawing.Library!.ServerKey, drawing.Library.Id, drawing.Library.Revision));
|
|
Assert.Same(drawing, Assert.Single(form.AddedDrawings));
|
|
Assert.Equal(new[] { (record.Id, record.Revision), (record.Id, record.Revision) }, library.Downloads);
|
|
},
|
|
TestTimeout,
|
|
"The drawing library dialog test did not complete.");
|
|
}
|
|
|
|
[Fact]
|
|
public void MismatchWarning_IsShownBeforeTheAdd_AndCancelLeavesTheNestAlone()
|
|
{
|
|
StaTestThread.Run(
|
|
() =>
|
|
{
|
|
var library = new FakeLibrary();
|
|
library.Add("SYN-202", "Other Steel", "Grade B", 0.1875, Units.Inches, "");
|
|
var nest = MatchingNest();
|
|
var answers = new Queue<DialogResult>(new[] { DialogResult.Cancel, DialogResult.OK });
|
|
var messages = new List<string>();
|
|
|
|
using var form = new DrawingLibraryForm(library, Server, nest);
|
|
form.ShowMessage = (text, buttons, _) =>
|
|
{
|
|
messages.Add(text);
|
|
Assert.Equal(MessageBoxButtons.OKCancel, buttons);
|
|
return answers.Dequeue();
|
|
};
|
|
form.Show();
|
|
var grid = Field<DataGridView>(form, "drawingGrid");
|
|
var showAll = Field<CheckBox>(form, "showAllCheckBox");
|
|
|
|
showAll.Checked = true;
|
|
PumpUntil(() => grid.Rows.Count == 1, "the unfiltered page");
|
|
Assert.Equal(("", "", (double?)null), (library.Queries[^1].MaterialName, library.Queries[^1].MaterialGrade, library.Queries[^1].Thickness));
|
|
|
|
Run(form.AddSelectedAsync());
|
|
Assert.Empty(nest.Drawings);
|
|
Run(form.AddSelectedAsync());
|
|
|
|
Assert.Equal(2, messages.Count);
|
|
Assert.Contains("thickness 0.1875 (nest 0.25)", messages[0]);
|
|
Assert.Contains("cut pins are dropped", messages[0]);
|
|
Assert.Single(nest.Drawings);
|
|
},
|
|
TestTimeout,
|
|
"The drawing library mismatch test did not complete.");
|
|
}
|
|
|
|
[Fact]
|
|
public void Refusal_IsShownAndNothingIsAdded()
|
|
{
|
|
StaTestThread.Run(
|
|
() =>
|
|
{
|
|
var library = new FakeLibrary();
|
|
library.Add("SYN-303", "Synthetic Steel", "Grade A", 0.25, Units.Millimeters, "");
|
|
var nest = MatchingNest();
|
|
var messages = new List<string>();
|
|
|
|
using var form = new DrawingLibraryForm(library, Server, nest);
|
|
form.ShowMessage = (text, buttons, _) => { messages.Add(text); return DialogResult.OK; };
|
|
form.Show();
|
|
var grid = Field<DataGridView>(form, "drawingGrid");
|
|
|
|
PumpUntil(() => grid.Rows.Count == 1, "the library page");
|
|
Run(form.AddSelectedAsync());
|
|
|
|
Assert.Contains("millimeters", Assert.Single(messages));
|
|
Assert.Empty(nest.Drawings);
|
|
Assert.Empty(form.AddedDrawings);
|
|
},
|
|
TestTimeout,
|
|
"The drawing library refusal test did not complete.");
|
|
}
|
|
|
|
|
|
|
|
private static Nest MatchingNest() => new("Synthetic job")
|
|
{
|
|
Units = Units.Inches,
|
|
Thickness = 0.25,
|
|
Material = new Material("Synthetic Steel", "Grade A"),
|
|
};
|
|
|
|
private static void Run(Task task) => PumpUntil(() => task.IsCompleted, "the add to finish");
|
|
|
|
private static void PumpUntil(Func<bool> condition, string what)
|
|
{
|
|
var until = DateTime.UtcNow.AddSeconds(15);
|
|
while (!condition() && DateTime.UtcNow < until)
|
|
{
|
|
Application.DoEvents();
|
|
Thread.Sleep(10);
|
|
}
|
|
|
|
Assert.True(condition(), $"Timed out waiting for {what}.");
|
|
}
|
|
|
|
private static T Field<T>(object owner, string name) =>
|
|
(T)owner.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(owner)!;
|
|
|
|
/// <summary>Serves every record (ignoring filters) and real packages by revision.</summary>
|
|
private sealed class FakeLibrary : IDrawingLibrary
|
|
{
|
|
private readonly List<DrawingLibraryRecord> _records = new();
|
|
private readonly Dictionary<(Guid, int), byte[]> _packages = new();
|
|
|
|
public List<DrawingLibraryQuery> Queries { get; } = new();
|
|
|
|
public List<(Guid, int)> Downloads { get; } = new();
|
|
|
|
public DrawingLibraryRecord Add(string name, string material, string grade, double thickness, Units units, string notes)
|
|
{
|
|
var drawing = new RectangleShape { Name = name, Length = 4, Width = 2 }.GetDrawing();
|
|
var package = DrawingPackage.Write(drawing, units);
|
|
var record = new DrawingLibraryRecord
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Revision = 2,
|
|
Name = name,
|
|
Customer = "Synthetic customer",
|
|
MaterialName = material,
|
|
MaterialGrade = grade,
|
|
Thickness = thickness,
|
|
Units = units,
|
|
CutType = CutType.Laser,
|
|
Notes = notes,
|
|
PackageSha256 = Convert.ToHexString(SHA256.HashData(package)).ToLowerInvariant(),
|
|
SavedAt = new DateTime(2026, 10, 1, 12, 0, 0, DateTimeKind.Utc),
|
|
};
|
|
_records.Add(record);
|
|
_packages[(record.Id, record.Revision)] = package;
|
|
return record;
|
|
}
|
|
|
|
public Task<DrawingLibraryPage> QueryAsync(DrawingLibraryQuery query, CancellationToken cancellationToken = default)
|
|
{
|
|
Queries.Add(query);
|
|
return Task.FromResult(new DrawingLibraryPage
|
|
{
|
|
Items = _records.ToArray(),
|
|
Total = _records.Count,
|
|
Offset = query.Offset,
|
|
Limit = query.Limit,
|
|
});
|
|
}
|
|
|
|
public Task<byte[]?> GetRevisionPackageAsync(Guid id, int revision, CancellationToken cancellationToken = default)
|
|
{
|
|
Downloads.Add((id, revision));
|
|
return Task.FromResult(_packages.TryGetValue((id, revision), out var package) ? package : null);
|
|
}
|
|
}
|
|
}
|