Sfoglia il codice sorgente

Fixed Scenarios and unit tests

Tig 1 anno fa
parent
commit
9f09e030ae

+ 13 - 8
Terminal.Gui/Application/Application.cs

@@ -76,7 +76,7 @@ public static partial class Application
     // this in a function like this ensures we don't make mistakes in
     // this in a function like this ensures we don't make mistakes in
     // guaranteeing that the state of this singleton is deterministic when Init
     // guaranteeing that the state of this singleton is deterministic when Init
     // starts running and after Shutdown returns.
     // starts running and after Shutdown returns.
-    internal static void ResetState ()
+    internal static void ResetState (bool ignoreDisposed = false)
     {
     {
         // Shutdown is the bookend for Init. As such it needs to clean up all resources
         // Shutdown is the bookend for Init. As such it needs to clean up all resources
         // Init created. Apps that do any threading will need to code defensively for this.
         // Init created. Apps that do any threading will need to code defensively for this.
@@ -84,11 +84,6 @@ public static partial class Application
         foreach (Toplevel t in _topLevels)
         foreach (Toplevel t in _topLevels)
         {
         {
             t.Running = false;
             t.Running = false;
-#if DEBUG_IDISPOSABLE
-
-            // Don't dispose the toplevels. It's up to caller dispose them
-            //Debug.Assert (t.WasDisposed);
-#endif
         }
         }
 
 
         _topLevels.Clear ();
         _topLevels.Clear ();
@@ -96,7 +91,7 @@ public static partial class Application
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
 
 
         // Don't dispose the Top. It's up to caller dispose it
         // Don't dispose the Top. It's up to caller dispose it
-        if (Top is { })
+        if (!ignoreDisposed && Top is { })
         {
         {
             Debug.Assert (Top.WasDisposed);
             Debug.Assert (Top.WasDisposed);
 
 
@@ -313,6 +308,7 @@ public static partial class Application
         SupportedCultures = GetSupportedCultures ();
         SupportedCultures = GetSupportedCultures ();
         _mainThreadId = Thread.CurrentThread.ManagedThreadId;
         _mainThreadId = Thread.CurrentThread.ManagedThreadId;
         _initialized = true;
         _initialized = true;
+        InitializedChanged?.Invoke (null, new (false, _initialized));
     }
     }
 
 
     private static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
     private static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
@@ -353,8 +349,17 @@ public static partial class Application
         // TODO: Throw an exception if Init hasn't been called.
         // TODO: Throw an exception if Init hasn't been called.
         ResetState ();
         ResetState ();
         PrintJsonErrors ();
         PrintJsonErrors ();
+        InitializedChanged?.Invoke (null, new (true, _initialized));
     }
     }
 
 
+    /// <summary>
+    ///     This event is fired after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
+    /// </summary>
+    /// <remarks>
+    ///     Intended to support unit tests that need to know when the application has been initialized.
+    /// </remarks>
+    public static event EventHandler<StateEventArgs<bool>> InitializedChanged;
+
     #endregion Initialization (Init/Shutdown)
     #endregion Initialization (Init/Shutdown)
 
 
     #region Run (Begin, Run, End, Stop)
     #region Run (Begin, Run, End, Stop)
@@ -676,7 +681,7 @@ public static partial class Application
     /// </param>
     /// </param>
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     public static T Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null)
     public static T Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null)
