using AcDream.App.UI; using Xunit; namespace AcDream.App.Tests.UI; public class DragDropSpineTests { // A spy handler used across the spine tests. private sealed class SpyHandler : IItemListDragHandler { public bool AcceptResult = true; public (UiItemList list, UiItemSlot cell, ItemDragPayload payload)? LastOver; public (UiItemList list, UiItemSlot cell, ItemDragPayload payload)? LastDrop; public bool OnDragOver(UiItemList list, UiItemSlot cell, ItemDragPayload p) { LastOver = (list, cell, p); return AcceptResult; } public void HandleDropRelease(UiItemList list, UiItemSlot cell, ItemDragPayload p) { LastDrop = (list, cell, p); } } [Fact] public void Payload_holdsAllFields() { var src = new UiItemSlot(); var p = new ItemDragPayload(0x5001u, ItemDragSource.ShortcutBar, 3, src); Assert.Equal(0x5001u, p.ObjId); Assert.Equal(ItemDragSource.ShortcutBar, p.SourceKind); Assert.Equal(3, p.SourceSlot); Assert.Same(src, p.SourceCell); } [Fact] public void UiItemList_registerDragHandler_roundtrips() { var list = new UiItemList(_ => (0u, 0, 0)); Assert.Null(list.DragHandler); var h = new SpyHandler(); list.RegisterDragHandler(h); Assert.Same(h, list.DragHandler); } // ── UiItemSlot drag-source payload/ghost ──────────────────────────────── [Fact] public void GetDragPayload_emptyCell_isNull() => Assert.Null(new UiItemSlot().GetDragPayload()); [Fact] public void GetDragPayload_boundCell_snapshotsFields() { var cell = new UiItemSlot { SlotIndex = 4, SourceKind = ItemDragSource.ShortcutBar }; cell.SetItem(0x5001u, 0x99u); var p = Assert.IsType(cell.GetDragPayload()); Assert.Equal(0x5001u, p.ObjId); Assert.Equal(ItemDragSource.ShortcutBar, p.SourceKind); Assert.Equal(4, p.SourceSlot); Assert.Same(cell, p.SourceCell); } [Fact] public void GetDragGhost_emptyCell_isNull() => Assert.Null(new UiItemSlot().GetDragGhost()); [Fact] public void GetDragGhost_boundCell_returnsIconTuple() { var cell = new UiItemSlot { Width = 32, Height = 32 }; cell.SetItem(0x5001u, 0x99u); var g = cell.GetDragGhost(); Assert.NotNull(g); Assert.Equal(0x99u, g!.Value.tex); Assert.Equal(32, g.Value.w); Assert.Equal(32, g.Value.h); } // ── cell drop-target: DragEnter overlay + DropReleased dispatch ────────── private static (UiItemList list, UiItemSlot cell, SpyHandler h) ListWithHandler() { var list = new UiItemList(_ => (1u, 1, 1)); // non-zero resolve so overlay draw is harmless var h = new SpyHandler(); list.RegisterDragHandler(h); return (list, list.Cell, h); } private static ItemDragPayload SomePayload() => new(0x5001u, ItemDragSource.ShortcutBar, 0, new UiItemSlot()); [Fact] public void DragEnter_setsAcceptOverlay_whenHandlerAccepts() { var (_, cell, h) = ListWithHandler(); h.AcceptResult = true; cell.OnEvent(new UiEvent(0u, cell, UiEventType.DragEnter, Payload: SomePayload())); Assert.Equal(UiItemSlot.DragAcceptState.Accept, cell.DragAcceptVisual); } [Fact] public void DragEnter_setsRejectOverlay_whenHandlerRejects() { var (_, cell, h) = ListWithHandler(); h.AcceptResult = false; cell.OnEvent(new UiEvent(0u, cell, UiEventType.DragEnter, Payload: SomePayload())); Assert.Equal(UiItemSlot.DragAcceptState.Reject, cell.DragAcceptVisual); } [Fact] public void DragOver_resetsOverlayToNeutral() { var (_, cell, h) = ListWithHandler(); cell.OnEvent(new UiEvent(0u, cell, UiEventType.DragEnter, Payload: SomePayload())); cell.OnEvent(new UiEvent(0u, cell, UiEventType.DragOver, Payload: SomePayload())); Assert.Equal(UiItemSlot.DragAcceptState.None, cell.DragAcceptVisual); } [Fact] public void DropReleased_accepted_dispatchesToHandler() { var (list, cell, h) = ListWithHandler(); var p = SomePayload(); cell.OnEvent(new UiEvent(0u, cell, UiEventType.DropReleased, Data0: 1, Payload: p)); Assert.NotNull(h.LastDrop); Assert.Same(list, h.LastDrop!.Value.list); Assert.Same(cell, h.LastDrop.Value.cell); Assert.Same(p, h.LastDrop.Value.payload); } [Fact] public void DropReleased_notAccepted_skipsDispatch() { var (_, cell, h) = ListWithHandler(); cell.OnEvent(new UiEvent(0u, cell, UiEventType.DropReleased, Data0: 0, Payload: SomePayload())); Assert.Null(h.LastDrop); } }