Skip to content

Commit

Permalink
Update ValueConverterExtension to allow for comparison of Nullable ty…
Browse files Browse the repository at this point in the history
…pes to corresponding underlying types (#1307)

* UPDATE - Updated ValidateTargetType to allow comparison on nullable types

* UPDATE - Removed incorrect ValueConverterExtension.shared.cs file.
Updated failing test in BoolToObjectConverterTests.cs

* REVERT - Reverted changes to BoolToObjectConverterTests.cs

* DELETE - Deleted ValueConverterExtensionTests.cs

* ADD - Introduced PerformNullableTypeValidation in ValueConverterExtension.shared.cs, added unit tests to IsStringNotNullOrEmptyConverterTests.cs

* REFACTOR - Refactored ValueConverterExtension.shared.cs to remove redundant code and checks

* ADD - Added sample to IsStringNotNullOrEmptyConverterPage.xaml

* ADD - Implemented additional tests for IsStringNotNullOrWhiteSpaceConverterTests.cs, IsStringNullOrEmptyConverterTests.cs and
IsStringNullOrWhiteSpaceConverterTests.cs

* ADD - Added further tests. Renamed new tests to use Name_Should_When convention

* Move `NullableBoolComponentWithLabel` to `Views`, `dotnet format`

* Add logic to allow Nullable Value Types for `TTo`

* Add nullable input test

* Remove unnecessary `ContentView`

* Add `NullableBoolComponentWithLabel` to `IsStringNullOrEmptyConverterPage`

* Fix failing `GravatarImageSource` test

* Update InvertedBoolConverterTests.cs

---------

Co-authored-by: Brandon Minnick <[email protected]>
  • Loading branch information
BruceTheBrick and brminnick authored Aug 2, 2023
1 parent 172a1e7 commit 112ff60
Show file tree
Hide file tree
Showing 19 changed files with 287 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<pages:BasePage
<pages:BasePage
x:Class="CommunityToolkit.Maui.Sample.Pages.Converters.IsStringNotNullOrEmptyConverterPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:views="clr-namespace:CommunityToolkit.Maui.Sample.Views"
xmlns:vm="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Converters"
x:Class="CommunityToolkit.Maui.Sample.Pages.Converters.IsStringNotNullOrEmptyConverterPage"
x:DataType="vm:IsStringNotNullOrEmptyConverterViewModel"
x:TypeArguments="vm:IsStringNotNullOrEmptyConverterViewModel">

Expand All @@ -23,11 +24,24 @@

<Label Text="The IsStringNotNullOrEmptyConverter is a converter that allows users to convert an incoming string binding to a bool value. This value represents if the incoming string binding value is not null or empty using string.IsNullOrEmpty." TextColor="{StaticResource NormalLabelTextColor}" />

<Label Text="Enter text into the Entry, below. If the text is not null or empty, the value will be true." TextColor="{StaticResource NormalLabelTextColor}" FontAttributes="Bold"/>
<Label
FontAttributes="Bold"
Text="Enter text into the Entry, below. If the text is not null or empty, the value will be true."
TextColor="{StaticResource NormalLabelTextColor}" />

<views:NullableBoolComponentWithLabel
NullableIsVisible="{Binding LabelText, Mode=OneWay, Converter={StaticResource IsStringNotNullOrEmptyConverter}}" />

<Entry VerticalOptions="CenterAndExpand" HorizontalOptions="Fill" Text="{Binding Path=LabelText, Mode=OneWayToSource}" TextColor="{StaticResource NormalLabelTextColor}" />
<Entry
HorizontalOptions="Fill"
Text="{Binding Path=LabelText, Mode=OneWayToSource}"
TextColor="{StaticResource NormalLabelTextColor}"
VerticalOptions="CenterAndExpand" />

<Label VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" Text="{Binding Path=LabelText, Mode=OneWay, Converter={StaticResource IsStringNotNullOrEmptyConverter}}" />
<Label
HorizontalOptions="CenterAndExpand"
Text="{Binding Path=LabelText, Mode=OneWay, Converter={StaticResource IsStringNotNullOrEmptyConverter}}"
VerticalOptions="CenterAndExpand" />

</VerticalStackLayout>
</pages:BasePage.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:views="clr-namespace:CommunityToolkit.Maui.Sample.Views"
xmlns:vm="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Converters"
x:Class="CommunityToolkit.Maui.Sample.Pages.Converters.IsStringNullOrEmptyConverterPage"
x:DataType="vm:IsStringNullOrEmptyConverterViewModel"
Expand All @@ -23,11 +24,24 @@

<Label Text="The IsStringNullOrEmptyConverter is a converter that allows usersc to convert an incoming string binding to a bool value. This value represents if the incoming string binding value is null or empty using string.IsNullOrEmpty." TextColor="{StaticResource NormalLabelTextColor}" />

<Label Text="Enter text into the Entry, below. If the text is null or empty, the value will be true." TextColor="{StaticResource NormalLabelTextColor}" FontAttributes="Bold" />
<Label
Text="Enter text into the Entry, below. If the text is null or empty, the value will be true."
TextColor="{StaticResource NormalLabelTextColor}"
FontAttributes="Bold" />

<Entry VerticalOptions="CenterAndExpand" HorizontalOptions="Fill" Text="{Binding Path=LabelText, Mode=OneWayToSource}" TextColor="{StaticResource NormalLabelTextColor}" />
<views:NullableBoolComponentWithLabel
NullableIsVisible="{Binding LabelText, Mode=OneWay, Converter={StaticResource IsStringNullOrEmptyConverter}}" />

<Label VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" Text="{Binding Path=LabelText, Mode=OneWay, Converter={StaticResource IsStringNullOrEmptyConverter}}" />
<Entry
VerticalOptions="CenterAndExpand"
HorizontalOptions="Fill"
Text="{Binding Path=LabelText, Mode=OneWayToSource}"
TextColor="{StaticResource NormalLabelTextColor}" />

<Label
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
Text="{Binding Path=LabelText, Mode=OneWay, Converter={StaticResource IsStringNullOrEmptyConverter}}" />

</VerticalStackLayout>
</pages:BasePage.Content>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>

<Label
x:Class="CommunityToolkit.Maui.Sample.Views.NullableBoolComponentWithLabel"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Name="This"
IsVisible="{Binding NullableIsVisible, Source={x:RelativeSource Self}}"
Text="Binding to a nullable `bool` (`bool?`) is also allowed. If the `bool?` is null, the converter will return false."
TextColor="{StaticResource NormalLabelTextColor}"
VerticalOptions="Center" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace CommunityToolkit.Maui.Sample.Views;

public partial class NullableBoolComponentWithLabel : Label
{
public static readonly BindableProperty NullableIsVisibleProperty = BindableProperty.Create(
nameof(NullableIsVisible),
typeof(bool?),
typeof(NullableBoolComponentWithLabel));


public NullableBoolComponentWithLabel()
{
InitializeComponent();
}

public bool? NullableIsVisible
{
get => (bool?)GetValue(NullableIsVisibleProperty);
set => SetValue(NullableIsVisibleProperty, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,30 @@ public void InvertedBoolConverter(bool value, bool expectedResult)
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
public void InvertedBoolConverter_ShouldConvert_WhenTargetTypeIsNullableBool(bool value, bool expectedResult)
{
var invertedBoolConverter = new InvertedBoolConverter();

var convertResult = (bool?)((ICommunityToolkitValueConverter)invertedBoolConverter).Convert(value, typeof(bool?), null, CultureInfo.CurrentCulture);
var convertFromResult = invertedBoolConverter.ConvertFrom(value);

Assert.Equal(expectedResult, convertResult);
Assert.Equal(expectedResult, convertFromResult);
}

[Fact]
public void InvertedBoolConverter_ShouldThrowError_WhenInputTypeIsNullableBool()
{
var invertedBoolConverter = new InvertedBoolConverter();

bool? nullableBool = null;

Assert.Throws<ArgumentNullException>(() => (bool?)((ICommunityToolkitValueConverter)invertedBoolConverter).Convert(nullableBool, typeof(bool?), null, CultureInfo.CurrentCulture));
}

[Theory]
[InlineData(2)]
[InlineData("")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ namespace CommunityToolkit.Maui.UnitTests.Converters;

public class IsEqualConverterTests : BaseTest
{
public const string TestValue = nameof(TestValue);

[Theory]
[InlineData(true, true, true)]
[InlineData(int.MaxValue, int.MinValue, false)]
Expand All @@ -25,6 +23,23 @@ public void IsEqualConverterValidInputTest(object? value, object? comparedValue,
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(true, true, true)]
[InlineData(int.MaxValue, int.MinValue, false)]
[InlineData("Test", true, false)]
[InlineData(null, null, true)]
[InlineData(null, true, false)]
public void IsEqualConverter_ShouldConvert_WhenTargetTypeIsNullableBool(object? value, object? comparedValue, bool expectedResult)
{
var isEqualConverter = new IsEqualConverter();

var convertResult = (bool?)((ICommunityToolkitValueConverter)isEqualConverter).Convert(value, typeof(bool?), comparedValue, CultureInfo.CurrentCulture);
var convertFromResult = isEqualConverter.ConvertFrom(value, comparedValue);

Assert.Equal(expectedResult, convertResult);
Assert.Equal(expectedResult, convertFromResult);
}

[Fact]
public void IsEqualConverterNullInputTest()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ public void IsListNotNullOrEmptyConverter(IEnumerable? value, bool expectedResul
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[MemberData(nameof(Data))]
public void IsListNotNullOrEmptyConverter_ShouldConvert_WhenTargetTypeIsNullableBool(IEnumerable? value, bool expectedResult)
{
var listIsNotNullOrEmptyConverter = new IsListNotNullOrEmptyConverter();

var convertResult = (bool?)((ICommunityToolkitValueConverter)listIsNotNullOrEmptyConverter).Convert(value, typeof(bool?), null, CultureInfo.CurrentCulture);
var convertFromResult = listIsNotNullOrEmptyConverter.ConvertFrom(value);

Assert.Equal(expectedResult, convertResult);
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(7)]
[InlineData('c')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ public void IsListNullOrEmptyConverter(IEnumerable? value, bool expectedResult)
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[MemberData(nameof(Data))]
public void IsListNullOrEmptyConverter_ShouldConvert_WhenTargetTypeIsNullableBool(IEnumerable? value, bool expectedResult)
{
var listIstNullOrEmptyConverter = new IsListNullOrEmptyConverter();

var convertResult = (bool?)((ICommunityToolkitValueConverter)listIstNullOrEmptyConverter).Convert(value, typeof(bool?), null, CultureInfo.CurrentCulture);
var convertFromResult = listIstNullOrEmptyConverter.ConvertFrom(value);

Assert.Equal(expectedResult, convertResult);
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(0)]
public void InvalidConverterValuesThrowArgumentException(object value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ public void IsNotEqualConverterValidInputTest(object? value, object? comparedVal
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(true, true, false)]
[InlineData(int.MaxValue, int.MinValue, true)]
[InlineData("Test", true, true)]
[InlineData(null, null, false)]
[InlineData(null, true, true)]
public void IsNotEqualConverter_ShouldConvert_WhenTargetTypeIsNullableBool(object? value, object? comparedValue, bool expectedResult)
{
var notEqualConverter = new IsNotEqualConverter();

var convertResult = (bool?)((ICommunityToolkitValueConverter)notEqualConverter).Convert(value, typeof(bool?), comparedValue, CultureInfo.CurrentCulture);
var convertFromResult = notEqualConverter.ConvertFrom(value, comparedValue);

Assert.Equal(expectedResult, convertResult);
Assert.Equal(expectedResult, convertFromResult);
}

[Fact]
public void IsNotEqualConverterNullInputTest()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ public void ObjectToBoolConverter(object? value, bool expectedResult)
Assert.Equal(expectedResult, typedResult);
}

[Theory]
[ClassData(typeof(FooDataGenerator))]
public void IsNotNullConverter_ShouldConvert_WhenTargetTypeIsNullableBool(object? value, bool expectedResult)
{
var isNotNullConverter = new IsNotNullConverter();

var result = ((ICommunityToolkitValueConverter)isNotNullConverter).Convert(value, typeof(bool?), null, CultureInfo.CurrentCulture);
var typedResult = isNotNullConverter.ConvertFrom(value);

Assert.Equal(expectedResult, result);
Assert.Equal(expectedResult, typedResult);
}

public class FooDataGenerator : TheoryData<object?, bool>
{
public FooDataGenerator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ public void ObjectToBoolConverter(object? value, bool expectedResult)
Assert.Equal(expectedResult, typedResult);
}

[Theory]
[ClassData(typeof(FooDataGenerator))]
public void IsNullConverter_ShouldConvert_WhenTargetTypeIsNullableBool(object? value, bool expectedResult)
{
var isNullConverter = new IsNullConverter();

var result = ((ICommunityToolkitValueConverter)isNullConverter).Convert(value, typeof(bool?), null, CultureInfo.CurrentCulture);
var typedResult = isNullConverter.ConvertFrom(value);

Assert.Equal(expectedResult, result);
Assert.Equal(expectedResult, typedResult);
}

public class FooDataGenerator : TheoryData<object?, bool>
{
public FooDataGenerator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ public void IsStringNotNullOrEmptyConverter_ValidStringValue(string? value, bool
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData("Test", true)]
[InlineData(null, false)]
[InlineData("", false)]
[InlineData(" ", true)]
public void IsStringNotNullOrEmptyConverter_ShouldConvert_WhenTargetTypeIsNullableBool(string? value, bool expectedResult)
{
var isNotNullOrEmptyConverter = new IsStringNotNullOrEmptyConverter();

var convertResult = (bool?)((ICommunityToolkitValueConverter)isNotNullOrEmptyConverter).Convert(value, typeof(bool?), null, null);
var convertFromResult = isNotNullOrEmptyConverter.ConvertFrom(value);

Assert.Equal(expectedResult, convertResult);
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(17)]
[InlineData(true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ public void IsNotNullOrWhiteSpaceConverter_ValidStringValue(string? value, bool
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData("Test", true)]
[InlineData(null, false)]
[InlineData("", false)]
[InlineData(" ", false)]
[InlineData(" ", false)]
public void IsStringNotNullOrWhiteSpaceConverter_ShouldConvert_WhenTargetTypeIsNullableBool(string? value, bool expectedResult)
{
var isNotNullOrWhiteSpaceConverter = new IsStringNotNullOrWhiteSpaceConverter();

var convertResult = (bool?)((ICommunityToolkitValueConverter)isNotNullOrWhiteSpaceConverter).Convert(value, typeof(bool?), null, null);
var convertFromResult = isNotNullOrWhiteSpaceConverter.ConvertFrom(value);

Assert.Equal(expectedResult, convertResult);
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(17)]
[InlineData(true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ public void IsNullOrEmptyConverter_ValidStringValue(string? value, bool expected
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(null, true)]
[InlineData("", true)]
[InlineData("Test", false)]
[InlineData(" ", false)]
public void IsStringNullOrEmptyConverter_ShouldConvert_WhenTargetTypeIsNullableBool(string? value, bool expectedResult)
{
var isNullOrEmptyConverter = new IsStringNullOrEmptyConverter();

var convertResult = (bool?)((ICommunityToolkitValueConverter)isNullOrEmptyConverter).Convert(value, typeof(bool?), null, null);
var convertFromResult = isNullOrEmptyConverter.ConvertFrom(value);

Assert.Equal(expectedResult, convertResult);
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(17)]
[InlineData(true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ public void IsNullOrWhiteSpaceConverter_ValidStringValue(string? value, bool exp
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(null, true)]
[InlineData("", true)]
[InlineData("Test", false)]
[InlineData(" ", true)]
[InlineData(" ", true)]
public void IsStringNullOrWhiteSpaceConverter_ShouldConvert_WhenTargetTypeIsNullableBool(string? value, bool expectedResult)
{
var isNullOrWhiteSpaceConverter = new IsStringNullOrWhiteSpaceConverter();

var convertResult = (bool?)((ICommunityToolkitValueConverter)isNullOrWhiteSpaceConverter).Convert(value, typeof(bool?), null, null);
var convertFromResult = isNullOrWhiteSpaceConverter.ConvertFrom(value);

Assert.Equal(expectedResult, convertResult);
Assert.Equal(expectedResult, convertFromResult);
}

[Theory]
[InlineData(17)]
[InlineData(true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,8 @@ public async Task TestDefaultStream()
{
CancellationTokenSource cts = new();
var gravatarImageSource = new GravatarImageSource();
Stream stream = await gravatarImageSource.Stream(cts.Token);
stream.Length.Should().BePositive();
Stream? stream = await gravatarImageSource.Stream(cts.Token);
stream.Should().BeNull();
}

[Fact]
Expand Down
Loading

0 comments on commit 112ff60

Please sign in to comment.