From fc03fa377b62246faa0f5a51d5230c656a59e858 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 25 Apr 2026 00:25:26 +0200 Subject: [PATCH] test(ui): AcDream.UI.Abstractions unit tests (11 tests green) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers the pure-logic surface of commit 1: VitalsVMTests * HealthPercent reads from CombatState.GetHealthPercent * safe-default 1.0 when GUID unknown / not yet set / never updated * SetLocalPlayerGuid reroutes lookup to the new guid (no stale cache) * StaminaPercent / ManaPercent are null for D.2a scope * ctor throws ArgumentNullException on null CombatState PanelContextTests * record-struct fields round-trip * value-based equality NullCommandBusTests * Publish accepts any record type without throwing * Instance is a true singleton csproj template mirrors AcDream.Core.Tests (xUnit 2.9.3, Test.Sdk 17.14.1, runner 3.1.4, coverlet 6.0.4, implicit Using Xunit). References only AcDream.UI.Abstractions — no runtime / GL dependency, tests run fast. --- AcDream.slnx | 1 + .../AcDream.UI.Abstractions.Tests.csproj | 25 ++++++ .../NullCommandBusTests.cs | 22 +++++ .../PanelContextTests.cs | 24 ++++++ .../VitalsVMTests.cs | 80 +++++++++++++++++++ 5 files changed, 152 insertions(+) create mode 100644 tests/AcDream.UI.Abstractions.Tests/AcDream.UI.Abstractions.Tests.csproj create mode 100644 tests/AcDream.UI.Abstractions.Tests/NullCommandBusTests.cs create mode 100644 tests/AcDream.UI.Abstractions.Tests/PanelContextTests.cs create mode 100644 tests/AcDream.UI.Abstractions.Tests/VitalsVMTests.cs 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!)); + } +}