Skip to content
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using System.ComponentModel;
using System.Globalization;

namespace CommunityToolkit.Maui.Core.Extensions;

Expand All @@ -19,7 +20,7 @@ public static class ColorConversionExtensions
public static string ToRgbString(this Color color)
{
ArgumentNullException.ThrowIfNull(color);
return $"RGB({color.GetByteRed()},{color.GetByteGreen()},{color.GetByteBlue()})";
return FormattableString.Invariant($"RGB({color.GetByteRed()},{color.GetByteGreen()},{color.GetByteBlue()})");
}

/// <summary>
Expand All @@ -32,10 +33,24 @@ public static string ToRgbString(this Color color)
/// and <b>alpha</b> is a value between 0 and 1. (e.g. <c>RGBA(255,0,0,1)</c> for <see cref="Colors.Red"/>).
/// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="color"/> is null.</exception>
public static string ToRgbaString(this Color color, CultureInfo? cultureInfo = null)
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Do not use CultureInfo, this method should be culture invariant.")]
public static string ToRgbaString(this Color color, CultureInfo? cultureInfo) => ToRgbaString(color);


/// <summary>
/// Converts this <see cref="Color"/> to a <see cref="string"/> containing the red, green, blue and alpha components.
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert.</param>
/// <returns>
/// A <see cref="string"/> in the format: <c>RGBA(red,green,blue,alpha)</c> where <b>red</b>, <b>green</b> and <b>blue</b> will be a value between 0 and 255,
/// and <b>alpha</b> is a value between 0 and 1. (e.g. <c>RGBA(255,0,0,1)</c> for <see cref="Colors.Red"/>).
/// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="color"/> is null.</exception>
public static string ToRgbaString(this Color color)
{
ArgumentNullException.ThrowIfNull(color);
return $"RGBA({color.GetByteRed()},{color.GetByteGreen()},{color.GetByteBlue()},{color.Alpha.ToString(cultureInfo)})";
return FormattableString.Invariant($"RGBA({color.GetByteRed()},{color.GetByteGreen()},{color.GetByteBlue()},{color.Alpha})");
}

