]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add more UI helper methods for tests (#15463)
authorLeon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Sun, 16 Apr 2023 23:46:28 +0000 (11:46 +1200)
committerGitHub <noreply@github.com>
Sun, 16 Apr 2023 23:46:28 +0000 (09:46 +1000)
Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs
Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
Content.IntegrationTests/Tests/Interaction/InteractionTest.cs

index cbe3ee4dce45cb9959d1d6f03f4fb0a168e11c58..3978dfb1004bbd2bfe6e07d22a2daf5f805ab164 100644 (file)
@@ -1,4 +1,5 @@
 using System.Threading.Tasks;
+using Content.Client.Chemistry.UI;
 using Content.IntegrationTests.Tests.Interaction;
 using Content.Shared.Chemistry;
 using Content.Shared.Containers.ItemSlots;
@@ -22,7 +23,7 @@ public sealed class DispenserTest : InteractionTest
         Assert.IsNull(Hands.ActiveHandEntity);
 
         // Open BUI
-        await Interact("");
+        await Interact();
 
         // Eject beaker via BUI.
         var ev = new ItemSlotButtonPressedEvent(SharedChemMaster.InputSlotName);
@@ -31,5 +32,16 @@ public sealed class DispenserTest : InteractionTest
         // Beaker is back in the player's hands
         Assert.IsNotNull(Hands.ActiveHandEntity);
         AssertPrototype("Beaker", Hands.ActiveHandEntity);
+
+        // Re-insert the beaker
+        await Interact();
+        Assert.IsNull(Hands.ActiveHandEntity);
+
+        // Re-eject using the button directly instead of sending a BUI event. This test is really just a test of the
+        // bui/window helper methods.
+        await ClickControl<ReagentDispenserWindow>(nameof(ReagentDispenserWindow.EjectButton));
+        await RunTicks(5);
+        Assert.IsNotNull(Hands.ActiveHandEntity);
+        AssertPrototype("Beaker", Hands.ActiveHandEntity);
     }
 }
index 634755062bae5a0511176535fe512b6374e1bbcb..e945efe3dc283fff1a13b9a922cf6c92c40d2bf5 100644 (file)
@@ -3,7 +3,10 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
 using System.Threading.Tasks;
+using Content.Client.Chemistry.UI;
 using Content.Client.Construction;
 using Content.Server.Construction.Components;
 using Content.Server.Power.Components;
@@ -11,8 +14,13 @@ using Content.Server.Tools.Components;
 using Content.Shared.Construction.Prototypes;
 using Content.Shared.Item;
 using NUnit.Framework;
+using OpenToolkit.GraphicsLibraryFramework;
 using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
 using Robust.Shared.GameObjects;
+using Robust.Shared.Input;
 using Robust.Shared.Log;
 using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
@@ -221,6 +229,8 @@ public abstract partial class InteractionTest
         Assert.IsNull(Hands.ActiveHandEntity);
     }
 
+    #region Interact
+
     /// <summary>
     /// Use the currently held entity.
     /// </summary>
@@ -263,7 +273,14 @@ public abstract partial class InteractionTest
         }
 
         await PlaceInHands(entity);
+        await Interact(shouldSucceed, awaitDoAfters);
+    }
 
+    /// <summary>
+    /// Interact with an entity using the currently held entity.
+    /// </summary>
+    protected async Task Interact(bool shouldSucceed = true, bool awaitDoAfters = true)
+    {
         if (Target == null || !Target.Value.IsClientSide())
         {
             await Server.WaitPost(() => InteractSys.UserInteraction(Player, TargetCoords, Target));
@@ -283,6 +300,22 @@ public abstract partial class InteractionTest
         await CheckTargetChange(shouldSucceed && awaitDoAfters);
     }
 
+    /// <summary>
+    /// Variant of <see cref="InteractUsing"/> that performs several interactions using different entities.
+    /// </summary>
+    /// <remarks>
+    /// Empty strings imply empty hands.
+    /// </remarks>
+    protected async Task Interact(params EntitySpecifier[] specifiers)
+    {
+        foreach (var spec in specifiers)
+        {
+            await Interact(spec);
+        }
+    }
+
+    #endregion
+
     /// <summary>
     /// Wait for any currently active DoAfters to finish.
     /// </summary>
@@ -382,20 +415,6 @@ public abstract partial class InteractionTest
             await CheckTargetChange(shouldSucceed);
     }
 
-    /// <summary>
-    /// Variant of <see cref="InteractUsing"/> that performs several interactions using different entities.
-    /// </summary>
-    /// <remarks>
-    /// Empty strings imply empty hands.
-    /// </remarks>
-    protected async Task Interact(params EntitySpecifier[] specifiers)
-    {
-        foreach (var spec in specifiers)
-        {
-            await Interact(spec);
-        }
-    }
-
     #region Asserts
 
     protected void AssertPrototype(string? prototype, EntityUid? target = null)
@@ -721,6 +740,135 @@ public abstract partial class InteractionTest
 
     #endregion
 
