Browse Source

Merge pull request #3587 from tig/v2_1845-Application-ToString

Fixes #1845. `Application.ToString` returns formatted driver contents
Tig 1 year ago
parent
commit
ff659cd1de
2 changed files with 158 additions and 264 deletions
  1. 59 1
      Terminal.Gui/Application/Application.cs
  2. 99 263
      UnitTests/TestHelpers.cs

+ 59 - 1
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
+
+    /// <summary>
+    ///     Gets a string representation of the Application as rendered by <see cref="Driver"/>.
+    /// </summary>
+    /// <returns>A string representation of the Application </returns>
+    public new static string ToString ()
+    {
+        ConsoleDriver driver = Driver;
+
+        if (driver is null)
+        {
+            return string.Empty;
+        }
+
+        return ToString (driver);
+    }
+
+    /// <summary>
+    ///     Gets a string representation of the Application rendered by the provided <see cref="ConsoleDriver"/>.
+    /// </summary>
+    /// <param name="driver">The driver to use to render the contents.</param>
+    /// <returns>A string representation of the Application </returns>
+    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 ();
+    }
 }

+ 99 - 263
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..
+/// <summary>
+///     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..
+/// </summary>
 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
 public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
 {
-    private readonly Type _driverType;
-
     /// <summary>
     ///     Initializes a [AutoInitShutdown] attribute, which determines if/how Application.Init and Application.Shutdown
     ///     are automatically called Before/After a test runs.
     /// </summary>
     /// <param name="autoInit">If true, Application.Init will be called Before the test runs.</param>
-    /// <param name="autoShutdown">If true, Application.Shutdown will be called After the test runs.</param>
     /// <param name="consoleDriverType">
     ///     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: <not found>"
-                                            );
+                        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: <not found>"
+                                  );
                     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
     }
 
     /// <summary>
-    ///     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.
     /// </summary>
     /// <param name="expectedLook"></param>
     /// <param name="output"></param>
@@ -337,7 +335,6 @@ internal partial class TestHelpers
 
         Cell [,] contents = driver.Contents;
 
-
         for (var rowIndex = 0; rowIndex < driver.Rows; rowIndex++)
         {
             List<Rune> 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,95 +480,86 @@ internal partial class TestHelpers
     }
 #pragma warning restore xUnit1013 // Public method should be marked as test
 
-    public static string DriverContentsToString (ConsoleDriver driver = null)
+    public static View CreateViewFromType (Type type, ConstructorInfo ctor)
     {
-        var sb = new StringBuilder ();
-        driver ??= Application.Driver;
-
-        Cell [,] contents = driver.Contents;
+        View viewType = null;
 
-        for (var r = 0; r < driver.Rows; r++)
+        if (type.IsGenericType && type.IsTypeDefinition)
         {
-            for (var c = 0; c < driver.Cols; c++)
+            List<Type> gTypes = new ();
+
+            foreach (Type args in type.GetGenericArguments ())
             {
-                Rune rune = contents [r, c].Rune;
+                gTypes.Add (typeof (object));
+            }
+
+            type = type.MakeGenericType (gTypes.ToArray ());
+
+            Assert.IsType (type, (View)Activator.CreateInstance (type));
+        }
+        else
+        {
+            ParameterInfo [] paramsInfo = ctor.GetParameters ();
+            Type paramType;
+            List<object> pTypes = new ();
 
-                if (rune.DecodeSurrogatePair (out char [] sp))
+            if (type.IsGenericType)
+            {
+                foreach (Type args in type.GetGenericArguments ())
                 {
-                    sb.Append (sp);
+                    paramType = args.GetType ();
+
+                    if (args.Name == "T")
+                    {
+                        pTypes.Add (typeof (object));
+                    }
+                    else
+                    {
+                        AddArguments (paramType, pTypes);
+                    }
                 }
-                else
+            }
+
+            foreach (ParameterInfo p in paramsInfo)
+            {
+                paramType = p.ParameterType;
+
+                if (p.HasDefaultValue)
                 {
-                    sb.Append ((char)rune.Value);
+                    pTypes.Add (p.DefaultValue);
                 }
-
-                if (rune.GetColumns () > 1)
+                else
                 {
-                    c++;
+                    AddArguments (paramType, pTypes);
                 }
-
-                // See Issue #2616
-                //foreach (var combMark in contents [r, c].CombiningMarks) {
-                //	sb.Append ((char)combMark.Value);
-                //}
             }
 
-            sb.AppendLine ();
+            if (type.IsGenericType && !type.IsTypeDefinition)
+            {
+                viewType = (View)Activator.CreateInstance (type);
+                Assert.IsType (type, viewType);
+            }
+            else
+            {
+                viewType = (View)ctor.Invoke (pTypes.ToArray ());
+                Assert.IsType (type, viewType);
+            }
         }
 
-        return sb.ToString ();
+        return viewType;
     }
 
