diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 4ca317ce0..502cfb89f 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -2,19 +2,24 @@ name: Build & Test Terminal.Gui with .NET Core on: push: - branches: [ main, develop, v2_develop ] + branches: [ v2, v2_develop ] + paths-ignore: + - '**.md' pull_request: - branches: [ main, develop, v2_develop ] - + branches: [ v2, v2_develop ] + paths-ignore: + - '**.md' + jobs: build_and_test: - runs-on: windows-latest - + runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - - name: Setup dotnet + - uses: actions/checkout@v4 + + - name: Setup .NET Core uses: actions/setup-dotnet@v4 with: dotnet-version: 8.x @@ -34,19 +39,19 @@ jobs: mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/ # Note: this step is currently not writing to the gist for some reason - - name: Create Test Coverage Badge - uses: simon-k/dotnet-code-coverage-badge@v1.0.0 - id: create_coverage_badge - with: - label: Unit Test Coverage - color: brightgreen - path: UnitTests/TestResults/coverage.opencover.xml - gist-filename: code-coverage.json - # https://gist.github.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27 - gist-id: 90ef67a684cb71db1817921a970f8d27 - gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }} + # - name: Create Test Coverage Badge + # uses: simon-k/dotnet-code-coverage-badge@v1.0.0 + # id: create_coverage_badge + # with: + # label: Unit Test Coverage + # color: brightgreen + # path: UnitTests/TestResults/coverage.opencover.xml + # gist-filename: code-coverage.json + # # https://gist.github.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27 + # gist-id: 90ef67a684cb71db1817921a970f8d27 + # gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }} - - name: Print Code Coverage - run: | - echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%" - echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}" + # - name: Print Code Coverage + # run: | + # echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%" + # echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2c80e6e82..faf867527 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,7 +25,7 @@ jobs: includePrerelease: true - name: Determine Version - uses: gittools/actions/gitversion/execute@v0 + uses: gittools/actions/gitversion/execute@v1 with: useConfigFile: true #additionalArguments: /b develop diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 9e501f22c..947cab271 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -975,7 +975,7 @@ public static partial class Application if (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ()) { - state.Toplevel.SetNeedsDisplay(); + state.Toplevel.SetNeedsDisplay (); state.Toplevel.Draw (); Driver.UpdateScreen (); @@ -1439,4 +1439,62 @@ public static partial class Application } #endregion Toplevel handling + + /// + /// Gets a string representation of the Application as rendered by . + /// + /// A string representation of the Application + public new static string ToString () + { + ConsoleDriver driver = Driver; + + if (driver is null) + { + return string.Empty; + } + + return ToString (driver); + } + + /// + /// Gets a string representation of the Application rendered by the provided . + /// + /// The driver to use to render the contents. + /// A string representation of the Application + public static string ToString (ConsoleDriver driver) + { + var sb = new StringBuilder (); + + Cell [,] contents = driver.Contents; + + for (var r = 0; r < driver.Rows; r++) + { + for (var c = 0; c < driver.Cols; c++) + { + Rune rune = contents [r, c].Rune; + + if (rune.DecodeSurrogatePair (out char [] sp)) + { + sb.Append (sp); + } + else + { + sb.Append ((char)rune.Value); + } + + if (rune.GetColumns () > 1) + { + c++; + } + + // See Issue #2616 + //foreach (var combMark in contents [r, c].CombiningMarks) { + // sb.Append ((char)combMark.Value); + //} + } + + sb.AppendLine (); + } + return sb.ToString (); + } } diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index ad684470b..532c0af8a 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System.Numerics; +using System.Text.Json.Serialization; namespace Terminal.Gui; @@ -13,28 +14,18 @@ namespace Terminal.Gui; /// frame, /// with the thickness widths subtracted. /// -/// Use the helper API ( to draw the frame with the specified thickness. +/// +/// Use the helper API ( to draw the frame with the specified thickness. +/// +/// +/// Thickness uses intenrally. As a result, there is a potential precision loss for very +/// large numbers. This is typically not an issue for UI dimensions but could be relevant in other contexts. +/// /// -public class Thickness : IEquatable +public record struct Thickness { - /// Gets or sets the width of the lower side of the rectangle. - [JsonInclude] - public int Bottom; - - /// Gets or sets the width of the left side of the rectangle. - [JsonInclude] - public int Left; - - /// Gets or sets the width of the right side of the rectangle. - [JsonInclude] - public int Right; - - /// Gets or sets the width of the upper side of the rectangle. - [JsonInclude] - public int Top; - /// Initializes a new instance of the class with all widths set to 0. - public Thickness () { } + public Thickness () { _sides = Vector4.Zero; } /// Initializes a new instance of the class with a uniform width to each side. /// @@ -56,35 +47,23 @@ public class Thickness : IEquatable Bottom = bottom; } - // TODO: add operator overloads - /// Gets an empty thickness. - public static Thickness Empty => new (0); + private Vector4 _sides; /// - /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides - /// of the rectangle to half the specified value. + /// Adds the thickness widths of another to the current , returning a + /// new . /// - public int Horizontal - { - get => Left + Right; - set => Left = Right = value / 2; - } - - /// - /// Gets the total height of the top and bottom sides of the rectangle. Sets the height of the top and bottom - /// sides of the rectangle to half the specified value. - /// - public int Vertical - { - get => Top + Bottom; - set => Top = Bottom = value / 2; - } - - // IEquitable - /// Indicates whether the current object is equal to another object of the same type. /// - /// true if the current object is equal to the other parameter; otherwise, false. - public bool Equals (Thickness other) { return other is { } && Left == other.Left && Right == other.Right && Top == other.Top && Bottom == other.Bottom; } + /// + public readonly Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); } + + /// Gets or sets the width of the lower side of the rectangle. + [JsonInclude] + public int Bottom + { + get => (int)_sides.W; + set => _sides.W = value; + } /// /// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside @@ -100,22 +79,6 @@ public class Thickness : IEquatable return outside.Contains (location) && !inside.Contains (location); } - /// - /// Adds the thickness widths of another to the current , returning a - /// new . - /// - /// - /// - public Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); } - - /// - /// Adds the thickness widths of another to another . - /// - /// - /// - /// - public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); } - /// Draws the rectangle with an optional diagnostics label. /// /// If is set to @@ -240,31 +203,8 @@ public class Thickness : IEquatable return GetInside (rect); } - /// Determines whether the specified object is equal to the current object. - /// The object to compare with the current object. - /// true if the specified object is equal to the current object; otherwise, false. - public override bool Equals (object obj) - { - //Check for null and compare run-time types. - if (obj is null || !GetType ().Equals (obj.GetType ())) - { - return false; - } - - return Equals ((Thickness)obj); - } - - /// - public override int GetHashCode () - { - var hashCode = 1380952125; - hashCode = hashCode * -1521134295 + Left.GetHashCode (); - hashCode = hashCode * -1521134295 + Right.GetHashCode (); - hashCode = hashCode * -1521134295 + Top.GetHashCode (); - hashCode = hashCode * -1521134295 + Bottom.GetHashCode (); - - return hashCode; - } + /// Gets an empty thickness. + public static Thickness Empty => new (0); /// /// Returns a rectangle describing the location and size of the inside area of with the @@ -289,23 +229,59 @@ public class Thickness : IEquatable return new (x, y, width, height); } - /// - public static bool operator == (Thickness left, Thickness right) { return EqualityComparer.Default.Equals (left, right); } + /// + /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides + /// of the rectangle to half the specified value. + /// + public int Horizontal + { + get => Left + Right; + set => Left = Right = value / 2; + } - /// - public static bool operator != (Thickness left, Thickness right) { return !(left == right); } + /// Gets or sets the width of the left side of the rectangle. + [JsonInclude] + public int Left + { + get => (int)_sides.X; + set => _sides.X = value; + } + + /// + /// Adds the thickness widths of another to another . + /// + /// + /// + /// + public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); } + + /// Gets or sets the width of the right side of the rectangle. + [JsonInclude] + public int Right + { + get => (int)_sides.Z; + set => _sides.Z = value; + } + + /// Gets or sets the width of the upper side of the rectangle. + [JsonInclude] + public int Top + { + get => (int)_sides.Y; + set => _sides.Y = value; + } /// Returns the thickness widths of the Thickness formatted as a string. /// The thickness widths as a string. public override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; } - private int validate (int width) + /// + /// Gets the total height of the top and bottom sides of the rectangle. Sets the height of the top and bottom + /// sides of the rectangle to half the specified value. + /// + public int Vertical { - if (width < 0) - { - throw new ArgumentException ("Thickness widths cannot be negative."); - } - - return width; + get => Top + Bottom; + set => Top = Bottom = value / 2; } } diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 32b68eb37..899a35986 100644 --- a/UnitTests/TestHelpers.cs +++ b/UnitTests/TestHelpers.cs @@ -1,32 +1,28 @@ -using System.Collections; -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using UICatalog; using Xunit.Abstractions; using Xunit.Sdk; namespace Terminal.Gui; -// This class enables test functions annotated with the [AutoInitShutdown] attribute to -// automatically call Application.Init at start of the test and Application.Shutdown after the -// test exits. -// -// This is necessary because a) Application is a singleton and Init/Shutdown must be called -// as a pair, and b) all unit test functions should be atomic.. +/// +/// This class enables test functions annotated with the [AutoInitShutdown] attribute to +/// automatically call Application.Init at start of the test and Application.Shutdown after the +/// test exits. +/// This is necessary because a) Application is a singleton and Init/Shutdown must be called +/// as a pair, and b) all unit test functions should be atomic.. +/// [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)] public class AutoInitShutdownAttribute : BeforeAfterTestAttribute { - private readonly Type _driverType; - /// /// Initializes a [AutoInitShutdown] attribute, which determines if/how Application.Init and Application.Shutdown /// are automatically called Before/After a test runs. /// /// If true, Application.Init will be called Before the test runs. - /// If true, Application.Shutdown will be called After the test runs. /// /// Determines which ConsoleDriver (FakeDriver, WindowsDriver, CursesDriver, NetDriver) /// will be used when Application.Init is called. If null FakeDriver will be used. Only valid if @@ -65,7 +61,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute ConfigurationManager.Locations = configLocation; } - private bool AutoInit { get; } + private readonly Type _driverType; public override void After (MethodInfo methodUnderTest) { @@ -102,6 +98,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute ConfigurationManager.Reset (); #if DEBUG_IDISPOSABLE + // Clear out any lingering Responder instances from previous tests if (Responder.Instances.Count == 0) { @@ -115,6 +112,8 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute Application.Init ((ConsoleDriver)Activator.CreateInstance (_driverType)); } } + + private bool AutoInit { get; } } [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)] @@ -178,8 +177,8 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)] public class TestDateAttribute : BeforeAfterTestAttribute { - private readonly CultureInfo _currentCulture = CultureInfo.CurrentCulture; public TestDateAttribute () { CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; } + private readonly CultureInfo _currentCulture = CultureInfo.CurrentCulture; public override void After (MethodInfo methodUnderTest) { @@ -238,12 +237,12 @@ internal partial class TestHelpers switch (match.Count) { case 0: - throw new Exception ( - $"{DriverContentsToString (driver)}\n" - + $"Expected Attribute {val} (PlatformColor = {val.Value.PlatformColor}) at Contents[{line},{c}] {contents [line, c]} ((PlatformColor = {contents [line, c].Attribute.Value.PlatformColor}) was not found.\n" - + $" Expected: {string.Join (",", expectedAttributes.Select (c => c))}\n" - + $" But Was: " - ); + throw new ( + $"{Application.ToString (driver)}\n" + + $"Expected Attribute {val} (PlatformColor = {val.Value.PlatformColor}) at Contents[{line},{c}] {contents [line, c]} ((PlatformColor = {contents [line, c].Attribute.Value.PlatformColor}) was not found.\n" + + $" Expected: {string.Join (",", expectedAttributes.Select (c => c))}\n" + + $" But Was: " + ); case > 1: throw new ArgumentException ( $"Bad value for expectedColors, {match.Count} Attributes had the same Value" @@ -255,12 +254,12 @@ internal partial class TestHelpers if (colorUsed != userExpected) { - throw new Exception ( - $"{DriverContentsToString (driver)}\n" - + $"Unexpected Attribute at Contents[{line},{c}] {contents [line, c]}.\n" - + $" Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})\n" - + $" But Was: {colorUsed} ({val})\n" - ); + throw new ( + $"{Application.ToString (driver)}\n" + + $"Unexpected Attribute at Contents[{line},{c}] {contents [line, c]}.\n" + + $" Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})\n" + + $" But Was: {colorUsed} ({val})\n" + ); } } @@ -282,7 +281,7 @@ internal partial class TestHelpers ) { #pragma warning restore xUnit1013 // Public method should be marked as test - string actualLook = DriverContentsToString (driver); + var actualLook = Application.ToString (driver ?? Application.Driver); if (string.Equals (expectedLook, actualLook)) { @@ -314,8 +313,7 @@ internal partial class TestHelpers } /// - /// Asserts that the driver contents are equal to the expected look, and that the cursor is at the expected - /// position. + /// Asserts that the driver contents are equal to the provided string. /// /// /// @@ -337,7 +335,6 @@ internal partial class TestHelpers Cell [,] contents = driver.Contents; - for (var rowIndex = 0; rowIndex < driver.Rows; rowIndex++) { List runes = []; @@ -353,7 +350,7 @@ internal partial class TestHelpers x = colIndex; y = rowIndex; - for (int i = 0; i < colIndex; i++) + for (var i = 0; i < colIndex; i++) { runes.InsertRange (i, [SpaceRune]); } @@ -433,7 +430,7 @@ internal partial class TestHelpers if (string.Equals (expectedLook, actualLook)) { - return new Rectangle (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); + return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); } // standardize line endings for the comparison @@ -453,7 +450,7 @@ internal partial class TestHelpers Assert.Equal (expectedLook, actualLook); - return new Rectangle (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); + return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); } #pragma warning disable xUnit1013 // Public method should be marked as test @@ -483,278 +480,6 @@ internal partial class TestHelpers } #pragma warning restore xUnit1013 // Public method should be marked as test - public static string DriverContentsToString (ConsoleDriver driver = null) - { - var sb = new StringBuilder (); - driver ??= Application.Driver; - - Cell [,] contents = driver.Contents; - - for (var r = 0; r < driver.Rows; r++) - { - for (var c = 0; c < driver.Cols; c++) - { - Rune rune = contents [r, c].Rune; - - if (rune.DecodeSurrogatePair (out char [] sp)) - { - sb.Append (sp); - } - else - { - sb.Append ((char)rune.Value); - } - - if (rune.GetColumns () > 1) - { - c++; - } - - // See Issue #2616 - //foreach (var combMark in contents [r, c].CombiningMarks) { - // sb.Append ((char)combMark.Value); - //} - } - - sb.AppendLine (); - } - - return sb.ToString (); - } - - //// TODO: Update all tests that use GetALlViews to use GetAllViewsTheoryData instead - ///// Gets a list of instances of all classes derived from View. - ///// List of View objects - //public static List GetAllViews () - //{ - // return typeof (View).Assembly.GetTypes () - // .Where ( - // type => type.IsClass - // && !type.IsAbstract - // && type.IsPublic - // && type.IsSubclassOf (typeof (View)) - // ) - // .Select (type => CreateView (type, type.GetConstructor (Array.Empty ()))) - // .ToList (); - //} - - //public class AllViewsData : IEnumerable - //{ - // private Lazy> data; - - // public AllViewsData () - // { - // data = new Lazy> (GetTestData); - // } - - // public IEnumerator GetEnumerator () - // { - // return data.Value.GetEnumerator (); - // } - - // IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); - - // private List GetTestData () - // { - // var viewTypes = typeof (View).Assembly - // .GetTypes () - // .Where (type => type.IsClass && !type.IsAbstract && type.IsPublic && type.IsSubclassOf (typeof (View))); - - // var testData = new List (); - - // foreach (var type in viewTypes) - // { - // var view = CreateView (type, type.GetConstructor (Array.Empty ())); - // testData.Add (new object [] { view, type.Name }); - // } - - // return testData; - // } - //} - - - /// - /// Verifies the console used all the when rendering. 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. - /// - /// if null uses - /// - internal static void AssertDriverUsedColors (ConsoleDriver driver = null, params Attribute [] expectedColors) - { - driver ??= Application.Driver; - Cell [,] contents = driver.Contents; - - List toFind = expectedColors.ToList (); - - // Contents 3rd column is an Attribute - HashSet colorsUsed = new (); - - for (var r = 0; r < driver.Rows; r++) - { - for (var c = 0; c < driver.Cols; c++) - { - Attribute? val = contents [r, c].Attribute; - - if (val.HasValue) - { - colorsUsed.Add (val.Value); - - Attribute match = toFind.FirstOrDefault (e => e == val); - - // need to check twice because Attribute is a struct and therefore cannot be null - if (toFind.Any (e => e == val)) - { - toFind.Remove (match); - } - } - } - } - - if (!toFind.Any ()) - { - return; - } - - var sb = new StringBuilder (); - sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => a.ToString ()))); - sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (a => a.ToString ()))); - - throw new Exception (sb.ToString ()); - } - - private static void AddArguments (Type paramType, List pTypes) - { - if (paramType == typeof (Rectangle)) - { - pTypes.Add (Rectangle.Empty); - } - else if (paramType == typeof (string)) - { - pTypes.Add (string.Empty); - } - else if (paramType == typeof (int)) - { - pTypes.Add (0); - } - else if (paramType == typeof (bool)) - { - pTypes.Add (true); - } - else if (paramType.Name == "IList") - { - pTypes.Add (new List ()); - } - else if (paramType.Name == "View") - { - var top = new Toplevel (); - var view = new View (); - top.Add (view); - pTypes.Add (view); - } - else if (paramType.Name == "View[]") - { - pTypes.Add (new View [] { }); - } - else if (paramType.Name == "Stream") - { - pTypes.Add (new MemoryStream ()); - } - else if (paramType.Name == "String") - { - pTypes.Add (string.Empty); - } - else if (paramType.Name == "TreeView`1[T]") - { - pTypes.Add (string.Empty); - } - else - { - pTypes.Add (null); - } - } - - public static View CreateView (Type type, ConstructorInfo ctor) - { - View view = null; - - if (type.IsGenericType && type.IsTypeDefinition) - { - List gTypes = new (); - - foreach (Type args in type.GetGenericArguments ()) - { - gTypes.Add (typeof (object)); - } - - type = type.MakeGenericType (gTypes.ToArray ()); - - Assert.IsType (type, (View)Activator.CreateInstance (type)); - } - else - { - ParameterInfo [] paramsInfo = ctor.GetParameters (); - Type paramType; - List pTypes = new (); - - if (type.IsGenericType) - { - foreach (Type args in type.GetGenericArguments ()) - { - paramType = args.GetType (); - - if (args.Name == "T") - { - pTypes.Add (typeof (object)); - } - else - { - AddArguments (paramType, pTypes); - } - } - } - - foreach (ParameterInfo p in paramsInfo) - { - paramType = p.ParameterType; - - if (p.HasDefaultValue) - { - pTypes.Add (p.DefaultValue); - } - else - { - AddArguments (paramType, pTypes); - } - } - - if (type.IsGenericType && !type.IsTypeDefinition) - { - view = (View)Activator.CreateInstance (type); - Assert.IsType (type, view); - } - else - { - view = (View)ctor.Invoke (pTypes.ToArray ()); - Assert.IsType (type, view); - } - } - - return view; - } - - public static List GetAllViewClasses () - { - return typeof (View).Assembly.GetTypes () - .Where ( - myType => myType.IsClass - && !myType.IsAbstract - && myType.IsPublic - && myType.IsSubclassOf (typeof (View)) - ) - .ToList (); - } - public static View CreateViewFromType (Type type, ConstructorInfo ctor) { View viewType = null; @@ -824,6 +549,119 @@ internal partial class TestHelpers return viewType; } + public static List GetAllViewClasses () + { + return typeof (View).Assembly.GetTypes () + .Where ( + myType => myType.IsClass + && !myType.IsAbstract + && myType.IsPublic + && myType.IsSubclassOf (typeof (View)) + ) + .ToList (); + } + + /// + /// Verifies the console used all the when rendering. 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. + /// + /// if null uses + /// + internal static void AssertDriverUsedColors (ConsoleDriver driver = null, params Attribute [] expectedColors) + { + driver ??= Application.Driver; + Cell [,] contents = driver.Contents; + + List toFind = expectedColors.ToList (); + + // Contents 3rd column is an Attribute + HashSet colorsUsed = new (); + + for (var r = 0; r < driver.Rows; r++) + { + for (var c = 0; c < driver.Cols; c++) + { + Attribute? val = contents [r, c].Attribute; + + if (val.HasValue) + { + colorsUsed.Add (val.Value); + + Attribute match = toFind.FirstOrDefault (e => e == val); + + // need to check twice because Attribute is a struct and therefore cannot be null + if (toFind.Any (e => e == val)) + { + toFind.Remove (match); + } + } + } + } + + if (!toFind.Any ()) + { + return; + } + + var sb = new StringBuilder (); + sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => a.ToString ()))); + sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (a => a.ToString ()))); + + throw new (sb.ToString ()); + } + + private static void AddArguments (Type paramType, List pTypes) + { + if (paramType == typeof (Rectangle)) + { + pTypes.Add (Rectangle.Empty); + } + else if (paramType == typeof (string)) + { + pTypes.Add (string.Empty); + } + else if (paramType == typeof (int)) + { + pTypes.Add (0); + } + else if (paramType == typeof (bool)) + { + pTypes.Add (true); + } + else if (paramType.Name == "IList") + { + pTypes.Add (new List ()); + } + else if (paramType.Name == "View") + { + var top = new Toplevel (); + var view = new View (); + top.Add (view); + pTypes.Add (view); + } + else if (paramType.Name == "View[]") + { + pTypes.Add (new View [] { }); + } + else if (paramType.Name == "Stream") + { + pTypes.Add (new MemoryStream ()); + } + else if (paramType.Name == "String") + { + pTypes.Add (string.Empty); + } + else if (paramType.Name == "TreeView`1[T]") + { + pTypes.Add (string.Empty); + } + else + { + pTypes.Add (null); + } + } + [GeneratedRegex ("^\\s+", RegexOptions.Multiline)] private static partial Regex LeadingWhitespaceRegEx (); @@ -832,11 +670,11 @@ internal partial class TestHelpers string replaced = toReplace; replaced = Environment.NewLine.Length switch - { - 2 when !replaced.Contains ("\r\n") => replaced.Replace ("\n", Environment.NewLine), - 1 => replaced.Replace ("\r\n", Environment.NewLine), - var _ => replaced - }; + { + 2 when !replaced.Contains ("\r\n") => replaced.Replace ("\n", Environment.NewLine), + 1 => replaced.Replace ("\r\n", Environment.NewLine), + var _ => replaced + }; return replaced; } @@ -863,6 +701,4 @@ public class TestsAllViews return Activator.CreateInstance (type) as View; } - } - diff --git a/UnitTests/View/Adornment/BorderTests.cs b/UnitTests/View/Adornment/BorderTests.cs index d2f88916b..387844dbe 100644 --- a/UnitTests/View/Adornment/BorderTests.cs +++ b/UnitTests/View/Adornment/BorderTests.cs @@ -18,7 +18,7 @@ public class BorderTests (ITestOutputHelper output) view.Border.Thickness = new (0, 1, 0, 0); view.Border.LineStyle = LineStyle.Single; - view.ColorScheme = new() + view.ColorScheme = new () { Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red) @@ -53,7 +53,7 @@ public class BorderTests (ITestOutputHelper output) view.Border.Thickness = new (0, 1, 0, 0); view.Border.LineStyle = LineStyle.Single; - view.ColorScheme = new() + view.ColorScheme = new () { Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red) }; @@ -90,7 +90,7 @@ public class BorderTests (ITestOutputHelper output) { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double }; - win.Border.Thickness.Top = 4; + win.Border.Thickness = win.Border.Thickness with { Top = 4 }; RunState rs = Application.Begin (win); var firstIteration = false; @@ -224,7 +224,7 @@ public class BorderTests (ITestOutputHelper output) { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double }; - win.Border.Thickness.Top = 3; + win.Border.Thickness = win.Border.Thickness with { Top = 3 }; RunState rs = Application.Begin (win); var firstIteration = false; @@ -358,7 +358,7 @@ public class BorderTests (ITestOutputHelper output) { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double }; - win.Border.Thickness.Top = 2; + win.Border.Thickness = win.Border.Thickness with { Top = 2 }; RunState rs = Application.Begin (win); var firstIteration = false;