Browse Source

Partially Addresses #2616. Support combining sequences that don't normalize (#2932)

* Fixes #2616. Support combining sequences that don't normalize

* Decouples Application from ConsoleDriver in TestHelpers

* Updates driver tests to match new arch

* Start on making all driver tests test all drivers

* Improves handling if combining marks.

* Fix unit tests fails.

* Fix unit tests fails.

* Handling combining mask.

* Tying to fix this unit test that sometimes fail.

* Add support for combining mask on NetDriver.

* Enable CombiningMarks as List<Rune>.

* Prevents combining marks on invalid runes default and space.

* Formatting for CI tests.

* Fix non-normalized combining mark to add 1 to Col.

* Reformatting for retest the CI.

* Forces non-normalized CMs to be ignored.

---------

Co-authored-by: Tig <[email protected]>
BDisp 1 year ago
parent
commit
aa8b952509

+ 49 - 19
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -6,6 +6,8 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using static Terminal.Gui.ColorScheme;
+using System.Linq;
+using System.Data;
 
 namespace Terminal.Gui;
 
@@ -165,21 +167,38 @@ public abstract class ConsoleDriver {
 		if (validLocation) {
 			rune = rune.MakePrintable ();
 			runeWidth = rune.GetColumns ();
-			if (runeWidth == 0 && rune.IsCombiningMark () && Col > 0) {
-				// This is a combining character, and we are not at the beginning of the line.
-				// TODO: Remove hard-coded [0] once combining pairs is supported
-
-				// Convert Runes to string and concatenate
-				string combined = Contents [Row, Col - 1].Rune.ToString () + rune.ToString ();
-
-				// Normalize to Form C (Canonical Composition)
-				string normalized = combined.Normalize (NormalizationForm.FormC);
-
-				Contents [Row, Col - 1].Rune = (Rune)normalized [0]; ;
-				Contents [Row, Col - 1].Attribute = CurrentAttribute;
-				Contents [Row, Col - 1].IsDirty = true;
-
-				//Col--;
+			if (runeWidth == 0 && rune.IsCombiningMark ()) {
+				if (Col > 0) {
+					if (Contents [Row, Col - 1].CombiningMarks.Count > 0) {
+						// Just add this mark to the list
+						Contents [Row, Col - 1].CombiningMarks.Add (rune);
+						// Don't move to next column (let the driver figure out what to do).
+					} else {
+						// Attempt to normalize the cell to our left combined with this mark
+						string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
+
+						// Normalize to Form C (Canonical Composition)
+						string normalized = combined.Normalize (NormalizationForm.FormC);
+						if (normalized.Length == 1) {
+							// It normalized! We can just set the Cell to the left with the
+							// normalized codepoint 
+							Contents [Row, Col - 1].Rune = (Rune)normalized [0];
+							// Don't move to next column because we're already there
+						} else {
+							// It didn't normalize. Add it to the Cell to left's CM list
+							Contents [Row, Col - 1].CombiningMarks.Add (rune);
+							// Don't move to next column (let the driver figure out what to do).
+						}
+					}
+					Contents [Row, Col - 1].Attribute = CurrentAttribute;
+					Contents [Row, Col - 1].IsDirty = true;
+				} else {
+					// Most drivers will render a combining mark at col 0 as the mark
+					Contents [Row, Col].Rune = rune;
+					Contents [Row, Col].Attribute = CurrentAttribute;
+					Contents [Row, Col].IsDirty = true;
+					Col++;
+				}
 			} else {
 				Contents [Row, Col].Attribute = CurrentAttribute;
 				Contents [Row, Col].IsDirty = true;
@@ -267,8 +286,19 @@ public abstract class ConsoleDriver {
 	/// <param name="str">String.</param>
 	public void AddStr (string str)
 	{
-		foreach (var rune in str.EnumerateRunes ()) {
-			AddRune (rune);
+		var runes = str.EnumerateRunes ().ToList ();
+		for (var i = 0; i < runes.Count; i++) {
+			//if (runes [i].IsCombiningMark()) {
+
+			//	// Attempt to normalize
+			//	string combined = runes [i-1] + runes [i].ToString();
+
+			//	// Normalize to Form C (Canonical Composition)
+			//	string normalized = combined.Normalize (NormalizationForm.FormC);
+
+			//	runes [i-]
+			//}
+			AddRune (runes [i]);
 		}
 	}
 
@@ -453,7 +483,7 @@ public abstract class ConsoleDriver {
 	/// Called after a key has been pressed and released. Fires the <see cref="KeyPressed"/> event.
 	/// </summary>
 	/// <param name="a"></param>
-	public void OnKeyPressed (KeyEventEventArgs a) => KeyPressed?.Invoke(this, a);
+	public void OnKeyPressed (KeyEventEventArgs a) => KeyPressed?.Invoke (this, a);
 
 	/// <summary>
 	/// Event fired when a key is released.
@@ -476,7 +506,7 @@ public abstract class ConsoleDriver {
 	/// </summary>
 	/// <param name="a"></param>
 	public void OnKeyDown (KeyEventEventArgs a) => KeyDown?.Invoke (this, a);
-	
+
 	/// <summary>
 	/// Event fired when a mouse event occurs.
 	/// </summary>

+ 8 - 2
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -15,8 +15,14 @@ namespace Terminal.Gui;
 /// </summary>
 internal class CursesDriver : ConsoleDriver {
 
-	public override int Cols => Curses.Cols;
-	public override int Rows => Curses.Lines;
+	public override int Cols {
+		get => Curses.Cols;
+		internal set => Curses.Cols = value;
+	}
+	public override int Rows {
+		get => Curses.Lines;
+		internal set => Curses.Lines = value;
+	}
 
 	CursorVisibility? _initialCursorVisibility = null;
 	CursorVisibility? _currentCursorVisibility = null;

+ 8 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs

@@ -152,12 +152,20 @@ namespace Unix.Terminal {
 			get {
 				return lines;
 			}
+			internal set {
+				// For unit tests
+				lines = value;
+			}
 		}
 
 		public static int Cols {
 			get {
 				return cols;
 			}
+			internal set {
+				// For unit tests
+				cols = value;
+			}
 		}
 
 		//

+ 15 - 3
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -784,8 +784,9 @@ internal class NetDriver : ConsoleDriver {
 						} else if (lastCol == -1) {
 							lastCol = col;
 						}
-						if (lastCol + 1 < cols)
+						if (lastCol + 1 < cols) {
 							lastCol++;
+						}
 						continue;
 					}
 
@@ -809,8 +810,19 @@ internal class NetDriver : ConsoleDriver {
 					}
 					outputWidth++;
 					var rune = (Rune)Contents [row, col].Rune;
-					output.Append (rune.ToString ());
-					if (rune.IsSurrogatePair () && rune.GetColumns () < 2) {
+					output.Append (rune);
+					if (Contents [row, col].CombiningMarks.Count > 0) {
+						// AtlasEngine does not support NON-NORMALIZED combining marks in a way
+						// compatible with the driver architecture. Any CMs (except in the first col)
+						// are correctly combined with the base char, but are ALSO treated as 1 column
+						// width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
+						// 
+						// For now, we just ignore the list of CMs.
+						//foreach (var combMark in Contents [row, col].CombiningMarks) {
+						//	output.Append (combMark);
+						//}
+						// WriteToConsole (output, ref lastCol, row, ref outputWidth);
+					} else  if ((rune.IsSurrogatePair () && rune.GetColumns () < 2)) {
 						WriteToConsole (output, ref lastCol, row, ref outputWidth);
 						SetCursorPosition (col - 1, row);
 					}

+ 21 - 23
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -807,28 +807,26 @@ internal class WindowsDriver : ConsoleDriver {
 	internal override MainLoop Init ()
 	{
 		_mainLoopDriver = new WindowsMainLoop (this);
-		if (RunningUnitTests) {
-			return new MainLoop (_mainLoopDriver);
-		}
-
-		try {
-			if (WinConsole != null) {
-				// BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. 
-				// Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED
-				var winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
-				Cols = winSize.Width;
-				Rows = winSize.Height;
-			}
-			WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion);
+		if (!RunningUnitTests) {
+			try {
+				if (WinConsole != null) {
+					// BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. 
+					// Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED
+					var winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
+					Cols = winSize.Width;
+					Rows = winSize.Height;
+				}
+				WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion);
 
-			if (_isWindowsTerminal) {
-				Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+				if (_isWindowsTerminal) {
+					Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+				}
+			} catch (Win32Exception e) {
+				// We are being run in an environment that does not support a console
+				// such as a unit test, or a pipe.
+				Debug.WriteLine ($"Likely running unit tests. Setting WinConsole to null so we can test it elsewhere. Exception: {e}");
+				WinConsole = null;
 			}
-		} catch (Win32Exception e) {
-			// We are being run in an environment that does not support a console
-			// such as a unit test, or a pipe.
-			Debug.WriteLine ($"Likely running unit tests. Setting WinConsole to null so we can test it elsewhere. Exception: {e}");
-			WinConsole = null;
 		}
 
 		CurrentAttribute = new Attribute (Color.White, Color.Black);
@@ -885,7 +883,7 @@ internal class WindowsDriver : ConsoleDriver {
 	// It also is broken when modifiers keys are down too
 	//
 	//Key _keyDown = (Key)0xffffffff;
-	
+
 	internal void ProcessInput (WindowsConsole.InputRecord inputEvent)
 	{
 		switch (inputEvent.EventType) {
@@ -976,9 +974,9 @@ internal class WindowsDriver : ConsoleDriver {
 					_keyModifiers ??= new KeyModifiers ();
 
 					//if (_keyDown == (Key)0xffffffff) {
-					       // Avoid sending repeat keydowns
+					// Avoid sending repeat keydowns
 					//	_keyDown = map;
-						OnKeyDown (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
+					OnKeyDown (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
 					//}
 					OnKeyPressed (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
 				} else {

+ 17 - 12
Terminal.Gui/Drawing/Cell.cs

@@ -2,28 +2,33 @@
 using System.Text;
 
 
-namespace Terminal.Gui; 
+namespace Terminal.Gui;
 
 /// <summary>
 /// Represents a single row/column in a Terminal.Gui rendering surface
 /// (e.g. <see cref="LineCanvas"/> and <see cref="ConsoleDriver"/>).
 /// </summary>
 public class Cell {
+	Rune _rune;
 	/// <summary>
 	/// The character to display. If <see cref="Rune"/> is <see langword="null"/>, then <see cref="Rune"/> is ignored.
 	/// </summary>
-	public Rune Rune { get; set; }
+	public Rune Rune {
+		get => _rune;
+		set {
+			CombiningMarks.Clear ();
+			_rune = value;
+		}
+	}
 
-	// TODO: Uncomment this once combining sequences that could not be normalized are supported.
-	///// <summary>
-	///// The combining mark for <see cref="Rune"/> that when combined makes this Cell a combining sequence that could
-	///// not be normalized to a single Rune.
-	///// If <see cref="CombiningMark"/> is <see langword="null"/>, then <see cref="CombiningMark"/> is ignored.
-	///// </summary>
-	///// <remarks>
-	///// Only valid in the rare case where <see cref="Rune"/> is a combining sequence that could not be normalized to a single Rune.
-	///// </remarks>
-	//internal Rune CombiningMark { get; set; }
+	/// <summary>
+	/// The combining marks for <see cref="Rune"/> that when combined makes this Cell a combining sequence.
+	/// If <see cref="CombiningMarks"/> empty, then <see cref="CombiningMarks"/> is ignored.
+	/// </summary>
+	/// <remarks>
+	/// Only valid in the rare case where <see cref="Rune"/> is a combining sequence that could not be normalized to a single Rune.
+	/// </remarks>
+	internal List<Rune> CombiningMarks { get; } = new List<Rune> ();
 
 	/// <summary>
 	/// The attributes to use when drawing the Glyph.

+ 2 - 1
Terminal.Gui/Text/TextFormatter.cs

@@ -1411,7 +1411,8 @@ namespace Terminal.Gui {
 					} else {
 						Application.Driver?.AddRune (rune);
 					}
-					var runeWidth = Math.Max (rune.GetColumns (), 1);
+					// BUGBUG: I think this is a bug. If rune is a combining mark current should not be incremented.
+					var runeWidth = rune.GetColumns (); //Math.Max (rune.GetColumns (), 1);
 					if (isVertical) {
 						current++;
 					} else {

+ 22 - 1
UICatalog/Scenarios/CharacterMap.cs

@@ -473,7 +473,28 @@ class CharMap : ScrollView {
 
 				// are we at first row of the row?
 				if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) {
-					Driver.AddRune (rune);
+					if (width > 0) {
+						Driver.AddRune (rune);
+					} else {
+						if (rune.IsCombiningMark ()) {
+							// This is a hack to work around the fact that combining marks
+							// a) can't be rendered on their own
+							// b) that don't normalize are not properly supported in 
+							//    any known terminal (esp Windows/AtlasEngine). 
+							// See Issue #2616
+							var sb = new StringBuilder ();
+							sb.Append ('a');
+							sb.Append (rune);
+							// Try normalizing after combining with 'a'. If it normalizes, at least 
+							// it'll show on the 'a'. If not, just show the replacement char.
+							var normal = sb.ToString ().Normalize (NormalizationForm.FormC);
+							if (normal.Length == 1) {
+								Driver.AddRune (normal [0]);
+							} else {
+								Driver.AddRune (Rune.ReplacementChar);
+							}
+						}
+					}
 				} else {
 					Driver.SetAttribute (ColorScheme.HotNormal);
 					Driver.AddStr ($"{width}");

+ 35 - 0
UICatalog/Scenarios/CombiningMarks.cs

@@ -0,0 +1,35 @@
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Combining Marks", Description: "Illustrates how Unicode Combining Marks work (or don't).")]
+	[ScenarioCategory ("Text and Formatting")]
+	public class CombiningMarks : Scenario {
+		public override void Init ()
+		{
+			Application.Init ();
+			ConfigurationManager.Themes.Theme = Theme;
+			ConfigurationManager.Apply ();
+			Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
+		}
+
+		public override void Setup ()
+		{
+			Application.Top.DrawContentComplete += (s, e) => {
+				Application.Driver.Move (0, 0);
+				Application.Driver.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
+				Application.Driver.Move (0, 2);
+				Application.Driver.AddStr ("\u0301\u0301\u0328<- \"\\u301\\u301\\u328]\" using AddStr.");
+				Application.Driver.Move (0, 3);
+				Application.Driver.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u301\\u301\\u328]\" using AddStr.");
+				Application.Driver.Move (0, 4);
+				Application.Driver.AddRune ('[');
+				Application.Driver.AddRune ('a');
+				Application.Driver.AddRune ('\u0301');
+				Application.Driver.AddRune ('\u0301');
+				Application.Driver.AddRune ('\u0328');
+				Application.Driver.AddRune (']');
+				Application.Driver.AddStr ("<- \"[a\\u301\\u301\\u328]\" using AddRune for each.");
+			};
+		}
+	}
+}

+ 2 - 2
UICatalog/Scenarios/TabViewExample.cs

@@ -67,8 +67,8 @@ namespace UICatalog.Scenarios {
 				"Long name Tab, I mean seriously long.  Like you would not believe how long this tab's name is its just too much really woooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooowwww thats long",
 				 new Label ("This tab has a very long name which should be truncated.  See TabView.MaxTabTextWidth")),
 				 false);
-			tabView.AddTab (new Tab ("Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables", new Label ("This tab name is unicode")), false);
-
+			tabView.AddTab (new Tab ("Les Mise" + '\u0301' + "rables", new Label ("This tab name is unicode")), false);
+			tabView.AddTab (new Tab ("Les Mise" + '\u0328' + '\u0301' + "rables", new Label ("This tab name has two combining marks. Only one will show due to Issue #2616.")), false);
 			for (int i = 0; i < 100; i++) {
 				tabView.AddTab (new Tab ($"Tab{i}", new Label ($"Welcome to tab {i}")), false);
 			}

+ 5 - 2
UICatalog/Scenarios/Unicode.cs

@@ -42,9 +42,12 @@ namespace UICatalog.Scenarios {
 
 			label = new Label ("Label (CanFocus):") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 };
 			Win.Add (label);
-			testlabel = new Label ("Стоял &он, дум великих полн") { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50), CanFocus = true, HotKeySpecifier = new Rune ('&') };
+			var sb = new StringBuilder ();
+			sb.Append ('e');
+			sb.Append ('\u0301');
+			sb.Append ('\u0301');
+			testlabel = new Label ($"Should be [e with two accents, but isn't due to #2616]: [{sb}]") { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50), CanFocus = true, HotKeySpecifier = new Rune ('&') };
 			Win.Add (testlabel);
-
 			label = new Label ("Button:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 };
 			Win.Add (label);
 			var button = new Button ("A123456789♥♦♣♠JQK") { X = 20, Y = Pos.Y (label) };

+ 9 - 4
UnitTests/Application/ApplicationTests.cs

@@ -422,20 +422,25 @@ public class ApplicationTests {
 
 	#region ShutdownTests
 	[Fact]
-	public void Shutdown_Allows_Async ()
+	public async void Shutdown_Allows_Async ()
 	{
-		static async Task TaskWithAsyncContinuation ()
+		bool isCompletedSuccessfully = false;
+
+		async Task TaskWithAsyncContinuation ()
 		{
 			await Task.Yield ();
 			await Task.Yield ();
+
+			isCompletedSuccessfully = true;
 		}
 
 		Init ();
 		Application.Shutdown ();
 
-		var task = TaskWithAsyncContinuation ();
+		Assert.False (isCompletedSuccessfully);
+		await TaskWithAsyncContinuation ();
 		Thread.Sleep (100);
-		Assert.True (task.IsCompletedSuccessfully);
+		Assert.True (isCompletedSuccessfully);
 	}
 
 	[Fact]

+ 13 - 16
UnitTests/ConsoleDrivers/AddRuneTests.cs

@@ -1,4 +1,5 @@
-using System.Buffers;
+using System;
+using System.Buffers;
 using System.Text;
 using Xunit;
 using Xunit.Abstractions;
@@ -11,29 +12,33 @@ public class AddRuneTests {
 
 	public AddRuneTests (ITestOutputHelper output)
 	{
+		ConsoleDriver.RunningUnitTests = true;
 		this._output = output;
 	}
 
-	[Fact]
-	public void AddRune ()
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	[InlineData (typeof (CursesDriver))]
+	[InlineData (typeof (WindowsDriver))]
+	public void AddRune (Type driverType)
 	{
-
-		var driver = new FakeDriver ();
-		Application.Init (driver);
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
 		driver.Init ();
 
+		driver.Rows = 25;
+		driver.Cols = 80;
+		driver.Init ();
 		driver.AddRune (new Rune ('a'));
 		Assert.Equal ((Rune)'a', driver.Contents [0, 0].Rune);
 
 		driver.End ();
-		Application.Shutdown ();
 	}
 
 	[Fact]
 	public void AddRune_InvalidLocation_DoesNothing ()
 	{
 		var driver = new FakeDriver ();
-		Application.Init (driver);
 		driver.Init ();
 
 		driver.Move (driver.Cols, driver.Rows);
@@ -46,14 +51,12 @@ public class AddRuneTests {
 		}
 
 		driver.End ();
-		Application.Shutdown ();
 	}
 
 	[Fact]
 	public void AddRune_MovesToNextColumn ()
 	{
 		var driver = new FakeDriver ();
-		Application.Init (driver);
 		driver.Init ();
 
 		driver.AddRune ('a');
@@ -87,14 +90,12 @@ public class AddRuneTests {
 		}
 
 		driver.End ();
-		Application.Shutdown ();
 	}
 
 	[Fact]
 	public void AddRune_MovesToNextColumn_Wide ()
 	{
 		var driver = new FakeDriver ();
-		Application.Init (driver);
 		driver.Init ();
 
 		// 🍕 Slice of Pizza "\U0001F355"
@@ -134,15 +135,12 @@ public class AddRuneTests {
 		//}
 
 		driver.End ();
-		Application.Shutdown ();
 	}
 
-
 	[Fact]
 	public void AddRune_Accented_Letter_With_Three_Combining_Unicode_Chars ()
 	{
 		var driver = new FakeDriver ();
-		Application.Init (driver);
 		driver.Init ();
 
 		var expected = new Rune ('ắ');
@@ -189,6 +187,5 @@ public class AddRuneTests {
 		//		TestHelpers.AssertDriverContentsWithFrameAre (@"
 		//ắ", output);
 		driver.End ();
-		Application.Shutdown ();
 	}
 }

+ 83 - 102
UnitTests/ConsoleDrivers/ConsoleDriverTests.cs

@@ -20,53 +20,33 @@ namespace Terminal.Gui.DriverTests {
 
 		[Theory]
 		[InlineData (typeof (FakeDriver))]
-		//[InlineData (typeof (NetDriver))]
-		//[InlineData (typeof (CursesDriver))]
-		//[InlineData (typeof (WindowsDriver))]
+		[InlineData (typeof (NetDriver))]
+		[InlineData (typeof (CursesDriver))]
+		[InlineData (typeof (WindowsDriver))]
 		public void Init_Inits (Type driverType)
 		{
 			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-			driver.Init ();
-
-			Assert.Equal (80, Console.BufferWidth);
-			Assert.Equal (25, Console.BufferHeight);
+			var ml = driver.Init ();
+			Assert.NotNull (ml);
+			Assert.NotNull (driver.Clipboard);
+			Console.ForegroundColor = ConsoleColor.Red;
+			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
+			Console.BackgroundColor = ConsoleColor.Green;
+			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
 
-			// MockDriver is always 80x25
-			Assert.Equal (Console.BufferWidth, driver.Cols);
-			Assert.Equal (Console.BufferHeight, driver.Rows);
 			driver.End ();
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
 		}
 
 		[Theory]
 		[InlineData (typeof (FakeDriver))]
-		//[InlineData (typeof (NetDriver))]
-		//[InlineData (typeof (CursesDriver))]
-		//[InlineData (typeof (WindowsDriver))]
+		[InlineData (typeof (NetDriver))]
+		[InlineData (typeof (CursesDriver))]
+		[InlineData (typeof (WindowsDriver))]
 		public void End_Cleans_Up (Type driverType)
 		{
 			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
 			driver.Init ();
-
-			Console.ForegroundColor = ConsoleColor.Red;
-			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
-
-			Console.BackgroundColor = ConsoleColor.Green;
-			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
-			driver.Move (2, 3);
-
 			driver.End ();
-			Assert.Equal (0, Console.CursorLeft);
-			Assert.Equal (0, Console.CursorTop);
-			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
-			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
 		}
 
 		[Theory]
@@ -199,88 +179,89 @@ namespace Terminal.Gui.DriverTests {
 		
 		[Theory]
 		[InlineData (typeof (FakeDriver))]
+		[InlineData (typeof (NetDriver))]
+		[InlineData (typeof (CursesDriver))]
+		[InlineData (typeof (WindowsDriver))]
 		public void TerminalResized_Simulation (Type driverType)
 		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
+			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+			driver?.Init ();
+			driver.Cols = 80;
+			driver.Rows = 25;
+			
 			var wasTerminalResized = false;
-			Application.SizeChanging += (s, e) => {
+			driver.SizeChanged += (s, e) => {
 				wasTerminalResized = true;
 				Assert.Equal (120, e.Size.Width);
 				Assert.Equal (40, e.Size.Height);
 			};
 
-			Assert.Equal (80, Console.BufferWidth);
-			Assert.Equal (25, Console.BufferHeight);
-
-			// MockDriver is by default 80x25
-			Assert.Equal (Console.BufferWidth, driver.Cols);
-			Assert.Equal (Console.BufferHeight, driver.Rows);
+			Assert.Equal (80, driver.Cols);
+			Assert.Equal (25, driver.Rows);
 			Assert.False (wasTerminalResized);
 
-			// MockDriver will now be sets to 120x40
-			driver.SetBufferSize (120, 40);
-			Assert.Equal (120, Application.Driver.Cols);
-			Assert.Equal (40, Application.Driver.Rows);
+			driver.Cols = 120;
+			driver.Rows = 40;
+			driver.OnSizeChanged (new SizeChangedEventArgs(new Size(driver.Cols, driver.Rows)));
+			Assert.Equal (120, driver.Cols);
+			Assert.Equal (40, driver.Rows);
 			Assert.True (wasTerminalResized);
-
-
-			Application.Shutdown ();
+			driver.End ();
 		}
 
 		// Disabled due to test error - Change Task.Delay to an await
-//		[Fact, AutoInitShutdown]
-//		public void Write_Do_Not_Change_On_ProcessKey ()
-//		{
-//			var win = new Window ();
-//			Application.Begin (win);
-//			((FakeDriver)Application.Driver).SetBufferSize (20, 8);
-
-//			System.Threading.Tasks.Task.Run (() => {
-//				System.Threading.Tasks.Task.Delay (500).Wait ();
-//				Application.Invoke (() => {
-//					var lbl = new Label ("Hello World") { X = Pos.Center () };
-//					var dlg = new Dialog ();
-//					dlg.Add (lbl);
-//					Application.Begin (dlg);
-
-//					var expected = @"
-//┌──────────────────┐
-//│┌───────────────┐ │
-//││  Hello World  │ │
-//││               │ │
-//││               │ │
-//││               │ │
-//│└───────────────┘ │
-//└──────────────────┘
-//";
-
-//					var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-//					Assert.Equal (new Rect (0, 0, 20, 8), pos);
-
-//					Assert.True (dlg.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())));
-//					dlg.Draw ();
-
-//					expected = @"
-//┌──────────────────┐
-//│┌───────────────┐ │
-//││  Hello World  │ │
-//││               │ │
-//││               │ │
-//││               │ │
-//│└───────────────┘ │
-//└──────────────────┘
-//";
-
-//					pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-//					Assert.Equal (new Rect (0, 0, 20, 8), pos);
-
-//					win.RequestStop ();
-//				});
-//			});
-
-//			Application.Run (win);
-//			Application.Shutdown ();
-//		}
+		//		[Fact, AutoInitShutdown]
+		//		public void Write_Do_Not_Change_On_ProcessKey ()
+		//		{
+		//			var win = new Window ();
+		//			Application.Begin (win);
+		//			((FakeDriver)Application.Driver).SetBufferSize (20, 8);
+
+		//			System.Threading.Tasks.Task.Run (() => {
+		//				System.Threading.Tasks.Task.Delay (500).Wait ();
+		//				Application.Invoke (() => {
+		//					var lbl = new Label ("Hello World") { X = Pos.Center () };
+		//					var dlg = new Dialog ();
+		//					dlg.Add (lbl);
+		//					Application.Begin (dlg);
+
+		//					var expected = @"
+		//┌──────────────────┐
+		//│┌───────────────┐ │
+		//││  Hello World  │ │
+		//││               │ │
+		//││               │ │
+		//││               │ │
+		//│└───────────────┘ │
+		//└──────────────────┘
+		//";
+
+		//					var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+		//					Assert.Equal (new Rect (0, 0, 20, 8), pos);
+
+		//					Assert.True (dlg.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())));
+		//					dlg.Draw ();
+
+		//					expected = @"
+		//┌──────────────────┐
+		//│┌───────────────┐ │
+		//││  Hello World  │ │
+		//││               │ │
+		//││               │ │
+		//││               │ │
+		//│└───────────────┘ │
+		//└──────────────────┘
+		//";
+
+		//					pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+		//					Assert.Equal (new Rect (0, 0, 20, 8), pos);
+
+		//					win.RequestStop ();
+		//				});
+		//			});
+
+		//			Application.Run (win);
+		//			Application.Shutdown ();
+		//		}
 	}
 }

+ 58 - 23
UnitTests/ConsoleDrivers/ContentsTests.cs

@@ -2,6 +2,7 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -14,54 +15,90 @@ public class ContentsTests {
 
 	public ContentsTests (ITestOutputHelper output)
 	{
+		ConsoleDriver.RunningUnitTests = true;
 		this.output = output;
 	}
 
+
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (CursesDriver))] // TODO: Uncomment when #2796 and #2615 are fixed
+	//[InlineData (typeof (WindowsDriver))] // TODO: Uncomment when #2610 is fixed
+	public void AddStr_Combining_Character_1st_Column (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		driver.Init ();
+		var expected = "\u0301!";
+		driver.AddStr ("\u0301!"); // acute accent + exclamation mark
+		TestHelpers.AssertDriverContentsAre (expected, output, driver);
+
+		driver.End ();
+	}
+
 	[Theory]
 	[InlineData (typeof (FakeDriver))]
-	//[InlineData (typeof (NetDriver))]
-	//[InlineData (typeof (CursesDriver))]
-	//[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (CursesDriver))] // TODO: Uncomment when #2796 and #2615 are fixed
+	//[InlineData (typeof (WindowsDriver))] // TODO: Uncomment when #2610 is fixed
 	public void AddStr_With_Combining_Characters (Type driverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-		Application.Init (driver);
-		// driver.Init (null);
+		driver.Init ();
 
 		var acuteaccent = new System.Text.Rune (0x0301); // Combining acute accent (é)
 		var combined = "e" + acuteaccent;
 		var expected = "é";
 
 		driver.AddStr (combined);
-		TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-
-#if false // Disabled Until #2616 is fixed
+		TestHelpers.AssertDriverContentsAre (expected, output, driver);
 
 		// 3 char combine
-		// a + ogonek + acute = <U+0061, U+0328, U+0301> ( ǫ́ )
+		// a + ogonek + acute = <U+0061, U+0328, U+0301> ( ą́ )
 		var ogonek = new System.Text.Rune (0x0328); // Combining ogonek (a small hook or comma shape)
 		combined = "a" + ogonek + acuteaccent;
-		expected = "ǫ́";
+		expected = ("a" + ogonek).Normalize(NormalizationForm.FormC); // See Issue #2616
 
 		driver.Move (0, 0);
 		driver.AddStr (combined);
-		TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+		TestHelpers.AssertDriverContentsAre (expected, output, driver);
 
-#endif
-		
-		// Shutdown must be called to safely clean up Application if Init has been called
-		Application.Shutdown ();
+		// e + ogonek + acute = <U+0061, U+0328, U+0301> ( ę́́ )
+		combined = "e" + ogonek + acuteaccent;
+		expected = ("e" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+
+		driver.Move (0, 0);
+		driver.AddStr (combined);
+		TestHelpers.AssertDriverContentsAre (expected, output, driver);
+
+		// i + ogonek + acute = <U+0061, U+0328, U+0301> ( į́́́ )
+		combined = "i" + ogonek + acuteaccent;
+		expected = ("i" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+
+		driver.Move (0, 0);
+		driver.AddStr (combined);
+		TestHelpers.AssertDriverContentsAre (expected, output, driver);
+
+		// u + ogonek + acute = <U+0061, U+0328, U+0301> ( ų́́́́ )
+		combined = "u" + ogonek + acuteaccent;
+		expected = ("u" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+
+		driver.Move (0, 0);
+		driver.AddStr (combined);
+		TestHelpers.AssertDriverContentsAre (expected, output, driver);
+
+		driver.End ();
 	}
 
 	[Theory]
 	[InlineData (typeof (FakeDriver))]
-	//[InlineData (typeof (NetDriver))]
-	//[InlineData (typeof (CursesDriver))]
-	//[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (NetDriver))]
+	[InlineData (typeof (CursesDriver))]
+	[InlineData (typeof (WindowsDriver))]
 	public void Move_Bad_Coordinates (Type driverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-		Application.Init (driver);
+		driver.Init ();
 
 		Assert.Equal (0, driver.Col);
 		Assert.Equal (0, driver.Row);
@@ -85,13 +122,11 @@ public class ContentsTests {
 		driver.Move (500, 500);
 		Assert.Equal (500, driver.Col);
 		Assert.Equal (500, driver.Row);
-
-		// Shutdown must be called to safely clean up Application if Init has been called
-		Application.Shutdown ();
+		driver.End ();
 	}
 
 	// TODO: Add these unit tests
-	
+
 	// AddRune moves correctly
 
 	// AddRune with wide characters are handled correctly

+ 2 - 9
UnitTests/ConsoleDrivers/DriverColorTests.cs

@@ -19,7 +19,7 @@ namespace Terminal.Gui.DriverTests {
 		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);
@@ -34,8 +34,7 @@ namespace Terminal.Gui.DriverTests {
 			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
 			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
 
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
+			driver.End ();
 		}
 
 
@@ -52,9 +51,6 @@ namespace Terminal.Gui.DriverTests {
 			Assert.Equal (expectedSetting, driver.SupportsTrueColor);
 
 			driver.End ();
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
 		}
 
 		[Theory]
@@ -71,9 +67,6 @@ namespace Terminal.Gui.DriverTests {
 			Assert.True (driver.Force16Colors);
 
 			driver.End ();
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
 		}
 	}
 }

+ 0 - 6
UnitTests/Drawing/AttributeTests.cs

@@ -90,7 +90,6 @@ public class AttributeTests {
 	public void Constuctors_Constuct ()
 	{
 		var driver = new FakeDriver ();
-		Application.Init (driver);
 		driver.Init ();
 
 		// Test parameterless constructor
@@ -127,7 +126,6 @@ public class AttributeTests {
 		Assert.Equal (bg, attr.Background);
 
 		driver.End ();
-		Application.Shutdown ();
 	}
 
 	[Fact]
@@ -196,7 +194,6 @@ public class AttributeTests {
 	public void Implicit_Assign ()
 	{
 		var driver = new FakeDriver ();
-		Application.Init (driver);
 		driver.Init ();
 
 		var attr = new Attribute ();
@@ -216,7 +213,6 @@ public class AttributeTests {
 		Assert.Equal (value, attr.PlatformColor);
 
 		driver.End ();
-		Application.Shutdown ();
 	}
 
 	[Fact]
@@ -237,7 +233,6 @@ public class AttributeTests {
 	public void Make_Creates ()
 	{
 		var driver = new FakeDriver ();
-		Application.Init (driver);
 		driver.Init ();
 
 		var fg = new Color ();
@@ -252,7 +247,6 @@ public class AttributeTests {
 		Assert.Equal (bg, attr.Background);
 
 		driver.End ();
-		Application.Shutdown ();
 	}
 
 	[Fact]

+ 2 - 2
UnitTests/FileServices/FileDialogTests.cs

@@ -401,7 +401,7 @@ namespace Terminal.Gui.FileServicesTests {
 │{CM.Glyphs.LeftBracket} ►► {CM.Glyphs.RightBracket} Enter Search                                   {CM.Glyphs.LeftBracket} OK {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Cancel {CM.Glyphs.RightBracket}  │
 └─────────────────────────────────────────────────────────────────────────┘
 ";
-			TestHelpers.AssertDriverContentsAre (expected, output, true);
+			TestHelpers.AssertDriverContentsAre (expected, output, ignoreLeadingWhitespace: true);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -437,7 +437,7 @@ namespace Terminal.Gui.FileServicesTests {
 │{CM.Glyphs.LeftBracket} ►► {CM.Glyphs.RightBracket} Enter Search                                   {CM.Glyphs.LeftBracket} OK {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Cancel {CM.Glyphs.RightBracket}  │
 └─────────────────────────────────────────────────────────────────────────┘
 ";
-			TestHelpers.AssertDriverContentsAre (expected, output, true);
+			TestHelpers.AssertDriverContentsAre (expected, output, ignoreLeadingWhitespace: true);
 		}
 
 

+ 32 - 11
UnitTests/TestHelpers.cs

@@ -147,18 +147,24 @@ partial class TestHelpers {
 	private static partial Regex LeadingWhitespaceRegEx ();
 
 #pragma warning disable xUnit1013 // Public method should be marked as test
-	public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelper output, bool ignoreLeadingWhitespace = false)
+	/// <summary>
+	/// Asserts that the driver contents match the expected contents, optionally ignoring any trailing whitespace.
+	/// </summary>
+	/// <param name="expectedLook"></param>
+	/// <param name="output"></param>
+	/// <param name="driver">The ConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
+	/// <param name="ignoreLeadingWhitespace"></param>
+	public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelper output, ConsoleDriver driver = null, bool ignoreLeadingWhitespace = false)
 	{
 #pragma warning restore xUnit1013 // Public method should be marked as test
 
 		var sb = new StringBuilder ();
-		var driver = (FakeDriver)Application.Driver;
+		driver ??= Application.Driver;
 
 		var contents = driver.Contents;
 
 		for (int r = 0; r < driver.Rows; r++) {
 			for (int c = 0; c < driver.Cols; c++) {
-				// TODO: Remove hard-coded [0] once combining pairs is supported
 				Rune rune = contents [r, c].Rune;
 				if (rune.DecodeSurrogatePair (out char [] spair)) {
 					sb.Append (spair);
@@ -168,6 +174,10 @@ partial class TestHelpers {
 				if (rune.GetColumns () > 1) {
 					c++;
 				}
+				// See Issue #2616
+				//foreach (var combMark in contents [r, c].CombiningMarks) {
+				//	sb.Append ((char)combMark.Value);
+				//}
 			}
 			sb.AppendLine ();
 		}
@@ -198,11 +208,18 @@ partial class TestHelpers {
 		Assert.Equal (expectedLook, actualLook);
 	}
 
-	public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestOutputHelper output)
+	/// <summary>
+	/// Asserts that the driver contents are equal to the expected look, and that the cursor is at the expected position.
+	/// </summary>
+	/// <param name="expectedLook"></param>
+	/// <param name="output"></param>
+	/// <param name="driver">The ConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
+	/// <returns></returns>
+	public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestOutputHelper output, ConsoleDriver driver = null)
 	{
 		var lines = new List<List<Rune>> ();
 		var sb = new StringBuilder ();
-		var driver = Application.Driver;
+		driver ??= Application.Driver;
 		var x = -1;
 		var y = -1;
 		var w = -1;
@@ -213,7 +230,6 @@ partial class TestHelpers {
 		for (var r = 0; r < driver.Rows; r++) {
 			var runes = new List<Rune> ();
 			for (var c = 0; c < driver.Cols; c++) {
-				// TODO: Remove hard-coded [0] once combining pairs is supported
 				Rune rune = contents [r, c].Rune;
 				if (rune != (Rune)' ') {
 					if (x == -1) {
@@ -232,6 +248,10 @@ partial class TestHelpers {
 					h = r - y + 1;
 				}
 				if (x > -1) runes.Add (rune);
+				// See Issue #2616
+				//foreach (var combMark in contents [r, c].CombiningMarks) {
+				//	runes.Add (combMark);
+				//}
 			}
 			if (runes.Count > 0) lines.Add (runes);
 		}
@@ -290,15 +310,16 @@ partial class TestHelpers {
 	/// test method will verify those colors were used in the row/col of the console during rendering
 	/// </summary>
 	/// <param name="expectedLook">Numbers between 0 and 9 for each row/col of the console.  Must be valid indexes of <paramref name="expectedColors"/></param>
+	/// <param name="driver">The ConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
 	/// <param name="expectedColors"></param>
-	public static void AssertDriverColorsAre (string expectedLook, params Attribute [] expectedColors)
+	public static void AssertDriverColorsAre (string expectedLook, ConsoleDriver driver = null, params Attribute [] expectedColors)
 	{
 #pragma warning restore xUnit1013 // Public method should be marked as test
 
 		if (expectedColors.Length > 10) throw new ArgumentException ("This method only works for UIs that use at most 10 colors");
 
 		expectedLook = expectedLook.Trim ();
-		var driver = (FakeDriver)Application.Driver;
+		driver ??= Application.Driver;
 
 		var contents = driver.Contents;
 
@@ -331,11 +352,11 @@ partial class TestHelpers {
 	/// If one or more of the expected colors are not used then the failure will output both
 	/// the colors that were found to be used and which of your expectations was not met.
 	/// </summary>
+	/// <param name="driver">if null uses <see cref="Application.Driver"/></param>
 	/// <param name="expectedColors"></param>
-	internal static void AssertDriverUsedColors (params Attribute [] expectedColors)
+	internal static void AssertDriverUsedColors (ConsoleDriver driver = null, params Attribute [] expectedColors)
 	{
-		var driver = (FakeDriver)Application.Driver;
-
+		driver ??= Application.Driver;
 		var contents = driver.Contents;
 
 		var toFind = expectedColors.ToList ();

+ 3 - 3
UnitTests/View/DrawTests.cs

@@ -58,7 +58,7 @@ namespace Terminal.Gui.ViewsTests {
 0020000000
 0000000000
 0111000000
-0000000000", expectedColors);
+0000000000", driver: Application.Driver, expectedColors);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -106,7 +106,7 @@ namespace Terminal.Gui.ViewsTests {
 0022000000
 0000000000
 0111000000
-0000000000", expectedColors);
+0000000000", driver: Application.Driver, expectedColors);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -147,7 +147,7 @@ t     ", output);
 0
 0
 0
-0", new Attribute [] { Colors.Base.Normal });
+0", driver: Application.Driver, new Attribute [] { Colors.Base.Normal });
 		}
 
 		[Fact, AutoInitShutdown]

+ 3 - 3
UnitTests/View/ViewTests.cs

@@ -942,11 +942,11 @@ cccccccccccccccccccc", output);
 			if (label) {
 				TestHelpers.AssertDriverColorsAre (@"
 111111111111111111110
-111111111111111111110", attributes);
+111111111111111111110", driver: Application.Driver, attributes);
 			} else {
 				TestHelpers.AssertDriverColorsAre (@"
 222222222222222222220
-111111111111111111110", attributes);
+111111111111111111110", driver: Application.Driver, attributes);
 			}
 
 			if (label) {
@@ -958,7 +958,7 @@ cccccccccccccccccccc", output);
 				Application.Refresh ();
 				TestHelpers.AssertDriverColorsAre (@"
 222222222222222222220
-111111111111111111110", attributes);
+111111111111111111110", driver: Application.Driver, attributes);
 			}
 			Application.End (runState);
 		}

+ 6 - 6
UnitTests/Views/ComboBoxTests.cs

@@ -836,7 +836,7 @@ Three ", output);
 000000
 222222
 222222
-222222", attributes);
+222222", driver: Application.Driver, attributes);
 
 			Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
 			Assert.Equal ("", selected);
@@ -848,7 +848,7 @@ Three ", output);
 000000
 222222
 000002
-222222", attributes);
+222222", driver: Application.Driver, attributes);
 
 			Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
 			Assert.Equal ("", selected);
@@ -860,7 +860,7 @@ Three ", output);
 000000
 222222
 222222
-000002", attributes);
+000002", driver: Application.Driver, attributes);
 
 			Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			Assert.Equal ("Three", selected);
@@ -878,7 +878,7 @@ Three ", output);
 000000
 222222
 222222
-000002", attributes);
+000002", driver: Application.Driver, attributes);
 
 			Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ())));
 			Assert.Equal ("Three", selected);
@@ -890,7 +890,7 @@ Three ", output);
 000000
 222222
 000002
-111112", attributes);
+111112", driver: Application.Driver, attributes);
 
 			Assert.True (cb.Subviews [1].ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ())));
 			Assert.Equal ("Three", selected);
@@ -902,7 +902,7 @@ Three ", output);
 000000
 000002
 222222
-111112", attributes);
+111112", driver: Application.Driver, attributes);
 
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ())));
 			Assert.Equal ("Three", selected);

+ 4 - 4
UnitTests/Views/MenuTests.cs

@@ -1589,7 +1589,7 @@ Edit
 	    };
 
 			TestHelpers.AssertDriverColorsAre (@"
-00000000000000", attributes);
+00000000000000", driver: Application.Driver, attributes);
 
 			Assert.True (menu.MouseEvent (new MouseEvent {
 				X = 0,
@@ -1605,7 +1605,7 @@ Edit
 02222222222220
 00000000000000
 00000000000000
-00000000000000", attributes);
+00000000000000", driver: Application.Driver, attributes);
 
 			Assert.True (top.Subviews [1].MouseEvent (new MouseEvent {
 				X = 0,
@@ -1621,7 +1621,7 @@ Edit
 02222222222220
 00000000000000
 00000000000000
-00000000000000", attributes);
+00000000000000", driver: Application.Driver, attributes);
 
 			Assert.True (top.Subviews [1].MouseEvent (new MouseEvent {
 				X = 0,
@@ -1637,7 +1637,7 @@ Edit
 02222222222220
 00000000000000
 00000000000000
-00000000000000", attributes);
+00000000000000", driver: Application.Driver, attributes);
 		}
 
 		[Fact, AutoInitShutdown]

+ 4 - 4
UnitTests/Views/RuneCellTests.cs

@@ -116,12 +116,12 @@ Error   ";
 2222220000
 3333000000
 4444400000";
-			TestHelpers.AssertDriverColorsAre (expectedColor, attributes);
+			TestHelpers.AssertDriverColorsAre (expectedColor, driver: Application.Driver, attributes);
 
 			tv.WordWrap = true;
 			Application.Refresh ();
 			TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output);
-			TestHelpers.AssertDriverColorsAre (expectedColor, attributes);
+			TestHelpers.AssertDriverColorsAre (expectedColor, driver: Application.Driver, attributes);
 
 			tv.CursorPosition = new Point (6, 2);
 			tv.SelectionStartColumn = 0;
@@ -149,7 +149,7 @@ Dialogror ";
 4444444444
 4444000000
 4444444440";
-			TestHelpers.AssertDriverColorsAre (expectedColor, attributes);
+			TestHelpers.AssertDriverColorsAre (expectedColor, driver: Application.Driver, attributes);
 
 			tv.Undo ();
 			tv.CursorPosition = new Point (0, 3);
@@ -180,7 +180,7 @@ ror       ";
 4444000000
 4444440000
 4440000000";
-			TestHelpers.AssertDriverColorsAre (expectedColor, attributes);
+			TestHelpers.AssertDriverColorsAre (expectedColor, driver: Application.Driver, attributes);
 
 			Application.End (rs);
 		}

+ 10 - 10
UnitTests/Views/TableViewTests.cs

@@ -836,7 +836,7 @@ namespace Terminal.Gui.ViewsTests {
 01000
 ";
 
-			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, driver: Application.Driver, new Attribute [] {
 				// 0
 				tv.ColorScheme.Normal,				
 				// 1
@@ -883,7 +883,7 @@ namespace Terminal.Gui.ViewsTests {
 			var invertFocus = new Attribute (tv.ColorScheme.Focus.Background, tv.ColorScheme.Focus.Foreground);
 			var invertHotNormal = new Attribute (tv.ColorScheme.HotNormal.Background, tv.ColorScheme.HotNormal.Foreground);
 
-			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, driver: Application.Driver, new Attribute [] {
 				// 0
 				tv.ColorScheme.Normal,				
 				// 1
@@ -938,7 +938,7 @@ namespace Terminal.Gui.ViewsTests {
 21222
 ";
 
-			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, driver: Application.Driver, new Attribute [] {
 				// 0
 				tv.ColorScheme.Normal,				
 				// 1
@@ -971,7 +971,7 @@ namespace Terminal.Gui.ViewsTests {
 			// now we only see 2 colors used (the selected cell color and Normal
 			// rowHighlight should no longer be used because the delegate returned null
 			// (now that the cell value is 5 - which does not match the conditional)
-			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, driver: Application.Driver, new Attribute [] {
 				// 0
 				tv.ColorScheme.Normal,
 				// 1
@@ -1030,7 +1030,7 @@ namespace Terminal.Gui.ViewsTests {
 01020
 ";
 
-			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, driver: Application.Driver, new Attribute [] {
 				// 0
 				tv.ColorScheme.Normal,				
 				// 1
@@ -1063,7 +1063,7 @@ namespace Terminal.Gui.ViewsTests {
 			// now we only see 2 colors used (the selected cell color and Normal
 			// cellHighlight should no longer be used because the delegate returned null
 			// (now that the cell value is 5 - which does not match the conditional)
-			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, driver: Application.Driver, new Attribute [] {
 				// 0
 				tv.ColorScheme.Normal,				
 				// 1
@@ -2113,7 +2113,7 @@ namespace Terminal.Gui.ViewsTests {
 00000000000000000000
 01111101101111111110
 ";
-			TestHelpers.AssertDriverColorsAre (expected, new Attribute [] { tv.ColorScheme.Normal, color });
+			TestHelpers.AssertDriverColorsAre (expected, driver: Application.Driver, new Attribute [] { tv.ColorScheme.Normal, color });
 
 		}
 
@@ -2233,7 +2233,7 @@ namespace Terminal.Gui.ViewsTests {
 0111110
 0000000";
 
-			TestHelpers.AssertDriverColorsAre (expected, normal, focus);
+			TestHelpers.AssertDriverColorsAre (expected, driver: Application.Driver, normal, focus);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -2290,7 +2290,7 @@ namespace Terminal.Gui.ViewsTests {
 0101010
 0000000";
 
-			TestHelpers.AssertDriverColorsAre (expected, normal, focus);
+			TestHelpers.AssertDriverColorsAre (expected, driver: Application.Driver, normal, focus);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -2785,7 +2785,7 @@ A B C
 000000
 111111";
 
-			TestHelpers.AssertDriverColorsAre (expected, normal, focus);
+			TestHelpers.AssertDriverColorsAre (expected, driver: Application.Driver, normal, focus);
 		}
 
 		public static DataTableSource BuildTable (int cols, int rows)

+ 10 - 10
UnitTests/Views/TreeViewTests.cs

@@ -858,19 +858,19 @@ namespace Terminal.Gui.ViewsTests {
 
 
 			// Normal drawing of the tree view
-			TestHelpers.AssertDriverContentsAre (
-@"├-normal
+			TestHelpers.AssertDriverContentsAre (@"
+├-normal
 │ ├─pink
 │ └─normal
 └─pink
 ", output);
 			// Should all be the same color
-			TestHelpers.AssertDriverColorsAre (
-@"00000000
+			TestHelpers.AssertDriverColorsAre (@"
+00000000
 00000000
 0000000000
 000000
-",
+", driver: Application.Driver,
 				new [] { tv.ColorScheme.Normal, pink });
 
 			var pinkScheme = new ColorScheme {
@@ -887,20 +887,20 @@ namespace Terminal.Gui.ViewsTests {
 			tv.Draw ();
 
 			// Same text
-			TestHelpers.AssertDriverContentsAre (
-@"├-normal
+			TestHelpers.AssertDriverContentsAre (@"
+├-normal
 │ ├─pink
 │ └─normal
 └─pink
 ", output);
 			// but now the item (only not lines) appear
 			// in pink when they are the word "pink"
-			TestHelpers.AssertDriverColorsAre (
-@"00000000
+			TestHelpers.AssertDriverColorsAre (@"
+00000000
 00001111
 0000000000
 001111
-",
+", driver: Application.Driver,
 				new [] { tv.ColorScheme.Normal, pink });
 		}