From 190682b45f420c817c7877db3d106b1b0dfca9c9 Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Sun, 14 Jan 2024 21:00:39 -0500 Subject: [PATCH 1/9] Added hex color conversion --- src/Spectre.Console/Color.cs | 20 +++++++++++++++++++ test/Spectre.Console.Tests/Unit/ColorTests.cs | 9 +++++++++ 2 files changed, 29 insertions(+) diff --git a/src/Spectre.Console/Color.cs b/src/Spectre.Console/Color.cs index 38825a991..560e1b1f3 100644 --- a/src/Spectre.Console/Color.cs +++ b/src/Spectre.Console/Color.cs @@ -55,6 +55,26 @@ public Color(byte red, byte green, byte blue) Number = null; } + /// + /// Initializes a new instance of the struct. + /// + /// The hex string of color. Accepts with and without leading #. + public Color(string hex) + { + if (hex.FirstOrDefault() == '#') + { + hex = hex.Substring(1); + } + byte r = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber); + byte g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); + byte b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber); + R = r; + G = g; + B = b; + IsDefault = false; + Number = null; + } + /// /// Blends two colors. /// diff --git a/test/Spectre.Console.Tests/Unit/ColorTests.cs b/test/Spectre.Console.Tests/Unit/ColorTests.cs index 5c07195be..6c7e6ee26 100644 --- a/test/Spectre.Console.Tests/Unit/ColorTests.cs +++ b/test/Spectre.Console.Tests/Unit/ColorTests.cs @@ -4,6 +4,15 @@ public sealed class ColorTests { public sealed class TheEqualsMethod { + + [Fact] + public void Initialize_Color_With_Hex() + { + var name = new Color("#800080").ToString(); + + name.ShouldBe("#800080 (RGB=128,0,128)"); + } + [Fact] public void Should_Consider_Color_And_Non_Color_Equal() { From 06022d96c12d602e5a7f1c4f5097c05968381c26 Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Sun, 14 Jan 2024 21:05:30 -0500 Subject: [PATCH 2/9] Updated formatting --- src/Spectre.Console/Color.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Spectre.Console/Color.cs b/src/Spectre.Console/Color.cs index 560e1b1f3..99e96d27c 100644 --- a/src/Spectre.Console/Color.cs +++ b/src/Spectre.Console/Color.cs @@ -63,11 +63,13 @@ public Color(string hex) { if (hex.FirstOrDefault() == '#') { - hex = hex.Substring(1); + hex = hex[1..]; } - byte r = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber); - byte g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); - byte b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber); + + var r = byte.Parse(hex[..2], NumberStyles.HexNumber); + var g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); + var b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber); + R = r; G = g; B = b; From 5223237b95d11dc02b4decfa92779c9eca13e357 Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Sun, 14 Jan 2024 21:17:08 -0500 Subject: [PATCH 3/9] Updated to range selection --- src/Spectre.Console/Color.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Spectre.Console/Color.cs b/src/Spectre.Console/Color.cs index 99e96d27c..bc9008acd 100644 --- a/src/Spectre.Console/Color.cs +++ b/src/Spectre.Console/Color.cs @@ -67,8 +67,8 @@ public Color(string hex) } var r = byte.Parse(hex[..2], NumberStyles.HexNumber); - var g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); - var b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber); + var g = byte.Parse(hex[2..4], NumberStyles.HexNumber); + var b = byte.Parse(hex[4..6], NumberStyles.HexNumber); R = r; G = g; From 2fabff1feac7b55542da7ff63773a8ae897cfdd3 Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Tue, 16 Jan 2024 11:35:58 -0500 Subject: [PATCH 4/9] Moved constructor to FromHex. Added TryFromHex --- src/Spectre.Console/Color.cs | 60 ++++++++++++------- test/Spectre.Console.Tests/Unit/ColorTests.cs | 2 +- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/Spectre.Console/Color.cs b/src/Spectre.Console/Color.cs index bc9008acd..207eea28c 100644 --- a/src/Spectre.Console/Color.cs +++ b/src/Spectre.Console/Color.cs @@ -55,27 +55,6 @@ public Color(byte red, byte green, byte blue) Number = null; } - /// - /// Initializes a new instance of the struct. - /// - /// The hex string of color. Accepts with and without leading #. - public Color(string hex) - { - if (hex.FirstOrDefault() == '#') - { - hex = hex[1..]; - } - - var r = byte.Parse(hex[..2], NumberStyles.HexNumber); - var g = byte.Parse(hex[2..4], NumberStyles.HexNumber); - var b = byte.Parse(hex[4..6], NumberStyles.HexNumber); - - R = r; - G = g; - B = b; - IsDefault = false; - Number = null; - } /// /// Blends two colors. @@ -235,6 +214,45 @@ public static Color FromInt32(int number) return ColorTable.GetColor(number); } + /// + /// Creates a color from a hexadecimal string representation. + /// + /// The hexadecimal string representation of the color. + /// The color created from the hexadecimal string. + public static Color FromHex(string hex) + { + if (hex.FirstOrDefault() == '#') + { + hex = hex[1..]; + } + + var r = byte.Parse(hex[..2], NumberStyles.HexNumber); + var g = byte.Parse(hex[2..4], NumberStyles.HexNumber); + var b = byte.Parse(hex[4..6], NumberStyles.HexNumber); + + return new Color(r, g, b); + } + + /// + /// Tries to convert a hexadecimal color code to a object. + /// + /// The hexadecimal color code. + /// When this method returns, contains the equivalent of the hexadecimal color code, if the conversion succeeded, or if the conversion failed. + /// true if the conversion succeeded; otherwise, false. + public static bool TryFromHex(string hex, out Color color) + { + try + { + color = FromHex(hex); + return true; + } + catch + { + color = Color.Default; + return false; + } + } + /// /// Converts a to a . /// diff --git a/test/Spectre.Console.Tests/Unit/ColorTests.cs b/test/Spectre.Console.Tests/Unit/ColorTests.cs index 6c7e6ee26..15d278125 100644 --- a/test/Spectre.Console.Tests/Unit/ColorTests.cs +++ b/test/Spectre.Console.Tests/Unit/ColorTests.cs @@ -8,7 +8,7 @@ public sealed class TheEqualsMethod [Fact] public void Initialize_Color_With_Hex() { - var name = new Color("#800080").ToString(); + var name = Color.FromHex("#800080").ToString(); name.ShouldBe("#800080 (RGB=128,0,128)"); } From b49c1b06ea35cb580cfb04c284af1cdd4e116a6b Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Tue, 16 Jan 2024 11:47:58 -0500 Subject: [PATCH 5/9] Fixed extra spacing linting --- src/Spectre.Console/Color.cs | 1 - test/Spectre.Console.Tests/Unit/ColorTests.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Spectre.Console/Color.cs b/src/Spectre.Console/Color.cs index 207eea28c..20d547eff 100644 --- a/src/Spectre.Console/Color.cs +++ b/src/Spectre.Console/Color.cs @@ -55,7 +55,6 @@ public Color(byte red, byte green, byte blue) Number = null; } - /// /// Blends two colors. /// diff --git a/test/Spectre.Console.Tests/Unit/ColorTests.cs b/test/Spectre.Console.Tests/Unit/ColorTests.cs index 15d278125..31a34ba85 100644 --- a/test/Spectre.Console.Tests/Unit/ColorTests.cs +++ b/test/Spectre.Console.Tests/Unit/ColorTests.cs @@ -4,7 +4,6 @@ public sealed class ColorTests { public sealed class TheEqualsMethod { - [Fact] public void Initialize_Color_With_Hex() { From 56c600db01a5cf68e5a0580186a27cb7e2eeed01 Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Thu, 18 Jan 2024 22:40:35 -0500 Subject: [PATCH 6/9] netstandard support --- src/Spectre.Console/Color.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Spectre.Console/Color.cs b/src/Spectre.Console/Color.cs index 20d547eff..396288983 100644 --- a/src/Spectre.Console/Color.cs +++ b/src/Spectre.Console/Color.cs @@ -220,14 +220,14 @@ public static Color FromInt32(int number) /// The color created from the hexadecimal string. public static Color FromHex(string hex) { - if (hex.FirstOrDefault() == '#') + if (hex.StartsWith('#')) { - hex = hex[1..]; + hex = hex.Substring(1); } - var r = byte.Parse(hex[..2], NumberStyles.HexNumber); - var g = byte.Parse(hex[2..4], NumberStyles.HexNumber); - var b = byte.Parse(hex[4..6], NumberStyles.HexNumber); + var r = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber); + var g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); + var b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber); return new Color(r, g, b); } From 7e313bdc23066aa13694798e1c57a8252ec610dd Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Fri, 19 Jan 2024 11:14:42 -0500 Subject: [PATCH 7/9] Netstandard2.0 doesn't support single char. Double quote strings required --- src/Spectre.Console/Color.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Spectre.Console/Color.cs b/src/Spectre.Console/Color.cs index 396288983..cbb1ea5ce 100644 --- a/src/Spectre.Console/Color.cs +++ b/src/Spectre.Console/Color.cs @@ -220,7 +220,7 @@ public static Color FromInt32(int number) /// The color created from the hexadecimal string. public static Color FromHex(string hex) { - if (hex.StartsWith('#')) + if (hex.StartsWith("#")) { hex = hex.Substring(1); } From 16f3d833aeb87c7ad60f06f9b862d08014fc9c56 Mon Sep 17 00:00:00 2001 From: Frank Ray <52075808+FrankRay78@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:24:26 +0000 Subject: [PATCH 8/9] Full unit test coverage of Color.FromHex and Color.TryFromHex --- test/Spectre.Console.Tests/Unit/ColorTests.cs | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/test/Spectre.Console.Tests/Unit/ColorTests.cs b/test/Spectre.Console.Tests/Unit/ColorTests.cs index 31a34ba85..9878212cf 100644 --- a/test/Spectre.Console.Tests/Unit/ColorTests.cs +++ b/test/Spectre.Console.Tests/Unit/ColorTests.cs @@ -1,3 +1,5 @@ +using System.Drawing; + namespace Spectre.Console.Tests.Unit; public sealed class ColorTests @@ -5,11 +7,56 @@ public sealed class ColorTests public sealed class TheEqualsMethod { [Fact] - public void Initialize_Color_With_Hex() + public void Should_Consider_Color_And_Color_From_Hex_Equal() { - var name = Color.FromHex("#800080").ToString(); + // Given + var color1 = new Color(128, 0, 128); - name.ShouldBe("#800080 (RGB=128,0,128)"); + // When + var color2 = Color.FromHex("#800080"); + + // Then + color2.ShouldBe(color1); + } + + [Fact] + public void Should_Consider_Color_And_Color_Try_From_Hex_Equal() + { + // Given + var color1 = new Color(128, 0, 128); + + // When + var result = Color.TryFromHex("#800080", out var color2); + + // Then + result.ShouldBeTrue(); + color2.ShouldBe(color1); + } + + [Fact] + public void Should_Not_Parse_Non_Color_From_Hex() + { + // Given, When + var result = Record.Exception(() => Color.FromHex("FOO")); + + // Then + result.ShouldBeOfType(); +#if NET7_0_OR_GREATER + result.Message.ShouldBe("The input string 'FO' was not in a correct format."); +#else + result.Message.ShouldBe("Input string was not in a correct format."); +#endif + } + + [Fact] + public void Should_Not_Parse_Non_Color_Try_From_Hex() + { + // Given, When + var result = Color.TryFromHex("FOO", out var color); + + // Then + result.ShouldBeFalse(); + color.ShouldBe(Color.Default); } [Fact] From cf479fe4f1eee8a98f839ce51f934ff29cbe1b0a Mon Sep 17 00:00:00 2001 From: Frank Ray <52075808+FrankRay78@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:36:11 +0000 Subject: [PATCH 9/9] Significantly bolstered unit test coverage --- src/Spectre.Console/Color.cs | 5 +++ test/Spectre.Console.Tests/Unit/ColorTests.cs | 45 +++++++++++-------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/Spectre.Console/Color.cs b/src/Spectre.Console/Color.cs index cbb1ea5ce..d3fc25396 100644 --- a/src/Spectre.Console/Color.cs +++ b/src/Spectre.Console/Color.cs @@ -220,6 +220,11 @@ public static Color FromInt32(int number) /// The color created from the hexadecimal string. public static Color FromHex(string hex) { + if (hex is null) + { + throw new ArgumentNullException(nameof(hex)); + } + if (hex.StartsWith("#")) { hex = hex.Substring(1); diff --git a/test/Spectre.Console.Tests/Unit/ColorTests.cs b/test/Spectre.Console.Tests/Unit/ColorTests.cs index 9878212cf..e2ea9dde1 100644 --- a/test/Spectre.Console.Tests/Unit/ColorTests.cs +++ b/test/Spectre.Console.Tests/Unit/ColorTests.cs @@ -6,53 +6,62 @@ public sealed class ColorTests { public sealed class TheEqualsMethod { - [Fact] - public void Should_Consider_Color_And_Color_From_Hex_Equal() + [Theory] + [InlineData("800080")] + [InlineData("#800080")] + public void Should_Consider_Color_And_Color_From_Hex_Equal(string color) { // Given var color1 = new Color(128, 0, 128); // When - var color2 = Color.FromHex("#800080"); + var color2 = Color.FromHex(color); // Then color2.ShouldBe(color1); } - [Fact] - public void Should_Consider_Color_And_Color_Try_From_Hex_Equal() + [Theory] + [InlineData("800080")] + [InlineData("#800080")] + public void Should_Consider_Color_And_Color_Try_From_Hex_Equal(string color) { // Given var color1 = new Color(128, 0, 128); // When - var result = Color.TryFromHex("#800080", out var color2); + var result = Color.TryFromHex(color, out var color2); // Then result.ShouldBeTrue(); color2.ShouldBe(color1); } - [Fact] - public void Should_Not_Parse_Non_Color_From_Hex() + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("#")] + [InlineData("#80")] + [InlineData("FOO")] + public void Should_Not_Parse_Non_Color_From_Hex(string noncolor) { // Given, When - var result = Record.Exception(() => Color.FromHex("FOO")); + var result = Record.Exception(() => Color.FromHex(noncolor)); // Then - result.ShouldBeOfType(); -#if NET7_0_OR_GREATER - result.Message.ShouldBe("The input string 'FO' was not in a correct format."); -#else - result.Message.ShouldBe("Input string was not in a correct format."); -#endif + result.ShouldBeAssignableTo(); } - [Fact] - public void Should_Not_Parse_Non_Color_Try_From_Hex() + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("#")] + [InlineData("#80")] + [InlineData("FOO")] + public void Should_Not_Parse_Non_Color_Try_From_Hex(string noncolor) { // Given, When - var result = Color.TryFromHex("FOO", out var color); + var result = Color.TryFromHex(noncolor, out var color); // Then result.ShouldBeFalse();