diff --git a/OpenNest.WinForms.Tests/ArchUnitsTests.cs b/OpenNest.WinForms.Tests/ArchUnitsTests.cs new file mode 100644 index 0000000..00a121b --- /dev/null +++ b/OpenNest.WinForms.Tests/ArchUnitsTests.cs @@ -0,0 +1,65 @@ +using System.Drawing; +using System.Globalization; +using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Text.RegularExpressions; +using System.Windows.Forms; + +namespace OpenNest.WinForms.Tests; + +public class ArchUnitsTests +{ + [Fact] + public void ArchitecturalRegexHasExplicitTimeout() + { + var regex = Assert.IsType(typeof(ArchUnits) + .GetField("UnitRegex", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)); + Assert.Equal(TimeSpan.FromMilliseconds(250), regex.MatchTimeout); + Assert.Equal(RegexOptions.None, regex.Options); + } + + [Theory] + [InlineData("5' 6.5\"", 66.5)] + [InlineData("2 feet 3 1/2 inches", 27.5)] + [InlineData("1-1/2\"", 1.5)] + [InlineData("48", 48)] + [InlineData("", 0)] + public void OrdinaryArchitecturalInputsKeepTheirMeaning(string input, double expected) + { + var original = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + Assert.Equal(expected, ArchUnits.ParseToInches(input)); + } + finally { CultureInfo.CurrentCulture = original; } + } + + [Fact] + public void InvalidFieldIsRedAndNaNAndCorrectionRestoresIt() => RunSta(() => + { + using var field = new TextBox { Text = "invalid dimension" }; + Assert.True(double.IsNaN(ArchUnits.GetLengthInches(field))); + Assert.Equal(Color.Red, field.ForeColor); + field.Text = "12\""; + Assert.Equal(12, ArchUnits.GetLengthInches(field)); + Assert.Equal(SystemColors.WindowText, field.ForeColor); + }); + + // Actual timeout execution is covered by the shared Regex mechanism in FractionTests; + // these desktop tests deliberately avoid timing-sensitive oversized-input fixtures. + internal static void RunSta(Action action) + { + Exception? error = null; + var thread = new Thread(() => + { + try { action(); } + catch (Exception ex) { error = ex; } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + Assert.True(thread.Join(TimeSpan.FromSeconds(15))); + if (error != null) + ExceptionDispatchInfo.Capture(error).Throw(); + } +} diff --git a/OpenNest.WinForms.Tests/Forms/AutoNestFormStockOptionsTests.cs b/OpenNest.WinForms.Tests/Forms/AutoNestFormStockOptionsTests.cs index a360f95..c4a176b 100644 --- a/OpenNest.WinForms.Tests/Forms/AutoNestFormStockOptionsTests.cs +++ b/OpenNest.WinForms.Tests/Forms/AutoNestFormStockOptionsTests.cs @@ -1,6 +1,8 @@ using System.Collections; using System.ComponentModel; +using System.Reflection; using System.Runtime.ExceptionServices; +using System.Text.RegularExpressions; using System.Windows.Forms; using OpenNest.Forms; @@ -73,6 +75,117 @@ public class AutoNestFormStockOptionsTests }); } + [Fact] + public void StockSizeRegexHasExplicitTimeout() + { + var regex = Assert.IsType(typeof(AutoNestForm) + .GetField("SizePattern", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)); + Assert.Equal(TimeSpan.FromMilliseconds(250), regex.MatchTimeout); + Assert.Equal(RegexOptions.None, regex.Options); + } + + [Theory] + [InlineData("48 x")] + [InlineData("abc")] + [InlineData("0 x 96")] + [InlineData("-1 x 96")] + [InlineData("١ x 96")] + public void NonblankInvalidRowRejectsWholeCollection(string invalid) => RunSta(() => + { + using var form = new StockTestForm(); + form.LoadPlateOptions(new List { new() { Width = 48, Length = 96, Cost = 25 } }, 0.5); + var grid = GetGrid(form); + var rows = Assert.IsAssignableFrom(grid.DataSource); + var row = rows.AddNew()!; + SetValue(row, "Size", invalid); + ((ICancelAddNew)rows).EndNew(rows.Count - 1); + + Assert.False(form.TryGetPlateOptions(out var options, out var error)); + Assert.Empty(options); + Assert.Contains(invalid, error); + Assert.Contains("row 2", error); + Assert.Throws(() => form.GetPlateOptions()); + Assert.NotEmpty(grid.Rows[1].ErrorText); + + SetValue(row, "Size", "72.5 × 144.25"); + Assert.True(form.TryGetPlateOptions(out options, out error)); + Assert.Null(error); + Assert.Equal(2, options.Count); + Assert.Equal(72.5, options[1].Width); + Assert.Equal(144.25, options[1].Length); + Assert.Equal(25, options[0].Cost); + Assert.Empty(grid.Rows[1].ErrorText); + }); + + [Fact] + public void TimeoutAfterValidRowPublishesNoPrefixAndDoesNotAcceptDialog() => RunSta(() => + { + using var form = new StockTestForm(); + form.LoadPlateOptions(new List { new() { Width = 48, Length = 96 } }, 0.5); + var grid = GetGrid(form); + var rows = Assert.IsAssignableFrom(grid.DataSource); + var row = rows.AddNew()!; + SetValue(row, "Size", "timeout row"); + ((ICancelAddNew)rows).EndNew(rows.Count - 1); + form.FailOn = "timeout row"; + + Assert.False(form.TryGetPlateOptions(out var options, out var error)); + Assert.Empty(options); + Assert.Contains("timeout row", error); + Assert.True(form.TryClose(DialogResult.OK)); + Assert.Equal(DialogResult.None, form.DialogResult); + Assert.Contains("timeout row", Assert.Single(form.Notices)); + Assert.True(form.MatchCalls > 1); + + form.Notices.Clear(); + Assert.False(form.TryClose(DialogResult.Cancel)); + Assert.Empty(form.Notices); + Assert.False(Assert.IsType