fix: enforce sentinel reactively in OnPlateAdded/OnPlateRemoved
Without this, RemoveEmptyPlates would destroy the sentinel with no recovery, and tail-plate subscriptions would go stale after plate list mutations. Added tests for both scenarios. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -198,6 +198,9 @@ namespace OpenNest
|
|||||||
|
|
||||||
private void OnPlateAdded(object sender, ItemAddedEventArgs<Plate> e)
|
private void OnPlateAdded(object sender, ItemAddedEventArgs<Plate> e)
|
||||||
{
|
{
|
||||||
|
if (!suppressNavigation && !batching)
|
||||||
|
EnsureSentinel();
|
||||||
|
|
||||||
PlateListChanged?.Invoke(this, EventArgs.Empty);
|
PlateListChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
if (!suppressNavigation)
|
if (!suppressNavigation)
|
||||||
@@ -212,6 +215,9 @@ namespace OpenNest
|
|||||||
if (CurrentIndex >= Count && Count > 0)
|
if (CurrentIndex >= Count && Count > 0)
|
||||||
CurrentIndex = Count - 1;
|
CurrentIndex = Count - 1;
|
||||||
|
|
||||||
|
if (!suppressNavigation && !batching)
|
||||||
|
EnsureSentinel();
|
||||||
|
|
||||||
PlateListChanged?.Invoke(this, EventArgs.Empty);
|
PlateListChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
if (!suppressNavigation)
|
if (!suppressNavigation)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using OpenNest.CNC;
|
using OpenNest.CNC;
|
||||||
|
using OpenNest.Collections;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
namespace OpenNest.Tests;
|
namespace OpenNest.Tests;
|
||||||
@@ -373,7 +374,10 @@ public class PlateManagerTests
|
|||||||
|
|
||||||
mgr.RemoveCurrent();
|
mgr.RemoveCurrent();
|
||||||
|
|
||||||
Assert.Equal(1, nest.Plates.Count);
|
// plate1 + sentinel (created by reactive sentinel enforcement)
|
||||||
|
Assert.Equal(2, nest.Plates.Count);
|
||||||
|
Assert.Same(plate1, nest.Plates[0]);
|
||||||
|
Assert.Equal(0, nest.Plates[^1].Parts.Count);
|
||||||
Assert.Equal(0, mgr.CurrentIndex);
|
Assert.Equal(0, mgr.CurrentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,4 +398,43 @@ public class PlateManagerTests
|
|||||||
mgr.LoadFirst();
|
mgr.LoadFirst();
|
||||||
Assert.True(mgr.CanRemoveCurrent);
|
Assert.True(mgr.CanRemoveCurrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveEmptyPlates_SentinelIsRestored()
|
||||||
|
{
|
||||||
|
var nest = CreateNest();
|
||||||
|
var plate = nest.CreatePlate();
|
||||||
|
plate.Parts.Add(MakePart());
|
||||||
|
using var mgr = new PlateManager(nest);
|
||||||
|
mgr.EnsureSentinel();
|
||||||
|
Assert.Equal(2, nest.Plates.Count);
|
||||||
|
|
||||||
|
// RemoveEmptyPlates removes the sentinel
|
||||||
|
nest.Plates.RemoveEmptyPlates();
|
||||||
|
|
||||||
|
// Sentinel should be restored reactively by OnPlateRemoved
|
||||||
|
Assert.Equal(2, nest.Plates.Count);
|
||||||
|
Assert.Equal(0, nest.Plates[^1].Parts.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PlateRemoval_RefreshesTailSubscriptions()
|
||||||
|
{
|
||||||
|
var nest = CreateNest();
|
||||||
|
var plate1 = nest.CreatePlate();
|
||||||
|
plate1.Parts.Add(MakePart());
|
||||||
|
var plate2 = nest.CreatePlate();
|
||||||
|
plate2.Parts.Add(MakePart());
|
||||||
|
using var mgr = new PlateManager(nest);
|
||||||
|
mgr.EnsureSentinel();
|
||||||
|
// plates: [plate1(parts), plate2(parts), sentinel(empty)]
|
||||||
|
|
||||||
|
// Remove plate2 — tail subscriptions should refresh
|
||||||
|
nest.Plates.Remove(plate2);
|
||||||
|
// plates: [plate1(parts), sentinel(empty)]
|
||||||
|
|
||||||
|
// Verify reactive subscriptions still work on the new tail
|
||||||
|
nest.Plates[^1].Parts.Add(MakePart());
|
||||||
|
Assert.Equal(0, nest.Plates[^1].Parts.Count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user