From aa8c8778a840936a9f637c2c014b117853c5eeeb Mon Sep 17 00:00:00 2001 From: Mike Schulze Date: Fri, 11 Mar 2022 22:28:16 +0100 Subject: [PATCH 1/3] GD-129-4: Add scene runner prototype --- addons/gdUnit3/src/SceneRunner.cs | 128 +++++++++++ addons/gdUnit3/src/core/SceneRunner.cs | 207 ++++++++++++++++++ .../src/core/execution/ExecutionContext.cs | 9 + addons/gdUnit3/test/core/SceneRunnerTest.cs | 32 +++ gdUnit3.csproj | 4 +- 5 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 addons/gdUnit3/src/SceneRunner.cs create mode 100644 addons/gdUnit3/src/core/SceneRunner.cs create mode 100644 addons/gdUnit3/test/core/SceneRunnerTest.cs diff --git a/addons/gdUnit3/src/SceneRunner.cs b/addons/gdUnit3/src/SceneRunner.cs new file mode 100644 index 00000000..a3f0355e --- /dev/null +++ b/addons/gdUnit3/src/SceneRunner.cs @@ -0,0 +1,128 @@ +using System; +using System.Threading.Tasks; + +namespace GdUnit3 +{ + using Godot; + + /// + /// Scene runner to test interactions like keybord/mouse inputs on a Godot scene. + /// + public interface SceneRunner : IDisposable + { + + public static SceneRunner Load(string resourcePath) + { + return new Core.SceneRunner(resourcePath, true); + } + + /// + /// Simulates that a key has been pressed + /// + /// the key code e.g. 'KeyList.Enter' + /// false by default set to true if simmulate shift is press + /// false by default set to true if simmulate control is press + /// SceneRunner + SceneRunner SimulateKeyPressed(KeyList keyCode, bool shift = false, bool control = false); + + + /// + /// Simulates that a key is pressed + /// + /// the key code e.g. 'KeyList.Enter' + /// false by default set to true if simmulate shift is press + /// false by default set to true if simmulate control is press + /// + SceneRunner SimulateKeyPress(KeyList keyCode, bool shift = false, bool control = false); + + + /// + /// Simulates that a key has been released + /// + /// the key code e.g. 'KeyList.Enter' + /// false by default set to true if simmulate shift is press + /// false by default set to true if simmulate control is press + /// + SceneRunner SimulateKeyRelease(KeyList keyCode, bool shift = false, bool control = false); + + /// + /// Simulates a mouse moved to relative position by given speed + /// + /// The mouse position relative to the previous position (position at the last frame). + /// The mouse speed in pixels per second. + /// + SceneRunner SimulateMouseMove(Vector2 relative, Vector2 speeds = default); + + /// + /// Simulates a mouse button pressed + /// + /// The mouse button identifier, one of the ButtonList button or button wheel constants. + /// + SceneRunner SimulateMouseButtonPressed(ButtonList button); + + /// + /// Simulates a mouse button press (holding) + /// + /// The mouse button identifier, one of the ButtonList button or button wheel constants. + /// + SceneRunner SimulateMouseButtonPress(ButtonList button); + + /// + /// Simulates a mouse button released + /// + /// The mouse button identifier, one of the ButtonList button or button wheel constants. + /// + SceneRunner SimulateMouseButtonRelease(ButtonList button); + + /// + /// Sets how fast or slow the scene simulation is processed (clock ticks versus the real). + /// It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life, + /// whilst a value of 0.5 means the game moves at half the regular speed. + /// + /// + /// + /// + SceneRunner SetTimeFactor(double timeFactor = 1.0); + + + /// + /// Simulates scene processing for a certain number of frames by given delta peer frame by ignoring the time factor + /// + /// amount of frames to process + /// the time delta between a frame in ms + /// + Task Simulate(int frames, long deltaPeerFrame); + + /// + /// Simulates scene processing for a certain number of frames + /// + /// amount of frames to process + /// + Task SimulateFrames(int frames); + + /// + /// Waits until next frame (idle_frame) + /// + /// SignalAwaiter + public SignalAwaiter OnIdleFrame(); + + /// + /// Waits for a specific amount of seconds + /// + /// Seconds to wait. 1.0 for one Second + /// + public SignalAwaiter OnWait(float timeSec); + + /// + /// Access to current running scene + /// + /// Node + public Node Scene(); + + /// + /// Shows the running scene and moves the window to the foreground. + /// + public void ShowScene(); + } + +} diff --git a/addons/gdUnit3/src/core/SceneRunner.cs b/addons/gdUnit3/src/core/SceneRunner.cs new file mode 100644 index 00000000..b4702352 --- /dev/null +++ b/addons/gdUnit3/src/core/SceneRunner.cs @@ -0,0 +1,207 @@ +using System; +using System.Threading.Tasks; + +namespace GdUnit3.Core +{ + using Godot; + using Executions; + using Tools; + internal sealed class SceneRunner : GdUnit3.SceneRunner + { + private bool isSimulateRunnig; + + private SceneTree SceneTree { get; set; } + + private Node CurrentScene { get; set; } + private bool Verbose { get; set; } + private Vector2 CurrentMousePos { get; set; } + private double TimeFactor { get; set; } + private int SavedIterationsPerSecond { get; set; } + private bool IsSimulateRunnig { get; set; } + + public SceneRunner(string resourcePath, bool verbose = false) + { + Verbose = verbose; + ExecutionContext.RegisterDisposable(this); + SceneTree = Godot.Engine.GetMainLoop() as SceneTree; + CurrentScene = (Godot.ResourceLoader.Load(resourcePath) as PackedScene).Instance(); + SceneTree.Root.AddChild(CurrentScene); + CurrentMousePos = default; + SavedIterationsPerSecond = (int)ProjectSettings.GetSetting("physics/common/physics_fps"); + SetTimeFactor(1.0); + IsSimulateRunnig = false; + } + + public GdUnit3.SceneRunner SimulateKeyPress(KeyList key_code, bool shift = false, bool control = false) + { + PrintCurrentFocus(); + var action = new InputEventKey(); + action.Pressed = true; + action.Scancode = ((uint)key_code); + action.Shift = shift; + action.Control = control; + + Print(" process key event {0} ({1}) <- {2}:{3}", CurrentScene, SceneName(), action.AsText(), action.IsPressed() ? "pressing" : "released"); + SceneTree.InputEvent(action); + return this; + } + + public GdUnit3.SceneRunner SimulateKeyPressed(KeyList key_code, bool shift = false, bool control = false) + { + SimulateKeyPress(key_code, shift, control); + SimulateKeyRelease(key_code, shift, control); + return this; + } + + public GdUnit3.SceneRunner SimulateKeyRelease(KeyList key_code, bool shift = false, bool control = false) + { + PrintCurrentFocus(); + var action = new InputEventKey(); + action.Pressed = false; + action.Scancode = ((uint)key_code); + action.Shift = shift; + action.Control = control; + + Print(" process key event {0} ({1}) <- {2}:{3}", CurrentScene, SceneName(), action.AsText(), action.IsPressed() ? "pressing" : "released"); + SceneTree.InputEvent(action); + return this; + } + + public GdUnit3.SceneRunner SimulateMouseMove(Vector2 relative, Vector2 speed = default) + { + var action = new InputEventMouseMotion(); + action.Relative = relative; + action.Speed = speed == default ? Vector2.One : speed; + + Print(" process mouse motion event {0} ({1}) <- {2}", CurrentScene, SceneName(), action.AsText()); + SceneTree.InputEvent(action); + return this; + } + + public GdUnit3.SceneRunner SimulateMouseButtonPressed(ButtonList buttonIndex) + { + SimulateMouseButtonPress(buttonIndex); + SimulateMouseButtonRelease(buttonIndex); + return this; + } + + public GdUnit3.SceneRunner SimulateMouseButtonPress(ButtonList buttonIndex) + { + var action = new InputEventMouseButton(); + action.ButtonIndex = (int)buttonIndex; + action.ButtonMask = (int)buttonIndex; + action.Pressed = true; + action.Position = CurrentMousePos; + + Print(" process mouse button event {0} ({1}) <- {2}", CurrentScene, SceneName(), action.AsText()); + SceneTree.InputEvent(action); + return this; + } + + public GdUnit3.SceneRunner SimulateMouseButtonRelease(ButtonList buttonIndex) + { + var action = new InputEventMouseButton(); + action.ButtonIndex = (int)buttonIndex; + action.ButtonMask = 0; + action.Pressed = false; + action.Position = CurrentMousePos; + + Print(" process mouse button event {0} ({1}) <- {2}", CurrentScene, SceneName(), action.AsText()); + SceneTree.InputEvent(action); + return this; + } + + public GdUnit3.SceneRunner SetTimeFactor(double timeFactor = 1.0) + { + TimeFactor = Math.Min(9.0, timeFactor); + + Print("set time factor: {0}", TimeFactor); + Print("set physics iterations_per_second: {0}", SavedIterationsPerSecond * TimeFactor); + return this; + } + + public async Task Simulate(int frames, long deltaPeerFrame) + { + DeactivateTimeFactor(); + IsSimulateRunnig = true; + for (int frame = 0; frame < frames; frame++) + await OnWait(deltaPeerFrame); + IsSimulateRunnig = false; + return this; + } + + public async Task SimulateFrames(int frames) + { + var timeShiftFrames = Math.Max(1, frames / TimeFactor); + ActivateTimeFactor(); + IsSimulateRunnig = true; + for (int frame = 0; frame < frames; frame++) + await OnIdleFrame(); + DeactivateTimeFactor(); + IsSimulateRunnig = false; + return this; + } + + private void ActivateTimeFactor() + { + Engine.TimeScale = (float)TimeFactor; + Engine.IterationsPerSecond = (int)(SavedIterationsPerSecond * TimeFactor); + } + + private void DeactivateTimeFactor() + { + Engine.TimeScale = 1; + Engine.IterationsPerSecond = SavedIterationsPerSecond; + } + + private void Print(string message, params object[] args) + { + if (Verbose) + Console.WriteLine(String.Format(message, args)); + } + + private void PrintCurrentFocus() + { + if (!Verbose) + return; + var focusedNode = (CurrentScene as Control)?.GetFocusOwner(); + + if (focusedNode != null) + Console.WriteLine(" focus on {0}", focusedNode); + else + Console.WriteLine(" no focus set"); + } + + private string SceneName() + { + var sceneScript = CurrentScene.GetScript(); + + if (!(sceneScript is Script)) + return CurrentScene.Name; + if (!CurrentScene.Name.BeginsWith("@")) + return CurrentScene.Name; + + return (sceneScript as Script).ResourceName.BaseName(); + } + + public Node Scene() => CurrentScene; + + public SignalAwaiter OnIdleFrame() => CurrentScene.ToSignal(CurrentScene.GetTree(), "idle_frame"); + + public SignalAwaiter OnWait(float timeSec) => CurrentScene.ToSignal(CurrentScene.GetTree().CreateTimer(timeSec), "timeout"); + + public void ShowScene() + { + OS.WindowMaximized = true; + OS.CenterWindow(); + OS.MoveWindowToForeground(); + } + + public void Dispose() + { + Console.WriteLine("Dispose SceneRunner"); + SceneTree.Root.RemoveChild(CurrentScene); + CurrentScene.QueueFree(); + } + } +} \ No newline at end of file diff --git a/addons/gdUnit3/src/core/execution/ExecutionContext.cs b/addons/gdUnit3/src/core/execution/ExecutionContext.cs index a9ed868c..b725333f 100644 --- a/addons/gdUnit3/src/core/execution/ExecutionContext.cs +++ b/addons/gdUnit3/src/core/execution/ExecutionContext.cs @@ -25,6 +25,7 @@ public ExecutionContext(TestSuite testInstance, IEnumerable EventListeners = eventListeners; ReportCollector = new TestReportCollector(); SubExecutionContexts = new List(); + Disposables = new List(); } public ExecutionContext(ExecutionContext context) : this(context.TestSuite, context.EventListeners, context.ReportOrphanNodesEnabled) { @@ -61,6 +62,9 @@ public Stopwatch Stopwatch public TestSuite TestSuite { get; private set; } + private List Disposables + { get; set; } + public static ExecutionContext Current => Thread.GetData(Thread.GetNamedDataSlot("ExecutionContext")) as ExecutionContext; private IEnumerable EventListeners @@ -137,8 +141,13 @@ public void FireBeforeTestEvent() => public void FireAfterTestEvent() => FireTestEvent(TestEvent.AfterTest(TestSuite.ResourcePath, TestSuite.Name, CurrentTestCase.Name, BuildStatistics(OrphanCount(true)), CollectReports)); + + public static void RegisterDisposable(IDisposable disposable) => + ExecutionContext.Current.Disposables.Add(disposable); + public void Dispose() { + Disposables.ForEach(disposable => disposable.Dispose()); Stopwatch.Stop(); } diff --git a/addons/gdUnit3/test/core/SceneRunnerTest.cs b/addons/gdUnit3/test/core/SceneRunnerTest.cs new file mode 100644 index 00000000..6adb33e0 --- /dev/null +++ b/addons/gdUnit3/test/core/SceneRunnerTest.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; + +namespace GdUnit3.Tests +{ + using GdUnit3.Asserts; + using Executions; + + using static Assertions; + + [TestSuite] + class SceneRunnerTest + { + + [TestCase] + public async Task RunScene() + { + + SceneRunner runner = SceneRunner.Load("res://addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn"); + runner.ShowScene(); + runner.SimulateKeyPressed(Godot.KeyList.Enter); + + + runner.SimulateMouseMove(Godot.Vector2.One); + runner.Scene().Call("start_color_cycle"); + + await runner.Scene().ToSignal(runner.Scene(), "panel_color_change"); + + //await runner.DoWaitSignal("panel_color_change", runner.Scene()._Get("_box1"), Godot.Colors.Red); + await runner.SimulateFrames(100); + } + } +} diff --git a/gdUnit3.csproj b/gdUnit3.csproj index af6af65f..278f9c57 100644 --- a/gdUnit3.csproj +++ b/gdUnit3.csproj @@ -1,6 +1,6 @@ - net472 - preview + netstandard2.1 + 8.0 From a22b754e0f6953bfb54c1b8edc4523a4c066f876 Mon Sep 17 00:00:00 2001 From: Mike Schulze Date: Sun, 13 Mar 2022 20:45:26 +0100 Subject: [PATCH 2/3] append --- addons/gdUnit3/src/Assertions.cs | 27 +++- addons/gdUnit3/src/SceneRunner.cs | 139 +++++++++++++----- addons/gdUnit3/src/asserts/Comparable.cs | 9 +- addons/gdUnit3/src/asserts/ExceptionAssert.cs | 16 +- addons/gdUnit3/src/core/SceneRunner.cs | 84 +++++++++-- .../src/core/execution/ExecutionStage.cs | 10 +- addons/gdUnit3/test/core/SceneRunnerTest.cs | 119 +++++++++++++-- .../test/mocker/resources/scenes/TestScene.gd | 3 + .../mocker/resources/scenes/TestScene.tscn | 3 - 9 files changed, 333 insertions(+), 77 deletions(-) diff --git a/addons/gdUnit3/src/Assertions.cs b/addons/gdUnit3/src/Assertions.cs index 35143d38..28c6deb7 100644 --- a/addons/gdUnit3/src/Assertions.cs +++ b/addons/gdUnit3/src/Assertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Threading.Tasks; namespace GdUnit3 { @@ -62,9 +63,33 @@ public sealed class Assertions /// An Assertion to verify for expecting exceptions /// /// A function callback where throw possible exceptions - /// + /// IExceptionAssert public static IExceptionAssert AssertThrown(Func supplier) => new ExceptionAssert(supplier); + /// + /// An Assertion to verify for expecting exceptions when performing a task. + /// + /// + /// await AssertThrown(task.WithTimeout(500)) + /// .ContinueWith(result => result.Result.HasMessage("timed out after 500ms.")); + /// + /// + /// + /// A task where throw possible exceptions + /// a task of IExceptionAssert to await + public async static Task AssertThrown(Task task) + { + try + { + await task; + return default; + } + catch (Exception e) + { + return new ExceptionAssert(e); + } + } + /// ----------- Helpers ------------------------------------------------------------------------------------------------------- /// diff --git a/addons/gdUnit3/src/SceneRunner.cs b/addons/gdUnit3/src/SceneRunner.cs index a3f0355e..646972eb 100644 --- a/addons/gdUnit3/src/SceneRunner.cs +++ b/addons/gdUnit3/src/SceneRunner.cs @@ -11,13 +11,23 @@ namespace GdUnit3 public interface SceneRunner : IDisposable { - public static SceneRunner Load(string resourcePath) - { - return new Core.SceneRunner(resourcePath, true); - } + /// + /// Loads a scene into the SceneRunner to be simmulated. + /// + /// The path to the scene resource. + /// Prints detailt infos on scene simmulation. + /// + public static SceneRunner Load(string resourcePath, bool verbose = false) => new Core.SceneRunner(resourcePath, verbose); + + /// + /// Sets the actual mouse position relative to the viewport. + /// + /// The position in x/y coordinates + /// + SceneRunner SetMousePos(Vector2 position); /// - /// Simulates that a key has been pressed + /// Simulates that a key has been pressed. /// /// the key code e.g. 'KeyList.Enter' /// false by default set to true if simmulate shift is press @@ -25,68 +35,72 @@ public static SceneRunner Load(string resourcePath) /// SceneRunner SceneRunner SimulateKeyPressed(KeyList keyCode, bool shift = false, bool control = false); - /// - /// Simulates that a key is pressed + /// Simulates that a key is pressed. /// /// the key code e.g. 'KeyList.Enter' /// false by default set to true if simmulate shift is press /// false by default set to true if simmulate control is press - /// + /// SceneRunner SceneRunner SimulateKeyPress(KeyList keyCode, bool shift = false, bool control = false); - /// - /// Simulates that a key has been released + /// Simulates that a key has been released. /// /// the key code e.g. 'KeyList.Enter' /// false by default set to true if simmulate shift is press /// false by default set to true if simmulate control is press - /// + /// SceneRunner SceneRunner SimulateKeyRelease(KeyList keyCode, bool shift = false, bool control = false); /// - /// Simulates a mouse moved to relative position by given speed + /// Simulates a mouse moved to relative position by given speed. /// /// The mouse position relative to the previous position (position at the last frame). /// The mouse speed in pixels per second. - /// + /// SceneRunner SceneRunner SimulateMouseMove(Vector2 relative, Vector2 speeds = default); /// - /// Simulates a mouse button pressed + /// Simulates a mouse button pressed. /// /// The mouse button identifier, one of the ButtonList button or button wheel constants. - /// + /// SceneRunner SceneRunner SimulateMouseButtonPressed(ButtonList button); /// - /// Simulates a mouse button press (holding) + /// Simulates a mouse button press. (holding) /// /// The mouse button identifier, one of the ButtonList button or button wheel constants. - /// + /// SceneRunner SceneRunner SimulateMouseButtonPress(ButtonList button); /// - /// Simulates a mouse button released + /// Simulates a mouse button released. /// /// The mouse button identifier, one of the ButtonList button or button wheel constants. - /// + /// SceneRunner SceneRunner SimulateMouseButtonRelease(ButtonList button); /// /// Sets how fast or slow the scene simulation is processed (clock ticks versus the real). - /// It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life, - /// whilst a value of 0.5 means the game moves at half the regular speed. + /// + /// 'It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life,' + /// 'whilst a value of 0.5 means the game moves at half the regular speed' + /// /// /// - /// - /// + /// SceneRunner SceneRunner SetTimeFactor(double timeFactor = 1.0); - /// - /// Simulates scene processing for a certain number of frames by given delta peer frame by ignoring the time factor + /// Simulates scene processing for a certain number of frames by given delta peer frame by ignoring the current time factor + /// + /// + /// 'Waits until given frames are rendered with a delta of 20 peer frame' + /// await runner.SimulateFrames(100, 20); + /// + /// /// /// amount of frames to process /// the time delta between a frame in ms @@ -94,35 +108,92 @@ public static SceneRunner Load(string resourcePath) Task Simulate(int frames, long deltaPeerFrame); /// - /// Simulates scene processing for a certain number of frames + /// Simulates scene processing for a certain number of frames. + /// + /// + /// 'Waits until 100 frames are rendered' + /// await runner.SimulateFrames(100); + /// + /// /// /// amount of frames to process /// Task SimulateFrames(int frames); /// - /// Waits until next frame (idle_frame) + /// Waits until next frame is processed (idle_frame) + /// + /// + /// 'Waits until next frame is processed' + /// await runner.OnIdleFrame(); + /// + /// + /// await OnIdleFrame(); + /// + /// SignalAwaiter + SignalAwaiter OnIdleFrame(); + + /// + /// Waits for given signal is emited. + /// + /// + /// 'Waits for signal "mySignal"' + /// await runner.OnSignal("mySignal"); + /// + /// /// + /// The name of signal to wait /// SignalAwaiter - public SignalAwaiter OnIdleFrame(); + SignalAwaiter OnSignal(string signal); /// - /// Waits for a specific amount of seconds + /// Waits for a specific amount of seconds. + /// + /// + /// 'Waits for two seconds' + /// await runner.OnWait(2.0); + /// + /// /// /// Seconds to wait. 1.0 for one Second - /// - public SignalAwaiter OnWait(float timeSec); + /// SignalAwaiter + SignalAwaiter OnWait(float timeSec); /// /// Access to current running scene /// /// Node - public Node Scene(); + Node Scene(); /// /// Shows the running scene and moves the window to the foreground. /// - public void ShowScene(); - } + void MoveWindowToForeground(); + + /// + /// Invokes the method by given name and arguments. + /// + /// The name of method to invoke + /// The function arguments + /// The invoced method return value + /// + public object Invoke(string name, params object[] args); + + /// + /// Returns the property by given name. + /// + /// The type of the property + /// The parameter name + /// Returns the value of property or throws a MissingFieldException + /// + public T GetProperty(string name); + /// + /// Finds the node by given name. + /// + /// The name of node to find + /// Allow recursive search + /// The node if found or Null + public Node FindNode(string name, bool recursive = true); + } } diff --git a/addons/gdUnit3/src/asserts/Comparable.cs b/addons/gdUnit3/src/asserts/Comparable.cs index 7411997f..eaaeb3cb 100644 --- a/addons/gdUnit3/src/asserts/Comparable.cs +++ b/addons/gdUnit3/src/asserts/Comparable.cs @@ -60,11 +60,14 @@ public static Result IsEqual(T left, T right, MODE compareMode = MODE.CASE_SE if (left == null || right == null) return new Result(false, left, right); + if (object.ReferenceEquals(left, right)) + return new Result(true, left, right, r); + var type = left.GetType(); if (type.IsEnum) return new Result(left.Equals(right), left, right, r); - if (type.IsPrimitive || typeof(string).Equals(type) || left is IEquatable) + if (type.IsPrimitive || typeof(string).Equals(type) || left is IEquatable || left is System.ValueType) { //Godot.GD.PrintS("IsPrimitive", type, left, right); if (left is String && compareMode == MODE.CASE_INSENSITIVE) @@ -72,9 +75,6 @@ public static Result IsEqual(T left, T right, MODE compareMode = MODE.CASE_SE return new Result(left.Equals(right), left, right, r); } - if (object.ReferenceEquals(left, right)) - return new Result(true, left, right, r); - if (type.IsArray) { var la = left as Array; @@ -116,6 +116,7 @@ public static Result IsEqual(T left, T right, MODE compareMode = MODE.CASE_SE if (!left.GetType().Equals(right.GetType())) return new Result(false, left, right, r); + // deep compare foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { diff --git a/addons/gdUnit3/src/asserts/ExceptionAssert.cs b/addons/gdUnit3/src/asserts/ExceptionAssert.cs index 62e5a570..5d51f149 100644 --- a/addons/gdUnit3/src/asserts/ExceptionAssert.cs +++ b/addons/gdUnit3/src/asserts/ExceptionAssert.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace GdUnit3.Asserts { @@ -10,14 +11,13 @@ internal sealed class ExceptionAssert : IExceptionAssert public ExceptionAssert(Func supplier) { - try - { - supplier.Invoke(); - } - catch (Exception e) - { - Current = e; - } + try { supplier.Invoke(); } + catch (Exception e) { Current = e; } + } + + public ExceptionAssert(Exception e) + { + Current = e; } public IExceptionAssert IsInstanceOf() diff --git a/addons/gdUnit3/src/core/SceneRunner.cs b/addons/gdUnit3/src/core/SceneRunner.cs index b4702352..224591cb 100644 --- a/addons/gdUnit3/src/core/SceneRunner.cs +++ b/addons/gdUnit3/src/core/SceneRunner.cs @@ -1,5 +1,42 @@ using System; +using System.Threading; using System.Threading.Tasks; +using System.Linq; + +namespace GdUnit3 +{ + public static class GdUnitAwaiter + { + public static async Task WithTimeout(this Task task, int timeoutMillis) + { + var wrapperTask = Task.Run(async () => await task); + using var token = new CancellationTokenSource(); + var completedTask = await Task.WhenAny(wrapperTask, Task.Delay(timeoutMillis, token.Token)); + if (completedTask == wrapperTask) + { + token.Cancel(); + return await task; + } + throw new TimeoutException($"AwaitOnSignal: timed out after {timeoutMillis}ms."); + } + + public static async Task AwaitOnSignal(this GdUnit3.SceneRunner runner, string signal, params object[] args) + { + object[] signalArgs = await runner.Scene().ToSignal(runner.Scene(), signal); + if (signalArgs.SequenceEqual(args)) + return default; + return await AwaitOnSignal(runner, signal, args); + } + + public static async Task AwaitOnSignal(this Godot.Node node, string signal, params object[] args) + { + object[] signalArgs = await node.ToSignal(node, signal); + if (signalArgs.SequenceEqual(args)) + return default; + return await AwaitOnSignal(node, signal, args); + } + } +} namespace GdUnit3.Core { @@ -8,16 +45,12 @@ namespace GdUnit3.Core using Tools; internal sealed class SceneRunner : GdUnit3.SceneRunner { - private bool isSimulateRunnig; - private SceneTree SceneTree { get; set; } - private Node CurrentScene { get; set; } private bool Verbose { get; set; } private Vector2 CurrentMousePos { get; set; } private double TimeFactor { get; set; } private int SavedIterationsPerSecond { get; set; } - private bool IsSimulateRunnig { get; set; } public SceneRunner(string resourcePath, bool verbose = false) { @@ -29,7 +62,12 @@ public SceneRunner(string resourcePath, bool verbose = false) CurrentMousePos = default; SavedIterationsPerSecond = (int)ProjectSettings.GetSetting("physics/common/physics_fps"); SetTimeFactor(1.0); - IsSimulateRunnig = false; + } + + public GdUnit3.SceneRunner SetMousePos(Vector2 position) + { + CurrentScene.GetViewport().WarpMouse(position); + CurrentMousePos = position; return this; } public GdUnit3.SceneRunner SimulateKeyPress(KeyList key_code, bool shift = false, bool control = false) @@ -87,11 +125,13 @@ public GdUnit3.SceneRunner SimulateMouseButtonPressed(ButtonList buttonIndex) public GdUnit3.SceneRunner SimulateMouseButtonPress(ButtonList buttonIndex) { + PrintCurrentFocus(); var action = new InputEventMouseButton(); action.ButtonIndex = (int)buttonIndex; action.ButtonMask = (int)buttonIndex; action.Pressed = true; action.Position = CurrentMousePos; + action.GlobalPosition = CurrentMousePos; Print(" process mouse button event {0} ({1}) <- {2}", CurrentScene, SceneName(), action.AsText()); SceneTree.InputEvent(action); @@ -105,6 +145,7 @@ public GdUnit3.SceneRunner SimulateMouseButtonRelease(ButtonList buttonIndex) action.ButtonMask = 0; action.Pressed = false; action.Position = CurrentMousePos; + action.GlobalPosition = CurrentMousePos; Print(" process mouse button event {0} ({1}) <- {2}", CurrentScene, SceneName(), action.AsText()); SceneTree.InputEvent(action); @@ -123,10 +164,8 @@ public GdUnit3.SceneRunner SetTimeFactor(double timeFactor = 1.0) public async Task Simulate(int frames, long deltaPeerFrame) { DeactivateTimeFactor(); - IsSimulateRunnig = true; for (int frame = 0; frame < frames; frame++) await OnWait(deltaPeerFrame); - IsSimulateRunnig = false; return this; } @@ -134,11 +173,9 @@ public GdUnit3.SceneRunner SetTimeFactor(double timeFactor = 1.0) { var timeShiftFrames = Math.Max(1, frames / TimeFactor); ActivateTimeFactor(); - IsSimulateRunnig = true; for (int frame = 0; frame < frames; frame++) await OnIdleFrame(); DeactivateTimeFactor(); - IsSimulateRunnig = false; return this; } @@ -186,11 +223,32 @@ private string SceneName() public Node Scene() => CurrentScene; - public SignalAwaiter OnIdleFrame() => CurrentScene.ToSignal(CurrentScene.GetTree(), "idle_frame"); + public SignalAwaiter OnIdleFrame() => SceneTree.ToSignal(SceneTree, "idle_frame"); + + public SignalAwaiter OnWait(float timeSec) => SceneTree.ToSignal(SceneTree.CreateTimer(timeSec), "timeout"); + + public SignalAwaiter OnSignal(string signal) => SceneTree.ToSignal(CurrentScene, signal); + + public object Invoke(string name, params object[] args) + { + if (!CurrentScene.HasMethod(name)) + throw new MissingMethodException($"The method '{name}' not exist on loaded scene."); + return CurrentScene.Call(name, args); + } + + public T GetProperty(string name) + { + foreach (var element in CurrentScene.GetPropertyList()) + { + if (element.ToString().Contains($"name:{name}")) + return (T)CurrentScene.Get(name); + } + throw new MissingFieldException($"The property '{name}' not exist on loaded scene."); + } - public SignalAwaiter OnWait(float timeSec) => CurrentScene.ToSignal(CurrentScene.GetTree().CreateTimer(timeSec), "timeout"); + public Node FindNode(string name, bool recursive = true) => CurrentScene.FindNode(name, recursive, false); - public void ShowScene() + public void MoveWindowToForeground() { OS.WindowMaximized = true; OS.CenterWindow(); @@ -199,7 +257,7 @@ public void ShowScene() public void Dispose() { - Console.WriteLine("Dispose SceneRunner"); + Godot.GD.PrintS("Dispose Scene"); SceneTree.Root.RemoveChild(CurrentScene); CurrentScene.QueueFree(); } diff --git a/addons/gdUnit3/src/core/execution/ExecutionStage.cs b/addons/gdUnit3/src/core/execution/ExecutionStage.cs index a1584095..1fad85a5 100644 --- a/addons/gdUnit3/src/core/execution/ExecutionStage.cs +++ b/addons/gdUnit3/src/core/execution/ExecutionStage.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading; @@ -72,7 +73,10 @@ public virtual async Task Execute(ExecutionContext context) // unexpected exceptions Godot.GD.PushError(baseException.Message); Godot.GD.PushError(baseException.StackTrace); - context.ReportCollector.Consume(new TestReport(TestReport.TYPE.ABORT, -1, baseException.Message)); + + StackTrace stack = new StackTrace(baseException, true); + var lineNumber = stack.FrameCount > 1 ? stack.GetFrame(1).GetFileLineNumber() : -1; + context.ReportCollector.Consume(new TestReport(TestReport.TYPE.ABORT, lineNumber, baseException.Message)); } } catch (Exception e) @@ -80,7 +84,9 @@ public virtual async Task Execute(ExecutionContext context) // unexpected exceptions Godot.GD.PushError(e.Message); Godot.GD.PushError(e.StackTrace); - context.ReportCollector.Consume(new TestReport(TestReport.TYPE.ABORT, -1, e.Message)); + StackTrace stack = new StackTrace(e, true); + var lineNumber = stack.FrameCount > 1 ? stack.GetFrame(1).GetFileLineNumber() : -1; + context.ReportCollector.Consume(new TestReport(TestReport.TYPE.ABORT, lineNumber, e.Message)); } } diff --git a/addons/gdUnit3/test/core/SceneRunnerTest.cs b/addons/gdUnit3/test/core/SceneRunnerTest.cs index 6adb33e0..9cbbc3cf 100644 --- a/addons/gdUnit3/test/core/SceneRunnerTest.cs +++ b/addons/gdUnit3/test/core/SceneRunnerTest.cs @@ -2,31 +2,126 @@ namespace GdUnit3.Tests { - using GdUnit3.Asserts; - using Executions; - + using Godot; using static Assertions; [TestSuite] class SceneRunnerTest { + [TestCase] + public void GetProperty() + { + SceneRunner scene = SceneRunner.Load("res://addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn"); + AssertObject(scene.GetProperty("_box1")).IsInstanceOf(); + AssertThrown(() => scene.GetProperty("_invalid")) + .IsInstanceOf() + .HasMessage("The property '_invalid' not exist on loaded scene."); + } [TestCase] - public async Task RunScene() + public void InvokeSceneMethod() + { + SceneRunner scene = SceneRunner.Load("res://addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn"); + AssertString(scene.Invoke("add", 10, 12).ToString()).IsEqual("22"); + AssertThrown(() => scene.Invoke("sub", 12, 10)) + .IsInstanceOf() + .HasMessage("The method 'sub' not exist on loaded scene."); + } + + [TestCase(Description = "Example to test a scene with do a color cycle on box one each 500ms", Timeout = 4000)] + public async Task RunScene_ColorCycle() + { + SceneRunner scene = SceneRunner.Load("res://addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn"); + scene.MoveWindowToForeground(); + + var box1 = scene.GetProperty("_box1"); + // verify inital color + AssertObject(box1.Color).IsEqual(Colors.White); + + // start color cycle by invoke the function 'start_color_cycle' + scene.Invoke("start_color_cycle"); + + // await for each color cycle is emited + await scene.AwaitOnSignal("panel_color_change", box1, Colors.Red); + AssertObject(box1.Color).IsEqual(Colors.Red); + await scene.AwaitOnSignal("panel_color_change", box1, Colors.Blue); + AssertObject(box1.Color).IsEqual(Colors.Blue); + await scene.AwaitOnSignal("panel_color_change", box1, Colors.Green); + AssertObject(box1.Color).IsEqual(Colors.Green); + + // AwaitOnSignal must fail after an maximum timeout of 500ms because no signal 'panel_color_change' with given args color=Yellow is emited + await AssertThrown(scene.AwaitOnSignal("panel_color_change", box1, Colors.Yellow).WithTimeout(700)) + .ContinueWith(result => result.Result.IsInstanceOf().HasMessage("AwaitOnSignal: timed out after 700ms.")); + // verify the box is still green + AssertObject(box1.Color).IsEqual(Colors.Green); + } + + [TestCase(Description = "Example to simulate the enter key is pressed to shoot a spell", Timeout = 2000)] + public async Task RunScene_SimulateKeyPressed() + { + SceneRunner scene = SceneRunner.Load("res://addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn"); + + // inital no spell is fired + AssertObject(scene.FindNode("Spell")).IsNull(); + + // fire spell be pressing enter key + scene.SimulateKeyPressed(KeyList.Enter); + // wait until next frame + await scene.OnIdleFrame(); + + // verify a spell is created + AssertObject(scene.FindNode("Spell")).IsNotNull(); + + // wait until spell is explode after around 1s + var spell = scene.FindNode("Spell"); + await spell.AwaitOnSignal("spell_explode", spell).WithTimeout(1100); + + // verify spell is removed when is explode + AssertObject(scene.FindNode("Spell")).IsNull(); + } + + [TestCase(Description = "Example to simulate mouse pressed on buttons", Timeout = 2000)] + public async Task RunScene_SimulateMouseEvents() { + SceneRunner scene = SceneRunner.Load("res://addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn"); + scene.MoveWindowToForeground(); + + var box1 = scene.GetProperty("_box1"); + var box2 = scene.GetProperty("_box2"); + var box3 = scene.GetProperty("_box3"); - SceneRunner runner = SceneRunner.Load("res://addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn"); - runner.ShowScene(); - runner.SimulateKeyPressed(Godot.KeyList.Enter); + // verify inital colors + AssertObject(box1.Color).IsEqual(Colors.White); + AssertObject(box2.Color).IsEqual(Colors.White); + AssertObject(box3.Color).IsEqual(Colors.White); + // set mouse position to button one and simulate is pressed + scene.SetMousePos(new Vector2(60, 20)) + .SimulateMouseButtonPressed(ButtonList.Left); - runner.SimulateMouseMove(Godot.Vector2.One); - runner.Scene().Call("start_color_cycle"); + // wait until next frame + await scene.OnIdleFrame(); + // verify box one is changed to gray + AssertObject(box1.Color).IsEqual(Colors.Gray); + AssertObject(box2.Color).IsEqual(Colors.White); + AssertObject(box3.Color).IsEqual(Colors.White); - await runner.Scene().ToSignal(runner.Scene(), "panel_color_change"); + // set mouse position to button two and simulate is pressed + scene.SetMousePos(new Vector2(160, 20)) + .SimulateMouseButtonPressed(ButtonList.Left); + // verify box two is changed to gray + AssertObject(box1.Color).IsEqual(Colors.Gray); + AssertObject(box2.Color).IsEqual(Colors.Gray); + AssertObject(box3.Color).IsEqual(Colors.White); - //await runner.DoWaitSignal("panel_color_change", runner.Scene()._Get("_box1"), Godot.Colors.Red); - await runner.SimulateFrames(100); + // set mouse position to button three and simulate is pressed + scene.SetMousePos(new Vector2(260, 20)) + .SimulateMouseButtonPressed(ButtonList.Left); + // verify box three is changed to red and after around 1s to gray + AssertObject(box3.Color).IsEqual(Colors.Red); + await scene.AwaitOnSignal("panel_color_change", box3, Colors.Gray).WithTimeout(1100); + AssertObject(box3.Color).IsEqual(Colors.Gray); } } + } diff --git a/addons/gdUnit3/test/mocker/resources/scenes/TestScene.gd b/addons/gdUnit3/test/mocker/resources/scenes/TestScene.gd index f14db929..d6752f29 100644 --- a/addons/gdUnit3/test/mocker/resources/scenes/TestScene.gd +++ b/addons/gdUnit3/test/mocker/resources/scenes/TestScene.gd @@ -66,3 +66,6 @@ func _destroy_spell(spell :Spell) -> void: func _input(event): if event.is_action_released("ui_accept"): add_child(create_spell()) + +func add(a: int, b :int) -> int: + return a + b diff --git a/addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn b/addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn index af1e9307..722f7ebc 100644 --- a/addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn +++ b/addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn @@ -66,7 +66,6 @@ margin_bottom = -4.0 margin_right = 100.0 margin_bottom = 520.0 rect_min_size = Vector2( 100, 100 ) -color = Color( 0.694118, 0.207843, 0.207843, 1 ) [node name="Label" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer/Panel1"] anchor_right = 1.0 @@ -82,7 +81,6 @@ margin_left = 104.0 margin_right = 204.0 margin_bottom = 520.0 rect_min_size = Vector2( 100, 100 ) -color = Color( 0.219608, 0.662745, 0.380392, 1 ) [node name="Label" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer/Panel2"] anchor_right = 1.0 @@ -98,7 +96,6 @@ margin_left = 208.0 margin_right = 308.0 margin_bottom = 520.0 rect_min_size = Vector2( 100, 100 ) -color = Color( 0.12549, 0.286275, 0.776471, 1 ) [node name="Label" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer/Panel3"] anchor_right = 1.0 From 6c19e870ede27766e53f30862d240697fab02625 Mon Sep 17 00:00:00 2001 From: Mike Schulze Date: Mon, 14 Mar 2022 18:03:26 +0100 Subject: [PATCH 3/3] append --- addons/gdUnit3/src/SceneRunner.cs | 26 ++++----- addons/gdUnit3/src/core/SceneRunner.cs | 23 +++++--- addons/gdUnit3/test/core/SceneRunnerTest.cs | 61 ++++++++++++++++++++- 3 files changed, 85 insertions(+), 25 deletions(-) diff --git a/addons/gdUnit3/src/SceneRunner.cs b/addons/gdUnit3/src/SceneRunner.cs index 646972eb..53908209 100644 --- a/addons/gdUnit3/src/SceneRunner.cs +++ b/addons/gdUnit3/src/SceneRunner.cs @@ -97,15 +97,15 @@ public interface SceneRunner : IDisposable /// Simulates scene processing for a certain number of frames by given delta peer frame by ignoring the current time factor /// /// - /// 'Waits until given frames are rendered with a delta of 20 peer frame' + /// 'Waits until 100 frames are rendered with a delta of 20ms peer frame' /// await runner.SimulateFrames(100, 20); /// /// /// /// amount of frames to process - /// the time delta between a frame in ms + /// the time delta between a frame in milliseconds /// - Task Simulate(int frames, long deltaPeerFrame); + Task SimulateFrames(uint frames, uint deltaPeerFrame); /// /// Simulates scene processing for a certain number of frames. @@ -118,46 +118,46 @@ public interface SceneRunner : IDisposable /// /// amount of frames to process /// - Task SimulateFrames(int frames); + Task SimulateFrames(uint frames); /// - /// Waits until next frame is processed (idle_frame) + /// Waits until next frame is processed (signal idle_frame) /// /// /// 'Waits until next frame is processed' - /// await runner.OnIdleFrame(); + /// await runner.AwaitOnIdleFrame(); /// /// /// await OnIdleFrame(); /// /// SignalAwaiter - SignalAwaiter OnIdleFrame(); + SignalAwaiter AwaitOnIdleFrame(); /// /// Waits for given signal is emited. /// /// /// 'Waits for signal "mySignal"' - /// await runner.OnSignal("mySignal"); + /// await runner.AwaitOnSignal("mySignal"); /// /// /// /// The name of signal to wait /// SignalAwaiter - SignalAwaiter OnSignal(string signal); + SignalAwaiter AwaitOnSignal(string signal); /// - /// Waits for a specific amount of seconds. + /// Waits for a specific amount of milliseconds. /// /// /// 'Waits for two seconds' - /// await runner.OnWait(2.0); + /// await runner.AwaitOnMillis(2000); /// /// /// - /// Seconds to wait. 1.0 for one Second + /// Seconds to wait. 1.0 for one Second /// SignalAwaiter - SignalAwaiter OnWait(float timeSec); + Task AwaitOnMillis(uint timeMillis); /// /// Access to current running scene diff --git a/addons/gdUnit3/src/core/SceneRunner.cs b/addons/gdUnit3/src/core/SceneRunner.cs index 224591cb..45c19e79 100644 --- a/addons/gdUnit3/src/core/SceneRunner.cs +++ b/addons/gdUnit3/src/core/SceneRunner.cs @@ -161,20 +161,20 @@ public GdUnit3.SceneRunner SetTimeFactor(double timeFactor = 1.0) return this; } - public async Task Simulate(int frames, long deltaPeerFrame) + public async Task SimulateFrames(uint frames, uint deltaPeerFrame) { DeactivateTimeFactor(); for (int frame = 0; frame < frames; frame++) - await OnWait(deltaPeerFrame); + await AwaitOnMillis(deltaPeerFrame); return this; } - public async Task SimulateFrames(int frames) + public async Task SimulateFrames(uint frames) { var timeShiftFrames = Math.Max(1, frames / TimeFactor); ActivateTimeFactor(); for (int frame = 0; frame < frames; frame++) - await OnIdleFrame(); + await AwaitOnIdleFrame(); DeactivateTimeFactor(); return this; } @@ -223,11 +223,17 @@ private string SceneName() public Node Scene() => CurrentScene; - public SignalAwaiter OnIdleFrame() => SceneTree.ToSignal(SceneTree, "idle_frame"); + public SignalAwaiter AwaitOnIdleFrame() => SceneTree.ToSignal(SceneTree, "idle_frame"); - public SignalAwaiter OnWait(float timeSec) => SceneTree.ToSignal(SceneTree.CreateTimer(timeSec), "timeout"); + public async Task AwaitOnMillis(uint timeMillis) + { + using (var tokenSource = new CancellationTokenSource()) + { + await Task.Delay(System.TimeSpan.FromMilliseconds(timeMillis), tokenSource.Token); + } + } - public SignalAwaiter OnSignal(string signal) => SceneTree.ToSignal(CurrentScene, signal); + public SignalAwaiter AwaitOnSignal(string signal) => SceneTree.ToSignal(CurrentScene, signal); public object Invoke(string name, params object[] args) { @@ -257,9 +263,8 @@ public void MoveWindowToForeground() public void Dispose() { - Godot.GD.PrintS("Dispose Scene"); SceneTree.Root.RemoveChild(CurrentScene); CurrentScene.QueueFree(); } } -} \ No newline at end of file +} diff --git a/addons/gdUnit3/test/core/SceneRunnerTest.cs b/addons/gdUnit3/test/core/SceneRunnerTest.cs index 9cbbc3cf..f097e8d4 100644 --- a/addons/gdUnit3/test/core/SceneRunnerTest.cs +++ b/addons/gdUnit3/test/core/SceneRunnerTest.cs @@ -28,6 +28,61 @@ public void InvokeSceneMethod() .HasMessage("The method 'sub' not exist on loaded scene."); } + [TestCase] + public async Task AwaitForMilliseconds() + { + SceneRunner scene = SceneRunner.Load("res://addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn"); + System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); + stopwatch.Start(); + await scene.AwaitOnMillis(1000); + stopwatch.Stop(); + // verify we wait around 1000 ms (using 100ms offset because timing is not 100% accurate) + AssertInt((int)stopwatch.ElapsedMilliseconds).IsBetween(900, 1100); + } + + [TestCase] + public async Task SimulateFrames() + { + SceneRunner scene = SceneRunner.Load("res://addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn"); + System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); + + var box1 = scene.GetProperty("_box1"); + // initial is white + AssertObject(box1.Color).IsEqual(Colors.White); + + // start color cycle by invoke the function 'start_color_cycle' + scene.Invoke("start_color_cycle"); + + // we wait for 10 frames + await scene.SimulateFrames(10); + // after 10 frame is still white + AssertObject(box1.Color).IsEqual(Colors.White); + + // we wait 90 more frames + await scene.SimulateFrames(90); + // after 100 frames the box one should be changed to red + AssertObject(box1.Color).IsEqual(Colors.Red); + } + + [TestCase] + public async Task SimulateFramesWithDelay() + { + SceneRunner scene = SceneRunner.Load("res://addons/gdUnit3/test/mocker/resources/scenes/TestScene.tscn"); + System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); + + var box1 = scene.GetProperty("_box1"); + // initial is white + AssertObject(box1.Color).IsEqual(Colors.White); + + // start color cycle by invoke the function 'start_color_cycle' + scene.Invoke("start_color_cycle"); + + // we wait for 10 frames each with a 50ms delay + await scene.SimulateFrames(10, 50); + // after 10 frame and in sum 500ms is should be changed to red + AssertObject(box1.Color).IsEqual(Colors.Red); + } + [TestCase(Description = "Example to test a scene with do a color cycle on box one each 500ms", Timeout = 4000)] public async Task RunScene_ColorCycle() { @@ -38,7 +93,7 @@ public async Task RunScene_ColorCycle() // verify inital color AssertObject(box1.Color).IsEqual(Colors.White); - // start color cycle by invoke the function 'start_color_cycle' + // start color cycle by invoke the function 'start_color_cycle' scene.Invoke("start_color_cycle"); // await for each color cycle is emited @@ -67,7 +122,7 @@ public async Task RunScene_SimulateKeyPressed() // fire spell be pressing enter key scene.SimulateKeyPressed(KeyList.Enter); // wait until next frame - await scene.OnIdleFrame(); + await scene.AwaitOnIdleFrame(); // verify a spell is created AssertObject(scene.FindNode("Spell")).IsNotNull(); @@ -100,7 +155,7 @@ public async Task RunScene_SimulateMouseEvents() .SimulateMouseButtonPressed(ButtonList.Left); // wait until next frame - await scene.OnIdleFrame(); + await scene.AwaitOnIdleFrame(); // verify box one is changed to gray AssertObject(box1.Color).IsEqual(Colors.Gray); AssertObject(box2.Color).IsEqual(Colors.White);