using System; using System.Collections.Generic; using AcDream.App.UI; using AcDream.App.UI.Layout; using AcDream.Core.Combat; using AcDream.Core.Items; using AcDream.Core.Net.Messages; using Xunit; namespace AcDream.App.Tests.UI.Layout; public class ToolbarControllerTests { private static readonly uint[] Row1 = { 0x100001A7,0x100001A8,0x100001A9,0x100001AA,0x100001AB,0x100001AC,0x100001AD,0x100001AE,0x100001AF }; private static readonly uint[] Row2 = { 0x100006B7,0x100006B8,0x100006B9,0x100006BA,0x100006BB,0x100006BC,0x100006BD,0x100006BE,0x100006BF }; // The four mutually-exclusive combat-mode indicator element ids (must match ToolbarController's list). private static readonly uint[] CombatIds = { 0x10000192u, 0x10000193u, 0x10000194u, 0x10000195u }; private static (ImportedLayout layout, Dictionary slots, Dictionary indicators) FakeToolbar() { var dict = new Dictionary(); var slots = new Dictionary(); var indicators = new Dictionary(); var root = new UiPanel(); foreach (var id in Row1) AddSlot(id); foreach (var id in Row2) AddSlot(id); // Add combat indicator elements as plain UiPanels keyed by id. foreach (var id in CombatIds) { var e = new UiPanel { Visible = true }; dict[id] = e; indicators[id] = e; root.AddChild(e); } return (new ImportedLayout(root, dict), slots, indicators); void AddSlot(uint id) { var list = new UiItemList(_ => (0u, 0, 0)) { Width = 32, Height = 32 }; dict[id] = list; slots[id] = list; root.AddChild(list); } } [Fact] public void Populate_bindsShortcutToCorrectSlot() { var (layout, slots, _) = FakeToolbar(); var repo = new ItemRepository(); repo.AddOrUpdate(new ItemInstance { ObjectId = 0x5001u, WeenieClassId = 1u, IconId = 0x06001234u }); var shortcuts = new List { new(Index: 0, ObjectGuid: 0x5001u, SpellId: 0, Layer: 0) }; ToolbarController.Bind(layout, repo, () => shortcuts, iconIds: (_,_,_,_) => 0x77u, useItem: _ => { }); Assert.Equal(0x5001u, slots[Row1[0]].Cell.ItemId); Assert.Equal(0x77u, slots[Row1[0]].Cell.IconTexture); Assert.Equal(0u, slots[Row1[1]].Cell.ItemId); // others empty } [Fact] public void DeferredRebind_whenItemArrivesLate() { var (layout, slots, _) = FakeToolbar(); var repo = new ItemRepository(); // item NOT present yet var shortcuts = new List { new(Index: 2, ObjectGuid: 0x5002u, SpellId: 0, Layer: 0) }; ToolbarController.Bind(layout, repo, () => shortcuts, iconIds: (_,_,_,_) => 0x88u, useItem: _ => { }); Assert.Equal(0u, slots[Row1[2]].Cell.ItemId); // not bound yet repo.AddOrUpdate(new ItemInstance { ObjectId = 0x5002u, WeenieClassId = 1u, IconId = 0x06005678u }); Assert.Equal(0x5002u, slots[Row1[2]].Cell.ItemId); // rebound on ItemAdded } [Fact] public void Click_emitsUseForBoundItem() { var (layout, slots, _) = FakeToolbar(); var repo = new ItemRepository(); repo.AddOrUpdate(new ItemInstance { ObjectId = 0x5001u, WeenieClassId = 1u, IconId = 0x06001234u }); var shortcuts = new List { new(Index: 0, ObjectGuid: 0x5001u, SpellId: 0, Layer: 0) }; uint used = 0; ToolbarController.Bind(layout, repo, () => shortcuts, iconIds: (_,_,_,_) => 0x77u, useItem: g => used = g); // UiEvent is a positional record struct: (SourceId, Target, Type, Data0..3, Payload) slots[Row1[0]].Cell.OnEvent(new UiEvent(0u, null, UiEventType.MouseDown)); Assert.Equal(0x5001u, used); } // ── C1: combat-mode indicator tests ───────────────────────────────────── /// /// At bind time (default NonCombat), only the peace indicator (0x10000192) is visible; /// the melee/missile/magic indicators (0x10000193/4/5) are hidden. /// Port of gmToolbarUI::RecvNotice_SetCombatMode (acclient_2013_pseudo_c.txt:196632-196669). /// [Fact] public void CombatIndicator_defaultNonCombat_onlyPeaceVisible() { var (layout, _, indicators) = FakeToolbar(); var repo = new ItemRepository(); ToolbarController.Bind(layout, repo, () => Array.Empty(), iconIds: (_,_,_,_) =>0u, useItem: _ => { }); // Only peace indicator (index 0 = 0x10000192) is visible. Assert.True (indicators[0x10000192u].Visible, "peace indicator should be visible after bind"); Assert.False(indicators[0x10000193u].Visible, "melee indicator should be hidden after bind"); Assert.False(indicators[0x10000194u].Visible, "missile indicator should be hidden after bind"); Assert.False(indicators[0x10000195u].Visible, "magic indicator should be hidden after bind"); } /// /// SetCombatMode(Melee) hides peace/missile/magic and shows only the melee indicator. /// [Fact] public void CombatIndicator_setCombatModeMelee_onlyMeleeVisible() { var (layout, _, indicators) = FakeToolbar(); var repo = new ItemRepository(); var ctrl = ToolbarController.Bind(layout, repo, () => Array.Empty(), iconIds: (_,_,_,_) =>0u, useItem: _ => { }); ctrl.SetCombatMode(CombatMode.Melee); Assert.False(indicators[0x10000192u].Visible, "peace indicator should be hidden in melee mode"); Assert.True (indicators[0x10000193u].Visible, "melee indicator should be visible in melee mode"); Assert.False(indicators[0x10000194u].Visible, "missile indicator should be hidden in melee mode"); Assert.False(indicators[0x10000195u].Visible, "magic indicator should be hidden in melee mode"); } /// /// CombatModeChanged event on CombatState automatically updates the indicator. /// [Fact] public void CombatIndicator_liveSignal_updatesWhenCombatStateChanges() { var (layout, _, indicators) = FakeToolbar(); var repo = new ItemRepository(); var combat = new CombatState(); ToolbarController.Bind(layout, repo, () => Array.Empty(), iconIds: (_,_,_,_) =>0u, useItem: _ => { }, combatState: combat); // Initially NonCombat after bind. Assert.True(indicators[0x10000192u].Visible, "peace should be visible initially"); // Server fires CombatModeChanged → Magic. combat.SetCombatMode(CombatMode.Magic); Assert.False(indicators[0x10000192u].Visible, "peace should be hidden in magic mode"); Assert.False(indicators[0x10000193u].Visible, "melee should be hidden in magic mode"); Assert.False(indicators[0x10000194u].Visible, "missile should be hidden in magic mode"); Assert.True (indicators[0x10000195u].Visible, "magic indicator should be visible"); } }