Pārlūkot izejas kodu

merge with master

Charlie Kindel 5 gadi atpakaļ
vecāks
revīzija
535f7e63f9

+ 51 - 0
.github/workflows/codeql-analysis.yml

@@ -0,0 +1,51 @@
+name: "Code scanning - action"
+
+on:
+  push:
+  pull_request:
+  schedule:
+    - cron: '0 4 * * 0'
+
+jobs:
+  CodeQL-Build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v2
+      with:
+        # We must fetch at least the immediate parents so that if this is
+        # a pull request then we can checkout the head.
+        fetch-depth: 2
+
+    # If this run was triggered by a pull request event, then checkout
+    # the head of the pull request instead of the merge commit.
+    - run: git checkout HEAD^2
+      if: ${{ github.event_name == 'pull_request' }}
+      
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v1
+      # Override language selection by uncommenting this and choosing your languages
+      with:
+        languages: csharp
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v1
+
+    # ℹ️ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v1

+ 30 - 7
Terminal.Gui/Core/PosDim.cs

@@ -366,12 +366,14 @@ namespace Terminal.Gui {
 			return 0;
 		}
 
-		class DimFactor : Dim {
+		internal class DimFactor : Dim {
 			float factor;
+			bool remaining;
 
-			public DimFactor (float n)
+			public DimFactor (float n, bool r = false)
 			{
-				this.factor = n;
+				factor = n;
+				remaining = r;
 			}
 
 			internal override int Anchor (int width)
@@ -379,14 +381,19 @@ namespace Terminal.Gui {
 				return (int)(width * factor);
 			}
 
+			public bool IsFromRemaining ()
+			{
+				return remaining;
+			}
+
 			public override string ToString ()
 			{
-				return $"Dim.Factor({factor})";
+				return $"Dim.Factor(factor={factor}, remaining={remaining})";
 			}
 
 			public override int GetHashCode () => factor.GetHashCode ();
 
-			public override bool Equals (object other) => other is DimFactor f && f.factor == factor;
+			public override bool Equals (object other) => other is DimFactor f && f.factor == factor && f.remaining == remaining;
 
 		}
 
@@ -395,6 +402,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <returns>The percent <see cref="Dim"/> object.</returns>
 		/// <param name="n">A value between 0 and 100 representing the percentage.</param>
+		/// <param name="r">If <c>true</c> the Percent is computed based on the remaining space after the X/Y anchor positions. If <c>false</c> is computed based on the whole original space.</param>
 		/// <example>
 		/// This initializes a <see cref="TextField"/>that is centered horizontally, is 50% of the way down, 
 		/// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
@@ -407,12 +415,12 @@ namespace Terminal.Gui {
 		/// };
 		/// </code>
 		/// </example>
-		public static Dim Percent (float n)
+		public static Dim Percent (float n, bool r = false)
 		{
 			if (n < 0 || n > 100)
 				throw new ArgumentException ("Percent value must be between 0 and 100");
 
-			return new DimFactor (n / 100);
+			return new DimFactor (n / 100, r);
 		}
 
 		internal class DimAbsolute : Dim {
@@ -563,6 +571,11 @@ namespace Terminal.Gui {
 					return 0;
 				}
 			}
+
+			public override int GetHashCode () => Target.GetHashCode ();
+
+			public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
+
 		}
 		/// <summary>
 		/// Returns a <see cref="Dim"/> object tracks the Width of the specified <see cref="View"/>.
@@ -577,5 +590,15 @@ namespace Terminal.Gui {
 		/// <returns>The <see cref="Dim"/> of the other <see cref="View"/>.</returns>
 		/// <param name="view">The view that will be tracked.</param>
 		public static Dim Height (View view) => new DimView (view, 0);
+
+		/// <summary>Serves as the default hash function. </summary>
+		/// <returns>A hash code for the current object.</returns>
+		public override int GetHashCode () => GetHashCode ();
+
+		/// <summary>Determines whether the specified object is equal to the current object.</summary>
+		/// <param name="other">The object to compare with the current object. </param>
+		/// <returns>
+		///     <see langword="true" /> if the specified object  is equal to the current object; otherwise, <see langword="false" />.</returns>
+		public override bool Equals (object other) => other is Dim abs && abs == this;
 	}
 }

+ 22 - 24
Terminal.Gui/Core/Toplevel.cs

@@ -269,31 +269,28 @@ namespace Terminal.Gui {
 
 		internal void PositionToplevels ()
 		{
-			if (this != Application.Top) {
-				EnsureVisibleBounds (this, Frame.X, Frame.Y, out int nx, out int ny);
-				if ((nx != Frame.X || ny != Frame.Y) && LayoutStyle != LayoutStyle.Computed) {
-					X = nx;
-					Y = ny;
+			foreach (var top in Subviews) {
+				if (top is Toplevel) {
+					PositionToplevel ((Toplevel)top);
 				}
-			} else {
-				foreach (var top in Subviews) {
-					if (top is Toplevel) {
-						EnsureVisibleBounds ((Toplevel)top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
-						if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle != LayoutStyle.Computed) {
-							top.X = nx;
-							top.Y = ny;
-						}
-						if (StatusBar != null) {
-							if (ny + top.Frame.Height > Driver.Rows - 1) {
-								if (top.Height is Dim.DimFill)
-									top.Height = Dim.Fill () - 1;
-							}
-							if (StatusBar.Frame.Y != Driver.Rows - 1) {
-								StatusBar.Y = Driver.Rows - 1;
-								SetNeedsDisplay ();
-							}
-						}
-					}
+			}
+		}
+
+		private void PositionToplevel (Toplevel top)
+		{
+			EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
+			if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle != LayoutStyle.Computed) {
+				top.X = nx;
+				top.Y = ny;
+			}
+			if (StatusBar != null) {
+				if (ny + top.Frame.Height > Driver.Rows - 1) {
+					if (top.Height is Dim.DimFill)
+						top.Height = Dim.Fill () - 1;
+				}
+				if (StatusBar.Frame.Y != Driver.Rows - 1) {
+					StatusBar.Y = Driver.Rows - 1;
+					SetNeedsDisplay ();
 				}
 			}
 		}
@@ -311,6 +308,7 @@ namespace Terminal.Gui {
 					// (the bounds passed to us).
 					Clear (bounds);
 					Driver.SetAttribute (Colors.Base.Normal);
+					PositionToplevels ();
 				}
 				foreach (var view in Subviews) {
 					if (view.Frame.IntersectsWith (bounds)) {

+ 6 - 2
Terminal.Gui/Core/View.cs

@@ -1439,8 +1439,10 @@ namespace Terminal.Gui {
 					_x = x.Anchor (hostFrame.Width);
 				if (width == null)
 					w = hostFrame.Width;
+				else if (width is Dim.DimFactor && !((Dim.DimFactor)width).IsFromRemaining ())
+					w = width.Anchor (hostFrame.Width);
 				else
-					w = width.Anchor (hostFrame.Width - _x);
+					w = Math.Max (width.Anchor (hostFrame.Width - _x), 0);
 			}
 
 			if (y is Pos.PosCenter) {
@@ -1456,8 +1458,10 @@ namespace Terminal.Gui {
 					_y = y.Anchor (hostFrame.Height);
 				if (height == null)
 					h = hostFrame.Height;
+				else if (height is Dim.DimFactor && !((Dim.DimFactor)height).IsFromRemaining ())
+					h = height.Anchor (hostFrame.Height);
 				else
-					h = height.Anchor (hostFrame.Height - _y);
+					h = Math.Max (height.Anchor (hostFrame.Height - _y), 0);
 			}
 			Frame = new Rect (_x, _y, w, h);
 		}

+ 3 - 0
Terminal.Gui/Types/Point.cs

@@ -111,6 +111,9 @@ namespace Terminal.Gui
 
 		public static explicit operator Size (Point p)
 		{
+			if (p.X < 0 || p.Y < 0)
+				throw new ArgumentException ("Either Width and Height must be greater or equal to 0.");
+
 			return new Size (p.X, p.Y);
 		}
 

+ 29 - 8
Terminal.Gui/Types/Rect.cs

@@ -17,6 +17,9 @@ namespace Terminal.Gui
 	/// </summary>
 	public struct Rect
 	{
+		int width;
+		int height;
+
 		/// <summary>
 		/// Gets or sets the x-coordinate of the upper-left corner of this Rectangle structure.
 		/// </summary>
@@ -29,12 +32,26 @@ namespace Terminal.Gui
 		/// <summary>
 		/// Gets or sets the width of this Rect structure.
 		/// </summary>
-		public int Width;
+		public int Width {
+			get { return width; }
+			set {
+				if (value < 0)
+					throw new ArgumentException ("Width must be greater or equal to 0.");
+				width = value;
+			}
+		}
 
 		/// <summary>
 		/// Gets or sets the height of this Rectangle structure.
 		/// </summary>
-		public int Height;
+		public int Height {
+			get { return height; }
+			set {
+				if (value < 0)
+					throw new ArgumentException ("Height must be greater or equal to 0.");
+				height = value;
+			}
+		}
 
 		/// <summary>
 		///	Empty Shared Field
@@ -209,8 +226,10 @@ namespace Terminal.Gui
 		{
 			X = location.X;
 			Y = location.Y;
-			Width = size.Width;
-			Height = size.Height;
+			width = size.Width;
+			height = size.Height;
+			Width = width;
+			Height = height;
 		}
 
 		/// <summary>
@@ -224,10 +243,12 @@ namespace Terminal.Gui
 
 		public Rect (int x, int y, int width, int height)
 		{
-			this.X = x;
-			this.Y = y;
-			this.Width = width;
-			this.Height = height;
+			X = x;
+			Y = y;
+			this.width = width;
+			this.height = height;
+			Width = this.width;
+			Height = this.height;
 		}
 
 

+ 7 - 0
Terminal.Gui/Types/Size.cs

@@ -121,6 +121,9 @@ namespace Terminal.Gui {
 
 		public Size (int width, int height)
 		{
+			if (width < 0 || height < 0)
+				throw new ArgumentException ("Either Width and Height must be greater or equal to 0.");
+
 			this.width = width;
 			this.height = height;
 		}
@@ -152,6 +155,8 @@ namespace Terminal.Gui {
 				return width;
 			}
 			set {
+				if (value < 0)
+					throw new ArgumentException ("Width must be greater or equal to 0.");
 				width = value;
 			}
 		}
@@ -169,6 +174,8 @@ namespace Terminal.Gui {
 				return height;
 			}
 			set {
+				if (value < 0)
+					throw new ArgumentException ("Height must be greater or equal to 0.");
 				height = value;
 			}
 		}

+ 1 - 1
Terminal.Gui/Views/StatusBar.cs

@@ -90,7 +90,7 @@ namespace Terminal.Gui {
 			Width = Dim.Fill ();
 			Height = 1;
 
-			Application.Resized += (e) => {
+			LayoutComplete += (e) => {
 				X = 0;
 				Height = 1;
 				if (SuperView == null || SuperView == Application.Top) {

+ 2 - 2
UICatalog/Scenarios/Buttons.cs

@@ -210,7 +210,7 @@ namespace UICatalog {
 			var moveHotKeyBtn = new Button (mhkb) {
 				X = 2,
 				Y = Pos.Bottom (radioGroup) + 1,
-				Width = mhkb.Length + 10,
+				Width = Dim.Width (computedFrame) - 2,
 				ColorScheme = Colors.TopLevel,
 			};
 			moveHotKeyBtn.Clicked = () => {
@@ -222,7 +222,7 @@ namespace UICatalog {
 			var moveUnicodeHotKeyBtn = new Button (muhkb) {
 				X = Pos.Left (absoluteFrame) + 1,
 				Y = Pos.Bottom (radioGroup) + 1,
-				Width = muhkb.RuneCount + 30,
+				Width = Dim.Width (absoluteFrame) - 2, // BUGBUG: Not always the width isn't calculated correctly.
 				ColorScheme = Colors.TopLevel,
 			};
 			moveUnicodeHotKeyBtn.Clicked = () => {

+ 11 - 11
UICatalog/Scenarios/Keys.cs

@@ -7,11 +7,11 @@ namespace UICatalog {
 	[ScenarioCategory ("Input")]
 	class Keys : Scenario {
 
-		static List<string> _processKeyList = new List<string> ();
-		static List<string> _processHotKeyList = new List<string> ();
-		static List<string> _processColdKeyList = new List<string> ();
-
 		class TestWindow : Window {
+			public List<string> _processKeyList = new List<string> ();
+			public List<string> _processHotKeyList = new List<string> ();
+			public List<string> _processColdKeyList = new List<string> ();
+
 			public TestWindow (ustring title = null) : base (title)
 			{
 			}
@@ -112,7 +112,7 @@ namespace UICatalog {
 			var keyStrokeListView = new ListView (keyStrokelist) {
 				X = 0,
 				Y = Pos.Top (keyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent (30),
 				Height = Dim.Fill (),
 			};
 			keyStrokeListView.ColorScheme = Colors.TopLevel;
@@ -127,10 +127,10 @@ namespace UICatalog {
 
 			maxLogEntry = $"{fakeKeyPress}".Length;
 			yOffset = (Top == Application.Top ? 1 : 6);
-			var processKeyListView = new ListView (_processKeyList) {
+			var processKeyListView = new ListView (((TestWindow)Win)._processKeyList) {
 				X = Pos.Left (processKeyLogLabel),
 				Y = Pos.Top (processKeyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent(30),
 				Height = Dim.Fill (),
 			};
 			processKeyListView.ColorScheme = Colors.TopLevel;
@@ -145,10 +145,10 @@ namespace UICatalog {
 			Win.Add (processHotKeyLogLabel);
 
 			yOffset = (Top == Application.Top ? 1 : 6);
-			var processHotKeyListView = new ListView (_processHotKeyList) {
+			var processHotKeyListView = new ListView (((TestWindow)Win)._processHotKeyList) {
 				X = Pos.Left (processHotKeyLogLabel),
 				Y = Pos.Top (processHotKeyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent (20),
 				Height = Dim.Fill (),
 			};
 			processHotKeyListView.ColorScheme = Colors.TopLevel;
@@ -163,10 +163,10 @@ namespace UICatalog {
 			Win.Add (processColdKeyLogLabel);
 
 			yOffset = (Top == Application.Top ? 1 : 6);
-			var processColdKeyListView = new ListView (_processColdKeyList) {
+			var processColdKeyListView = new ListView (((TestWindow)Win)._processColdKeyList) {
 				X = Pos.Left (processColdKeyLogLabel),
 				Y = Pos.Top (processColdKeyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent (20),
 				Height = Dim.Fill (),
 			};
 

+ 2 - 2
UICatalog/Scenarios/ListsAndCombos.cs

@@ -42,14 +42,14 @@ namespace UICatalog.Scenarios {
 			var lbComboBox = new Label ("ComboBox") {
 				ColorScheme = Colors.TopLevel,
 				X = Pos.Right (lbListView) + 1,
-				Width = Dim.Percent(60)
+				Width = Dim.Percent(40)
 			};
 
 			var comboBox = new ComboBox () {
 				X = Pos.Right (listview) + 1,
 				Y = Pos.Bottom (lbListView) + 1,
 				Height = Dim.Fill (2),
-				Width = Dim.Percent(60)
+				Width = Dim.Percent(40)
 			};
 			comboBox.SetSource (items);
 

+ 4 - 2
UICatalog/UICatalog.cs

@@ -87,6 +87,8 @@ namespace UICatalog {
 				scenario.Run ();
 				scenario = GetScenarioToRun ();
 			}
+			if (!_top.Running)
+				Application.Shutdown (true);
 		}
 
 		/// <summary>
@@ -118,8 +120,8 @@ namespace UICatalog {
 				}
 			};
 
-			Application.Run (_top, true);
-			Application.Shutdown ();
+			Application.Run (_top, false);
+			Application.Shutdown (false);
 			return _runningScenario;
 		}
 

+ 23 - 12
UnitTests/DimTests.cs

@@ -85,8 +85,8 @@ namespace Terminal.Gui {
 
 			var dim1 = Dim.Width (view1);
 			var dim2 = Dim.Width (view1);
-			// BUGBUG: Dim.Width should support Equals() and this should change to Euqal.
-			Assert.NotEqual (dim1, dim2);
+			// FIXED: Dim.Width should support Equals() and this should change to Equal.
+			Assert.Equal (dim1, dim2);
 
 			dim2 = Dim.Width (view2);
 			Assert.NotEqual (dim1, dim2);
@@ -96,18 +96,19 @@ namespace Terminal.Gui {
 			testRect2 = new Rect (0, 1, 2, 3);
 			dim1 = Dim.Width (view1);
 			dim2 = Dim.Width (view1);
-			// BUGBUG: Dim.Width should support Equals() and this should change to Euqal.
-			Assert.NotEqual (dim1, dim2);
+			// FIXED: Dim.Width should support Equals() and this should change to Equal.
+			Assert.Equal (dim1, dim2);
 
-			testRect1 = new Rect (0, -1, -2, -3);
+			Assert.Throws<ArgumentException> (() => new Rect (0, -1, -2, -3));
+			testRect1 = new Rect (0, -1, 2, 3);
 			view1 = new View (testRect1);
-			testRect2 = new Rect (0, -1, -2, -3);
+			testRect2 = new Rect (0, -1, 2, 3);
 			dim1 = Dim.Width (view1);
 			dim2 = Dim.Width (view1);
-			// BUGBUG: Dim.Width should support Equals() and this should change to Euqal.
-			Assert.NotEqual (dim1, dim2);
+			// FIXED: Dim.Width should support Equals() and this should change to Equal.
+			Assert.Equal (dim1, dim2);
 
-			testRect1 = new Rect (0, -1, -2, -3);
+			testRect1 = new Rect (0, -1, 2, 3);
 			view1 = new View (testRect1);
 			testRect2 = Rect.Empty;
 			view2 = new View (testRect2);
@@ -166,13 +167,13 @@ namespace Terminal.Gui {
 		{
 			float f = 0;
 			var dim = Dim.Percent (f);
-			Assert.Equal ($"Dim.Factor({f / 100:0.###})", dim.ToString ());
+			Assert.Equal ($"Dim.Factor(factor={f / 100:0.###}, remaining={false})", dim.ToString ());
 			f = 0.5F;
 			dim = Dim.Percent (f);
-			Assert.Equal ($"Dim.Factor({f / 100:0.###})", dim.ToString ());
+			Assert.Equal ($"Dim.Factor(factor={f / 100:0.###}, remaining={false})", dim.ToString ());
 			f = 100;
 			dim = Dim.Percent (f);
-			Assert.Equal ($"Dim.Factor({f / 100:0.###})", dim.ToString ());
+			Assert.Equal ($"Dim.Factor(factor={f / 100:0.###}, remaining={false})", dim.ToString ());
 		}
 
 		[Fact]
@@ -199,6 +200,16 @@ namespace Terminal.Gui {
 			dim2 = Dim.Percent (n2);
 			Assert.Equal (dim1, dim2);
 
+			n1 = n2 = 0.3f;
+			dim1 = Dim.Percent (n1, true);
+			dim2 = Dim.Percent (n2, true);
+			Assert.Equal (dim1, dim2);
+
+			n1 = n2 = 0.3f;
+			dim1 = Dim.Percent (n1);
+			dim2 = Dim.Percent (n2, true);
+			Assert.NotEqual (dim1, dim2);
+
 			n1 = 0;
 			n2 = 1;
 			dim1 = Dim.Percent (n1);

+ 78 - 0
UnitTests/PointTests.cs

@@ -0,0 +1,78 @@
+using System;
+using Xunit;
+
+namespace Terminal.Gui {
+	public class PointTests {
+		[Fact]
+		public void Point_New ()
+		{
+			var point = new Point ();
+			Assert.True (point.IsEmpty);
+
+			point = new Point (new Size ());
+			Assert.True (point.IsEmpty);
+
+			point = new Point (1, 2);
+			Assert.False (point.IsEmpty);
+
+			point = new Point (-1, -2);
+			Assert.False (point.IsEmpty);
+		}
+
+		[Fact]
+		public void Point__SetsValue ()
+		{
+			var point = new Point () {
+				X = 0,
+				Y = 0
+			};
+			Assert.True (point.IsEmpty);
+
+			point = new Point () {
+				X = 1,
+				Y = 2
+			};
+			Assert.False (point.IsEmpty);
+
+			point = new Point () {
+				X = -1,
+				Y = -2
+			};
+			Assert.False (point.IsEmpty);
+		}
+
+		[Fact]
+		public void Point_Equals ()
+		{
+			var point1 = new Point ();
+			var point2 = new Point ();
+			Assert.Equal (point1, point2);
+
+			point1 = new Point (1, 2);
+			point2 = new Point (1, 2);
+			Assert.Equal (point1, point2);
+
+			point1 = new Point (1, 2);
+			point2 = new Point (0, 2);
+			Assert.NotEqual (point1, point2);
+
+			point1 = new Point (1, 2);
+			point2 = new Point (0, 3);
+			Assert.NotEqual (point1, point2);
+		}
+
+		[Fact]
+		public void Point_Size ()
+		{
+			var point = new Point (1, 2);
+			var size = (Size)point;
+			Assert.False (size.IsEmpty);
+
+			point = new Point (-1, 2);
+			Action action = () => size = (Size)point;
+			var ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Either Width and Height must be greater or equal to 0.", ex.Message);
+
+		}
+	}
+}

+ 114 - 0
UnitTests/RectTests.cs

@@ -0,0 +1,114 @@
+using System;
+using Xunit;
+
+namespace Terminal.Gui {
+	public class RectTests {
+		[Fact]
+		public void Rect_New ()
+		{
+			var rect = new Rect ();
+			Assert.True (rect.IsEmpty);
+
+			rect = new Rect (new Point (), new Size ());
+			Assert.True (rect.IsEmpty);
+
+			rect = new Rect (1, 2, 3, 4);
+			Assert.False (rect.IsEmpty);
+
+			rect = new Rect (-1, -2, 3, 4);
+			Assert.False (rect.IsEmpty);
+
+			Action action = () => new Rect (1, 2, -3, 4);
+			var ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+
+			action = () => new Rect (1, 2, 3, -4);
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Height must be greater or equal to 0.", ex.Message);
+
+			action = () => new Rect (1, 2, -3, -4);
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+
+		}
+
+		[Fact]
+		public void Rect__SetsValue ()
+		{
+			var rect = new Rect () {
+				X = 0,
+				Y = 0
+			};
+			Assert.True (rect.IsEmpty);
+
+			rect = new Rect () {
+				X = -1,
+				Y = -2
+			};
+			Assert.False (rect.IsEmpty);
+
+			rect = new Rect () {
+				Width = 3,
+				Height = 4
+			};
+			Assert.False (rect.IsEmpty);
+
+			rect = new Rect () {
+				X = -1,
+				Y = -2,
+				Width = 3,
+				Height = 4
+			};
+			Assert.False (rect.IsEmpty);
+
+			Action action = () => {
+				rect = new Rect () {
+					X = -1,
+					Y = -2,
+					Width = -3,
+					Height = 4
+				};
+			};
+			var ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+
+			action = () => {
+				rect = new Rect () {
+					X = -1,
+					Y = -2,
+					Width = 3,
+					Height = -4
+				};
+			};
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Height must be greater or equal to 0.", ex.Message);
+
+			action = () => {
+				rect = new Rect () {
+					X = -1,
+					Y = -2,
+					Width = -3,
+					Height = -4
+				};
+			};
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+		}
+
+		[Fact]
+		public void Rect_Equals ()
+		{
+			var rect1 = new Rect ();
+			var rect2 = new Rect ();
+			Assert.Equal (rect1, rect2);
+
+			rect1 = new Rect (1, 2, 3, 4);
+			rect2 = new Rect (1, 2, 3, 4);
+			Assert.Equal (rect1, rect2);
+
+			rect1 = new Rect (1, 2, 3, 4);
+			rect2 = new Rect (-1, 2, 3, 4);
+			Assert.NotEqual (rect1, rect2);
+		}
+	}
+}

+ 91 - 0
UnitTests/SizeTests.cs

@@ -0,0 +1,91 @@
+using System;
+using Xunit;
+
+namespace Terminal.Gui {
+	public class SizeTests {
+		[Fact]
+		public void Size_New ()
+		{
+			var size = new Size ();
+			Assert.True (size.IsEmpty);
+
+			size = new Size (new Point ());
+			Assert.True (size.IsEmpty);
+
+			size = new Size (3, 4);
+			Assert.False (size.IsEmpty);
+
+			Action action = () => new Size (-3, 4);
+			var ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Either Width and Height must be greater or equal to 0.", ex.Message);
+
+			action = () => new Size (3, -4);
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Either Width and Height must be greater or equal to 0.", ex.Message);
+
+			action = () => new Size (-3, -4);
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Either Width and Height must be greater or equal to 0.", ex.Message);
+
+		}
+
+		[Fact]
+		public void Size__SetsValue ()
+		{
+			var size = new Size () {
+				Width = 0,
+				Height = 0
+			};
+			Assert.True (size.IsEmpty);
+
+			size = new Size () {
+				Width = 3,
+				Height = 4
+			};
+			Assert.False (size.IsEmpty);
+
+			Action action = () => {
+				size = new Size () {
+					Width = -3,
+					Height = 4
+				};
+			};
+			var ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+
+			action = () => {
+				size = new Size () {
+					Width = 3,
+					Height = -4
+				};
+			};
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Height must be greater or equal to 0.", ex.Message);
+
+			action = () => {
+				size = new Size () {
+					Width = -3,
+					Height = -4
+				};
+			};
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+		}
+
+		[Fact]
+		public void Size_Equals ()
+		{
+			var size1 = new Size ();
+			var size2 = new Size ();
+			Assert.Equal (size1, size2);
+
+			size1 = new Size (3, 4);
+			size2 = new Size (3, 4);
+			Assert.Equal (size1, size2);
+
+			size1 = new Size (3, 4);
+			size2 = new Size (4, 4);
+			Assert.NotEqual (size1, size2);
+		}
+	}
+}