-        where T : Toplevel, new ()
+        where T : Toplevel, new()
     {
     {
         if (!_initialized)
         if (!_initialized)
         {
         {

+ 1 - 6
Terminal.Gui/View/Layout/Dim.cs

@@ -110,14 +110,9 @@ public abstract class Dim
     ///     Specifies how <see cref="Dim.Auto"/> will compute the dimension. The default is <see cref="DimAutoStyle.Auto"/>.
     ///     Specifies how <see cref="Dim.Auto"/> will compute the dimension. The default is <see cref="DimAutoStyle.Auto"/>.
     /// </param>
     /// </param>
     /// <param name="minimumContentDim">The minimum dimension the View's ContentSize will be constrained to.</param>
     /// <param name="minimumContentDim">The minimum dimension the View's ContentSize will be constrained to.</param>
-    /// <param name="maximumContentDim">The maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED.</param>
+    /// <param name="maximumContentDim">The maximum dimension the View's ContentSize will be fit to.</param>
     public static Dim? Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim? minimumContentDim = null, Dim? maximumContentDim = null)
     public static Dim? Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim? minimumContentDim = null, Dim? maximumContentDim = null)
     {
     {
-        if (maximumContentDim is { })
-        {
-            Debug.WriteLine (@"WARNING: maximumContentDim is not fully implemented.");
-        }
-
         return new DimAuto ()
         return new DimAuto ()
         {
         {
             MinimumContentDim = minimumContentDim,
             MinimumContentDim = minimumContentDim,

+ 1 - 1
Terminal.Gui/View/ViewSubViews.cs

@@ -528,7 +528,7 @@ public partial class View
     // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews.
     // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews.
     /// <summary>Returns the currently focused Subview inside this view, or null if nothing is focused.</summary>
     /// <summary>Returns the currently focused Subview inside this view, or null if nothing is focused.</summary>
     /// <value>The focused.</value>
     /// <value>The focused.</value>
-    public View Focused { get;  set; }
+    public View Focused { get; private set; }
 
 
     // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews.
     // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews.
     /// <summary>Returns the most focused Subview in the chain of subviews (the leaf view that has the focus).</summary>
     /// <summary>Returns the most focused Subview in the chain of subviews (the leaf view that has the focus).</summary>

+ 2 - 0
UICatalog/Scenario.cs

@@ -188,6 +188,8 @@ public class Scenario : IDisposable
     {
     {
         // Must explicitly call Application.Shutdown method to shutdown.
         // Must explicitly call Application.Shutdown method to shutdown.
         Application.Run (Top);
         Application.Run (Top);
+        Top.Dispose ();
+        Application.Shutdown ();
     }
     }
 
 
     /// <summary>Override this to implement the <see cref="Scenario"/> setup logic (create controls, etc...).</summary>
     /// <summary>Override this to implement the <see cref="Scenario"/> setup logic (create controls, etc...).</summary>

+ 2 - 0
UICatalog/Scenarios/ASCIICustomButton.cs

@@ -59,6 +59,8 @@ public class ASCIICustomButtonTest : Scenario
         Application.Run (top);
         Application.Run (top);
         top.Dispose ();
         top.Dispose ();
 
 
+        Application.Shutdown ();
+
         return;
         return;
 
 
         void ChangeWindowSize ()
         void ChangeWindowSize ()

+ 1 - 0
UICatalog/Scenarios/BasicColors.cs

@@ -111,5 +111,6 @@ public class BasicColors : Scenario
 
 
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
+        Application.Shutdown ();
     }
     }
 }
 }

+ 1 - 0
UICatalog/Scenarios/Buttons.cs

@@ -396,6 +396,7 @@ public class Buttons : Scenario
         main.Ready += (s, e) => radioGroup.Refresh ();
         main.Ready += (s, e) => radioGroup.Refresh ();
         Application.Run (main);
         Application.Run (main);
         main.Dispose ();
         main.Dispose ();
+        Application.Shutdown ();
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 1 - 0
UICatalog/Scenarios/CharacterMap.cs

@@ -180,6 +180,7 @@ public class CharacterMap : Scenario
 
 
         Application.Run (top);
         Application.Run (top);
         top.Dispose ();
         top.Dispose ();
+        Application.Shutdown ();
     }
     }
 
 
     private void _categoryList_Initialized (object sender, EventArgs e) { _charMap.Width = Dim.Fill () - _categoryList.Width; }
     private void _categoryList_Initialized (object sender, EventArgs e) { _charMap.Width = Dim.Fill () - _categoryList.Width; }

+ 1 - 0
UICatalog/Scenarios/ColorPicker.cs

@@ -86,6 +86,7 @@ public class ColorPickers : Scenario
 
 
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
+        Application.Shutdown ();
     }
     }
 
 
     /// <summary>Fired when background color is changed.</summary>
     /// <summary>Fired when background color is changed.</summary>

