Files
OpenNest/OpenNest.WinForms.Tests/Controls/CommonLineSelectionTests.cs
T
ajandClaude Opus 5.5 f09a9ad705 feat(common-line): move joined parts as one and add a part right-click menu
Moving one member of a common-line group used to break the group, so a drag
or nudge silently undid a join. Selecting a member, by click or window, now
selects its whole group, so drag, nudge, push, rotate and align carry the
joined parts together and keep their shared spans.

Right-clicking a part now offers Join Common Line and Remove Common Line,
running the Plate > Arrange commands on the selection. A right-click on a part
keeps its selection (or selects it with its group) instead of clearing it, so
the menu has something to act on; a right-click on empty plate still clears.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:52:54 -05:00

153 lines
5.6 KiB
C#

using OpenNest.Controls;
using OpenNest.Geometry;
namespace OpenNest.WinForms.Tests.Controls;
public class CommonLineSelectionTests
{
private const double Kerf = 0.06;
[Fact]
public void SelectingOneMemberSelectsItsWholeGroup() => RunSta(() =>
{
var (plate, left, right, loose) = JoinedPairWithLoosePart();
using var view = new PlateView { Plate = plate };
view.Selection.Select(Layout(view, left));
Assert.Equal(new[] { left, right }, view.SelectedParts.Select(p => p.BasePart));
Assert.All(view.SelectedParts, p => Assert.True(p.IsSelected));
Assert.False(Layout(view, loose).IsSelected);
});
[Fact]
public void SelectingALoosePartSelectsOnlyThatPart() => RunSta(() =>
{
var (plate, _, _, loose) = JoinedPairWithLoosePart();
using var view = new PlateView { Plate = plate };
view.Selection.Select(Layout(view, loose));
Assert.Equal(new[] { loose }, view.SelectedParts.Select(p => p.BasePart));
});
[Fact]
public void NudgingOneMemberMovesTheJoinedPairAndKeepsItsSharedLine() => RunSta(() =>
{
var (plate, left, right, _) = JoinedPairWithLoosePart();
using var view = new PlateView { Plate = plate };
var leftStart = left.BoundingBox.Location;
var rightStart = right.BoundingBox.Location;
view.Selection.Select(Layout(view, right));
view.Selection.NudgeSelected(1, 2);
Assert.Equal(leftStart + new Vector(1, 2), left.BoundingBox.Location);
Assert.Equal(rightStart + new Vector(1, 2), right.BoundingBox.Location);
var group = Assert.Single(plate.CommonLines);
Assert.True(CommonLineGeometry.Check(group).IsIntact);
});
[Fact]
public void PartMenuOffersRemoveForAGroupAndJoinWhenALoosePartIsAdded() => RunSta(() =>
{
var (plate, left, _, loose) = JoinedPairWithLoosePart();
using var view = new PlateView { Plate = plate };
var joins = 0;
var removes = 0;
view.JoinCommonLineCommand = () => joins++;
view.RemoveCommonLineCommand = () => removes++;
var onLeft = view.PointWorldToControl(left.BoundingBox.Center);
// Off the selection there is no menu.
Assert.Null(view.PartMenu.BuildMenu(onLeft));
view.Selection.Select(Layout(view, left));
var items = MenuItems(view.PartMenu.BuildMenu(onLeft));
Assert.Equal(new[] { "Join Common Line", "Remove Common Line" }, items.Select(i => i.Text));
Assert.False(items[0].Enabled);
Assert.True(items[1].Enabled);
view.Selection.Select(Layout(view, loose));
items = MenuItems(view.PartMenu.BuildMenu(onLeft));
Assert.True(items[0].Enabled);
items[0].PerformClick();
items[1].PerformClick();
Assert.Equal(1, joins);
Assert.Equal(1, removes);
});
[Fact]
public void PartMenuOffersJoinButNotRemoveForLooseParts() => RunSta(() =>
{
var (plate, _, _, loose) = JoinedPairWithLoosePart();
var other = new Part(loose.BaseDrawing, new Vector(30, 10));
plate.Parts.Add(other);
using var view = new PlateView { Plate = plate };
view.JoinCommonLineCommand = () => { };
view.RemoveCommonLineCommand = () => { };
view.Selection.Select(Layout(view, loose));
var onLoose = view.PointWorldToControl(loose.BoundingBox.Center);
var items = MenuItems(view.PartMenu.BuildMenu(onLoose));
Assert.False(items[0].Enabled);
Assert.False(items[1].Enabled);
view.Selection.Select(Layout(view, other));
items = MenuItems(view.PartMenu.BuildMenu(onLoose));
Assert.True(items[0].Enabled);
Assert.False(items[1].Enabled);
view.InspectionOnly = true;
Assert.Null(view.PartMenu.BuildMenu(onLoose));
});
[Fact]
public void PartMenuIsAbsentWithoutHostCommands() => RunSta(() =>
{
var (plate, left, _, _) = JoinedPairWithLoosePart();
using var view = new PlateView { Plate = plate };
view.Selection.Select(Layout(view, left));
Assert.Null(view.PartMenu.BuildMenu(view.PointWorldToControl(left.BoundingBox.Center)));
});
private static List<System.Windows.Forms.ToolStripItem> MenuItems(System.Windows.Forms.ContextMenuStrip? menu)
{
Assert.NotNull(menu);
return menu.Items.Cast<System.Windows.Forms.ToolStripItem>().ToList();
}
private static (Plate Plate, Part Left, Part Right, Part Loose) JoinedPairWithLoosePart()
{
var plate = new Plate(40, 20);
var drawing = new Drawing("square", Square(4));
var left = new Part(drawing, new Vector(2, 2));
var right = new Part(drawing, new Vector(6 + Kerf, 2));
var loose = new Part(drawing, new Vector(20, 10));
plate.Parts.Add(left);
plate.Parts.Add(right);
plate.Parts.Add(loose);
plate.JoinCommonLine(new[] { left, right }, Kerf);
Assert.True(CommonLineGeometry.Check(plate.CommonLines[0]).IsIntact);
return (plate, left, right, loose);
}
private static CNC.Program Square(double size)
{
var program = new CNC.Program();
program.MoveTo(0, 0);
program.LineTo(size, 0);
program.LineTo(size, size);
program.LineTo(0, size);
program.LineTo(0, 0);
return program;
}
private static LayoutPart Layout(PlateView view, Part part) =>
view.LayoutParts.Single(p => ReferenceEquals(p.BasePart, part));
private static void RunSta(System.Action action) =>
StaTestThread.Run(action, TimeSpan.FromSeconds(30), "Common-line selection test timed out.");
}