Skip to content

Commit

Permalink
Improve the usage experience with IsInRangeConverter in XAML (#1983)
Browse files Browse the repository at this point in the history
* Improve the usage experience with IsInRangeConverter in XAML
Updated unit tests to be cleaner

* Removed unused properties
Validate that the MinValue and MaxValue types can be converted to the valueType.

* Update src/CommunityToolkit.Maui/Converters/IsInRangeConverter.shared.cs

Co-authored-by: Shaun Lawrence <shaunrlawrence@gmail.com>

* Update src/CommunityToolkit.Maui/Converters/IsInRangeConverter.shared.cs

Co-authored-by: Shaun Lawrence <shaunrlawrence@gmail.com>

* Update src/CommunityToolkit.Maui/Converters/IsInRangeConverter.shared.cs

Co-authored-by: Shaun Lawrence <shaunrlawrence@gmail.com>

* Update src/CommunityToolkit.Maui/Converters/IsInRangeConverter.shared.cs

Co-authored-by: Shaun Lawrence <shaunrlawrence@gmail.com>

* Removed 'Ignore Spelling'
Removed unnecessary checks
Removed no longer used method.

* Removed unnecessary checks.
Updated tests to use correct exception thrown then using incompatible TValue objects.

* Renamed variable to correct naming.

* Reinstate BindableProperty definitions

---------

Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com>
Co-authored-by: Shaun Lawrence <shaunrlawrence@gmail.com>
Co-authored-by: Shaun Lawrence <17139988+bijington@users.noreply.github.com>
  • Loading branch information
4 people committed Aug 21, 2024
1 parent 6486b6d commit f5cf4b4
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public class IsInRangeConverterTests : BaseOneWayConverterTest<IsInRangeConverte
{
public const string FalseTestObject = nameof(FalseTestObject);
public const string TrueTestObject = nameof(TrueTestObject);
public const DateTimeKind UtcDateTimeKind = DateTimeKind.Utc;
public const DateTimeKind UnspecifiedDateTimeKind = DateTimeKind.Unspecified;

public enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };

Expand All @@ -32,17 +34,17 @@ public enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
[new DateOnly(2022, 01, 02), new DateOnly(2022, 5, 5), new DateOnly(2022, 12, 25), TrueTestObject, FalseTestObject, FalseTestObject],
[new DateOnly(2022, 12, 26), new DateOnly(2022, 5, 5), new DateOnly(2022, 12, 25), TrueTestObject, FalseTestObject, FalseTestObject],
// System.DateTime
[new DateTime(2022, 07, 07), new DateTime(2022, 5, 5), new DateTime(2022, 12, 25), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTime(2022, 05, 05), new DateTime(2022, 5, 5), new DateTime(2022, 12, 25), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTime(2022, 12, 25), new DateTime(2022, 5, 5), new DateTime(2022, 12, 25), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTime(2022, 01, 02), new DateTime(2022, 5, 5), new DateTime(2022, 12, 25), TrueTestObject, FalseTestObject, FalseTestObject],
[new DateTime(2022, 12, 26), new DateTime(2022, 5, 5), new DateTime(2022, 12, 25), TrueTestObject, FalseTestObject, FalseTestObject],
[new DateTime(2022, 07, 07, 0, 0, 0, UtcDateTimeKind), new DateTime(2022, 5, 5, 0, 0, 0, UtcDateTimeKind), new DateTime(2022, 12, 25, 0, 0, 0, UtcDateTimeKind), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTime(2022, 05, 05, 0, 0, 0, UtcDateTimeKind), new DateTime(2022, 5, 5, 0, 0, 0, UtcDateTimeKind), new DateTime(2022, 12, 25, 0, 0, 0, UtcDateTimeKind), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTime(2022, 12, 25, 0, 0, 0, UtcDateTimeKind), new DateTime(2022, 5, 5, 0, 0, 0, UtcDateTimeKind), new DateTime(2022, 12, 25, 0, 0, 0, UtcDateTimeKind), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTime(2022, 01, 02, 0, 0, 0, UtcDateTimeKind), new DateTime(2022, 5, 5, 0, 0, 0, UtcDateTimeKind), new DateTime(2022, 12, 25, 0, 0, 0, UtcDateTimeKind), TrueTestObject, FalseTestObject, FalseTestObject],
[new DateTime(2022, 12, 26, 0, 0, 0, UtcDateTimeKind), new DateTime(2022, 5, 5, 0, 0, 0, UtcDateTimeKind), new DateTime(2022, 12, 25, 0, 0, 0, UtcDateTimeKind), TrueTestObject, FalseTestObject, FalseTestObject],
// System.DateTimeOffset
[new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(3, 0, 0)), new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(4, 0, 0)), new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(2, 0, 0)), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(4, 0, 0)), new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(4, 0, 0)), new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(2, 0, 0)), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(2, 0, 0)), new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(4, 0, 0)), new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(2, 0, 0)), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(1, 0, 0)), new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(4, 0, 0)), new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(2, 0, 0)), TrueTestObject, FalseTestObject, FalseTestObject],
[new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(5, 0, 0)), new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(4, 0, 0)), new DateTimeOffset(new DateTime(1973, 1, 1), new TimeSpan(2, 0, 0)), TrueTestObject, FalseTestObject, FalseTestObject],
[new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(3, 0, 0)), new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(4, 0, 0)), new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(2, 0, 0)), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(4, 0, 0)), new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(4, 0, 0)), new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(2, 0, 0)), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(2, 0, 0)), new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(4, 0, 0)), new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(2, 0, 0)), TrueTestObject, FalseTestObject, TrueTestObject],
[new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(1, 0, 0)), new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(4, 0, 0)), new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(2, 0, 0)), TrueTestObject, FalseTestObject, FalseTestObject],
[new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(5, 0, 0)), new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(4, 0, 0)), new DateTimeOffset(DateTime.SpecifyKind(new DateTime(1973, 1, 1, 0, 0, 0, UnspecifiedDateTimeKind), UnspecifiedDateTimeKind), new TimeSpan(2, 0, 0)), TrueTestObject, FalseTestObject, FalseTestObject],
// System.Decimal
[new decimal(037.73), new decimal(7.73), new decimal(73.37), TrueTestObject, FalseTestObject, TrueTestObject],
[new decimal(007.73), new decimal(7.73), new decimal(73.37), TrueTestObject, FalseTestObject, TrueTestObject],
Expand Down Expand Up @@ -181,7 +183,7 @@ public enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
[InlineData(20d, "A", 'B', TrueTestObject, FalseTestObject)]
[InlineData(20d, 1d, 'B', TrueTestObject, FalseTestObject)]
[InlineData(20d, "A", 1d, TrueTestObject, FalseTestObject)]
public void InvalidIComparableThrowArgumentException(IComparable value, IComparable comparingMinValue, IComparable comparingMaxValue, object trueObject, object falseObject)
public void InvalidArgumentException(IComparable value, IComparable comparingMinValue, IComparable comparingMaxValue, object trueObject, object falseObject)
{
IsInRangeConverter isInRangeConverter = new()
{
Expand Down Expand Up @@ -223,7 +225,7 @@ public void InvalidValuesThrowArgumentException(IComparable value, IComparable?
};

Assert.Throws<ArgumentException>(() => ((ICommunityToolkitValueConverter)isInRangeConverter).Convert(value, typeof(object), null, CultureInfo.CurrentCulture));
Assert.Throws<ArgumentException>(() => isInRangeConverter.ConvertFrom(value));
Assert.Throws<ArgumentException>(() => isInRangeConverter.ConvertFrom(value, CultureInfo.CurrentCulture));
}

[Theory]
Expand All @@ -239,7 +241,7 @@ public void IsInRangeConverterConvertFrom(IComparable value, IComparable compari
};

object? convertResult = ((ICommunityToolkitValueConverter)isInRangeConverter).Convert(value, typeof(object), null, CultureInfo.CurrentCulture);
object convertFromResult = isInRangeConverter.ConvertFrom(value);
object convertFromResult = isInRangeConverter.ConvertFrom(value, CultureInfo.CurrentCulture);

Assert.Equal(expectedResult, convertResult);
Assert.Equal(expectedResult, convertFromResult);
Expand All @@ -260,7 +262,7 @@ public void NullToMinValueIsInRangeConverterConvertFrom(IComparable value, IComp
TrueObject = trueObject,
};

object convertFromResult = isInRangeConverter.ConvertFrom(value);
object convertFromResult = isInRangeConverter.ConvertFrom(value, CultureInfo.CurrentCulture);
Assert.Equal(expectedResult, convertFromResult);
}