+ 5 - 0
UICatalog/Scenarios/ComputedLayout.cs

@@ -53,6 +53,10 @@ public class ComputedLayout : Scenario
 
 
         app.LayoutComplete += (s, a) =>
         app.LayoutComplete += (s, a) =>
                                           {
                                           {
+                                              if (horizontalRuler.Viewport.Width == 0 || horizontalRuler.Viewport.Height == 0)
+                                              {
+                                                  return;
+                                              }
                                               horizontalRuler.Text =
                                               horizontalRuler.Text =
                                                   rule.Repeat ((int)Math.Ceiling (horizontalRuler.Viewport.Width / (double)rule.Length)) [
                                                   rule.Repeat ((int)Math.Ceiling (horizontalRuler.Viewport.Width / (double)rule.Length)) [
                                                    ..horizontalRuler.Viewport.Width];
                                                    ..horizontalRuler.Viewport.Width];
@@ -409,5 +413,6 @@ public class ComputedLayout : Scenario
 
 
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
+        Application.Shutdown ();
     }
     }
 }
 }

+ 3 - 2
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -16,7 +16,8 @@ public class DynamicStatusBar : Scenario
     public override void Main ()
     public override void Main ()
     {
     {
 
 
-        Application.Run<DynamicStatusBarSample> ().Dispose();
+        Application.Run<DynamicStatusBarSample> ().Dispose ();
+        Application.Shutdown ();
     }
     }
 
 
     public class Binding
     public class Binding
@@ -165,7 +166,7 @@ public class DynamicStatusBar : Scenario
                     return true;
                     return true;
                 }
                 }
 
 
-                TextShortcut.Text = k.ToString();
+                TextShortcut.Text = k.ToString ();
                 return true;
                 return true;
             }
             }
 
 

+ 1 - 0
UICatalog/Scenarios/HexEditor.cs

@@ -21,6 +21,7 @@ public class HexEditor : Scenario
 
 
     public override void Main ()
     public override void Main ()
     {
     {
+        Application.Init ();
         Toplevel app = new Toplevel ()
         Toplevel app = new Toplevel ()
         {
         {
             ColorScheme = Colors.ColorSchemes ["Base"]
             ColorScheme = Colors.ColorSchemes ["Base"]

+ 1 - 0
UICatalog/Scenarios/HotKeys.cs

@@ -123,5 +123,6 @@ public class HotKeys : Scenario
 
 
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
+        Application.Shutdown ();
     }
     }
 }
 }

+ 1 - 0
UICatalog/Scenarios/InteractiveTree.cs

@@ -12,6 +12,7 @@ public class InteractiveTree : Scenario
 
 
     public override void Main ()
     public override void Main ()
     {
     {
+        Application.Init ();
         var appWindow = new Toplevel ()
         var appWindow = new Toplevel ()
         {
         {
             Title = GetName (),
             Title = GetName (),

+ 1 - 0
UICatalog/Scenarios/LineCanvasExperiment.cs

@@ -139,5 +139,6 @@ public class LineCanvasExperiment : Scenario
 
 
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
+        Application.Shutdown ();
     }
     }
 }
 }

+ 4 - 3
UICatalog/Scenarios/Mouse.cs

@@ -18,7 +18,7 @@ public class Mouse : Scenario
             Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"
             Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"
         };
         };
 
 
-        Slider<MouseFlags> filterSlider = new()
+        Slider<MouseFlags> filterSlider = new ()
         {
         {
             Title = "_Filter",
             Title = "_Filter",
             X = 0,
             X = 0,
@@ -57,7 +57,7 @@ public class Mouse : Scenario
         win.Add (clearButton);
         win.Add (clearButton);
         Label ml;
         Label ml;
         var count = 0;
         var count = 0;
-        ml = new() { X = Pos.Right (filterSlider), Y = 0, Text = "Mouse: " };
+        ml = new () { X = Pos.Right (filterSlider), Y = 0, Text = "Mouse: " };
 
 
         win.Add (ml);
         win.Add (ml);
 
 
@@ -138,7 +138,7 @@ public class Mouse : Scenario
                                       }
                                       }
                                   };
                                   };
 
 
