diff --git a/AcDream.slnx b/AcDream.slnx index 760229e..5a5029d 100644 --- a/AcDream.slnx +++ b/AcDream.slnx @@ -15,5 +15,6 @@ + diff --git a/tests/AcDream.UI.Abstractions.Tests/AcDream.UI.Abstractions.Tests.csproj b/tests/AcDream.UI.Abstractions.Tests/AcDream.UI.Abstractions.Tests.csproj new file mode 100644 index 0000000..f99c6e2 --- /dev/null +++ b/tests/AcDream.UI.Abstractions.Tests/AcDream.UI.Abstractions.Tests.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + diff --git a/tests/AcDream.UI.Abstractions.Tests/NullCommandBusTests.cs b/tests/AcDream.UI.Abstractions.Tests/NullCommandBusTests.cs new file mode 100644 index 0000000..6f34d5e --- /dev/null +++ b/tests/AcDream.UI.Abstractions.Tests/NullCommandBusTests.cs @@ -0,0 +1,22 @@ +namespace AcDream.UI.Abstractions.Tests; + +public sealed class NullCommandBusTests +{ + private sealed record FakeCmd(int Value); + + [Fact] + public void Publish_DoesNotThrow_OnAnyRecordType() + { + var bus = NullCommandBus.Instance; + + bus.Publish(new FakeCmd(42)); + bus.Publish("a string command"); + bus.Publish(12345); + } + + [Fact] + public void Instance_IsSingleton() + { + Assert.Same(NullCommandBus.Instance, NullCommandBus.Instance); + } +} diff --git a/tests/AcDream.UI.Abstractions.Tests/PanelContextTests.cs b/tests/AcDream.UI.Abstractions.Tests/PanelContextTests.cs new file mode 100644 index 0000000..2a665c9 --- /dev/null +++ b/tests/AcDream.UI.Abstractions.Tests/PanelContextTests.cs @@ -0,0 +1,24 @@ +namespace AcDream.UI.Abstractions.Tests; + +public sealed class PanelContextTests +{ + [Fact] + public void Fields_RoundTripThroughConstructor() + { + var ctx = new PanelContext(DeltaSeconds: 0.016f, Commands: NullCommandBus.Instance); + + Assert.Equal(0.016f, ctx.DeltaSeconds); + Assert.Same(NullCommandBus.Instance, ctx.Commands); + } + + [Fact] + public void RecordEquality_ByValue() + { + var a = new PanelContext(1f / 60f, NullCommandBus.Instance); + var b = new PanelContext(1f / 60f, NullCommandBus.Instance); + + // Record-struct equality is value-based on DeltaSeconds + reference-based + // on Commands (since ICommandBus is a reference type, same instance → equal). + Assert.Equal(a, b); + } +} diff --git a/tests/AcDream.UI.Abstractions.Tests/VitalsVMTests.cs b/tests/AcDream.UI.Abstractions.Tests/VitalsVMTests.cs new file mode 100644 index 0000000..3abf2c3 --- /dev/null +++ b/tests/AcDream.UI.Abstractions.Tests/VitalsVMTests.cs @@ -0,0 +1,80 @@ +using AcDream.Core.Combat; +using AcDream.UI.Abstractions.Panels.Vitals; + +namespace AcDream.UI.Abstractions.Tests; + +public sealed class VitalsVMTests +{ + [Fact] + public void HealthPercent_ReturnsCombatStateValue_AfterUpdateHealth() + { + var combat = new CombatState(); + uint guid = 0x5000_0042u; + combat.OnUpdateHealth(guid, 0.42f); + + var vm = new VitalsVM(combat); + vm.SetLocalPlayerGuid(guid); + + Assert.Equal(0.42f, vm.HealthPercent, precision: 3); + } + + [Fact] + public void HealthPercent_ReturnsOne_WhenGuidUnknown() + { + var combat = new CombatState(); + var vm = new VitalsVM(combat); + + // No SetLocalPlayerGuid call — defaults to 0 which CombatState has never seen. + Assert.Equal(1f, vm.HealthPercent); + } + + [Fact] + public void HealthPercent_ReturnsOne_WhenGuidSetButNeverUpdated() + { + var combat = new CombatState(); + var vm = new VitalsVM(combat); + vm.SetLocalPlayerGuid(0xDEAD_BEEFu); + + Assert.Equal(1f, vm.HealthPercent); + } + + [Fact] + public void StaminaPercent_IsNull_ForD2aScope() + { + // D.2a explicitly defers Stamina until LocalPlayerState + PlayerDescription + // wiring. When that arrives VitalsVM.StaminaPercent becomes non-null and + // VitalsPanel starts drawing the Stam bar automatically. + var vm = new VitalsVM(new CombatState()); + Assert.Null(vm.StaminaPercent); + } + + [Fact] + public void ManaPercent_IsNull_ForD2aScope() + { + var vm = new VitalsVM(new CombatState()); + Assert.Null(vm.ManaPercent); + } + + [Fact] + public void SetLocalPlayerGuid_ReroutesHealthLookup_WithoutStaleCache() + { + // Simulate the realistic GameWindow flow: VM is constructed pre-login + // with GUID=0, then SetLocalPlayerGuid is called at EnterWorld. + var combat = new CombatState(); + uint playerGuid = 0x5003_E219u; + combat.OnUpdateHealth(playerGuid, 0.75f); + + var vm = new VitalsVM(combat); + // Before SetLocalPlayerGuid — reads GUID=0 → returns safe 1.0. + Assert.Equal(1f, vm.HealthPercent); + + vm.SetLocalPlayerGuid(playerGuid); + Assert.Equal(0.75f, vm.HealthPercent, precision: 3); + } + + [Fact] + public void Constructor_ThrowsOnNullCombat() + { + Assert.Throws(() => new VitalsVM(null!)); + } +}