+    #region UI
+
+    /// <summary>
+    ///     Presses and releases a button on some client-side window. Will fail if the button cannot be found.
+    /// </summary>
+    protected async Task ClickControl<TWindow>(string name) where TWindow : BaseWindow
+    {
+        await ClickControl(GetControl<TWindow, Control>(name));
+    }
+
+    /// <summary>
+    ///     Simulates a click and release at the center of some UI Constrol.
+    /// </summary>
+    protected async Task ClickControl(Control control)
+    {
+        var screenCoords = new ScreenCoordinates(
+            control.GlobalPixelPosition + control.PixelSize/2,
+            control.Window?.Id ?? default);
+
+        var relativePos = screenCoords.Position / control.UIScale - control.GlobalPosition;
+        var relativePixelPos =  screenCoords.Position - control.GlobalPixelPosition;
+
+        var args = new GUIBoundKeyEventArgs(
+            EngineKeyFunctions.UIClick,
+            BoundKeyState.Down,
+            screenCoords,
+            default,
+            relativePos,
+            relativePixelPos);
+
+        await Client.DoGuiEvent(control, args);
+        await RunTicks(1);
+
+        args = new GUIBoundKeyEventArgs(
+            EngineKeyFunctions.UIClick,
+            BoundKeyState.Up,
+            screenCoords,
+            default,
+            relativePos,
+            relativePixelPos);
+
+        await Client.DoGuiEvent(control, args);
+        await RunTicks(1);
+    }
+
+    /// <summary>
+    ///     Attempts to find a control on some client-side window. Will fail if the control cannot be found.
+    /// </summary>
+    protected TControl GetControl<TWindow, TControl>(string name)
+        where TWindow : BaseWindow
+        where TControl : Control
+    {
+        var control = GetControl<TWindow>(name);
+        Assert.That(control.GetType().IsAssignableTo(typeof(TControl)));
+        return (TControl) control;
+    }
+
+    protected Control GetControl<TWindow>(string name) where TWindow : BaseWindow
+    {
+        const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
+        var field = typeof(TWindow).GetField(name, flags);
+        var prop = typeof(TWindow).GetProperty(name, flags);
+
+        if (field == null && prop == null)
+        {
+            Assert.Fail($"Window {typeof(TWindow).Name} does not have a field or property named {name}");
+            return default!;
+        }
+
+        var window = GetWindow<TWindow>();
+        var control = (field?.GetValue(window) ?? prop?.GetValue(window)) as Control;
+
+        if (control == null)
+        {
+            Assert.Fail($"{name} was null or was not a control.");
+            return default!;
+        }
+
+        return control;
+    }
+
+    /// <summary>
+    /// Attempts to find a currently open client-side window. Will fail if the window cannot be found.
+    /// </summary>
+    /// <remarks>
+    /// Note that this just returns the very first open window of this type that is found.
+    /// </remarks>
+    protected TWindow GetWindow<TWindow>() where TWindow : BaseWindow
+    {
+        if (TryFindWindow(out TWindow? window))
+            return window;
+
+        Assert.Fail($"Could not find a window assignable to {nameof(TWindow)}");
+        return default!;
+    }
+
+    /// <summary>
+    /// Attempts to find a currently open client-side window.
+    /// </summary>
+    /// <remarks>
+    /// Note that this just returns the very first open window of this type that is found.
+    /// </remarks>
+    protected bool TryFindWindow<TWindow>([NotNullWhen(true)] out TWindow? window) where TWindow : BaseWindow
+    {
+        TryFindWindow(typeof(TWindow), out var control);
+        window = control as TWindow;
+        return window != null;
+    }
+
+
+    /// <summary>
+    /// Attempts to find a currently open client-side window.
+    /// </summary>
+    /// <remarks>
+    /// Note that this just returns the very first open window of this type that is found.
+    /// </remarks>
+    protected bool TryFindWindow(Type type, [NotNullWhen(true)] out BaseWindow? window)
+    {
+        Assert.That(type.IsAssignableTo(typeof(BaseWindow)));
+        window = UiMan.WindowRoot.Children
+            .OfType<BaseWindow>()
+            .Where(x => x.IsOpen)
+            .FirstOrDefault(x => x.GetType().IsAssignableTo(type));
+
+        return window != null;
+    }
+
+    #endregion
+
     #region Power
 
     protected void ToggleNeedPower(EntityUid? target = null)
index 16f6c71ca004f9a86f9ce0a33d37bc04b0c2ebf4..6c2c2296e73611f128cc805767910544e2fb498b 100644 (file)
@@ -14,6 +14,7 @@ using Content.Shared.Hands.EntitySystems;
 using Content.Shared.Interaction;
 using NUnit.Framework;
 using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
 using Robust.Shared.GameObjects;
 using Robust.Shared.Map;
 using Robust.Shared.Prototypes;
@@ -91,6 +92,7 @@ public abstract partial class InteractionTest
 
     // CLIENT dependencies
     protected IEntityManager CEntMan = default!;
+    protected IUserInterfaceManager UiMan = default!;
     protected ConstructionSystem CConSys = default!;
     protected ExamineSystem ExamineSys = default!;
     protected InteractionTestSystem CTestSystem = default!;
@@ -98,7 +100,6 @@ public abstract partial class InteractionTest
     // player components
     protected HandsComponent Hands = default!;
     protected DoAfterComponent DoAfters = default!;
-    protected UserInterfaceSystem CUISystem = default!;
 
     public float TickPeriod => (float)Timing.TickPeriod.TotalSeconds;
 
@@ -125,10 +126,10 @@ public abstract partial class InteractionTest
 
         // client dependencies
         CEntMan = Client.ResolveDependency<IEntityManager>();
+        UiMan = Client.ResolveDependency<IUserInterfaceManager>();
         CTestSystem = CEntMan.System<InteractionTestSystem>();
         CConSys = CEntMan.System<ConstructionSystem>();
         ExamineSys = CEntMan.System<ExamineSystem>();
-        CUISystem = CEntMan.System<UserInterfaceSystem>();
 
         // Setup map.
         MapData = await PoolManager.CreateTestMap(PairTracker);