-    //// TODO: Update all tests that use GetALlViews to use GetAllViewsTheoryData instead
-    ///// <summary>Gets a list of instances of all classes derived from View.</summary>
-    ///// <returns>List of View objects</returns>
-    //public static List<View> 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<Type> ())))
-    //                        .ToList ();
-    //}
-
-    //public class AllViewsData : IEnumerable<object []>
-    //{
-    //    private Lazy<List<object []>> data;
-
-    //    public AllViewsData ()
-    //    {
-    //        data = new Lazy<List<object []>> (GetTestData);
-    //    }
-
-    //    public IEnumerator<object []> GetEnumerator ()
-    //    {
-    //        return data.Value.GetEnumerator ();
-    //    }
-
-    //    IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
-
-    //    private List<object []> GetTestData ()
-    //    {
-    //        var viewTypes = typeof (View).Assembly
-    //                                     .GetTypes ()
-    //                                     .Where (type => type.IsClass && !type.IsAbstract && type.IsPublic && type.IsSubclassOf (typeof (View)));
-
-    //        var testData = new List<object []> ();
-
-    //        foreach (var type in viewTypes)
-    //        {
-    //            var view = CreateView (type, type.GetConstructor (Array.Empty<Type> ()));
-    //            testData.Add (new object [] { view, type.Name });
-    //        }
-
-    //        return testData;
-    //    }
-    //}
-
+    public static List<Type> GetAllViewClasses ()
+    {
+        return typeof (View).Assembly.GetTypes ()
+                            .Where (
+                                    myType => myType.IsClass
+                                              && !myType.IsAbstract
+                                              && myType.IsPublic
+                                              && myType.IsSubclassOf (typeof (View))
+                                   )
+                            .ToList ();
+    }
 
     /// <summary>
     ///     Verifies the console used all the <paramref name="expectedColors"/> when rendering. If one or more of the
@@ -620,7 +608,7 @@ internal partial class TestHelpers
         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 ());
+        throw new (sb.ToString ());
     }
 
     private static void AddArguments (Type paramType, List<object> pTypes)
@@ -674,156 +662,6 @@ internal partial class TestHelpers
         }
     }
 
-    public static View CreateView (Type type, ConstructorInfo ctor)
-    {
-        View view = null;
-
-        if (type.IsGenericType && type.IsTypeDefinition)
-        {
-            List<Type> 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<object> 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<Type> 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;
-
-        if (type.IsGenericType && type.IsTypeDefinition)
-        {
-            List<Type> 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<object> 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)
-            {
-                viewType = (View)Activator.CreateInstance (type);
-                Assert.IsType (type, viewType);
-            }
-            else
-            {
-                viewType = (View)ctor.Invoke (pTypes.ToArray ());
-                Assert.IsType (type, viewType);
-            }
-        }
-
-        return viewType;
-    }
-
     [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;
     }
-
 }
-