test(D.2b): close drag-drop spec-review coverage gaps

Add the 3 tests the spec-compliance review flagged: 0x0022 ConfirmMove clears
the pending move (no rollback after a confirm); OnDragOver advisory-accepts a
closed bag (cap>0, contents not indexed = AP-61); App-level drop -> RollbackMove
reverts the optimistic move to the original container+slot. Production code was
already correct; these exercise the spec's test plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-22 20:03:24 +02:00
parent 468da225d3
commit 8df395894e
2 changed files with 52 additions and 0 deletions

View file

@ -484,6 +484,36 @@ public class InventoryControllerTests
Assert.Equal(Player, objects.Get(0xAu)!.ContainerId); // NOT removed on lift (unlike the toolbar)
}
[Fact]
public void OnDragOver_closedBag_unknownCount_advisoryAccepts() // AP-61
{
var (layout, _, containers, _, _, _, _, _) = BuildLayout();
var objects = new ClientObjectTable();
SeedBag(objects, 0xC, slot: 0, itemsCapacity: 24); // a bag with capacity but NO indexed contents (closed)
objects.AddOrUpdate(new ClientObject { ObjectId = 0xFFFFu });
var ctrl = (IItemListDragHandler)Bind(layout, objects);
// GetContents(0xC) is empty (a closed bag's contents aren't indexed until opened) → not known-full → accept.
Assert.True(ctrl.OnDragOver(containers, containers.GetItem(0)!, Payload(0xFFFFu)));
}
[Fact]
public void Drop_thenServerRollback_revertsTheMove() // optimistic + InventoryServerSaveFailed snap-back
{
var (layout, _, containers, _, _, _, _, _) = BuildLayout();
var objects = new ClientObjectTable();
SeedContained(objects, 0xA, Player, slot: 3); // item starts in the main pack at slot 3
SeedBag(objects, 0xC, slot: 0, itemsCapacity: 24);
var ctrl = Bind(layout, objects);
((IItemListDragHandler)ctrl).HandleDropRelease(containers, containers.GetItem(0)!, Payload(0xAu));
Assert.Equal(0xCu, objects.Get(0xAu)!.ContainerId); // moved into the bag optimistically (instant)
objects.RollbackMove(0xAu); // server rejected (InventoryServerSaveFailed)
Assert.Equal(Player, objects.Get(0xAu)!.ContainerId); // snapped back to the main pack
Assert.Equal(3, objects.Get(0xAu)!.ContainerSlot); // and the original slot
}
// Reads the text of the UiText caption child attached by the controller.
private static string CaptionText(UiElement host)
{

View file

@ -606,6 +606,28 @@ public sealed class GameEventWiringTests
Assert.Equal(2, items.Get(0x50000B01u)!.ContainerSlot);
}
[Fact]
public void WireAll_InventoryPutObjInContainer_ConfirmsOptimisticMove_soNoRollback()
{
var (d, items, _, _, _) = MakeAll();
items.RecordMembership(0x50000B02u, containerId: 0x500000C0u);
items.MoveItem(0x50000B02u, 0x500000C0u, 2);
items.MoveItemOptimistic(0x50000B02u, 0x500000C1u, 0); // optimistic move (pending snapshot)
// Server CONFIRMS via InventoryPutObjInContainer (item, container, placement, containerType).
byte[] payload = new byte[16];
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(0), 0x50000B02u);
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(4), 0x500000C1u);
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(8), 0u); // placement
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(12), 0u); // containerType
var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.InventoryPutObjInContainer, payload));
d.Dispatch(env!.Value);
// ConfirmMove cleared the pending snapshot → a later rollback is a no-op (the move stuck).
Assert.False(items.RollbackMove(0x50000B02u));
Assert.Equal(0x500000C1u, items.Get(0x50000B02u)!.ContainerId);
}
[Fact]
public void WireAll_InventoryPutObjectIn3D_UnparentsFromContainer()
{