Skip to content

Commit e17d471

Browse files
authored
Enable developers to inherit from BaseBehavior and BaseConverter again (#2573)
1 parent 2a64d25 commit e17d471

File tree

9 files changed

+435
-70
lines changed

9 files changed

+435
-70
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using CommunityToolkit.Maui.UnitTests.Mocks;
2+
using FluentAssertions;
3+
using Xunit;
4+
5+
namespace CommunityToolkit.Maui.UnitTests.Behaviors;
6+
7+
public class BaseBehaviorTests
8+
{
9+
[Fact]
10+
public void AttachAndDetachCallsShouldCorrectlyAssignView()
11+
{
12+
var label = new Label();
13+
var mockBehavior = new MockBehavior();
14+
15+
mockBehavior.AssertViewIsNull();
16+
mockBehavior.IsAttached.Should().BeFalse();
17+
18+
label.Behaviors.Add(mockBehavior);
19+
20+
mockBehavior.AssertViewIsEqual(label);
21+
mockBehavior.IsAttached.Should().BeTrue();
22+
23+
label.Behaviors.Remove(mockBehavior);
24+
25+
mockBehavior.AssertViewIsNull();
26+
mockBehavior.IsAttached.Should().BeFalse();
27+
}
28+
29+
[Fact]
30+
public void ViewPropertyChangesShouldBeHandledWithinBehavior()
31+
{
32+
var label = new Label();
33+
var mockBehavior = new MockBehavior();
34+
35+
label.Behaviors.Add(mockBehavior);
36+
37+
mockBehavior.PropertyChanges.Should().BeEmpty();
38+
39+
label.Text = "Text";
40+
41+
mockBehavior.PropertyChanges.Should().ContainSingle();
42+
var change = mockBehavior.PropertyChanges.Single();
43+
44+
change.Should().NotBeNull();
45+
change.PropertyName.Should().Be(nameof(Label.Text));
46+
}
47+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System.Globalization;
2+
using CommunityToolkit.Maui.Converters;
3+
using CommunityToolkit.Maui.UnitTests.Mocks;
4+
using FluentAssertions;
5+
using Xunit;
6+
7+
namespace CommunityToolkit.Maui.UnitTests.Converters;
8+
9+
public abstract class BaseConverterOneWayTests
10+
{
11+
[Theory]
12+
[InlineData(4.123, typeof(double))]
13+
[InlineData(4, typeof(int))]
14+
public abstract void Convert_WithMismatchedTargetType(object? inputValue, Type targetType);
15+
16+
[Theory]
17+
[InlineData(4.123, typeof(string))]
18+
[InlineData(true, typeof(string))]
19+
public abstract void Convert_WithInvalidValueType(object? inputValue, Type targetType);
20+
21+
[Theory]
22+
[InlineData(1, typeof(string))]
23+
public void Convert_ShouldCorrectlyReturnValueWithMatchingTargetType(object? inputValue, Type targetType)
24+
{
25+
ICommunityToolkitValueConverter converter = CreateConverter();
26+
27+
Assert.Equal("Two", converter.Convert(inputValue, targetType, null, CultureInfo.CurrentCulture));
28+
}
29+
30+
[Fact]
31+
public void Setting_DefaultConvertBackReturnValue_WillThrowNotSupportedException()
32+
{
33+
ICommunityToolkitValueConverter converter = CreateConverter();
34+
35+
Assert.Throws<NotSupportedException>(() => new MockOneWayConverter(["One", "Two", "Three"])
36+
{
37+
DefaultConvertReturnValue = "Three",
38+
DefaultConvertBackReturnValue = 1
39+
});
40+
}
41+
42+
protected static BaseConverter<int, string> CreateConverter() => new MockOneWayConverter(["One", "Two", "Three"])
43+
{
44+
DefaultConvertReturnValue = "Three"
45+
};
46+
47+
protected BaseConverterOneWayTests(bool suppressExceptions)
48+
{
49+
new Options().SetShouldSuppressExceptionsInConverters(suppressExceptions);
50+
}
51+
}
52+
53+
/// <summary>
54+
/// Unit tests that target <see cref="BaseConverter{TFrom,TTo}"/> and their public APIs with <see cref="Options.SetShouldSuppressExceptionsInConverters"/> == false.
55+
/// </summary>
56+
public class BaseConverterOneWayTestsWithExceptionsEnabled : BaseConverterOneWayTests
57+
{
58+
public BaseConverterOneWayTestsWithExceptionsEnabled() : base(false)
59+
{
60+
}
61+
62+
public override void Convert_WithMismatchedTargetType(object? inputValue, Type targetType)
63+
{
64+
ICommunityToolkitValueConverter converter = CreateConverter();
65+
66+
var exception = Assert.Throws<ArgumentException>(() => converter.Convert(inputValue, targetType, null, CultureInfo.CurrentCulture));
67+
68+
exception.Message.Should().Be($"targetType needs to be assignable from {converter.ToType}. (Parameter 'targetType')");
69+
}
70+
71+
public override void Convert_WithInvalidValueType(object? inputValue, Type targetType)
72+
{
73+
ICommunityToolkitValueConverter converter = CreateConverter();
74+
75+
var exception = Assert.Throws<ArgumentException>(() => converter.Convert(inputValue, targetType, null, CultureInfo.CurrentCulture));
76+
77+
exception.Message.Should().Be($"Value needs to be of type {converter.FromType} (Parameter 'value')");
78+
}
79+
}
80+
81+
/// <summary>
82+
/// Unit tests that target <see cref="BaseConverter{TFrom,TTo}"/> and their public APIs with <see cref="Options.SetShouldSuppressExceptionsInConverters"/> == true.
83+
/// </summary>
84+
public class BaseConverterOneWayTestsWithExceptionsSuppressed : BaseConverterOneWayTests
85+
{
86+
public BaseConverterOneWayTestsWithExceptionsSuppressed() : base(true)
87+
{
88+
}
89+
90+
public override void Convert_WithMismatchedTargetType(object? inputValue, Type targetType)
91+
{
92+
ICommunityToolkitValueConverter converter = CreateConverter();
93+
94+
converter.Convert(inputValue, targetType, null, CultureInfo.CurrentCulture).Should().Be(converter.DefaultConvertReturnValue);
95+
}
96+
97+
public override void Convert_WithInvalidValueType(object? inputValue, Type targetType)
98+
{
99+
ICommunityToolkitValueConverter converter = CreateConverter();
100+
101+
converter.Convert(inputValue, targetType, null, CultureInfo.CurrentCulture).Should().Be(converter.DefaultConvertReturnValue);
102+
}
103+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System.Globalization;
2+
using CommunityToolkit.Maui.Converters;
3+
using Xunit;
4+
5+
namespace CommunityToolkit.Maui.UnitTests.Converters;
6+
7+
public abstract class BaseOneWayConverterTest<TConverter> : ConverterTest<TConverter> where TConverter : ICommunityToolkitValueConverter, new()
8+
{
9+
[Fact]
10+
public void ConvertBack_ShouldThrowNotSupportedException()
11+
{
12+
var options = new Options();
13+
options.SetShouldSuppressExceptionsInConverters(true);
14+
15+
var converter = InitializeConverterForInvalidConverterTests();
16+
17+
Assert.ThrowsAny<NotSupportedException>(() => converter.ConvertBack(GetInvalidConvertBackValue(), converter.FromType, null, CultureInfo.CurrentCulture));
18+
}
19+
}
20+
21+
public abstract class BaseConverterTest<TConverter> : ConverterTest<TConverter> where TConverter : ICommunityToolkitValueConverter, new()
22+
{
23+
[Fact]
24+
public void InvalidConvertBackValue_ShouldThrowException()
25+
{
26+
var options = new Options();
27+
options.SetShouldSuppressExceptionsInConverters(false);
28+
29+
var converter = InitializeConverterForInvalidConverterTests();
30+
31+
Assert.ThrowsAny<ArgumentException>(() => converter.ConvertBack(GetInvalidConvertBackValue(), converter.FromType, null, CultureInfo.CurrentCulture));
32+
}
33+
34+
[Fact]
35+
public void InvalidConvertBackValue_ShouldSuppressExceptionsInConverters_ShouldReturnDefaultConvertValue()
36+
{
37+
var options = new Options();
38+
options.SetShouldSuppressExceptionsInConverters(true);
39+
40+
var converter = InitializeConverterForInvalidConverterTests();
41+
42+
var result = converter.ConvertBack(GetInvalidConvertBackValue(), converter.FromType, null, CultureInfo.CurrentCulture);
43+
44+
Assert.Equal(converter.DefaultConvertBackReturnValue, result);
45+
}
46+
}
47+
48+
public abstract class ConverterTest<TConverter> : BaseHandlerTest where TConverter : ICommunityToolkitValueConverter, new()
49+
{
50+
[Fact]
51+
public void InvalidConvertValue_ShouldThrowException()
52+
{
53+
var options = new Options();
54+
options.SetShouldSuppressExceptionsInConverters(false);
55+
56+
var converter = InitializeConverterForInvalidConverterTests();
57+
58+
Assert.ThrowsAny<ArgumentException>(() => converter.Convert(GetInvalidConvertFromValue(), converter.ToType, null, CultureInfo.CurrentCulture));
59+
}
60+
61+
[Fact]
62+
public void InvalidConverterValue_ShouldSuppressExceptionsInConverters_ShouldReturnDefaultConvertValue()
63+
{
64+
var options = new Options();
65+
options.SetShouldSuppressExceptionsInConverters(true);
66+
67+
var converter = InitializeConverterForInvalidConverterTests();
68+
69+
var result = converter.Convert(GetInvalidConvertFromValue(), converter.ToType, null, CultureInfo.CurrentCulture);
70+
71+
Assert.Equal(converter.DefaultConvertReturnValue, result);
72+
}
73+
74+
protected virtual object? GetInvalidConvertBackValue() => GetInvalidValue(InitializeConverterForInvalidConverterTests().ToType);
75+
protected virtual object? GetInvalidConvertFromValue() => GetInvalidValue(InitializeConverterForInvalidConverterTests().FromType);
76+
protected virtual TConverter InitializeConverterForInvalidConverterTests() => new();
77+
78+
static object GetInvalidValue(Type type)
79+
{
80+
if (type != typeof(string))
81+
{
82+
return string.Empty;
83+
}
84+
85+
if (type != typeof(bool))
86+
{
87+
return true;
88+
}
89+
90+
throw new NotImplementedException($"Invalid value not valid for {typeof(TConverter).Name}. If {nameof(InvalidConvertValue_ShouldThrowException)} is failing, please override {nameof(GetInvalidConvertFromValue)} and provide an invalid value. Otherwise, override {nameof(GetInvalidConvertBackValue)} and provide an invalid value");
91+
}
92+
}

0 commit comments

Comments
 (0)