Files
OpenNest/OpenNest.Desktop.Tests/Interaction/SelectToolTests.cs
T
ajandClaude Opus 5.5 313b5f57d6 feat(desktop): add a toolkit-neutral plate tool and selection model
PlateToolManager keeps one active tool with the WinForms action rules
(Escape cancels, then returns to Select, then resumes the ended tool) and
status text. SelectTool selects by click, Ctrl+click and window
(contains/intersect), takes common-line groups together and picks
cut-offs by their strokes. PlateHitTester hit-tests the shared display
geometry; PlateSelection reads and sets by part index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 19:30:47 -05:00

144 lines
3.8 KiB
C#

using OpenNest.Desktop.Interaction;
namespace OpenNest.Desktop.Tests.Interaction;
public class SelectToolTests
{
private readonly Plate plate = InteractionFixtures.Plate();
private readonly TestToolHost host;
public SelectToolTests()
{
host = new TestToolHost(plate);
}
private PlateSelection Selection => host.Selection;
private SelectTool Tool => Assert.IsType<SelectTool>(host.Tools.Current);
[Fact]
public void A_click_on_a_part_selects_it_alone()
{
host.Click(25, 5);
host.Click(1, 1);
Assert.Equal(new[] { plate.Parts[0] }, Selection.Parts);
}
[Fact]
public void Ctrl_click_adds_to_the_selection()
{
host.Click(25, 5);
host.Click(1, 1, ToolModifiers.Control);
Assert.Equal(new[] { plate.Parts[1], plate.Parts[0] }, Selection.Parts);
}
[Fact]
public void Ctrl_shift_click_replaces_the_selection_as_in_WinForms()
{
host.Click(25, 5);
host.Click(1, 1, ToolModifiers.Control | ToolModifiers.Shift);
Assert.Equal(new[] { plate.Parts[0] }, Selection.Parts);
}
[Fact]
public void A_click_on_a_selected_part_keeps_the_selection()
{
host.Click(25, 5);
host.Click(1, 1, ToolModifiers.Control);
host.Click(25, 5);
Assert.Equal(2, Selection.Parts.Count);
}
[Fact]
public void A_click_on_a_common_line_member_selects_its_group()
{
host.Click(45, 5);
Assert.Equal(new[] { plate.Parts[2], plate.Parts[3] }, Selection.Parts);
}
[Fact]
public void A_click_in_a_hole_or_on_empty_sheet_clears_the_selection()
{
host.Click(25, 5);
host.Click(5, 5);
Assert.True(Selection.IsEmpty);
Assert.Null(Tool.Window);
}
[Fact]
public void A_click_near_a_cut_off_selects_it_alone()
{
host.Click(25, 5);
host.Click(80.3, 50);
Assert.Empty(Selection.Parts);
Assert.Equal(new[] { plate.CutOffs[0] }, Selection.CutOffs);
host.Click(25, 5);
Assert.Empty(Selection.CutOffs);
}
[Fact]
public void Dragging_right_selects_what_the_window_contains()
{
host.Drag(-1, -1, 31, 11);
Assert.Equal(new[] { plate.Parts[0], plate.Parts[1] }, Selection.Parts);
Assert.Null(Tool.Window);
}
[Fact]
public void Dragging_left_selects_what_the_window_touches_with_common_line_groups_and_cut_offs()
{
host.Drag(85, 5, 45, 8);
Assert.Equal(new[] { plate.Parts[2], plate.Parts[3] }, Selection.Parts);
Assert.Equal(new[] { plate.CutOffs[0] }, Selection.CutOffs);
}
[Fact]
public void The_window_follows_the_pointer_while_dragging()
{
host.Tools.Current.PointerPressed(host.At(60, 60));
host.Tools.Current.PointerMoved(host.At(70, 65));
var window = Assert.IsType<SelectionWindow>(Tool.Window);
Assert.Equal(WindowSelectionMode.Contains, window.Mode);
Assert.Equal(70, window.Right);
Assert.True(Tool.IsBusy);
}
[Fact]
public void A_right_click_on_an_unselected_part_selects_it_and_elsewhere_clears()
{
host.Click(25, 5);
host.Click(1, 1, ToolModifiers.Control);
host.Tools.Current.PointerPressed(host.At(25, 5, ToolButton.Right));
Assert.Equal(2, Selection.Parts.Count);
host.Tools.Current.PointerPressed(host.At(25, 25, ToolButton.Right));
Assert.Equal(new[] { plate.Parts[4] }, Selection.Parts);
host.Tools.Current.PointerPressed(host.At(70, 70, ToolButton.Right));
Assert.True(Selection.IsEmpty);
}
[Fact]
public void A_click_skips_parts_already_removed_from_the_selection_plate()
{
host.Click(25, 5);
plate.Parts.RemoveAt(1);
host.Click(1, 1, ToolModifiers.Control);
Assert.Equal(new[] { plate.Parts[0] }, Selection.Parts);
}
}