Browse Source

first unit tests

Charlie Kindel 5 năm trước cách đây
mục cha
commit
8730ac6f37

+ 1661 - 0
Terminal.Gui/ConsoleDrivers/MockDriver/MockConsole.cs

@@ -0,0 +1,1661 @@
+//
+// MockConsole.cs: A mock .NET Windows Console API implementaiton for unit tests.
+//
+// Authors:
+//   Charlie Kindel (github.com/tig)
+//
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// 
+	/// </summary>
+	public static class MockConsole {
+
+		//
+		// Summary:
+		//     Gets or sets the width of the console window.
+		//
+		// Returns:
+		//     The width of the console window measured in columns.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight
+		//     property is less than or equal to 0.-or-The value of the System.Console.WindowHeight
+		//     property plus the value of the System.Console.WindowTop property is greater than
+		//     or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth
+		//     property or the value of the System.Console.WindowHeight property is greater
+		//     than the largest possible window width or height for the current screen resolution
+		//     and console font.
+		//
+		//   T:System.IO.IOException:
+		//     Error reading or writing information.
+		public static int WindowWidth { get; set; } = 80;
+		//
+		// Summary:
+		//     Gets a value that indicates whether output has been redirected from the standard
+		//     output stream.
+		//
+		// Returns:
+		//     true if output is redirected; otherwise, false.
+		public static bool IsOutputRedirected { get; }
+		//
+		// Summary:
+		//     Gets a value that indicates whether the error output stream has been redirected
+		//     from the standard error stream.
+		//
+		// Returns:
+		//     true if error output is redirected; otherwise, false.
+		public static bool IsErrorRedirected { get; }
+		//
+		// Summary:
+		//     Gets the standard input stream.
+		//
+		// Returns:
+		//     A System.IO.TextReader that represents the standard input stream.
+		public static TextReader In { get; }
+		//
+		// Summary:
+		//     Gets the standard output stream.
+		//
+		// Returns:
+		//     A System.IO.TextWriter that represents the standard output stream.
+		public static TextWriter Out { get; }
+		//
+		// Summary:
+		//     Gets the standard error output stream.
+		//
+		// Returns:
+		//     A System.IO.TextWriter that represents the standard error output stream.
+		public static TextWriter Error { get; }
+		//
+		// Summary:
+		//     Gets or sets the encoding the console uses to read input.
+		//
+		// Returns:
+		//     The encoding used to read console input.
+		//
+		// Exceptions:
+		//   T:System.ArgumentNullException:
+		//     The property value in a set operation is null.
+		//
+		//   T:System.IO.IOException:
+		//     An error occurred during the execution of this operation.
+		//
+		//   T:System.Security.SecurityException:
+		//     Your application does not have permission to perform this operation.
+		public static Encoding InputEncoding { get; set; }
+		//
+		// Summary:
+		//     Gets or sets the encoding the console uses to write output.
+		//
+		// Returns:
+		//     The encoding used to write console output.
+		//
+		// Exceptions:
+		//   T:System.ArgumentNullException:
+		//     The property value in a set operation is null.
+		//
+		//   T:System.IO.IOException:
+		//     An error occurred during the execution of this operation.
+		//
+		//   T:System.Security.SecurityException:
+		//     Your application does not have permission to perform this operation.
+		public static Encoding OutputEncoding { get; set; }
+		//
+		// Summary:
+		//     Gets or sets the background color of the console.
+		//
+		// Returns:
+		//     A value that specifies the background color of the console; that is, the color
+		//     that appears behind each character. The default is black.
+		//
+		// Exceptions:
+		//   T:System.ArgumentException:
+		//     The color specified in a set operation is not a valid member of System.ConsoleColor.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		/// <summary>
+		/// 
+		/// </summary>
+		public static ConsoleColor BackgroundColor { get; set; } = _defaultBackgroundColor;
+		static ConsoleColor _defaultBackgroundColor = ConsoleColor.Black;
+
+		//
+		// Summary:
+		//     Gets or sets the foreground color of the console.
+		//
+		// Returns:
+		//     A System.ConsoleColor that specifies the foreground color of the console; that
+		//     is, the color of each character that is displayed. The default is gray.
+		//
+		// Exceptions:
+		//   T:System.ArgumentException:
+		//     The color specified in a set operation is not a valid member of System.ConsoleColor.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		/// <summary>
+		/// 
+		/// </summary>
+		public static ConsoleColor ForegroundColor { get; set; } = _defaultForegroundColor;
+		static ConsoleColor _defaultForegroundColor = ConsoleColor.Gray;
+		//
+		// Summary:
+		//     Gets or sets the height of the buffer area.
+		//
+		// Returns:
+		//     The current height, in rows, of the buffer area.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     The value in a set operation is less than or equal to zero.-or- The value in
+		//     a set operation is greater than or equal to System.Int16.MaxValue.-or- The value
+		//     in a set operation is less than System.Console.WindowTop + System.Console.WindowHeight.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static int BufferHeight { get; set; } = 25;
+		//
+		// Summary:
+		//     Gets or sets the width of the buffer area.
+		//
+		// Returns:
+		//     The current width, in columns, of the buffer area.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     The value in a set operation is less than or equal to zero.-or- The value in
+		//     a set operation is greater than or equal to System.Int16.MaxValue.-or- The value
+		//     in a set operation is less than System.Console.WindowLeft + System.Console.WindowWidth.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static int BufferWidth { get; set; } = 80;
+		//
+		// Summary:
+		//     Gets or sets the height of the console window area.
+		//
+		// Returns:
+		//     The height of the console window measured in rows.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight
+		//     property is less than or equal to 0.-or-The value of the System.Console.WindowHeight
+		//     property plus the value of the System.Console.WindowTop property is greater than
+		//     or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth
+		//     property or the value of the System.Console.WindowHeight property is greater
+		//     than the largest possible window width or height for the current screen resolution
+		//     and console font.
+		//
+		//   T:System.IO.IOException:
+		//     Error reading or writing information.
+		public static int WindowHeight { get; set; } = 25;
+		//
+		// Summary:
+		//     Gets or sets a value indicating whether the combination of the System.ConsoleModifiers.Control
+		//     modifier key and System.ConsoleKey.C console key (Ctrl+C) is treated as ordinary
+		//     input or as an interruption that is handled by the operating system.
+		//
+		// Returns:
+		//     true if Ctrl+C is treated as ordinary input; otherwise, false.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     Unable to get or set the input mode of the console input buffer.
+		public static bool TreatControlCAsInput { get; set; }
+		//
+		// Summary:
+		//     Gets the largest possible number of console window columns, based on the current
+		//     font and screen resolution.
+		//
+		// Returns:
+		//     The width of the largest possible console window measured in columns.
+		public static int LargestWindowWidth { get; }
+		//
+		// Summary:
+		//     Gets the largest possible number of console window rows, based on the current
+		//     font and screen resolution.
+		//
+		// Returns:
+		//     The height of the largest possible console window measured in rows.
+		public static int LargestWindowHeight { get; }
+		//
+		// Summary:
+		//     Gets or sets the leftmost position of the console window area relative to the
+		//     screen buffer.
+		//
+		// Returns:
+		//     The leftmost console window position measured in columns.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     In a set operation, the value to be assigned is less than zero.-or-As a result
+		//     of the assignment, System.Console.WindowLeft plus System.Console.WindowWidth
+		//     would exceed System.Console.BufferWidth.
+		//
+		//   T:System.IO.IOException:
+		//     Error reading or writing information.
+		public static int WindowLeft { get; set; }
+		//
+		// Summary:
+		//     Gets or sets the top position of the console window area relative to the screen
+		//     buffer.
+		//
+		// Returns:
+		//     The uppermost console window position measured in rows.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     In a set operation, the value to be assigned is less than zero.-or-As a result
+		//     of the assignment, System.Console.WindowTop plus System.Console.WindowHeight
+		//     would exceed System.Console.BufferHeight.
+		//
+		//   T:System.IO.IOException:
+		//     Error reading or writing information.
+		public static int WindowTop { get; set; }
+		//
+		// Summary:
+		//     Gets or sets the column position of the cursor within the buffer area.
+		//
+		// Returns:
+		//     The current position, in columns, of the cursor.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     The value in a set operation is less than zero.-or- The value in a set operation
+		//     is greater than or equal to System.Console.BufferWidth.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static int CursorLeft { get; set; }
+		//
+		// Summary:
+		//     Gets or sets the row position of the cursor within the buffer area.
+		//
+		// Returns:
+		//     The current position, in rows, of the cursor.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     The value in a set operation is less than zero.-or- The value in a set operation
+		//     is greater than or equal to System.Console.BufferHeight.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static int CursorTop { get; set; }
+		//
+		// Summary:
+		//     Gets or sets the height of the cursor within a character cell.
+		//
+		// Returns:
+		//     The size of the cursor expressed as a percentage of the height of a character
+		//     cell. The property value ranges from 1 to 100.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     The value specified in a set operation is less than 1 or greater than 100.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static int CursorSize { get; set; }
+		//
+		// Summary:
+		//     Gets or sets a value indicating whether the cursor is visible.
+		//
+		// Returns:
+		//     true if the cursor is visible; otherwise, false.
+		//
+		// Exceptions:
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static bool CursorVisible { get; set; }
+		//
+		// Summary:
+		//     Gets or sets the title to display in the console title bar.
+		//
+		// Returns:
+		//     The string to be displayed in the title bar of the console. The maximum length
+		//     of the title string is 24500 characters.
+		//
+		// Exceptions:
+		//   T:System.InvalidOperationException:
+		//     In a get operation, the retrieved title is longer than 24500 characters.
+		//
+		//   T:System.ArgumentOutOfRangeException:
+		//     In a set operation, the specified title is longer than 24500 characters.
+		//
+		//   T:System.ArgumentNullException:
+		//     In a set operation, the specified title is null.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static string Title { get; set; }
+		//
+		// Summary:
+		//     Gets a value indicating whether a key press is available in the input stream.
+		//
+		// Returns:
+		//     true if a key press is available; otherwise, false.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//
+		//   T:System.InvalidOperationException:
+		//     Standard input is redirected to a file instead of the keyboard.
+		public static bool KeyAvailable { get; }
+		//
+		// Summary:
+		//     Gets a value indicating whether the NUM LOCK keyboard toggle is turned on or
+		//     turned off.
+		//
+		// Returns:
+		//     true if NUM LOCK is turned on; false if NUM LOCK is turned off.
+		public static bool NumberLock { get; }
+		//
+		// Summary:
+		//     Gets a value indicating whether the CAPS LOCK keyboard toggle is turned on or
+		//     turned off.
+		//
+		// Returns:
+		//     true if CAPS LOCK is turned on; false if CAPS LOCK is turned off.
+		public static bool CapsLock { get; }
+		//
+		// Summary:
+		//     Gets a value that indicates whether input has been redirected from the standard
+		//     input stream.
+		//
+		// Returns:
+		//     true if input is redirected; otherwise, false.
+		public static bool IsInputRedirected { get; }
+
+		//
+		// Summary:
+		//     Occurs when the System.ConsoleModifiers.Control modifier key (Ctrl) and either
+		//     the System.ConsoleKey.C console key (C) or the Break key are pressed simultaneously
+		//     (Ctrl+C or Ctrl+Break).
+		//public static event ConsoleCancelEventHandler CancelKeyPress;
+
+		//
+		// Summary:
+		//     Plays the sound of a beep through the console speaker.
+		//
+		// Exceptions:
+		//   T:System.Security.HostProtectionException:
+		//     This method was executed on a server, such as SQL Server, that does not permit
+		//     access to a user interface.
+		public static void Beep ()
+		{
+			throw new NotImplementedException ();
+		}
+		//
+		// Summary:
+		//     Plays the sound of a beep of a specified frequency and duration through the console
+		//     speaker.
+		//
+		// Parameters:
+		//   frequency:
+		//     The frequency of the beep, ranging from 37 to 32767 hertz.
+		//
+		//   duration:
+		//     The duration of the beep measured in milliseconds.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     frequency is less than 37 or more than 32767 hertz.-or- duration is less than
+		//     or equal to zero.
+		//
+		//   T:System.Security.HostProtectionException:
+		//     This method was executed on a server, such as SQL Server, that does not permit
+		//     access to the console.
+		public static void Beep (int frequency, int duration)
+		{
+			throw new NotImplementedException ();
+		}
+		//
+		// Summary:
+		//     Clears the console buffer and corresponding console window of display information.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Clear ()
+		{
+			_buffer = new char [WindowWidth, WindowHeight];
+			SetCursorPosition (0, 0);
+		}
+
+		static char [,] _buffer = new char [WindowWidth, WindowHeight];
+
+		//
+		// Summary:
+		//     Copies a specified source area of the screen buffer to a specified destination
+		//     area.
+		//
+		// Parameters:
+		//   sourceLeft:
+		//     The leftmost column of the source area.
+		//
+		//   sourceTop:
+		//     The topmost row of the source area.
+		//
+		//   sourceWidth:
+		//     The number of columns in the source area.
+		//
+		//   sourceHeight:
+		//     The number of rows in the source area.
+		//
+		//   targetLeft:
+		//     The leftmost column of the destination area.
+		//
+		//   targetTop:
+		//     The topmost row of the destination area.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     One or more of the parameters is less than zero.-or- sourceLeft or targetLeft
+		//     is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop
+		//     is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight
+		//     is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth
+		//     is greater than or equal to System.Console.BufferWidth.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Copies a specified source area of the screen buffer to a specified destination
+		//     area.
+		//
+		// Parameters:
+		//   sourceLeft:
+		//     The leftmost column of the source area.
+		//
+		//   sourceTop:
+		//     The topmost row of the source area.
+		//
+		//   sourceWidth:
+		//     The number of columns in the source area.
+		//
+		//   sourceHeight:
+		//     The number of rows in the source area.
+		//
+		//   targetLeft:
+		//     The leftmost column of the destination area.
+		//
+		//   targetTop:
+		//     The topmost row of the destination area.
+		//
+		//   sourceChar:
+		//     The character used to fill the source area.
+		//
+		//   sourceForeColor:
+		//     The foreground color used to fill the source area.
+		//
+		//   sourceBackColor:
+		//     The background color used to fill the source area.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     One or more of the parameters is less than zero.-or- sourceLeft or targetLeft
+		//     is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop
+		//     is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight
+		//     is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth
+		//     is greater than or equal to System.Console.BufferWidth.
+		//
+		//   T:System.ArgumentException:
+		//     One or both of the color parameters is not a member of the System.ConsoleColor
+		//     enumeration.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//[SecuritySafeCritical]
+		public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop, char sourceChar, ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Acquires the standard error stream.
+		//
+		// Returns:
+		//     The standard error stream.
+		public static Stream OpenStandardError ()
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Acquires the standard error stream, which is set to a specified buffer size.
+		//
+		// Parameters:
+		//   bufferSize:
+		//     The internal stream buffer size.
+		//
+		// Returns:
+		//     The standard error stream.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     bufferSize is less than or equal to zero.
+		public static Stream OpenStandardError (int bufferSize)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Acquires the standard input stream, which is set to a specified buffer size.
+		//
+		// Parameters:
+		//   bufferSize:
+		//     The internal stream buffer size.
+		//
+		// Returns:
+		//     The standard input stream.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     bufferSize is less than or equal to zero.
+		public static Stream OpenStandardInput (int bufferSize)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Acquires the standard input stream.
+		//
+		// Returns:
+		//     The standard input stream.
+		public static Stream OpenStandardInput ()
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Acquires the standard output stream, which is set to a specified buffer size.
+		//
+		// Parameters:
+		//   bufferSize:
+		//     The internal stream buffer size.
+		//
+		// Returns:
+		//     The standard output stream.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     bufferSize is less than or equal to zero.
+		public static Stream OpenStandardOutput (int bufferSize)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Acquires the standard output stream.
+		//
+		// Returns:
+		//     The standard output stream.
+		public static Stream OpenStandardOutput ()
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Reads the next character from the standard input stream.
+		//
+		// Returns:
+		//     The next character from the input stream, or negative one (-1) if there are currently
+		//     no more characters to be read.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static int Read ()
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Obtains the next character or function key pressed by the user. The pressed key
+		//     is optionally displayed in the console window.
+		//
+		// Parameters:
+		//   intercept:
+		//     Determines whether to display the pressed key in the console window. true to
+		//     not display the pressed key; otherwise, false.
+		//
+		// Returns:
+		//     An object that describes the System.ConsoleKey constant and Unicode character,
+		//     if any, that correspond to the pressed console key. The System.ConsoleKeyInfo
+		//     object also describes, in a bitwise combination of System.ConsoleModifiers values,
+		//     whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously
+		//     with the console key.
+		//
+		// Exceptions:
+		//   T:System.InvalidOperationException:
+		//     The System.Console.In property is redirected from some stream other than the
+		//     console.
+		//[SecuritySafeCritical]
+		public static ConsoleKeyInfo ReadKey (bool intercept)
+		{
+			if (MockKeyPresses.Count > 0) {
+				return MockKeyPresses.Pop();
+			} else {
+				return new ConsoleKeyInfo ('~', ConsoleKey.Oem3, false,false,false);
+			}
+		}
+
+		public static Stack<ConsoleKeyInfo> MockKeyPresses = new Stack<ConsoleKeyInfo> ();
+
+		//
+		// Summary:
+		//     Obtains the next character or function key pressed by the user. The pressed key
+		//     is displayed in the console window.
+		//
+		// Returns:
+		//     An object that describes the System.ConsoleKey constant and Unicode character,
+		//     if any, that correspond to the pressed console key. The System.ConsoleKeyInfo
+		//     object also describes, in a bitwise combination of System.ConsoleModifiers values,
+		//     whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously
+		//     with the console key.
+		//
+		// Exceptions:
+		//   T:System.InvalidOperationException:
+		//     The System.Console.In property is redirected from some stream other than the
+		//     console.
+		public static ConsoleKeyInfo ReadKey ()
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Reads the next line of characters from the standard input stream.
+		//
+		// Returns:
+		//     The next line of characters from the input stream, or null if no more lines are
+		//     available.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//
+		//   T:System.OutOfMemoryException:
+		//     There is insufficient memory to allocate a buffer for the returned string.
+		//
+		//   T:System.ArgumentOutOfRangeException:
+		//     The number of characters in the next line of characters is greater than System.Int32.MaxValue.
+		public static string ReadLine ()
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Sets the foreground and background console colors to their defaults.
+		//
+		// Exceptions:
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//[SecuritySafeCritical]
+		public static void ResetColor ()
+		{
+			BackgroundColor = _defaultBackgroundColor;
+			ForegroundColor = _defaultForegroundColor;
+		}
+
+		//
+		// Summary:
+		//     Sets the height and width of the screen buffer area to the specified values.
+		//
+		// Parameters:
+		//   width:
+		//     The width of the buffer area measured in columns.
+		//
+		//   height:
+		//     The height of the buffer area measured in rows.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     height or width is less than or equal to zero.-or- height or width is greater
+		//     than or equal to System.Int16.MaxValue.-or- width is less than System.Console.WindowLeft
+		//     + System.Console.WindowWidth.-or- height is less than System.Console.WindowTop
+		//     + System.Console.WindowHeight.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//[SecuritySafeCritical]
+		public static void SetBufferSize (int width, int height)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Sets the position of the cursor.
+		//
+		// Parameters:
+		//   left:
+		//     The column position of the cursor. Columns are numbered from left to right starting
+		//     at 0.
+		//
+		//   top:
+		//     The row position of the cursor. Rows are numbered from top to bottom starting
+		//     at 0.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     left or top is less than zero.-or- left is greater than or equal to System.Console.BufferWidth.-or-
+		//     top is greater than or equal to System.Console.BufferHeight.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//[SecuritySafeCritical]
+		public static void SetCursorPosition (int left, int top)
+		{
+			CursorLeft = left;
+			CursorTop = top;
+		}
+
+		//
+		// Summary:
+		//     Sets the System.Console.Error property to the specified System.IO.TextWriter
+		//     object.
+		//
+		// Parameters:
+		//   newError:
+		//     A stream that is the new standard error output.
+		//
+		// Exceptions:
+		//   T:System.ArgumentNullException:
+		//     newError is null.
+		//
+		//   T:System.Security.SecurityException:
+		//     The caller does not have the required permission.
+		//[SecuritySafeCritical]
+		public static void SetError (TextWriter newError)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Sets the System.Console.In property to the specified System.IO.TextReader object.
+		//
+		// Parameters:
+		//   newIn:
+		//     A stream that is the new standard input.
+		//
+		// Exceptions:
+		//   T:System.ArgumentNullException:
+		//     newIn is null.
+		//
+		//   T:System.Security.SecurityException:
+		//     The caller does not have the required permission.
+		//[SecuritySafeCritical]
+		public static void SetIn (TextReader newIn)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Sets the System.Console.Out property to the specified System.IO.TextWriter object.
+		//
+		// Parameters:
+		//   newOut:
+		//     A stream that is the new standard output.
+		//
+		// Exceptions:
+		//   T:System.ArgumentNullException:
+		//     newOut is null.
+		//
+		//   T:System.Security.SecurityException:
+		//     The caller does not have the required permission.
+		//[SecuritySafeCritical]
+		public static void SetOut (TextWriter newOut)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Sets the position of the console window relative to the screen buffer.
+		//
+		// Parameters:
+		//   left:
+		//     The column position of the upper left corner of the console window.
+		//
+		//   top:
+		//     The row position of the upper left corner of the console window.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     left or top is less than zero.-or- left + System.Console.WindowWidth is greater
+		//     than System.Console.BufferWidth.-or- top + System.Console.WindowHeight is greater
+		//     than System.Console.BufferHeight.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//[SecuritySafeCritical]
+		public static void SetWindowPosition (int left, int top)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Sets the height and width of the console window to the specified values.
+		//
+		// Parameters:
+		//   width:
+		//     The width of the console window measured in columns.
+		//
+		//   height:
+		//     The height of the console window measured in rows.
+		//
+		// Exceptions:
+		//   T:System.ArgumentOutOfRangeException:
+		//     width or height is less than or equal to zero.-or- width plus System.Console.WindowLeft
+		//     or height plus System.Console.WindowTop is greater than or equal to System.Int16.MaxValue.
+		//     -or- width or height is greater than the largest possible window width or height
+		//     for the current screen resolution and console font.
+		//
+		//   T:System.Security.SecurityException:
+		//     The user does not have permission to perform this action.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//[SecuritySafeCritical]
+		public static void SetWindowSize (int width, int height)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the specified string value to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (string value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified object to the standard output
+		//     stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write, or null.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (object value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified 64-bit unsigned integer value
+		//     to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//[CLSCompliant (false)]
+		public static void Write (ulong value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified 64-bit signed integer value to
+		//     the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (long value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified objects to the standard output
+		//     stream using the specified format information.
+		//
+		// Parameters:
+		//   format:
+		//     A composite format string (see Remarks).
+		//
+		//   arg0:
+		//     The first object to write using format.
+		//
+		//   arg1:
+		//     The second object to write using format.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//
+		//   T:System.ArgumentNullException:
+		//     format is null.
+		//
+		//   T:System.FormatException:
+		//     The format specification in format is invalid.
+		public static void Write (string format, object arg0, object arg1)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified 32-bit signed integer value to
+		//     the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (int value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified object to the standard output
+		//     stream using the specified format information.
+		//
+		// Parameters:
+		//   format:
+		//     A composite format string (see Remarks).
+		//
+		//   arg0:
+		//     An object to write using format.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//
+		//   T:System.ArgumentNullException:
+		//     format is null.
+		//
+		//   T:System.FormatException:
+		//     The format specification in format is invalid.
+		public static void Write (string format, object arg0)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified 32-bit unsigned integer value
+		//     to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//[CLSCompliant (false)]
+		public static void Write (uint value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//[CLSCompliant (false)]
+		public static void Write (string format, object arg0, object arg1, object arg2, object arg3)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified array of objects to the standard
+		//     output stream using the specified format information.
+		//
+		// Parameters:
+		//   format:
+		//     A composite format string (see Remarks).
+		//
+		//   arg:
+		//     An array of objects to write using format.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//
+		//   T:System.ArgumentNullException:
+		//     format or arg is null.
+		//
+		//   T:System.FormatException:
+		//     The format specification in format is invalid.
+		public static void Write (string format, params object [] arg)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified Boolean value to the standard
+		//     output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (bool value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the specified Unicode character value to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (char value)
+		{
+			_buffer [CursorLeft, CursorTop] = value;
+		}
+
+		//
+		// Summary:
+		//     Writes the specified array of Unicode characters to the standard output stream.
+		//
+		// Parameters:
+		//   buffer:
+		//     A Unicode character array.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (char [] buffer)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the specified subarray of Unicode characters to the standard output stream.
+		//
+		// Parameters:
+		//   buffer:
+		//     An array of Unicode characters.
+		//
+		//   index:
+		//     The starting position in buffer.
+		//
+		//   count:
+		//     The number of characters to write.
+		//
+		// Exceptions:
+		//   T:System.ArgumentNullException:
+		//     buffer is null.
+		//
+		//   T:System.ArgumentOutOfRangeException:
+		//     index or count is less than zero.
+		//
+		//   T:System.ArgumentException:
+		//     index plus count specify a position that is not within buffer.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (char [] buffer, int index, int count)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified objects to the standard output
+		//     stream using the specified format information.
+		//
+		// Parameters:
+		//   format:
+		//     A composite format string (see Remarks).
+		//
+		//   arg0:
+		//     The first object to write using format.
+		//
+		//   arg1:
+		//     The second object to write using format.
+		//
+		//   arg2:
+		//     The third object to write using format.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//
+		//   T:System.ArgumentNullException:
+		//     format is null.
+		//
+		//   T:System.FormatException:
+		//     The format specification in format is invalid.
+		public static void Write (string format, object arg0, object arg1, object arg2)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified System.Decimal value to the standard
+		//     output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (decimal value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified single-precision floating-point
+		//     value to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (float value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified double-precision floating-point
+		//     value to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void Write (double value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the current line terminator to the standard output stream.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine ()
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified single-precision floating-point
+		//     value, followed by the current line terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (float value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified 32-bit signed integer value,
+		//     followed by the current line terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (int value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified 32-bit unsigned integer value,
+		//     followed by the current line terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//[CLSCompliant (false)]
+		public static void WriteLine (uint value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified 64-bit signed integer value,
+		//     followed by the current line terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (long value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified 64-bit unsigned integer value,
+		//     followed by the current line terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//[CLSCompliant (false)]
+		public static void WriteLine (ulong value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified object, followed by the current
+		//     line terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (object value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the specified string value, followed by the current line terminator, to
+		//     the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (string value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified object, followed by the current
+		//     line terminator, to the standard output stream using the specified format information.
+		//
+		// Parameters:
+		//   format:
+		//     A composite format string (see Remarks).
+		//
+		//   arg0:
+		//     An object to write using format.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//
+		//   T:System.ArgumentNullException:
+		//     format is null.
+		//
+		//   T:System.FormatException:
+		//     The format specification in format is invalid.
+		public static void WriteLine (string format, object arg0)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified objects, followed by the current
+		//     line terminator, to the standard output stream using the specified format information.
+		//
+		// Parameters:
+		//   format:
+		//     A composite format string (see Remarks).
+		//
+		//   arg0:
+		//     The first object to write using format.
+		//
+		//   arg1:
+		//     The second object to write using format.
+		//
+		//   arg2:
+		//     The third object to write using format.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//
+		//   T:System.ArgumentNullException:
+		//     format is null.
+		//
+		//   T:System.FormatException:
+		//     The format specification in format is invalid.
+		public static void WriteLine (string format, object arg0, object arg1, object arg2)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//[CLSCompliant (false)]
+		public static void WriteLine (string format, object arg0, object arg1, object arg2, object arg3)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified array of objects, followed by
+		//     the current line terminator, to the standard output stream using the specified
+		//     format information.
+		//
+		// Parameters:
+		//   format:
+		//     A composite format string (see Remarks).
+		//
+		//   arg:
+		//     An array of objects to write using format.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//
+		//   T:System.ArgumentNullException:
+		//     format or arg is null.
+		//
+		//   T:System.FormatException:
+		//     The format specification in format is invalid.
+		public static void WriteLine (string format, params object [] arg)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the specified subarray of Unicode characters, followed by the current
+		//     line terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   buffer:
+		//     An array of Unicode characters.
+		//
+		//   index:
+		//     The starting position in buffer.
+		//
+		//   count:
+		//     The number of characters to write.
+		//
+		// Exceptions:
+		//   T:System.ArgumentNullException:
+		//     buffer is null.
+		//
+		//   T:System.ArgumentOutOfRangeException:
+		//     index or count is less than zero.
+		//
+		//   T:System.ArgumentException:
+		//     index plus count specify a position that is not within buffer.
+		//
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (char [] buffer, int index, int count)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified System.Decimal value, followed
+		//     by the current line terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (decimal value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the specified array of Unicode characters, followed by the current line
+		//     terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   buffer:
+		//     A Unicode character array.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (char [] buffer)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the specified Unicode character, followed by the current line terminator,
+		//     value to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (char value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified Boolean value, followed by the
+		//     current line terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (bool value)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified objects, followed by the current
+		//     line terminator, to the standard output stream using the specified format information.
+		//
+		// Parameters:
+		//   format:
+		//     A composite format string (see Remarks).
+		//
+		//   arg0:
+		//     The first object to write using format.
+		//
+		//   arg1:
+		//     The second object to write using format.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		//
+		//   T:System.ArgumentNullException:
+		//     format is null.
+		//
+		//   T:System.FormatException:
+		//     The format specification in format is invalid.
+		public static void WriteLine (string format, object arg0, object arg1)
+		{
+			throw new NotImplementedException ();
+		}
+
+		//
+		// Summary:
+		//     Writes the text representation of the specified double-precision floating-point
+		//     value, followed by the current line terminator, to the standard output stream.
+		//
+		// Parameters:
+		//   value:
+		//     The value to write.
+		//
+		// Exceptions:
+		//   T:System.IO.IOException:
+		//     An I/O error occurred.
+		public static void WriteLine (double value)
+		{
+			throw new NotImplementedException ();
+		}
+
+	}
+}

+ 440 - 0
Terminal.Gui/ConsoleDrivers/MockDriver/MockDriver.cs

@@ -0,0 +1,440 @@
+//
+// MockDriver.cs: A mock ConsoleDriver for unit tests.
+//
+// Authors:
+//   Charlie Kindel (github.com/tig)
+//
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using NStack;
+
+namespace Terminal.Gui {
+	public class MockDriver : ConsoleDriver, IMainLoopDriver {
+		int cols, rows;
+		public override int Cols => cols;
+		public override int Rows => rows;
+
+		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
+		int [,,] contents;
+		bool [] dirtyLine;
+
+		void UpdateOffscreen ()
+		{
+			int cols = Cols;
+			int rows = Rows;
+
+			contents = new int [rows, cols, 3];
+			for (int r = 0; r < rows; r++) {
+				for (int c = 0; c < cols; c++) {
+					contents [r, c, 0] = ' ';
+					contents [r, c, 1] = MakeColor (ConsoleColor.Gray, ConsoleColor.Black);
+					contents [r, c, 2] = 0;
+				}
+			}
+			dirtyLine = new bool [rows];
+			for (int row = 0; row < rows; row++)
+				dirtyLine [row] = true;
+		}
+
+		static bool sync = false;
+
+		public MockDriver ()
+		{
+			cols = MockConsole.WindowWidth;
+			rows = MockConsole.WindowHeight; // - 1;
+			UpdateOffscreen ();
+		}
+
+		bool needMove;
+		// Current row, and current col, tracked by Move/AddCh only
+		int ccol, crow;
+		public override void Move (int col, int row)
+		{
+			ccol = col;
+			crow = row;
+
+			if (Clip.Contains (col, row)) {
+				MockConsole.CursorTop = row;
+				MockConsole.CursorLeft = col;
+				needMove = false;
+			} else {
+				MockConsole.CursorTop = Clip.Y;
+				MockConsole.CursorLeft = Clip.X;
+				needMove = true;
+			}
+
+		}
+
+		public override void AddRune (Rune rune)
+		{
+			if (Clip.Contains (ccol, crow)) {
+				if (needMove) {
+					//MockConsole.CursorLeft = ccol;
+					//MockConsole.CursorTop = crow;
+					needMove = false;
+				}
+				contents [crow, ccol, 0] = (int)(uint)rune;
+				contents [crow, ccol, 1] = currentAttribute;
+				contents [crow, ccol, 2] = 1;
+				dirtyLine [crow] = true;
+			} else
+				needMove = true;
+			ccol++;
+			//if (ccol == Cols) {
+			//	ccol = 0;
+			//	if (crow + 1 < Rows)
+			//		crow++;
+			//}
+			if (sync)
+				UpdateScreen ();
+		}
+
+		public override void AddStr (ustring str)
+		{
+			foreach (var rune in str)
+				AddRune (rune);
+		}
+
+		public override void End ()
+		{
+			MockConsole.ResetColor ();
+			MockConsole.Clear ();
+		}
+
+		static Attribute MakeColor (ConsoleColor f, ConsoleColor b)
+		{
+			// Encode the colors into the int value.
+			return new Attribute () { value = ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff) };
+		}
+
+
+		public override void Init (Action terminalResized)
+		{
+			Colors.TopLevel = new ColorScheme ();
+			Colors.Base = new ColorScheme ();
+			Colors.Dialog = new ColorScheme ();
+			Colors.Menu = new ColorScheme ();
+			Colors.Error = new ColorScheme ();
+			Clip = new Rect (0, 0, Cols, Rows);
+
+			Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
+			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
+			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
+			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
+
+			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Blue);
+			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
+			Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue);
+			Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
+
+			// Focused,
+			//    Selected, Hot: Yellow on Black
+			//    Selected, text: white on black
+			//    Unselected, hot: yellow on cyan
+			//    unselected, text: same as unfocused
+			Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
+			Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
+			Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
+			Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
+			Colors.Menu.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Cyan);
+
+			Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
+			Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
+			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
+			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Cyan);
+
+			Colors.Error.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Red);
+			Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
+			Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red);
+			Colors.Error.HotFocus = Colors.Error.HotNormal;
+
+			HLine = '\u2500';
+			VLine = '\u2502';
+			Stipple = '\u2592';
+			Diamond = '\u25c6';
+			ULCorner = '\u250C';
+			LLCorner = '\u2514';
+			URCorner = '\u2510';
+			LRCorner = '\u2518';
+			LeftTee = '\u251c';
+			RightTee = '\u2524';
+			TopTee = '\u22a4';
+			BottomTee = '\u22a5';
+			Checked = '\u221a';
+			UnChecked = ' ';
+			Selected = '\u25cf';
+			UnSelected = '\u25cc';
+			RightArrow = '\u25ba';
+			LeftArrow = '\u25c4';
+			UpArrow = '\u25b2';
+			DownArrow = '\u25bc';
+			LeftDefaultIndicator = '\u25e6';
+			RightDefaultIndicator = '\u25e6';
+			LeftBracket = '[';
+			RightBracket = ']';
+			OnMeterSegment = '\u258c';
+			OffMeterSegement = ' ';
+
+			//MockConsole.Clear ();
+		}
+
+		public override Attribute MakeAttribute (Color fore, Color back)
+		{
+			return MakeColor ((ConsoleColor)fore, (ConsoleColor)back);
+		}
+
+		int redrawColor = -1;
+		void SetColor (int color)
+		{
+			redrawColor = color;
+			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
+			      .OfType<ConsoleColor> ()
+			      .Select (s => (int)s);
+			if (values.Contains (color & 0xffff)) {
+				MockConsole.BackgroundColor = (ConsoleColor)(color & 0xffff);
+			}
+			if (values.Contains ((color >> 16) & 0xffff)) {
+				MockConsole.ForegroundColor = (ConsoleColor)((color >> 16) & 0xffff);
+			}
+		}
+
+		public override void UpdateScreen ()
+		{
+			int rows = Rows;
+			int cols = Cols;
+
+			MockConsole.CursorTop = 0;
+			MockConsole.CursorLeft = 0;
+			for (int row = 0; row < rows; row++) {
+				dirtyLine [row] = false;
+				for (int col = 0; col < cols; col++) {
+					contents [row, col, 2] = 0;
+					var color = contents [row, col, 1];
+					if (color != redrawColor)
+						SetColor (color);
+					MockConsole.Write ((char)contents [row, col, 0]);
+				}
+			}
+		}
+
+		public override void Refresh ()
+		{
+			int rows = Rows;
+			int cols = Cols;
+
+			var savedRow = MockConsole.CursorTop;
+			var savedCol = MockConsole.CursorLeft;
+			for (int row = 0; row < rows; row++) {
+				if (!dirtyLine [row])
+					continue;
+				dirtyLine [row] = false;
+				for (int col = 0; col < cols; col++) {
+					if (contents [row, col, 2] != 1)
+						continue;
+
+					MockConsole.CursorTop = row;
+					MockConsole.CursorLeft = col;
+					for (; col < cols && contents [row, col, 2] == 1; col++) {
+						var color = contents [row, col, 1];
+						if (color != redrawColor)
+							SetColor (color);
+
+						MockConsole.Write ((char)contents [row, col, 0]);
+						contents [row, col, 2] = 0;
+					}
+				}
+			}
+			MockConsole.CursorTop = savedRow;
+			MockConsole.CursorLeft = savedCol;
+		}
+
+		public override void UpdateCursor ()
+		{
+			//
+		}
+
+		public override void StartReportingMouseMoves ()
+		{
+		}
+
+		public override void StopReportingMouseMoves ()
+		{
+		}
+
+		public override void Suspend ()
+		{
+		}
+
+		int currentAttribute;
+		public override void SetAttribute (Attribute c)
+		{
+			currentAttribute = c.value;
+		}
+
+		Key MapKey (ConsoleKeyInfo keyInfo)
+		{
+			switch (keyInfo.Key) {
+			case ConsoleKey.Escape:
+				return Key.Esc;
+			case ConsoleKey.Tab:
+				return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+			case ConsoleKey.Home:
+				return Key.Home;
+			case ConsoleKey.End:
+				return Key.End;
+			case ConsoleKey.LeftArrow:
+				return Key.CursorLeft;
+			case ConsoleKey.RightArrow:
+				return Key.CursorRight;
+			case ConsoleKey.UpArrow:
+				return Key.CursorUp;
+			case ConsoleKey.DownArrow:
+				return Key.CursorDown;
+			case ConsoleKey.PageUp:
+				return Key.PageUp;
+			case ConsoleKey.PageDown:
+				return Key.PageDown;
+			case ConsoleKey.Enter:
+				return Key.Enter;
+			case ConsoleKey.Spacebar:
+				return Key.Space;
+			case ConsoleKey.Backspace:
+				return Key.Backspace;
+			case ConsoleKey.Delete:
+				return Key.Delete;
+
+			case ConsoleKey.Oem1:
+			case ConsoleKey.Oem2:
+			case ConsoleKey.Oem3:
+			case ConsoleKey.Oem4:
+			case ConsoleKey.Oem5:
+			case ConsoleKey.Oem6:
+			case ConsoleKey.Oem7:
+			case ConsoleKey.Oem8:
+			case ConsoleKey.Oem102:
+			case ConsoleKey.OemPeriod:
+			case ConsoleKey.OemComma:
+			case ConsoleKey.OemPlus:
+			case ConsoleKey.OemMinus:
+				return (Key)((uint)keyInfo.KeyChar);
+			}
+
+			var key = keyInfo.Key;
+			if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
+				var delta = key - ConsoleKey.A;
+				if (keyInfo.Modifiers == ConsoleModifiers.Control)
+					return (Key)((uint)Key.ControlA + delta);
+				if (keyInfo.Modifiers == ConsoleModifiers.Alt)
+					return (Key)(((uint)Key.AltMask) | ((uint)'A' + delta));
+				if (keyInfo.Modifiers == ConsoleModifiers.Shift)
+					return (Key)((uint)'A' + delta);
+				else
+					return (Key)((uint)'a' + delta);
+			}
+			if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
+				var delta = key - ConsoleKey.D0;
+				if (keyInfo.Modifiers == ConsoleModifiers.Alt)
+					return (Key)(((uint)Key.AltMask) | ((uint)'0' + delta));
+				if (keyInfo.Modifiers == ConsoleModifiers.Shift)
+					return (Key)((uint)keyInfo.KeyChar);
+				return (Key)((uint)'0' + delta);
+			}
+			if (key >= ConsoleKey.F1 && key <= ConsoleKey.F10) {
+				var delta = key - ConsoleKey.F1;
+
+				return (Key)((int)Key.F1 + delta);
+			}
+			return (Key)(0xffffffff);
+		}
+
+		KeyModifiers keyModifiers = new KeyModifiers ();
+
+		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
+		{
+			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
+			(mainLoop.Driver as MockDriver).WindowsKeyPressed = delegate (ConsoleKeyInfo consoleKey) {
+				var map = MapKey (consoleKey);
+				if (map == (Key)0xffffffff)
+					return;
+				keyHandler (new KeyEvent (map, keyModifiers));
+				keyUpHandler (new KeyEvent (map, keyModifiers));
+			};
+		}
+
+		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
+		{
+			throw new NotImplementedException ();
+		}
+
+		public override void SetColors (short foregroundColorId, short backgroundColorId)
+		{
+			throw new NotImplementedException ();
+		}
+
+		public override void CookMouse ()
+		{
+		}
+
+		public override void UncookMouse ()
+		{
+		}
+
+		AutoResetEvent keyReady = new AutoResetEvent (false);
+		AutoResetEvent waitForProbe = new AutoResetEvent (false);
+		ConsoleKeyInfo? windowsKeyResult = null;
+		public Action<ConsoleKeyInfo> WindowsKeyPressed;
+		MainLoop mainLoop;
+
+		void WindowsKeyReader ()
+		{
+			while (true) {
+				waitForProbe.WaitOne ();
+				windowsKeyResult = MockConsole.ReadKey (true);
+				keyReady.Set ();
+			}
+		}
+
+		void IMainLoopDriver.Setup (MainLoop mainLoop)
+		{
+			this.mainLoop = mainLoop;
+			Thread readThread = new Thread (WindowsKeyReader);
+			readThread.Start ();
+		}
+
+		void IMainLoopDriver.Wakeup ()
+		{
+		}
+
+		bool IMainLoopDriver.EventsPending (bool wait)
+		{
+			long now = DateTime.UtcNow.Ticks;
+
+			int waitTimeout;
+			if (mainLoop.timeouts.Count > 0) {
+				waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
+				if (waitTimeout < 0)
+					return true;
+			} else
+				waitTimeout = -1;
+
+			if (!wait)
+				waitTimeout = 0;
+
+			windowsKeyResult = null;
+			waitForProbe.Set ();
+			keyReady.WaitOne (waitTimeout);
+			return windowsKeyResult.HasValue;
+		}
+
+		void IMainLoopDriver.MainIteration ()
+		{
+			if (windowsKeyResult.HasValue) {
+				if (WindowsKeyPressed != null)
+					WindowsKeyPressed (windowsKeyResult.Value);
+				windowsKeyResult = null;
+			}
+		}
+	}
+}

