Przeglądaj źródła

Merge pull request #2206 from tig/fixes_2205_fakeclipboard

Fixes #2205. Reduce fragility of clipboard related unit tests
Tig 2 lat temu
rodzic
commit
0d183c2437
42 zmienionych plików z 1633 dodań i 826 usunięć
  1. 2 1
      .github/workflows/dotnet-core.yml
  2. 566 0
      Terminal.Gui UnitTests/ScenarioTests.cs
  3. 57 0
      Terminal.Gui UnitTests/UnitTests.csproj
  4. 96 166
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  5. 5 0
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
  6. 71 8
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  7. 5 11
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs
  8. 27 11
      Terminal.Gui/Core/Application.cs
  9. 38 11
      Terminal.Gui/Core/Clipboard/Clipboard.cs
  10. 25 20
      Terminal.Gui/Core/Clipboard/ClipboardBase.cs
  11. 73 0
      Terminal.Gui/Core/ConsoleDriver.cs
  12. 2 1
      Terminal.Gui/Core/MainLoop.cs
  13. 4 4
      Terminal.Gui/Terminal.Gui.csproj
  14. 9 0
      Terminal.Gui/Windows/FileDialog.cs
  15. 13 15
      UICatalog/Properties/launchSettings.json
  16. 1 1
      UICatalog/Scenarios/Editor.cs
  17. 7 6
      UICatalog/UICatalog.cs
  18. 2 1
      UICatalog/UICatalog.csproj
  19. 2 2
      UnitTests/AllViewsTests.cs
  20. 14 11
      UnitTests/ApplicationTests.cs
  21. 4 4
      UnitTests/AttributeTests.cs
  22. 165 189
      UnitTests/ClipboardTests.cs
  23. 70 45
      UnitTests/ConsoleDriverTests.cs
  24. 11 0
      UnitTests/DialogTests.cs
  25. 10 10
      UnitTests/DimTests.cs
  26. 2 2
      UnitTests/GraphViewTests.cs
  27. 23 18
      UnitTests/MainLoopTests.cs
  28. 6 3
      UnitTests/MdiTests.cs
  29. 8 8
      UnitTests/PosTests.cs
  30. 9 2
      UnitTests/RunStateTests.cs
  31. 3 3
      UnitTests/ScenarioTests.cs
  32. 21 30
      UnitTests/ScrollBarViewTests.cs
  33. 1 1
      UnitTests/StatusBarTests.cs
  34. 1 1
      UnitTests/TabViewTests.cs
  35. 50 11
      UnitTests/TestHelpers.cs
  36. 129 129
      UnitTests/TextFieldTests.cs
  37. 2 2
      UnitTests/TextFormatterTests.cs
  38. 82 85
      UnitTests/TextViewTests.cs
  39. 2 2
      UnitTests/ToplevelTests.cs
  40. 1 1
      UnitTests/TreeViewTests.cs
  41. 4 1
      UnitTests/UnitTests.csproj
  42. 10 10
      UnitTests/ViewTests.cs

+ 2 - 1
.github/workflows/dotnet-core.yml

@@ -20,7 +20,8 @@ jobs:
         dotnet-version: 6.0.100
 
     - name: Install dependencies
-      run: dotnet restore
+      run: |
+        dotnet restore
 
     - name: Build Debug
       run: dotnet build --configuration Debug --no-restore

+ 566 - 0
Terminal.Gui UnitTests/ScenarioTests.cs

@@ -0,0 +1,566 @@
+using NStack;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Terminal.Gui;
+using UICatalog;
+using Xunit;
+using Xunit.Abstractions;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace UICatalog {
+	public class ScenarioTests {
+		readonly ITestOutputHelper output;
+
+		public ScenarioTests (ITestOutputHelper output)
+		{
+#if DEBUG_IDISPOSABLE
+			Responder.Instances.Clear ();
+#endif
+			this.output = output;
+		}
+
+		int CreateInput (string input)
+		{
+			// Put a control-q in at the end
+			FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo ('q', ConsoleKey.Q, shift: false, alt: false, control: true));
+			foreach (var c in input.Reverse ()) {
+				if (char.IsLetter (c)) {
+					FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (char.ToLower (c), (ConsoleKey)char.ToUpper (c), shift: char.IsUpper (c), alt: false, control: false));
+				} else {
+					FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)c, shift: false, alt: false, control: false));
+				}
+			}
+			return FakeConsole.MockKeyPresses.Count;
+		}
+
+
+		/// <summary>
+		/// <para>
+		/// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run.
+		/// </para>
+		/// <para>
+		/// Should find any Scenarios which crash on load or do not respond to <see cref="Application.RequestStop()"/>.
+		/// </para>
+		/// </summary>
+		[Fact]
+		public void Run_All_Scenarios ()
+		{
+			List<Scenario> scenarios = Scenario.GetScenarios ();
+			Assert.NotEmpty (scenarios);
+
+			foreach (var scenario in scenarios) {
+
+				output.WriteLine ($"Running Scenario '{scenario}'");
+
+				Func<MainLoop, bool> closeCallback = (MainLoop loop) => {
+					Application.RequestStop ();
+					return false;
+				};
+
+				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+				// Close after a short period of time
+				var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), closeCallback);
+
+				scenario.Init (Colors.Base);
+				scenario.Setup ();
+				scenario.Run ();
+				Application.Shutdown ();
+#if DEBUG_IDISPOSABLE
+				foreach (var inst in Responder.Instances) {
+					Assert.True (inst.WasDisposed);
+				}
+				Responder.Instances.Clear ();
+#endif
+			}
+#if DEBUG_IDISPOSABLE
+			foreach (var inst in Responder.Instances) {
+				Assert.True (inst.WasDisposed);
+			}
+			Responder.Instances.Clear ();
+#endif
+		}
+
+		[Fact]
+		public void Run_Generic ()
+		{
+			List<Scenario> scenarios = Scenario.GetScenarios ();
+			Assert.NotEmpty (scenarios);
+
+			var item = scenarios.FindIndex (s => s.GetName ().Equals ("Generic", StringComparison.OrdinalIgnoreCase));
+			var generic = scenarios [item];
+			// Setup some fake keypresses 
+			// Passing empty string will cause just a ctrl-q to be fired
+			int stackSize = CreateInput ("");
+
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			int iterations = 0;
+			Application.Iteration = () => {
+				iterations++;
+				// Stop if we run out of control...
+				if (iterations == 10) {
+					Application.RequestStop ();
+				}
+			};
+
+			var ms = 1000;
+			var abortCount = 0;
+			Func<MainLoop, bool> abortCallback = (MainLoop loop) => {
+				abortCount++;
+				Application.RequestStop ();
+				return false;
+			};
+			var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback);
+
+			Application.Top.KeyPress += (View.KeyEventEventArgs args) => {
+				Assert.Equal (Key.CtrlMask | Key.Q, args.KeyEvent.Key);
+			};
+
+			generic.Init (Colors.Base);
+			generic.Setup ();
+			// There is no need to call Application.Begin because Init already creates the Application.Top
+			// If Application.RunState is used then the Application.RunLoop must also be used instead Application.Run.
+			//var rs = Application.Begin (Application.Top);
+			generic.Run ();
+
+			//Application.End (rs);
+
+			Assert.Equal (0, abortCount);
+			// # of key up events should match # of iterations
+			Assert.Equal (1, iterations);
+			// Using variable in the left side of Assert.Equal/NotEqual give error. Must be used literals values.
+			//Assert.Equal (stackSize, iterations);
+
+			// Shutdown must be called to safely clean up Application if Init has been called
+			Application.Shutdown ();
+
+#if DEBUG_IDISPOSABLE
+			foreach (var inst in Responder.Instances) {
+				Assert.True (inst.WasDisposed);
+			}
+			Responder.Instances.Clear ();
+#endif
+		}
+
+		[Fact]
+		public void Run_All_Views_Tester_Scenario ()
+		{
+			Window _leftPane;
+			ListView _classListView;
+			FrameView _hostPane;
+
+			Dictionary<string, Type> _viewClasses;
+			View _curView = null;
+
+			// Settings
+			FrameView _settingsPane;
+			CheckBox _computedCheckBox;
+			FrameView _locationFrame;
+			RadioGroup _xRadioGroup;
+			TextField _xText;
+			int _xVal = 0;
+			RadioGroup _yRadioGroup;
+			TextField _yText;
+			int _yVal = 0;
+
+			FrameView _sizeFrame;
+			RadioGroup _wRadioGroup;
+			TextField _wText;
+			int _wVal = 0;
+			RadioGroup _hRadioGroup;
+			TextField _hText;
+			int _hVal = 0;
+			List<string> posNames = new List<String> { "Factor", "AnchorEnd", "Center", "Absolute" };
+			List<string> dimNames = new List<String> { "Factor", "Fill", "Absolute" };
+
+
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var Top = Application.Top;
+
+			_viewClasses = GetAllViewClassesCollection ()
+				.OrderBy (t => t.Name)
+				.Select (t => new KeyValuePair<string, Type> (t.Name, t))
+				.ToDictionary (t => t.Key, t => t.Value);
+
+			_leftPane = new Window ("Classes") {
+				X = 0,
+				Y = 0,
+				Width = 15,
+				Height = Dim.Fill (1), // for status bar
+				CanFocus = false,
+				ColorScheme = Colors.TopLevel,
+			};
+
+			_classListView = new ListView (_viewClasses.Keys.ToList ()) {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (0),
+				Height = Dim.Fill (0),
+				AllowsMarking = false,
+				ColorScheme = Colors.TopLevel,
+			};
+			_leftPane.Add (_classListView);
+
+			_settingsPane = new FrameView ("Settings") {
+				X = Pos.Right (_leftPane),
+				Y = 0, // for menu
+				Width = Dim.Fill (),
+				Height = 10,
+				CanFocus = false,
+				ColorScheme = Colors.TopLevel,
+			};
+			_computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 };
+			_settingsPane.Add (_computedCheckBox);
+
+			var radioItems = new ustring [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" };
+			_locationFrame = new FrameView ("Location (Pos)") {
+				X = Pos.Left (_computedCheckBox),
+				Y = Pos.Bottom (_computedCheckBox),
+				Height = 3 + radioItems.Length,
+				Width = 36,
+			};
+			_settingsPane.Add (_locationFrame);
+
+			var label = new Label ("x:") { X = 0, Y = 0 };
+			_locationFrame.Add (label);
+			_xRadioGroup = new RadioGroup (radioItems) {
+				X = 0,
+				Y = Pos.Bottom (label),
+			};
+			_xText = new TextField ($"{_xVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
+			_locationFrame.Add (_xText);
+
+			_locationFrame.Add (_xRadioGroup);
+
+			radioItems = new ustring [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" };
+			label = new Label ("y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 };
+			_locationFrame.Add (label);
+			_yText = new TextField ($"{_yVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
+			_locationFrame.Add (_yText);
+			_yRadioGroup = new RadioGroup (radioItems) {
+				X = Pos.X (label),
+				Y = Pos.Bottom (label),
+			};
+			_locationFrame.Add (_yRadioGroup);
+
+			_sizeFrame = new FrameView ("Size (Dim)") {
+				X = Pos.Right (_locationFrame),
+				Y = Pos.Y (_locationFrame),
+				Height = 3 + radioItems.Length,
+				Width = 40,
+			};
+
+			radioItems = new ustring [] { "Percent(width)", "Fill(width)", "Sized(width)" };
+			label = new Label ("width:") { X = 0, Y = 0 };
+			_sizeFrame.Add (label);
+			_wRadioGroup = new RadioGroup (radioItems) {
+				X = 0,
+				Y = Pos.Bottom (label),
+			};
+			_wText = new TextField ($"{_wVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
+			_sizeFrame.Add (_wText);
+			_sizeFrame.Add (_wRadioGroup);
+
+			radioItems = new ustring [] { "Percent(height)", "Fill(height)", "Sized(height)" };
+			label = new Label ("height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 };
+			_sizeFrame.Add (label);
+			_hText = new TextField ($"{_hVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
+			_sizeFrame.Add (_hText);
+
+			_hRadioGroup = new RadioGroup (radioItems) {
+				X = Pos.X (label),
+				Y = Pos.Bottom (label),
+			};
+			_sizeFrame.Add (_hRadioGroup);
+
+			_settingsPane.Add (_sizeFrame);
+
+			_hostPane = new FrameView ("") {
+				X = Pos.Right (_leftPane),
+				Y = Pos.Bottom (_settingsPane),
+				Width = Dim.Fill (),
+				Height = Dim.Fill (1), // + 1 for status bar
+				ColorScheme = Colors.Dialog,
+			};
+
+			_classListView.OpenSelectedItem += (a) => {
+				_settingsPane.SetFocus ();
+			};
+			_classListView.SelectedItemChanged += (args) => {
+				ClearClass (_curView);
+				_curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]);
+			};
+
+			_computedCheckBox.Toggled += (previousState) => {
+				if (_curView != null) {
+					_curView.LayoutStyle = previousState ? LayoutStyle.Absolute : LayoutStyle.Computed;
+					_hostPane.LayoutSubviews ();
+				}
+			};
+
+			_xRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
+
+			_xText.TextChanged += (args) => {
+				try {
+					_xVal = int.Parse (_xText.Text.ToString ());
+					DimPosChanged (_curView);
+				} catch {
+
+				}
+			};
+
+			_yText.TextChanged += (args) => {
+				try {
+					_yVal = int.Parse (_yText.Text.ToString ());
+					DimPosChanged (_curView);
+				} catch {
+
+				}
+			};
+
+			_yRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
+
+			_wRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
+
+			_wText.TextChanged += (args) => {
+				try {
+					_wVal = int.Parse (_wText.Text.ToString ());
+					DimPosChanged (_curView);
+				} catch {
+
+				}
+			};
+
+			_hText.TextChanged += (args) => {
+				try {
+					_hVal = int.Parse (_hText.Text.ToString ());
+					DimPosChanged (_curView);
+				} catch {
+
+				}
+			};
+
+			_hRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
+
+			Top.Add (_leftPane, _settingsPane, _hostPane);
+
+			Top.LayoutSubviews ();
+
+			_curView = CreateClass (_viewClasses.First ().Value);
+
+			int iterations = 0;
+
+			Application.Iteration += () => {
+				iterations++;
+
+				if (iterations < _viewClasses.Count) {
+					_classListView.MoveDown ();
+					Assert.Equal (_curView.GetType ().Name,
+						_viewClasses.Values.ToArray () [_classListView.SelectedItem].Name);
+				} else {
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+
+			Assert.Equal (_viewClasses.Count, iterations);
+
+			Application.Shutdown ();
+
+
+			void DimPosChanged (View view)
+			{
+				if (view == null) {
+					return;
+				}
+
+				var layout = view.LayoutStyle;
+
+				try {
+					view.LayoutStyle = LayoutStyle.Absolute;
+
+					switch (_xRadioGroup.SelectedItem) {
+					case 0:
+						view.X = Pos.Percent (_xVal);
+						break;
+					case 1:
+						view.X = Pos.AnchorEnd (_xVal);
+						break;
+					case 2:
+						view.X = Pos.Center ();
+						break;
+					case 3:
+						view.X = Pos.At (_xVal);
+						break;
+					}
+
+					switch (_yRadioGroup.SelectedItem) {
+					case 0:
+						view.Y = Pos.Percent (_yVal);
+						break;
+					case 1:
+						view.Y = Pos.AnchorEnd (_yVal);
+						break;
+					case 2:
+						view.Y = Pos.Center ();
+						break;
+					case 3:
+						view.Y = Pos.At (_yVal);
+						break;
+					}
+
+					switch (_wRadioGroup.SelectedItem) {
+					case 0:
+						view.Width = Dim.Percent (_wVal);
+						break;
+					case 1:
+						view.Width = Dim.Fill (_wVal);
+						break;
+					case 2:
+						view.Width = Dim.Sized (_wVal);
+						break;
+					}
+
+					switch (_hRadioGroup.SelectedItem) {
+					case 0:
+						view.Height = Dim.Percent (_hVal);
+						break;
+					case 1:
+						view.Height = Dim.Fill (_hVal);
+						break;
+					case 2:
+						view.Height = Dim.Sized (_hVal);
+						break;
+					}
+				} catch (Exception e) {
+					MessageBox.ErrorQuery ("Exception", e.Message, "Ok");
+				} finally {
+					view.LayoutStyle = layout;
+				}
+				UpdateTitle (view);
+			}
+
+			void UpdateSettings (View view)
+			{
+				var x = view.X.ToString ();
+				var y = view.Y.ToString ();
+				_xRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => x.Contains (s)).First ());
+				_yRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => y.Contains (s)).First ());
+				_xText.Text = $"{view.Frame.X}";
+				_yText.Text = $"{view.Frame.Y}";
+
+				var w = view.Width.ToString ();
+				var h = view.Height.ToString ();
+				_wRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => w.Contains (s)).First ());
+				_hRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => h.Contains (s)).First ());
+				_wText.Text = $"{view.Frame.Width}";
+				_hText.Text = $"{view.Frame.Height}";
+			}
+
+			void UpdateTitle (View view)
+			{
+				_hostPane.Title = $"{view.GetType ().Name} - {view.X.ToString ()}, {view.Y.ToString ()}, {view.Width.ToString ()}, {view.Height.ToString ()}";
+			}
+
+			List<Type> GetAllViewClassesCollection ()
+			{
+				List<Type> types = new List<Type> ();
+				foreach (Type type in typeof (View).Assembly.GetTypes ()
+				 .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) {
+					types.Add (type);
+				}
+				return types;
+			}
+
+			void ClearClass (View view)
+			{
+				// Remove existing class, if any
+				if (view != null) {
+					view.LayoutComplete -= LayoutCompleteHandler;
+					_hostPane.Remove (view);
+					view.Dispose ();
+					_hostPane.Clear ();
+				}
+			}
+
+			View CreateClass (Type type)
+			{
+				// If we are to create a generic Type
+				if (type.IsGenericType) {
+
+					// For each of the <T> arguments
+					List<Type> typeArguments = new List<Type> ();
+
+					// use <object>
+					foreach (var arg in type.GetGenericArguments ()) {
+						typeArguments.Add (typeof (object));
+					}
+
+					// And change what type we are instantiating from MyClass<T> to MyClass<object>
+					type = type.MakeGenericType (typeArguments.ToArray ());
+				}
+				// Instantiate view
+				var view = (View)Activator.CreateInstance (type);
+
+				//_curView.X = Pos.Center ();
+				//_curView.Y = Pos.Center ();
+				view.Width = Dim.Percent (75);
+				view.Height = Dim.Percent (75);
+
+				// Set the colorscheme to make it stand out if is null by default
+				if (view.ColorScheme == null) {
+					view.ColorScheme = Colors.Base;
+				}
+
+				// If the view supports a Text property, set it so we have something to look at
+				if (view.GetType ().GetProperty ("Text") != null) {
+					try {
+						view.GetType ().GetProperty ("Text")?.GetSetMethod ()?.Invoke (view, new [] { ustring.Make ("Test Text") });
+					} catch (TargetInvocationException e) {
+						MessageBox.ErrorQuery ("Exception", e.InnerException.Message, "Ok");
+						view = null;
+					}
+				}
+
+				// If the view supports a Title property, set it so we have something to look at
+				if (view != null && view.GetType ().GetProperty ("Title") != null) {
+					view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { ustring.Make ("Test Title") });
+				}
+
+				// If the view supports a Source property, set it so we have something to look at
+				if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (Terminal.Gui.IListDataSource)) {
+					var source = new ListWrapper (new List<ustring> () { ustring.Make ("Test Text #1"), ustring.Make ("Test Text #2"), ustring.Make ("Test Text #3") });
+					view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
+				}
+
+				// Set Settings
+				_computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed;
+
+				// Add
+				_hostPane.Add (view);
+				//DimPosChanged ();
+				_hostPane.LayoutSubviews ();
+				_hostPane.Clear ();
+				_hostPane.SetNeedsDisplay ();
+				UpdateSettings (view);
+				UpdateTitle (view);
+
+				view.LayoutComplete += LayoutCompleteHandler;
+
+				return view;
+			}
+
+			void LayoutCompleteHandler (View.LayoutEventArgs args)
+			{
+				UpdateTitle (_curView);
+			}
+		}
+	}
+}

