acdream/tests/AcDream.App.Tests/UI/UiItemSlotTests.cs
Erik 5090aa7217 fix #212: preserve shortcut tint in physical combat
Port the retail m_bShortcutGhosted meaning instead of treating SetShortcutNum's boolean as peace versus war. Keep occupied shortcut numbers on the gold sheet in NonCombat, Melee, and Missile, reserve the gray sheet for Magic, and lock the four-mode matrix with conformance tests and corrected research.

Co-Authored-By: Codex <codex@openai.com>
2026-07-13 11:21:12 +02:00

186 lines
6.2 KiB
C#

using AcDream.App.UI;
namespace AcDream.App.Tests.UI;
public class UiItemSlotTests
{
[Fact]
public void IsLeafWidget()
=> Assert.True(new UiItemSlot().ConsumesDatChildren);
[Fact]
public void DefaultEmptySprite_isToolbarBorder()
=> Assert.Equal(0x060074CFu, new UiItemSlot().EmptySprite);
[Fact]
public void DefaultWaitingSprite_isRetailGhostMesh()
=> Assert.Equal(0x0600109Au, new UiItemSlot().WaitingSprite);
[Fact]
public void Empty_whenNoItem()
{
var s = new UiItemSlot();
Assert.Equal(0u, s.ItemId);
Assert.Equal(0u, s.IconTexture);
}
[Fact]
public void SetItem_setsIdAndTexture()
{
var s = new UiItemSlot();
s.SetItem(0x5001u, 0x99u);
Assert.Equal(0x5001u, s.ItemId);
Assert.Equal(0x99u, s.IconTexture);
}
[Fact]
public void Clear_afterSetItem_resetsToEmpty()
{
var s = new UiItemSlot();
s.SetItem(0x5001u, 0x99u);
s.Clear();
Assert.Equal(0u, s.ItemId);
Assert.Equal(0u, s.IconTexture);
}
[Fact]
public void DoubleClick_invokesDoubleClicked()
{
var s = new UiItemSlot();
bool fired = false;
s.DoubleClicked = () => fired = true;
s.OnEvent(new UiEvent(0u, s, UiEventType.DoubleClick));
Assert.True(fired);
}
// ── Shortcut number tests ────────────────────────────────────────────────
// Port of UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465).
[Fact]
public void ShortcutNum_defaultIsMinusOne()
{
var s = new UiItemSlot();
Assert.Equal(-1, s.ShortcutNum);
}
[Fact]
public void ShortcutGhosted_defaultsFalse()
{
var s = new UiItemSlot();
Assert.False(s.ShortcutGhosted);
}
[Fact]
public void SetShortcutNum_setsIndexAndGhostedState()
{
var s = new UiItemSlot();
s.SetShortcutNum(3, ghosted: true);
Assert.Equal(3, s.ShortcutNum);
Assert.True(s.ShortcutGhosted);
}
[Fact]
public void SetShortcutNum_regularState()
{
var s = new UiItemSlot();
s.SetShortcutNum(0, ghosted: false);
Assert.Equal(0, s.ShortcutNum);
Assert.False(s.ShortcutGhosted);
}
[Fact]
public void ClearShortcutNum_setsMinusOne()
{
var s = new UiItemSlot();
s.SetShortcutNum(5, ghosted: false);
s.ClearShortcutNum();
Assert.Equal(-1, s.ShortcutNum);
}
// ── ActiveDigitArray occupancy gating (decomp UIElement_UIItem::SetShortcutNum:229481) ──
private static readonly uint[] Regular = { 0x10u, 0x11u, 0x12u };
private static readonly uint[] Ghosted = { 0x20u, 0x21u, 0x22u };
private static readonly uint[] Empty = { 0x30u, 0x31u, 0x32u };
/// <summary>
/// When ItemId == 0 (empty slot), ActiveDigitArray returns EmptyDigits regardless
/// of ShortcutGhosted. Retail ref: UIElement_UIItem::SetShortcutNum (decomp 229481) —
/// else branch when m_elem_Icon->m_state == 0x1000001c (empty).
/// </summary>
[Fact]
public void ActiveDigitArray_emptySlot_returnsEmptyDigits()
{
var s = new UiItemSlot { RegularDigits = Regular, GhostedDigits = Ghosted, EmptyDigits = Empty };
s.SetShortcutNum(0, ghosted: false);
// ItemId == 0 → EmptyDigits
Assert.Same(Empty, s.ActiveDigitArray());
}
[Fact]
public void ActiveDigitArray_emptySlot_ghosted_stillReturnsEmptyDigits()
{
var s = new UiItemSlot { RegularDigits = Regular, GhostedDigits = Ghosted, EmptyDigits = Empty };
s.SetShortcutNum(0, ghosted: true);
// ItemId == 0 → EmptyDigits regardless of stance
Assert.Same(Empty, s.ActiveDigitArray());
}
/// <summary>
/// When ItemId != 0 (occupied), ActiveDigitArray returns RegularDigits or GhostedDigits
/// depending on ShortcutGhosted. Retail ref: UIElement_UIItem::SetShortcutNum (decomp 229481/229493).
/// </summary>
[Fact]
public void ActiveDigitArray_occupiedSlot_regular_returnsRegularDigits()
{
var s = new UiItemSlot { RegularDigits = Regular, GhostedDigits = Ghosted, EmptyDigits = Empty };
s.SetItem(0x5001u, 0x99u);
s.SetShortcutNum(0, ghosted: false);
Assert.Same(Regular, s.ActiveDigitArray());
}
[Fact]
public void ActiveDigitArray_occupiedSlot_ghosted_returnsGhostedDigits()
{
var s = new UiItemSlot { RegularDigits = Regular, GhostedDigits = Ghosted, EmptyDigits = Empty };
s.SetItem(0x5001u, 0x99u);
s.SetShortcutNum(0, ghosted: true);
Assert.Same(Ghosted, s.ActiveDigitArray());
}
[Fact]
public void ActiveDigitArray_emptySlot_nullEmptyDigits_returnsNull()
{
var s = new UiItemSlot { RegularDigits = Regular, GhostedDigits = Ghosted, EmptyDigits = null };
s.SetShortcutNum(0, ghosted: false);
Assert.Null(s.ActiveDigitArray());
}
// ── Container-switching indicator overlays (decomp SetOpenContainerState 0x004e1200 /
// SetSelectedState 0x004e1240) ───────────────────────────────────────────────────────
[Fact]
public void Selected_defaultsFalse() => Assert.False(new UiItemSlot().Selected);
[Fact]
public void IsOpenContainer_defaultsFalse() => Assert.False(new UiItemSlot().IsOpenContainer);
[Fact]
public void SelectedSprite_default_isGreenYellowSquare()
=> Assert.Equal(0x06004D21u, new UiItemSlot().SelectedSprite);
[Fact]
public void OpenContainerSprite_default_isTriangle()
=> Assert.Equal(0x06005D9Cu, new UiItemSlot().OpenContainerSprite);
// ── Container capacity bar (decomp UpdateCapacityDisplay 0x004e16e0, element 0x10000347) ──
[Fact]
public void CapacityFill_defaultsHidden() => Assert.Equal(-1f, new UiItemSlot().CapacityFill);
[Fact]
public void CapacityBackSprite_default() => Assert.Equal(0x06004D22u, new UiItemSlot().CapacityBackSprite);
[Fact]
public void CapacityFrontSprite_default() => Assert.Equal(0x06004D23u, new UiItemSlot().CapacityFrontSprite);
}