Support nullable enum in InputSelect (#19506)
As BindConverter supports Nullable enums (here), call it if the underlying type has type of enum. Addresses #13624
This commit is contained in:
parent
c4305ff51e
commit
58c13c312e
|
|
@ -17,6 +17,8 @@ namespace Microsoft.AspNetCore.Components.Forms
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||||
|
|
||||||
|
private readonly Type _nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(TValue));
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
{
|
{
|
||||||
|
|
@ -38,7 +40,7 @@ namespace Microsoft.AspNetCore.Components.Forms
|
||||||
validationErrorMessage = null;
|
validationErrorMessage = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (typeof(TValue).IsEnum)
|
else if (typeof(TValue).IsEnum || (_nullableUnderlyingType != null && _nullableUnderlyingType.IsEnum))
|
||||||
{
|
{
|
||||||
var success = BindConverter.TryConvertTo<TValue>(value, CultureInfo.CurrentCulture, out var parsedValue);
|
var success = BindConverter.TryConvertTo<TValue>(value, CultureInfo.CurrentCulture, out var parsedValue);
|
||||||
if (success)
|
if (success)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
|
{
|
||||||
|
public class InputSelectTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task ParsesCurrentValueWhenUsingNotNullableEnumWithNotEmptyValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new TestModel();
|
||||||
|
var rootComponent = new TestInputSelectHostComponent<TestEnum>
|
||||||
|
{
|
||||||
|
EditContext = new EditContext(model),
|
||||||
|
ValueExpression = () => model.NotNullableEnum
|
||||||
|
};
|
||||||
|
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
inputSelectComponent.CurrentValueAsString = "Two";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(TestEnum.Two, inputSelectComponent.CurrentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParsesCurrentValueWhenUsingNotNullableEnumWithEmptyValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new TestModel();
|
||||||
|
var rootComponent = new TestInputSelectHostComponent<TestEnum>
|
||||||
|
{
|
||||||
|
EditContext = new EditContext(model),
|
||||||
|
ValueExpression = () => model.NotNullableEnum
|
||||||
|
};
|
||||||
|
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
inputSelectComponent.CurrentValueAsString = "";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(default, inputSelectComponent.CurrentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParsesCurrentValueWhenUsingNullableEnumWithNotEmptyValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new TestModel();
|
||||||
|
var rootComponent = new TestInputSelectHostComponent<TestEnum?>
|
||||||
|
{
|
||||||
|
EditContext = new EditContext(model),
|
||||||
|
ValueExpression = () => model.NullableEnum
|
||||||
|
};
|
||||||
|
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
inputSelectComponent.CurrentValueAsString = "Two";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(TestEnum.Two, inputSelectComponent.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParsesCurrentValueWhenUsingNullableEnumWithEmptyValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var model = new TestModel();
|
||||||
|
var rootComponent = new TestInputSelectHostComponent<TestEnum?>
|
||||||
|
{
|
||||||
|
EditContext = new EditContext(model),
|
||||||
|
ValueExpression = () => model.NullableEnum
|
||||||
|
};
|
||||||
|
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
inputSelectComponent.CurrentValueAsString = "";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(inputSelectComponent.CurrentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TestInputSelect<TValue> FindInputSelectComponent<TValue>(CapturedBatch batch)
|
||||||
|
=> batch.ReferenceFrames
|
||||||
|
.Where(f => f.FrameType == RenderTreeFrameType.Component)
|
||||||
|
.Select(f => f.Component)
|
||||||
|
.OfType<TestInputSelect<TValue>>()
|
||||||
|
.Single();
|
||||||
|
|
||||||
|
private static async Task<TestInputSelect<TValue>> RenderAndGetTestInputComponentAsync<TValue>(TestInputSelectHostComponent<TValue> hostComponent)
|
||||||
|
{
|
||||||
|
var testRenderer = new TestRenderer();
|
||||||
|
var componentId = testRenderer.AssignRootComponentId(hostComponent);
|
||||||
|
await testRenderer.RenderRootComponentAsync(componentId);
|
||||||
|
return FindInputSelectComponent<TValue>(testRenderer.Batches.Single());
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TestEnum
|
||||||
|
{
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestModel
|
||||||
|
{
|
||||||
|
public TestEnum NotNullableEnum { get; set; }
|
||||||
|
|
||||||
|
public TestEnum? NullableEnum { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestInputSelect<TValue> : InputSelect<TValue>
|
||||||
|
{
|
||||||
|
public new TValue CurrentValue => base.CurrentValue;
|
||||||
|
|
||||||
|
public new string CurrentValueAsString
|
||||||
|
{
|
||||||
|
get => base.CurrentValueAsString;
|
||||||
|
set => base.CurrentValueAsString = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestInputSelectHostComponent<TValue> : AutoRenderComponent
|
||||||
|
{
|
||||||
|
public EditContext EditContext { get; set; }
|
||||||
|
|
||||||
|
public Expression<Func<TValue>> ValueExpression { get; set; }
|
||||||
|
|
||||||
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
builder.OpenComponent<CascadingValue<EditContext>>(0);
|
||||||
|
builder.AddAttribute(1, "Value", EditContext);
|
||||||
|
builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder =>
|
||||||
|
{
|
||||||
|
childBuilder.OpenComponent<TestInputSelect<TValue>>(0);
|
||||||
|
childBuilder.AddAttribute(0, "ValueExpression", ValueExpression);
|
||||||
|
childBuilder.CloseComponent();
|
||||||
|
}));
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue