using System.Text;
using System.Text.RegularExpressions;
using Xunit.Abstractions;
using Attribute = Terminal.Gui.Attribute;
namespace UnitTests;
///
/// Provides xUnit-style assertions for contents.
///
internal partial class DriverAssert
{
private const char SpaceChar = ' ';
private static readonly Rune SpaceRune = (Rune)SpaceChar;
#pragma warning disable xUnit1013 // Public method should be marked as test
///
/// Verifies are found at the locations specified by
/// . is a bitmap of indexes into
/// (e.g. "00110" means the attribute at expectedAttributes[1] is expected
/// at the 3rd and 4th columns of the 1st row of driver.Contents).
///
///
/// Numbers between 0 and 9 for each row/col of the console. Must be valid indexes into
/// .
///
///
/// The IConsoleDriver to use. If null will be used.
///
public static void AssertDriverAttributesAre (
string expectedLook,
ITestOutputHelper output,
IConsoleDriver driver = null,
params Attribute [] expectedAttributes
)
{
#pragma warning restore xUnit1013 // Public method should be marked as test
if (expectedAttributes.Length > 10)
{
throw new ArgumentException ("This method only works for UIs that use at most 10 colors");
}
expectedLook = expectedLook.Trim ();
driver ??= Application.Driver;
Cell [,] contents = driver.Contents;
var line = 0;
foreach (string lineString in expectedLook.Split ('\n').Select (l => l.Trim ()))
{
for (var c = 0; c < lineString.Length; c++)
{
Attribute? val = contents [line, c].Attribute;
List match = expectedAttributes.Where (e => e == val).ToList ();
switch (match.Count)
{
case 0:
output.WriteLine (
$"{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: "
);
Assert.Empty (match);
return;
case > 1:
throw new ArgumentException (
$"Bad value for expectedColors, {match.Count} Attributes had the same Value"
);
}
char colorUsed = Array.IndexOf (expectedAttributes, match [0]).ToString () [0];
char userExpected = lineString [c];
if (colorUsed != userExpected)
{
output.WriteLine ($"{Application.ToString (driver)}");
output.WriteLine ($"Unexpected Attribute at Contents[{line},{c}] {contents [line, c]}.");
output.WriteLine ($" Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})");
output.WriteLine ($" But Was: {colorUsed} ({val})");
Assert.Equal (userExpected, colorUsed);
return;
}
}
line++;
}
}
#pragma warning disable xUnit1013 // Public method should be marked as test
/// Asserts that the driver contents match the expected contents, optionally ignoring any trailing whitespace.
///
///
/// The IConsoleDriver to use. If null will be used.
///
public static void AssertDriverContentsAre (
string expectedLook,
ITestOutputHelper output,
IConsoleDriver driver = null,
bool ignoreLeadingWhitespace = false
)
{
#pragma warning restore xUnit1013 // Public method should be marked as test
var actualLook = Application.ToString (driver ?? Application.Driver);
if (string.Equals (expectedLook, actualLook))
{
return;
}
// get rid of trailing whitespace on each line (and leading/trailing whitespace of start/end of full string)
expectedLook = TrailingWhiteSpaceRegEx ().Replace (expectedLook, "").Trim ();
actualLook = TrailingWhiteSpaceRegEx ().Replace (actualLook, "").Trim ();
if (ignoreLeadingWhitespace)
{
expectedLook = LeadingWhitespaceRegEx ().Replace (expectedLook, "").Trim ();
actualLook = LeadingWhitespaceRegEx ().Replace (actualLook, "").Trim ();
}
// standardize line endings for the comparison
expectedLook = expectedLook.Replace ("\r\n", "\n");
actualLook = actualLook.Replace ("\r\n", "\n");
// If test is about to fail show user what things looked like
if (!string.Equals (expectedLook, actualLook))
{
output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
output?.WriteLine (" But Was:" + Environment.NewLine + actualLook);
}
Assert.Equal (expectedLook, actualLook);
}
///
/// Asserts that the driver contents are equal to the provided string.
///
///
///
/// The IConsoleDriver to use. If null will be used.
///
public static Rectangle AssertDriverContentsWithFrameAre (
string expectedLook,
ITestOutputHelper output,
IConsoleDriver driver = null
)
{
List> lines = new ();
var sb = new StringBuilder ();
driver ??= Application.Driver;
int x = -1;
int y = -1;
int w = -1;
int h = -1;
Cell [,] contents = driver.Contents;
for (var rowIndex = 0; rowIndex < driver.Rows; rowIndex++)
{
List runes = [];
for (var colIndex = 0; colIndex < driver.Cols; colIndex++)
{
Rune runeAtCurrentLocation = contents [rowIndex, colIndex].Rune;
if (runeAtCurrentLocation != SpaceRune)
{
if (x == -1)
{
x = colIndex;
y = rowIndex;
for (var i = 0; i < colIndex; i++)
{
runes.InsertRange (i, [SpaceRune]);
}
}
if (runeAtCurrentLocation.GetColumns () > 1)
{
colIndex++;
}
if (colIndex + 1 > w)
{
w = colIndex + 1;
}
h = rowIndex - y + 1;
}
if (x > -1)
{
runes.Add (runeAtCurrentLocation);
}
// See Issue #2616
//foreach (var combMark in contents [r, c].CombiningMarks) {
// runes.Add (combMark);
//}
}
if (runes.Count > 0)
{
lines.Add (runes);
}
}
// Remove unnecessary empty lines
if (lines.Count > 0)
{
for (int r = lines.Count - 1; r > h - 1; r--)
{
lines.RemoveAt (r);
}
}
// Remove trailing whitespace on each line
foreach (List row in lines)
{
for (int c = row.Count - 1; c >= 0; c--)
{
Rune rune = row [c];
if (rune != (Rune)' ' || row.Sum (x => x.GetColumns ()) == w)
{
break;
}
row.RemoveAt (c);
}
}
// Convert Rune list to string
for (var r = 0; r < lines.Count; r++)
{
var line = StringExtensions.ToString (lines [r]);
if (r == lines.Count - 1)
{
sb.Append (line);
}
else
{
sb.AppendLine (line);
}
}
var actualLook = sb.ToString ();
if (string.Equals (expectedLook, actualLook))
{
return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
}
// standardize line endings for the comparison
expectedLook = expectedLook.ReplaceLineEndings ();
actualLook = actualLook.ReplaceLineEndings ();
// Remove the first and the last line ending from the expectedLook
if (expectedLook.StartsWith (Environment.NewLine))
{
expectedLook = expectedLook [Environment.NewLine.Length..];
}
if (expectedLook.EndsWith (Environment.NewLine))
{
expectedLook = expectedLook [..^Environment.NewLine.Length];
}
// If test is about to fail show user what things looked like
if (!string.Equals (expectedLook, actualLook))
{
output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
output?.WriteLine (" But Was:" + Environment.NewLine + actualLook);
}
Assert.Equal (expectedLook, actualLook);
return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
}
///
/// 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 (IConsoleDriver 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 ());
}
[GeneratedRegex ("^\\s+", RegexOptions.Multiline)]
private static partial Regex LeadingWhitespaceRegEx ();
[GeneratedRegex ("\\s+$", RegexOptions.Multiline)]
private static partial Regex TrailingWhiteSpaceRegEx ();
}