diff --git a/Terminal.Gui/Configuration/ColorJsonConverter.cs b/Terminal.Gui/Configuration/ColorJsonConverter.cs index aae59b2d7..6da4490d9 100644 --- a/Terminal.Gui/Configuration/ColorJsonConverter.cs +++ b/Terminal.Gui/Configuration/ColorJsonConverter.cs @@ -4,7 +4,16 @@ using ColorHelper; namespace Terminal.Gui; -/// Json converter for the class. +/// +/// Json converter for the class. +/// +/// Serialization outputs a string with the color name if the color matches a name in +/// or the "#RRGGBB" hexadecimal representation (e.g. "#FF0000" for red). +/// +/// +/// 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. +/// internal class ColorJsonConverter : JsonConverter { private static ColorJsonConverter _instance; diff --git a/Terminal.Gui/Drawing/Color.Formatting.cs b/Terminal.Gui/Drawing/Color.Formatting.cs index 65cad5626..dd775fe04 100644 --- a/Terminal.Gui/Drawing/Color.Formatting.cs +++ b/Terminal.Gui/Drawing/Color.Formatting.cs @@ -501,7 +501,7 @@ public readonly partial record struct Color /// /// /// 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 string + /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any W3C color name."/> string /// values. /// /// diff --git a/Terminal.Gui/Input/Key.cs b/Terminal.Gui/Input/Key.cs index 5c92cc489..11f8d938e 100644 --- a/Terminal.Gui/Input/Key.cs +++ b/Terminal.Gui/Input/Key.cs @@ -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 public Key (KeyCode k) { KeyCode = k; } /// - /// Copy constructor. + /// Copy constructor. /// /// The Key to copy public Key (Key key) @@ -155,18 +154,13 @@ public class Key : EventArgs, IEquatable /// public Rune AsRune => ToRune (KeyCode); - private bool _handled = false; - /// /// 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. /// - public bool Handled - { - get => _handled; - set => _handled = value; - } + public bool Handled { get; set; } /// Gets a value indicating whether the Alt key was pressed (real or synthesized) /// if is alternate; otherwise, . @@ -339,11 +333,11 @@ public class Key : EventArgs, IEquatable 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 return default (Rune); } - return new Rune ((uint)baseKey); + return new ((uint)baseKey); } #region Operators @@ -381,17 +375,17 @@ public class Key : EventArgs, IEquatable /// Cast to a . /// - public static implicit operator Key (KeyCode keyCode) { return new Key (keyCode); } + public static implicit operator Key (KeyCode keyCode) { return new (keyCode); } /// Cast to a . /// See for more information. /// - public static implicit operator Key (char ch) { return new Key (ch); } + public static implicit operator Key (char ch) { return new (ch); } /// Cast to a . /// See for more information. /// - public static implicit operator Key (string str) { return new Key (str); } + public static implicit operator Key (string str) { return new (str); } /// Cast a to a . /// See for more information. @@ -399,10 +393,7 @@ public class Key : EventArgs, IEquatable public static implicit operator string (Key key) { return key.ToString (); } /// - 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.Equals (Key other) { return Equals (other); } @@ -568,7 +559,10 @@ public class Key : EventArgs, IEquatable /// Converts the provided string to a new instance. /// /// 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). + /// + /// The separator can be any character, not just (e.g. "Ctrl@Alt@X"). + /// /// /// The parsed value. /// A boolean value indicating whether parsing was successful. @@ -577,38 +571,88 @@ public class Key : EventArgs, IEquatable { 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 { 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 { keyCode = keyCode & ~KeyCode.Space; } - key = new Key (keyCode | modifiers); + + key = new (keyCode | modifiers); return true; } @@ -675,7 +720,7 @@ public class Key : EventArgs, IEquatable { 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 { parsedKeyCode = parsedKeyCode & ~KeyCode.Space; } - key = new Key (parsedKeyCode | modifiers); + + key = new (parsedKeyCode | modifiers); return true; } @@ -705,12 +751,12 @@ public class Key : EventArgs, IEquatable 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 if (GetIsKeyCodeAtoZ (parsedKeyCode)) { - key = new Key (parsedKeyCode | (modifiers & ~KeyCode.Space)); + key = new (parsedKeyCode | (modifiers & ~KeyCode.Space)); return true; } diff --git a/UnitTests/Configuration/AttributeJsonConverterTests.cs b/UnitTests/Configuration/AttributeJsonConverterTests.cs new file mode 100644 index 000000000..8487e0271 --- /dev/null +++ b/UnitTests/Configuration/AttributeJsonConverterTests.cs @@ -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 (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 (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); + } +} \ No newline at end of file diff --git a/UnitTests/Configuration/ColorJsonConverterTests.cs b/UnitTests/Configuration/ColorJsonConverterTests.cs new file mode 100644 index 000000000..ed06432d7 --- /dev/null +++ b/UnitTests/Configuration/ColorJsonConverterTests.cs @@ -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 ( + 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 ( + 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 (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 ( + 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 ( + 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); + } +} \ No newline at end of file diff --git a/UnitTests/Configuration/ColorSchemeJsonConverterTests.cs b/UnitTests/Configuration/ColorSchemeJsonConverterTests.cs new file mode 100644 index 000000000..2fd50838b --- /dev/null +++ b/UnitTests/Configuration/ColorSchemeJsonConverterTests.cs @@ -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 (serializedColorScheme, ConfigurationManagerTests._jsonOptions); + + // Assert + Assert.Equal (expectedColorScheme, actualColorScheme); + } +} \ No newline at end of file diff --git a/UnitTests/Configuration/JsonConverterTests.cs b/UnitTests/Configuration/JsonConverterTests.cs deleted file mode 100644 index 2d1262647..000000000 --- a/UnitTests/Configuration/JsonConverterTests.cs +++ /dev/null @@ -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 ( - 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 ( - 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 (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 ( - 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 ( - 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 (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 (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 (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 (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 (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); - } -} diff --git a/UnitTests/Configuration/KeyCodeJsonConverterTests.cs b/UnitTests/Configuration/KeyCodeJsonConverterTests.cs new file mode 100644 index 000000000..0ee4fd877 --- /dev/null +++ b/UnitTests/Configuration/KeyCodeJsonConverterTests.cs @@ -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 (json, options); + + // Assert + Assert.Equal (expectedStringTo, deserializedKey.ToString ()); + } +} \ No newline at end of file diff --git a/UnitTests/Configuration/KeyJsonConverterTests.cs b/UnitTests/Configuration/KeyJsonConverterTests.cs new file mode 100644 index 000000000..4e716c866 --- /dev/null +++ b/UnitTests/Configuration/KeyJsonConverterTests.cs @@ -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 (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 (json, ConfigurationManager._serializerOptions); + + Key expectedKey = new Key ((KeyCode)keyChar).WithCtrl.WithAlt; + // Assert + Assert.Equal (expectedKey, deserializedKey); + } + finally + { + Key.Separator = savedSeparator; + } + + Key.Separator = savedSeparator; + } +} \ No newline at end of file diff --git a/UnitTests/Input/KeyTests.cs b/UnitTests/Input/KeyTests.cs index b3973b80e..fa2695e5f 100644 --- a/UnitTests/Input/KeyTests.cs +++ b/UnitTests/Input/KeyTests.cs @@ -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)); }