+ 57 - 0
Terminal.Gui UnitTests/UnitTests.csproj

@@ -0,0 +1,57 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <IsPackable>false</IsPackable>
+    <UseDataCollector />
+    <!-- Version numbers are automatically updated by gitversion when a release is released -->
+    <!-- In the source tree the version will always be 1.0 for all projects. -->
+    <!-- Do not modify these. -->
+    <AssemblyVersion>1.0</AssemblyVersion>
+    <FileVersion>1.0</FileVersion>
+    <Version>1.0</Version>
+    <InformationalVersion>1.0</InformationalVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
+    <PackageReference Include="ReportGenerator" Version="5.1.10" />
+    <PackageReference Include="System.Collections" Version="4.3.0" />
+    <PackageReference Include="xunit" Version="2.4.2" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+    <PackageReference Include="coverlet.collector" Version="3.2.0">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
+    <ProjectReference Include="..\UICatalog\UICatalog.csproj" />
+  </ItemGroup>
+  <PropertyGroup Label="FineCodeCoverage">
+    <Enabled>
+      True
+    </Enabled>
+    <Exclude>
+      [UICatalog]*
+    </Exclude>
+    <Include></Include>
+    <ExcludeByFile>
+      <!--**/Migrations/*
+      **/Hacks/*.cs-->
+    </ExcludeByFile>
+    <ExcludeByAttribute>
+      <!--MyCustomExcludeFromCodeCoverage-->
+    </ExcludeByAttribute>
+    <IncludeTestAssembly>
+      False
+    </IncludeTestAssembly>
+  </PropertyGroup>
+</Project>

+ 96 - 166
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -6,6 +6,7 @@
 //
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Runtime.InteropServices;
 using System.Threading.Tasks;
@@ -954,11 +955,13 @@ namespace Terminal.Gui {
 
 		public static bool Is_WSL_Platform ()
 		{
-			if (new CursesClipboard ().IsSupported) {
-				return false;
-			}
-			var result = BashRunner.Run ("uname -a", runCurses: false);
-			if (result.Contains ("microsoft") && result.Contains ("WSL")) {
+			// xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell
+			//if (new CursesClipboard ().IsSupported) {
+			//	// If xclip is installed on Linux under WSL, this will return true.
+			//	return false;
+			//}
+			var (exitCode, result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
+			if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) {
 				return true;
 			}
 			return false;
@@ -1259,134 +1262,79 @@ namespace Terminal.Gui {
 		}
 	}
 
+	/// <summary>
+	///  A clipboard implementation for Linux.
+	///  This implementation uses the xclip command to access the clipboard.
+	/// </summary>	
+	/// <remarks>
+	/// If xclip is not installed, this implementation will not work.
+	/// </remarks>
 	class CursesClipboard : ClipboardBase {
 		public CursesClipboard ()
 		{
 			IsSupported = CheckSupport ();
 		}
 
+		string xclipPath = string.Empty;
 		public override bool IsSupported { get; }
 
 		bool CheckSupport ()
 		{
 			try {
-				var result = BashRunner.Run ("which xclip", runCurses: false);
-				return result.FileExists ();
+				var (exitCode, result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true);
+				if (exitCode == 0 && result.FileExists ()) {
+					xclipPath = result;
+					return true;
+				}
 			} catch (Exception) {
 				// Permissions issue.
-				return false;
 			}
+			return false;
 		}
 
 		protected override string GetClipboardDataImpl ()
 		{
 			var tempFileName = System.IO.Path.GetTempFileName ();
-			try {
-				// BashRunner.Run ($"xsel -o --clipboard > {tempFileName}");
-				BashRunner.Run ($"xclip -selection clipboard -o > {tempFileName}");
-				return System.IO.File.ReadAllText (tempFileName);
-			} finally {
-				System.IO.File.Delete (tempFileName);
-			}
-		}
-
-		protected override void SetClipboardDataImpl (string text)
-		{
-			// var tempFileName = System.IO.Path.GetTempFileName ();
-			// System.IO.File.WriteAllText (tempFileName, text);
-			// try {
-			// 	// BashRunner.Run ($"cat {tempFileName} | xsel -i --clipboard");
-			// 	BashRunner.Run ($"cat {tempFileName} | xclip -selection clipboard");
-			// } finally {
-			// 	System.IO.File.Delete (tempFileName);
-			// }
-
-			BashRunner.Run ("xclip -selection clipboard -i", false, text);
-		}
-	}
+			var xclipargs = "-selection clipboard -o";
 
-	static class BashRunner {
-		public static string Run (string commandLine, bool output = true, string inputText = "", bool runCurses = true)
-		{
-			var arguments = $"-c \"{commandLine}\"";
-
-			if (output) {
-				var errorBuilder = new System.Text.StringBuilder ();
-				var outputBuilder = new System.Text.StringBuilder ();
-
-				using (var process = new System.Diagnostics.Process {
-					StartInfo = new System.Diagnostics.ProcessStartInfo {
-						FileName = "bash",
-						Arguments = arguments,
-						RedirectStandardOutput = true,
-						RedirectStandardError = true,
-						UseShellExecute = false,
-						CreateNoWindow = false,
-					}
-				}) {
-					process.Start ();
-					process.OutputDataReceived += (sender, args) => { outputBuilder.AppendLine (args.Data); };
-					process.BeginOutputReadLine ();
-					process.ErrorDataReceived += (sender, args) => { errorBuilder.AppendLine (args.Data); };
-					process.BeginErrorReadLine ();
-					if (!process.DoubleWaitForExit ()) {
-						var timeoutError = $@"Process timed out. Command line: bash {arguments}.
-							Output: {outputBuilder}
-							Error: {errorBuilder}";
-						throw new Exception (timeoutError);
-					}
-					if (process.ExitCode == 0) {
-						if (runCurses && Application.Driver is CursesDriver) {
-							Curses.raw ();
-							Curses.noecho ();
-						}
-						return outputBuilder.ToString ();
-					}
-
-					var error = $@"Could not execute process. Command line: bash {arguments}.
-						Output: {outputBuilder}
-						Error: {errorBuilder}";
-					throw new Exception (error);
-				}
-			} else {
-				using (var process = new System.Diagnostics.Process {
-					StartInfo = new System.Diagnostics.ProcessStartInfo {
-						FileName = "bash",
-						Arguments = arguments,
-						RedirectStandardInput = true,
-						RedirectStandardError = true,
-						UseShellExecute = false,
-						CreateNoWindow = false
-					}
-				}) {
-					process.Start ();
-					process.StandardInput.Write (inputText);
-					process.StandardInput.Close ();
-					process.WaitForExit ();
-					if (runCurses && Application.Driver is CursesDriver) {
+			try {
+				var (exitCode, result) = ClipboardProcessRunner.Bash ($"{xclipPath} {xclipargs} > {tempFileName}", waitForOutput: false);
+				if (exitCode == 0) {
+					if (Application.Driver is CursesDriver) {
 						Curses.raw ();
 						Curses.noecho ();
 					}
-					return inputText;
+					return System.IO.File.ReadAllText (tempFileName);
 				}
+			} catch (Exception e) {
+				throw new NotSupportedException ($"\"{xclipPath} {xclipargs}\" failed.", e);
+			} finally {
+				System.IO.File.Delete (tempFileName);
 			}
+			return string.Empty;
 		}
 
-		public static bool DoubleWaitForExit (this System.Diagnostics.Process process)
+		protected override void SetClipboardDataImpl (string text)
 		{
-			var result = process.WaitForExit (500);
-			if (result) {
-				process.WaitForExit ();
+			var xclipargs = "-selection clipboard -i";
+			try {
+				var (exitCode, _) = ClipboardProcessRunner.Bash ($"{xclipPath} {xclipargs}", text, waitForOutput: false);
+				if (exitCode == 0 && Application.Driver is CursesDriver) {
+					Curses.raw ();
+					Curses.noecho ();
+				}
+			} catch (Exception e) {
+				throw new NotSupportedException ($"\"{xclipPath} {xclipargs} < {text}\" failed", e);
 			}
-			return result;
-		}
-
-		public static bool FileExists (this string value)
-		{
-			return !string.IsNullOrEmpty (value) && !value.Contains ("not found");
 		}
 	}
 
+	/// <summary>
+	///  A clipboard implementation for MacOSX. 
+	///  This implementation uses the Mac clipboard API (via P/Invoke) to copy/paste.
+	///  The existance of the Mac pbcopy and pbpaste commands 
+	///  is used to determine if copy/paste is supported.
+	/// </summary>	
 	class MacOSXClipboard : ClipboardBase {
 		IntPtr nsString = objc_getClass ("NSString");
 		IntPtr nsPasteboard = objc_getClass ("NSPasteboard");
@@ -1413,12 +1361,12 @@ namespace Terminal.Gui {
 
 		bool CheckSupport ()
 		{
-			var result = BashRunner.Run ("which pbcopy");
-			if (!result.FileExists ()) {
+			var (exitCode, result) = ClipboardProcessRunner.Bash ("which pbcopy", waitForOutput: true);
+			if (exitCode != 0 || !result.FileExists ()) {
 				return false;
 			}
-			result = BashRunner.Run ("which pbpaste");
-			return result.FileExists ();
+			(exitCode, result) = ClipboardProcessRunner.Bash ("which pbpaste", waitForOutput: true);
+			return exitCode == 0 && result.FileExists ();
 		}
 
 		protected override string GetClipboardDataImpl ()
@@ -1461,95 +1409,77 @@ namespace Terminal.Gui {
 		static extern IntPtr sel_registerName (string selectorName);
 	}
 
+	/// <summary>
+	///  A clipboard implementation for Linux, when running under WSL. 
+	///  This implementation uses the Windows clipboard to store the data, and uses Windows'
+	///  powershell.exe (launched via WSL interop services) to set/get the Windows
+	///  clipboard. 
+	/// </summary>
 	class WSLClipboard : ClipboardBase {
+		bool isSupported = false;
 		public WSLClipboard ()
 		{
-			IsSupported = CheckSupport ();
+			isSupported = CheckSupport ();
 		}
 
-		public override bool IsSupported { get; }
+		public override bool IsSupported {
+			get {
+				return isSupported = CheckSupport ();
+			}
+		}
+
+		private static string powershellPath = string.Empty;
 
 		bool CheckSupport ()
 		{
-			try {
-				var result = BashRunner.Run ("which powershell.exe");
-				return result.FileExists ();
-			} catch (System.Exception) {
-				return false;
-			}
+			if (string.IsNullOrEmpty (powershellPath)) {
+				// Specify pwsh.exe (not pwsh) to ensure we get the Windows version (invoked via WSL)
+				var (exitCode, result) = ClipboardProcessRunner.Bash ("which pwsh.exe", waitForOutput: true);
+				if (exitCode > 0) {
+					(exitCode, result) = ClipboardProcessRunner.Bash ("which powershell.exe", waitForOutput: true);
+				}
 
-			//var result = BashRunner.Run ("which powershell.exe");
-			//if (!result.FileExists ()) {
-			//	return false;
-			//}
-			//result = BashRunner.Run ("which clip.exe");
-			//return result.FileExists ();
+				if (exitCode == 0) {
+					powershellPath = result;
+				}
+			}
+			return !string.IsNullOrEmpty (powershellPath);
 		}
 
 		protected override string GetClipboardDataImpl ()
 		{
-			using (var powershell = new System.Diagnostics.Process {
-				StartInfo = new System.Diagnostics.ProcessStartInfo {
-					RedirectStandardOutput = true,
-					FileName = "powershell.exe",
-					Arguments = "-noprofile -command \"Get-Clipboard\"",
-					UseShellExecute = false,
-					CreateNoWindow = true
-				}
-			}) {
-				powershell.Start ();
-				var result = powershell.StandardOutput.ReadToEnd ();
-				powershell.StandardOutput.Close ();
-				if (!powershell.DoubleWaitForExit ()) {
-					var timeoutError = $@"Process timed out. Command line: bash {powershell.StartInfo.Arguments}.
-							Output: {powershell.StandardOutput.ReadToEnd ()}
-							Error: {powershell.StandardError.ReadToEnd ()}";
-					throw new Exception (timeoutError);
-				}
+			if (!IsSupported) {
+				return string.Empty;
+			}
+
+			var (exitCode, output) = ClipboardProcessRunner.Process (powershellPath, "-noprofile -command \"Get-Clipboard\"");
+			if (exitCode == 0) {
 				if (Application.Driver is CursesDriver) {
 					Curses.raw ();
 					Curses.noecho ();
 				}
-				if (result.EndsWith ("\r\n")) {
-					result = result.Substring (0, result.Length - 2);
+
+				if (output.EndsWith ("\r\n")) {
+					output = output.Substring (0, output.Length - 2);
 				}
-				return result;
+				return output;
 			}
+			return string.Empty;
 		}
 
 		protected override void SetClipboardDataImpl (string text)
 		{
-			using (var powershell = new System.Diagnostics.Process {
-				StartInfo = new System.Diagnostics.ProcessStartInfo {
-					FileName = "powershell.exe",
-					Arguments = $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\""
-				}
-			}) {
-				powershell.Start ();
-				powershell.WaitForExit ();
-				if (!powershell.DoubleWaitForExit ()) {
-					var timeoutError = $@"Process timed out. Command line: bash {powershell.StartInfo.Arguments}.
-							Output: {powershell.StandardOutput.ReadToEnd ()}
-							Error: {powershell.StandardError.ReadToEnd ()}";
-					throw new Exception (timeoutError);
-				}
+			if (!IsSupported) {
+				return;
+			}
+
+			var (exitCode, output) = ClipboardProcessRunner.Process (powershellPath, $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\"");
+			if (exitCode == 0) {
 				if (Application.Driver is CursesDriver) {
 					Curses.raw ();
 					Curses.noecho ();
 				}
 			}
-
-			//using (var clipExe = new System.Diagnostics.Process {
-			//	StartInfo = new System.Diagnostics.ProcessStartInfo {
-			//		FileName = "clip.exe",
-			//		RedirectStandardInput = true
-			//	}
-			//}) {
-			//	clipExe.Start ();
-			//	clipExe.StandardInput.Write (text);
-			//	clipExe.StandardInput.Close ();
-			//	clipExe.WaitForExit ();
-			//}
 		}
 	}
 }

+ 5 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -38,6 +38,11 @@ namespace Terminal.Gui {
 	/// can watch file descriptors using the AddWatch methods.
 	/// </remarks>
 	internal class UnixMainLoop : IMainLoopDriver {
+		public UnixMainLoop (ConsoleDriver consoleDriver = null)
+		{
+			// UnixDriver doesn't use the consoleDriver parameter, but the WindowsDriver does.
+		}
+
 		public const int KEY_RESIZE = unchecked((int)0xffffffffffffffff);
 
 		[StructLayout (LayoutKind.Sequential)]

+ 71 - 8
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -6,10 +6,12 @@
 //
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Runtime.InteropServices;
 using System.Threading;
 using NStack;
+
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
@@ -19,6 +21,27 @@ namespace Terminal.Gui {
 	/// </summary>
 	public class FakeDriver : ConsoleDriver {
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+
+		public class Behaviors {
+
+			public bool UseFakeClipboard { get; internal set; }
+			public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; }
+			public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; }
+
+			public Behaviors (bool useFakeClipboard = false, bool fakeClipboardAlwaysThrowsNotSupportedException = false, bool fakeClipboardIsSupportedAlwaysTrue = false)
+			{
+				UseFakeClipboard = useFakeClipboard;
+				FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
+				FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
+				
+				// double check usage is correct
+				Debug.Assert (useFakeClipboard == false && fakeClipboardAlwaysThrowsNotSupportedException == false);
+				Debug.Assert (useFakeClipboard == false && fakeClipboardIsSupportedAlwaysTrue == false);
+			}
+		}
+
+		public static FakeDriver.Behaviors FakeBehaviors = new Behaviors ();
+
 		int cols, rows, left, top;
 		public override int Cols => cols;
 		public override int Rows => rows;
@@ -26,7 +49,8 @@ namespace Terminal.Gui {
 		public override int Left => 0;
 		public override int Top => 0;
 		public override bool HeightAsBuffer { get; set; }
-		public override IClipboard Clipboard { get; }
+		private IClipboard clipboard = null;
+		public override IClipboard Clipboard => clipboard;
 
 		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
 		int [,,] contents;
@@ -59,15 +83,19 @@ namespace Terminal.Gui {
 
 		public FakeDriver ()
 		{
-			if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
-				Clipboard = new WindowsClipboard ();
-			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-				Clipboard = new MacOSXClipboard ();
+			if (FakeBehaviors.UseFakeClipboard) {
+				clipboard = new FakeClipboard (FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException, FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse);
 			} else {
-				if (CursesDriver.Is_WSL_Platform ()) {
-					Clipboard = new WSLClipboard ();
+				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+					clipboard = new WindowsClipboard ();
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
+					clipboard = new MacOSXClipboard ();
 				} else {
-					Clipboard = new CursesClipboard ();
+					if (CursesDriver.Is_WSL_Platform ()) {
+						clipboard = new WSLClipboard ();
+					} else {
+						clipboard = new CursesClipboard ();
+					}
 				}
 			}
 		}
@@ -646,6 +674,41 @@ namespace Terminal.Gui {
 		}
 
 		#endregion
+
+		public class FakeClipboard : ClipboardBase {
+			public Exception FakeException = null;
+
+			string contents = string.Empty;
+
+			bool isSupportedAlwaysFalse = false;
+
+			public override bool IsSupported => !isSupportedAlwaysFalse;
+
+			public FakeClipboard (bool fakeClipboardThrowsNotSupportedException = false, bool isSupportedAlwaysFalse = false)
+			{
+				this.isSupportedAlwaysFalse = isSupportedAlwaysFalse;
+				if (fakeClipboardThrowsNotSupportedException) {
+					FakeException = new NotSupportedException ("Fake clipboard exception");
+				}
+			}
+
+			protected override string GetClipboardDataImpl ()
+			{
+				if (FakeException != null) {
+					throw FakeException;
+				}
+				return contents;
+			}
+
+			protected override void SetClipboardDataImpl (string text)
+			{
+				if (FakeException != null) {
+					throw FakeException;
+				}
+				contents = text;
+			}
+		}
+
 #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
 	}
 }

+ 5 - 11
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs

@@ -15,7 +15,7 @@ namespace Terminal.Gui {
 		AutoResetEvent waitForProbe = new AutoResetEvent (false);
 		ConsoleKeyInfo? keyResult = null;
 		MainLoop mainLoop;
-		Func<ConsoleKeyInfo> consoleKeyReaderFn = null;
+		Func<ConsoleKeyInfo> consoleKeyReaderFn = () => FakeConsole.ReadKey (true);
 
 		/// <summary>
 		/// Invoked when a Key is pressed.
@@ -23,18 +23,12 @@ namespace Terminal.Gui {
 		public Action<ConsoleKeyInfo> KeyPressed;
 
 		/// <summary>
-		/// Initializes the class.
+		/// Creates an instance of the FakeMainLoop. <paramref name="consoleDriver"/> is not used.
 		/// </summary>
-		/// <remarks>
-		///   Passing a consoleKeyReaderfn is provided to support unit test scenarios.
-		/// </remarks>
-		/// <param name="consoleKeyReaderFn">The method to be called to get a key from the console.</param>
-		public FakeMainLoop (Func<ConsoleKeyInfo> consoleKeyReaderFn = null)
+		/// <param name="consoleDriver"></param>
+		public FakeMainLoop (ConsoleDriver consoleDriver = null)
 		{
-			if (consoleKeyReaderFn == null) {
-				throw new ArgumentNullException ("key reader function must be provided.");
-			}
-			this.consoleKeyReaderFn = consoleKeyReaderFn;
+			// consoleDriver is not needed/used in FakeConsole
 		}
 
 		void WindowsKeyReader ()

+ 27 - 11
Terminal.Gui/Core/Application.cs

@@ -375,14 +375,14 @@ namespace Terminal.Gui {
 				ResetState ();
 			}
 
-			// FakeDriver (for UnitTests)
+			// For UnitTests
 			if (driver != null) {
-				if (mainLoopDriver == null) {
-					throw new ArgumentNullException ("InternalInit mainLoopDriver cannot be null if driver is provided.");
-				}
-				if (!(driver is FakeDriver)) {
-					throw new InvalidOperationException ("InternalInit can only be called with FakeDriver.");
-				}
+				//if (mainLoopDriver == null) {
+				//	throw new ArgumentNullException ("InternalInit mainLoopDriver cannot be null if driver is provided.");
+				//}
+				//if (!(driver is FakeDriver)) {
+				//	throw new InvalidOperationException ("InternalInit can only be called with FakeDriver.");
+				//}
 				Driver = driver;
 			}
 
@@ -391,18 +391,34 @@ namespace Terminal.Gui {
 				if (ForceFakeConsole) {
 					// For Unit Testing only
 					Driver = new FakeDriver ();
-					mainLoopDriver = new FakeMainLoop (() => FakeConsole.ReadKey (true));
 				} else if (UseSystemConsole) {
 					Driver = new NetDriver ();
-					mainLoopDriver = new NetMainLoop (Driver);
 				} else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
 					Driver = new WindowsDriver ();
-					mainLoopDriver = new WindowsMainLoop (Driver);
 				} else {
-					mainLoopDriver = new UnixMainLoop ();
 					Driver = new CursesDriver ();
 				}
+				if (Driver == null) {
+					throw new InvalidOperationException ("Init could not determine the ConsoleDriver to use.");
+				}
 			}
+
+			if (mainLoopDriver == null) {
+				// TODO: Move this logic into ConsoleDriver
+				if (Driver is FakeDriver) {
+					mainLoopDriver = new FakeMainLoop (Driver);
+				} else if (Driver is NetDriver) {
+					mainLoopDriver = new NetMainLoop (Driver);
+				} else if (Driver is WindowsDriver) {
+					mainLoopDriver = new WindowsMainLoop (Driver);
+				} else if (Driver is CursesDriver) {
+					mainLoopDriver = new UnixMainLoop (Driver);
+				}
+				if (mainLoopDriver == null) {
+					throw new InvalidOperationException ("Init could not determine the MainLoopDriver to use.");
+				}
+			}
+
 			MainLoop = new MainLoop (mainLoopDriver);
 
 			try {

+ 38 - 11
Terminal.Gui/Core/Clipboard/Clipboard.cs

@@ -3,13 +3,32 @@ using System;
 
 namespace Terminal.Gui {
 	/// <summary>
-	/// Provides cut, copy, and paste support for the clipboard with OS interaction.
+	/// Provides cut, copy, and paste support for the OS clipboard.
 	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// On Windows, the <see cref="Clipboard"/> class uses the Windows Clipboard APIs via P/Invoke.
+	/// </para>
+	/// <para>
+	/// On Linux, when not running under Windows Subsystem for Linux (WSL),
+	/// the <see cref="Clipboard"/> class uses the xclip command line tool. If xclip is not installed,
+	/// the clipboard will not work.
+	/// </para>
+	/// <para>
+	/// On Linux, when running under Windows Subsystem for Linux (WSL),
+	/// the <see cref="Clipboard"/> class launches Windows' powershell.exe via WSL interop and uses the
+	/// "Set-Clipboard" and "Get-Clipboard" Powershell CmdLets. 
+	/// </para>
+	/// <para>
+	/// On the Mac, the <see cref="Clipboard"/> class uses the MacO OS X pbcopy and pbpaste command line tools
+	/// and the Mac clipboard APIs vai P/Invoke.
+	/// </para>
+	/// </remarks>
 	public static class Clipboard {
 		static ustring contents;
 
 		/// <summary>
-		/// Get or sets the operation system clipboard, otherwise the contents field.
+		/// Gets (copies from) or sets (pastes to) the contents of the OS clipboard.
 		/// </summary>
 		public static ustring Contents {
 			get {
@@ -25,10 +44,15 @@ namespace Terminal.Gui {
 			}
 			set {
 				try {
-					if (IsSupported && value != null) {
+					if (IsSupported) {
+						if (value == null) {
+							value = string.Empty;
+						}
 						Application.Driver.Clipboard.SetClipboardData (value.ToString ());
 					}
 					contents = value;
+				} catch (NotSupportedException e) {
+					throw e;
 				} catch (Exception) {
 					contents = value;
 				}
@@ -38,32 +62,35 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Returns true if the environmental dependencies are in place to interact with the OS clipboard.
 		/// </summary>
+		/// <remarks>
+		/// </remarks>
 		public static bool IsSupported { get => Application.Driver.Clipboard.IsSupported; }
 
 		/// <summary>
-		/// Gets the operation system clipboard if possible.
+		/// Copies the contents of the OS clipboard to <paramref name="result"/> if possible.
 		/// </summary>
-		/// <param name="result">Clipboard contents read</param>
-		/// <returns>true if it was possible to read the OS clipboard.</returns>
+		/// <param name="result">The contents of the OS clipboard if successful, <see cref="string.Empty"/> if not.</param>
+		/// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
 		public static bool TryGetClipboardData (out string result)
 		{
-			if (Application.Driver.Clipboard.TryGetClipboardData (out result)) {
+			if (IsSupported && Application.Driver.Clipboard.TryGetClipboardData (out result)) {
 				if (contents != result) {
 					contents = result;
 				}
 				return true;
 			}
+			result = string.Empty;
 			return false;
 		}
 
 		/// <summary>
-		/// Sets the operation system clipboard if possible.
+		/// Pastes the <paramref name="text"/> to the OS clipboard if possible.
 		/// </summary>
-		/// <param name="text"></param>
-		/// <returns>True if the clipboard content was set successfully.</returns>
+		/// <param name="text">The text to paste to the OS clipboard.</param>
+		/// <returns><see langword="true"/> the OS clipboard was set, <see langword="false"/> otherwise.</returns>
 		public static bool TrySetClipboardData (string text)
 		{
-			if (Application.Driver.Clipboard.TrySetClipboardData (text)) {
+			if (IsSupported && Application.Driver.Clipboard.TrySetClipboardData (text)) {
 				contents = text;
 				return true;
 			}

+ 25 - 20
Terminal.Gui/Core/Clipboard/ClipboardBase.cs

@@ -15,48 +15,52 @@ namespace Terminal.Gui {
 		public abstract bool IsSupported { get; }
 
 		/// <summary>
-		/// Get the operation system clipboard.
+		/// Returns the contents of the OS clipboard if possible.
 		/// </summary>
-		/// <exception cref="NotSupportedException">Thrown if it was not possible to read the clipboard contents</exception>
+		/// <returns>The contents of the OS clipboard if successful.</returns>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to copy from the OS clipboard.</exception>
 		public string GetClipboardData ()
 		{
 			try {
 				return GetClipboardDataImpl ();
-			} catch (Exception ex) {
-				throw new NotSupportedException ("Failed to read clipboard.", ex);
+			} catch (NotSupportedException ex) {
+				throw new NotSupportedException ("Failed to copy from the OS clipboard.", ex);
 			}
 		}
 
 		/// <summary>
-		/// Get the operation system clipboard.
+		/// Returns the contents of the OS clipboard if possible. Implemented by <see cref="ConsoleDriver"/>-specific subclasses.
 		/// </summary>
+		/// <returns>The contents of the OS clipboard if successful.</returns>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to copy from the OS clipboard.</exception>
 		protected abstract string GetClipboardDataImpl ();
 
 		/// <summary>
-		/// Sets the operation system clipboard.
+		/// Pastes the <paramref name="text"/> to the OS clipboard if possible.
 		/// </summary>
-		/// <param name="text"></param>
-		/// <exception cref="NotSupportedException">Thrown if it was not possible to set the clipboard contents</exception>
+		/// <param name="text">The text to paste to the OS clipboard.</param>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to paste to the OS clipboard.</exception>
 		public void SetClipboardData (string text)
 		{
 			try {
 				SetClipboardDataImpl (text);
-			} catch (Exception ex) {
-				throw new NotSupportedException ("Failed to write to clipboard.", ex);
+			} catch (NotSupportedException ex) {
+				throw new NotSupportedException ("Failed to paste to the OS clipboard.", ex);
 			}
 		}
 
 		/// <summary>
-		/// Sets the operation system clipboard.
+		/// Pastes the <paramref name="text"/> to the OS clipboard if possible. Implemented by <see cref="ConsoleDriver"/>-specific subclasses.
 		/// </summary>
-		/// <param name="text"></param>
+		/// <param name="text">The text to paste to the OS clipboard.</param>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to paste to the OS clipboard.</exception>
 		protected abstract void SetClipboardDataImpl (string text);
 
 		/// <summary>
-		/// Gets the operation system clipboard if possible.
+		/// Copies the contents of the OS clipboard to <paramref name="result"/> if possible.
 		/// </summary>
-		/// <param name="result">Clipboard contents read</param>
-		/// <returns>true if it was possible to read the OS clipboard.</returns>
+		/// <param name="result">The contents of the OS clipboard if successful, <see cref="string.Empty"/> if not.</param>
+		/// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
 		public bool TryGetClipboardData (out string result)
 		{
 			// Don't even try to read because environment is not set up.
@@ -71,17 +75,18 @@ namespace Terminal.Gui {
 					result = GetClipboardDataImpl ();
 				}
 				return true;
-			} catch (Exception) {
+			} catch (NotSupportedException ex) {
+				System.Diagnostics.Debug.WriteLine ($"TryGetClipboardData: {ex.Message}");
 				result = null;
 				return false;
 			}
 		}
 
 		/// <summary>
-		/// Sets the operation system clipboard if possible.
+		/// Pastes the <paramref name="text"/> to the OS clipboard if possible.
 		/// </summary>
-		/// <param name="text"></param>
-		/// <returns>True if the clipboard content was set successfully</returns>
+		/// <param name="text">The text to paste to the OS clipboard.</param>
+		/// <returns><see langword="true"/> the OS clipboard was set, <see langword="false"/> otherwise.</returns>
 		public bool TrySetClipboardData (string text)
 		{
 			// Don't even try to set because environment is not set up
@@ -92,7 +97,7 @@ namespace Terminal.Gui {
 			try {
 				SetClipboardDataImpl (text);
 				return true;
-			} catch (Exception ex) {
+			} catch (NotSupportedException ex) {
 				System.Diagnostics.Debug.WriteLine ($"TrySetClipboardData: {ex.Message}");
 				return false;
 			}

+ 73 - 0
Terminal.Gui/Core/ConsoleDriver.cs

@@ -8,8 +8,11 @@
 using NStack;
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Unix.Terminal;
 
 namespace Terminal.Gui {
 	/// <summary>
@@ -1389,4 +1392,74 @@ namespace Terminal.Gui {
 			Colors.Error.Disabled = MakeColor (Color.DarkGray, Color.White);
 		}
 	}
+
+	/// <summary>
+	/// Helper class for console drivers to invoke shell commands to interact with the clipboard.
+	/// Used primarily by CursesDriver, but also used in Unit tests which is why it is in
+	/// ConsoleDriver.cs.
+	/// </summary>
+	internal static class ClipboardProcessRunner {
+		public static (int exitCode, string result) Bash (string commandLine, string inputText = "", bool waitForOutput = false)
+		{
+			var arguments = $"-c \"{commandLine}\"";
+			var (exitCode, result) = Process ("bash", arguments, inputText, waitForOutput);
+
+			return (exitCode, result.TrimEnd ());
+		}
+
+		public static (int exitCode, string result) Process (string cmd, string arguments, string input = null, bool waitForOutput = true)
+		{
+			var output = string.Empty;
+
+			using (Process process = new Process {
+				StartInfo = new ProcessStartInfo {
+					FileName = cmd,
+					Arguments = arguments,
+					RedirectStandardOutput = true,
+					RedirectStandardError = true,
+					RedirectStandardInput = true,
+					UseShellExecute = false,
+					CreateNoWindow = true,
+				}
+			}) {
+				var eventHandled = new TaskCompletionSource<bool> ();
+				process.Start ();
+				if (!string.IsNullOrEmpty (input)) {
+					process.StandardInput.Write (input);
+					process.StandardInput.Close ();
+				}
+
+				if (!process.WaitForExit (5000)) {
+					var timeoutError = $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.";
+					throw new TimeoutException (timeoutError);
+				}
+
+				if (waitForOutput && process.StandardOutput.Peek () != -1) {
+					output = process.StandardOutput.ReadToEnd ();
+				}
+
+				if (process.ExitCode > 0) {
+					output = $@"Process failed to run. Command line: {cmd} {arguments}.
+										Output: {output}
+										Error: {process.StandardError.ReadToEnd ()}";
+				}
+
+				return (process.ExitCode, output);
+			}
+		}
+
+		public static bool DoubleWaitForExit (this System.Diagnostics.Process process)
+		{
+			var result = process.WaitForExit (500);
+			if (result) {
+				process.WaitForExit ();
+			}
+			return result;
+		}
+
+		public static bool FileExists (this string value)
+		{
+			return !string.IsNullOrEmpty (value) && !value.Contains ("not found");
+		}
+	}
 }

+ 2 - 1
Terminal.Gui/Core/MainLoop.cs

@@ -102,7 +102,8 @@ namespace Terminal.Gui {
 		/// <summary>
 		///  Creates a new Mainloop. 
 		/// </summary>
-		/// <param name="driver">Should match the <see cref="ConsoleDriver"/> (one of the implementations UnixMainLoop, NetMainLoop or WindowsMainLoop).</param>
+		/// <param name="driver">Should match the <see cref="ConsoleDriver"/> 
+		/// (one of the implementations FakeMainLoop, UnixMainLoop, NetMainLoop or WindowsMainLoop).</param>
 		public MainLoop (IMainLoopDriver driver)
 		{
 			Driver = driver;

+ 4 - 4
Terminal.Gui/Terminal.Gui.csproj

@@ -10,12 +10,12 @@
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- In the source tree the version will always be 1.0 for all projects. -->
     <!-- Do not modify these. Do NOT commit after manually running `dotnet-gitversion /updateprojectfiles` -->
-    <AssemblyVersion>1.0</AssemblyVersion>
-    <FileVersion>1.0</FileVersion>
-    <Version>1.0</Version>
-    <InformationalVersion>1.0</InformationalVersion>
+    <AssemblyVersion>1.9</AssemblyVersion>
+    <Version>1.9</Version>
+    <InformationalVersion>1.9</InformationalVersion>
   </PropertyGroup>
   <ItemGroup>
+    <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
     <PackageReference Include="NStack.Core" Version="1.0.7" />
     <InternalsVisibleTo Include="UnitTests" />

+ 9 - 0
Terminal.Gui/Windows/FileDialog.cs

@@ -83,6 +83,7 @@ namespace Terminal.Gui {
 				case DirectoryNotFoundException _:
 				case ArgumentException _:
 					dirInfo = null;
+					watcher?.Dispose ();
 					watcher = null;
 					infos.Clear ();
 					valid = true;
@@ -104,7 +105,15 @@ namespace Terminal.Gui {
 		{
 			if (!_disposedValue) {
 				if (disposing) {
+					if (watcher != null) {
+						watcher.Changed -= Watcher_Changed;
+						watcher.Created -= Watcher_Changed;
+						watcher.Deleted -= Watcher_Changed;
+						watcher.Renamed -= Watcher_Changed;
+						watcher.Error -= Watcher_Error;
+					}
 					watcher?.Dispose ();
+					watcher = null;
 				}
 
 				_disposedValue = true;

+ 13 - 15
UICatalog/Properties/launchSettings.json

@@ -3,10 +3,16 @@
     "UICatalog": {
       "commandName": "Project"
     },
-    "UICatalog : -usc": {
+    "UICatalog -usc": {
       "commandName": "Project",
       "commandLineArgs": "-usc"
     },
+    "WSL: UICatalog -usc": {
+      "commandName": "Executable",
+      "executablePath": "wsl",
+      "commandLineArgs": "dotnet UICatalog.dll -usc",
+      "distributionName": ""
+    },
     "Wizards": {
       "commandName": "Project",
       "commandLineArgs": "Wizards"
@@ -35,20 +41,6 @@
       "commandName": "Project",
       "commandLineArgs": "\"Character Map\""
     },
-    "WSL2": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll"
-    },
-    "WSL2 : -usc": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll -usc"
-    },
-    "WSL": {
-      "commandName": "WSL2",
-      "distributionName": ""
-    },
     "All Views Tester": {
       "commandName": "Project",
       "commandLineArgs": "\"All Views Tester\""
@@ -56,6 +48,12 @@
     "Windows & FrameViews": {
       "commandName": "Project",
       "commandLineArgs": "\"Windows & FrameViews\""
+    },
+    "WSL : UICatalog": {
+      "commandName": "Executable",
+      "executablePath": "wsl",
+      "commandLineArgs": "dotnet UICatalog.dll",
+      "distributionName": ""
     }
   }
 }

+ 1 - 1
UICatalog/Scenarios/Editor.cs

@@ -343,7 +343,7 @@ namespace UICatalog.Scenarios {
 		private bool CanCloseFile ()
 		{
 			if (_textView.Text == _originalText) {
-				System.Diagnostics.Debug.Assert (!_textView.IsDirty);
+				//System.Diagnostics.Debug.Assert (!_textView.IsDirty);
 				return true;
 			}
 

+ 7 - 6
UICatalog/UICatalog.cs

@@ -4,10 +4,10 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Globalization;
 using System.Linq;
-using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Text;
 using Terminal.Gui;
+using Microsoft.DotNet.PlatformAbstractions;
 using Rune = System.Rune;
 
 /// <summary>
@@ -160,6 +160,7 @@ namespace UICatalog {
 			public StatusItem Numlock;
 			public StatusItem Scrolllock;
 			public StatusItem DriverName;
+			public StatusItem OS;
 
 			public UICatalogTopLevel ()
 			{
@@ -177,19 +178,17 @@ namespace UICatalog {
 							"About UI Catalog", () =>  MessageBox.Query ("About UI Catalog", _aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A),
 					}),
 				});
-
+				
 				Capslock = new StatusItem (Key.CharMask, "Caps", null);
 				Numlock = new StatusItem (Key.CharMask, "Num", null);
 				Scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
 				DriverName = new StatusItem (Key.CharMask, "Driver:", null);
+				OS = new StatusItem (Key.CharMask, "OS:", null);
 
 				StatusBar = new StatusBar () {
 					Visible = true,
 				};
 				StatusBar.Items = new StatusItem [] {
-					Capslock,
-					Numlock,
-					Scrolllock,
 					new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => {
 						if (_selectedScenario is null){
 							// This causes GetScenarioToRun to return null
@@ -199,7 +198,7 @@ namespace UICatalog {
 							_selectedScenario.RequestStop();
 						}
 					}),
-					new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => {
+					new StatusItem(Key.F10, "~F10~ Status Bar", () => {
 						StatusBar.Visible = !StatusBar.Visible;
 						LeftPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
 						RightPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
@@ -207,6 +206,7 @@ namespace UICatalog {
 						SetChildNeedsDisplay();
 					}),
 					DriverName,
+					OS
 				};
 
 				LeftPane = new FrameView ("Categories") {
@@ -281,6 +281,7 @@ namespace UICatalog {
 				miIsMouseDisabled.Checked = Application.IsMouseDisabled;
 				miHeightAsBuffer.Checked = Application.HeightAsBuffer;
 				DriverName.Title = $"Driver: {Driver.GetType ().Name}";
+				OS.Title = $"OS: {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem} {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion}";
 
 				if (_selectedScenario != null) {
 					_selectedScenario = null;

+ 2 - 1
UICatalog/UICatalog.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net6.0</TargetFramework>
+    <TargetFramework>net7.0</TargetFramework>
     <LangVersion>8.0</LangVersion>
     <StartupObject>UICatalog.UICatalogApp</StartupObject>
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
@@ -20,6 +20,7 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="CsvHelper" Version="30.0.1" />
+    <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />

+ 2 - 2
UnitTests/AllViewsTests.cs

@@ -10,7 +10,7 @@ namespace Terminal.Gui.Views {
 		[Fact]
 		public void AllViews_Tests_All_Constructors ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			foreach (var type in GetAllViewClassesCollection ()) {
 				Assert.True (Constructors_FullTest (type));
@@ -127,7 +127,7 @@ namespace Terminal.Gui.Views {
 		public void AllViews_Enter_Leave_Events ()
 		{
 			foreach (var type in GetAllViewClassesCollection ()) {
-				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+				Application.Init (new FakeDriver ());
 
 				var top = Application.Top;
 				var vType = GetTypeInitializer (type, type.GetConstructor (Array.Empty<Type> ()));

+ 14 - 11
UnitTests/ApplicationTests.cs

@@ -45,7 +45,7 @@ namespace Terminal.Gui.Core {
 
 		void Init ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (SynchronizationContext.Current);
@@ -62,7 +62,7 @@ namespace Terminal.Gui.Core {
 			// Verify initial state is per spec
 			Pre_Init_State ();
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			// Verify post-Init state is correct
 			Post_Init_State ();
@@ -75,33 +75,36 @@ namespace Terminal.Gui.Core {
 
 			// Verify state is back to initial
 			Pre_Init_State ();
-
+#if DEBUG_IDISPOSABLE
 			// Validate there are no outstanding Responder-based instances 
 			// after a scenario was selected to run. This proves the main UI Catalog
 			// 'app' closed cleanly.
 			foreach (var inst in Responder.Instances) {
 				Assert.True (inst.WasDisposed);
 			}
+#endif
 		}
 
 		[Fact]
 		public void Init_Shutdown_Toplevel_Not_Disposed ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			Application.Shutdown ();
 
+#if DEBUG_IDISPOSABLE
 			Assert.Single (Responder.Instances);
 			Assert.True (Responder.Instances [0].WasDisposed);
+#endif
 		}
 
 		[Fact]
 		public void Init_Unbalanced_Throwss ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			Toplevel topLevel = null;
-			Assert.Throws<InvalidOperationException> (() => Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))));
+			Assert.Throws<InvalidOperationException> (() => Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ()));
 			Shutdown ();
 
 			Assert.Null (Application.Top);
@@ -110,9 +113,9 @@ namespace Terminal.Gui.Core {
 
 			// Now try the other way
 			topLevel = null;
-			Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ());
 
-			Assert.Throws<InvalidOperationException> (() => Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))));
+			Assert.Throws<InvalidOperationException> (() => Application.Init (new FakeDriver ()));
 			Shutdown ();
 
 			Assert.Null (Application.Top);
@@ -182,7 +185,7 @@ namespace Terminal.Gui.Core {
 			// NOTE: Run<T>, when called after Init has been called behaves differently than
 			// when called if Init has not been called.
 			Toplevel topLevel = null;
-			Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ());
 
 			Application.RunState runstate = null;
 			Action<Application.RunState> NewRunStateFn = (rs) => {
@@ -255,7 +258,7 @@ namespace Terminal.Gui.Core {
 			Init ();
 
 			// Run<Toplevel> when already initialized with a Driver will throw (because Toplevel is not dervied from TopLevel)
-			Assert.Throws<ArgumentException> (() => Application.Run<Toplevel> (errorHandler: null, new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))));
+			Assert.Throws<ArgumentException> (() => Application.Run<Toplevel> (errorHandler: null, new FakeDriver ()));
 
 			Shutdown ();
 
@@ -354,7 +357,7 @@ namespace Terminal.Gui.Core {
 			};
 
 			// Init has NOT been called and we're passing a valid driver to Run<TestTopLevel>. This is ok.
-			Application.Run<TestToplevel> (errorHandler: null, new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Run<TestToplevel> (errorHandler: null, new FakeDriver ());
 
 			Shutdown ();
 

+ 4 - 4
UnitTests/AttributeTests.cs

@@ -13,7 +13,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 		public void Constuctors_Constuct ()
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver);
 			driver.Init (() => { });
 
 			// Test parameterless constructor
@@ -59,7 +59,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 		public void Implicit_Assign ()
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver);
 			driver.Init (() => { });
 
 			var attr = new Attribute ();
@@ -100,7 +100,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 		public void Make_Creates ()
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver);
 			driver.Init (() => { });
 
 			var fg = new Color ();
@@ -128,7 +128,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 		public void Get_Gets ()
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver);
 			driver.Init (() => { });
 
 			var value = 42;

+ 165 - 189
UnitTests/ClipboardTests.cs

@@ -1,25 +1,104 @@
-using System.Diagnostics;
+using System;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
 using Xunit;
+using Xunit.Abstractions;
+using static AutoInitShutdownAttribute;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ConsoleDrivers {
 	public class ClipboardTests {
-		[Fact]
-		[AutoInitShutdown]
+		readonly ITestOutputHelper output;
+
+		public ClipboardTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		[Fact, AutoInitShutdown (useFakeClipboard: true, fakeClipboardAlwaysThrowsNotSupportedException: true)]
+		public void IClipboard_GetClipBoardData_Throws_NotSupportedException ()
+		{
+			IClipboard iclip = Application.Driver.Clipboard;
+			Assert.Throws<NotSupportedException> (() => iclip.GetClipboardData ());
+		}
+
+		[Fact, AutoInitShutdown (useFakeClipboard: true, fakeClipboardAlwaysThrowsNotSupportedException: true)]
+		public void IClipboard_SetClipBoardData_Throws_NotSupportedException ()
+		{
+			IClipboard iclip = Application.Driver.Clipboard;
+			Assert.Throws<NotSupportedException> (() => iclip.SetClipboardData ("foo"));
+		}
+
+		[Fact, AutoInitShutdown (useFakeClipboard: true)]
+		public void Contents_Fake_Gets_Sets ()
+		{
+			if (!Clipboard.IsSupported) {
+				output.WriteLine ($"The Clipboard not supported on this platform.");
+				return;
+			}
+
+			var clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard.";
+			Clipboard.Contents = clipText;
+
+			Application.Iteration += () => Application.RequestStop ();
+			Application.Run ();
+
+			Assert.Equal (clipText, Clipboard.Contents.ToString ());
+		}
+
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
 		public void Contents_Gets_Sets ()
 		{
-			var clipText = "This is a clipboard unit test.";
+			if (!Clipboard.IsSupported) {
+				output.WriteLine ($"The Clipboard not supported on this platform.");
+				return;
+			}
+
+			var clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard.";
 			Clipboard.Contents = clipText;
 
 			Application.Iteration += () => Application.RequestStop ();
+			Application.Run ();
+
+			Assert.Equal (clipText, Clipboard.Contents.ToString ());
+		}
+
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
+		public void Contents_Gets_Sets_When_IsSupportedFalse ()
+		{
+
+			if (!Clipboard.IsSupported) {
+				output.WriteLine ($"The Clipboard not supported on this platform.");
+				return;
+			}
+
+			var clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard.";
+			Clipboard.Contents = clipText;
 
+			Application.Iteration += () => Application.RequestStop ();
 			Application.Run ();
 
-			Assert.Equal (clipText, Clipboard.Contents);
+			Assert.Equal (clipText, Clipboard.Contents.ToString ());
 		}
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown (useFakeClipboard: true)]
+		public void Contents_Fake_Gets_Sets_When_IsSupportedFalse ()
+		{
+
+			if (!Clipboard.IsSupported) {
+				output.WriteLine ($"The Clipboard not supported on this platform.");
+				return;
+			}
+
+			var clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard.";
+			Clipboard.Contents = clipText;
+
+			Application.Iteration += () => Application.RequestStop ();
+			Application.Run ();
+
+			Assert.Equal (clipText, Clipboard.Contents.ToString ());
+		}
+
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
 		public void IsSupported_Get ()
 		{
 			if (Clipboard.IsSupported) {
@@ -29,11 +108,10 @@ namespace Terminal.Gui.Core {
 			}
 		}
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
 		public void TryGetClipboardData_Gets_From_OS_Clipboard ()
 		{
-			var clipText = "Trying to get from the OS clipboard.";
+			var clipText = "The TryGetClipboardData_Gets_From_OS_Clipboard unit test pasted this to the OS clipboard.";
 			Clipboard.Contents = clipText;
 
 			Application.Iteration += () => Application.RequestStop ();
@@ -49,11 +127,10 @@ namespace Terminal.Gui.Core {
 			}
 		}
 
-		[Fact]
-		[AutoInitShutdown]
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
 		public void TrySetClipboardData_Sets_The_OS_Clipboard ()
 		{
-			var clipText = "Trying to set the OS clipboard.";
+			var clipText = "The TrySetClipboardData_Sets_The_OS_Clipboard unit test pasted this to the OS clipboard.";
 			if (Clipboard.IsSupported) {
 				Assert.True (Clipboard.TrySetClipboardData (clipText));
 			} else {
@@ -71,113 +148,67 @@ namespace Terminal.Gui.Core {
 			}
 		}
 
-		[Fact]
-		[AutoInitShutdown]
-		public void Contents_Gets_From_OS_Clipboard ()
+
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
+		public void Contents_Copies_From_OS_Clipboard ()
 		{
-			var clipText = "This is a clipboard unit test to get clipboard from OS.";
-			var exit = false;
+			if (!Clipboard.IsSupported) {
+				output.WriteLine ($"The Clipboard not supported on this platform.");
+				return;
+			}
+
+			var clipText = "The Contents_Copies_From_OS_Clipboard unit test pasted this to the OS clipboard.";
+			var failed = false;
 			var getClipText = "";
 
 			Application.Iteration += () => {
+				int exitCode = 0;
+				string result = "";
+				output.WriteLine ($"Pasting to OS clipboard: {clipText}...");
+
 				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
-					// using (Process clipExe = new Process {
-					// 	StartInfo = new ProcessStartInfo {
-					// 		RedirectStandardInput = true,
-					// 		FileName = "clip"
-					// 	}
-					// }) {
-					// 	clipExe.Start ();
-					// 	clipExe.StandardInput.Write (clipText);
-					// 	clipExe.StandardInput.Close ();
-					// 	var result = clipExe.WaitForExit (500);
-					// 	if (result) {
-					// 		clipExe.WaitForExit ();
-					// 	}
-					// }
-
-					using (Process pwsh = new Process {
-						StartInfo = new ProcessStartInfo {
-							FileName = "powershell",
-							Arguments = $"-command \"Set-Clipboard -Value \\\"{clipText}\\\"\""
-						}
-					}) {
-						pwsh.Start ();
-						pwsh.WaitForExit ();
-					}
+					(exitCode, result) = ClipboardProcessRunner.Process ("pwsh", $"-command \"Set-Clipboard -Value \\\"{clipText}\\\"\"");
+					output.WriteLine ($"  Windows: pwsh Set-Clipboard: exitCode = {exitCode}, result = {result}");
 					getClipText = Clipboard.Contents.ToString ();
 
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-					using (Process copy = new Process {
-						StartInfo = new ProcessStartInfo {
-							RedirectStandardInput = true,
-							FileName = "pbcopy"
-						}
-					}) {
-						copy.Start ();
-						copy.StandardInput.Write (clipText);
-						copy.StandardInput.Close ();
-						copy.WaitForExit ();
-					}
+					(exitCode, result) = ClipboardProcessRunner.Process ("pbcopy", string.Empty, clipText);
+					output.WriteLine ($"  OSX: pbcopy: exitCode = {exitCode}, result = {result}");
 					getClipText = Clipboard.Contents.ToString ();
 
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
 					if (Is_WSL_Platform ()) {
 						try {
-							using (Process bash = new Process {
-								StartInfo = new ProcessStartInfo {
-									FileName = "powershell.exe",
-									Arguments = $"-noprofile -command \"Set-Clipboard -Value \\\"{clipText}\\\"\""
-								}
-							}) {
-								bash.Start ();
-								bash.WaitForExit ();
-							}
-
-							//using (Process clipExe = new Process {
-							//	StartInfo = new ProcessStartInfo {
-							//		RedirectStandardInput = true,
-							//		FileName = "clip.exe"
-							//	}
-							//}) {
-							//	clipExe.Start ();
-							//	clipExe.StandardInput.Write (clipText);
-							//	clipExe.StandardInput.Close ();
-							//	clipExe.WaitForExit ();
-							//	//var result = clipExe.WaitForExit (500);
-							//	//if (result) {
-							//	//	clipExe.WaitForExit ();
-							//	//}
-							//}
+							// This runs the WINDOWS version of powershell.exe via WSL.
+							(exitCode, result) = ClipboardProcessRunner.Process ("powershell.exe", $"-noprofile -command \"Set-Clipboard -Value \\\"{clipText}\\\"\"");
+							output.WriteLine ($"  WSL: powershell.exe Set-Clipboard: exitCode = {exitCode}, result = {result}");
 						} catch {
-							exit = true;
+							failed = true;
 						}
-						if (!exit) {
+
+						if (!failed) {
+							// If we set the OS clipboard via Powershell, then getting Contents should return the same text.
 							getClipText = Clipboard.Contents.ToString ();
+							output.WriteLine ($"  WSL: Clipboard.Contents: {getClipText}");
 						}
 						Application.RequestStop ();
 						return;
 					}
-					if (exit = xclipExists () == false) {
-						// xclip doesn't exist then exit.
+
+					if (failed = xclipExists () == false) {
+						// if xclip doesn't exist then exit.
+						output.WriteLine ($"  WSL: no xclip found.");
 						Application.RequestStop ();
 						return;
 					}
 
-					using (Process bash = new Process {
-						StartInfo = new ProcessStartInfo {
-							FileName = "bash",
-							Arguments = $"-c \"xclip -sel clip -i\"",
-							RedirectStandardInput = true,
-						}
-					}) {
-						bash.Start ();
-						bash.StandardInput.Write (clipText);
-						bash.StandardInput.Close ();
-						bash.WaitForExit ();
-					}
-					if (!exit) {
+					// If we get here, powershell didn't work and xclip exists...
+					(exitCode, result) = ClipboardProcessRunner.Process ("bash", $"-c \"xclip -sel clip -i\"", clipText);
+					output.WriteLine ($"  Linux: bash xclip -sel clip -i: exitCode = {exitCode}, result = {result}");
+
+					if (!failed) {
 						getClipText = Clipboard.Contents.ToString ();
+						output.WriteLine ($"  Linux via xclip: Clipboard.Contents: {getClipText}");
 					}
 				}
 
@@ -186,84 +217,57 @@ namespace Terminal.Gui.Core {
 
 			Application.Run ();
 
-			if (!exit) {
+			if (!failed) {
 				Assert.Equal (clipText, getClipText);
 			}
 		}
 
-		[Fact]
-		[AutoInitShutdown]
-		public void Contents_Sets_The_OS_Clipboard ()
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
+		public void Contents_Pastes_To_OS_Clipboard ()
 		{
-			var clipText = "This is a clipboard unit test to set the OS clipboard.";
+			if (!Clipboard.IsSupported) {
+				output.WriteLine ($"The Clipboard not supported on this platform.");
+				return;
+			}
+
+			var clipText = "The Contents_Pastes_To_OS_Clipboard unit test pasted this via Clipboard.Contents.";
 			var clipReadText = "";
-			var exit = false;
+			var failed = false;
 
 			Application.Iteration += () => {
 				Clipboard.Contents = clipText;
 
+				int exitCode = 0;
+				output.WriteLine ($"Getting OS clipboard...");
+
 				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
-					using (Process pwsh = new Process {
-						StartInfo = new ProcessStartInfo {
-							RedirectStandardOutput = true,
-							FileName = "powershell.exe",
-							Arguments = "-noprofile -command \"Get-Clipboard\""
-						}
-					}) {
-						pwsh.Start ();
-						clipReadText = pwsh.StandardOutput.ReadToEnd ().TrimEnd ();
-						pwsh.StandardOutput.Close ();
-						pwsh.WaitForExit ();
-					}
+					(exitCode, clipReadText) = ClipboardProcessRunner.Process ("pwsh", "-noprofile -command \"Get-Clipboard\"");
+					output.WriteLine ($"  Windows: pwsh Get-Clipboard: exitCode = {exitCode}, result = {clipReadText}");
+
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-					using (Process paste = new Process {
-						StartInfo = new ProcessStartInfo {
-							RedirectStandardOutput = true,
-							FileName = "pbpaste"
-						}
-					}) {
-						paste.Start ();
-						clipReadText = paste.StandardOutput.ReadToEnd ();
-						paste.StandardOutput.Close ();
-						paste.WaitForExit ();
-					}
+					(exitCode, clipReadText) = ClipboardProcessRunner.Process ("pbpaste", "");
+					output.WriteLine ($"  OSX: pbpaste: exitCode = {exitCode}, result = {clipReadText}");
+
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
 					if (Is_WSL_Platform ()) {
-						try {
-							using (Process bash = new Process {
-								StartInfo = new ProcessStartInfo {
-									RedirectStandardOutput = true,
-									FileName = "powershell.exe",
-									Arguments = "-noprofile -command \"Get-Clipboard\""
-								}
-							}) {
-								bash.Start ();
-								clipReadText = bash.StandardOutput.ReadToEnd ();
-								bash.StandardOutput.Close ();
-								bash.WaitForExit ();
-							}
-						} catch {
-							exit = true;
+						(exitCode, clipReadText) = ClipboardProcessRunner.Process ("powershell.exe", "-noprofile -command \"Get-Clipboard\"");
+						output.WriteLine ($"  WSL: powershell.exe Get-Clipboard: exitCode = {exitCode}, result = {clipReadText}");
+						if (exitCode == 0) {
+							Application.RequestStop ();
+							return;
 						}
-						Application.RequestStop ();
+						failed = true;
 					}
-					if (exit = xclipExists () == false) {
+
+					if (failed = xclipExists () == false) {
 						// xclip doesn't exist then exit.
 						Application.RequestStop ();
+						return;
 					}
 
-					using (Process bash = new Process {
-						StartInfo = new ProcessStartInfo {
-							RedirectStandardOutput = true,
-							FileName = "bash",
-							Arguments = $"-c \"xclip -sel clip -o\""
-						}
-					}) {
-						bash.Start ();
-						clipReadText = bash.StandardOutput.ReadToEnd ();
-						bash.StandardOutput.Close ();
-						bash.WaitForExit ();
-					}
+					(exitCode, clipReadText) = ClipboardProcessRunner.Process ("bash", $"-c \"xclip -sel clip -o\"");
+					output.WriteLine ($"  Linux: bash xclip -sel clip -o: exitCode = {exitCode}, result = {clipReadText}");
+					Assert.Equal (0, exitCode);
 				}
 
 				Application.RequestStop ();
@@ -271,51 +275,23 @@ namespace Terminal.Gui.Core {
 
 			Application.Run ();
 
-			if (!exit) {
-				Assert.Equal (clipText, clipReadText);
+			if (!failed) {
+				Assert.Equal (clipText, clipReadText.TrimEnd ());
 			}
+
 		}
 
 		bool Is_WSL_Platform ()
 		{
-			using (Process bash = new Process {
-				StartInfo = new ProcessStartInfo {
-					FileName = "bash",
-					Arguments = $"-c \"uname -a\"",
-					RedirectStandardOutput = true,
-				}
-			}) {
-				bash.Start ();
-				var result = bash.StandardOutput.ReadToEnd ();
-				var isWSL = false;
-				if (result.Contains ("microsoft") && result.Contains ("WSL")) {
-					isWSL = true;
-				}
-				bash.StandardOutput.Close ();
-				bash.WaitForExit ();
-				return isWSL;
-			}
+			var (_, result) = ClipboardProcessRunner.Process ("bash", $"-c \"uname -a\"");
+			return result.Contains ("microsoft") && result.Contains ("WSL");
 		}
 
 		bool xclipExists ()
 		{
 			try {
-				using (Process bash = new Process {
-					StartInfo = new ProcessStartInfo {
-						FileName = "bash",
-						Arguments = $"-c \"which xclip\"",
-						RedirectStandardOutput = true,
-					}
-				}) {
-					bash.Start ();
-					bool exist = bash.StandardOutput.ReadToEnd ().TrimEnd () != "";
-					bash.StandardOutput.Close ();
-					bash.WaitForExit ();
-					if (exist) {
-						return true;
-					}
-				}
-				return false;
+				var (_, result) = ClipboardProcessRunner.Process ("bash", $"-c \"which xclip\"");
+				return result.TrimEnd () != "";
 			} catch (System.Exception) {
 				return false;
 			}

+ 70 - 45
UnitTests/ConsoleDriverTests.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using Terminal.Gui.Views;
 using Xunit;
@@ -18,11 +19,15 @@ namespace Terminal.Gui.ConsoleDrivers {
 			this.output = output;
 		}
 
-		[Fact]
-		public void Init_Inits ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		//[InlineData (typeof (NetDriver))]
+		//[InlineData (typeof (CursesDriver))]
+		//[InlineData (typeof (WindowsDriver))]
+		public void Init_Inits (Type driverType)
 		{
-			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 			driver.Init (() => { });
 
 			Assert.Equal (80, Console.BufferWidth);
@@ -37,11 +42,15 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void End_Cleans_Up ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		//[InlineData (typeof (NetDriver))]
+		//[InlineData (typeof (CursesDriver))]
+		//[InlineData (typeof (WindowsDriver))]
+		public void End_Cleans_Up (Type driverType)
 		{
-			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 			driver.Init (() => { });
 
 			FakeConsole.ForegroundColor = ConsoleColor.Red;
@@ -63,11 +72,15 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void SetColors_Changes_Colors ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		//[InlineData (typeof (NetDriver))]
+		//[InlineData (typeof (CursesDriver))]
+		//[InlineData (typeof (WindowsDriver))]
+		public void SetColors_Changes_Colors (Type driverType)
 		{
-			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 			driver.Init (() => { });
 			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
 			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
@@ -87,10 +100,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses (Type driverType)
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 
 			var top = Application.Top;
 			var view = new View ();
@@ -117,10 +132,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void FakeDriver_MockKeyPresses ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void FakeDriver_MockKeyPresses (Type driverType)
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 
 			var text = "MockKeyPresses";
 			var mKeys = new Stack<ConsoleKeyInfo> ();
@@ -159,10 +176,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void SendKeys_Test ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void SendKeys_Test (Type driverType)
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 
 			var top = Application.Top;
 			var view = new View ();
@@ -256,11 +275,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void TerminalResized_Simulation ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void TerminalResized_Simulation (Type driverType)
 		{
-			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 			var wasTerminalResized = false;
 			Application.Resized = (e) => {
 				wasTerminalResized = true;
@@ -297,11 +317,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void HeightAsBuffer_Is_False_Left_And_Top_Is_Always_Zero ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void HeightAsBuffer_Is_False_Left_And_Top_Is_Always_Zero (Type driverType)
 		{
-			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 
 			Assert.False (Application.HeightAsBuffer);
 			Assert.Equal (0, Console.WindowLeft);
@@ -314,11 +335,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void HeightAsBuffer_Is_True_Left_Cannot_Be_Greater_Than_WindowWidth ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void HeightAsBuffer_Is_True_Left_Cannot_Be_Greater_Than_WindowWidth (Type driverType)
 		{
-			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 
 			Application.HeightAsBuffer = true;
 			Assert.True (Application.HeightAsBuffer);
@@ -330,11 +352,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void HeightAsBuffer_Is_True_Left_Cannot_Be_Greater_Than_BufferWidth_Minus_WindowWidth ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void HeightAsBuffer_Is_True_Left_Cannot_Be_Greater_Than_BufferWidth_Minus_WindowWidth (Type driverType)
 		{
-			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 
 			Application.HeightAsBuffer = true;
 			Assert.True (Application.HeightAsBuffer);
@@ -369,11 +392,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void HeightAsBuffer_Is_True_Top_Cannot_Be_Greater_Than_WindowHeight ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void HeightAsBuffer_Is_True_Top_Cannot_Be_Greater_Than_WindowHeight (Type driverType)
 		{
-			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 
 			Application.HeightAsBuffer = true;
 			Assert.True (Application.HeightAsBuffer);
@@ -385,11 +409,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void HeightAsBuffer_Is_True_Top_Cannot_Be_Greater_Than_BufferHeight_Minus_WindowHeight ()
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void HeightAsBuffer_Is_True_Top_Cannot_Be_Greater_Than_BufferHeight_Minus_WindowHeight (Type driverType)
 		{
-			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
 
 			Application.HeightAsBuffer = true;
 			Assert.True (Application.HeightAsBuffer);

+ 11 - 0
UnitTests/DialogTests.cs

@@ -575,5 +575,16 @@ namespace Terminal.Gui.Views {
 			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void FileDialog_FileSystemWatcher ()
+		{
+			for (int i = 0; i < 8; i++) {
+				var fd = new FileDialog ();
+				fd.Ready += () => Application.RequestStop ();
+				Application.Run (fd);
+			}
+		}
 	}
 }

+ 10 - 10
UnitTests/DimTests.cs

@@ -252,7 +252,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void ForceValidatePosDim_True_Dim_Validation_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -292,7 +292,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Null ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -313,7 +313,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -346,7 +346,7 @@ namespace Terminal.Gui.Core {
 		{
 			// Testing with the Button because it properly handles the Dim class.
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -541,7 +541,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void DimCombine_Do_Not_Throws ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -588,7 +588,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void PosCombine_Will_Throws ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -622,7 +622,7 @@ namespace Terminal.Gui.Core {
 		public void Dim_Add_Operator ()
 		{
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var top = Application.Top;
 
@@ -989,7 +989,7 @@ namespace Terminal.Gui.Core {
 		public void Dim_Add_Operator_With_Text ()
 		{
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var top = Application.Top;
 
@@ -1056,7 +1056,7 @@ namespace Terminal.Gui.Core {
 		public void Dim_Subtract_Operator ()
 		{
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var top = Application.Top;
 
@@ -1116,7 +1116,7 @@ namespace Terminal.Gui.Core {
 		public void Dim_Subtract_Operator_With_Text ()
 		{
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var top = Application.Top;
 

+ 2 - 2
UnitTests/GraphViewTests.cs

@@ -58,7 +58,7 @@ namespace Terminal.Gui.Views {
 		{
 			var driver = new FakeDriver ();
 			try {
-				Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+				Application.Init (driver);
 			} catch (InvalidOperationException) {
 
 				// close it so that we don't get a thousand of these errors in a row
@@ -1506,7 +1506,7 @@ namespace Terminal.Gui.Views {
 		public void LabelChangeText_RendersCorrectly (bool useFill)
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver);
 			driver.Init (() => { });
 
 			// create a wide window

+ 23 - 18
UnitTests/MainLoopTests.cs

@@ -14,12 +14,17 @@ using Xunit.Sdk;
 using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui.Core {
+	/// <summary>
+	/// Tests MainLoop using the FakeMainLoop.
+	/// </summary>
 	public class MainLoopTests {
 
+		// TODO: Expand to test all the MainLoop implementations.
+
 		[Fact]
 		public void Constructor_Setups_Driver ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 			Assert.NotNull (ml.Driver);
 		}
 
@@ -27,7 +32,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddIdle_Adds_And_Removes ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 
 			Func<bool> fnTrue = () => true;
 			Func<bool> fnFalse = () => false;
@@ -81,7 +86,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddIdle_Function_GetsCalled_OnIteration ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 
 			var functionCalled = 0;
 			Func<bool> fn = () => {
@@ -97,7 +102,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void RemoveIdle_Function_NotCalled ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 
 			var functionCalled = 0;
 			Func<bool> fn = () => {
@@ -113,7 +118,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddThenRemoveIdle_Function_NotCalled ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 
 			var functionCalled = 0;
 			Func<bool> fn = () => {
@@ -130,7 +135,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddTwice_Function_CalledTwice ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 
 			var functionCalled = 0;
 			Func<bool> fn = () => {
@@ -161,7 +166,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void False_Idle_Stops_It_Being_Called_Again ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 
 			var functionCalled = 0;
 			Func<bool> fn1 = () => {
@@ -195,7 +200,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddIdle_Twice_Returns_False_Called_Twice ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 
 			var functionCalled = 0;
 			Func<bool> fn1 = () => {
@@ -227,7 +232,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Run_Runs_Idle_Stop_Stops_Idle ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 
 			var functionCalled = 0;
 			Func<bool> fn = () => {
@@ -249,7 +254,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddTimer_Adds_Removes_NoFaults ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 			var ms = 100;
 
 			var callbackCount = 0;
@@ -270,7 +275,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddTimer_Run_Called ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 			var ms = 100;
 
 			var callbackCount = 0;
@@ -290,7 +295,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public async Task AddTimer_Duplicate_Keys_Not_Allowed ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 			const int ms = 100;
 			object token1 = null, token2 = null;
 
@@ -322,7 +327,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddTimer_In_Parallel_Wont_Throw ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 			const int ms = 100;
 			object token1 = null, token2 = null;
 
@@ -359,7 +364,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddTimer_Run_CalledAtApproximatelyRightTime ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 			var ms = TimeSpan.FromMilliseconds (50);
 			var watch = new System.Diagnostics.Stopwatch ();
 
@@ -385,7 +390,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 			var ms = TimeSpan.FromMilliseconds (50);
 			var watch = new System.Diagnostics.Stopwatch ();
 
@@ -413,7 +418,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddTimer_Remove_NotCalled ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 			var ms = TimeSpan.FromMilliseconds (50);
 
 			// Force stop if 10 iterations
@@ -442,7 +447,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void AddTimer_ReturnFalse_StopsBeingCalled ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 			var ms = TimeSpan.FromMilliseconds (50);
 
 			// Force stop if 10 iterations
@@ -475,7 +480,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Invoke_Adds_Idle ()
 		{
-			var ml = new MainLoop (new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			var ml = new MainLoop (new FakeMainLoop ());
 
 			var actionCalled = 0;
 			ml.Invoke (() => { actionCalled++; });

+ 6 - 3
UnitTests/MdiTests.cs

@@ -22,7 +22,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Dispose_Toplevel_IsMdiContainer_False_With_Begin_End ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var top = new Toplevel ();
 			var rs = Application.Begin (top);
@@ -30,25 +30,28 @@ namespace Terminal.Gui.Core {
 
 			Application.Shutdown ();
 
+#if DEBUG_IDISPOSABLE
 			Assert.Equal (2, Responder.Instances.Count);
 			Assert.True (Responder.Instances [0].WasDisposed);
 			Assert.True (Responder.Instances [1].WasDisposed);
+#endif
 		}
 
 		[Fact]
 		public void Dispose_Toplevel_IsMdiContainer_True_With_Begin ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var mdi = new Toplevel { IsMdiContainer = true };
 			var rs = Application.Begin (mdi);
 			Application.End (rs);
 
 			Application.Shutdown ();
-
+#if DEBUG_IDISPOSABLE
 			Assert.Equal (2, Responder.Instances.Count);
 			Assert.True (Responder.Instances [0].WasDisposed);
 			Assert.True (Responder.Instances [1].WasDisposed);
+#endif
 		}
 
 		[Fact, AutoInitShutdown]

+ 8 - 8
UnitTests/PosTests.cs

@@ -555,7 +555,7 @@ namespace Terminal.Gui.Core {
 			// Setup Fake driver
 			(Window win, Button button) setup ()
 			{
-				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+				Application.Init (new FakeDriver ());
 				Application.Iteration = () => {
 					Application.RequestStop ();
 				};
@@ -700,7 +700,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void ForceValidatePosDim_True_Pos_Validation_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -733,7 +733,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -755,7 +755,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -788,7 +788,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void PosCombine_Do_Not_Throws ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -835,7 +835,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void PosCombine_Will_Throws ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -868,7 +868,7 @@ namespace Terminal.Gui.Core {
 		public void Pos_Add_Operator ()
 		{
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var top = Application.Top;
 
@@ -917,7 +917,7 @@ namespace Terminal.Gui.Core {
 		public void Pos_Subtract_Operator ()
 		{
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var top = Application.Top;
 

+ 9 - 2
UnitTests/RunStateTests.cs

@@ -40,8 +40,9 @@ namespace Terminal.Gui.Core {
 
 			// Should not throw because Toplevel was null
 			rs.Dispose ();
+#if DEBUG_IDISPOSABLE
 			Assert.True (rs.WasDisposed);
-
+#endif
 			var top = new Toplevel ();
 			rs = new Application.RunState (top);
 			Assert.NotNull (rs);
@@ -52,13 +53,15 @@ namespace Terminal.Gui.Core {
 			rs.Toplevel.Dispose ();
 			rs.Toplevel = null;
 			rs.Dispose ();
+#if DEBUG_IDISPOSABLE
 			Assert.True (rs.WasDisposed);
 			Assert.True (top.WasDisposed);
+#endif
 		}
 
 		void Init ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (SynchronizationContext.Current);
@@ -67,10 +70,12 @@ namespace Terminal.Gui.Core {
 		void Shutdown ()
 		{
 			Application.Shutdown ();
+#if DEBUG_IDISPOSABLE
 			// Validate there are no outstanding RunState-based instances left
 			foreach (var inst in Application.RunState.Instances) {
 				Assert.True (inst.WasDisposed);
 			}
+#endif
 		}
 
 		[Fact]
@@ -95,7 +100,9 @@ namespace Terminal.Gui.Core {
 
 			Shutdown ();
 
+#if DEBUG_IDISPOSABLE
 			Assert.True (rs.WasDisposed);
+#endif
 
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);

+ 3 - 3
UnitTests/ScenarioTests.cs

@@ -61,7 +61,7 @@ namespace UICatalog {
 					return false;
 				};
 
-				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+				Application.Init (new FakeDriver ());
 
 				// Close after a short period of time
 				var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), closeCallback);
@@ -97,7 +97,7 @@ namespace UICatalog {
 			// Passing empty string will cause just a ctrl-q to be fired
 			int stackSize = CreateInput ("");
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			int iterations = 0;
 			Application.Iteration = () => {
@@ -179,7 +179,7 @@ namespace UICatalog {
 			List<string> dimNames = new List<String> { "Factor", "Fill", "Absolute" };
 
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var Top = Application.Top;
 

+ 21 - 30
UnitTests/ScrollBarViewTests.cs

@@ -20,19 +20,11 @@ namespace Terminal.Gui.Views {
 		// This is necessary because a) Application is a singleton and Init/Shutdown must be called
 		// as a pair, and b) all unit test functions should be atomic.
 		[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
-		public class InitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
+		public class ScrollBarAutoInitShutdownAttribute : AutoInitShutdownAttribute {
 
 			public override void Before (MethodInfo methodUnderTest)
 			{
-				Debug.WriteLine ($"Before: {methodUnderTest.Name}");
-
-				if (_hostView != null) {
-					throw new InvalidOperationException ("After did not run.");
-				}
-
-				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
-
-				var top = Application.Top;
+				base.Before (methodUnderTest);
 
 				ScrollBarViewTests._hostView = new HostView () {
 					Width = Dim.Fill (),
@@ -43,14 +35,13 @@ namespace Terminal.Gui.Views {
 					Cols = 100
 				};
 
-				top.Add (ScrollBarViewTests._hostView);
+				Application.Top.Add (ScrollBarViewTests._hostView);
 			}
 
 			public override void After (MethodInfo methodUnderTest)
 			{
-				Debug.WriteLine ($"After: {methodUnderTest.Name}");
 				ScrollBarViewTests._hostView = null;
-				Application.Shutdown ();
+				base.After (methodUnderTest);
 			}
 		}
 
@@ -113,7 +104,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void Hosting_A_Null_View_To_A_ScrollBarView_Throws_ArgumentNullException ()
 		{
 			Assert.Throws<ArgumentNullException> ("The host parameter can't be null.",
@@ -123,7 +114,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void Hosting_A_Null_SuperView_View_To_A_ScrollBarView_Throws_ArgumentNullException ()
 		{
 			Assert.Throws<ArgumentNullException> ("The host SuperView parameter can't be null.",
@@ -133,7 +124,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void Hosting_Two_Vertical_ScrollBarView_Throws_ArgumentException ()
 		{
 			var top = new Toplevel ();
@@ -147,7 +138,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void Hosting_Two_Horizontal_ScrollBarView_Throws_ArgumentException ()
 		{
 			var top = new Toplevel ();
@@ -161,7 +152,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void Scrolling_With_Default_Constructor_Do_Not_Scroll ()
 		{
 			var sbv = new ScrollBarView {
@@ -172,7 +163,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void Hosting_A_View_To_A_ScrollBarView ()
 		{
 			RemoveHandlers ();
@@ -198,7 +189,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void ChangedPosition_Update_The_Hosted_View ()
 		{
 			Hosting_A_View_To_A_ScrollBarView ();
@@ -213,7 +204,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void ChangedPosition_Scrolling ()
 		{
 			Hosting_A_View_To_A_ScrollBarView ();
@@ -240,7 +231,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void ChangedPosition_Negative_Value ()
 		{
 			Hosting_A_View_To_A_ScrollBarView ();
@@ -257,7 +248,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void DrawContent_Update_The_ScrollBarView_Position ()
 		{
 			Hosting_A_View_To_A_ScrollBarView ();
@@ -274,7 +265,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void OtherScrollBarView_Not_Null ()
 		{
 			Hosting_A_View_To_A_ScrollBarView ();
@@ -287,7 +278,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void ShowScrollIndicator_Check ()
 		{
 			Hosting_A_View_To_A_ScrollBarView ();
@@ -299,7 +290,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void KeepContentAlwaysInViewport_True ()
 		{
 			Hosting_A_View_To_A_ScrollBarView ();
@@ -339,7 +330,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void KeepContentAlwaysInViewport_False ()
 		{
 			Hosting_A_View_To_A_ScrollBarView ();
@@ -361,7 +352,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[ScrollBarAutoInitShutdown]
 		public void AutoHideScrollBars_Check ()
 		{
 			Hosting_A_View_To_A_ScrollBarView ();
@@ -457,7 +448,7 @@ namespace Terminal.Gui.Views {
 		public void Constructor_ShowBothScrollIndicator_False_And_IsVertical_True_Refresh_Does_Not_Throws_An_Object_Null_Exception ()
 		{
 			var exception = Record.Exception (() => {
-				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+				Application.Init (new FakeDriver ());
 
 				var top = Application.Top;
 
@@ -527,7 +518,7 @@ namespace Terminal.Gui.Views {
 		public void Constructor_ShowBothScrollIndicator_False_And_IsVertical_False_Refresh_Does_Not_Throws_An_Object_Null_Exception ()
 		{
 			var exception = Record.Exception (() => {
-				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+				Application.Init (new FakeDriver ());
 
 				var top = Application.Top;
 

+ 1 - 1
UnitTests/StatusBarTests.cs

@@ -36,7 +36,7 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (1, sb.Height);
 
 			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver);
 
 			sb = new StatusBar ();
 

+ 1 - 1
UnitTests/TabViewTests.cs

@@ -763,7 +763,7 @@ namespace Terminal.Gui.Views {
 		private void InitFakeDriver ()
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver);
 			driver.Init (() => { });
 		}
 	}

+ 50 - 11
UnitTests/TestHelpers.cs

@@ -10,37 +10,76 @@ using Rune = System.Rune;
 using Attribute = Terminal.Gui.Attribute;
 using System.Text.RegularExpressions;
 using System.Reflection;
+using System.Diagnostics;
 
 
 // This class enables test functions annotated with the [AutoInitShutdown] attribute to 
-// automatically call Application.Init before called and Application.Shutdown after
+// automatically call Application.Init at start of the test and Application.Shutdown after the
+// test exits. 
 // 
 // This is necessary because a) Application is a singleton and Init/Shutdown must be called
-// as a pair, and b) all unit test functions should be atomic.
+// as a pair, and b) all unit test functions should be atomic..
 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
 public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
+	/// <summary>
+	/// Initializes a [AutoInitShutdown] attribute, which determines if/how Application.Init and
+	/// Application.Shutdown are automatically called Before/After a test runs.
+	/// </summary>
+	/// <param name="autoInit">If true, Application.Init will be called Before the test runs.</param>
+	/// <param name="autoShutdown">If true, Application.Shutdown will be called After the test runs.</param>
+	/// <param name="consoleDriverType">Determins which ConsoleDriver (FakeDriver, WindowsDriver, 
+	/// CursesDriver, NetDriver) will be used when Appliation.Init is called. If null FakeDriver will be used.
+	/// Only valid if <paramref name="autoInit"/> is true.</param>
+	/// <param name="useFakeClipboard">If true, will force the use of <see cref="FakeDriver.FakeClipboard"/>. 
+	/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
+	/// <param name="fakeClipboardAlwaysThrowsNotSupportedException">Only valid if <paramref name="autoInit"/> is true.
+	/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
+	/// <param name="fakeClipboardIsSupportedAlwaysTrue">Only valid if <paramref name="autoInit"/> is true.
+	/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
+	public AutoInitShutdownAttribute (bool autoInit = true, bool autoShutdown = true,
+		Type consoleDriverType = null, 
+		bool useFakeClipboard = false, 
+		bool fakeClipboardAlwaysThrowsNotSupportedException = false, 
+		bool fakeClipboardIsSupportedAlwaysTrue = false)
+	{
+		//Assert.True (autoInit == false && consoleDriverType == null);
+
+		AutoInit = autoInit;
+		AutoShutdown = autoShutdown;
+		DriverType = consoleDriverType ?? typeof (FakeDriver);
+		FakeDriver.FakeBehaviors.UseFakeClipboard = useFakeClipboard;
+		FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
+		FakeDriver.FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
+	}
 
 	static bool _init = false;
+	bool AutoInit { get; }
+	bool AutoShutdown { get;  }
+	Type DriverType;
+
 	public override void Before (MethodInfo methodUnderTest)
 	{
-		if (_init) {
-			throw new InvalidOperationException ("After did not run.");
+		Debug.WriteLine ($"Before: {methodUnderTest.Name}");
+		if (AutoShutdown && _init) {
+			throw new InvalidOperationException ("After did not run when AutoShutdown was specified.");
+		}
+		if (AutoInit) {
+			Application.Init ((ConsoleDriver)Activator.CreateInstance (DriverType));
+			_init = true;
 		}
-
-		Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
-		_init = true;
 	}
 
 	public override void After (MethodInfo methodUnderTest)
 	{
-		Application.Shutdown ();
-		_init = false;
+		Debug.WriteLine ($"After: {methodUnderTest.Name}");
+		if (AutoShutdown) {
+			Application.Shutdown ();
+			_init = false;
+		}
 	}
 }
 
 class TestHelpers {
-
-
 #pragma warning disable xUnit1013 // Public method should be marked as test
 	public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelper output)
 	{

+ 129 - 129
UnitTests/TextFieldTests.cs

@@ -11,11 +11,11 @@ namespace Terminal.Gui.Views {
 		// This is necessary because a) Application is a singleton and Init/Shutdown must be called
 		// as a pair, and b) all unit test functions should be atomic.
 		[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
-		public class InitShutdown : Xunit.Sdk.BeforeAfterTestAttribute {
+		public class TextFieldTestsAutoInitShutdown : AutoInitShutdownAttribute {
 
 			public override void Before (MethodInfo methodUnderTest)
 			{
-				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+				base.Before (methodUnderTest);
 
 				//                                                    1         2         3 
 				//                                          01234567890123456789012345678901=32 (Length)
@@ -25,14 +25,14 @@ namespace Terminal.Gui.Views {
 			public override void After (MethodInfo methodUnderTest)
 			{
 				TextFieldTests._textField = null;
-				Application.Shutdown ();
+				base.After (methodUnderTest);
 			}
 		}
 
 		private static TextField _textField;
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void Changing_SelectedStart_Or_CursorPosition_Update_SelectedLength_And_SelectedText ()
 		{
 			_textField.SelectedStart = 2;
@@ -46,7 +46,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void SelectedStart_With_Value_Less_Than_Minus_One_Changes_To_Minus_One ()
 		{
 			_textField.SelectedStart = -2;
@@ -56,7 +56,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void SelectedStart_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length ()
 		{
 			_textField.CursorPosition = 2;
@@ -67,7 +67,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void SelectedStart_And_CursorPosition_With_Value_Greater_Than_Text_Length_Changes_Both_To_Text_Length ()
 		{
 			_textField.CursorPosition = 33;
@@ -79,18 +79,18 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void SelectedStart_Greater_Than_CursorPosition_All_Selection_Is_Overwritten_On_Typing ()
 		{
 			_textField.SelectedStart = 19;
 			_textField.CursorPosition = 12;
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 			_textField.ProcessKey (new KeyEvent ((Key)0x75, new KeyModifiers ())); // u
-			Assert.Equal ("TAB to jump u text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump u text fields.", _textField.Text.ToString ());
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void CursorPosition_With_Value_Less_Than_Zero_Changes_To_Zero ()
 		{
 			_textField.CursorPosition = -1;
@@ -100,7 +100,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void CursorPosition_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length ()
 		{
 			_textField.CursorPosition = 33;
@@ -110,7 +110,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void WordForward_With_No_Selection ()
 		{
 			_textField.CursorPosition = 0;
@@ -161,7 +161,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void WordBackward_With_No_Selection ()
 		{
 			_textField.CursorPosition = _textField.Text.Length;
@@ -212,7 +212,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void WordForward_With_Selection ()
 		{
 			_textField.CursorPosition = 0;
@@ -264,7 +264,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void WordBackward_With_Selection ()
 		{
 			_textField.CursorPosition = _textField.Text.Length;
@@ -316,7 +316,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void WordForward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text ()
 		{
 			_textField.CursorPosition = 10;
@@ -356,7 +356,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void WordBackward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text ()
 		{
 			_textField.CursorPosition = 10;
@@ -390,7 +390,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void WordForward_With_No_Selection_And_With_More_Than_Only_One_Whitespace_And_With_Only_One_Letter ()
 		{
 			//                           1         2         3         4         5    
@@ -474,7 +474,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void WordBackward_With_No_Selection_And_With_More_Than_Only_One_Whitespace_And_With_Only_One_Letter ()
 		{
 			//                           1         2         3         4         5    
@@ -558,7 +558,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void Copy_Or_Cut_Null_If_No_Selection ()
 		{
 			_textField.SelectedStart = -1;
@@ -569,7 +569,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void Copy_Or_Cut_Not_Null_If_Has_Selection ()
 		{
 			_textField.SelectedStart = 20;
@@ -581,45 +581,45 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void Copy_Or_Cut_And_Paste_With_Selection ()
 		{
 			_textField.SelectedStart = 20;
 			_textField.CursorPosition = 24;
 			_textField.Copy ();
 			Assert.Equal ("text", _textField.SelectedText);
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 			_textField.Paste ();
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 			_textField.SelectedStart = 20;
 			_textField.Cut ();
 			_textField.Paste ();
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void Copy_Or_Cut_And_Paste_With_No_Selection ()
 		{
 			_textField.SelectedStart = 20;
 			_textField.CursorPosition = 24;
 			_textField.Copy ();
 			Assert.Equal ("text", _textField.SelectedText);
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 			_textField.SelectedStart = -1;
 			_textField.Paste ();
-			Assert.Equal ("TAB to jump between texttext fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between texttext fields.", _textField.Text.ToString ());
 			_textField.SelectedStart = 24;
 			_textField.Cut ();
 			Assert.Null (_textField.SelectedText);
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 			_textField.SelectedStart = -1;
 			_textField.Paste ();
-			Assert.Equal ("TAB to jump between texttext fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between texttext fields.", _textField.Text.ToString ());
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void Copy_Or_Cut__Not_Allowed_If_Secret_Is_True ()
 		{
 			_textField.Secret = true;
@@ -637,7 +637,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void Paste_Always_Clear_The_SelectedText ()
 		{
 			_textField.SelectedStart = 20;
@@ -649,7 +649,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void TextChanging_Event ()
 		{
 			bool cancel = true;
@@ -662,14 +662,14 @@ namespace Terminal.Gui.Views {
 			};
 
 			_textField.Text = "changing";
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 			cancel = false;
 			_textField.Text = "changing";
-			Assert.Equal ("changing", _textField.Text);
+			Assert.Equal ("changing", _textField.Text.ToString ());
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void TextChanged_Event ()
 		{
 			_textField.TextChanged += (e) => {
@@ -677,40 +677,40 @@ namespace Terminal.Gui.Views {
 			};
 
 			_textField.Text = "changed";
-			Assert.Equal ("changed", _textField.Text);
+			Assert.Equal ("changed", _textField.Text.ToString ());
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void Used_Is_True_By_Default ()
 		{
 			_textField.CursorPosition = 10;
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 			_textField.ProcessKey (new KeyEvent ((Key)0x75, new KeyModifiers ())); // u
-			Assert.Equal ("TAB to jumup between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jumup between text fields.", _textField.Text.ToString ());
 			_textField.ProcessKey (new KeyEvent ((Key)0x73, new KeyModifiers ())); // s
-			Assert.Equal ("TAB to jumusp between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jumusp between text fields.", _textField.Text.ToString ());
 			_textField.ProcessKey (new KeyEvent ((Key)0x65, new KeyModifiers ())); // e
-			Assert.Equal ("TAB to jumusep between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jumusep between text fields.", _textField.Text.ToString ());
 			_textField.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d
-			Assert.Equal ("TAB to jumusedp between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jumusedp between text fields.", _textField.Text.ToString ());
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void Used_Is_False ()
 		{
 			_textField.Used = false;
 			_textField.CursorPosition = 10;
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 			_textField.ProcessKey (new KeyEvent ((Key)0x75, new KeyModifiers ())); // u
-			Assert.Equal ("TAB to jumu between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jumu between text fields.", _textField.Text.ToString ());
 			_textField.ProcessKey (new KeyEvent ((Key)0x73, new KeyModifiers ())); // s
-			Assert.Equal ("TAB to jumusbetween text fields.", _textField.Text);
+			Assert.Equal ("TAB to jumusbetween text fields.", _textField.Text.ToString ());
 			_textField.ProcessKey (new KeyEvent ((Key)0x65, new KeyModifiers ())); // e
-			Assert.Equal ("TAB to jumuseetween text fields.", _textField.Text);
+			Assert.Equal ("TAB to jumuseetween text fields.", _textField.Text.ToString ());
 			_textField.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d
-			Assert.Equal ("TAB to jumusedtween text fields.", _textField.Text);
+			Assert.Equal ("TAB to jumusedtween text fields.", _textField.Text.ToString ());
 		}
 
 		[Fact]
@@ -718,22 +718,22 @@ namespace Terminal.Gui.Views {
 		{
 			var tf = new TextField ("ABC");
 			tf.EnsureFocus ();
-			Assert.Equal ("ABC", tf.Text);
+			Assert.Equal ("ABC", tf.Text.ToString ());
 			Assert.Equal (3, tf.CursorPosition);
 
 			// now delete the C
 			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
-			Assert.Equal ("AB", tf.Text);
+			Assert.Equal ("AB", tf.Text.ToString ());
 			Assert.Equal (2, tf.CursorPosition);
 
 			// then delete the B
 			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
-			Assert.Equal ("A", tf.Text);
+			Assert.Equal ("A", tf.Text.ToString ());
 			Assert.Equal (1, tf.CursorPosition);
 
 			// then delete the A
 			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
-			Assert.Equal ("", tf.Text);
+			Assert.Equal ("", tf.Text.ToString ());
 			Assert.Equal (0, tf.CursorPosition);
 		}
 
@@ -743,24 +743,24 @@ namespace Terminal.Gui.Views {
 			var tf = new TextField ("ABC");
 			tf.EnsureFocus ();
 			tf.CursorPosition = 2;
-			Assert.Equal ("ABC", tf.Text);
+			Assert.Equal ("ABC", tf.Text.ToString ());
 
 			// now delete the B
 			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
-			Assert.Equal ("AC", tf.Text);
+			Assert.Equal ("AC", tf.Text.ToString ());
 
 			// then delete the A
 			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
-			Assert.Equal ("C", tf.Text);
+			Assert.Equal ("C", tf.Text.ToString ());
 
 			// then delete nothing
 			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
-			Assert.Equal ("C", tf.Text);
+			Assert.Equal ("C", tf.Text.ToString ());
 
 			// now delete the C
 			tf.CursorPosition = 1;
 			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
-			Assert.Equal ("", tf.Text);
+			Assert.Equal ("", tf.Text.ToString ());
 		}
 
 		[Fact]
@@ -769,35 +769,35 @@ namespace Terminal.Gui.Views {
 			var tf = new TextField ();
 			tf.EnsureFocus ();
 			tf.ProcessKey (new KeyEvent (Key.A, new KeyModifiers ()));
-			Assert.Equal ("A", tf.Text);
+			Assert.Equal ("A", tf.Text.ToString ());
 
 			// cancel the next keystroke
 			tf.TextChanging += (e) => e.Cancel = e.NewText == "AB";
 			tf.ProcessKey (new KeyEvent (Key.B, new KeyModifiers ()));
 
 			// B was canceled so should just be A
-			Assert.Equal ("A", tf.Text);
+			Assert.Equal ("A", tf.Text.ToString ());
 
 			// now delete the A
 			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
 
-			Assert.Equal ("", tf.Text);
+			Assert.Equal ("", tf.Text.ToString ());
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void Text_Replaces_Tabs_With_Empty_String ()
 		{
 			_textField.Text = "\t\tTAB to jump between text fields.";
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 			_textField.Text = "";
 			Clipboard.Contents = "\t\tTAB to jump between text fields.";
 			_textField.Paste ();
-			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text.ToString ());
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void TextField_SpaceHandling ()
 		{
 			var tf = new TextField () {
@@ -825,7 +825,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextFieldTestsAutoInitShutdown]
 		public void CanFocus_False_Wont_Focus_With_Mouse ()
 		{
 			var top = Application.Top;
@@ -901,201 +901,201 @@ namespace Terminal.Gui.Views {
 			Assert.False (tf.ReadOnly);
 
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
-			Assert.Equal ("This is a test.", tf.Text);
+			Assert.Equal ("This is a test.", tf.Text.ToString ());
 			tf.CursorPosition = 0;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
-			Assert.Equal ("his is a test.", tf.Text);
+			Assert.Equal ("his is a test.", tf.Text.ToString ());
 			tf.ReadOnly = true;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.D | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("his is a test.", tf.Text);
+			Assert.Equal ("his is a test.", tf.Text.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers ())));
-			Assert.Equal ("his is a test.", tf.Text);
+			Assert.Equal ("his is a test.", tf.Text.ToString ());
 			tf.ReadOnly = false;
 			tf.CursorPosition = 1;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			tf.CursorPosition = 5;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Home | Key.ShiftMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("is is", tf.SelectedText);
 			tf.CursorPosition = 5;
 			tf.SelectedStart = -1;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Home | Key.ShiftMask | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("is is", tf.SelectedText);
 			tf.CursorPosition = 5;
 			tf.SelectedStart = -1;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.A | Key.ShiftMask | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("is is", tf.SelectedText);
 			tf.CursorPosition = 5;
 			tf.SelectedStart = -1;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.End | Key.ShiftMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (" a test.", tf.SelectedText);
 			tf.CursorPosition = 5;
 			tf.SelectedStart = -1;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.End | Key.ShiftMask | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (" a test.", tf.SelectedText);
 			tf.CursorPosition = 5;
 			tf.SelectedStart = -1;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.E | Key.ShiftMask | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (" a test.", tf.SelectedText);
 			tf.CursorPosition = 5;
 			tf.SelectedStart = -1;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (0, tf.CursorPosition);
 			tf.CursorPosition = 5;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Home | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (0, tf.CursorPosition);
 			tf.CursorPosition = 5;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.A | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (0, tf.CursorPosition);
 			tf.CursorPosition = 5;
 			tf.SelectedStart = -1;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("s", tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("is", tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("s", tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Null (tf.SelectedText);
 			tf.CursorPosition = 7;
 			tf.SelectedStart = -1;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("a", tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("is a", tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent ((Key)((int)'B' + Key.ShiftMask | Key.AltMask), new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("is is a", tf.SelectedText);
 			tf.CursorPosition = 3;
 			tf.SelectedStart = -1;
 			Assert.Null (tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("is ", tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("is a ", tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent ((Key)((int)'F' + Key.ShiftMask | Key.AltMask), new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal ("is a test.", tf.SelectedText);
 			Assert.Equal (13, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Null (tf.SelectedText);
 			Assert.Equal (12, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (11, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.End, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (13, tf.CursorPosition);
 			tf.CursorPosition = 0;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.End | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (13, tf.CursorPosition);
 			tf.CursorPosition = 0;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.E | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (13, tf.CursorPosition);
 			tf.CursorPosition = 0;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (1, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.F | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.Equal (2, tf.CursorPosition);
 			tf.CursorPosition = 9;
 			tf.ReadOnly = true;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.K | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			tf.ReadOnly = false;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.K | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
-			Assert.Equal ("est.", Clipboard.Contents);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
+			Assert.Equal ("est.", Clipboard.Contents.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Backspace | Key.AltMask, new KeyModifiers ())));
-			Assert.Equal ("is is a test.", tf.Text);
+			Assert.Equal ("is is a test.", tf.Text.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
 			Assert.Equal (8, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorUp | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
 			Assert.Equal (6, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent ((Key)((int)'B' + Key.AltMask), new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
 			Assert.Equal (3, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
 			Assert.Equal (6, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.CursorDown | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
 			Assert.Equal (8, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent ((Key)((int)'F' + Key.AltMask), new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
 			Assert.Equal (9, tf.CursorPosition);
 			Assert.True (tf.Used);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.InsertChar, new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
 			Assert.Equal (9, tf.CursorPosition);
 			Assert.False (tf.Used);
 			tf.SelectedStart = 3;
 			tf.CursorPosition = 7;
 			Assert.Equal ("is a", tf.SelectedText);
-			Assert.Equal ("est.", Clipboard.Contents);
+			Assert.Equal ("est.", Clipboard.Contents.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
-			Assert.Equal ("is a", Clipboard.Contents);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
+			Assert.Equal ("is a", Clipboard.Contents.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.X | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is  t", tf.Text);
-			Assert.Equal ("is a", Clipboard.Contents);
+			Assert.Equal ("is  t", tf.Text.ToString ());
+			Assert.Equal ("is a", Clipboard.Contents.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.V | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("is is a t", tf.Text);
-			Assert.Equal ("is a", Clipboard.Contents);
+			Assert.Equal ("is is a t", tf.Text.ToString ());
+			Assert.Equal ("is a", Clipboard.Contents.ToString ());
 			Assert.Equal (7, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.K | Key.AltMask, new KeyModifiers ())));
-			Assert.Equal (" t", tf.Text);
-			Assert.Equal ("is is a", Clipboard.Contents);
+			Assert.Equal (" t", tf.Text.ToString ());
+			Assert.Equal ("is is a", Clipboard.Contents.ToString ());
 			tf.Text = "TAB to jump between text fields.";
 			Assert.Equal (0, tf.CursorPosition);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("to jump between text fields.", tf.Text);
+			Assert.Equal ("to jump between text fields.", tf.Text.ToString ());
 			tf.CursorPosition = tf.Text.Length;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("to jump between text ", tf.Text);
+			Assert.Equal ("to jump between text ", tf.Text.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.T | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal ("to jump between text ", tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.D | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ())));
-			Assert.Equal ("", tf.Text);
+			Assert.Equal ("", tf.Text.ToString ());
 		}
 
 		[Fact]
@@ -1134,7 +1134,7 @@ namespace Terminal.Gui.Views {
 			Application.Top.Add (tf);
 			Application.Begin (Application.Top);
 
-			Assert.Equal ("-1", tf.Text);
+			Assert.Equal ("-1", tf.Text.ToString ());
 
 			// InsertText
 			tf.SelectedStart = 1;
@@ -1144,7 +1144,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers ())));
 			Assert.Equal ("-2", newText);
 			Assert.Equal ("-1", oldText);
-			Assert.Equal ("-2", tf.Text);
+			Assert.Equal ("-2", tf.Text.ToString ());
 
 			// DeleteCharLeft
 			tf.SelectedStart = 1;
@@ -1154,7 +1154,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.Equal ("-", newText);
 			Assert.Equal ("-2", oldText);
-			Assert.Equal ("-", tf.Text);
+			Assert.Equal ("-", tf.Text.ToString ());
 
 			// DeleteCharRight
 			tf.Text = "-1";
@@ -1165,7 +1165,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
 			Assert.Equal ("-", newText);
 			Assert.Equal ("-1", oldText);
-			Assert.Equal ("-", tf.Text);
+			Assert.Equal ("-", tf.Text.ToString ());
 
 			// Cut
 			tf.Text = "-1";
@@ -1176,7 +1176,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.X | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal ("-", newText);
 			Assert.Equal ("-1", oldText);
-			Assert.Equal ("-", tf.Text);
+			Assert.Equal ("-", tf.Text.ToString ());
 		}
 
 		[Fact]

+ 2 - 2
UnitTests/TextFormatterTests.cs

@@ -2968,7 +2968,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Draw_Horizontal_Throws_IndexOutOfRangeException_With_Negative_Bounds ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var top = Application.Top;
 
@@ -3008,7 +3008,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Draw_Vertical_Throws_IndexOutOfRangeException_With_Negative_Bounds ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var top = Application.Top;
 

+ 82 - 85
UnitTests/TextViewTests.cs

@@ -22,16 +22,12 @@ namespace Terminal.Gui.Views {
 		// This is necessary because a) Application is a singleton and Init/Shutdown must be called
 		// as a pair, and b) all unit test functions should be atomic.
 		[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
-		public class InitShutdown : Xunit.Sdk.BeforeAfterTestAttribute {
+		public class TextViewTestsAutoInitShutdown : AutoInitShutdownAttribute {
 
 			public static string txt = "TAB to jump between text fields.";
 			public override void Before (MethodInfo methodUnderTest)
 			{
-				if (_textView != null) {
-					throw new InvalidOperationException ("After did not run.");
-				}
-
-				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+				base.Before (methodUnderTest);
 
 				//                   1         2         3 
 				//         01234567890123456789012345678901=32 (Length)
@@ -47,12 +43,12 @@ namespace Terminal.Gui.Views {
 			public override void After (MethodInfo methodUnderTest)
 			{
 				_textView = null;
-				Application.Shutdown ();
+				base.After (methodUnderTest);
 			}
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Changing_Selection_Or_CursorPosition_Update_SelectedLength_And_SelectedText ()
 		{
 			_textView.SelectionStartColumn = 2;
@@ -69,7 +65,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Selection_With_Value_Less_Than_Zero_Changes_To_Zero ()
 		{
 			_textView.SelectionStartColumn = -2;
@@ -81,7 +77,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Selection_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length ()
 		{
 			_textView.CursorPosition = new Point (2, 0);
@@ -94,7 +90,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Selection_With_Empty_Text ()
 		{
 			_textView = new TextView ();
@@ -108,7 +104,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Selection_And_CursorPosition_With_Value_Greater_Than_Text_Length_Changes_Both_To_Text_Length ()
 		{
 			_textView.CursorPosition = new Point (33, 2);
@@ -123,7 +119,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void CursorPosition_With_Value_Less_Than_Zero_Changes_To_Zero ()
 		{
 			_textView.CursorPosition = new Point (-1, -1);
@@ -134,7 +130,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void CursorPosition_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length ()
 		{
 			_textView.CursorPosition = new Point (33, 1);
@@ -145,7 +141,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordForward_With_No_Selection ()
 		{
 			_textView.CursorPosition = new Point (0, 0);
@@ -208,7 +204,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordBackward_With_No_Selection ()
 		{
 			_textView.CursorPosition = new Point (_textView.Text.Length, 0);
@@ -271,7 +267,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordForward_With_Selection ()
 		{
 			_textView.CursorPosition = new Point (0, 0);
@@ -336,7 +332,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordBackward_With_Selection ()
 		{
 			_textView.CursorPosition = new Point (_textView.Text.Length, 0);
@@ -401,7 +397,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordForward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text ()
 		{
 			_textView.CursorPosition = new Point (10, 0);
@@ -450,7 +446,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordBackward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text ()
 		{
 			_textView.CursorPosition = new Point (10, 0);
@@ -491,7 +487,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordForward_With_No_Selection_And_With_More_Than_Only_One_Whitespace_And_With_Only_One_Letter ()
 		{
 			//                          1         2         3         4         5    
@@ -597,7 +593,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordBackward_With_No_Selection_And_With_More_Than_Only_One_Whitespace_And_With_Only_One_Letter ()
 		{
 			//                          1         2         3         4         5    
@@ -703,7 +699,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordBackward_Multiline_With_Selection ()
 		{
 			//		          4         3          2         1
@@ -818,7 +814,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordForward_Multiline_With_Selection ()
 		{
 			//			    1         2          3         4
@@ -932,7 +928,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Kill_To_End_Delete_Forwards_And_Copy_To_The_Clipboard ()
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.";
@@ -945,26 +941,26 @@ namespace Terminal.Gui.Views {
 					_textView.ProcessKey (new KeyEvent (Key.K | Key.CtrlMask, new KeyModifiers ()));
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
-					Assert.Equal ($"{Environment.NewLine}This is the second line.", _textView.Text);
-					Assert.Equal ("This is the first line.", Clipboard.Contents);
+					Assert.Equal ($"{Environment.NewLine}This is the second line.", _textView.Text.ToString ());
+					Assert.Equal ("This is the first line.", Clipboard.Contents.ToString ());
 					break;
 				case 1:
 					_textView.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
-					Assert.Equal ("This is the second line.", _textView.Text);
-					Assert.Equal ($"This is the first line.{Environment.NewLine}", Clipboard.Contents);
+					Assert.Equal ("This is the second line.", _textView.Text.ToString ());
+					Assert.Equal ($"This is the first line.{Environment.NewLine}", Clipboard.Contents.ToString());
 					break;
 				case 2:
 					_textView.ProcessKey (new KeyEvent (Key.K | Key.CtrlMask, new KeyModifiers ()));
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
-					Assert.Equal ("", _textView.Text);
-					Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.", Clipboard.Contents);
+					Assert.Equal ("", _textView.Text.ToString ());
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.", Clipboard.Contents.ToString ());
 
 					// Paste
 					_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ()));
-					Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.", _textView.Text);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.", _textView.Text.ToString ());
 					break;
 				default:
 					iterationsFinished = true;
@@ -975,7 +971,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Kill_To_Start_Delete_Backwards_And_Copy_To_The_Clipboard ()
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.";
@@ -989,26 +985,26 @@ namespace Terminal.Gui.Views {
 					_textView.ProcessKey (new KeyEvent (Key.K | Key.AltMask, new KeyModifiers ()));
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
-					Assert.Equal ($"This is the first line.{Environment.NewLine}", _textView.Text);
-					Assert.Equal ($"This is the second line.", Clipboard.Contents);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}", _textView.Text.ToString());
+					Assert.Equal ($"This is the second line.", Clipboard.Contents.ToString ());
 					break;
 				case 1:
 					_textView.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
 					Assert.Equal (23, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
-					Assert.Equal ("This is the first line.", _textView.Text);
-					Assert.Equal ($"This is the second line.{Environment.NewLine}", Clipboard.Contents);
+					Assert.Equal ("This is the first line.", _textView.Text.ToString ());
+					Assert.Equal ($"This is the second line.{Environment.NewLine}", Clipboard.Contents.ToString ());
 					break;
 				case 2:
 					_textView.ProcessKey (new KeyEvent (Key.K | Key.AltMask, new KeyModifiers ()));
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
-					Assert.Equal ("", _textView.Text);
-					Assert.Equal ($"This is the second line.{Environment.NewLine}This is the first line.", Clipboard.Contents);
+					Assert.Equal ("", _textView.Text.ToString ());
+					Assert.Equal ($"This is the second line.{Environment.NewLine}This is the first line.", Clipboard.Contents.ToString ());
 
 					// Paste inverted
 					_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ()));
-					Assert.Equal ($"This is the second line.{Environment.NewLine}This is the first line.", _textView.Text);
+					Assert.Equal ($"This is the second line.{Environment.NewLine}This is the first line.", _textView.Text.ToString ());
 					break;
 				default:
 					iterationsFinished = true;
@@ -1019,7 +1015,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Kill_Delete_WordForward ()
 		{
 			_textView.Text = "This is the first line.";
@@ -1063,7 +1059,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Kill_Delete_WordBackward ()
 		{
 			_textView.Text = "This is the first line.";
@@ -1108,7 +1104,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Kill_Delete_WordForward_Multiline ()
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.";
@@ -1188,7 +1184,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Kill_Delete_WordBackward_Multiline ()
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.";
@@ -1268,7 +1264,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Copy_Or_Cut_Null_If_No_Selection ()
 		{
 			_textView.SelectionStartColumn = 0;
@@ -1280,7 +1276,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Copy_Or_Cut_Not_Null_If_Has_Selection ()
 		{
 			_textView.SelectionStartColumn = 20;
@@ -1293,7 +1289,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Copy_Or_Cut_And_Paste_With_Selection ()
 		{
 			_textView.SelectionStartColumn = 20;
@@ -1312,7 +1308,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Copy_Or_Cut_And_Paste_With_No_Selection ()
 		{
 			_textView.SelectionStartColumn = 20;
@@ -1347,7 +1343,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Cut_Not_Allowed_If_ReadOnly_Is_True ()
 		{
 			_textView.ReadOnly = true;
@@ -1368,7 +1364,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Paste_Always_Clear_The_SelectedText ()
 		{
 			_textView.SelectionStartColumn = 20;
@@ -1381,7 +1377,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void TextChanged_Event ()
 		{
 			_textView.TextChanged += () => {
@@ -1396,7 +1392,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void TextChanged_Event_NoFires_OnTyping ()
 		{
 			var eventcount = 0;
@@ -1412,7 +1408,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Used_Is_True_By_Default ()
 		{
 			_textView.CursorPosition = new Point (10, 0);
@@ -1428,7 +1424,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Used_Is_False ()
 		{
 			_textView.Used = false;
@@ -1445,7 +1441,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Copy_Without_Selection ()
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.\n";
@@ -1464,7 +1460,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void TabWidth_Setting_To_Zero_Keeps_AllowsTab ()
 		{
 			Assert.Equal (4, _textView.TabWidth);
@@ -1483,7 +1479,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void AllowsTab_Setting_To_True_Changes_TabWidth_To_Default_If_It_Is_Zero ()
 		{
 			_textView.TabWidth = 0;
@@ -1499,7 +1495,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void AllowsReturn_Setting_To_True_Changes_Multiline_To_True_If_It_Is_False ()
 		{
 			Assert.True (_textView.AllowsReturn);
@@ -1521,7 +1517,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Multiline_Setting_Changes_AllowsReturn_AllowsTab_Height_WordWrap ()
 		{
 			Assert.True (_textView.Multiline);
@@ -1556,7 +1552,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Tab_Test_Follow_By_BackTab ()
 		{
 			Application.Top.Add (_textView);
@@ -1592,7 +1588,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void BackTab_Test_Follow_By_Tab ()
 		{
 			Application.Top.Add (_textView);
@@ -1635,7 +1631,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Tab_Test_Follow_By_CursorLeft_And_Then_Follow_By_CursorRight ()
 		{
 			Application.Top.Add (_textView);
@@ -1678,7 +1674,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Tab_Test_Follow_By_BackTab_With_Text ()
 		{
 			Application.Top.Add (_textView);
@@ -1714,7 +1710,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Tab_Test_Follow_By_Home_And_Then_Follow_By_End_And_Then_Follow_By_BackTab_With_Text ()
 		{
 			Application.Top.Add (_textView);
@@ -1772,7 +1768,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Tab_Test_Follow_By_CursorLeft_And_Then_Follow_By_CursorRight_With_Text ()
 		{
 			Application.Top.Add (_textView);
@@ -2020,7 +2016,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordWrap_WrapModel_Output ()
 		{
 			//          0123456789
@@ -2103,7 +2099,7 @@ a
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void WordWrap_ReadOnly_CursorPosition_SelectedText_Copy ()
 		{
 			//          0123456789
@@ -2228,7 +2224,7 @@ line.
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void BottomOffset_Sets_To_Zero_Adjust_TopRow ()
 		{
 			string text = "";
@@ -2258,7 +2254,7 @@ line.
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void RightOffset_Sets_To_Zero_Adjust_leftColumn ()
 		{
 			string text = "";
@@ -2288,7 +2284,7 @@ line.
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void TextView_SpaceHandling ()
 		{
 			var tv = new TextView () {
@@ -2316,7 +2312,7 @@ line.
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void CanFocus_False_Wont_Focus_With_Mouse ()
 		{
 			var top = Application.Top;
@@ -2383,7 +2379,7 @@ line.
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void DesiredCursorVisibility_Vertical_Navigation ()
 		{
 			string text = "";
@@ -2422,7 +2418,7 @@ line.
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void DesiredCursorVisibility_Horizontal_Navigation ()
 		{
 			string text = "";
@@ -5942,7 +5938,7 @@ line.
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void Mouse_Button_Shift_Preserves_Selection ()
 		{
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
@@ -6333,7 +6329,7 @@ This is the second line.
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void TextView_InsertText_Newline_LF ()
 		{
 			var tv = new TextView {
@@ -6402,7 +6398,7 @@ This is the second line.
 		}
 
 		[Fact]
-		[InitShutdown]
+		[TextViewTestsAutoInitShutdown]
 		public void TextView_InsertText_Newline_CRLF ()
 		{
 			var tv = new TextView {
@@ -6621,7 +6617,7 @@ This is the second line.
 			Assert.Equal ("Yay", tv.Text.ToString ());
 		}
 
-		[Fact, InitShutdown]
+		[Fact, TextViewTestsAutoInitShutdown]
 		public void ContentsChanged_Event_Fires_Using_Kill_Delete_Tests ()
 		{
 			var eventcount = 0;
@@ -6647,7 +6643,8 @@ This is the second line.
 			Assert.Equal (expectedEventCount, eventcount);
 		}
 
-		[Fact, InitShutdown]
+
+		[Fact, TextViewTestsAutoInitShutdown]
 		public void ContentsChanged_Event_Fires_Using_Copy_Or_Cut_Tests ()
 		{
 			var eventcount = 0;
@@ -6659,7 +6656,7 @@ This is the second line.
 			var expectedEventCount = 1;
 
 			// reset
-			_textView.Text = InitShutdown.txt;
+			_textView.Text = TextViewTestsAutoInitShutdown.txt;
 			Assert.Equal (expectedEventCount, eventcount);
 
 			expectedEventCount += 3;
@@ -6668,7 +6665,7 @@ This is the second line.
 
 			// reset
 			expectedEventCount += 1;
-			_textView.Text = InitShutdown.txt;
+			_textView.Text = TextViewTestsAutoInitShutdown.txt;
 			Assert.Equal (expectedEventCount, eventcount);
 
 			expectedEventCount += 3;
@@ -6677,7 +6674,7 @@ This is the second line.
 
 			// reset
 			expectedEventCount += 1;
-			_textView.Text = InitShutdown.txt;
+			_textView.Text = TextViewTestsAutoInitShutdown.txt;
 			Assert.Equal (expectedEventCount, eventcount);
 
 			expectedEventCount += 1;
@@ -6686,7 +6683,7 @@ This is the second line.
 
 			// reset
 			expectedEventCount += 1;
-			_textView.Text = InitShutdown.txt;
+			_textView.Text = TextViewTestsAutoInitShutdown.txt;
 			Assert.Equal (expectedEventCount, eventcount);
 
 			expectedEventCount += 1;
@@ -6695,7 +6692,7 @@ This is the second line.
 
 			// reset
 			expectedEventCount += 1;
-			_textView.Text = InitShutdown.txt;
+			_textView.Text = TextViewTestsAutoInitShutdown.txt;
 			Assert.Equal (expectedEventCount, eventcount);
 
 			expectedEventCount += 4;
@@ -6704,7 +6701,7 @@ This is the second line.
 
 			// reset
 			expectedEventCount += 1;
-			_textView.Text = InitShutdown.txt;
+			_textView.Text = TextViewTestsAutoInitShutdown.txt;
 			Assert.Equal (expectedEventCount, eventcount);
 
 			expectedEventCount += 4;
@@ -6712,7 +6709,7 @@ This is the second line.
 			Assert.Equal (expectedEventCount, eventcount);
 		}
 
-		[Fact, InitShutdown]
+		[Fact, TextViewTestsAutoInitShutdown]
 		public void ContentsChanged_Event_Fires_On_Undo_Redo ()
 		{
 			var eventcount = 0;

+ 2 - 2
UnitTests/ToplevelTests.cs

@@ -596,7 +596,7 @@ namespace Terminal.Gui.Core {
 
 			var win = new Window ();
 			win.Add (view);
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 			var top = Application.Top;
 			top.Add (win);
 
@@ -663,7 +663,7 @@ namespace Terminal.Gui.Core {
 		[AutoInitShutdown]
 		public void FileDialog_FileSystemWatcher ()
 		{
-			for (int i = 0; i < 256; i++) {
+			for (int i = 0; i < 8; i++) {
 				var fd = new FileDialog ();
 				fd.Ready += () => Application.RequestStop ();
 				Application.Run (fd);

+ 1 - 1
UnitTests/TreeViewTests.cs

@@ -953,7 +953,7 @@ namespace Terminal.Gui.Views {
 		private void InitFakeDriver ()
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver);
 			driver.Init (() => { });
 		}
 	}

+ 4 - 1
UnitTests/UnitTests.csproj

@@ -1,6 +1,9 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>net6.0</TargetFramework>
+    <TargetFramework>net7.0</TargetFramework>
+    <!-- https://stackoverflow.com/questions/294216/why-does-c-sharp-forbid-generic-attribute-types -->
+    <!-- for AutoInitShutdown attribute -->
+    <LangVersion>Preview</LangVersion>
     <IsPackable>false</IsPackable>
     <UseDataCollector />
     <!-- Version numbers are automatically updated by gitversion when a release is released -->

+ 10 - 10
UnitTests/ViewTests.cs

@@ -574,7 +574,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Initialized_Event_Comparing_With_Added_Event ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = new Toplevel () { Id = "0", };
 
@@ -673,7 +673,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Initialized_Event_Will_Be_Invoked_When_Added_Dynamically ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = new Toplevel () { Id = "0", };
 
@@ -782,7 +782,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void CanFocus_Faced_With_Container_Before_Run ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -819,7 +819,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void CanFocus_Faced_With_Container_After_Run ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -862,7 +862,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void CanFocus_Container_ToFalse_Turns_All_Subviews_ToFalse_Too ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -897,7 +897,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void CanFocus_Container_Toggling_All_Subviews_To_Old_Value_When_Is_True ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 
@@ -941,7 +941,7 @@ namespace Terminal.Gui.Core {
 		{
 			// Non-regression test for #882 (NullReferenceException during keyboard navigation when Focused is null)
 
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			Application.Top.Ready += () => {
 				Assert.Null (Application.Top.Focused);
@@ -959,7 +959,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Multi_Thread_Toplevels ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			var t = Application.Top;
 			var w = new Window ();
@@ -1148,7 +1148,7 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void KeyPress_Handled_To_True_Prevents_Changes ()
 		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (new FakeDriver ());
 
 			Console.MockKeyPresses.Push (new ConsoleKeyInfo ('N', ConsoleKey.N, false, false, false));
 
@@ -1584,7 +1584,7 @@ Y
 		public void LabelChangeText_RendersCorrectly_Constructors (int choice)
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Application.Init (driver);
 
 			try {
 				// Create a label with a short text