using EnvelopeRenderer.Desktop.Core.Design; namespace EnvelopeRenderer.Desktop.Tests; public class CanvasElementEditorTests { // Fixed 20x10-point bounding box for every element, regardless of content — keeps hit-test // math deterministic without depending on real GDI text measurement. private static (double Width, double Height) FixedSize(TextElementLayout _) => (20.0, 10.0); private static CanvasElementEditor CreateEditor(out TemplateLayoutDocument document) { document = TemplateLayoutDocument.CreateDefault(); return new CanvasElementEditor(document, FixedSize); } [Fact] public void AddStaticText_AddsToDocumentAndSelectsIt() { var editor = CreateEditor(out var document); var element = editor.AddStaticText(10, 20, "Hello"); Assert.Single(document.Elements); Assert.Same(element, document.Elements[0]); Assert.Same(element, editor.Selected); Assert.Equal("Hello", element.StaticText); Assert.False(element.IsDynamic); } [Fact] public void AddDynamicPlaceholder_AddsToDocumentAndSelectsIt() { var editor = CreateEditor(out var document); var element = editor.AddDynamicPlaceholder(10, 20, "Full Name"); Assert.Single(document.Elements); Assert.True(element.IsDynamic); Assert.Equal("Full Name", element.ColumnName); Assert.Same(element, editor.Selected); } [Fact] public void AddMultipleElements_AssignsIncreasingZOrder() { var editor = CreateEditor(out _); var first = editor.AddStaticText(0, 0, "A"); var second = editor.AddDynamicPlaceholder(0, 0, "B"); var third = editor.AddStaticText(0, 0, "C"); Assert.Equal(0, first.ZOrder); Assert.Equal(1, second.ZOrder); Assert.Equal(2, third.ZOrder); } [Fact] public void HitTest_PointInsideElementBounds_ReturnsElement() { var editor = CreateEditor(out _); var element = editor.AddStaticText(100, 100, "Hello"); var hit = editor.HitTest(105, 105); // inside [100,120] x [100,110] Assert.Same(element, hit); } [Fact] public void HitTest_PointOutsideElementBounds_ReturnsNull() { var editor = CreateEditor(out _); editor.AddStaticText(100, 100, "Hello"); var hit = editor.HitTest(0, 0); Assert.Null(hit); } [Fact] public void HitTest_OverlappingElements_ReturnsTopmostZOrder() { var editor = CreateEditor(out _); var bottom = editor.AddStaticText(100, 100, "Bottom"); var top = editor.AddStaticText(100, 100, "Top"); // same bounds, added later -> higher z-order var hit = editor.HitTest(105, 105); Assert.Same(top, hit); Assert.NotSame(bottom, hit); } [Fact] public void TrySelectAt_Hit_SelectsAndReturnsTrue() { var editor = CreateEditor(out _); var element = editor.AddStaticText(100, 100, "Hello"); editor.Select(null); var result = editor.TrySelectAt(105, 105); Assert.True(result); Assert.Same(element, editor.Selected); } [Fact] public void TrySelectAt_Miss_ClearsSelectionAndReturnsFalse() { var editor = CreateEditor(out _); editor.AddStaticText(100, 100, "Hello"); var result = editor.TrySelectAt(0, 0); Assert.False(result); Assert.Null(editor.Selected); } [Fact] public void DragTo_MovesSelectedElementPreservingGrabOffset() { var editor = CreateEditor(out _); var element = editor.AddStaticText(100, 100, "Hello"); editor.TrySelectAt(105, 105); // grab 5pt into the element from its origin editor.BeginDrag(105, 105); editor.DragTo(205, 155); // moved 100 right, 50 up Assert.Equal(200.0, element.X, precision: 6); Assert.Equal(150.0, element.Y, precision: 6); Assert.True(editor.IsDragging); } [Fact] public void EndDrag_StopsFurtherMovementFromDragTo() { var editor = CreateEditor(out _); var element = editor.AddStaticText(100, 100, "Hello"); editor.TrySelectAt(105, 105); editor.BeginDrag(105, 105); editor.EndDrag(); editor.DragTo(500, 500); Assert.Equal(100.0, element.X, precision: 6); Assert.Equal(100.0, element.Y, precision: 6); Assert.False(editor.IsDragging); } [Fact] public void DragTo_WithoutSelection_IsNoOp() { var editor = CreateEditor(out _); editor.DragTo(50, 50); // nothing selected, nothing dragging — should not throw Assert.Null(editor.Selected); } [Fact] public void Move_UpdatesTemplateLayoutDocumentInPlace_SoCanvasAndStoredStateAgree() { var editor = CreateEditor(out var document); var element = editor.AddStaticText(0, 0, "Hello"); editor.TrySelectAt(10, 5); editor.BeginDrag(10, 5); editor.DragTo(60, 45); // The document's element list holds the same instance that was moved — no separate // "committed" copy step, so the canvas (reading from `document.Elements`) and this // editor's own state can never disagree. Assert.Same(element, document.Elements[0]); Assert.Equal(50.0, document.Elements[0].X, precision: 6); Assert.Equal(40.0, document.Elements[0].Y, precision: 6); } }