Merge branch 'v2_develop' into gradients

This commit is contained in:
Thomas Nind
2024-07-07 21:17:07 +01:00
committed by GitHub
6 changed files with 313 additions and 438 deletions

View File

@@ -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}}"

View File

@@ -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

View File

@@ -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 ();
}
}

View File

@@ -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.
/// </para>
/// <para>Use the helper API (<see cref="Draw(Rectangle, string)"/> to draw the frame with the specified thickness.</para>
/// <para>
/// Use the helper API (<see cref="Draw(Rectangle, string)"/> to draw the frame with the specified thickness.
/// </para>
/// <para>
/// Thickness uses <see langword="float"/> 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.
/// </para>
/// </remarks>
public class Thickness : IEquatable<Thickness>
public record struct Thickness
{
/// <summary>Gets or sets the width of the lower side of the rectangle.</summary>
[JsonInclude]
public int Bottom;
/// <summary>Gets or sets the width of the left side of the rectangle.</summary>
[JsonInclude]
public int Left;
/// <summary>Gets or sets the width of the right side of the rectangle.</summary>
[JsonInclude]
public int Right;
/// <summary>Gets or sets the width of the upper side of the rectangle.</summary>
[JsonInclude]
public int Top;
/// <summary>Initializes a new instance of the <see cref="Thickness"/> class with all widths set to 0.</summary>
public Thickness () { }
public Thickness () { _sides = Vector4.Zero; }
/// <summary>Initializes a new instance of the <see cref="Thickness"/> class with a uniform width to each side.</summary>
/// <param name="width"></param>
@@ -56,35 +47,23 @@ public class Thickness : IEquatable<Thickness>
Bottom = bottom;
}
// TODO: add operator overloads
/// <summary>Gets an empty thickness.</summary>
public static Thickness Empty => new (0);
private Vector4 _sides;
/// <summary>
/// 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 <see cref="Thickness"/> to the current <see cref="Thickness"/>, returning a
/// new <see cref="Thickness"/>.
/// </summary>
public int Horizontal
{
get => Left + Right;
set => Left = Right = value / 2;
}
/// <summary>
/// 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.
/// </summary>
public int Vertical
{
get => Top + Bottom;
set => Top = Bottom = value / 2;
}
// IEquitable
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other"></param>
/// <returns>true if the current object is equal to the other parameter; otherwise, false.</returns>
public bool Equals (Thickness other) { return other is { } && Left == other.Left && Right == other.Right && Top == other.Top && Bottom == other.Bottom; }
/// <returns></returns>
public readonly Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); }
/// <summary>Gets or sets the width of the lower side of the rectangle.</summary>
[JsonInclude]
public int Bottom
{
get => (int)_sides.W;
set => _sides.W = value;
}
/// <summary>
/// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside
@@ -100,22 +79,6 @@ public class Thickness : IEquatable<Thickness>
return outside.Contains (location) && !inside.Contains (location);
}
/// <summary>
/// Adds the thickness widths of another <see cref="Thickness"/> to the current <see cref="Thickness"/>, returning a
/// new <see cref="Thickness"/>.
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); }
/// <summary>
/// Adds the thickness widths of another <see cref="Thickness"/> to another <see cref="Thickness"/>.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); }
/// <summary>Draws the <see cref="Thickness"/> rectangle with an optional diagnostics label.</summary>
/// <remarks>
/// If <see cref="ViewDiagnosticFlags"/> is set to
@@ -240,31 +203,8 @@ public class Thickness : IEquatable<Thickness>
return GetInside (rect);
}
/// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
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);
}
/// <inheritdoc/>
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;
}
/// <summary>Gets an empty thickness.</summary>
public static Thickness Empty => new (0);
/// <summary>
/// Returns a rectangle describing the location and size of the inside area of <paramref name="rect"/> with the
@@ -289,23 +229,59 @@ public class Thickness : IEquatable<Thickness>
return new (x, y, width, height);
}
/// <inheritdoc/>
public static bool operator == (Thickness left, Thickness right) { return EqualityComparer<Thickness>.Default.Equals (left, right); }
/// <summary>
/// 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.
/// </summary>
public int Horizontal
{
get => Left + Right;
set => Left = Right = value / 2;
}
/// <inheritdoc/>
public static bool operator != (Thickness left, Thickness right) { return !(left == right); }
/// <summary>Gets or sets the width of the left side of the rectangle.</summary>
[JsonInclude]
public int Left
{
get => (int)_sides.X;
set => _sides.X = value;
}
/// <summary>
/// Adds the thickness widths of another <see cref="Thickness"/> to another <see cref="Thickness"/>.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); }
/// <summary>Gets or sets the width of the right side of the rectangle.</summary>
[JsonInclude]
public int Right
{
get => (int)_sides.Z;
set => _sides.Z = value;
}
/// <summary>Gets or sets the width of the upper side of the rectangle.</summary>
[JsonInclude]
public int Top
{
get => (int)_sides.Y;
set => _sides.Y = value;
}
/// <summary>Returns the thickness widths of the Thickness formatted as a string.</summary>
/// <returns>The thickness widths as a string.</returns>
public override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; }
private int validate (int width)
/// <summary>
/// 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.
/// </summary>
public int Vertical
{
if (width < 0)
{
throw new ArgumentException ("Thickness widths cannot be negative.");
}
return width;
get => Top + Bottom;
set => Top = Bottom = value / 2;
}
}

View File

@@ -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,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
///// <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;
// }
//}
/// <summary>
/// Verifies the console used all the <paramref name="expectedColors"/> 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.
/// </summary>
/// <param name="driver">if null uses <see cref="Application.Driver"/></param>
/// <param name="expectedColors"></param>
internal static void AssertDriverUsedColors (ConsoleDriver driver = null, params Attribute [] expectedColors)
{
driver ??= Application.Driver;
Cell [,] contents = driver.Contents;
List<Attribute> toFind = expectedColors.ToList ();
// Contents 3rd column is an Attribute
HashSet<Attribute> 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<object> 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<object> ());
}
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<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;
@@ -824,6 +549,119 @@ internal partial class TestHelpers
return viewType;
}
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
/// 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 (ConsoleDriver driver = null, params Attribute [] expectedColors)
{
driver ??= Application.Driver;
Cell [,] contents = driver.Contents;
List<Attribute> toFind = expectedColors.ToList ();
// Contents 3rd column is an Attribute
HashSet<Attribute> 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<object> 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<object> ());
}
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;
}
}

View File

@@ -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;