+ 16 - 5
Terminal.Gui/Core/Application.cs

@@ -157,21 +157,28 @@ namespace Terminal.Gui {
 		/// Creates a <see cref="Toplevel"/> and assigns it to <see cref="Top"/> and <see cref="CurrentView"/>
 		/// </para>
 		/// </remarks>
-		public static void Init () => Init (() => Toplevel.Create ());
+		public static void Init (ConsoleDriver driver = null) => Init (() => Toplevel.Create (), driver);
 
 		internal static bool _initialized = false;
 
 		/// <summary>
 		/// Initializes the Terminal.Gui application
 		/// </summary>
-		static void Init (Func<Toplevel> topLevelFactory)
+		static void Init (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null)
 		{
 			if (_initialized) return;
 
+			// This supports Unit Tests and the passing of a mock driver/loopdriver
+			if (driver != null) {
+				Driver = driver;
+				Driver.Init (TerminalResized);
+				MainLoop = new MainLoop ((IMainLoopDriver)driver);
+				SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
+			}
+
 			if (Driver == null) {
 				var p = Environment.OSVersion.Platform;
 				IMainLoopDriver mainLoopDriver;
-
 				if (UseSystemConsole) {
 					mainLoopDriver = new NetMainLoop ();
 					Driver = new NetDriver ();
@@ -199,7 +206,11 @@ namespace Terminal.Gui {
 		public class RunState : IDisposable {
 			internal bool closeDriver = true;
 
-			internal RunState (Toplevel view)
+			/// <summary>
+			/// Initializes a new <see cref="RunState"/> class.
+			/// </summary>
+			/// <param name="view"></param>
+			public RunState (Toplevel view)
 			{
 				Toplevel = view;
 			}
@@ -476,7 +487,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Shutdown an application initialized with <see cref="Init()"/>
+		/// Shutdown an application initialized with <see cref="Init(ConsoleDriver)"/>
 		/// </summary>
 		/// /// <param name="closeDriver"><c>true</c>Closes the application.<c>false</c>Closes toplevels only.</param>
 		public static void Shutdown (bool closeDriver = true)

+ 1 - 1
Terminal.Gui/Core/Toplevel.cs

@@ -20,7 +20,7 @@ namespace Terminal.Gui {
 	///     been called (which sets the <see cref="Toplevel.Running"/> property to false). 
 	///   </para>
 	///   <para>
-	///     A Toplevel is created when an application initialzies Terminal.Gui by callling <see cref="Application.Init()"/>.
+	///     A Toplevel is created when an application initialzies Terminal.Gui by callling <see cref="Application.Init(ConsoleDriver)"/>.
 	///     The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional Toplevels can be created 
 	///     and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and 
 	///     call <see cref="Application.Run(Toplevel, bool)"/>.

+ 11 - 1
Terminal.sln

@@ -6,10 +6,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example.
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui", "Terminal.Gui\Terminal.Gui.csproj", "{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Designer", "Designer\Designer.csproj", "{1228D992-C801-49BB-839A-7BD28A3FFF0A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Designer", "Designer\Designer.csproj", "{1228D992-C801-49BB-839A-7BD28A3FFF0A}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UICatalog", "UICatalog\UICatalog.csproj", "{88979F89-9A42-448F-AE3E-3060145F6375}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{8B901EDE-8974-4820-B100-5226917E2990}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -50,6 +52,14 @@ Global
 		{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.Build.0 = Release|Any CPU
 		{88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.ActiveCfg = Release|Any CPU
 		{88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.Build.0 = Release|Any CPU
+		{8B901EDE-8974-4820-B100-5226917E2990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8B901EDE-8974-4820-B100-5226917E2990}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8B901EDE-8974-4820-B100-5226917E2990}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{8B901EDE-8974-4820-B100-5226917E2990}.Debug|x86.Build.0 = Debug|Any CPU
+		{8B901EDE-8974-4820-B100-5226917E2990}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8B901EDE-8974-4820-B100-5226917E2990}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8B901EDE-8974-4820-B100-5226917E2990}.Release|x86.ActiveCfg = Release|Any CPU
+		{8B901EDE-8974-4820-B100-5226917E2990}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 195 - 0
UnitTests/ApplicationTests.cs

@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Terminal.Gui;
+using Xunit;
+
+// Alais Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.MockConsole;
+
+// Since Application is a singleton we can't run tests in parallel
+[assembly: CollectionBehavior (DisableTestParallelization = true)]
+
+namespace Terminal.Gui {
+	public class ApplicationTests {
+		[Fact]
+		public void TestInitShutdown ()
+		{
+			Assert.Null (Application.Current);
+			Assert.Null (Application.CurrentView);
+			Assert.Null (Application.Top);
+			Assert.Null (Application.MainLoop);
+			Assert.Null (Application.Driver);
+
+			Application.Init (new MockDriver ());
+			Assert.NotNull (Application.Current);
+			Assert.NotNull (Application.CurrentView);
+			Assert.NotNull (Application.Top);
+			Assert.NotNull (Application.MainLoop);
+			Assert.NotNull (Application.Driver);
+
+			// MockDriver is always 80x25
+			Assert.Equal (80, Application.Driver.Cols);
+			Assert.Equal (25, Application.Driver.Rows);
+
+			Application.Shutdown (true);
+			Assert.Null (Application.Current);
+			Assert.Null (Application.CurrentView);
+			Assert.Null (Application.Top);
+			Assert.Null (Application.MainLoop);
+			Assert.Null (Application.Driver);
+		}
+
+		[Fact]
+		public void TestNewRunState ()
+		{
+			var rs = new Application.RunState (null);
+			Assert.NotNull (rs);
+
+			// Should not throw because Toplevel was null
+			rs.Dispose ();
+
+			var top = new Toplevel ();
+			rs = new Application.RunState (top);
+			Assert.NotNull (rs);
+
+			// Should throw because there's no stack
+			Assert.Throws<InvalidOperationException> (() => rs.Dispose ());
+		}
+
+		[Fact]
+		public void TestBeginEnd ()
+		{
+			// Setup Mock driver
+			Application.Init (new MockDriver ());
+			Assert.NotNull (Application.Driver);
+
+			// Test null Toplevel
+			Assert.Throws<ArgumentNullException> (() => Application.Begin (null));
+
+			var top = new Toplevel ();
+			var rs = Application.Begin (top);
+			Assert.NotNull (rs);
+			Assert.Equal (top, Application.Current);
+			Application.End (rs, true);
+
+			Assert.Null (Application.Current);
+			Assert.Null (Application.CurrentView);
+			Assert.Null (Application.Top);
+			Assert.Null (Application.MainLoop);
+			Assert.Null (Application.Driver);
+
+			Application.Shutdown (true);
+		}
+
+		[Fact]
+		public void TestRequestStop ()
+		{
+			// Setup Mock driver
+			Application.Init (new MockDriver ());
+			Assert.NotNull (Application.Driver);
+
+			var top = new Toplevel ();
+			var rs = Application.Begin (top);
+			Assert.NotNull (rs);
+			Assert.Equal (top, Application.Current);
+
+			Application.Iteration = () => {
+				Application.RequestStop ();
+			};
+
+			Application.Run (top, true);
+
+			Application.Shutdown (true);
+			Assert.Null (Application.Current);
+			Assert.Null (Application.CurrentView);
+			Assert.Null (Application.Top);
+			Assert.Null (Application.MainLoop);
+			Assert.Null (Application.Driver);
+		}
+
+		[Fact]
+		public void TestRunningFalse ()
+		{
+			// Setup Mock driver
+			Application.Init (new MockDriver ());
+			Assert.NotNull (Application.Driver);
+
+			var top = new Toplevel ();
+			var rs = Application.Begin (top);
+			Assert.NotNull (rs);
+			Assert.Equal (top, Application.Current);
+
+			Application.Iteration = () => {
+				top.Running = false;
+			};
+
+			Application.Run (top, true);
+
+			Application.Shutdown (true);
+			Assert.Null (Application.Current);
+			Assert.Null (Application.CurrentView);
+			Assert.Null (Application.Top);
+			Assert.Null (Application.MainLoop);
+			Assert.Null (Application.Driver);
+		}
+
+
+		[Fact]
+		public void TestKeyUp ()
+		{
+			// Setup Mock driver
+			Application.Init (new MockDriver ());
+			Assert.NotNull (Application.Driver);
+
+			// Setup some fake kepresses (This)
+			var input = "Tests";
+
+			// Put a control-q in at the end
+			Console.MockKeyPresses.Push (new ConsoleKeyInfo ('q', ConsoleKey.Q, shift: false, alt: false, control: true));
+			foreach (var c in input.Reverse()) {
+				if (char.IsLetter (c)) {
+					Console.MockKeyPresses.Push (new ConsoleKeyInfo (char.ToLower (c), (ConsoleKey)char.ToUpper (c), shift: char.IsUpper (c), alt: false, control: false));
+				}
+				else
+				{
+					Console.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)c, shift: false, alt: false, control: false));
+				}
+			}
+
+			int stackSize = Console.MockKeyPresses.Count;
+
+			int iterations = 0;
+			Application.Iteration = () => {
+				iterations++;
+			};
+
+			int keyUps = 0;
+			var output = string.Empty;
+			Application.Top.KeyUp += (View.KeyEventEventArgs args) => {
+				if (args.KeyEvent.Key != Key.ControlQ) {
+					output += (char)args.KeyEvent.KeyValue;
+				}
+				keyUps++;
+			};
+
+			Application.Run (Application.Top, true);
+
+			// Input string should match output
+			Assert.Equal (input, output);
+
+			// # of key up events should match stack size
+			Assert.Equal (stackSize, keyUps);
+
+			// # of key up events should match # of iterations
+			Assert.Equal (stackSize, iterations);
+
+			Application.Shutdown (true);
+			Assert.Null (Application.Current);
+			Assert.Null (Application.CurrentView);
+			Assert.Null (Application.Top);
+			Assert.Null (Application.MainLoop);
+			Assert.Null (Application.Driver);
+		}
+	}
+}

+ 67 - 0
UnitTests/ConsoleDriverTests.cs

@@ -0,0 +1,67 @@
+using System;
+using Terminal.Gui;
+using Xunit;
+
+// Alais Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.MockConsole;
+
+namespace Terminal.Gui {
+	public class ConsoleDriverTests {
+		[Fact]
+		public void TestInit ()
+		{
+			var driver = new MockDriver ();
+			driver.Init (() => { });
+
+			Assert.Equal (80, Console.BufferWidth);
+			Assert.Equal (25, Console.BufferHeight);
+
+			// MockDriver is always 80x25
+			Assert.Equal (Console.BufferWidth, driver.Cols);
+			Assert.Equal (Console.BufferHeight, driver.Rows);
+			driver.End ();
+		}
+
+		[Fact]
+		public void TestEnd ()
+		{
+			var driver = new MockDriver ();
+			driver.Init (() => { });
+
+			MockConsole.ForegroundColor = ConsoleColor.Red;
+			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
+
+			MockConsole.BackgroundColor = ConsoleColor.Green;
+			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
+			driver.Move (2, 3);
+			Assert.Equal (2, Console.CursorLeft);
+			Assert.Equal (3, Console.CursorTop);
+
+			driver.End ();
+			Assert.Equal (0, Console.CursorLeft);
+			Assert.Equal (0, Console.CursorTop);
+			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
+			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
+		}
+
+		[Fact]
+		public void TestSetColors ()
+		{
+			var driver = new MockDriver ();
+			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 ();
+		}
+	}
+}

+ 20 - 0
UnitTests/UnitTests.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
+    <PackageReference Include="xunit" Version="2.4.0" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
+    <PackageReference Include="coverlet.collector" Version="1.2.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
+    <ProjectReference Include="..\UICatalog\UICatalog.csproj" />
+  </ItemGroup>
+
+</Project>

+ 5 - 0
UnitTests/xunit.runner.json

@@ -0,0 +1,5 @@
+{
+  "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
+  "parallelizeTestCollections": false,
+  "parallelizeAssembly": false
+}