/// <summary>
Expand All @@ -50,7 +65,7 @@ public static string ToRgbaString(this Color color, CultureInfo? cultureInfo = n
public static string ToCmykString(this Color color)
{
ArgumentNullException.ThrowIfNull(color);
return $"CMYK({color.GetPercentCyan():P0},{color.GetPercentMagenta():P0},{color.GetPercentYellow():P0},{color.GetPercentBlackKey():P0})";
return FormattableString.Invariant($"CMYK({color.GetPercentCyan():0%},{color.GetPercentMagenta():0%},{color.GetPercentYellow():0%},{color.GetPercentBlackKey():0%})");
}

/// <summary>
Expand All @@ -63,10 +78,24 @@ public static string ToCmykString(this Color color)
/// 0% and 100% and <b>alpha</b> will be a value between 0 and 1. (e.g. <c>CMYKA(100%,100%,0%,100%,1)</c> for <see cref="Colors.Red"/>).
/// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="color"/> is null.</exception>
public static string ToCmykaString(this Color color, CultureInfo? cultureInfo = null)

[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Do not use CultureInfo, this method should be culture invariant.")]
public static string ToCmykaString(this Color color, CultureInfo? cultureInfo) => ToCmykaString(color);

/// <summary>
/// Converts this <see cref="Color"/> to a <see cref="string"/> containing the cyan, magenta, yellow, key and alpha components.
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert.</param>
/// <returns>
/// A <see cref="string"/> in the format: <c>CMYKA(cyan,magenta,yellow,key,alpha)</c> where <b>cyan</b>, <b>magenta</b>, <b>yellow </b>and <b>key</b> will be a value between
/// 0% and 100% and <b>alpha</b> will be a value between 0 and 1. (e.g. <c>CMYKA(100%,100%,0%,100%,1)</c> for <see cref="Colors.Red"/>).
/// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="color"/> is null.</exception>
public static string ToCmykaString(this Color color)
{
ArgumentNullException.ThrowIfNull(color);
return $"CMYKA({color.GetPercentCyan():P0},{color.GetPercentMagenta():P0},{color.GetPercentYellow():P0},{color.GetPercentBlackKey():P0},{color.Alpha.ToString(cultureInfo)})";
return FormattableString.Invariant($"CMYKA({color.GetPercentCyan():0%},{color.GetPercentMagenta():0%},{color.GetPercentYellow():0%},{color.GetPercentBlackKey():0%},{color.Alpha})");
}

/// <summary>
Expand All @@ -81,7 +110,7 @@ public static string ToCmykaString(this Color color, CultureInfo? cultureInfo =
public static string ToHslString(this Color color)
{
ArgumentNullException.ThrowIfNull(color);
return $"HSL({color.GetDegreeHue():0},{color.GetSaturation():P0},{color.GetLuminosity():P0})";
return FormattableString.Invariant($"HSL({color.GetDegreeHue():0},{color.GetSaturation():0%},{color.GetLuminosity():0%})");
}

/// <summary>
Expand All @@ -94,10 +123,23 @@ public static string ToHslString(this Color color)
/// will be a value between 0% and 100%, and <b>alpha</b> will be a value between 0 and 1. (e.g. <c>HSLA(0,100%,50%,1)</c> for <see cref="Colors.Red"/>).
/// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="color"/> is null.</exception>
public static string ToHslaString(this Color color, CultureInfo? cultureInfo = null)
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Do not use CultureInfo, this method should be culture invariant.")]
public static string ToHslaString(this Color color, CultureInfo? cultureInfo) => ToHslaString(color);

/// <summary>
/// Converts this <see cref="Color"/> to a <see cref="string"/> containing the hue, saturation, lightness and alpha components.
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert.</param>
/// <returns>
/// A <see cref="string"/> in the format: <c>HSLA(hue,saturation,lightness,alpha)</c> where <b>hue</b> will be a value between 0 and 360, <b>saturation</b> and <b>lightness</b>
/// will be a value between 0% and 100%, and <b>alpha</b> will be a value between 0 and 1. (e.g. <c>HSLA(0,100%,50%,1)</c> for <see cref="Colors.Red"/>).
/// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="color"/> is null.</exception>
public static string ToHslaString(this Color color)
{
ArgumentNullException.ThrowIfNull(color);
return $"HSLA({color.GetDegreeHue():0},{color.GetSaturation():P0},{color.GetLuminosity():P0},{color.Alpha.ToString(cultureInfo)})";
return FormattableString.Invariant($"HSLA({color.GetDegreeHue():0},{color.GetSaturation():0%},{color.GetLuminosity():0%},{color.Alpha})");
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CommunityToolkit.Maui.Behaviors;
using System.Threading.Tasks;
using CommunityToolkit.Maui.Behaviors;
using CommunityToolkit.Maui.UnitTests.Mocks;
using Xunit;
using Xunit.v3;
Expand All @@ -8,7 +9,7 @@ namespace CommunityToolkit.Maui.UnitTests.Behaviors;
public class ValidationBehaviorTests(ITestOutputHelper testOutputHelper) : BaseBehaviorTest<ValidationBehavior, VisualElement>(new MockValidationBehavior(), new View())
{
[Fact]
public void ValidateOnValueChanged()
public async Task ValidateOnValueChanged()
{
// Arrange
var entry = new Entry
Expand All @@ -18,6 +19,7 @@ public void ValidateOnValueChanged()
var behavior = new MockValidationBehavior()
{
ExpectedValue = "321",
SimulateValidationDelay = false,
Flags = ValidationFlags.ValidateOnValueChanged
};

Expand All @@ -26,6 +28,9 @@ public void ValidateOnValueChanged()
// Act
entry.Text = "321";

// Fails sometimes randomly without delay
await Task.Delay(10, TestContext.Current.CancellationToken);

// Assert
Assert.True(behavior.IsValid);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,15 @@ public class ColorToCmykStringConverterTests : BaseOneWayConverterTest<ColorToCm
},
};

[Theory]
[CulturedTheory(cultures: ["en-US", "uk-UA", "de-DE"])]
[MemberData(nameof(ValidInputData))]
public void ColorToCmykStringConverterValidInputTest(float red, float green, float blue, float alpha, string expectedResult)
{
var converter = new ColorToCmykStringConverter();
var color = new Color(red, green, blue, alpha);

var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, null);
var resultConvertFrom = converter.ConvertFrom(color);
var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, Thread.CurrentThread.CurrentCulture);
var resultConvertFrom = converter.ConvertFrom(color, Thread.CurrentThread.CurrentCulture);

Assert.Equal(expectedResult, resultConvert);
Assert.Equal(expectedResult, resultConvertFrom);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CommunityToolkit.Maui.Converters;
using System.Runtime.CompilerServices;
using CommunityToolkit.Maui.Converters;
using Xunit;

namespace CommunityToolkit.Maui.UnitTests.Converters;
Expand Down Expand Up @@ -135,29 +136,15 @@ public class ColorToCmykaStringConverterTests : BaseOneWayConverterTest<ColorToC
},
};

[Theory]
[CulturedTheory(cultures: ["en-US", "uk-UA", "de-DE"])]
[MemberData(nameof(ValidInputData))]
public void ColorToCmykaStringConverterValidInputTest(float red, float green, float blue, float alpha, string expectedResult)
{
var converter = new ColorToCmykaStringConverter();
var color = new Color(red, green, blue, alpha);

var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, new System.Globalization.CultureInfo("en-US"));
var resultConvertFrom = converter.ConvertFrom(color, new System.Globalization.CultureInfo("en-US"));

Assert.Equal(expectedResult, resultConvert);
Assert.Equal(expectedResult, resultConvertFrom);
}

[Fact]
public void ColorToRgbStringConverterCultureTest()
{
var expectedResult = "CMYKA(0%,0%,0%,100%,0,5)";
var converter = new ColorToCmykaStringConverter();
var color = new Color(0, 0, 0, 0.5f);

var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, new System.Globalization.CultureInfo("uk-UA"));
var resultConvertFrom = converter.ConvertFrom(color, new System.Globalization.CultureInfo("uk-UA"));
var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, Thread.CurrentThread.CurrentCulture);
var resultConvertFrom = converter.ConvertFrom(color, Thread.CurrentThread.CurrentCulture);

Assert.Equal(expectedResult, resultConvert);
Assert.Equal(expectedResult, resultConvertFrom);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,22 @@ public class ColorToHslStringConverterTests : BaseOneWayConverterTest<ColorToHsl
0.25f, 0.25f, 0.25f, 0, "HSL(0,0%,25%)"
},
{
0.25f, 0.25f, 1, 1, "HSL(240,100%,62%)"
0.25f, 0.25f, 1, 1, "HSL(240,100%,63%)"
},
{
0.25f, 0.25f, 1, 0, "HSL(240,100%,62%)"
0.25f, 0.25f, 1, 0, "HSL(240,100%,63%)"
},
{
0.25f, 1, 0.25f, 1, "HSL(120,100%,62%)"
0.25f, 1, 0.25f, 1, "HSL(120,100%,63%)"
},
{
0.25f, 1, 0.25f, 0, "HSL(120,100%,62%)"
0.25f, 1, 0.25f, 0, "HSL(120,100%,63%)"
},
{
0.75f, 1, 0.25f, 1, "HSL(80,100%,62%)"
0.75f, 1, 0.25f, 1, "HSL(80,100%,63%)"
},
{
0.75f, 1, 0.25f, 0, "HSL(80,100%,62%)"
0.75f, 1, 0.25f, 0, "HSL(80,100%,63%)"
},
{
0.75f, 0, 1, 1, "HSL(285,100%,50%)"
Expand All @@ -133,15 +133,15 @@ public class ColorToHslStringConverterTests : BaseOneWayConverterTest<ColorToHsl
},
};

[Theory]
[CulturedTheory(cultures: ["en-US", "uk-UA", "de-DE"])]
[MemberData(nameof(ValidInputData))]
public void ColorToHslStringConverterValidInputTest(float red, float green, float blue, float alpha, string expectedResult)
{
var converter = new ColorToHslStringConverter();
var color = new Color(red, green, blue, alpha);

var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, null);
var resultConvertFrom = converter.ConvertFrom(color);
var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, Thread.CurrentThread.CurrentCulture);
var resultConvertFrom = converter.ConvertFrom(color, Thread.CurrentThread.CurrentCulture);

