Browse Source

Merge branch 'develop' into line-drawer

Thomas Nind 2 years ago
parent
commit
16a3c0b8ae
84 changed files with 2421 additions and 1619 deletions
  1. 2 1
      .github/workflows/dotnet-core.yml
  2. 0 1
      README.md
  3. 0 0
      Terminal.Gui UnitTests/ScenarioTests.cs
  4. 57 0
      Terminal.Gui UnitTests/UnitTests.csproj
  5. 96 166
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  6. 8 4
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
  7. 71 8
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  8. 5 11
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs
  9. 3 3
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  10. 27 11
      Terminal.Gui/Core/Application.cs
  11. 38 11
      Terminal.Gui/Core/Clipboard/Clipboard.cs
  12. 25 20
      Terminal.Gui/Core/Clipboard/ClipboardBase.cs
  13. 78 131
      Terminal.Gui/Core/ConsoleDriver.cs
  14. 2 1
      Terminal.Gui/Core/MainLoop.cs
  15. 6 6
      Terminal.Gui/Core/TextFormatter.cs
  16. 5 4
      Terminal.Gui/Core/Toplevel.cs
  17. 21 7
      Terminal.Gui/Core/View.cs
  18. 0 1
      Terminal.Gui/README.md
  19. 4 4
      Terminal.Gui/Terminal.Gui.csproj
  20. 8 5
      Terminal.Gui/Views/ListView.cs
  21. 68 5
      Terminal.Gui/Views/TableView.cs
  22. 1 0
      Terminal.Gui/Views/TextField.cs
  23. 9 0
      Terminal.Gui/Windows/FileDialog.cs
  24. 13 15
      UICatalog/Properties/launchSettings.json
  25. 1 1
      UICatalog/Scenarios/Editor.cs
  26. 7 6
      UICatalog/UICatalog.cs
  27. 2 1
      UICatalog/UICatalog.csproj
  28. 15 12
      UnitTests/Application/ApplicationTests.cs
  29. 46 63
      UnitTests/Application/MainLoopTests.cs
  30. 13 7
      UnitTests/Application/RunStateTests.cs
  31. 1 1
      UnitTests/Application/StackExtensionsTests.cs
  32. 1 1
      UnitTests/Application/SynchronizatonContextTests.cs
  33. 0 324
      UnitTests/ClipboardTests.cs
  34. 1 1
      UnitTests/Core/BorderTests.cs
  35. 1 6
      UnitTests/Core/ResponderTests.cs
  36. 5 5
      UnitTests/Drivers/AttributeTests.cs
  37. 288 0
      UnitTests/Drivers/ClipboardTests.cs
  38. 39 0
      UnitTests/Drivers/ColorTests.cs
  39. 72 280
      UnitTests/Drivers/ConsoleDriverTests.cs
  40. 1 1
      UnitTests/Drivers/KeyTests.cs
  41. 2 2
      UnitTests/Menus/ContextMenuTests.cs
  42. 2 2
      UnitTests/Menus/MenuTests.cs
  43. 52 13
      UnitTests/TestHelpers.cs
  44. 1 1
      UnitTests/Text/CollectionNavigatorTests.cs
  45. 65 36
      UnitTests/Text/TextFormatterTests.cs
  46. 45 34
      UnitTests/TopLevels/DialogTests.cs
  47. 22 40
      UnitTests/TopLevels/MdiTests.cs
  48. 3 2
      UnitTests/TopLevels/MessageBoxTests.cs
  49. 22 15
      UnitTests/TopLevels/ToplevelTests.cs
  50. 8 7
      UnitTests/TopLevels/WindowTests.cs
  51. 21 21
      UnitTests/TopLevels/WizardTests.cs
  52. 14 19
      UnitTests/Types/DimTests.cs
  53. 1 1
      UnitTests/Types/PointTests.cs
  54. 9 10
      UnitTests/Types/PosTests.cs
  55. 1 1
      UnitTests/Types/RectTests.cs
  56. 1 1
      UnitTests/Types/SizeTests.cs
  57. 566 0
      UnitTests/UICatalog/ScenarioTests.cs
  58. 4 1
      UnitTests/UnitTests.csproj
  59. 3 3
      UnitTests/Views/AllViewsTests.cs
  60. 1 1
      UnitTests/Views/AutocompleteTests.cs
  61. 1 1
      UnitTests/Views/ButtonTests.cs
  62. 1 1
      UnitTests/Views/CheckboxTests.cs
  63. 1 1
      UnitTests/Views/ColorPickerTests.cs
  64. 1 1
      UnitTests/Views/ComboBoxTests.cs
  65. 1 1
      UnitTests/Views/DateFieldTests.cs
  66. 1 1
      UnitTests/Views/FrameViewTests.cs
  67. 3 3
      UnitTests/Views/GraphViewTests.cs
  68. 1 1
      UnitTests/Views/HexViewTests.cs
  69. 1 1
      UnitTests/Views/LineViewTests.cs
  70. 64 2
      UnitTests/Views/ListViewTests.cs
  71. 1 1
      UnitTests/Views/PanelViewTests.cs
  72. 1 1
      UnitTests/Views/ProgressBarTests.cs
  73. 1 1
      UnitTests/Views/RadioGroupTests.cs
  74. 22 31
      UnitTests/Views/ScrollBarViewTests.cs
  75. 1 1
      UnitTests/Views/ScrollViewTests.cs
  76. 2 2
      UnitTests/Views/StatusBarTests.cs
  77. 2 2
      UnitTests/Views/TabViewTests.cs
  78. 118 1
      UnitTests/Views/TableViewTests.cs
  79. 157 130
      UnitTests/Views/TextFieldTests.cs
  80. 1 1
      UnitTests/Views/TextValidateFieldTests.cs
  81. 83 86
      UnitTests/Views/TextViewTests.cs
  82. 1 1
      UnitTests/Views/TimeFieldTests.cs
  83. 2 2
      UnitTests/Views/TreeViewTests.cs
  84. 71 12
      UnitTests/Views/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

+ 0 - 1
README.md

@@ -12,7 +12,6 @@ A toolkit for building rich console apps for .NET, .NET Core, and Mono that work
 
 ![Sample app](docfx/images/sample.gif)
 
-
 ## Quick Start
 
 Paste these commands into your favorite terminal on Windows, Mac, or Linux. This will install the [Terminal.Gui.Templates](https://github.com/gui-cs/Terminal.Gui.templates), create a new "Hello World" TUI app, and run it.

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


+ 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 ();
-			//}
 		}
 	}
 }

