Skip to content

Commit 65e1c62

Browse files
committed
Feat: Add StringColorExtensions with tests for hex-to-RGB conversion
1 parent 6a9d957 commit 65e1c62

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using System.Diagnostics.CodeAnalysis;
5+
6+
// ReSharper disable once CheckNamespace
7+
namespace System;
8+
// ---------------------------------------------------------------------------------------------------------------------
9+
// Code
10+
// ---------------------------------------------------------------------------------------------------------------------
11+
public static class StringColorExtensions {
12+
public static string ConvertToRgbValues(this string? hexColor)
13+
=> hexColor.TryConvertToRgbValues(out string? rgbString) ? rgbString : throw new ArgumentException("Invalid hex color");
14+
15+
public static bool TryConvertToRgbValues(this string? hexColor, [NotNullWhen(true)] out string? rgbString) {
16+
if (hexColor.IsNullOrEmpty()) {
17+
rgbString = null;
18+
return false;
19+
}
20+
21+
ReadOnlySpan<char> hexSpan = hexColor.AsSpan();
22+
int offset = hexSpan.StartsWith('#') ? 1 : 0;
23+
24+
Range rRange;
25+
Range gRange;
26+
Range bRange;
27+
28+
switch (hexSpan.Length - offset) {
29+
case 3:
30+
rRange = offset..(offset + 1);
31+
gRange = (offset + 1)..(offset + 2);
32+
bRange = (offset + 2)..(offset + 3);
33+
break;
34+
case 6:
35+
rRange = offset..(offset + 2);
36+
gRange = (offset + 2)..(offset + 4);
37+
bRange = (offset + 4)..(offset + 6);
38+
break;
39+
default:
40+
rgbString = null;
41+
return false;
42+
}
43+
44+
if (!int.TryParse(hexSpan[rRange], Globalization.NumberStyles.HexNumber, null, out int r)
45+
|| !int.TryParse(hexSpan[gRange], Globalization.NumberStyles.HexNumber, null, out int g)
46+
|| !int.TryParse(hexSpan[bRange], Globalization.NumberStyles.HexNumber, null, out int b)) {
47+
rgbString = null;
48+
return false;
49+
}
50+
51+
rgbString = $"{r}, {g}, {b}";
52+
return true;
53+
}
54+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
namespace Tests.CodeOfChaos.Extensions;
5+
// ---------------------------------------------------------------------------------------------------------------------
6+
// Code
7+
// ---------------------------------------------------------------------------------------------------------------------
8+
public class StringColorExtensionTests {
9+
public static IEnumerable<Func<(string, string)>> ValidDataSources() {
10+
yield return () => ("#FF0000", "255, 0, 0");
11+
yield return () => ("FF0000", "255, 0, 0");
12+
yield return () => ("#00FF00", "0, 255, 0");
13+
yield return () => ("#0000FF", "0, 0, 255");
14+
yield return () => ("#F00", "15, 0, 0");
15+
yield return () => ("#FFF", "15, 15, 15");
16+
17+
// Common Colors
18+
yield return () => ("#FFFFFF", "255, 255, 255");
19+
yield return () => ("#000000", "0, 0, 0");
20+
yield return () => ("#808080", "128, 128, 128");
21+
yield return () => ("#FFF", "15, 15, 15");
22+
yield return () => ("#000", "0, 0, 0");
23+
24+
yield return () => ("#5BCEFA", "91, 206, 250");
25+
yield return () => ("#F5A9B8", "245, 169, 184");
26+
yield return () => ("#FFFFFF", "255, 255, 255");
27+
yield return () => ("#F5A9B8", "245, 169, 184");
28+
yield return () => ("#5BCEFA", "91, 206, 250");
29+
}
30+
31+
public static IEnumerable<Func<string?>> InvalidDataSources() {
32+
yield return () => null;
33+
yield return () => ("");
34+
yield return () => (" ");
35+
yield return () => ("#");
36+
yield return () => ("#F");
37+
yield return () => ("#FF");
38+
yield return () => ("#FFFF");
39+
yield return () => ("#FFFFF");
40+
yield return () => ("#FFFFFFF");
41+
yield return () => ("#GG0000");
42+
yield return () => ("GGGGGG");
43+
}
44+
45+
[Test]
46+
[MethodDataSource<StringColorExtensionTests>(nameof(ValidDataSources))]
47+
public async Task ConvertToRgbValues_ValidHexColors_ReturnsRgbString(string input, string expected) {
48+
// Act
49+
string result = input.ConvertToRgbValues();
50+
51+
// Assert
52+
await Assert.That(result).IsNotNull().And.IsEquatableOrEqualTo(expected);
53+
}
54+
55+
[Test]
56+
[MethodDataSource<StringColorExtensionTests>(nameof(InvalidDataSources))]
57+
public async Task ConvertToRgbValues_InvalidInput_ThrowsArgumentException(string? input) {
58+
// Act & Assert
59+
Assert.Throws<ArgumentException>(() => input.ConvertToRgbValues());
60+
}
61+
62+
63+
[Test]
64+
[MethodDataSource<StringColorExtensionTests>(nameof(ValidDataSources))]
65+
public async Task TryConvertToRgbValues_ValidHexColors_ReturnsTrue(string input, string expected) {
66+
// Act
67+
bool result = input.TryConvertToRgbValues(out string? rgb);
68+
69+
// Assert
70+
await Assert.That(result).IsTrue();
71+
await Assert.That(rgb).IsNotNull().And.IsEquatableOrEqualTo(expected);
72+
}
73+
74+
// ReSharper disable StringLiteralTypo
75+
[Test]
76+
[MethodDataSource<StringColorExtensionTests>(nameof(InvalidDataSources))]
77+
public async Task TryConvertToRgbValues_InvalidInput_ReturnsFalse(string? input) {
78+
// Act
79+
bool result = input.TryConvertToRgbValues(out string? rgb);
80+
81+
// Assert
82+
await Assert.That(result).IsFalse();
83+
await Assert.That(rgb).IsNull();
84+
}
85+
}

0 commit comments

Comments
 (0)