fix(ui): keep refused BOM cell text out of the rows on every commit path

The first windows-desktop run of BomImportFormTests (PR #4, run
37379965924) failed four cases:

- InvalidQty_IsRefusedAndTheOldValueKept("0" / "-1" / ""): the tests
  committed with DataGridView.EndEdit(), which never raises
  CellValidating, so the grid's default parser stored 0, -1 or null.
- BlankingMaterialOrThickness...: a null value from CellParsing is
  ignored by the grid, so a blank material was stored as " ".

Operator commits (Enter, leaving the cell or the grid) do validate, but
a programmatic EndEdit() must not store text validation refuses either.
CellParsing now keeps the row's current quantity or thickness for text
validation would refuse, trims the material (a blank one is ""), and a
cleared thickness or quantity cell stores null through the columns'
DataSourceNullValue. A thickness of only spaces is refused like any
other invalid text.

The tests now commit by pressing Enter (ProcessEnterKey), as the
operator does, and a new ProgrammaticCommitOfInvalidText test covers
the EndEdit() path.
This commit is contained in:
aj committed 2026-10-05 18:09:45 -04:00
1 parent 124d70afe7
commit 248972c6e7
3 files changed
+63 -12

No files matched your search

@@ -76,6 +76,30 @@ public class BomImportFormTests
});
}
[Theory]
[InlineData("colQty", "0")]
[InlineData("colQty", "abc")]
[InlineData("colThickness", "-1")]
[InlineData("colThickness", "abc")]
public void ProgrammaticCommitOfInvalidText_KeepsTheRowsValue(string column, string text)
{
RunSta(() =>
{
using var form = Show(out var parts);
var part = Ready("PT01", qty: 2);
form.LoadRows(new[] { part });
parts.CurrentCell = parts.Rows[0].Cells[column];
Assert.True(parts.BeginEdit(selectAll: true));
parts.EditingControl!.Text = text;
Assert.True(parts.EndEdit());
Assert.Equal(2, part.Qty);
Assert.Equal(0.25, part.Thickness);
Assert.Equal(BomRowStatus.Ready, part.Status);
});
}
[Fact]
public void Edits_ChangeThePartShownInThatRow_AfterTheRowsAreReloadedInAnotherOrder()
{
@@ -131,7 +155,7 @@ public class BomImportFormTests
form.LoadRows(new[] { first, second });
Assert.True(Edit(parts, 0, "colMaterial", " "));
Assert.Null(first.Material);
Assert.Equal("", first.Material);
Assert.Equal("Needs material", parts.Rows[0].Cells["colStatus"].Value);
Assert.False(Edit(parts, 1, "colThickness", "abc"));
@@ -156,12 +180,22 @@ public class BomImportFormTests
return form;
}
/// <summary>
/// Types <paramref name="text"/> into the cell and presses Enter, which
/// validates the cell the way the operator's commit does. Returns true
/// when the edit was accepted (the cell left edit mode).
/// </summary>
private static bool Edit(DataGridView grid, int row, string column, string text)
{
grid.CurrentCell = grid.Rows[row].Cells[column];
Assert.True(grid.BeginEdit(selectAll: true));
grid.EditingControl!.Text = text;
return grid.EndEdit();
var processEnterKey = typeof(DataGridView).GetMethod(
"ProcessEnterKey",
BindingFlags.Instance | BindingFlags.NonPublic
)!;
processEnterKey.Invoke(grid, new object[] { Keys.Enter });
return !grid.IsCurrentCellInEditMode;
}
private static DataTable GroupsTable(BomImportForm form) =>
+2
View File
@@ -326,6 +326,7 @@ namespace OpenNest.Forms
// colThickness
//
thicknessCellStyle.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleRight;
thicknessCellStyle.DataSourceNullValue = null;
thicknessCellStyle.Format = "0.####";
colThickness.DataPropertyName = "Thickness";
colThickness.DefaultCellStyle = thicknessCellStyle;
@@ -337,6 +338,7 @@ namespace OpenNest.Forms
// colQty
//
qtyCellStyle.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleRight;
qtyCellStyle.DataSourceNullValue = null;
colQty.DataPropertyName = "Qty";
colQty.DefaultCellStyle = qtyCellStyle;
colQty.FillWeight = 50F;
+25 -10
View File
@@ -181,7 +181,7 @@ namespace OpenNest.Forms
error = "Enter a whole number of 1 or more.";
else if (
e.ColumnIndex == colThickness.Index
&& !string.IsNullOrWhiteSpace(text)
&& !string.IsNullOrEmpty(text)
&& !TryParseThickness(text, out _)
)
error = "Enter a thickness greater than 0, or leave it blank.";
@@ -191,27 +191,42 @@ namespace OpenNest.Forms
e.Cancel = true;
}
// CellValidating refuses bad text when the operator commits a cell. A
// programmatic EndEdit() skips validation, so parsing keeps the row's
// current value rather than storing text validation would refuse. The
// grid ignores a null parsed value, so a blank thickness is left to the
// column's DataSourceNullValue (null).
private void DgvParts_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
{
var text = e.Value?.ToString();
var text = e.Value?.ToString() ?? "";
var part = PartAt(e.RowIndex);
if (e.ColumnIndex == colQty.Index && BomQuantity.TryParse(text, out var qty))
if (e.ColumnIndex == colQty.Index)
{
e.Value = qty;
e.ParsingApplied = true;
if (BomQuantity.TryParse(text, out var qty))
Parsed(e, qty);
else if (part?.Qty is int current)
Parsed(e, current);
}
else if (e.ColumnIndex == colThickness.Index)
else if (e.ColumnIndex == colThickness.Index && text.Length > 0)
{
e.Value = TryParseThickness(text, out var thickness) ? thickness : null;
e.ParsingApplied = true;
if (TryParseThickness(text, out var thickness))
Parsed(e, thickness);
else if (part?.Thickness is double current)
Parsed(e, current);
}
else if (e.ColumnIndex == colMaterial.Index)
{
e.Value = string.IsNullOrWhiteSpace(text) ? null : text;
e.ParsingApplied = true;
Parsed(e, text.Trim());
}
}
private static void Parsed(DataGridViewCellParsingEventArgs e, object value)
{
e.Value = value;
e.ParsingApplied = true;
}
private void DgvParts_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
dgvParts.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = "";