Fixed a bunch of Key related issues

This commit is contained in:
Tig
2024-09-25 10:34:28 -06:00
parent 5a85d439ce
commit 525d332c9b
10 changed files with 546 additions and 419 deletions

View File

@@ -4,7 +4,16 @@ using ColorHelper;
namespace Terminal.Gui;
/// <summary>Json converter for the <see cref="Color"/> class.</summary>
/// <summary>
/// Json converter for the <see cref="Color"/> class.
/// <para>
/// Serialization outputs a string with the color name if the color matches a name in <see cref="ColorStrings"/>
/// or the "#RRGGBB" hexadecimal representation (e.g. "#FF0000" for red).
/// </para>
/// <para>
/// Deserialization formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
/// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", or any W3C color name.</para>
/// </summary>
internal class ColorJsonConverter : JsonConverter<Color>
{
private static ColorJsonConverter _instance;

View File

@@ -501,7 +501,7 @@ public readonly partial record struct Color
/// </summary>
/// <param name="text">
/// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
/// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="GetClosestNamedColor16(Terminal.Gui.Color)"/> string
/// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any W3C color name."/> string
/// values.
/// </param>
/// <param name="formatProvider">

View File

@@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.Json.Serialization;
namespace Terminal.Gui;
@@ -80,7 +79,7 @@ public class Key : EventArgs, IEquatable<Key>
public Key (KeyCode k) { KeyCode = k; }
/// <summary>
/// Copy constructor.
/// Copy constructor.
/// </summary>
/// <param name="key">The Key to copy</param>
public Key (Key key)
@@ -155,18 +154,13 @@ public class Key : EventArgs, IEquatable<Key>
/// </remarks>
public Rune AsRune => ToRune (KeyCode);
private bool _handled = false;
/// <summary>
/// Indicates if the current Key event has already been processed and the driver should stop notifying any other
/// event subscriber. It's important to set this value to true specially when updating any View's layout from inside the
/// event subscriber. It's important to set this value to true specially when updating any View's layout from inside
/// the
/// subscriber method.
/// </summary>
public bool Handled
{
get => _handled;
set => _handled = value;
}
public bool Handled { get; set; }
/// <summary>Gets a value indicating whether the Alt key was pressed (real or synthesized)</summary>
/// <value><see langword="true"/> if is alternate; otherwise, <see langword="false"/>.</value>
@@ -339,11 +333,11 @@ public class Key : EventArgs, IEquatable<Key>
switch (baseKey)
{
case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask):
return new Rune ((uint)(baseKey + 32));
return new ((uint)(baseKey + 32));
case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask):
return new Rune ((uint)baseKey);
return new ((uint)baseKey);
case > KeyCode.Null and < KeyCode.A:
return new Rune ((uint)baseKey);
return new ((uint)baseKey);
}
if (Enum.IsDefined (typeof (KeyCode), baseKey))
@@ -351,7 +345,7 @@ public class Key : EventArgs, IEquatable<Key>
return default (Rune);
}
return new Rune ((uint)baseKey);
return new ((uint)baseKey);
}
#region Operators
@@ -381,17 +375,17 @@ public class Key : EventArgs, IEquatable<Key>
/// <summary>Cast <see cref="KeyCode"/> to a <see cref="Key"/>.</summary>
/// <param name="keyCode"></param>
public static implicit operator Key (KeyCode keyCode) { return new Key (keyCode); }
public static implicit operator Key (KeyCode keyCode) { return new (keyCode); }
/// <summary>Cast <see langword="char"/> to a <see cref="Key"/>.</summary>
/// <remarks>See <see cref="Key(char)"/> for more information.</remarks>
/// <param name="ch"></param>
public static implicit operator Key (char ch) { return new Key (ch); }
public static implicit operator Key (char ch) { return new (ch); }
/// <summary>Cast <see langword="string"/> to a <see cref="Key"/>.</summary>
/// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
/// <param name="str"></param>
public static implicit operator Key (string str) { return new Key (str); }
public static implicit operator Key (string str) { return new (str); }
/// <summary>Cast a <see cref="Key"/> to a <see langword="string"/>.</summary>
/// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
@@ -399,10 +393,7 @@ public class Key : EventArgs, IEquatable<Key>
public static implicit operator string (Key key) { return key.ToString (); }
/// <inheritdoc/>
public override bool Equals (object obj)
{
return obj is Key k && k.KeyCode == KeyCode && k.Handled == Handled;
}
public override bool Equals (object obj) { return obj is Key k && k.KeyCode == KeyCode && k.Handled == Handled; }
bool IEquatable<Key>.Equals (Key other) { return Equals (other); }
@@ -568,7 +559,10 @@ public class Key : EventArgs, IEquatable<Key>
/// <summary>Converts the provided string to a new <see cref="Key"/> instance.</summary>
/// <param name="text">
/// The text to analyze. Formats supported are "Ctrl+X", "Alt+X", "Shift+X", "Ctrl+Alt+X",
/// "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", and "X".
/// "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", "X", and "120" (Unicode codepoint).
/// <para>
/// The separator can be any character, not just <see cref="Key.Separator"/> (e.g. "Ctrl@Alt@X").
/// </para>
/// </param>
/// <param name="key">The parsed value.</param>
/// <returns>A boolean value indicating whether parsing was successful.</returns>
@@ -577,38 +571,88 @@ public class Key : EventArgs, IEquatable<Key>
{
if (string.IsNullOrEmpty (text))
{
key = Key.Empty;
key = Empty;
return true;
}
switch (text)
{
case "Ctrl":
key = KeyCode.CtrlMask;
return true;
case "Alt":
key = KeyCode.AltMask;
return true;
case "Shift":
key = KeyCode.ShiftMask;
return true;
}
key = null;
// Split the string into parts
string [] parts = text.Split ('+', '-', (char)Separator.Value);
Rune separator = Separator;
if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
// Perhaps the separator was written using a different Key.Separator? Does the string
// start with "Ctrl", "Alt" or "Shift"? If so, get the char after the modifier string and use that as the separator.
if (text.StartsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase))
{
separator = (Rune)text [4];
}
else if (text.StartsWith ("Alt", StringComparison.InvariantCultureIgnoreCase))
{
separator = (Rune)text [3];
}
else if (text.StartsWith ("Shift", StringComparison.InvariantCultureIgnoreCase))
{
separator = (Rune)text [5];
}
else if (text.EndsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase))
{
separator = (Rune)text [^5];
}
else if (text.EndsWith ("Alt", StringComparison.InvariantCultureIgnoreCase))
{
separator = (Rune)text [^4];
}
else if (text.EndsWith ("Shift", StringComparison.InvariantCultureIgnoreCase))
{
separator = (Rune)text [^6];
}
// Split the string into parts using the set Separator
string [] parts = text.Split ((char)separator.Value);
if (parts.Length is > 4)
{
// Invalid
return false;
}
// if it's just a shift key
if (parts.Length == 1)
// e.g. "Ctrl++"
if ((Rune)text [^1] != separator && parts.Any (string.IsNullOrEmpty))
{
switch (parts [0])
// Invalid
return false;
}
if ((Rune)text [^1] == separator)
{
parts [^1] = separator.Value.ToString ();
key = (char)separator.Value;
}
if (separator != Separator && (parts.Length is 1 || (key is { } && parts.Length is 2)))
{
parts = text.Split ((char)separator.Value);
if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
{
case "Ctrl":
key = KeyCode.CtrlMask;
return true;
case "Alt":
key = KeyCode.AltMask;
return true;
case "Shift":
key = KeyCode.ShiftMask;
return true;
// Invalid
return false;
}
}
@@ -649,12 +693,12 @@ public class Key : EventArgs, IEquatable<Key>
{
if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
{
key = new Key (parsedKeyCode | KeyCode.ShiftMask);
key = new (parsedKeyCode | KeyCode.ShiftMask);
return true;
}
key = new Key (parsedKeyCode | modifiers);
key = new (parsedKeyCode | modifiers);
return true;
}
@@ -664,7 +708,8 @@ public class Key : EventArgs, IEquatable<Key>
{
keyCode = keyCode & ~KeyCode.Space;
}
key = new Key (keyCode | modifiers);
key = new (keyCode | modifiers);
return true;
}
@@ -675,7 +720,7 @@ public class Key : EventArgs, IEquatable<Key>
{
if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
{
key = new Key (parsedKeyCode | KeyCode.ShiftMask);
key = new (parsedKeyCode | KeyCode.ShiftMask);
return true;
}
@@ -684,7 +729,8 @@ public class Key : EventArgs, IEquatable<Key>
{
parsedKeyCode = parsedKeyCode & ~KeyCode.Space;
}
key = new Key (parsedKeyCode | modifiers);
key = new (parsedKeyCode | modifiers);
return true;
}
@@ -705,12 +751,12 @@ public class Key : EventArgs, IEquatable<Key>
if ((KeyCode)parsedInt is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
{
key = new Key ((KeyCode)parsedInt | KeyCode.ShiftMask);
key = new ((KeyCode)parsedInt | KeyCode.ShiftMask);
return true;
}
key = new Key ((KeyCode)parsedInt);
key = new ((KeyCode)parsedInt);
return true;
}
@@ -722,7 +768,7 @@ public class Key : EventArgs, IEquatable<Key>
if (GetIsKeyCodeAtoZ (parsedKeyCode))
{
key = new Key (parsedKeyCode | (modifiers & ~KeyCode.Space));
key = new (parsedKeyCode | (modifiers & ~KeyCode.Space));
return true;
}

