acdream/tests/AcDream.App.Tests/UI/RetailUiAutomationProbeTests.cs
Erik 124e046976 fix(runtime): align portal and movement presentation
Port retail portal viewport projection and reveal behavior, preserve outbound combat style, drive remote and local grounded movement from authored CSequence root frames, and reuse the local prepared pose so animation hooks advance once.

User-verified portal, observer movement, combat stance, and short-tap locomotion gates. Release build passed with 5,767 tests and five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 08:48:27 +02:00

207 lines
6.8 KiB
C#

using System.Collections.Generic;
using System.IO;
using AcDream.App.UI;
using AcDream.App.UI.Testing;
using AcDream.Core.Items;
namespace AcDream.App.Tests.UI;
public sealed class RetailUiAutomationProbeTests
{
private sealed class SpyHandler : IItemListDragHandler
{
public (UiItemList list, UiItemSlot cell, ItemDragPayload payload)? LastDrop;
public (UiItemList list, UiItemSlot cell, ItemDragPayload payload)? LastLift;
public void OnDragLift(UiItemList sourceList, UiItemSlot sourceCell, ItemDragPayload payload)
=> LastLift = (sourceList, sourceCell, payload);
public ItemDragAcceptance OnDragOver(UiItemList targetList, UiItemSlot targetCell, ItemDragPayload payload)
=> ItemDragAcceptance.Accept;
public void HandleDropRelease(UiItemList targetList, UiItemSlot targetCell, ItemDragPayload payload)
=> LastDrop = (targetList, targetCell, payload);
}
private static (UiRoot root, UiItemList source, UiItemList target, SpyHandler handler, ClientObjectTable objects)
RootWithTwoItemLists()
{
var root = new UiRoot { Width = 240, Height = 120 };
var objects = new ClientObjectTable();
var source = new UiItemList(_ => (1u, 1, 1))
{
DatElementId = 0x10000010u,
Left = 10,
Top = 10,
Width = 32,
Height = 32,
};
source.Cell.SlotIndex = 0;
source.Cell.SourceKind = ItemDragSource.Inventory;
source.Cell.SetItem(0x5001u, 0x99u);
var target = new UiItemList(_ => (1u, 1, 1))
{
DatElementId = 0x10000020u,
Left = 70,
Top = 10,
Width = 32,
Height = 32,
};
target.Cell.SlotIndex = 1;
var handler = new SpyHandler();
target.RegisterDragHandler(handler);
root.AddChild(source);
root.AddChild(target);
return (root, source, target, handler, objects);
}
[Fact]
public void Snapshot_listsDatElementsAndItemSlots()
{
var (root, _, _, _, objects) = RootWithTwoItemLists();
var probe = new RetailUiAutomationProbe(root, objects);
var rows = probe.Snapshot();
Assert.Contains(rows, r => r.DatElementId == 0x10000010u && r.TypeName == nameof(UiItemList));
var itemRow = Assert.Single(rows, r => r.ItemId == 0x5001u);
Assert.Equal(ItemDragSource.Inventory, itemRow.SourceKind);
Assert.Equal(0, itemRow.SlotIndex);
Assert.True(itemRow.Width > 0f);
Assert.True(itemRow.Height > 0f);
}
[Fact]
public void DoubleClickItem_routesThroughUiRootDoubleClick()
{
var (root, source, _, _, objects) = RootWithTwoItemLists();
bool doubleClicked = false;
source.Cell.DoubleClicked = () => doubleClicked = true;
var probe = new RetailUiAutomationProbe(root, objects);
Assert.True(probe.DoubleClickItem(0x5001u, ItemDragSource.Inventory));
Assert.True(doubleClicked);
}
[Fact]
public void DragItemToElement_deliversDropReleaseToTargetCell()
{
var (root, _, target, handler, objects) = RootWithTwoItemLists();
var probe = new RetailUiAutomationProbe(root, objects);
Assert.True(probe.DragItemToElement(0x5001u, 0x10000020u, ItemDragSource.Inventory));
Assert.NotNull(handler.LastDrop);
Assert.Same(target, handler.LastDrop!.Value.list);
Assert.Same(target.Cell, handler.LastDrop.Value.cell);
Assert.Equal(0x5001u, handler.LastDrop.Value.payload.ObjId);
}
[Fact]
public void DragItemOutside_raisesRootOutsideUiEvent()
{
var (root, _, _, _, objects) = RootWithTwoItemLists();
object? payload = null;
(int x, int y) release = default;
root.DragReleasedOutsideUi += (p, x, y) =>
{
payload = p;
release = (x, y);
};
var probe = new RetailUiAutomationProbe(root, objects);
Assert.True(probe.DragItemOutside(0x5001u, 220, 100, ItemDragSource.Inventory));
var itemPayload = Assert.IsType<ItemDragPayload>(payload);
Assert.Equal(0x5001u, itemPayload.ObjId);
Assert.Equal((220, 100), release);
}
[Fact]
public void AssertItem_checksObjectTableState()
{
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject
{
ObjectId = 0x5001u,
ContainerId = 0x7001u,
ContainerSlot = 3,
CurrentlyEquippedLocation = EquipMask.ChestArmor | EquipMask.UpperArmArmor,
});
var probe = new RetailUiAutomationProbe(new UiRoot(), objects);
var ok = probe.AssertItem(
0x5001u,
equippedLocation: EquipMask.ChestArmor | EquipMask.UpperArmArmor,
containerId: 0x7001u,
slot: 3);
var bad = probe.AssertItem(0x5001u, slot: 4);
Assert.True(ok.Success);
Assert.False(bad.Success);
}
[Fact]
public void ScriptRunner_waitItemThenDoubleClick_executesThroughProbe()
{
var (root, source, _, _, objects) = RootWithTwoItemLists();
bool doubleClicked = false;
source.Cell.DoubleClicked = () => doubleClicked = true;
var probe = new RetailUiAutomationProbe(root, objects);
var logs = new List<string>();
string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".ui-probe.txt");
File.WriteAllLines(path, new[]
{
"wait item 0x5001 inventory 100",
"doubleclick item 0x5001 inventory",
});
try
{
var runner = new RetailUiAutomationScriptRunner(probe, path, dumpOnStart: false, logs.Add);
runner.Tick(0.016);
Assert.True(doubleClicked);
Assert.True(runner.Completed);
Assert.Contains(logs, line => line.Contains("UI probe script complete"));
}
finally
{
File.Delete(path);
}
}
[Fact]
public void ScriptRunner_command_routesThroughInjectedClientSubmitPath()
{
var (root, _, _, _, objects) = RootWithTwoItemLists();
var probe = new RetailUiAutomationProbe(root, objects);
var submitted = new List<string>();
string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".ui-probe.txt");
File.WriteAllText(path, "command /ls");
try
{
var runner = new RetailUiAutomationScriptRunner(
probe,
path,
dumpOnStart: false,
submitCommand: submitted.Add);
runner.Tick(0.016);
Assert.Equal(["/ls"], submitted);
Assert.True(runner.Completed);
}
finally
{
File.Delete(path);
}
}
}