Expand All @@ -277,7 +279,7 @@ public void ReturnObjectsNullExpectBoolReturn(IComparable value, IComparable com
TrueObject = trueObject,
};

object convertFromResult = isInRangeConverter.ConvertFrom(value);
object convertFromResult = isInRangeConverter.ConvertFrom(value, CultureInfo.CurrentCulture);
Assert.Equal(expectedResult, convertFromResult);
}
}
102 changes: 46 additions & 56 deletions src/CommunityToolkit.Maui/Converters/IsInRangeConverter.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,67 @@
namespace CommunityToolkit.Maui.Converters;

/// <summary>Converts the incoming value to a <see cref="bool"/> indicating whether or not the value is within a range.</summary>
public sealed class IsInRangeConverter : IsInRangeConverter<object>
{
}
public sealed class IsInRangeConverter : IsInRangeConverter<IComparable, object>;

/// <summary>Converts the incoming value to a <see cref="bool"/> indicating whether or not the value is within a range.</summary>
public abstract class IsInRangeConverter<TObject> : BaseConverterOneWay<IComparable, object>
public abstract class IsInRangeConverter<TValue, TReturnObject> : BaseConverterOneWay<TValue, object> where TValue : IComparable
{
/// <summary>
/// Bindable property for <see cref="MinValue"/>
/// Bindable property for <see cref="FalseObject"/>
/// </summary>
public static readonly BindableProperty MinValueProperty = BindableProperty.Create(nameof(MinValue), typeof(IComparable), typeof(IsInRangeConverter<TObject>));

public static readonly BindableProperty FalseObjectProperty = BindableProperty.Create(nameof(FalseObject), typeof(TReturnObject?), typeof(IsInRangeConverter<TValue, TReturnObject>));
/// <summary>
/// Bindable property for <see cref="MaxValue"/>
/// </summary>
public static readonly BindableProperty MaxValueProperty = BindableProperty.Create(nameof(MaxValue), typeof(IComparable), typeof(IsInRangeConverter<TObject>));

public static readonly BindableProperty MaxValueProperty = BindableProperty.Create(nameof(MaxValue), typeof(TValue), typeof(IsInRangeConverter<TValue, TReturnObject>));
/// <summary>
/// Bindable property for <see cref="TrueObject"/>
/// Bindable property for <see cref="MinValue"/>
/// </summary>
public static readonly BindableProperty TrueObjectProperty = BindableProperty.Create(nameof(TrueObject), typeof(TObject?), typeof(IsInRangeConverter<TObject>));
public static readonly BindableProperty MinValueProperty = BindableProperty.Create(nameof(MinValue), typeof(TValue), typeof(IsInRangeConverter<TValue, TReturnObject>));

/// <summary>
/// Bindable property for <see cref="FalseObject"/>
/// Bindable property for <see cref="TrueObject"/>
/// </summary>
public static readonly BindableProperty FalseObjectProperty = BindableProperty.Create(nameof(FalseObject), typeof(TObject?), typeof(IsInRangeConverter<TObject>));

public static readonly BindableProperty TrueObjectProperty = BindableProperty.Create(nameof(TrueObject), typeof(TReturnObject?), typeof(IsInRangeConverter<TValue, TReturnObject>));
/// <inheritdoc/>
public override object DefaultConvertReturnValue { get; set; } = new();

/// <summary>Minimum value.</summary>
public IComparable? MinValue
/// <summary>If supplied this value will be returned when the converter receives an input value that is <b>outside</b> of the <see cref="MinValue" /> and <see cref="MaxValue" />s.</summary>
public TReturnObject? FalseObject
{
get => (IComparable?)GetValue(MinValueProperty);
set => SetValue(MinValueProperty, value);
get => (TReturnObject?)GetValue(FalseObjectProperty);
set => SetValue(FalseObjectProperty, value);
}

/// <summary>Maximum value.</summary>
public IComparable? MaxValue
/// <summary>The upper bounds of the range to compare against when determining whether the input value to the convert is within range.</summary>
public TValue? MaxValue
{
get => (IComparable?)GetValue(MaxValueProperty);
get => (TValue?)GetValue(MaxValueProperty);
set => SetValue(MaxValueProperty, value);
}

/// <summary>The object that corresponds to True value.</summary>
public TObject? TrueObject
/// <summary>The lower bounds of the range to compare against when determining whether the input value to the convert is within range.</summary>
public TValue? MinValue
{
get => (TObject?)GetValue(TrueObjectProperty);
set => SetValue(TrueObjectProperty, value);
get => (TValue?)GetValue(MinValueProperty);
set => SetValue(MinValueProperty, value);
}

/// <summary>The object that corresponds to False value.</summary>
public TObject? FalseObject
/// <summary>If supplied this value will be returned when the converter receives an input value that is <b>inside</b> (inclusive) of the <see cref="MinValue" /> and <see cref="MaxValue" />s.</summary>
public TReturnObject? TrueObject
{
get => (TObject?)GetValue(FalseObjectProperty);
set => SetValue(FalseObjectProperty, value);
get => (TReturnObject?)GetValue(TrueObjectProperty);
set => SetValue(TrueObjectProperty, value);
}

/// <summary>Converts an object that implemented IComparable to a <see cref="bool"/> based on the object being within a <see cref="MinValue"/> and <see cref="MaxValue"/> range.</summary>
/// <param name="value">The value to convert.</param>
/// <param name="culture">(Not Used)</param>
/// <returns>The object assigned to <see cref="TrueObject"/> if value is between <see cref="MinValue"/> and <see cref="MaxValue"/> then <see cref="TrueObject"/> returns true, otherwise the value assigned to <see cref="FalseObject"/>.</returns>
public override object ConvertFrom(IComparable value, CultureInfo? culture = null)
/// <returns>The object assigned to <see cref="TrueObject"/> if value is between <see cref="MinValue"/> and <see cref="MaxValue"/> returns true, otherwise the value assigned to <see cref="FalseObject"/>.</returns>
public override object ConvertFrom(TValue value, CultureInfo? culture)
{
ArgumentNullException.ThrowIfNull(value);

Expand All @@ -79,37 +77,29 @@ public override object ConvertFrom(IComparable value, CultureInfo? culture = nul
throw new InvalidOperationException($"{nameof(TrueObject)} and {nameof(FalseObject)} should either be both defined or both omitted.");
}

var valueType = value.GetType();
if (MinValue is not null && MinValue.GetType() != valueType)
{
throw new ArgumentException($"{nameof(value)} is expected to be of type {nameof(MinValue)}, but is {valueType}", nameof(value));
}

if (MaxValue is not null && MaxValue.GetType() != valueType)
{
throw new ArgumentException($"{nameof(value)} is expected to be of type {nameof(MaxValue)}, but is {valueType}", nameof(value));
}

var shouldReturnObjectResult = TrueObject is not null && FalseObject is not null;

bool shouldReturnObjectResult = TrueObject is not null && FalseObject is not null;
if (MaxValue is null)
{
return EvaluateCondition(value.CompareTo(MinValue) >= 0, shouldReturnObjectResult);
}

if (MinValue is null)
{
return EvaluateCondition(value.CompareTo(MaxValue) <= 0, shouldReturnObjectResult);
}

return EvaluateCondition(value.CompareTo(MinValue) >= 0 && value.CompareTo(MaxValue) <= 0, shouldReturnObjectResult);
return MinValue is null
? EvaluateCondition(value.CompareTo(MaxValue) <= 0, shouldReturnObjectResult)
: EvaluateCondition(value.CompareTo(MinValue) >= 0 && value.CompareTo(MaxValue) <= 0, shouldReturnObjectResult);
}

object EvaluateCondition(bool comparisonResult, bool shouldReturnObject) => (comparisonResult, shouldReturnObject) switch
/// <summary>Evaluates a condition based on the given comparison result and returns an object.</summary>
/// <param name="comparisonResult">The result of the comparison.</param>
/// <param name="shouldReturnObject">Indicates whether an object should be returned.</param>
/// <returns>The result of the evaluation.</returns>
object EvaluateCondition(bool comparisonResult, bool shouldReturnObject)
{
(true, true) => TrueObject ?? throw new InvalidOperationException($"{nameof(TrueObject)} cannot be null"),
(false, true) => FalseObject ?? throw new InvalidOperationException($"{nameof(FalseObject)} cannot be null"),
(true, false) => true,
(false, false) => false
};
return (comparisonResult, shouldReturnObject) switch
{
(true, true) => TrueObject ?? throw new InvalidOperationException($"{nameof(TrueObject)} cannot be null"),
(false, true) => FalseObject ?? throw new InvalidOperationException($"{nameof(FalseObject)} cannot be null"),
(true, false) => true,
(false, false) => false
};
}
}

0 comments on commit f5cf4b4

Please sign in to comment.