From cbacc3f12147d50e5b109118f36868a48e58b323 Mon Sep 17 00:00:00 2001 From: Vinicius Dutra Date: Mon, 30 Sep 2019 10:08:12 -0300 Subject: [PATCH 1/3] included tests for each function changed + considered enum name or value during conversion --- .../Mvvm/AutoInitializeViewModelFixture.cs | 70 +++++++++++++++++++ Source/Prism/Common/ParametersExtensions.cs | 21 ++++-- 2 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 Source/Prism.Tests/Mvvm/AutoInitializeViewModelFixture.cs diff --git a/Source/Prism.Tests/Mvvm/AutoInitializeViewModelFixture.cs b/Source/Prism.Tests/Mvvm/AutoInitializeViewModelFixture.cs new file mode 100644 index 000000000..a3c28c178 --- /dev/null +++ b/Source/Prism.Tests/Mvvm/AutoInitializeViewModelFixture.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using Prism.Common; +using Xunit; + +namespace Prism.Tests.Mvvm +{ + internal class MockParameters : ParametersBase + { + public MockParameters() : base() { } + public MockParameters(string query) : base(query) { } + } + + internal enum MockEnum + { + None = 0, + Foo = 1, + Bar = 2, + Fizz = 3 + } + + public class AutoInitializeViewModelFixture + { + [Fact] + public void TryGetValueOfT() + { + var parameters = new MockParameters("mock=Foo&mock2=1"); + bool success = false; + MockEnum value = default; + MockEnum value1 = default; + + var ex = Record.Exception(() => success = parameters.TryGetValue("mock", out value)); + var ex2 = Record.Exception(() => success = parameters.TryGetValue("mock2", out value1)); + Assert.Null(ex); + Assert.True(success); + Assert.Equal(MockEnum.Foo, value); + Assert.Equal(value, value1); + } + + [Fact] + public void GetValuesOfT() + { + var parameters = new MockParameters("mock=Foo&mock=2&mock=Fizz"); + bool success = false; + IEnumerable values = default; + + var ex = Record.Exception(() => values = parameters.GetValues("mock")); + Assert.Null(ex); + Assert.Equal(3, values.Count()); + Assert.Equal(MockEnum.Foo, values.ElementAt(0)); + Assert.Equal(MockEnum.Bar, values.ElementAt(1)); + Assert.Equal(MockEnum.Fizz, values.ElementAt(2)); + } + + + [Fact] + public void GetValue() + { + var parameters = new MockParameters("mock=Foo&mock1=2&mock2=Fizz"); + MockEnum value = default; + MockEnum value1 = default; + + var ex = Record.Exception(() => value = parameters.GetValue("mock")); + var ex2 = Record.Exception(() => value1 = parameters.GetValue("mock1")); + Assert.Null(ex); + Assert.Equal(MockEnum.Foo, value); + Assert.Equal(MockEnum.Bar, value1); + } + } +} diff --git a/Source/Prism/Common/ParametersExtensions.cs b/Source/Prism/Common/ParametersExtensions.cs index 5222fffcc..52786f7b0 100644 --- a/Source/Prism/Common/ParametersExtensions.cs +++ b/Source/Prism/Common/ParametersExtensions.cs @@ -25,6 +25,8 @@ public static object GetValue(this IEnumerable> par return kvp.Value; else if (type.IsAssignableFrom(kvp.Value.GetType())) return kvp.Value; + else if (type.IsEnum && (Enum.IsDefined(type, kvp.Value.ToString()) || int.TryParse(kvp.Value.ToString(), out var enumValue) && Enum.IsDefined(type, enumValue))) + return Enum.Parse(type, kvp.Value.ToString()); else return Convert.ChangeType(kvp.Value, type); } @@ -36,18 +38,22 @@ public static object GetValue(this IEnumerable> par [EditorBrowsable(EditorBrowsableState.Never)] public static bool TryGetValue(this IEnumerable> parameters, string key, out T value) { + var type = typeof(T); + foreach (var kvp in parameters) { if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0) { if (kvp.Value == null) value = default; - else if (kvp.Value.GetType() == typeof(T)) + else if (kvp.Value.GetType() == type) value = (T)kvp.Value; - else if (typeof(T).IsAssignableFrom(kvp.Value.GetType())) + else if (type.IsAssignableFrom(kvp.Value.GetType())) value = (T)kvp.Value; + else if (type.IsEnum && (Enum.IsDefined(type, kvp.Value.ToString()) || int.TryParse(kvp.Value.ToString(), out var enumValue) && Enum.IsDefined(type, enumValue))) + value = (T)Enum.Parse(type, kvp.Value.ToString()); else - value = (T)Convert.ChangeType(kvp.Value, typeof(T)); + value = (T)Convert.ChangeType(kvp.Value, type); return true; } @@ -61,6 +67,7 @@ public static bool TryGetValue(this IEnumerable> public static IEnumerable GetValues(this IEnumerable> parameters, string key) { List values = new List(); + var type = typeof(T); foreach (var kvp in parameters) { @@ -68,12 +75,14 @@ public static IEnumerable GetValues(this IEnumerable Date: Thu, 29 Aug 2019 16:05:49 -0300 Subject: [PATCH 2/3] included tests for each function changed + considered enum name or value during conversion Allow bind Enum values in IAutoInitialize ViewModels Currently if anyone try pass through a enum value as raw type(value or name) within INavigationParameters get an error of conversion, to solve this problem was included the conditions above. Allow bind Enum values in IAutoInitialize ViewModels Currently if anyone try pass through a enum value as raw type(value or name) within INavigationParameters get an error of conversion, to solve this problem was included the conditions above. Update ParametersExtensions.cs force to use only enum name I'm considering to use only the enum name because if the value is being considered during the conversion can cause side effect because the value as number can be refered to a lot of things in navigation parameters not the enum type searched. --- Source/Prism/Common/ParametersExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Prism/Common/ParametersExtensions.cs b/Source/Prism/Common/ParametersExtensions.cs index 52786f7b0..a2e0d33fe 100644 --- a/Source/Prism/Common/ParametersExtensions.cs +++ b/Source/Prism/Common/ParametersExtensions.cs @@ -25,7 +25,7 @@ public static object GetValue(this IEnumerable> par return kvp.Value; else if (type.IsAssignableFrom(kvp.Value.GetType())) return kvp.Value; - else if (type.IsEnum && (Enum.IsDefined(type, kvp.Value.ToString()) || int.TryParse(kvp.Value.ToString(), out var enumValue) && Enum.IsDefined(type, enumValue))) + else if (type.IsEnum && Enum.IsDefined(type, kvp.Value.ToString())) return Enum.Parse(type, kvp.Value.ToString()); else return Convert.ChangeType(kvp.Value, type); @@ -50,7 +50,7 @@ public static bool TryGetValue(this IEnumerable> value = (T)kvp.Value; else if (type.IsAssignableFrom(kvp.Value.GetType())) value = (T)kvp.Value; - else if (type.IsEnum && (Enum.IsDefined(type, kvp.Value.ToString()) || int.TryParse(kvp.Value.ToString(), out var enumValue) && Enum.IsDefined(type, enumValue))) + else if (type.IsEnum && Enum.IsDefined(type, kvp.Value.ToString())) value = (T)Enum.Parse(type, kvp.Value.ToString()); else value = (T)Convert.ChangeType(kvp.Value, type); @@ -79,7 +79,7 @@ public static IEnumerable GetValues(this IEnumerable Date: Wed, 20 Nov 2019 13:29:03 -0800 Subject: [PATCH 3/3] Fixing Enum Support to allow setting from numeric or string --- Source/Prism.Tests/Common/Mocks/MockEnum.cs | 11 +++ .../Common/Mocks/MockParameters.cs | 10 +++ .../ParametersFixture.cs} | 22 +---- Source/Prism/Common/ParametersExtensions.cs | 85 ++++++++++++------- .../Mvvm/AutoInitializeFixture.cs | 72 ++++++++++++++++ .../AutoInitializedViewModelMock.cs | 17 ++++ .../Mvvm/Mocks/ViewModels/MockHttpStatus.cs | 10 +++ 7 files changed, 176 insertions(+), 51 deletions(-) create mode 100644 Source/Prism.Tests/Common/Mocks/MockEnum.cs create mode 100644 Source/Prism.Tests/Common/Mocks/MockParameters.cs rename Source/Prism.Tests/{Mvvm/AutoInitializeViewModelFixture.cs => Common/ParametersFixture.cs} (81%) create mode 100644 Source/Xamarin/Prism.Forms.Tests/Mvvm/AutoInitializeFixture.cs create mode 100644 Source/Xamarin/Prism.Forms.Tests/Mvvm/Mocks/ViewModels/AutoInitializedViewModelMock.cs create mode 100644 Source/Xamarin/Prism.Forms.Tests/Mvvm/Mocks/ViewModels/MockHttpStatus.cs diff --git a/Source/Prism.Tests/Common/Mocks/MockEnum.cs b/Source/Prism.Tests/Common/Mocks/MockEnum.cs new file mode 100644 index 000000000..74ebae993 --- /dev/null +++ b/Source/Prism.Tests/Common/Mocks/MockEnum.cs @@ -0,0 +1,11 @@ +namespace Prism.Tests.Common.Mocks +{ + internal enum MockEnum + { + None = 0, + Foo = 1, + Bar = 2, + Fizz = 3, + SomethingElse = 99 + } +} diff --git a/Source/Prism.Tests/Common/Mocks/MockParameters.cs b/Source/Prism.Tests/Common/Mocks/MockParameters.cs new file mode 100644 index 000000000..671f99014 --- /dev/null +++ b/Source/Prism.Tests/Common/Mocks/MockParameters.cs @@ -0,0 +1,10 @@ +using Prism.Common; + +namespace Prism.Tests.Common.Mocks +{ + internal class MockParameters : ParametersBase + { + public MockParameters() : base() { } + public MockParameters(string query) : base(query) { } + } +} diff --git a/Source/Prism.Tests/Mvvm/AutoInitializeViewModelFixture.cs b/Source/Prism.Tests/Common/ParametersFixture.cs similarity index 81% rename from Source/Prism.Tests/Mvvm/AutoInitializeViewModelFixture.cs rename to Source/Prism.Tests/Common/ParametersFixture.cs index a3c28c178..abd2aa8cf 100644 --- a/Source/Prism.Tests/Mvvm/AutoInitializeViewModelFixture.cs +++ b/Source/Prism.Tests/Common/ParametersFixture.cs @@ -1,25 +1,11 @@ using System.Collections.Generic; using System.Linq; -using Prism.Common; +using Prism.Tests.Common.Mocks; using Xunit; -namespace Prism.Tests.Mvvm +namespace Prism.Tests.Common { - internal class MockParameters : ParametersBase - { - public MockParameters() : base() { } - public MockParameters(string query) : base(query) { } - } - - internal enum MockEnum - { - None = 0, - Foo = 1, - Bar = 2, - Fizz = 3 - } - - public class AutoInitializeViewModelFixture + public class ParametersFixture { [Fact] public void TryGetValueOfT() @@ -41,7 +27,7 @@ public void TryGetValueOfT() public void GetValuesOfT() { var parameters = new MockParameters("mock=Foo&mock=2&mock=Fizz"); - bool success = false; + IEnumerable values = default; var ex = Record.Exception(() => values = parameters.GetValues("mock")); diff --git a/Source/Prism/Common/ParametersExtensions.cs b/Source/Prism/Common/ParametersExtensions.cs index a2e0d33fe..774bb6a2c 100644 --- a/Source/Prism/Common/ParametersExtensions.cs +++ b/Source/Prism/Common/ParametersExtensions.cs @@ -19,16 +19,10 @@ public static object GetValue(this IEnumerable> par { if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0) { - if (kvp.Value == null) - return GetDefault(type); - else if (kvp.Value.GetType() == type) - return kvp.Value; - else if (type.IsAssignableFrom(kvp.Value.GetType())) - return kvp.Value; - else if (type.IsEnum && Enum.IsDefined(type, kvp.Value.ToString())) - return Enum.Parse(type, kvp.Value.ToString()); - else - return Convert.ChangeType(kvp.Value, type); + if(TryGetValueInternal(kvp, type, out var value)) + return value; + + throw new InvalidCastException($"Unable to convert the value of Type '{kvp.Value.GetType().FullName}' to '{type.FullName}' for the key '{key}' "); } } @@ -44,18 +38,9 @@ public static bool TryGetValue(this IEnumerable> { if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0) { - if (kvp.Value == null) - value = default; - else if (kvp.Value.GetType() == type) - value = (T)kvp.Value; - else if (type.IsAssignableFrom(kvp.Value.GetType())) - value = (T)kvp.Value; - else if (type.IsEnum && Enum.IsDefined(type, kvp.Value.ToString())) - value = (T)Enum.Parse(type, kvp.Value.ToString()); - else - value = (T)Convert.ChangeType(kvp.Value, type); - - return true; + var success = TryGetValueInternal(kvp, typeof(T), out object valueAsObject); + value = (T)valueAsObject; + return success; } } @@ -67,28 +52,62 @@ public static bool TryGetValue(this IEnumerable> public static IEnumerable GetValues(this IEnumerable> parameters, string key) { List values = new List(); - var type = typeof(T); + var type = typeof(T); foreach (var kvp in parameters) { if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0) { - if (kvp.Value == null) - values.Add(default); - else if (kvp.Value.GetType() == type) - values.Add((T)kvp.Value); - else if (type.IsAssignableFrom(kvp.Value.GetType())) - values.Add((T)kvp.Value); - else if (type.IsEnum && Enum.IsDefined(type, kvp.Value.ToString())) - values.Add((T)Enum.Parse(type, kvp.Value.ToString())); - else - values.Add((T)Convert.ChangeType(kvp.Value, type)); + TryGetValueInternal(kvp, type, out var value); + values.Add((T)value); } } return values.AsEnumerable(); } + private static bool TryGetValueInternal(KeyValuePair kvp, Type type, out object value) + { + value = GetDefault(type); + var success = false; + if (kvp.Value == null) + { + success = true; + } + else if (kvp.Value.GetType() == type) + { + success = true; + value = kvp.Value; + } + else if (type.IsAssignableFrom(kvp.Value.GetType())) + { + success = true; + value = kvp.Value; + } + else if (type.IsEnum) + { + var valueAsString = kvp.Value.ToString(); + if (Enum.IsDefined(type, valueAsString)) + { + success = true; + value = Enum.Parse(type, valueAsString); + } + else if (int.TryParse(valueAsString, out var numericValue)) + { + success = true; + value = Enum.ToObject(type, numericValue); + } + } + + if (!success && type.GetInterface("System.IConvertible") != null) + { + success = true; + value = Convert.ChangeType(kvp.Value, type); + } + + return success; + } + [EditorBrowsable(EditorBrowsableState.Never)] public static bool ContainsKey(this IEnumerable> parameters, string key) => parameters.Any(x => string.Compare(x.Key, key, StringComparison.Ordinal) == 0); diff --git a/Source/Xamarin/Prism.Forms.Tests/Mvvm/AutoInitializeFixture.cs b/Source/Xamarin/Prism.Forms.Tests/Mvvm/AutoInitializeFixture.cs new file mode 100644 index 000000000..33dea9b38 --- /dev/null +++ b/Source/Xamarin/Prism.Forms.Tests/Mvvm/AutoInitializeFixture.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Prism.Common; +using Prism.Forms.Tests.Mvvm.Mocks.ViewModels; +using Prism.Navigation; +using Xunit; + +namespace Prism.Forms.Tests.Mvvm +{ + public class AutoInitializeFixture + { + [Fact] + public void ThrowsAnExceptionWithoutRequiredParameter() + { + var vm = new AutoInitializedViewModelMock(); + var parameters = new NavigationParameters("?foo=bar"); + var ex = Record.Exception(() => PageUtilities.Abracadabra(vm, parameters)); + + Assert.NotNull(ex); + Assert.IsType(ex); + } + + [Fact] + public void NoExceptionWhenTitleIsProvided() + { + var vm = new AutoInitializedViewModelMock(); + var parameters = new NavigationParameters("?success=true&title=Hello"); + var ex = Record.Exception(() => PageUtilities.Abracadabra(vm, parameters)); + + Assert.Null(ex); + Assert.Equal("Hello", vm.Title); + } + + [Fact] + public void SetsBooleanFromQueryString() + { + var vm = new AutoInitializedViewModelMock(); + var parameters = new NavigationParameters("?success=true&title=Hello"); + var ex = Record.Exception(() => PageUtilities.Abracadabra(vm, parameters)); + + Assert.Null(ex); + Assert.True(vm.Success); + } + + [Fact] + public void SetsDateTimeFromQueryString() + { + var vm = new AutoInitializedViewModelMock(); + var parameters = new NavigationParameters("?someDate=July 11, 2019 08:00&title=Hello"); + var ex = Record.Exception(() => PageUtilities.Abracadabra(vm, parameters)); + + Assert.Null(ex); + var expected = new DateTime(2019, 7, 11, 8, 0, 0); + Assert.Equal(expected, vm.SomeDate); + } + + [Theory] + [InlineData("status=OK", MockHttpStatus.OK)] + [InlineData("status=201", MockHttpStatus.Created)] + [InlineData("status=500", (MockHttpStatus)500)] + public void SetsEnumFromQueryString(string queryString, MockHttpStatus status) + { + var vm = new AutoInitializedViewModelMock(); + var parameters = new NavigationParameters($"?{queryString}&title=Hello"); + var ex = Record.Exception(() => PageUtilities.Abracadabra(vm, parameters)); + + Assert.Null(ex); + Assert.Equal(status, vm.Status); + } + } +} diff --git a/Source/Xamarin/Prism.Forms.Tests/Mvvm/Mocks/ViewModels/AutoInitializedViewModelMock.cs b/Source/Xamarin/Prism.Forms.Tests/Mvvm/Mocks/ViewModels/AutoInitializedViewModelMock.cs new file mode 100644 index 000000000..0d2029a89 --- /dev/null +++ b/Source/Xamarin/Prism.Forms.Tests/Mvvm/Mocks/ViewModels/AutoInitializedViewModelMock.cs @@ -0,0 +1,17 @@ +using System; +using Prism.AppModel; + +namespace Prism.Forms.Tests.Mvvm.Mocks.ViewModels +{ + public class AutoInitializedViewModelMock : IAutoInitialize + { + [AutoInitialize(true)] + public string Title { get; set; } + + public bool Success { get; set; } + + public DateTime SomeDate { get; set; } + + public MockHttpStatus Status { get; set; } + } +} diff --git a/Source/Xamarin/Prism.Forms.Tests/Mvvm/Mocks/ViewModels/MockHttpStatus.cs b/Source/Xamarin/Prism.Forms.Tests/Mvvm/Mocks/ViewModels/MockHttpStatus.cs new file mode 100644 index 000000000..fbe8b9258 --- /dev/null +++ b/Source/Xamarin/Prism.Forms.Tests/Mvvm/Mocks/ViewModels/MockHttpStatus.cs @@ -0,0 +1,10 @@ +namespace Prism.Forms.Tests.Mvvm.Mocks.ViewModels +{ + public enum MockHttpStatus + { + Continue = 100, + OK = 200, + Created = 201, + MultipleChoices = 300 + } +}