-        label = new()
+        label = new ()
         {
         {
             Text = "_Window Events:",
             Text = "_Window Events:",
             X = Pos.Right (appLog) + 1,
             X = Pos.Right (appLog) + 1,
@@ -184,6 +184,7 @@ public class Mouse : Scenario
 
 
         Application.Run (win);
         Application.Run (win);
         win.Dispose ();
         win.Dispose ();
+        Application.Shutdown ();
     }
     }
 
 
     public class MouseDemo : View
     public class MouseDemo : View

+ 2 - 3
UICatalog/Scenarios/RunTExample.cs

@@ -9,9 +9,8 @@ public class RunTExample : Scenario
     public override void Main ()
     public override void Main ()
     {
     {
         // No need to call Init if Application.Run<T> is used
         // No need to call Init if Application.Run<T> is used
-        Application.Run<ExampleWindow> ();
-
-        Application.Top.Dispose ();
+        Application.Run<ExampleWindow> ().Dispose ();
+        Application.Shutdown ();
     }
     }
 
 
     public class ExampleWindow : Window
     public class ExampleWindow : Window

+ 6 - 5
UICatalog/Scenarios/RuneWidthGreaterThanOne.cs

@@ -17,11 +17,11 @@ public class RuneWidthGreaterThanOne : Scenario
     private TextField _text;
     private TextField _text;
     private Window _win;
     private Window _win;
 
 
-    public override void Init ()
+    public override void Main ()
     {
     {
         Application.Init ();
         Application.Init ();
 
 
-        Top = new ();
+        Toplevel topLevel = new ();
 
 
         var menu = new MenuBar
         var menu = new MenuBar
         {
         {
@@ -87,16 +87,17 @@ public class RuneWidthGreaterThanOne : Scenario
         };
         };
         _win = new Window { X = 5, Y = 5, Width = Dim.Fill (22), Height = Dim.Fill (5) };
         _win = new Window { X = 5, Y = 5, Width = Dim.Fill (22), Height = Dim.Fill (5) };
         _win.Add (_label, _text, _button, _labelR, _labelV);
         _win.Add (_label, _text, _button, _labelR, _labelV);
-        Top.Add (menu, _win);
+        topLevel.Add (menu, _win);
 
 
         WideRunes ();
         WideRunes ();
 
 
         //NarrowRunes ();
         //NarrowRunes ();
         //MixedRunes ();
         //MixedRunes ();
-        Application.Run (Top);
+        Application.Run (topLevel);
+        topLevel.Dispose ();
+        Application.Shutdown ();
     }
     }
 
 
-    public override void Run () { }
     private void MixedMessage (object sender, EventArgs e) { MessageBox.Query ("Say Hello 你", $"Hello {_text.Text}", "Ok"); }
     private void MixedMessage (object sender, EventArgs e) { MessageBox.Query ("Say Hello 你", $"Hello {_text.Text}", "Ok"); }
 
 
     private void MixedRunes ()
     private void MixedRunes ()

+ 1 - 0
UICatalog/Scenarios/Scrolling.cs

@@ -247,6 +247,7 @@ public class Scrolling : Scenario
         app.Loaded -= App_Loaded;
         app.Loaded -= App_Loaded;
         app.Unloaded -= app_Unloaded;
         app.Unloaded -= app_Unloaded;
         app.Dispose ();
         app.Dispose ();
+        Application.Shutdown ();
 
 
         return;
         return;
 
 

+ 1 - 0
UICatalog/Scenarios/SingleBackgroundWorker.cs

@@ -15,6 +15,7 @@ public class SingleBackgroundWorker : Scenario
     public override void Main ()
     public override void Main ()
     {
     {
         Application.Run<MainApp> ().Dispose ();
         Application.Run<MainApp> ().Dispose ();
+        Application.Shutdown ();
     }
     }
 
 
     public class MainApp : Toplevel
     public class MainApp : Toplevel

