Skip to content

Commit

Permalink
Fixing Enum Support to allow setting from numeric or string
Browse files Browse the repository at this point in the history
  • Loading branch information
dansiegel committed Nov 20, 2019
1 parent 26e090c commit 053eb30
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 51 deletions.
11 changes: 11 additions & 0 deletions 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
}
}
10 changes: 10 additions & 0 deletions 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) { }
}
}
@@ -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()
Expand All @@ -41,7 +27,7 @@ public void TryGetValueOfT()
public void GetValuesOfT()
{
var parameters = new MockParameters("mock=Foo&mock=2&mock=Fizz");
bool success = false;

IEnumerable<MockEnum> values = default;

var ex = Record.Exception(() => values = parameters.GetValues<MockEnum>("mock"));
Expand Down
85 changes: 52 additions & 33 deletions Source/Prism/Common/ParametersExtensions.cs
Expand Up @@ -19,16 +19,10 @@ public static object GetValue(this IEnumerable<KeyValuePair<string, object>> 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}' ");
}
}

Expand All @@ -44,18 +38,9 @@ public static bool TryGetValue<T>(this IEnumerable<KeyValuePair<string, object>>
{
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;
}
}

Expand All @@ -67,28 +52,62 @@ public static bool TryGetValue<T>(this IEnumerable<KeyValuePair<string, object>>
public static IEnumerable<T> GetValues<T>(this IEnumerable<KeyValuePair<string, object>> parameters, string key)
{
List<T> values = new List<T>();
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<string, object> 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<KeyValuePair<string, object>> parameters, string key) =>
parameters.Any(x => string.Compare(x.Key, key, StringComparison.Ordinal) == 0);
Expand Down
72 changes: 72 additions & 0 deletions 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<ArgumentNullException>(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);
}
}
}
@@ -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; }
}
}
@@ -0,0 +1,10 @@
namespace Prism.Forms.Tests.Mvvm.Mocks.ViewModels
{
public enum MockHttpStatus
{
Continue = 100,
OK = 200,
Created = 201,
MultipleChoices = 300
}
}

1 comment on commit 053eb30

@james1301
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dansiegel how is the conversion supposed to work from string/int values for enums. I don't no if I'm using the wrong method or it shouldn't do what I expect but this seems to error for me:

var parameters = new NavigationParameters() { { "MyEnum", AlertReasonType.Clinical.ToString() } }; parameters.TryGetValue("MyEnum", out AlertReasonType reasonType);

Please sign in to comment.