+ 8 - 4
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)]
@@ -176,16 +181,15 @@ namespace Terminal.Gui {
 		{
 			UpdatePollMap ();
 
-			if (CheckTimers (wait, out var pollTimeout)) {
-				return true;
-			}
+			bool checkTimersResult = CheckTimers (wait, out var pollTimeout);
 
 			var n = poll (pollmap, (uint)pollmap.Length, pollTimeout);
 
 			if (n == KEY_RESIZE) {
 				winChanged = true;
 			}
-			return n >= KEY_RESIZE || CheckTimers (wait, out pollTimeout);
+
+			return checkTimersResult || n >= KEY_RESIZE;
 		}
 
 		bool CheckTimers (bool wait, out int pollTimeout)

+ 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 ()

+ 3 - 3
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -251,7 +251,7 @@ namespace Terminal.Gui {
 				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 			}
 			var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
-			if (!SetConsoleWindowInfo (ScreenBuffer, true, ref winRect)) {
+			if (!SetConsoleWindowInfo (OutputHandle, true, ref winRect)) {
 				//throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 				return new Size (cols, rows);
 			}
@@ -261,7 +261,7 @@ namespace Terminal.Gui {
 
 		void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi)
 		{
-			if (ScreenBuffer != IntPtr.Zero && !SetConsoleScreenBufferInfoEx (OutputHandle, ref csbi)) {
+			if (ScreenBuffer != IntPtr.Zero && !SetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) {
 				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 			}
 		}
@@ -773,7 +773,7 @@ namespace Terminal.Gui {
 					w += 3;
 				}
 				var newSize = WinConsole.SetConsoleWindow (
-					(short)Math.Max (w, 16), (short)Math.Max (e.Height, 1));
+					(short)Math.Max (w, 16), (short)Math.Max (e.Height, 0));
 				left = 0;
 				top = 0;
 				cols = newSize.Width;

+ 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;
 			}

+ 78 - 131
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>
@@ -209,153 +212,27 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// The default color for text, when the view is not focused.
 		/// </summary>
-		public Attribute Normal { get { return _normal; } set { _normal = SetAttribute (value); } }
+		public Attribute Normal { get { return _normal; } set { _normal = value; } }
 
 		/// <summary>
 		/// The color for text when the view has the focus.
 		/// </summary>
-		public Attribute Focus { get { return _focus; } set { _focus = SetAttribute (value); } }
+		public Attribute Focus { get { return _focus; } set { _focus = value; } }
 
 		/// <summary>
 		/// The color for the hotkey when a view is not focused
 		/// </summary>
-		public Attribute HotNormal { get { return _hotNormal; } set { _hotNormal = SetAttribute (value); } }
+		public Attribute HotNormal { get { return _hotNormal; } set { _hotNormal = value; } }
 
 		/// <summary>
 		/// The color for the hotkey when the view is focused.
 		/// </summary>
-		public Attribute HotFocus { get { return _hotFocus; } set { _hotFocus = SetAttribute (value); } }
+		public Attribute HotFocus { get { return _hotFocus; } set { _hotFocus = value; } }
 
 		/// <summary>
 		/// The default color for text, when the view is disabled.
 		/// </summary>
-		public Attribute Disabled { get { return _disabled; } set { _disabled = SetAttribute (value); } }
-
-		bool preparingScheme = false;
-
-		Attribute SetAttribute (Attribute attribute, [CallerMemberName] string callerMemberName = null)
-		{
-			if (!Application._initialized && !preparingScheme)
-				return attribute;
-
-			if (preparingScheme)
-				return attribute;
-
-			preparingScheme = true;
-			switch (caller) {
-			case "TopLevel":
-				switch (callerMemberName) {
-				case "Normal":
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					break;
-				case "Focus":
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
-					break;
-				case "HotNormal":
-					HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
-					break;
-				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
-					if (Focus.Foreground != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
-					break;
-				}
-				break;
-
-			case "Base":
-				switch (callerMemberName) {
-				case "Normal":
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					break;
-				case "Focus":
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
-					break;
-				case "HotNormal":
-					HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
-					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
-					break;
-				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
-					if (Focus.Foreground != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
-					break;
-				}
-				break;
-
-			case "Menu":
-				switch (callerMemberName) {
-				case "Normal":
-					if (Focus.Background != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (attribute.Foreground, Focus.Background);
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					Disabled = Application.Driver.MakeAttribute (Disabled.Foreground, attribute.Background);
-					break;
-				case "Focus":
-					Normal = Application.Driver.MakeAttribute (attribute.Foreground, Normal.Background);
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
-					break;
-				case "HotNormal":
-					if (Focus.Background != attribute.Background)
-						HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
-					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
-					Disabled = Application.Driver.MakeAttribute (Disabled.Foreground, attribute.Background);
-					break;
-				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
-					if (Focus.Foreground != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
-					break;
-				case "Disabled":
-					if (Focus.Background != attribute.Background)
-						HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
-					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					break;
-				}
-				break;
-
-			case "Dialog":
-				switch (callerMemberName) {
-				case "Normal":
-					if (Focus.Background != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (attribute.Foreground, Focus.Background);
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					break;
-				case "Focus":
-					Normal = Application.Driver.MakeAttribute (attribute.Foreground, Normal.Background);
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
-					break;
-				case "HotNormal":
-					if (Focus.Background != attribute.Background)
-						HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
-					if (Normal.Foreground != attribute.Background)
-						Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
-					break;
-				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
-					if (Focus.Foreground != attribute.Background)
-						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
-					break;
-				}
-				break;
-
-			case "Error":
-				switch (callerMemberName) {
-				case "Normal":
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
-					break;
-				case "HotNormal":
-				case "HotFocus":
-					HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, attribute.Background);
-					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
-					break;
-				}
-				break;
-			}
-			preparingScheme = false;
-			return attribute;
-		}
+		public Attribute Disabled { get { return _disabled; } set { _disabled = value; } }
 
 		/// <summary>
 		/// Compares two <see cref="ColorScheme"/> objects for equality.
@@ -1389,4 +1266,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;

+ 6 - 6
Terminal.Gui/Core/TextFormatter.cs

@@ -1212,11 +1212,11 @@ namespace Terminal.Gui {
 					if (isVertical) {
 						var runesWidth = GetSumMaxCharWidth (Lines, line);
 						x = bounds.Right - runesWidth;
-						CursorPosition = bounds.Width - runesWidth + hotKeyPos;
+						CursorPosition = bounds.Width - runesWidth + (hotKeyPos > -1 ? hotKeyPos : 0);
 					} else {
 						var runesWidth = GetTextWidth (ustring.Make (runes));
 						x = bounds.Right - runesWidth;
-						CursorPosition = bounds.Width - runesWidth + hotKeyPos;
+						CursorPosition = bounds.Width - runesWidth + (hotKeyPos > -1 ? hotKeyPos : 0);
 					}
 				} else if (textAlignment == TextAlignment.Left || textAlignment == TextAlignment.Justified) {
 					if (isVertical) {
@@ -1225,16 +1225,16 @@ namespace Terminal.Gui {
 					} else {
 						x = bounds.Left;
 					}
-					CursorPosition = hotKeyPos;
+					CursorPosition = hotKeyPos > -1 ? hotKeyPos : 0;
 				} else if (textAlignment == TextAlignment.Centered) {
 					if (isVertical) {
 						var runesWidth = GetSumMaxCharWidth (Lines, line);
 						x = bounds.Left + line + ((bounds.Width - runesWidth) / 2);
-						CursorPosition = (bounds.Width - runesWidth) / 2 + hotKeyPos;
+						CursorPosition = (bounds.Width - runesWidth) / 2 + (hotKeyPos > -1 ? hotKeyPos : 0);
 					} else {
 						var runesWidth = GetTextWidth (ustring.Make (runes));
 						x = bounds.Left + (bounds.Width - runesWidth) / 2;
-						CursorPosition = (bounds.Width - runesWidth) / 2 + hotKeyPos;
+						CursorPosition = (bounds.Width - runesWidth) / 2 + (hotKeyPos > -1 ? hotKeyPos : 0);
 					}
 				} else {
 					throw new ArgumentOutOfRangeException ();
@@ -1291,7 +1291,7 @@ namespace Terminal.Gui {
 							rune = runes [idx];
 						}
 					}
-					if (idx == HotKeyPos) {
+					if (HotKeyPos > -1 && idx == HotKeyPos) {
 						if ((isVertical && textVerticalAlignment == VerticalTextAlignment.Justified) ||
 						(!isVertical && textAlignment == TextAlignment.Justified)) {
 							CursorPosition = idx - start;

+ 5 - 4
Terminal.Gui/Core/Toplevel.cs

@@ -613,8 +613,9 @@ namespace Terminal.Gui {
 			}
 			nx = Math.Max (x, 0);
 			nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx;
-			if (nx + (top.Border != null && top.Border.DrawMarginFrame ? 2 : 1) > top.Frame.X + top.Frame.Width) {
-				nx = Math.Max (top.Frame.Right - (top.Border.DrawMarginFrame ? 2 : 1), 0);
+			var mfLength = top.Border?.DrawMarginFrame == true ? 2 : 1;
+			if (nx + mfLength > top.Frame.X + top.Frame.Width) {
+				nx = Math.Max (top.Frame.Right - mfLength, 0);
 			}
 			//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
 			bool m, s;
@@ -653,8 +654,8 @@ namespace Terminal.Gui {
 			}
 			ny = Math.Min (ny, l);
 			ny = ny + top.Frame.Height >= l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
-			if (ny + (top.Border != null && top.Border.DrawMarginFrame ? 2 : 1) > top.Frame.Y + top.Frame.Height) {
-				ny = Math.Max (top.Frame.Bottom - (top.Border.DrawMarginFrame ? 2 : 1), 0);
+			if (ny + mfLength > top.Frame.Y + top.Frame.Height) {
+				ny = Math.Max (top.Frame.Bottom - mfLength, 0);
 			}
 			//System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
 

+ 21 - 7
Terminal.Gui/Core/View.cs

@@ -1093,8 +1093,15 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public void Clear ()
 		{
-			var h = Frame.Height;
-			var w = Frame.Width;
+			Rect containerBounds = GetContainerBounds ();
+			Rect viewBounds = Bounds;
+			if (!containerBounds.IsEmpty) {
+				viewBounds.Width = Math.Min (viewBounds.Width, containerBounds.Width);
+				viewBounds.Height = Math.Min (viewBounds.Height, containerBounds.Height);
+			}
+
+			var h = viewBounds.Height;
+			var w = viewBounds.Width;
 			for (var line = 0; line < h; line++) {
 				Move (0, line);
 				for (var col = 0; col < w; col++)
@@ -1511,11 +1518,7 @@ namespace Terminal.Gui {
 				if (TextFormatter != null) {
 					TextFormatter.NeedsFormat = true;
 				}
-				var containerBounds = SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds);
-				containerBounds.X = Math.Max (containerBounds.X, Driver.Clip.X);
-				containerBounds.Y = Math.Max (containerBounds.Y, Driver.Clip.Y);
-				containerBounds.Width = Math.Min (containerBounds.Width, Driver.Clip.Width);
-				containerBounds.Height = Math.Min (containerBounds.Height, Driver.Clip.Height);
+				Rect containerBounds = GetContainerBounds ();
 				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
 				    HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
 				    containerBounds);
@@ -1558,6 +1561,17 @@ namespace Terminal.Gui {
 			ClearNeedsDisplay ();
 		}
 
+		Rect GetContainerBounds ()
+		{
+			var containerBounds = SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds);
+			var driverClip = Driver == null ? Rect.Empty : Driver.Clip;
+			containerBounds.X = Math.Max (containerBounds.X, driverClip.X);
+			containerBounds.Y = Math.Max (containerBounds.Y, driverClip.Y);
+			containerBounds.Width = Math.Min (containerBounds.Width, driverClip.Width);
+			containerBounds.Height = Math.Min (containerBounds.Height, driverClip.Height);
+			return containerBounds;
+		}
+
 		/// <summary>
 		/// Event invoked when the content area of the View is to be drawn.
 		/// </summary>

+ 0 - 1
Terminal.Gui/README.md

@@ -68,7 +68,6 @@ The PR title should be of the form "Release v2.3.4"
 git checkout develop
 git pull upstream develop
 git checkout -b v_2_3_4
-git merge develop
 git add .
 git commit -m "Release v2.3.4"
 git push

+ 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" />

+ 8 - 5
Terminal.Gui/Views/ListView.cs

@@ -430,7 +430,7 @@ namespace Terminal.Gui {
 				var newItem = KeystrokeNavigator?.GetNextMatchingItem (SelectedItem, (char)kb.KeyValue);
 				if (newItem is int && newItem != -1) {
 					SelectedItem = (int)newItem;
-					EnsuresVisibilitySelectedItem ();
+					EnsureSelectedItemVisible ();
 					SetNeedsDisplay ();
 					return true;
 				}
@@ -727,7 +727,7 @@ namespace Terminal.Gui {
 			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
 			if (lastSelectedItem == -1) {
-				EnsuresVisibilitySelectedItem ();
+				EnsureSelectedItemVisible ();
 			}
 
 			return base.OnEnter (view);
@@ -743,7 +743,10 @@ namespace Terminal.Gui {
 			return base.OnLeave (view);
 		}
 
-		void EnsuresVisibilitySelectedItem ()
+		/// <summary>
+		/// Ensures the selected item is always visible on the screen.
+		/// </summary>
+		public void EnsureSelectedItemVisible ()
 		{
 			SuperView?.LayoutSubviews ();
 			if (selected < top) {
@@ -840,7 +843,7 @@ namespace Terminal.Gui {
 			if (src == null || src?.Count == 0) {
 				return 0;
 			}
-			
+
 			int maxLength = 0;
 			for (int i = 0; i < src.Count; i++) {
 				var t = src [i];
@@ -924,7 +927,7 @@ namespace Terminal.Gui {
 						return i;
 					}
 				} else if (t is string s) {
-					if (s.ToUpperInvariant ().StartsWith (search.ToUpperInvariant ())) {
+					if (s.StartsWith (search, StringComparison.InvariantCultureIgnoreCase)) {
 						return i;
 					}
 				}

+ 68 - 5
Terminal.Gui/Views/TableView.cs

@@ -761,6 +761,41 @@ namespace Terminal.Gui {
 			SelectedRow = row;
 		}
 
+		/// <summary>
+		/// Unions the current selected cell (and/or regions) with the provided cell and makes
+		/// it the active one.
+		/// </summary>
+		/// <param name="col"></param>
+		/// <param name="row"></param>
+		private void UnionSelection (int col, int row)
+		{
+			if (!MultiSelect || TableIsNullOrInvisible()) {
+				return;
+			}
+			
+			EnsureValidSelection ();
+
+			var oldColumn = SelectedColumn;
+			var oldRow = SelectedRow;
+
+			// move us to the new cell
+			SelectedColumn = col;
+			SelectedRow = row;
+			MultiSelectedRegions.Push (
+				CreateTableSelection (col, row)
+				);
+
+			// if the old cell was not part of a rectangular select
+			// or otherwise selected we need to retain it in the selection
+
+			if (!IsSelected (oldColumn, oldRow)) {
+				MultiSelectedRegions.Push (
+					CreateTableSelection (oldColumn, oldRow)
+					);
+			}
+		}
+
+
 		/// <summary>
 		/// Moves the <see cref="SelectedRow"/> and <see cref="SelectedColumn"/> by the provided offsets. Optionally starting a box selection (see <see cref="MultiSelect"/>)
 		/// </summary>
@@ -794,22 +829,28 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Moves or extends the selection to the first cell in the table (0,0)
+		/// Moves or extends the selection to the first cell in the table (0,0).
+		/// If <see cref="FullRowSelect"/> is enabled then selection instead moves
+		/// to (<see cref="SelectedColumn"/>,0) i.e. no horizontal scrolling.
 		/// </summary>
 		/// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
 		public void ChangeSelectionToStartOfTable (bool extend)
 		{
-			SetSelection (0, 0, extend);
+			SetSelection (FullRowSelect ? SelectedColumn : 0, 0, extend);
 			Update ();
 		}
 
 		/// <summary>
-		/// Moves or extends the selection to the final cell in the table
+		/// Moves or extends the selection to the final cell in the table (nX,nY).
+		/// If <see cref="FullRowSelect"/> is enabled then selection instead moves
+		/// to (<see cref="SelectedColumn"/>,nY) i.e. no horizontal scrolling.
 		/// </summary>
 		/// <param name="extend">true to extend the current selection (if any) instead of replacing</param>
 		public void ChangeSelectionToEndOfTable(bool extend)
 		{
-			SetSelection (Table.Columns.Count - 1, Table.Rows.Count - 1, extend);
+			var finalColumn = Table.Columns.Count - 1;
+
+			SetSelection (FullRowSelect ? SelectedColumn : finalColumn, Table.Rows.Count - 1, extend);
 			Update ();
 		}
 
@@ -852,6 +893,8 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Returns all cells in any <see cref="MultiSelectedRegions"/> (if <see cref="MultiSelect"/> is enabled) and the selected cell
 		/// </summary>
+		/// <remarks>Return value is not affected by <see cref="FullRowSelect"/> (i.e. returned <see cref="Point"/>s are not expanded to 
+		/// include all points on row).</remarks>
 		/// <returns></returns>
 		public IEnumerable<Point> GetAllSelectedCells ()
 		{
@@ -914,6 +957,16 @@ namespace Terminal.Gui {
 			return new TableSelection (new Point (pt1X, pt1Y), new Rect (left, top, right - left + 1, bot - top + 1));
 		}
 
+		/// <summary>
+		/// Returns a single point as a <see cref="TableSelection"/>
+		/// </summary>
+		/// <param name="x"></param>
+		/// <param name="y"></param>
+		/// <returns></returns>
+		private TableSelection CreateTableSelection (int x, int y)
+		{
+			return CreateTableSelection (x, y, x, y);
+		}
 		/// <summary>
 		/// <para>
 		/// Returns true if the given cell is selected either because it is the active cell or part of a multi cell selection (e.g. <see cref="FullRowSelect"/>).
@@ -1039,7 +1092,12 @@ namespace Terminal.Gui {
 				var hit = ScreenToCell (me.X, me.Y);
 				if (hit != null) {
 
-					SetSelection (hit.Value.X, hit.Value.Y, me.Flags.HasFlag (MouseFlags.ButtonShift));
+					if(MultiSelect && HasControlOrAlt(me)) {
+						UnionSelection(hit.Value.X, hit.Value.Y);
+					} else {
+						SetSelection (hit.Value.X, hit.Value.Y, me.Flags.HasFlag (MouseFlags.ButtonShift));
+					}
+
 					Update ();
 				}
 			}
@@ -1055,6 +1113,11 @@ namespace Terminal.Gui {
 			return false;
 		}
 
+		private bool HasControlOrAlt (MouseEvent me)
+		{
+			return me.Flags.HasFlag (MouseFlags.ButtonAlt) || me.Flags.HasFlag (MouseFlags.ButtonCtrl);
+		}
+
 		/// <summary>.
 		/// Returns the column and row of <see cref="Table"/> that corresponds to a given point 
 		/// on the screen (relative to the control client area).  Returns null if the point is

+ 1 - 0
Terminal.Gui/Views/TextField.cs

@@ -298,6 +298,7 @@ namespace Terminal.Gui {
 					}
 					return;
 				}
+				ClearAllSelection ();
 				text = TextModel.ToRunes (newText.NewText);
 
 				if (!Secret && !historyText.IsFromHistory) {

+ 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" />

+ 15 - 12
UnitTests/ApplicationTests.cs → UnitTests/Application/ApplicationTests.cs

@@ -9,7 +9,7 @@ using Xunit;
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	public class ApplicationTests {
 		public ApplicationTests ()
 		{
@@ -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 ();
 

+ 46 - 63
UnitTests/MainLoopTests.cs → UnitTests/Application/MainLoopTests.cs

@@ -13,13 +13,18 @@ using Xunit.Sdk;
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
+	/// <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,14 +166,12 @@ 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 = () => {
 				functionCalled++;
-				if (functionCalled == 10) {
-					return false;
-				}
+				if (functionCalled == 10) 					return false;
 				return true;
 			};
 
@@ -176,9 +179,7 @@ namespace Terminal.Gui.Core {
 			var stopCount = 0;
 			Func<bool> fnStop = () => {
 				stopCount++;
-				if (stopCount == 20) {
-					ml.Stop ();
-				}
+				if (stopCount == 20) 					ml.Stop ();
 				return true;
 			};
 
@@ -195,7 +196,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 = () => {
@@ -207,9 +208,7 @@ namespace Terminal.Gui.Core {
 			var stopCount = 0;
 			Func<bool> fnStop = () => {
 				stopCount++;
-				if (stopCount == 10) {
-					ml.Stop ();
-				}
+				if (stopCount == 10) 					ml.Stop ();
 				return true;
 			};
 
@@ -227,14 +226,12 @@ 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 = () => {
 				functionCalled++;
-				if (functionCalled == 10) {
-					ml.Stop ();
-				}
+				if (functionCalled == 10) 					ml.Stop ();
 				return true;
 			};
 
@@ -249,11 +246,11 @@ 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;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				return true;
 			};
@@ -270,11 +267,11 @@ 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;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				ml.Stop ();
 				return true;
@@ -290,16 +287,14 @@ 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;
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
-				if (callbackCount == 2) {
-					ml.Stop ();
-				}
+				if (callbackCount == 2) 					ml.Stop ();
 				return true;
 			};
 
@@ -322,16 +317,14 @@ 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;
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
-				if (callbackCount == 2) {
-					ml.Stop ();
-				}
+				if (callbackCount == 2) 					ml.Stop ();
 				return true;
 			};
 
@@ -359,12 +352,12 @@ 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 ();
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				watch.Stop ();
 				callbackCount++;
 				ml.Stop ();
@@ -376,7 +369,7 @@ namespace Terminal.Gui.Core {
 			ml.Run ();
 			// +/- 100ms should be good enuf
 			// https://github.com/xunit/assert.xunit/pull/25
-			Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
+			Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
 
 			Assert.True (ml.RemoveTimeout (token));
 			Assert.Equal (1, callbackCount);
@@ -385,12 +378,12 @@ 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 ();
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				if (callbackCount == 2) {
 					watch.Stop ();
@@ -404,7 +397,7 @@ namespace Terminal.Gui.Core {
 			ml.Run ();
 			// +/- 100ms should be good enuf
 			// https://github.com/xunit/assert.xunit/pull/25
-			Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
+			Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
 
 			Assert.True (ml.RemoveTimeout (token));
 			Assert.Equal (2, callbackCount);
@@ -413,22 +406,20 @@ 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
 			var stopCount = 0;
 			Func<bool> fnStop = () => {
 				stopCount++;
-				if (stopCount == 10) {
-					ml.Stop ();
-				}
+				if (stopCount == 10) 					ml.Stop ();
 				return true;
 			};
 			ml.AddIdle (fnStop);
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				return true;
 			};
@@ -442,7 +433,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
@@ -450,15 +441,13 @@ namespace Terminal.Gui.Core {
 			Func<bool> fnStop = () => {
 				Thread.Sleep (10); // Sleep to enable timer to fire
 				stopCount++;
-				if (stopCount == 10) {
-					ml.Stop ();
-				}
+				if (stopCount == 10) 					ml.Stop ();
 				return true;
 			};
 			ml.AddIdle (fnStop);
 
 			var callbackCount = 0;
-			Func<MainLoop, bool> callback = (MainLoop loop) => {
+			Func<MainLoop, bool> callback = (loop) => {
 				callbackCount++;
 				return false;
 			};
@@ -475,7 +464,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++; });
@@ -536,10 +525,8 @@ namespace Terminal.Gui.Core {
 				Application.MainLoop.Invoke (() => {
 					tf.Text = $"index{r.Next ()}";
 					Interlocked.Increment (ref tbCounter);
-					if (target == tbCounter) {
-						// On last increment wake up the check
+					if (target == tbCounter) 						// On last increment wake up the check
 						_wakeUp.Set ();
-					}
 				});
 			});
 		}
@@ -549,9 +536,7 @@ namespace Terminal.Gui.Core {
 			for (int j = 0; j < numPasses; j++) {
 
 				_wakeUp.Reset ();
-				for (var i = 0; i < numIncrements; i++) {
-					Launch (r, tf, (j + 1) * numIncrements);
-				}
+				for (var i = 0; i < numIncrements; i++) 					Launch (r, tf, (j + 1) * numIncrements);
 
 
 				while (tbCounter != (j + 1) * numIncrements) // Wait for tbCounter to reach expected value
@@ -589,7 +574,7 @@ namespace Terminal.Gui.Core {
 
 			await task; // Propagate exception if any occurred
 
-			Assert.Equal ((numIncrements * numPasses), tbCounter);
+			Assert.Equal (numIncrements * numPasses, tbCounter);
 		}
 
 		private static int total;
@@ -647,9 +632,7 @@ namespace Terminal.Gui.Core {
 					Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null)));
 					Assert.Equal (cancel, btn.Text);
 					Assert.Equal (one, total);
-				} else if (taskCompleted) {
-					Application.RequestStop ();
-				}
+				} else if (taskCompleted) 					Application.RequestStop ();
 			};
 
 			Application.Run ();

+ 13 - 7
UnitTests/RunStateTests.cs → UnitTests/Application/RunStateTests.cs

@@ -3,12 +3,13 @@ using System.Diagnostics;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Terminal.Gui;
 using Xunit;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	/// <summary>
 	/// These tests focus on Application.RunState and the various ways it can be changed.
 	/// </summary>
@@ -26,7 +27,7 @@ namespace Terminal.Gui.Core {
 		{
 			var rs = new Application.RunState (null);
 			Assert.Null (rs.Toplevel);
-			
+
 			var top = new Toplevel ();
 			rs = new Application.RunState (top);
 			Assert.Equal (top, rs.Toplevel);
@@ -40,8 +41,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 +54,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 +71,10 @@ 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);
-			}
+			foreach (var inst in Application.RunState.Instances) 				Assert.True (inst.WasDisposed);
+#endif
 		}
 
 		[Fact]
@@ -95,7 +99,9 @@ namespace Terminal.Gui.Core {
 
 			Shutdown ();
 
+#if DEBUG_IDISPOSABLE
 			Assert.True (rs.WasDisposed);
+#endif
 
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);

+ 1 - 1
UnitTests/StackExtensionsTests.cs → UnitTests/Application/StackExtensionsTests.cs

@@ -2,7 +2,7 @@
 using System.Collections.Generic;
 using Xunit;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	public class StackExtensionsTests {
 		[Fact]
 		public void Stack_Toplevels_CreateToplevels ()

+ 1 - 1
UnitTests/SynchronizatonContextTests.cs → UnitTests/Application/SynchronizatonContextTests.cs

@@ -13,7 +13,7 @@ using Xunit.Sdk;
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ApplicationTests {
 	public class SyncrhonizationContextTests {
 
 		[Fact, AutoInitShutdown]

+ 0 - 324
UnitTests/ClipboardTests.cs

@@ -1,324 +0,0 @@
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using Xunit;
-
-namespace Terminal.Gui.Core {
-	public class ClipboardTests {
-		[Fact]
-		[AutoInitShutdown]
-		public void Contents_Gets_Sets ()
-		{
-			var clipText = "This is a clipboard unit test.";
-			Clipboard.Contents = clipText;
-
-			Application.Iteration += () => Application.RequestStop ();
-
-			Application.Run ();
-
-			Assert.Equal (clipText, Clipboard.Contents);
-		}
-
-		[Fact]
-		[AutoInitShutdown]
-		public void IsSupported_Get ()
-		{
-			if (Clipboard.IsSupported) {
-				Assert.True (Clipboard.IsSupported);
-			} else {
-				Assert.False (Clipboard.IsSupported);
-			}
-		}
-
-		[Fact]
-		[AutoInitShutdown]
-		public void TryGetClipboardData_Gets_From_OS_Clipboard ()
-		{
-			var clipText = "Trying to get from the OS clipboard.";
-			Clipboard.Contents = clipText;
-
-			Application.Iteration += () => Application.RequestStop ();
-
-			Application.Run ();
-
-			if (Clipboard.IsSupported) {
-				Assert.True (Clipboard.TryGetClipboardData (out string result));
-				Assert.Equal (clipText, result);
-			} else {
-				Assert.False (Clipboard.TryGetClipboardData (out string result));
-				Assert.NotEqual (clipText, result);
-			}
-		}
-
-		[Fact]
-		[AutoInitShutdown]
-		public void TrySetClipboardData_Sets_The_OS_Clipboard ()
-		{
-			var clipText = "Trying to set the OS clipboard.";
-			if (Clipboard.IsSupported) {
-				Assert.True (Clipboard.TrySetClipboardData (clipText));
-			} else {
-				Assert.False (Clipboard.TrySetClipboardData (clipText));
-			}
-
-			Application.Iteration += () => Application.RequestStop ();
-
-			Application.Run ();
-
-			if (Clipboard.IsSupported) {
-				Assert.Equal (clipText, Clipboard.Contents);
-			} else {
-				Assert.NotEqual (clipText, Clipboard.Contents);
-			}
-		}
-
-		[Fact]
-		[AutoInitShutdown]
-		public void Contents_Gets_From_OS_Clipboard ()
-		{
-			var clipText = "This is a clipboard unit test to get clipboard from OS.";
-			var exit = false;
-			var getClipText = "";
-
-			Application.Iteration += () => {
-				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 ();
-					}
-					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 ();
-					}
-					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 ();
-							//	//}
-							//}
-						} catch {
-							exit = true;
-						}
-						if (!exit) {
-							getClipText = Clipboard.Contents.ToString ();
-						}
-						Application.RequestStop ();
-						return;
-					}
-					if (exit = xclipExists () == false) {
-						// xclip doesn't exist then exit.
-						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) {
-						getClipText = Clipboard.Contents.ToString ();
-					}
-				}
-
-				Application.RequestStop ();
-			};
-
-			Application.Run ();
-
-			if (!exit) {
-				Assert.Equal (clipText, getClipText);
-			}
-		}
-
-		[Fact]
-		[AutoInitShutdown]
-		public void Contents_Sets_The_OS_Clipboard ()
-		{
-			var clipText = "This is a clipboard unit test to set the OS clipboard.";
-			var clipReadText = "";
-			var exit = false;
-
-			Application.Iteration += () => {
-				Clipboard.Contents = clipText;
-
-				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 ();
-					}
-				} 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 ();
-					}
-				} 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;
-						}
-						Application.RequestStop ();
-					}
-					if (exit = xclipExists () == false) {
-						// xclip doesn't exist then exit.
-						Application.RequestStop ();
-					}
-
-					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 ();
-					}
-				}
-
-				Application.RequestStop ();
-			};
-
-			Application.Run ();
-
-			if (!exit) {
-				Assert.Equal (clipText, clipReadText);
-			}
-		}
-
-		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;
-			}
-		}
-
-		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;
-			} catch (System.Exception) {
-				return false;
-			}
-		}
-	}
-}

+ 1 - 1
UnitTests/BorderTests.cs → UnitTests/Core/BorderTests.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Rune = System.Rune;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.CoreTests {
 	public class BorderTests {
 		[Fact]
 		[AutoInitShutdown]

+ 1 - 6
UnitTests/ResponderTests.cs → UnitTests/Core/ResponderTests.cs

@@ -1,14 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Terminal.Gui;
 using Xunit;
-using static Terminal.Gui.Core.ViewTests;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.CoreTests {
 	public class ResponderTests {
 		[Fact]
 		public void New_Initializes ()

+ 5 - 5
UnitTests/AttributeTests.cs → UnitTests/Drivers/AttributeTests.cs

@@ -7,13 +7,13 @@ using Xunit;
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.ConsoleDrivers {
+namespace Terminal.Gui.DriverTests {
 	public class AttributeTests {
 		[Fact]
 		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;

+ 288 - 0
UnitTests/Drivers/ClipboardTests.cs

@@ -0,0 +1,288 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Terminal.Gui;
+using Xunit;
+using Xunit.Abstractions;
+using static AutoInitShutdownAttribute;
+
+namespace Terminal.Gui.DriverTests {
+	public class ClipboardTests {
+		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 ()
+		{
+			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.ToString ());
+		}
+
+		[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) 				Assert.True (Clipboard.IsSupported);
+else 				Assert.False (Clipboard.IsSupported);
+		}
+
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
+		public void TryGetClipboardData_Gets_From_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 ();
+
+			Application.Run ();
+
+			if (Clipboard.IsSupported) {
+				Assert.True (Clipboard.TryGetClipboardData (out string result));
+				Assert.Equal (clipText, result);
+			} else {
+				Assert.False (Clipboard.TryGetClipboardData (out string result));
+				Assert.NotEqual (clipText, result);
+			}
+		}
+
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
+		public void TrySetClipboardData_Sets_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 				Assert.False (Clipboard.TrySetClipboardData (clipText));
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Application.Run ();
+
+			if (Clipboard.IsSupported) 				Assert.Equal (clipText, Clipboard.Contents);
+else 				Assert.NotEqual (clipText, Clipboard.Contents);
+		}
+
+
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
+		public void Contents_Copies_From_OS_Clipboard ()
+		{
+			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)) {
+					(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)) {
+					(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 {
+							// 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 {
+							failed = true;
+						}
+
+						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 (failed = xclipExists () == false) {
+						// if xclip doesn't exist then exit.
+						output.WriteLine ($"  WSL: no xclip found.");
+						Application.RequestStop ();
+						return;
+					}
+
+					// 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}");
+					}
+				}
+
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+
+			if (!failed) 				Assert.Equal (clipText, getClipText);
+		}
+
+		[Fact, AutoInitShutdown (useFakeClipboard: false)]
+		public void Contents_Pastes_To_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 failed = false;
+
+			Application.Iteration += () => {
+				Clipboard.Contents = clipText;
+
+				int exitCode = 0;
+				output.WriteLine ($"Getting OS clipboard...");
+
+				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+					(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)) {
+					(exitCode, clipReadText) = ClipboardProcessRunner.Process ("pbpaste", "");
+					output.WriteLine ($"  OSX: pbpaste: exitCode = {exitCode}, result = {clipReadText}");
+
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
+					if (Is_WSL_Platform ()) {
+						(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;
+						}
+						failed = true;
+					}
+
+					if (failed = xclipExists () == false) {
+						// xclip doesn't exist then exit.
+						Application.RequestStop ();
+						return;
+					}
+
+					(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 ();
+			};
+
+			Application.Run ();
+
+			if (!failed) 				Assert.Equal (clipText, clipReadText.TrimEnd ());
+
+		}
+
+		bool Is_WSL_Platform ()
+		{
+			var (_, result) = ClipboardProcessRunner.Process ("bash", $"-c \"uname -a\"");
+			return result.Contains ("microsoft") && result.Contains ("WSL");
+		}
+
+		bool xclipExists ()
+		{
+			try {
+				var (_, result) = ClipboardProcessRunner.Process ("bash", $"-c \"which xclip\"");
+				return result.TrimEnd () != "";
+			} catch (Exception) {
+				return false;
+			}
+		}
+	}
+}

+ 39 - 0
UnitTests/Drivers/ColorTests.cs

@@ -0,0 +1,39 @@
+using System;
+using Xunit;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.DriverTests {
+	public class ColorTests {
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		//[InlineData (typeof (NetDriver))]
+		//[InlineData (typeof (CursesDriver))]
+		//[InlineData (typeof (WindowsDriver))]
+		public void SetColors_Changes_Colors (Type driverType)
+		{
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+			driver.Init (() => { });
+			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
+			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
+
+			Console.ForegroundColor = ConsoleColor.Red;
+			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
+
+			Console.BackgroundColor = ConsoleColor.Green;
+			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
+
+			Console.ResetColor ();
+			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
+			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
+			driver.End ();
+
+			// Shutdown must be called to safely clean up Application if Init has been called
+			Application.Shutdown ();
+		}
+
+	}
+}

+ 72 - 280
UnitTests/ConsoleDriverTests.cs → UnitTests/Drivers/ConsoleDriverTests.cs

@@ -2,14 +2,13 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
-using Terminal.Gui.Views;
 using Xunit;
 using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.ConsoleDrivers {
+namespace Terminal.Gui.DriverTests {
 	public class ConsoleDriverTests {
 		readonly ITestOutputHelper output;
 
@@ -18,11 +17,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,17 +40,21 @@ 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;
+			Console.ForegroundColor = ConsoleColor.Red;
 			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
 
-			FakeConsole.BackgroundColor = ConsoleColor.Green;
+			Console.BackgroundColor = ConsoleColor.Green;
 			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
 			driver.Move (2, 3);
 			Assert.Equal (2, Console.CursorLeft);
@@ -63,34 +70,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void SetColors_Changes_Colors ()
-		{
-			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
-			driver.Init (() => { });
-			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
-			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
-
-			Console.ForegroundColor = ConsoleColor.Red;
-			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
-
-			Console.BackgroundColor = ConsoleColor.Green;
-			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
-
-			Console.ResetColor ();
-			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
-			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
-			driver.End ();
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			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 ();
@@ -104,9 +89,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 
 			Application.Iteration += () => {
 				count++;
-				if (count == 10) {
-					Application.RequestStop ();
-				}
+				if (count == 10) Application.RequestStop ();
 			};
 
 			Application.Run ();
@@ -117,10 +100,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> ();
@@ -129,7 +114,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 				var cki = new ConsoleKeyInfo (r, ck, false, false, false);
 				mKeys.Push (cki);
 			}
-			FakeConsole.MockKeyPresses = mKeys;
+			Console.MockKeyPresses = mKeys;
 
 			var top = Application.Top;
 			var view = new View ();
@@ -146,9 +131,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 			top.Add (view);
 
 			Application.Iteration += () => {
-				if (mKeys.Count == 0) {
-					Application.RequestStop ();
-				}
+				if (mKeys.Count == 0) Application.RequestStop ();
 			};
 
 			Application.Run ();
@@ -159,108 +142,12 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void SendKeys_Test ()
-		{
-			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
-
-			var top = Application.Top;
-			var view = new View ();
-			var shift = false; var alt = false; var control = false;
-			Key key = default;
-			Key lastKey = default;
-			List<Key> keyEnums = GetKeys ();
-			int i = 0;
-			int idxKey = 0;
-			var PushIterations = 0;
-			var PopIterations = 0;
-
-			List<Key> GetKeys ()
-			{
-				List<Key> keys = new List<Key> ();
-
-				foreach (Key k in Enum.GetValues (typeof (Key))) {
-					if ((uint)k <= 0xff) {
-						keys.Add (k);
-					} else if ((uint)k > 0xff) {
-						break;
-					}
-				}
-
-				return keys;
-			}
-
-			view.KeyPress += (e) => {
-				e.Handled = true;
-				PopIterations++;
-				var rMk = new KeyModifiers () {
-					Shift = e.KeyEvent.IsShift,
-					Alt = e.KeyEvent.IsAlt,
-					Ctrl = e.KeyEvent.IsCtrl
-				};
-				lastKey = ShortcutHelper.GetModifiersKey (new KeyEvent (e.KeyEvent.Key, rMk));
-				Assert.Equal (key, lastKey);
-			};
-			top.Add (view);
-
-			Application.Iteration += () => {
-				switch (i) {
-				case 0:
-					SendKeys ();
-					break;
-				case 1:
-					shift = true;
-					SendKeys ();
-					break;
-				case 2:
-					alt = true;
-					SendKeys ();
-					break;
-				case 3:
-					control = true;
-					SendKeys ();
-					break;
-				}
-				if (PushIterations == keyEnums.Count * 4) {
-					Application.RequestStop ();
-				}
-			};
-
-			void SendKeys ()
-			{
-				var k = shift && char.IsLetter ((char)keyEnums [idxKey]) && char.IsLower ((char)keyEnums [idxKey])
-					? (Key)char.ToUpper ((char)keyEnums [idxKey]) : keyEnums [idxKey];
-				var c = (char)k;
-				var ck = char.IsLetter (c) ? (ConsoleKey)char.ToUpper (c) : (ConsoleKey)c;
-				var mk = new KeyModifiers () {
-					Shift = shift,
-					Alt = alt,
-					Ctrl = control
-				};
-				key = ShortcutHelper.GetModifiersKey (new KeyEvent (k, mk));
-				Application.Driver.SendKeys (c, ck, shift, alt, control);
-				PushIterations++;
-				if (idxKey + 1 < keyEnums.Count) {
-					idxKey++;
-				} else {
-					idxKey = 0;
-					i++;
-				}
-			}
-
-			Application.Run ();
-
-			Assert.Equal (key, lastKey);
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			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 +184,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 +202,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 +219,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 +259,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 +276,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);
@@ -427,93 +319,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 
 			Application.Shutdown ();
 		}
-
-		[Fact]
-		public void Internal_Tests ()
-		{
-			var cs = new ColorScheme ();
-			Assert.Equal ("", cs.caller);
-		}
-
-		[Fact]
-		[AutoInitShutdown]
-		public void KeyModifiers_Resetting_At_New_Keystrokes ()
-		{
-			bool? okInitialFocused = null;
-			bool? cancelInitialFocused = null;
-			var okClicked = false;
-			var closing = false;
-			var cursorRight = false;
-			var endingKeyPress = false;
-			var closed = false;
-
-			var top = Application.Top;
-
-			var ok = new Button ("Ok");
-			ok.Clicked += () => {
-				if (!okClicked) {
-					okClicked = true;
-					Application.RequestStop ();
-				}
-			};
-
-			var cancel = new Button ("Cancel");
-
-			var d = new Dialog ("Quit", cancel, ok);
-			d.KeyPress += (e) => {
-				if (e.KeyEvent.Key == (Key.Q | Key.CtrlMask)) {
-					if (!okClicked && !closing) {
-						okInitialFocused = ok.HasFocus;
-						cancelInitialFocused = cancel.HasFocus;
-						closing = true;
-						var mKeys = new Stack<ConsoleKeyInfo> ();
-						var cki = new ConsoleKeyInfo ('\0', ConsoleKey.Enter, false, false, false);
-						mKeys.Push (cki);
-						cki = new ConsoleKeyInfo ('\0', ConsoleKey.RightArrow, false, false, false);
-						mKeys.Push (cki);
-						FakeConsole.MockKeyPresses = mKeys;
-					}
-					e.Handled = true;
-				} else if (e.KeyEvent.Key == Key.CursorRight) {
-					if (!cursorRight) {
-						cursorRight = true;
-					} else if (ok.HasFocus) {
-						e.Handled = endingKeyPress = true;
-					}
-				}
-			};
-			d.Loaded += () => {
-				var mKeys = new Stack<ConsoleKeyInfo> ();
-				var cki = new ConsoleKeyInfo ('q', ConsoleKey.Q, false, false, true);
-				mKeys.Push (cki);
-				FakeConsole.MockKeyPresses = mKeys;
-			};
-			d.Closed += (_) => {
-				if (okClicked && closing) {
-					closed = true;
-				}
-			};
-
-			top.Ready += () => Application.Run (d);
-
-			Application.Iteration += () => {
-				if (closed) {
-					Application.RequestStop ();
-				}
-			};
-
-			Application.Run ();
-
-			Assert.False (okInitialFocused);
-			Assert.True (cancelInitialFocused);
-			Assert.True (okClicked);
-			Assert.True (closing);
-			Assert.True (cursorRight);
-			Assert.True (endingKeyPress);
-			Assert.True (closed);
-			Assert.Empty (FakeConsole.MockKeyPresses);
-		}
-
+		
 		[Fact, AutoInitShutdown]
 		public void AddRune_On_Clip_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
 		{
@@ -645,33 +451,21 @@ namespace Terminal.Gui.ConsoleDrivers {
 		[ClassData (typeof (PacketTest))]
 		public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey, uint initialScanCode, Key expectedRemapping, uint expectedVirtualKey, uint expectedScanCode)
 		{
-			ConsoleModifiers modifiers = new ConsoleModifiers ();
-			if (shift) {
-				modifiers |= ConsoleModifiers.Shift;
-			}
-			if (alt) {
-				modifiers |= ConsoleModifiers.Alt;
-			}
-			if (control) {
-				modifiers |= ConsoleModifiers.Control;
-			}
+			var modifiers = new ConsoleModifiers ();
+			if (shift) modifiers |= ConsoleModifiers.Shift;
+			if (alt) modifiers |= ConsoleModifiers.Alt;
+			if (control) modifiers |= ConsoleModifiers.Control;
 			var mappedConsoleKey = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode, out uint outputChar);
 
-			if ((scanCode > 0 || mappedConsoleKey == 0) && mappedConsoleKey == initialVirtualKey) {
-				Assert.Equal (mappedConsoleKey, initialVirtualKey);
-			} else {
-				Assert.Equal (mappedConsoleKey, outputChar < 0xff ? (uint)(outputChar & 0xff | 0xff << 8) : outputChar);
-			}
+			if ((scanCode > 0 || mappedConsoleKey == 0) && mappedConsoleKey == initialVirtualKey) Assert.Equal (mappedConsoleKey, initialVirtualKey);
+			else Assert.Equal (mappedConsoleKey, outputChar < 0xff ? outputChar & 0xff | 0xff << 8 : outputChar);
 			Assert.Equal (scanCode, initialScanCode);
 
 			var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (mappedConsoleKey, modifiers, out uint consoleKey, out scanCode);
 
 			//if (scanCode > 0 && consoleKey == keyChar && consoleKey > 48 && consoleKey > 57 && consoleKey < 65 && consoleKey > 91) {
-			if (scanCode > 0 && keyChar == 0 && consoleKey == mappedConsoleKey) {
-				Assert.Equal (0, (double)keyChar);
-			} else {
-				Assert.Equal (keyChar, unicodeCharacter);
-			}
+			if (scanCode > 0 && keyChar == 0 && consoleKey == mappedConsoleKey) Assert.Equal (0, (double)keyChar);
+			else Assert.Equal (keyChar, unicodeCharacter);
 			Assert.Equal (consoleKey, expectedVirtualKey);
 			Assert.Equal (scanCode, expectedScanCode);
 
@@ -688,9 +482,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 
 			Application.Iteration += () => {
 				iterations++;
-				if (iterations == 0) {
-					Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control);
-				}
+				if (iterations == 0) Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control);
 			};
 
 			Application.Run ();

+ 1 - 1
UnitTests/KeyTests.cs → UnitTests/Drivers/KeyTests.cs

@@ -1,7 +1,7 @@
 using System;
 using Xunit;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.DriverTests {
 	public class KeyTests {
 		enum SimpleEnum { Zero, One, Two, Three, Four, Five }
 

+ 2 - 2
UnitTests/ContextMenuTests.cs → UnitTests/Menus/ContextMenuTests.cs

@@ -2,9 +2,9 @@
 using System.Threading;
 using Xunit;
 using Xunit.Abstractions;
-using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
+//using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.MenuTests {
 	public class ContextMenuTests {
 		readonly ITestOutputHelper output;
 

+ 2 - 2
UnitTests/MenuTests.cs → UnitTests/Menus/MenuTests.cs

@@ -3,9 +3,9 @@ using System.Collections.Generic;
 using System.Linq;
 using Xunit;
 using Xunit.Abstractions;
-using static Terminal.Gui.Views.MenuTests;
+//using static Terminal.Gui.ViewTests.MenuTests;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.MenuTests {
 	public class MenuTests {
 		readonly ITestOutputHelper output;
 

+ 52 - 13
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)
 	{
@@ -212,7 +251,7 @@ class TestHelpers {
 
 				var match = expectedColors.Where (e => e.Value == val).ToList ();
 				if (match.Count == 0) {
-					throw new Exception ($"Unexpected color {DescribeColor (val)} was used at row {r} and col {c} (indexes start at 0).  Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => c.Value))})");
+					throw new Exception ($"Unexpected color {DescribeColor (val)} was used at row {r} and col {c} (indexes start at 0).  Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => DescribeColor (c.Value)))})");
 				} else if (match.Count > 1) {
 					throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value");
 				}
@@ -221,7 +260,7 @@ class TestHelpers {
 				var userExpected = line [c];
 
 				if (colorUsed != userExpected) {
-					throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0).  Color index used was {DescribeColor (colorUsed)} but test expected {DescribeColor (userExpected)} (these are indexes into the expectedColors array)");
+					throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0).  Color index used was {colorUsed} ({DescribeColor (val)}) but test expected {userExpected} ({DescribeColor (expectedColors [int.Parse (userExpected.ToString ())].Value)}) (these are indexes into the expectedColors array)");
 				}
 			}
 

+ 1 - 1
UnitTests/CollectionNavigatorTests.cs → UnitTests/Text/CollectionNavigatorTests.cs

@@ -2,7 +2,7 @@
 using System.Threading;
 using Xunit;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TextTests {
 	public class CollectionNavigatorTests {
 		static string [] simpleStrings = new string []{
 		    "appricot", // 0

+ 65 - 36
UnitTests/TextFormatterTests.cs → UnitTests/Text/TextFormatterTests.cs

@@ -2,14 +2,14 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using Terminal.Gui.Views;
+using Terminal.Gui;
 using Xunit;
 using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TextTests {
 	public class TextFormatterTests {
 		readonly ITestOutputHelper output;
 
@@ -394,7 +394,7 @@ namespace Terminal.Gui.Core {
 			bool supportFirstUpperCase = true;
 
 			var text = ustring.Empty;
-			Rune hotKeySpecifier = (Rune)0;
+			var hotKeySpecifier = (Rune)0;
 			int hotPos = 0;
 			Key hotKey = Key.Unknown;
 			bool result = false;
@@ -437,7 +437,7 @@ namespace Terminal.Gui.Core {
 			bool supportFirstUpperCase = true;
 
 			var text = ustring.Empty;
-			Rune hotKeySpecifier = (Rune)0;
+			var hotKeySpecifier = (Rune)0;
 			int hotPos = 0;
 			Key hotKey = Key.Unknown;
 			bool result = false;
@@ -2162,9 +2162,7 @@ namespace Terminal.Gui.Core {
 			var height = 8;
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
+			foreach (var line in wrappedLines) 				breakLines += $"{line}{Environment.NewLine}";
 			var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 
@@ -2202,9 +2200,7 @@ namespace Terminal.Gui.Core {
 			var height = 3;
 			var wrappedLines = TextFormatter.WordWrap (text, height, true);
 			var breakLines = "";
-			for (int i = 0; i < wrappedLines.Count; i++) {
-				breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}";
-			}
+			for (int i = 0; i < wrappedLines.Count; i++) 				breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}";
 			var label = new Label (breakLines) {
 				TextDirection = TextDirection.TopBottom_LeftRight,
 				Width = Dim.Fill (),
@@ -2241,9 +2237,7 @@ namespace Terminal.Gui.Core {
 			var height = 8;
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
+			foreach (var line in wrappedLines) 				breakLines += $"{line}{Environment.NewLine}";
 			var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () };
 			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
 
@@ -2282,9 +2276,7 @@ namespace Terminal.Gui.Core {
 			var height = 4;
 			var wrappedLines = TextFormatter.WordWrap (text, width, true);
 			var breakLines = "";
-			for (int i = 0; i < wrappedLines.Count; i++) {
-				breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}";
-			}
+			for (int i = 0; i < wrappedLines.Count; i++) 				breakLines += $"{wrappedLines [i]}{(i < wrappedLines.Count - 1 ? Environment.NewLine : string.Empty)}";
 			var label = new Label (breakLines) {
 				TextDirection = TextDirection.TopBottom_LeftRight,
 				Width = Dim.Fill (),
@@ -2433,13 +2425,13 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (ustring.Make (new Rune [] { 't', tag, 's', 't' }), tf.ReplaceHotKeyWithTag (text, hotPos));
 
 			var result = tf.ReplaceHotKeyWithTag (text, hotPos);
-			Assert.Equal ('e', (uint)(result.ToRunes () [1]));
+			Assert.Equal ('e', result.ToRunes () [1]);
 
 			text = "Ok";
 			tag = 'O';
 			hotPos = 0;
 			Assert.Equal (ustring.Make (new Rune [] { tag, 'k' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
-			Assert.Equal ('O', (uint)(result.ToRunes () [0]));
+			Assert.Equal ('O', result.ToRunes () [0]);
 
 			text = "[◦ Ok ◦]";
 			text = ustring.Make (new Rune [] { '[', '◦', ' ', 'O', 'k', ' ', '◦', ']' });
@@ -2449,13 +2441,13 @@ namespace Terminal.Gui.Core {
 			tag = 'O';
 			hotPos = 3;
 			Assert.Equal (ustring.Make (new Rune [] { '[', '◦', ' ', tag, 'k', ' ', '◦', ']' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
-			Assert.Equal ('O', (uint)(result.ToRunes () [3]));
+			Assert.Equal ('O', result.ToRunes () [3]);
 
 			text = "^k";
 			tag = '^';
 			hotPos = 0;
 			Assert.Equal (ustring.Make (new Rune [] { tag, 'k' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
-			Assert.Equal ('^', (uint)(result.ToRunes () [0]));
+			Assert.Equal ('^', result.ToRunes () [0]);
 		}
 
 		[Fact]
@@ -2814,37 +2806,37 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void System_Rune_ColumnWidth ()
 		{
-			var c = new System.Rune ('a');
+			var c = new Rune ('a');
 			Assert.Equal (1, Rune.ColumnWidth (c));
 			Assert.Equal (1, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
 
-			c = new System.Rune ('b');
+			c = new Rune ('b');
 			Assert.Equal (1, Rune.ColumnWidth (c));
 			Assert.Equal (1, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
 
-			c = new System.Rune (123);
+			c = new Rune (123);
 			Assert.Equal (1, Rune.ColumnWidth (c));
 			Assert.Equal (1, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
 
-			c = new System.Rune ('\u1150');
+			c = new Rune ('\u1150');
 			Assert.Equal (2, Rune.ColumnWidth (c));      // 0x1150	ᅐ	Unicode Technical Report #11
 			Assert.Equal (2, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (3, ustring.Make (c).Length);
 
-			c = new System.Rune ('\u1161');
+			c = new Rune ('\u1161');
 			Assert.Equal (0, Rune.ColumnWidth (c));      // 0x1161	ᅡ	column width of 0
 			Assert.Equal (0, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (3, ustring.Make (c).Length);
 
-			c = new System.Rune (31);
+			c = new Rune (31);
 			Assert.Equal (-1, Rune.ColumnWidth (c));        // non printable character
 			Assert.Equal (0, ustring.Make (c).ConsoleWidth);// ConsoleWidth only returns zero or greater than zero
 			Assert.Equal (1, ustring.Make (c).Length);
 
-			c = new System.Rune (127);
+			c = new Rune (127);
 			Assert.Equal (-1, Rune.ColumnWidth (c));       // non printable character
 			Assert.Equal (0, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
@@ -2896,9 +2888,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("nd", list1 [10].ToString ());
 			Assert.Equal ("Line", list1 [11].ToString ());
 			Assert.Equal ("- 2.", list1 [^1].ToString ());
-			foreach (var txt in list1) {
-				wrappedText1 += txt;
-			}
+			foreach (var txt in list1) 				wrappedText1 += txt;
 			Assert.Equal (" Asentencehaswords.  This isthesecondLine- 2.", wrappedText1);
 
 			// With preserveTrailingSpaces = true.
@@ -2920,9 +2910,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("Line", list2 [13].ToString ());
 			Assert.Equal (" - ", list2 [14].ToString ());
 			Assert.Equal ("2. ", list2 [^1].ToString ());
-			foreach (var txt in list2) {
-				wrappedText2 += txt;
-			}
+			foreach (var txt in list2) 				wrappedText2 += txt;
 			Assert.Equal (" A sentence has words.  This is the second Line - 2. ", wrappedText2);
 		}
 
@@ -2968,7 +2956,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 +2996,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;
 
@@ -3430,7 +3418,7 @@ This TextFormatter (tf2) is rewritten.
 		[Fact]
 		public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes ()
 		{
-			List<ustring> text = new List<ustring> () { "Hello", "World" };
+			var text = new List<ustring> () { "Hello", "World" };
 			Assert.Equal (2, TextFormatter.GetSumMaxCharWidth (text));
 			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
 			text = new List<ustring> () { "こんにちは", "世界" };
@@ -4259,5 +4247,46 @@ This TextFormatter (tf2) is rewritten.
 0111000000
 0000000000", expectedColors);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Colors_On_TextAlignment_Right_And_Bottom ()
+		{
+			var labelRight = new Label ("Test") {
+				Width = 6,
+				Height = 1,
+				TextAlignment = TextAlignment.Right,
+				ColorScheme = Colors.Base
+			};
+			var labelBottom = new Label ("Test", TextDirection.TopBottom_LeftRight) {
+				Y = 1,
+				Width = 1,
+				Height = 6,
+				VerticalTextAlignment = VerticalTextAlignment.Bottom,
+				ColorScheme = Colors.Base
+			};
+			var top = Application.Top;
+			top.Add (labelRight, labelBottom);
+
+			Application.Begin (top);
+			((FakeDriver)Application.Driver).SetBufferSize (7, 7);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+  Test
+      
+      
+T     
+e     
+s     
+t     ", output);
+
+			TestHelpers.AssertDriverColorsAre (@"
+000000
+0
+0
+0
+0
+0
+0", new Attribute [] { Colors.Base.Normal });
+		}
 	}
 }

+ 45 - 34
UnitTests/DialogTests.cs → UnitTests/TopLevels/DialogTests.cs

@@ -9,7 +9,7 @@ using System.Globalization;
 using Xunit.Abstractions;
 using NStack;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.TopLevelTests {
 
 	public class DialogTests {
 		readonly ITestOutputHelper output;
@@ -29,7 +29,7 @@ namespace Terminal.Gui.Views {
 		[AutoInitShutdown]
 		public void ButtonAlignment_One ()
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 			Application.RunState runstate = null;
 
 			var title = "1234";
@@ -37,8 +37,8 @@ namespace Terminal.Gui.Views {
 			var btnText = "ok";
 			var buttonRow = $"{d.VLine}   {d.LeftBracket} {btnText} {d.RightBracket}   {d.VLine}";
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 
 			d.SetBufferSize (width, 3);
 
@@ -74,7 +74,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			// E.g "|[ yes ][ no ]|"
@@ -85,8 +85,8 @@ namespace Terminal.Gui.Views {
 
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}";
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 
 			d.SetBufferSize (buttonRow.Length, 3);
 
@@ -123,7 +123,7 @@ namespace Terminal.Gui.Views {
 			Application.RunState runstate = null;
 			bool firstIteration = false;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			// E.g "|[ yes ][ no ]|"
@@ -134,8 +134,8 @@ namespace Terminal.Gui.Views {
 
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}";
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 
 			d.SetBufferSize (buttonRow.Length, 3);
 
@@ -191,7 +191,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			// E.g "|[ yes ][ no ][ maybe ]|"
@@ -204,8 +204,8 @@ namespace Terminal.Gui.Views {
 
 			var buttonRow = $@"{d.VLine} {btn1} {btn2} {btn3} {d.VLine}";
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 
 			d.SetBufferSize (buttonRow.Length, 3);
 
@@ -241,7 +241,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 
@@ -257,8 +257,8 @@ namespace Terminal.Gui.Views {
 
 			var buttonRow = $"{d.VLine} {btn1} {btn2} {btn3} {btn4} {d.VLine}";
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 			d.SetBufferSize (buttonRow.Length, 3);
 
 			// Default - Center
@@ -294,7 +294,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 
@@ -349,7 +349,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 
@@ -368,8 +368,8 @@ namespace Terminal.Gui.Views {
 			//                         12345                           123456
 			var buttonRow = $"{d.VLine}     {btn1} {btn2} {btn3} {btn4}      {d.VLine}";
 			var width = ustring.Make (buttonRow).ConsoleWidth;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 			d.SetBufferSize (width, 3);
 
 			// Default - Center
@@ -405,7 +405,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 
@@ -423,8 +423,8 @@ namespace Terminal.Gui.Views {
 			//                         12345                          123456
 			var buttonRow = $"{d.VLine}     {btn1} {btn2} {btn3} {btn4}      {d.VLine}";
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 			d.SetBufferSize (buttonRow.Length, 3);
 
 			// Default - Center
@@ -460,14 +460,14 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 
 			var buttonRow = $"{d.VLine}        {d.VLine}";
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 			d.SetBufferSize (buttonRow.Length, 3);
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null);
@@ -482,15 +482,15 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			var btnText = "ok";
 			var buttonRow = $"{d.VLine}   {d.LeftBracket} {btnText} {d.RightBracket}   {d.VLine}";
 
 			var width = buttonRow.Length;
-			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
-			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			var topRow = $"┌ {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘";
 			d.SetBufferSize (buttonRow.Length, 3);
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText));
@@ -504,7 +504,7 @@ namespace Terminal.Gui.Views {
 		{
 			Application.RunState runstate = null;
 
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			var btn1Text = "yes";
@@ -516,8 +516,8 @@ namespace Terminal.Gui.Views {
 			var width = $@"{d.VLine} {btn1} {btn2} {d.VLine}".Length;
 			d.SetBufferSize (width, 3);
 
-			var topRow = $"{d.ULCorner} {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}{d.URCorner}";
-			var bottomRow = $"{d.LLCorner}{new String (d.HLine.ToString () [0], width - 2)}{d.LRCorner}";
+			var topRow = $"{d.ULCorner} {title} {new string (d.HLine.ToString () [0], width - title.Length - 4)}{d.URCorner}";
+			var bottomRow = $"{d.LLCorner}{new string (d.HLine.ToString () [0], width - 2)}{d.LRCorner}";
 
 			// Default (center)
 			var dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Center };
@@ -550,7 +550,7 @@ namespace Terminal.Gui.Views {
 			// Right
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Right };
 			runstate = Application.Begin (dlg);
-			buttonRow = $"{d.VLine}{new String (' ', width - btn1.Length - 2)}{btn1}{d.VLine}";
+			buttonRow = $"{d.VLine}{new string (' ', width - btn1.Length - 2)}{btn1}{d.VLine}";
 			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 
 			// Now add a second button
@@ -564,7 +564,7 @@ namespace Terminal.Gui.Views {
 			// Left
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Left };
 			runstate = Application.Begin (dlg);
-			buttonRow = $"{d.VLine}{btn1}{new String (' ', width - btn1.Length - 2)}{d.VLine}";
+			buttonRow = $"{d.VLine}{btn1}{new string (' ', width - btn1.Length - 2)}{d.VLine}";
 			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 
 			// Now add a second button
@@ -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);
+			}
+		}
 	}
 }

+ 22 - 40
UnitTests/MdiTests.cs → UnitTests/TopLevels/MdiTests.cs

@@ -3,12 +3,13 @@ using System.Diagnostics;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Terminal.Gui;
 using Xunit;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TopLevelTests {
 	public class MdiTests {
 		public MdiTests ()
 		{
@@ -22,7 +23,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 +31,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]
@@ -91,17 +95,11 @@ namespace Terminal.Gui.Core {
 
 			Application.Iteration += () => {
 				Assert.Null (Application.MdiChildes);
-				if (iterations == 4) {
-					Assert.True (Application.Current == d);
-				} else if (iterations == 3) {
-					Assert.True (Application.Current == top4);
-				} else if (iterations == 2) {
-					Assert.True (Application.Current == top3);
-				} else if (iterations == 1) {
-					Assert.True (Application.Current == top2);
-				} else {
-					Assert.True (Application.Current == top1);
-				}
+				if (iterations == 4) 					Assert.True (Application.Current == d);
+else if (iterations == 3) 					Assert.True (Application.Current == top4);
+else if (iterations == 2) 					Assert.True (Application.Current == top3);
+else if (iterations == 1) 					Assert.True (Application.Current == top2);
+else 					Assert.True (Application.Current == top1);
 				Application.RequestStop (top1);
 				iterations--;
 			};
@@ -168,9 +166,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (d.Running);
 				} else {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
-					for (int i = 0; i < iterations; i++) {
-						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
-					}
+					for (int i = 0; i < iterations; i++) 						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
 				}
 				iterations--;
 			};
@@ -228,9 +224,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (d.Running);
 				} else {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
-					for (int i = 0; i < iterations; i++) {
-						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
-					}
+					for (int i = 0; i < iterations; i++) 						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
 				}
 				iterations--;
 			};
@@ -289,9 +283,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (d.Running);
 				} else {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
-					for (int i = 0; i < iterations; i++) {
-						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
-					}
+					for (int i = 0; i < iterations; i++) 						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
 				}
 				iterations--;
 			};
@@ -389,9 +381,7 @@ namespace Terminal.Gui.Core {
 					Assert.False (Application.Current.Running);
 				} else {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
-					for (int i = 0; i < iterations; i++) {
-						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
-					}
+					for (int i = 0; i < iterations; i++) 						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
 				}
 				iterations--;
 			};
@@ -458,10 +448,8 @@ namespace Terminal.Gui.Core {
 					Assert.True (c4.Running);
 				} else {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
-					for (int i = 0; i < iterations; i++) {
-						Assert.Equal ((iterations - i + (iterations == 4 && i == 0 ? 2 : 1)).ToString (),
+					for (int i = 0; i < iterations; i++) 						Assert.Equal ((iterations - i + (iterations == 4 && i == 0 ? 2 : 1)).ToString (),
 							Application.MdiChildes [i].Id);
-					}
 				}
 				iterations--;
 			};
@@ -583,9 +571,7 @@ namespace Terminal.Gui.Core {
 					};
 
 					stage.Closed += (_) => {
-						if (iterations == 11) {
-							allStageClosed = true;
-						}
+						if (iterations == 11) 							allStageClosed = true;
 						Assert.Equal (iterations, Application.MdiChildes.Count);
 						if (running) {
 							stageCompleted = true;
@@ -607,16 +593,12 @@ namespace Terminal.Gui.Core {
 					running = false;
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 
-				} else if (!mdiRequestStop && running && !allStageClosed) {
-					Assert.Equal (iterations, Application.MdiChildes.Count);
-
-				} else if (!mdiRequestStop && !running && allStageClosed) {
+				} else if (!mdiRequestStop && running && !allStageClosed) 					Assert.Equal (iterations, Application.MdiChildes.Count);
+else if (!mdiRequestStop && !running && allStageClosed) {
 					Assert.Equal (iterations, Application.MdiChildes.Count);
 					mdiRequestStop = true;
 					mdi.RequestStop ();
-				} else {
-					Assert.Empty (Application.MdiChildes);
-				}
+				} else 					Assert.Empty (Application.MdiChildes);
 			};
 
 			Application.Run (mdi);

+ 3 - 2
UnitTests/MessageBoxTests.cs → UnitTests/TopLevels/MessageBoxTests.cs

@@ -2,8 +2,9 @@
 using Xunit;
 using Xunit.Abstractions;
 using System.Text;
+using Terminal.Gui;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.TopLevelTests {
 
 	public class MessageBoxTests {
 		readonly ITestOutputHelper output;
@@ -54,7 +55,7 @@ namespace Terminal.Gui.Views {
 				iterations++;
 
 				if (iterations == 0) {
-					StringBuilder aboutMessage = new StringBuilder ();
+					var aboutMessage = new StringBuilder ();
 					aboutMessage.AppendLine (@"A comprehensive sample library for");
 					aboutMessage.AppendLine (@"");
 					aboutMessage.AppendLine (@"  _______                  _             _   _____       _  ");

+ 22 - 15
UnitTests/ToplevelTests.cs → UnitTests/TopLevels/ToplevelTests.cs

@@ -1,8 +1,9 @@
 using System;
+using Terminal.Gui;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TopLevelTests {
 	public class ToplevelTests {
 		readonly ITestOutputHelper output;
 
@@ -596,7 +597,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 +664,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);
@@ -695,8 +696,7 @@ namespace Terminal.Gui.Core {
 					((FakeDriver)Application.Driver).SetBufferSize (40, 15);
 					MessageBox.Query ("About", "Hello Word", "Ok");
 
-				} else if (iterations == 1) {
-					TestHelpers.AssertDriverContentsWithFrameAre (@"
+				} else if (iterations == 1) 					TestHelpers.AssertDriverContentsWithFrameAre (@"
  File                                   
 ┌ Window ──────────────────────────────┐
 │                                      │
@@ -712,8 +712,7 @@ namespace Terminal.Gui.Core {
 │                                      │
 └──────────────────────────────────────┘
  CTRL-N New                             ", output);
-
-				} else if (iterations == 2) {
+else if (iterations == 2) {
 					Assert.Null (Application.MouseGrabView);
 					// Grab the mouse
 					ReflectionTools.InvokePrivate (
@@ -816,11 +815,8 @@ namespace Terminal.Gui.Core {
 
 					Assert.Null (Application.MouseGrabView);
 
-				} else if (iterations == 8) {
-					Application.RequestStop ();
-				} else if (iterations == 9) {
-					Application.RequestStop ();
-				}
+				} else if (iterations == 8) 					Application.RequestStop ();
+else if (iterations == 9) 					Application.RequestStop ();
 			};
 
 			Application.Run ();
@@ -960,12 +956,23 @@ namespace Terminal.Gui.Core {
 
 					Assert.Null (Application.MouseGrabView);
 
-				} else if (iterations == 8) {
-					Application.RequestStop ();
-				}
+				} else if (iterations == 8) 					Application.RequestStop ();
 			};
 
 			Application.Run ();
 		}
+
+		[Fact, AutoInitShutdown]
+		public void EnsureVisibleBounds_With_Border_Null_Not_Throws ()
+		{
+			var top = new Toplevel ();
+			Application.Begin (top);
+
+			var exception = Record.Exception (() => ((FakeDriver)Application.Driver).SetBufferSize (0, 10));
+			Assert.Null (exception);
+
+			exception = Record.Exception (() => ((FakeDriver)Application.Driver).SetBufferSize (10, 0));
+			Assert.Null (exception);
+		}
 	}
 }

+ 8 - 7
UnitTests/WindowTests.cs → UnitTests/TopLevels/WindowTests.cs

@@ -1,13 +1,14 @@
 using System;
 using Xunit;
 using Xunit.Abstractions;
-using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
+//using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using NStack;
+using Terminal.Gui;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TopLevelTests {
 	public class WindowTests {
 		readonly ITestOutputHelper output;
 
@@ -22,7 +23,7 @@ namespace Terminal.Gui.Core {
 			// Parameterless
 			var r = new Window ();
 			Assert.NotNull (r);
-			Assert.Equal(ustring.Empty, r.Title);
+			Assert.Equal (ustring.Empty, r.Title);
 			Assert.Equal (LayoutStyle.Computed, r.LayoutStyle);
 			Assert.Equal ("Window()({X=0,Y=0,Width=0,Height=0})", r.ToString ());
 			Assert.True (r.CanFocus);
@@ -116,13 +117,13 @@ namespace Terminal.Gui.Core {
 			r.Title = expectedDuring = expectedAfter = "title";
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 
-			expectedOld = r.Title.ToString();
+			expectedOld = r.Title.ToString ();
 			r.Title = expectedDuring = expectedAfter = "a different title";
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 
 			// Now setup cancelling the change and change it back to "title"
 			cancel = true;
-			expectedOld = r.Title.ToString();
+			expectedOld = r.Title.ToString ();
 			r.Title = expectedDuring = "title";
 			Assert.Equal (expectedAfter, r.Title.ToString ());
 			r.Dispose ();
@@ -154,7 +155,7 @@ namespace Terminal.Gui.Core {
 			r.Dispose ();
 		}
 
-		[Fact,AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void MenuBar_And_StatusBar_Inside_Window ()
 		{
 			var menu = new MenuBar (new MenuBarItem [] {
@@ -175,7 +176,7 @@ namespace Terminal.Gui.Core {
 
 			var fv = new FrameView ("Frame View") {
 				Y = 1,
-				Width = Dim.Fill(),
+				Width = Dim.Fill (),
 				Height = Dim.Fill (1)
 			};
 			var win = new Window ();

+ 21 - 21
UnitTests/WizardTests.cs → UnitTests/TopLevels/WizardTests.cs

@@ -9,7 +9,7 @@ using System.Globalization;
 using Xunit.Abstractions;
 using NStack;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.TopLevelTests {
 
 	public class WizardTests {
 		readonly ITestOutputHelper output;
@@ -92,7 +92,7 @@ namespace Terminal.Gui.Views {
 		[Fact, AutoInitShutdown]
 		public void DefaultConstructor_SizedProperly ()
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var wizard = new Wizard ();
 			Assert.NotEqual (0, wizard.Width);
@@ -104,7 +104,7 @@ namespace Terminal.Gui.Views {
 		// and that the title is correct
 		public void ZeroStepWizard_Shows ()
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			var stepTitle = "";
@@ -118,12 +118,12 @@ namespace Terminal.Gui.Views {
 			var btnNextText = "Finish";
 			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
 
-			var topRow = $"{d.ULDCorner} {title}{stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}";
-			var row2 = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}";
+			var topRow = $"{d.ULDCorner} {title}{stepTitle} {new string (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}";
+			var row2 = $"{d.VDLine}{new string (' ', width - 2)}{d.VDLine}";
 			var row3 = row2;
-			var separatorRow = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}";
-			var buttonRow = $"{d.VDLine}{btnBack}{new String (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
-			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
+			var separatorRow = $"{d.VDLine}{new string (' ', width - 2)}{d.VDLine}";
+			var buttonRow = $"{d.VDLine}{btnBack}{new string (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			var bottomRow = $"{d.LLDCorner}{new string (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
 
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			Application.End (Application.Begin (wizard));
@@ -135,7 +135,7 @@ namespace Terminal.Gui.Views {
 		// and that the title is correct
 		public void OneStepWizard_Shows ()
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			var stepTitle = "ABCD";
@@ -149,13 +149,13 @@ namespace Terminal.Gui.Views {
 			var btnNextText = "Finish"; // "Next";
 			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
 
-			var topRow = $"{d.ULDCorner} {title} - {stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 7)}{d.URDCorner}";
-			var row2 = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}";
+			var topRow = $"{d.ULDCorner} {title} - {stepTitle} {new string (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 7)}{d.URDCorner}";
+			var row2 = $"{d.VDLine}{new string (' ', width - 2)}{d.VDLine}";
 			var row3 = row2;
 			var row4 = row3;
-			var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
-			var buttonRow = $"{d.VDLine}{btnBack}{new String (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
-			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
+			var separatorRow = $"{d.VDLine}{new string (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
+			var buttonRow = $"{d.VDLine}{btnBack}{new string (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			var bottomRow = $"{d.LLDCorner}{new string (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
 
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			wizard.AddStep (new Wizard.WizardStep (stepTitle));
@@ -207,7 +207,7 @@ namespace Terminal.Gui.Views {
 		// this test is needed because Wizard overrides Dialog's title behavior ("Title - StepTitle")
 		public void Setting_Title_Works ()
 		{
-			var d = ((FakeDriver)Application.Driver);
+			var d = (FakeDriver)Application.Driver;
 
 			var title = "1234";
 			var stepTitle = " - ABCD";
@@ -219,13 +219,13 @@ namespace Terminal.Gui.Views {
 			var btnNextText = "Finish";
 			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
 
-			var topRow = $"{d.ULDCorner} {title}{stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}";
-			var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
+			var topRow = $"{d.ULDCorner} {title}{stepTitle} {new string (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}";
+			var separatorRow = $"{d.VDLine}{new string (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
 
 			// Once this is fixed, revert to commented out line: https://github.com/gui-cs/Terminal.Gui/issues/1791
-			var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			var buttonRow = $"{d.VDLine}{new string (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
 			//var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
-			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
+			var bottomRow = $"{d.LLDCorner}{new string (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
 
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			wizard.AddStep (new Wizard.WizardStep ("ABCD"));
@@ -560,13 +560,13 @@ namespace Terminal.Gui.Views {
 			runstate = Application.Begin (wizard);
 			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
 
-			Assert.Equal (step1.Title.ToString(), wizard.CurrentStep.Title.ToString());
+			Assert.Equal (step1.Title.ToString (), wizard.CurrentStep.Title.ToString ());
 			wizard.NextFinishButton.OnClicked ();
 			Assert.False (finishedFired);
 			Assert.False (closedFired);
 
 			Assert.Equal (step2.Title.ToString (), wizard.CurrentStep.Title.ToString ());
-			Assert.Equal (wizard.GetLastStep().Title.ToString(), wizard.CurrentStep.Title.ToString ());
+			Assert.Equal (wizard.GetLastStep ().Title.ToString (), wizard.CurrentStep.Title.ToString ());
 			wizard.NextFinishButton.OnClicked ();
 			Application.End (runstate);
 			Assert.True (finishedFired);

+ 14 - 19
UnitTests/DimTests.cs → UnitTests/Types/DimTests.cs

@@ -6,14 +6,13 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using Terminal.Gui;
-using Terminal.Gui.Views;
 using Xunit;
 using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TypeTests {
 	public class DimTests {
 		readonly ITestOutputHelper output;
 
@@ -22,7 +21,7 @@ namespace Terminal.Gui.Core {
 			this.output = output;
 			Console.OutputEncoding = System.Text.Encoding.Default;
 			// Change current culture
-			CultureInfo culture = CultureInfo.CreateSpecificCulture ("en-US");
+			var culture = CultureInfo.CreateSpecificCulture ("en-US");
 			Thread.CurrentThread.CurrentCulture = culture;
 			Thread.CurrentThread.CurrentUICulture = culture;
 		}
@@ -252,7 +251,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 +291,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 +312,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 +345,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 +540,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 +587,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 +621,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;
 
@@ -646,9 +645,7 @@ namespace Terminal.Gui.Core {
 			};
 
 			Application.Iteration += () => {
-				while (count < 20) {
-					field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
-				}
+				while (count < 20) 					field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
 
 				Application.RequestStop ();
 			};
@@ -989,7 +986,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 +1053,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;
 
@@ -1091,9 +1088,7 @@ namespace Terminal.Gui.Core {
 			};
 
 			Application.Iteration += () => {
-				while (count > 0) {
-					field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
-				}
+				while (count > 0) 					field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
 
 				Application.RequestStop ();
 			};
@@ -1116,7 +1111,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;
 

+ 1 - 1
UnitTests/PointTests.cs → UnitTests/Types/PointTests.cs

@@ -1,7 +1,7 @@
 using System;
 using Xunit;
 
-namespace Terminal.Gui.Types {
+namespace Terminal.Gui.TypeTests {
 	public class PointTests {
 		[Fact]
 		public void Point_New ()

+ 9 - 10
UnitTests/PosTests.cs → UnitTests/Types/PosTests.cs

@@ -5,14 +5,13 @@ using System.Data;
 using System.IO;
 using System.Linq;
 using Terminal.Gui;
-using Terminal.Gui.Views;
 using Xunit;
 using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.TypeTests {
 	public class PosTests {
 		readonly ITestOutputHelper output;
 
@@ -555,7 +554,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 +699,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 +732,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 +754,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 +787,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 +834,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 +867,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 +916,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;
 

+ 1 - 1
UnitTests/RectTests.cs → UnitTests/Types/RectTests.cs

@@ -1,7 +1,7 @@
 using System;
 using Xunit;
 
-namespace Terminal.Gui.Types {
+namespace Terminal.Gui.TypeTests {
 	public class RectTests {
 		[Fact]
 		public void Rect_New ()

+ 1 - 1
UnitTests/SizeTests.cs → UnitTests/Types/SizeTests.cs

@@ -1,7 +1,7 @@
 using System;
 using Xunit;
 
-namespace Terminal.Gui.Types {
+namespace Terminal.Gui.TypeTests {
 	public class SizeTests {
 		[Fact]
 		public void Size_New ()

+ 566 - 0
UnitTests/UICatalog/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.Tests {
+	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 ());
+
+				// 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 ());
+
+			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 ());
+
+			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);
+			}
+		}
+	}
+}

+ 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 -->

+ 3 - 3
UnitTests/AllViewsTests.cs → UnitTests/Views/AllViewsTests.cs

@@ -5,12 +5,12 @@ using System.Reflection;
 using Xunit;
 using System.IO;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class AllViewsTests {
 		[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> ()));

+ 1 - 1
UnitTests/AutocompleteTests.cs → UnitTests/Views/AutocompleteTests.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 using Terminal.Gui;
 using Xunit;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ViewTests {
 	public class AutocompleteTests {
 
 		[Fact]

+ 1 - 1
UnitTests/ButtonTests.cs → UnitTests/Views/ButtonTests.cs

@@ -2,7 +2,7 @@
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ButtonTests {
 		readonly ITestOutputHelper output;
 

+ 1 - 1
UnitTests/CheckboxTests.cs → UnitTests/Views/CheckboxTests.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class CheckboxTests {
 		readonly ITestOutputHelper output;
 

+ 1 - 1
UnitTests/ColorPickerTests.cs → UnitTests/Views/ColorPickerTests.cs

@@ -5,7 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ColorPickerTests {
 		[Fact]
 		public void Constructors ()

+ 1 - 1
UnitTests/ComboBoxTests.cs → UnitTests/Views/ComboBoxTests.cs

@@ -4,7 +4,7 @@ using System.Linq;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ComboBoxTests {
 		ITestOutputHelper output;
 

+ 1 - 1
UnitTests/DateFieldTests.cs → UnitTests/Views/DateFieldTests.cs

@@ -5,7 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class DateFieldTests {
 		[Fact]
 		public void Constructors_Defaults ()

+ 1 - 1
UnitTests/FrameViewTests.cs → UnitTests/Views/FrameViewTests.cs

@@ -5,7 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class FrameViewTests {
 		[Fact]
 		public void Constuctors_Defaults ()

+ 3 - 3
UnitTests/GraphViewTests.cs → UnitTests/Views/GraphViewTests.cs

@@ -11,7 +11,7 @@ using System.Text.RegularExpressions;
 using Xunit.Abstractions;
 using Rune = System.Rune;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 
 	#region Helper Classes
 	class FakeHAxis : HorizontalAxis {
@@ -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

+ 1 - 1
UnitTests/HexViewTests.cs → UnitTests/Views/HexViewTests.cs

@@ -6,7 +6,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class HexViewTests {
 		[Fact]
 		public void Constructors_Defaults ()

+ 1 - 1
UnitTests/LineViewTests.cs → UnitTests/Views/LineViewTests.cs

@@ -1,7 +1,7 @@
 using Terminal.Gui.Graphs;
 using Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class LineViewTests {
 
 		[Fact]

+ 64 - 2
UnitTests/ListViewTests.cs → UnitTests/Views/ListViewTests.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ListViewTests {
 		readonly ITestOutputHelper output;
 
@@ -203,7 +203,7 @@ namespace Terminal.Gui.Views {
 
 		[Fact]
 		[AutoInitShutdown]
-		public void EnsuresVisibilitySelectedItem_Top ()
+		public void EnsureSelectedItemVisible_Top ()
 		{
 			var source = new List<string> () { "First", "Second" };
 			ListView lv = new ListView (source) { Width = Dim.Fill (), Height = 1 };
@@ -451,5 +451,67 @@ namespace Terminal.Gui.Views {
 			lv.SetSourceAsync (null);
 			Assert.NotNull (lv.Source);
 		}
+
+		[Fact]
+		public void ListWrapper_StartsWith ()
+		{
+			var lw = new ListWrapper (new List<string> { "One", "Two", "Three" });
+
+			Assert.Equal (1, lw.StartsWith ("t"));
+			Assert.Equal (1, lw.StartsWith ("tw"));
+			Assert.Equal (2, lw.StartsWith ("th"));
+			Assert.Equal (1, lw.StartsWith ("T"));
+			Assert.Equal (1, lw.StartsWith ("TW"));
+			Assert.Equal (2, lw.StartsWith ("TH"));
+
+			lw = new ListWrapper (new List<NStack.ustring> { "One", "Two", "Three" });
+
+			Assert.Equal (1, lw.StartsWith ("t"));
+			Assert.Equal (1, lw.StartsWith ("tw"));
+			Assert.Equal (2, lw.StartsWith ("th"));
+			Assert.Equal (1, lw.StartsWith ("T"));
+			Assert.Equal (1, lw.StartsWith ("TW"));
+			Assert.Equal (2, lw.StartsWith ("TH"));
+		}
+
+		[Fact, AutoInitShutdown]
+		public void EnsureSelectedItemVisible_SelectedItem ()
+		{
+			var source = new List<string> ();
+			for (int i = 0; i < 10; i++) {
+				source.Add ($"Item {i}");
+			}
+			var lv = new ListView (source) {
+				Width = 10,
+				Height = 5
+			};
+			Application.Top.Add (lv);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+Item 0
+Item 1
+Item 2
+Item 3
+Item 4", output);
+
+			lv.SelectedItem = 6;
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+Item 0
+Item 1
+Item 2
+Item 3
+Item 4", output);
+
+			lv.EnsureSelectedItemVisible ();
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+Item 2
+Item 3
+Item 4
+Item 5
+Item 6", output);
+		}
 	}
 }

+ 1 - 1
UnitTests/PanelViewTests.cs → UnitTests/Views/PanelViewTests.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class PanelViewTests {
 		readonly ITestOutputHelper output;
 

+ 1 - 1
UnitTests/ProgressBarTests.cs → UnitTests/Views/ProgressBarTests.cs

@@ -5,7 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ProgressBarTests {
 		[Fact]
 		[AutoInitShutdown]

+ 1 - 1
UnitTests/RadioGroupTests.cs → UnitTests/Views/RadioGroupTests.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class RadioGroupTests {
 		readonly ITestOutputHelper output;
 

+ 22 - 31
UnitTests/ScrollBarViewTests.cs → UnitTests/Views/ScrollBarViewTests.cs

@@ -5,7 +5,7 @@ using System.Reflection;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ScrollBarViewTests {
 		readonly ITestOutputHelper output;
 
@@ -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/ScrollViewTests.cs → UnitTests/Views/ScrollViewTests.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class ScrollViewTests {
 		readonly ITestOutputHelper output;
 

+ 2 - 2
UnitTests/StatusBarTests.cs → UnitTests/Views/StatusBarTests.cs

@@ -2,7 +2,7 @@
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class StatusBarTests {
 		readonly ITestOutputHelper output;
 
@@ -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 ();
 

+ 2 - 2
UnitTests/TabViewTests.cs → UnitTests/Views/TabViewTests.cs

@@ -8,7 +8,7 @@ using Xunit;
 using System.Globalization;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 
 	public class TabViewTests {
 		readonly ITestOutputHelper output;
@@ -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 (() => { });
 		}
 	}

+ 118 - 1
UnitTests/TableViewTests.cs → UnitTests/Views/TableViewTests.cs

@@ -9,7 +9,7 @@ using System.Globalization;
 using Xunit.Abstractions;
 using System.Reflection;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 
 	public class TableViewTests {
 		readonly ITestOutputHelper output;
@@ -646,6 +646,75 @@ namespace Terminal.Gui.Views {
 			Application.Shutdown ();
 		}
 
+		[Fact, AutoInitShutdown]
+		public void TestShiftClick_MultiSelect_TwoRowTable_FullRowSelect()
+		{
+			var tv = GetTwoRowSixColumnTable ();
+
+			tv.MultiSelect = true;
+			
+			// Clicking in bottom row
+			tv.MouseEvent (new MouseEvent {
+				X = 1,
+				Y = 3,
+				Flags = MouseFlags.Button1Clicked
+			});
+
+			// should select that row
+			Assert.Equal (1, tv.SelectedRow);
+
+			// shift clicking top row
+			tv.MouseEvent (new MouseEvent {
+				X = 1,
+				Y = 2,
+				Flags = MouseFlags.Button1Clicked | MouseFlags.ButtonShift
+			});
+
+			// should extend the selection
+			Assert.Equal (0, tv.SelectedRow);
+
+			var selected = tv.GetAllSelectedCells ().ToArray();
+
+			Assert.Contains (new Point(0,0), selected);
+			Assert.Contains (new Point (0, 1), selected);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestControlClick_MultiSelect_ThreeRowTable_FullRowSelect ()
+		{
+			var tv = GetTwoRowSixColumnTable ();
+			tv.Table.Rows.Add (1, 2, 3, 4, 5, 6);
+
+			tv.MultiSelect = true;
+
+			// Clicking in bottom row
+			tv.MouseEvent (new MouseEvent {
+				X = 1,
+				Y = 4,
+				Flags = MouseFlags.Button1Clicked
+			});
+
+			// should select that row
+			Assert.Equal (2, tv.SelectedRow);
+
+			// shift clicking top row
+			tv.MouseEvent (new MouseEvent {
+				X = 1,
+				Y = 2,
+				Flags = MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl
+			});
+
+			// should extend the selection
+			// to include bottom and top row but not middle
+			Assert.Equal (0, tv.SelectedRow);
+
+			var selected = tv.GetAllSelectedCells ().ToArray ();
+
+			Assert.Contains (new Point (0, 0), selected);
+			Assert.DoesNotContain (new Point (0, 1), selected);
+			Assert.Contains (new Point (0, 2), selected);
+		}
+
 		[Theory]
 		[InlineData (false)]
 		[InlineData (true)]
@@ -1312,6 +1381,54 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (1, tableView.SelectedColumn);
 		}
 
+
+		[InlineData(true)]
+		[InlineData (false)]
+		[Theory, AutoInitShutdown]
+		public void TestMoveStartEnd_WithFullRowSelect(bool withFullRowSelect)
+		{
+			var tableView = GetTwoRowSixColumnTable ();
+			tableView.FullRowSelect = withFullRowSelect;
+
+			tableView.SelectedRow = 1;
+			tableView.SelectedColumn = 1;
+
+			tableView.ProcessKey (new KeyEvent 
+			{
+				Key = Key.Home  | Key.CtrlMask
+			});
+
+			if(withFullRowSelect)
+			{
+				// Should not be any horizontal movement when
+				// using navigate to Start/End and FullRowSelect
+				Assert.Equal (1, tableView.SelectedColumn);
+				Assert.Equal (0, tableView.SelectedRow);
+			}
+			else
+			{
+				Assert.Equal (0, tableView.SelectedColumn);
+				Assert.Equal (0, tableView.SelectedRow);
+			}
+
+			tableView.ProcessKey (new KeyEvent 
+			{
+				Key = Key.End  | Key.CtrlMask
+			});
+
+			if(withFullRowSelect)
+			{
+				Assert.Equal (1, tableView.SelectedColumn);
+				Assert.Equal (1, tableView.SelectedRow);
+			}
+			else
+			{
+				Assert.Equal (5, tableView.SelectedColumn);
+				Assert.Equal (1, tableView.SelectedRow);
+			}
+
+		}
+
 		[InlineData (true)]
 		[InlineData (false)]
 		[Theory, AutoInitShutdown]

+ 157 - 130
UnitTests/TextFieldTests.cs → UnitTests/Views/TextFieldTests.cs

@@ -2,7 +2,7 @@
 using System.Reflection;
 using Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class TextFieldTests {
 
 		// This class enables test functions annotated with the [InitShutdown] attribute
@@ -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]
@@ -1296,5 +1296,32 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ($"{text}A", tf.Text);
 			Assert.True (tf.IsDirty);
 		}
+
+		[InlineData ("a")] // Lower than selection
+		[InlineData ("aaaaaaaaaaa")] // Greater than selection
+		[InlineData ("aaaa")] // Equal than selection
+		[Theory]
+		public void TestSetTextAndMoveCursorToEnd_WhenExistingSelection (string newText)
+		{
+			var tf = new TextField ();
+			tf.Text = "fish";
+			tf.CursorPosition = tf.Text.Length;
+
+			tf.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+
+			tf.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
+			tf.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
+
+			Assert.Equal (1, tf.CursorPosition);
+			Assert.Equal (2, tf.SelectedLength);
+			Assert.Equal ("is", tf.SelectedText);
+
+			tf.Text = newText;
+			tf.CursorPosition = tf.Text.Length;
+
+			Assert.Equal (newText.Length, tf.CursorPosition);
+			Assert.Equal (0, tf.SelectedLength);
+			Assert.Null (tf.SelectedText);
+		}
 	}
 }

+ 1 - 1
UnitTests/TextValidateFieldTests.cs → UnitTests/Views/TextValidateFieldTests.cs

@@ -5,7 +5,7 @@ using Terminal.Gui.TextValidateProviders;
 
 using Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 
 	public class TextValidateField_NET_Provider_Tests {
 

+ 83 - 86
UnitTests/TextViewTests.cs → UnitTests/Views/TextViewTests.cs

@@ -6,7 +6,7 @@ using System.Text.RegularExpressions;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class TextViewTests {
 		private static TextView _textView;
 		readonly ITestOutputHelper output;
@@ -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;

+ 1 - 1
UnitTests/TimeFieldTests.cs → UnitTests/Views/TimeFieldTests.cs

@@ -5,7 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Xunit;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 	public class TimeFieldTests {
 		[Fact]
 		public void Constructors_Defaults ()

+ 2 - 2
UnitTests/TreeViewTests.cs → UnitTests/Views/TreeViewTests.cs

@@ -8,7 +8,7 @@ using Terminal.Gui.Trees;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.Views {
+namespace Terminal.Gui.ViewTests {
 
 	public class TreeViewTests {
 
@@ -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 (() => { });
 		}
 	}

+ 71 - 12
UnitTests/ViewTests.cs → UnitTests/Views/ViewTests.cs

@@ -1,12 +1,12 @@
 using System;
 using Xunit;
 using Xunit.Abstractions;
-using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
+//using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.Core {
+namespace Terminal.Gui.ViewTests {
 	public class ViewTests {
 		readonly ITestOutputHelper output;
 
@@ -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 
@@ -4117,5 +4117,64 @@ This is a tes
 			view.Enabled = false;
 			Assert.Equal (view.ColorScheme.Disabled, view.GetHotNormalColor ());
 		}
+
+		[Theory, AutoInitShutdown]
+		[InlineData (true)]
+		[InlineData (false)]
+		public void Clear_Does_Not_Spillover_Its_Parent (bool label)
+		{
+			var root = new View () { Width = 20, Height = 10 };
+
+			var v = label == true ?
+				new Label (new string ('c', 100)) {
+					Width = Dim.Fill ()
+				} :
+				(View)new TextView () {
+					Height = 1,
+					Text = new string ('c', 100),
+					Width = Dim.Fill ()
+				};
+
+			root.Add (v);
+
+			Application.Top.Add (root);
+			Application.Begin (Application.Top);
+
+			if (label) {
+				Assert.True (v.AutoSize);
+				Assert.False (v.CanFocus);
+				Assert.Equal (new Rect (0, 0, 100, 1), v.Frame);
+			} else {
+				Assert.False (v.AutoSize);
+				Assert.True (v.CanFocus);
+				Assert.Equal (new Rect (0, 0, 20, 1), v.Frame);
+			}
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+cccccccccccccccccccc", output);
+
+			var attributes = new Attribute [] {
+				Colors.TopLevel.Normal,
+				Colors.TopLevel.Focus,
+
+			};
+			if (label) {
+				TestHelpers.AssertDriverColorsAre (@"
+000000000000000000000", attributes);
+			} else {
+				TestHelpers.AssertDriverColorsAre (@"
+111111111111111111110", attributes);
+			}
+
+			if (label) {
+				root.CanFocus = true;
+				v.CanFocus = true;
+				Assert.False (v.HasFocus);
+				v.SetFocus ();
+				Application.Refresh ();
+				TestHelpers.AssertDriverColorsAre (@"
+111111111111111111110", attributes);
+			}
+		}
 	}
 }