+ 1 - 0
UICatalog/Scenarios/TextAlignmentAndDirection.cs

@@ -592,6 +592,7 @@ public class TextAlignmentAndDirection : Scenario
 
 
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
+        Application.Shutdown ();
 
 
         void ToggleJustify (bool oldValue, bool wasJustOptions = false)
         void ToggleJustify (bool oldValue, bool wasJustOptions = false)
         {
         {

+ 1 - 0
UICatalog/Scenarios/TextFormatterDemo.cs

@@ -144,5 +144,6 @@ public class TextFormatterDemo : Scenario
 
 
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
+        Application.Shutdown ();
     }
     }
 }
 }

+ 2 - 0
UICatalog/Scenarios/TrueColors.cs

@@ -125,6 +125,8 @@ public class TrueColors : Scenario
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
 
 
+        Application.Shutdown ();
+
         return;
         return;
 
 
         void SetupGradient (string name, int x, ref int y, Func<int, Color> colorFunc)
         void SetupGradient (string name, int x, ref int y, Func<int, Color> colorFunc)

+ 1 - 0
UICatalog/Scenarios/ViewExperiments.cs

@@ -248,5 +248,6 @@ public class ViewExperiments : Scenario
 
 
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
+        Application.Shutdown ();
     }
     }
 }
 }

+ 4 - 0
UICatalog/Scenarios/VkeyPacketSimulator.cs

@@ -260,6 +260,10 @@ public class VkeyPacketSimulator : Scenario
 
 
         void Win_LayoutComplete (object sender, LayoutEventArgs obj)
         void Win_LayoutComplete (object sender, LayoutEventArgs obj)
         {
         {
+            if (inputHorizontalRuler.Viewport.Width == 0 || inputVerticalRuler.Viewport.Height == 0)
+            {
+                return;
+            }
             inputHorizontalRuler.Text = outputHorizontalRuler.Text =
             inputHorizontalRuler.Text = outputHorizontalRuler.Text =
                                             ruler.Repeat (
                                             ruler.Repeat (
                                                           (int)Math.Ceiling (
                                                           (int)Math.Ceiling (

+ 1 - 0
UICatalog/Scenarios/WindowsAndFrameViews.cs

@@ -207,5 +207,6 @@ public class WindowsAndFrameViews : Scenario
 
 
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
+        Application.Shutdown ();
     }
     }
 }
 }

+ 8 - 11
UICatalog/Scenarios/WizardAsView.cs

@@ -6,7 +6,7 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Wizards")]
 [ScenarioCategory ("Wizards")]
 public class WizardAsView : Scenario
 public class WizardAsView : Scenario
 {
 {
-    public override void Init ()
+    public override void Main ()
     {
     {
         Application.Init ();
         Application.Init ();
 
 
@@ -52,8 +52,9 @@ public class WizardAsView : Scenario
                                 )
                                 )
             ]
             ]
         };
         };
-        Top = new ();
-        Top.Add (menu);
+
+        Toplevel topLevel = new ();
+        topLevel.Add (menu);
 
 
         // No need for a Title because the border is disabled
         // No need for a Title because the border is disabled
         var wizard = new Wizard { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
         var wizard = new Wizard { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
@@ -139,13 +140,9 @@ public class WizardAsView : Scenario
         lastStep.HelpText =
         lastStep.HelpText =
             "The wizard is complete!\n\nPress the Finish button to continue.\n\nPressing Esc will cancel.";
             "The wizard is complete!\n\nPress the Finish button to continue.\n\nPressing Esc will cancel.";
 
 
-        Top.Add (wizard);
-        Application.Run (Top);
-    }
-
-    public override void Run ()
-    {
-        // Do nothing in the override because we call Application.Run above
-        // (just to make it clear how the Top is being run and not the Wizard).
+        topLevel.Add (wizard);
+        Application.Run (topLevel);
+        topLevel.Dispose ();
+        Application.Shutdown ();
     }
     }
 }
 }

+ 51 - 39
UnitTests/UICatalog/ScenarioTests.cs