View File

@@ -0,0 +1,32 @@
using System.Text.Json;
namespace Terminal.Gui.ConfigurationTests;
public class AttributeJsonConverterTests
{
[Fact]
public void TestDeserialize ()
{
// Test deserializing from human-readable color names
var json = "{\"Foreground\":\"Blue\",\"Background\":\"Green\"}";
var attribute = JsonSerializer.Deserialize<Attribute> (json, ConfigurationManagerTests._jsonOptions);
Assert.Equal (Color.Blue, attribute.Foreground.GetClosestNamedColor16 ());
Assert.Equal (Color.Green, attribute.Background.GetClosestNamedColor16 ());
// Test deserializing from RGB values
json = "{\"Foreground\":\"rgb(255,0,0)\",\"Background\":\"rgb(0,255,0)\"}";
attribute = JsonSerializer.Deserialize<Attribute> (json, ConfigurationManagerTests._jsonOptions);
Assert.Equal (Color.Red, attribute.Foreground.GetClosestNamedColor16 ());
Assert.Equal (Color.BrightGreen, attribute.Background.GetClosestNamedColor16 ());
}
[Fact]
[AutoInitShutdown]
public void TestSerialize ()
{
// Test serializing to human-readable color names
var attribute = new Attribute (Color.Blue, Color.Green);
string json = JsonSerializer.Serialize (attribute, ConfigurationManagerTests._jsonOptions);
Assert.Equal ("{\"Foreground\":\"Blue\",\"Background\":\"Green\"}", json);
}
}

