using System.Numerics; using System.Runtime.CompilerServices; using AcDream.App.Composition; using AcDream.App.Input; using AcDream.App.Interaction; using AcDream.App.Net; using AcDream.App.Rendering; using AcDream.App.UI; using AcDream.App.UI.Layout; using AcDream.Core.Net; using AcDream.Core.World; using AcDream.UI.Abstractions; namespace AcDream.App.Tests.Composition; public sealed class InteractionUiRuntimeSourcesTests { [Fact] public void DesiredComponentSnapshotNormalizesNullAndClearsExactly() { var state = new DesiredComponentSnapshotState(); IReadOnlyList<(uint Id, uint Amount)> items = [(0x01020304u, 7u)]; Assert.Empty(state.Items); state.Items = items; Assert.Same(items, state.Items); state.Clear(); Assert.Empty(state.Items); state.Items = null!; Assert.Empty(state.Items); } [Fact] public void SessionAuthorityDefaultsBindUnbindRebindAndDeactivateExactly() { var source = new DeferredLiveSessionUiAuthority(); var first = new SessionTarget(true); var second = new SessionTarget(false); Assert.False(source.IsInWorld); Assert.Same(NullCommandBus.Instance, source.Commands); IDisposable stale = source.Bind(first); Assert.True(source.IsInWorld); Assert.Same(first.Commands, source.Commands); Assert.Throws(() => source.Bind(second)); stale.Dispose(); IDisposable current = source.Bind(second); stale.Dispose(); Assert.False(source.IsInWorld); Assert.Same(second.Commands, source.Commands); source.Deactivate(); current.Dispose(); Assert.Same(NullCommandBus.Instance, source.Commands); Assert.Throws(() => source.Bind(first)); } [Fact] public void SelectionAuthorityUsesRetailSafeDefaultsAndExactBindingLifetime() { var source = new DeferredSelectionUiAuthority(); var query = new SelectionQuery(); SelectionInteractionController interactions = Stub(); Assert.True(source.IsWithinExternalContainerUseRange(1u)); Assert.False(source.ShouldShowHealth(1u)); Assert.Null(source.ResolveVividTargetInfo(1u)); IDisposable binding = source.Bind(query, interactions); Assert.False(source.IsWithinExternalContainerUseRange(1u)); Assert.True(source.ShouldShowHealth(1u)); Assert.Equal(3f, source.ResolveVividTargetInfo(1u)?.SelectionSphereRadius); Assert.Throws(() => source.Bind(query, interactions)); binding.Dispose(); Assert.True(source.IsWithinExternalContainerUseRange(1u)); source.Deactivate(); Assert.Throws(() => source.Bind(query, interactions)); } [Fact] public void RadarNeverCachesAnUnboundBootstrapSnapshot() { var source = new DeferredRadarSnapshotSource(); Assert.Same(UiRadarSnapshot.Empty, source.Snapshot()); var provider = new RadarSnapshotProvider( new AcDream.Core.Items.ClientObjectTable(), static () => new Dictionary(), static () => new Dictionary(), static () => 0u, static () => 0f, static () => 0u, static () => null, static () => false, static () => true); IDisposable binding = source.Bind(provider); Assert.True(source.Snapshot().UiLocked); binding.Dispose(); Assert.False(source.Snapshot().UiLocked); } [Fact] public void AutomationProxyIsInertUntilExactRuntimeBinds() { var source = new DeferredWorldLifecycleAutomationRuntime(); Assert.False(source.TryWriteCheckpoint("early", out string earlyError)); Assert.Contains("not bound", earlyError); var target = new AutomationRuntime(); IDisposable binding = source.Bind(target); Assert.True(source.IsWorldReady); Assert.True(source.TryWriteCheckpoint("ready", out _)); Assert.Equal("ready", target.LastCheckpoint); binding.Dispose(); Assert.False(source.IsWorldReady); source.Deactivate(); Assert.Throws(() => source.Bind(target)); } [Fact] public void ExpectedOwnerBindingReleasesOnce() { object target = new(); int releases = 0; var binding = new ExpectedOwnerBinding(target, value => { Assert.Same(target, value); releases++; }); binding.Dispose(); binding.Dispose(); Assert.Equal(1, releases); } [Fact] public void RetainedInputCaptureBindingCannotClearAReplacement() { var slot = new RetainedUiInputCaptureSlot(); var first = new UiRoot(); var second = new UiRoot(); IDisposable stale = slot.Bind(first); Assert.Same(first, slot.Root); Assert.Throws(() => slot.Bind(second)); stale.Dispose(); IDisposable current = slot.Bind(second); stale.Dispose(); Assert.Same(second, slot.Root); current.Dispose(); Assert.Null(slot.Root); } [Fact] public void LateBindingCleanupRetriesOnlyTheFailedSuffix() { var bindings = new InteractionUiLateBindings(); var first = new RetryBinding(failures: 0); var second = new RetryBinding(failures: 1); bindings.AdoptLateOwnerBinding("first", first); bindings.AdoptLateOwnerBinding("second", second); Assert.Throws(bindings.Dispose); Assert.Equal(1, first.Calls); Assert.Equal(1, second.Calls); bindings.Dispose(); Assert.Equal(1, first.Calls); Assert.Equal(2, second.Calls); Assert.Throws(() => bindings.AdoptLateOwnerBinding("late", new RetryBinding(0))); } private sealed class SessionTarget(bool inWorld) : ILiveUiSessionTarget { public bool IsInWorld { get; } = inWorld; public WorldSession? CurrentSession => null; public ICommandBus Commands { get; } = new LiveCommandBus(); } private sealed class SelectionQuery : IRetainedUiSelectionQuery { public bool ShouldShowHealth(uint serverGuid) => true; public VividTargetInfo? ResolveVividTargetInfo(uint serverGuid) => new(Vector3.Zero, 3f, 0u, 0u); public bool IsWithinExternalContainerUseRange(uint serverGuid) => false; } private sealed class AutomationRuntime : AcDream.App.UI.Testing.IRetailUiAutomationRuntime { public bool IsWorldReady => true; public bool IsWorldViewportVisible => true; public int PortalMaterializationCount => 2; public string? LastCheckpoint { get; private set; } public bool TryWriteCheckpoint(string name, out string error) { LastCheckpoint = name; error = string.Empty; return true; } public bool TryRequestScreenshot(string name, out string error) { error = string.Empty; return true; } public bool IsScreenshotComplete(string name) => true; } private sealed class RetryBinding(int failures) : IDisposable { private int _failures = failures; public int Calls { get; private set; } public void Dispose() { Calls++; if (_failures > 0) { _failures--; throw new InvalidOperationException("retry"); } } } private static T Stub() where T : class => (T)RuntimeHelpers.GetUninitializedObject(typeof(T)); }