Assert.Equal(expectedResult, resultConvert);
Assert.Equal(expectedResult, resultConvertFrom);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,22 +107,22 @@ public class ColorToHslaStringConverterTests : BaseOneWayConverterTest<ColorToHs
0.25f, 0.25f, 0.25f, 0, "HSLA(0,0%,25%,0)"
},
{
0.25f, 0.25f, 1, 1, "HSLA(240,100%,62%,1)"
0.25f, 0.25f, 1, 1, "HSLA(240,100%,63%,1)"
},
{
0.25f, 0.25f, 1, 0, "HSLA(240,100%,62%,0)"
0.25f, 0.25f, 1, 0, "HSLA(240,100%,63%,0)"
},
{
0.25f, 1, 0.25f, 1, "HSLA(120,100%,62%,1)"
0.25f, 1, 0.25f, 1, "HSLA(120,100%,63%,1)"
},
{
0.25f, 1, 0.25f, 0, "HSLA(120,100%,62%,0)"
0.25f, 1, 0.25f, 0, "HSLA(120,100%,63%,0)"
},
{
0.75f, 1, 0.25f, 1, "HSLA(80,100%,62%,1)"
0.75f, 1, 0.25f, 1, "HSLA(80,100%,63%,1)"
},
{
0.75f, 1, 0.25f, 0, "HSLA(80,100%,62%,0)"
0.75f, 1, 0.25f, 0, "HSLA(80,100%,63%,0)"
},
{
0.75f, 0, 1, 1, "HSLA(285,100%,50%,1)"
Expand All @@ -132,29 +132,15 @@ public class ColorToHslaStringConverterTests : BaseOneWayConverterTest<ColorToHs
},
};

[Theory]
[CulturedTheory(cultures: ["en-US", "uk-UA", "de-DE"])]
[MemberData(nameof(ValidInputData))]
public void ColorToRgbStringConverterValidInputTest(float red, float green, float blue, float alpha, string expectedResult)
{
var converter = new ColorToHslaStringConverter();
var color = new Color(red, green, blue, alpha);

var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, new System.Globalization.CultureInfo("en-US"));
var resultConvertFrom = converter.ConvertFrom(color, new System.Globalization.CultureInfo("en-US"));

Assert.Equal(expectedResult, resultConvert);
Assert.Equal(expectedResult, resultConvertFrom);
}

[Fact]
public void ColorToRgbStringConverterCultureTest()
{
var expectedResult = "HSLA(0,0%,0%,0,5)";
var converter = new ColorToHslaStringConverter();
var color = new Color(0, 0, 0, 0.5f);

var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, new System.Globalization.CultureInfo("uk-UA"));
var resultConvertFrom = converter.ConvertFrom(color, new System.Globalization.CultureInfo("uk-UA"));
var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, Thread.CurrentThread.CurrentCulture);
var resultConvertFrom = converter.ConvertFrom(color, Thread.CurrentThread.CurrentCulture);

Assert.Equal(expectedResult, resultConvert);
Assert.Equal(expectedResult, resultConvertFrom);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,20 +163,6 @@ public void ColorToRgbStringConverterConvertBackValidInputTest(float red, float
AssertColorComparison(expectedResult, resultConvertBackTo);
}

[Fact]
public void ColorToRgbStringConverterCultureTest()
{
var expectedResult = "RGBA(0,0,0,0,5)";
var converter = new ColorToRgbaStringConverter();
var color = new Color(0, 0, 0, 0.5f);

var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, new System.Globalization.CultureInfo("uk-UA"));
var resultConvertFrom = converter.ConvertFrom(color, new System.Globalization.CultureInfo("uk-UA"));

Assert.Equal(expectedResult, resultConvert);
Assert.Equal(expectedResult, resultConvertFrom);
}

[Fact]
public void ColorToRgbStringConverterNullInputTest()
{
Expand Down
Loading
Loading