@@ -30,63 +30,75 @@ public class ScenarioTests : TestsAllViews
     /// </summary>
     /// </summary>
     [Theory]
     [Theory]
     [MemberData (nameof (AllScenarioTypes))]
     [MemberData (nameof (AllScenarioTypes))]
-    public void Run_All_Scenarios (Type scenarioType)
+    public void All_Scenarios_Quit_And_Init_Shutdown_Properly (Type scenarioType)
     {
     {
+        Application.ResetState (true);
+
         _output.WriteLine ($"Running Scenario '{scenarioType}'");
         _output.WriteLine ($"Running Scenario '{scenarioType}'");
 
 
         Scenario scenario = (Scenario)Activator.CreateInstance (scenarioType);
         Scenario scenario = (Scenario)Activator.CreateInstance (scenarioType);
 
 
-        // BUGBUG: Scenario.Main is supposed to call Init. This is a workaround for now.
-        // BUGBUG: To be able to test Scenarios we need Application to have an event like "Application.Started"
-        // BUGBUG: That tests like this could subscribe to.
-        Application.Init (new FakeDriver ());
+        uint abortTime = 500;
 
 
-        // Press QuitKey 
-        Assert.Empty (FakeConsole.MockKeyPresses);
+        bool initialized = false;
+        bool shutdown = false;
 
 
-        FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey);
+        object timeout = null;
 
 
-        uint abortTime = 500;
+        Application.InitializedChanged += (s, a) =>
+                                          {
+                                              if (a.NewValue)
+                                              {
+                                                  //output.WriteLine ($"  Add timeout to force quit after {abortTime}ms");
+                                                  timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback);
+
+                                                  Application.Iteration += OnApplicationOnIteration;
+                                                  initialized = true;
+                                              }
+                                              else
+                                              {
+                                                  if (timeout is { })
+                                                  {
+                                                      Application.RemoveTimeout (timeout);
+                                                      timeout = null;
+                                                  }
+                                                  Application.Iteration -= OnApplicationOnIteration;
+                                                  shutdown = true;
+                                              }
+                                          };
+
+        scenario.Main ();
+        scenario.Dispose ();
+
+        Assert.True (initialized);
+        Assert.True (shutdown);
+
+#if DEBUG_IDISPOSABLE
+        Assert.Empty (Responder.Instances);
+#endif
+
+        return;
 
 
         // If the scenario doesn't close within 500ms, this will force it to quit
         // If the scenario doesn't close within 500ms, this will force it to quit
         bool ForceCloseCallback ()
         bool ForceCloseCallback ()
         {
         {
-            if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0)
+            if (timeout is { })
             {
             {
-                Application.RequestStop ();
-
-                // See #2474 for why this is commented out
-                Assert.Fail (
-                             $"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey} after {abortTime}ms. Force quit.");
+                Application.RemoveTimeout (timeout);
+                timeout = null;
             }
             }
+            Application.ResetState (true);
+            Assert.Fail (
+                         $"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey} after {abortTime}ms. Force quit.");
 
 
             return false;
             return false;
         }
         }
 
 
-        //output.WriteLine ($"  Add timeout to force quit after {abortTime}ms");
-        _ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback);
-
-        Application.Iteration += (s, a) =>
-                                 {
-                                     // Press QuitKey 
-                                     Assert.Empty (FakeConsole.MockKeyPresses);
-                                     FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey);
-
-                                     //output.WriteLine ($"  iteration {++iterations}");
-                                     if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0)
-                                     {
-                                         Application.RequestStop ();
-                                         Assert.Fail ($"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey}. Force quit.");
-                                     }
-                                 };
-
-        scenario.Main ();
-        scenario.Dispose ();
-
-        Application.Shutdown ();
-#if DEBUG_IDISPOSABLE
-        Assert.Empty (Responder.Instances);
-#endif
+        void OnApplicationOnIteration (object s, IterationEventArgs a)
+        {
+            // Press QuitKey 
+            Application.OnKeyDown (Application.QuitKey);
+        }
     }
     }
 
 
     [Fact]
     [Fact]