View File

@@ -0,0 +1,180 @@
using System.Text;
using System.Text.Json;
namespace Terminal.Gui.ConfigurationTests;
public class ColorJsonConverterTests
{
[Theory]
[InlineData ("\"#000000\"", 0, 0, 0)]
public void DeserializesFromHexCode (string hexCode, int r, int g, int b)
{
// Arrange
var expected = new Color (r, g, b);
// Act
var actual = JsonSerializer.Deserialize<Color> (
hexCode,
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
//Assert
Assert.Equal (expected, actual);
}
[Theory]
[InlineData ("\"rgb(0,0,0)\"", 0, 0, 0)]
public void DeserializesFromRgb (string rgb, int r, int g, int b)
{
// Arrange
var expected = new Color (r, g, b);
// Act
var actual = JsonSerializer.Deserialize<Color> (
rgb,
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
//Assert
Assert.Equal (expected, actual);
}
[Theory]
[InlineData (ColorName16.Black, "Black")]
[InlineData (ColorName16.Blue, "Blue")]
[InlineData (ColorName16.Green, "Green")]
[InlineData (ColorName16.Cyan, "Cyan")]
[InlineData (ColorName16.Gray, "Gray")]
[InlineData (ColorName16.Red, "Red")]
[InlineData (ColorName16.Magenta, "Magenta")]
[InlineData (ColorName16.Yellow, "Yellow")]
[InlineData (ColorName16.DarkGray, "DarkGray")]
[InlineData (ColorName16.BrightBlue, "BrightBlue")]
[InlineData (ColorName16.BrightGreen, "BrightGreen")]
[InlineData (ColorName16.BrightCyan, "BrightCyan")]
[InlineData (ColorName16.BrightRed, "BrightRed")]
[InlineData (ColorName16.BrightMagenta, "BrightMagenta")]
[InlineData (ColorName16.BrightYellow, "BrightYellow")]
[InlineData (ColorName16.White, "White")]
public void SerializesColorName16ValuesAsStrings (ColorName16 colorName, string expectedJson)
{
var converter = new ColorJsonConverter ();
var options = new JsonSerializerOptions { Converters = { converter } };
string serialized = JsonSerializer.Serialize (new Color (colorName), options);
Assert.Equal ($"\"{expectedJson}\"", serialized);
}
[Theory]
[InlineData (1, 0, 0, "\"#010000\"")]
[InlineData (0, 0, 1, "\"#000001\"")]
public void SerializesToHexCode (int r, int g, int b, string expected)
{
// Arrange
// Act
string actual = JsonSerializer.Serialize (
new Color (r, g, b),
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
//Assert
Assert.Equal (expected, actual);
}
[Theory]
[InlineData ("Black", Color.Black)]
[InlineData ("Blue", Color.Blue)]
[InlineData ("BrightBlue", Color.BrightBlue)]
[InlineData ("BrightCyan", Color.BrightCyan)]
[InlineData ("BrightGreen", Color.BrightGreen)]
[InlineData ("BrightMagenta", Color.BrightMagenta)]
[InlineData ("BrightRed", Color.BrightRed)]
[InlineData ("BrightYellow", Color.BrightYellow)]
[InlineData ("Yellow", Color.Yellow)]
[InlineData ("Cyan", Color.Cyan)]
[InlineData ("DarkGray", Color.DarkGray)]
[InlineData ("Gray", Color.Gray)]
[InlineData ("Green", Color.Green)]
[InlineData ("Magenta", Color.Magenta)]
[InlineData ("Red", Color.Red)]
[InlineData ("White", Color.White)]
public void TestColorDeserializationFromHumanReadableColorName16 (string colorName, ColorName16 expectedColor)
{
// Arrange
var json = $"\"{colorName}\"";
// Act
var actualColor = JsonSerializer.Deserialize<Color> (json, ConfigurationManagerTests._jsonOptions);
// Assert
Assert.Equal (new Color (expectedColor), actualColor);
}
[Fact]
public void TestDeserializeColor_Black ()
{
// Arrange
var json = "\"Black\"";
var expectedColor = new Color ("Black");
// Act
var color = JsonSerializer.Deserialize<Color> (
json,
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
// Assert
Assert.Equal (expectedColor, color);
}
[Fact]
public void TestDeserializeColor_BrightRed ()
{
// Arrange
var json = "\"BrightRed\"";
var expectedColor = Color.BrightRed;
// Act
var color = JsonSerializer.Deserialize<Color> (
json,
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
// Assert
Assert.Equal (expectedColor, color);
}
[Fact]
public void TestSerializeColor_Black ()
{
// Arrange
var expectedJson = "\"Black\"";
// Act
string json = JsonSerializer.Serialize (
new Color (Color.Black),
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
// Assert
Assert.Equal (expectedJson, json);
}
[Fact]
public void TestSerializeColor_BrightRed ()
{
// Arrange
var expectedJson = "\"BrightRed\"";
// Act
string json = JsonSerializer.Serialize (
new Color (Color.BrightRed),
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
// Assert
Assert.Equal (expectedJson, json);
}
}

View File

@@ -0,0 +1,58 @@
using System.Text.Json;
namespace Terminal.Gui.ConfigurationTests;
public class ColorSchemeJsonConverterTests
{
//string json = @"
// {
// ""ColorSchemes"": {
// ""Base"": {
// ""normal"": {
// ""foreground"": ""White"",
// ""background"": ""Blue""
// },
// ""focus"": {
// ""foreground"": ""Black"",
// ""background"": ""Gray""
// },
// ""hotNormal"": {
// ""foreground"": ""BrightCyan"",
// ""background"": ""Blue""
// },
// ""hotFocus"": {
// ""foreground"": ""BrightBlue"",
// ""background"": ""Gray""
// },
// ""disabled"": {
// ""foreground"": ""DarkGray"",
// ""background"": ""Blue""
// }
// }
// }
// }";
[Fact]
[AutoInitShutdown]
public void TestColorSchemesSerialization ()
{
// Arrange
var expectedColorScheme = new ColorScheme
{
Normal = new Attribute (Color.White, Color.Blue),
Focus = new Attribute (Color.Black, Color.Gray),
HotNormal = new Attribute (Color.BrightCyan, Color.Blue),
HotFocus = new Attribute (Color.BrightBlue, Color.Gray),
Disabled = new Attribute (Color.DarkGray, Color.Blue)
};
string serializedColorScheme =
JsonSerializer.Serialize (expectedColorScheme, ConfigurationManagerTests._jsonOptions);
// Act
var actualColorScheme =
JsonSerializer.Deserialize<ColorScheme> (serializedColorScheme, ConfigurationManagerTests._jsonOptions);
// Assert
Assert.Equal (expectedColorScheme, actualColorScheme);
}
}

View File

@@ -1,355 +0,0 @@
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
namespace Terminal.Gui.ConfigurationTests;
public class ColorJsonConverterTests
{
[Theory]
[InlineData ("\"#000000\"", 0, 0, 0)]
public void DeserializesFromHexCode (string hexCode, int r, int g, int b)
{
// Arrange
var expected = new Color (r, g, b);
// Act
var actual = JsonSerializer.Deserialize<Color> (
hexCode,
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
//Assert
Assert.Equal (expected, actual);
}
[Theory]
[InlineData ("\"rgb(0,0,0)\"", 0, 0, 0)]
public void DeserializesFromRgb (string rgb, int r, int g, int b)
{
// Arrange
var expected = new Color (r, g, b);
// Act
var actual = JsonSerializer.Deserialize<Color> (
rgb,
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
//Assert
Assert.Equal (expected, actual);
}
[Theory]
[InlineData (ColorName16.Black, "Black")]
[InlineData (ColorName16.Blue, "Blue")]
[InlineData (ColorName16.Green, "Green")]
[InlineData (ColorName16.Cyan, "Cyan")]
[InlineData (ColorName16.Gray, "Gray")]
[InlineData (ColorName16.Red, "Red")]
[InlineData (ColorName16.Magenta, "Magenta")]
[InlineData (ColorName16.Yellow, "Yellow")]
[InlineData (ColorName16.DarkGray, "DarkGray")]
[InlineData (ColorName16.BrightBlue, "BrightBlue")]
[InlineData (ColorName16.BrightGreen, "BrightGreen")]
[InlineData (ColorName16.BrightCyan, "BrightCyan")]
[InlineData (ColorName16.BrightRed, "BrightRed")]
[InlineData (ColorName16.BrightMagenta, "BrightMagenta")]
[InlineData (ColorName16.BrightYellow, "BrightYellow")]
[InlineData (ColorName16.White, "White")]
public void SerializesEnumValuesAsStrings (ColorName16 colorName, string expectedJson)
{
var converter = new ColorJsonConverter ();
var options = new JsonSerializerOptions { Converters = { converter } };
string serialized = JsonSerializer.Serialize (new Color (colorName), options);
Assert.Equal ($"\"{expectedJson}\"", serialized);
}
[Theory (Skip = "Not anymore. If a W3C color matches, that's used")]
[InlineData (0, 0, 0, "\"#000000\"")]
[InlineData (0, 0, 1, "\"#000001\"")]
public void SerializesToHexCode (int r, int g, int b, string expected)
{
// Arrange
// Act
string actual = JsonSerializer.Serialize (
new Color (r, g, b),
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
//Assert
Assert.Equal (expected, actual);
}
[Theory]
[InlineData ("Black", Color.Black)]
[InlineData ("Blue", Color.Blue)]
[InlineData ("BrightBlue", Color.BrightBlue)]
[InlineData ("BrightCyan", Color.BrightCyan)]
[InlineData ("BrightGreen", Color.BrightGreen)]
[InlineData ("BrightMagenta", Color.BrightMagenta)]
[InlineData ("BrightRed", Color.BrightRed)]
[InlineData ("BrightYellow", Color.BrightYellow)]
[InlineData ("Yellow", Color.Yellow)]
[InlineData ("Cyan", Color.Cyan)]
[InlineData ("DarkGray", Color.DarkGray)]
[InlineData ("Gray", Color.Gray)]
[InlineData ("Green", Color.Green)]
[InlineData ("Magenta", Color.Magenta)]
[InlineData ("Red", Color.Red)]
[InlineData ("White", Color.White)]
public void TestColorDeserializationFromHumanReadableColorName16 (string colorName, ColorName16 expectedColor)
{
// Arrange
var json = $"\"{colorName}\"";
// Act
var actualColor = JsonSerializer.Deserialize<Color> (json, ConfigurationManagerTests._jsonOptions);
// Assert
Assert.Equal (new Color (expectedColor), actualColor);
}
[Fact]
public void TestDeserializeColor_Black ()
{
// Arrange
var json = "\"Black\"";
var expectedColor = new Color ("Black");
// Act
var color = JsonSerializer.Deserialize<Color> (
json,
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
// Assert
Assert.Equal (expectedColor, color);
}
[Fact]
public void TestDeserializeColor_BrightRed ()
{
// Arrange
var json = "\"BrightRed\"";
var expectedColor = Color.BrightRed;
// Act
var color = JsonSerializer.Deserialize<Color> (
json,
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
// Assert
Assert.Equal (expectedColor, color);
}
[Fact]
public void TestSerializeColor_Black ()
{
// Arrange
var expectedJson = "\"Black\"";
// Act
string json = JsonSerializer.Serialize (
new Color (Color.Black),
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
// Assert
Assert.Equal (expectedJson, json);
}
[Fact]
public void TestSerializeColor_BrightRed ()
{
// Arrange
var expectedJson = "\"BrightRed\"";
// Act
string json = JsonSerializer.Serialize (
new Color (Color.BrightRed),
new JsonSerializerOptions { Converters = { new ColorJsonConverter () } }
);
// Assert
Assert.Equal (expectedJson, json);
}
}
public class AttributeJsonConverterTests
{
[Fact]
public void TestDeserialize ()
{
// Test deserializing from human-readable color names
var json = "{\"Foreground\":\"Blue\",\"Background\":\"Green\"}";
var attribute = JsonSerializer.Deserialize<Attribute> (json, ConfigurationManagerTests._jsonOptions);
Assert.Equal (Color.Blue, attribute.Foreground.GetClosestNamedColor16 ());
Assert.Equal (Color.Green, attribute.Background.GetClosestNamedColor16 ());
// Test deserializing from RGB values
json = "{\"Foreground\":\"rgb(255,0,0)\",\"Background\":\"rgb(0,255,0)\"}";
attribute = JsonSerializer.Deserialize<Attribute> (json, ConfigurationManagerTests._jsonOptions);
Assert.Equal (Color.Red, attribute.Foreground.GetClosestNamedColor16 ());
Assert.Equal (Color.BrightGreen, attribute.Background.GetClosestNamedColor16 ());
}
[Fact]
[AutoInitShutdown]
public void TestSerialize ()
{
// Test serializing to human-readable color names
var attribute = new Attribute (Color.Blue, Color.Green);
string json = JsonSerializer.Serialize (attribute, ConfigurationManagerTests._jsonOptions);
Assert.Equal ("{\"Foreground\":\"Blue\",\"Background\":\"Green\"}", json);
}
}
public class ColorSchemeJsonConverterTests
{
//string json = @"
// {
// ""ColorSchemes"": {
// ""Base"": {
// ""normal"": {
// ""foreground"": ""White"",
// ""background"": ""Blue""
// },
// ""focus"": {
// ""foreground"": ""Black"",
// ""background"": ""Gray""
// },
// ""hotNormal"": {
// ""foreground"": ""BrightCyan"",
// ""background"": ""Blue""
// },
// ""hotFocus"": {
// ""foreground"": ""BrightBlue"",
// ""background"": ""Gray""
// },
// ""disabled"": {
// ""foreground"": ""DarkGray"",
// ""background"": ""Blue""
// }
// }
// }
// }";
[Fact]
[AutoInitShutdown]
public void TestColorSchemesSerialization ()
{
// Arrange
var expectedColorScheme = new ColorScheme
{
Normal = new Attribute (Color.White, Color.Blue),
Focus = new Attribute (Color.Black, Color.Gray),
HotNormal = new Attribute (Color.BrightCyan, Color.Blue),
HotFocus = new Attribute (Color.BrightBlue, Color.Gray),
Disabled = new Attribute (Color.DarkGray, Color.Blue)
};
string serializedColorScheme =
JsonSerializer.Serialize (expectedColorScheme, ConfigurationManagerTests._jsonOptions);
// Act
var actualColorScheme =
JsonSerializer.Deserialize<ColorScheme> (serializedColorScheme, ConfigurationManagerTests._jsonOptions);
// Assert
Assert.Equal (expectedColorScheme, actualColorScheme);
}
}
public class KeyCodeJsonConverterTests
{
[Theory]
[InlineData (KeyCode.A, "A")]
[InlineData (KeyCode.A | KeyCode.ShiftMask, "A, ShiftMask")]
[InlineData (KeyCode.A | KeyCode.CtrlMask, "A, CtrlMask")]
[InlineData (KeyCode.A | KeyCode.AltMask | KeyCode.CtrlMask, "A, CtrlMask, AltMask")]
[InlineData ((KeyCode)'a' | KeyCode.AltMask | KeyCode.CtrlMask, "Space, A, CtrlMask, AltMask")]
[InlineData ((KeyCode)'a' | KeyCode.ShiftMask, "Space, A, ShiftMask")]
[InlineData (KeyCode.Delete | KeyCode.AltMask | KeyCode.CtrlMask, "Delete, CtrlMask, AltMask")]
[InlineData (KeyCode.D4, "D4")]
[InlineData (KeyCode.Esc, "Esc")]
public void TestKeyRoundTripConversion (KeyCode key, string expectedStringTo)
{
// Arrange
var options = new JsonSerializerOptions ();
options.Converters.Add (new KeyCodeJsonConverter ());
// Act
string json = JsonSerializer.Serialize (key, options);
var deserializedKey = JsonSerializer.Deserialize<KeyCode> (json, options);
// Assert
Assert.Equal (expectedStringTo, deserializedKey.ToString ());
}
}
public class KeyJsonConverterTests
{
[Theory]
[InlineData (KeyCode.A, "\"a\"")]
[InlineData ((KeyCode)'â', "\"â\"")]
[InlineData (KeyCode.A | KeyCode.ShiftMask, "\"A\"")]
[InlineData (KeyCode.A | KeyCode.CtrlMask, "\"Ctrl+A\"")]
[InlineData (KeyCode.A | KeyCode.AltMask | KeyCode.CtrlMask, "\"Ctrl+Alt+A\"")]
[InlineData (KeyCode.Delete | KeyCode.AltMask | KeyCode.CtrlMask, "\"Ctrl+Alt+Delete\"")]
[InlineData (KeyCode.D4, "\"4\"")]
[InlineData (KeyCode.Esc, "\"Esc\"")]
public void TestKey_Serialize (KeyCode key, string expected)
{
// Arrange
var options = new JsonSerializerOptions ();
options.Converters.Add (new KeyJsonConverter ());
options.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
// Act
string json = JsonSerializer.Serialize ((Key)key, options);
// Assert
Assert.Equal (expected, json);
}
[Theory]
[InlineData (KeyCode.A, "a")]
[InlineData (KeyCode.A | KeyCode.ShiftMask, "A")]
[InlineData (KeyCode.A | KeyCode.CtrlMask, "Ctrl+A")]
[InlineData (KeyCode.A | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+A")]
[InlineData (KeyCode.Delete | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+Delete")]
[InlineData (KeyCode.D4, "4")]
[InlineData (KeyCode.Esc, "Esc")]
public void TestKeyRoundTripConversion (KeyCode key, string expectedStringTo)
{
// Arrange
var options = new JsonSerializerOptions ();
options.Converters.Add (new KeyJsonConverter ());
var encoderSettings = new TextEncoderSettings ();
encoderSettings.AllowCharacters ('+', '-');
encoderSettings.AllowRange (UnicodeRanges.BasicLatin);
options.Encoder = JavaScriptEncoder.Create (encoderSettings);
// Act
string json = JsonSerializer.Serialize ((Key)key, options);
var deserializedKey = JsonSerializer.Deserialize<Key> (json, options);
// Assert
Assert.Equal (expectedStringTo, deserializedKey.ToString ());
}
[Fact]
public void Separator_Property_Serializes_As_Glyph ()
{
// Act
string json = JsonSerializer.Serialize (Key.Separator, ConfigurationManager._serializerOptions);
// Assert
Assert.Equal ("\"+\"", json);
}
}

View File

@@ -0,0 +1,30 @@
using System.Text.Json;
namespace Terminal.Gui.ConfigurationTests;
public class KeyCodeJsonConverterTests
{
[Theory]
[InlineData (KeyCode.A, "A")]
[InlineData (KeyCode.A | KeyCode.ShiftMask, "A, ShiftMask")]
[InlineData (KeyCode.A | KeyCode.CtrlMask, "A, CtrlMask")]
[InlineData (KeyCode.A | KeyCode.AltMask | KeyCode.CtrlMask, "A, CtrlMask, AltMask")]
[InlineData ((KeyCode)'a' | KeyCode.AltMask | KeyCode.CtrlMask, "Space, A, CtrlMask, AltMask")]
[InlineData ((KeyCode)'a' | KeyCode.ShiftMask, "Space, A, ShiftMask")]
[InlineData (KeyCode.Delete | KeyCode.AltMask | KeyCode.CtrlMask, "Delete, CtrlMask, AltMask")]
[InlineData (KeyCode.D4, "D4")]
[InlineData (KeyCode.Esc, "Esc")]
public void TestKeyRoundTripConversion (KeyCode key, string expectedStringTo)
{
// Arrange
var options = new JsonSerializerOptions ();
options.Converters.Add (new KeyCodeJsonConverter ());
// Act
string json = JsonSerializer.Serialize (key, options);
var deserializedKey = JsonSerializer.Deserialize<KeyCode> (json, options);
// Assert
Assert.Equal (expectedStringTo, deserializedKey.ToString ());
}
}

View File

@@ -0,0 +1,127 @@
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
namespace Terminal.Gui.ConfigurationTests;
public class KeyJsonConverterTests
{
[Theory]
[InlineData (KeyCode.A, "\"a\"")]
[InlineData ((KeyCode)'â', "\"â\"")]
[InlineData (KeyCode.A | KeyCode.ShiftMask, "\"A\"")]
[InlineData (KeyCode.A | KeyCode.CtrlMask, "\"Ctrl+A\"")]
[InlineData (KeyCode.A | KeyCode.AltMask | KeyCode.CtrlMask, "\"Ctrl+Alt+A\"")]
[InlineData (KeyCode.Delete | KeyCode.AltMask | KeyCode.CtrlMask, "\"Ctrl+Alt+Delete\"")]
[InlineData (KeyCode.D4, "\"4\"")]
[InlineData (KeyCode.Esc, "\"Esc\"")]
[InlineData ((KeyCode)'+' | KeyCode.AltMask | KeyCode.CtrlMask, "\"Ctrl+Alt++\"")]
public void TestKey_Serialize (KeyCode key, string expected)
{
// Arrange
var options = new JsonSerializerOptions ();
options.Converters.Add (new KeyJsonConverter ());
options.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
// Act
string json = JsonSerializer.Serialize ((Key)key, options);
// Assert
Assert.Equal (expected, json);
}
[Theory]
[InlineData (KeyCode.A, "a")]
[InlineData (KeyCode.A | KeyCode.ShiftMask, "A")]
[InlineData (KeyCode.A | KeyCode.CtrlMask, "Ctrl+A")]
[InlineData (KeyCode.A | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+A")]
[InlineData (KeyCode.Delete | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+Delete")]
[InlineData (KeyCode.D4, "4")]
[InlineData (KeyCode.Esc, "Esc")]
[InlineData ((KeyCode)'+' | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt++")]
[InlineData ((KeyCode)'+' | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt++")]
public void TestKeyRoundTripConversion (KeyCode key, string expectedStringTo)
{
// Arrange
// Act
string json = JsonSerializer.Serialize ((Key)key, ConfigurationManager._serializerOptions);
var deserializedKey = JsonSerializer.Deserialize<Key> (json, ConfigurationManager._serializerOptions);
// Assert
Assert.Equal (expectedStringTo, deserializedKey.ToString ());
}
[Fact]
public void Separator_Property_Serializes_As_Glyph ()
{
// Act
string json = JsonSerializer.Serialize (Key.Separator, ConfigurationManager._serializerOptions);
// Assert
Assert.Equal ($"\"{Key.Separator}\"", json);
}
[Fact]
public void Separator_Property_Set_Changes_Serialization_Format ()
{
Rune savedSeparator = Key.Separator;
try
{
// Act
Key.Separator = (Rune)'*';
string json = JsonSerializer.Serialize (Key.Separator, ConfigurationManager._serializerOptions);
// Assert
Assert.Equal ("\"*\"", json);
}
finally
{
Key.Separator = savedSeparator;
}
Key.Separator = savedSeparator;
}
[Theory]
[InlineData ('A', '+', "\"Ctrl+Alt+A\"")]
[InlineData ('A', '-', "\"Ctrl+Alt+A\"")]
[InlineData ('A', '*', "\"Ctrl+Alt+A\"")]
[InlineData ('A', '@', "\"Ctrl+Alt+A\"")]
[InlineData ('A', '+', "\"Ctrl@Alt@A\"")]
[InlineData ('A', '-', "\"Ctrl@Alt@A\"")]
[InlineData ('A', '*', "\"Ctrl@Alt@A\"")]
[InlineData ('A', '@', "\"Ctrl@Alt@A\"")]
[InlineData ('+', '+', "\"Ctrl+Alt++\"")]
[InlineData ('+', '-', "\"Ctrl+Alt++\"")]
[InlineData ('+', '*', "\"Ctrl+Alt++\"")]
[InlineData ('+', '@', "\"Ctrl+Alt++\"")]
[InlineData ('+', '+', "\"Ctrl@Alt@+\"")]
[InlineData ('+', '-', "\"Ctrl@Alt@+\"")]
[InlineData ('+', '*', "\"Ctrl@Alt@+\"")]
[InlineData ('+', '@', "\"Ctrl@Alt@+\"")]
public void Separator_Property_Set_Deserialization_Works_With_Any (char keyChar, char separator, string json)
{
Rune savedSeparator = Key.Separator;
try
{
// Act
Key.Separator = (Rune)separator;
Key deserializedKey = JsonSerializer.Deserialize<Key> (json, ConfigurationManager._serializerOptions);
Key expectedKey = new Key ((KeyCode)keyChar).WithCtrl.WithAlt;
// Assert
Assert.Equal (expectedKey, deserializedKey);
}
finally
{
Key.Separator = savedSeparator;
}
Key.Separator = savedSeparator;
}
}

View File

@@ -17,9 +17,9 @@ public class KeyTests
{ "Alt+A", Key.A.WithAlt },
{ "Shift+A", Key.A.WithShift },
{ "A", Key.A.WithShift },
{ "â", new Key ((KeyCode)'â') },
{ "Shift+â", new Key ((KeyCode)'â' | KeyCode.ShiftMask) },
{ "Shift+Â", new Key ((KeyCode)'Â' | KeyCode.ShiftMask) },
{ "â", new ((KeyCode)'â') },
{ "Shift+â", new ((KeyCode)'â' | KeyCode.ShiftMask) },
{ "Shift+Â", new ((KeyCode)'Â' | KeyCode.ShiftMask) },
{ "Ctrl+Shift+CursorUp", Key.CursorUp.WithShift.WithCtrl },
{ "Ctrl+Alt+Shift+CursorUp", Key.CursorUp.WithShift.WithCtrl.WithAlt },
{ "ctrl+alt+shift+cursorup", Key.CursorUp.WithShift.WithCtrl.WithAlt },
@@ -439,7 +439,7 @@ public class KeyTests
[Fact]
public void ToString_ShouldReturnReadableString ()
{
var eventArgs = Key.A.WithCtrl;
Key eventArgs = Key.A.WithCtrl;
Assert.Equal ("Ctrl+A", eventArgs.ToString ());
}
@@ -454,8 +454,6 @@ public class KeyTests
[Theory]
[InlineData ("aa")]
[InlineData ("-1")]
[InlineData ("Crtl-A")]
[InlineData ("Ctrl=A")]
[InlineData ("Crtl")]
[InlineData ("99a")]
[InlineData ("a99")]
@@ -508,6 +506,8 @@ public class KeyTests
[InlineData ("Alt-A", KeyCode.A | KeyCode.AltMask)]
[InlineData ("A-Ctrl", KeyCode.A | KeyCode.CtrlMask)]
[InlineData ("Alt-A-Ctrl", KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask)]
[InlineData ("120", KeyCode.X)]
[InlineData ("Ctrl@A", KeyCode.A | KeyCode.CtrlMask)]
public void TryParse_ShouldReturnTrue_WhenValidKey (string keyString, KeyCode expected)
{
Assert.True (Key.TryParse (keyString, out Key key));
@@ -518,7 +518,7 @@ public class KeyTests
[Fact]
public void WithShift_ShouldReturnCorrectValue ()
{
var a = Key.A;
Key a = Key.A;
Assert.Equal (KeyCode.A | KeyCode.ShiftMask, a.WithShift);
Key CAD = Key.Delete.WithCtrl.WithAlt;
@@ -529,17 +529,17 @@ public class KeyTests
[Fact]
public void Equals_ShouldReturnTrue_WhenEqual ()
{
var a = Key.A;
var b = Key.A;
Key a = Key.A;
Key b = Key.A;
Assert.True (a.Equals (b));
}
[Fact]
public void Equals_Handled_Changed_ShouldReturnTrue_WhenEqual ()
{
var a = Key.A;
Key a = Key.A;
a.Handled = true;
var b = Key.A;
Key b = Key.A;
b.Handled = true;
Assert.True (a.Equals (b));
}
@@ -547,9 +547,9 @@ public class KeyTests
[Fact]
public void Equals_Handled_Changed_ShouldReturnFalse_WhenNotEqual ()
{
var a = Key.A;
Key a = Key.A;
a.Handled = true;
var b = Key.A;
Key b = Key.A;
Assert.False (a.Equals (b));
}