Skip to content

Commit 58c13c3

Browse files
Support nullable enum in InputSelect (#19506)
As BindConverter supports Nullable enums (here), call it if the underlying type has type of enum. Addresses #13624
1 parent c4305ff commit 58c13c3

File tree

2 files changed

+156
-1
lines changed

2 files changed

+156
-1
lines changed

src/Components/Web/src/Forms/InputSelect.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public class InputSelect<TValue> : InputBase<TValue>
1717
/// </summary>
1818
[Parameter] public RenderFragment ChildContent { get; set; }
1919

20+
private readonly Type _nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(TValue));
21+
2022
/// <inheritdoc />
2123
protected override void BuildRenderTree(RenderTreeBuilder builder)
2224
{
@@ -38,7 +40,7 @@ protected override bool TryParseValueFromString(string value, out TValue result,
3840
validationErrorMessage = null;
3941
return true;
4042
}
41-
else if (typeof(TValue).IsEnum)
43+
else if (typeof(TValue).IsEnum || (_nullableUnderlyingType != null && _nullableUnderlyingType.IsEnum))
4244
{
4345
var success = BindConverter.TryConvertTo<TValue>(value, CultureInfo.CurrentCulture, out var parsedValue);
4446
if (success)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Linq;
6+
using System.Linq.Expressions;
7+
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.Components.Rendering;
9+
using Microsoft.AspNetCore.Components.RenderTree;
10+
using Microsoft.AspNetCore.Components.Test.Helpers;
11+
using Xunit;
12+
13+
namespace Microsoft.AspNetCore.Components.Forms
14+
{
15+
public class InputSelectTest
16+
{
17+
[Fact]
18+
public async Task ParsesCurrentValueWhenUsingNotNullableEnumWithNotEmptyValue()
19+
{
20+
// Arrange
21+
var model = new TestModel();
22+
var rootComponent = new TestInputSelectHostComponent<TestEnum>
23+
{
24+
EditContext = new EditContext(model),
25+
ValueExpression = () => model.NotNullableEnum
26+
};
27+
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
28+
29+
// Act
30+
inputSelectComponent.CurrentValueAsString = "Two";
31+
32+
// Assert
33+
Assert.Equal(TestEnum.Two, inputSelectComponent.CurrentValue);
34+
}
35+
36+
[Fact]
37+
public async Task ParsesCurrentValueWhenUsingNotNullableEnumWithEmptyValue()
38+
{
39+
// Arrange
40+
var model = new TestModel();
41+
var rootComponent = new TestInputSelectHostComponent<TestEnum>
42+
{
43+
EditContext = new EditContext(model),
44+
ValueExpression = () => model.NotNullableEnum
45+
};
46+
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
47+
48+
// Act
49+
inputSelectComponent.CurrentValueAsString = "";
50+
51+
// Assert
52+
Assert.Equal(default, inputSelectComponent.CurrentValue);
53+
}
54+
55+
[Fact]
56+
public async Task ParsesCurrentValueWhenUsingNullableEnumWithNotEmptyValue()
57+
{
58+
// Arrange
59+
var model = new TestModel();
60+
var rootComponent = new TestInputSelectHostComponent<TestEnum?>
61+
{
62+
EditContext = new EditContext(model),
63+
ValueExpression = () => model.NullableEnum
64+
};
65+
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
66+
67+
// Act
68+
inputSelectComponent.CurrentValueAsString = "Two";
69+
70+
// Assert
71+
Assert.Equal(TestEnum.Two, inputSelectComponent.Value);
72+
}
73+
74+
[Fact]
75+
public async Task ParsesCurrentValueWhenUsingNullableEnumWithEmptyValue()
76+
{
77+
// Arrange
78+
var model = new TestModel();
79+
var rootComponent = new TestInputSelectHostComponent<TestEnum?>
80+
{
81+
EditContext = new EditContext(model),
82+
ValueExpression = () => model.NullableEnum
83+
};
84+
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
85+
86+
// Act
87+
inputSelectComponent.CurrentValueAsString = "";
88+
89+
// Assert
90+
Assert.Null(inputSelectComponent.CurrentValue);
91+
}
92+
93+
private static TestInputSelect<TValue> FindInputSelectComponent<TValue>(CapturedBatch batch)
94+
=> batch.ReferenceFrames
95+
.Where(f => f.FrameType == RenderTreeFrameType.Component)
96+
.Select(f => f.Component)
97+
.OfType<TestInputSelect<TValue>>()
98+
.Single();
99+
100+
private static async Task<TestInputSelect<TValue>> RenderAndGetTestInputComponentAsync<TValue>(TestInputSelectHostComponent<TValue> hostComponent)
101+
{
102+
var testRenderer = new TestRenderer();
103+
var componentId = testRenderer.AssignRootComponentId(hostComponent);
104+
await testRenderer.RenderRootComponentAsync(componentId);
105+
return FindInputSelectComponent<TValue>(testRenderer.Batches.Single());
106+
}
107+
108+
enum TestEnum
109+
{
110+
One,
111+
Two,
112+
Tree
113+
}
114+
115+
class TestModel
116+
{
117+
public TestEnum NotNullableEnum { get; set; }
118+
119+
public TestEnum? NullableEnum { get; set; }
120+
}
121+
122+
class TestInputSelect<TValue> : InputSelect<TValue>
123+
{
124+
public new TValue CurrentValue => base.CurrentValue;
125+
126+
public new string CurrentValueAsString
127+
{
128+
get => base.CurrentValueAsString;
129+
set => base.CurrentValueAsString = value;
130+
}
131+
}
132+
133+
class TestInputSelectHostComponent<TValue> : AutoRenderComponent
134+
{
135+
public EditContext EditContext { get; set; }
136+
137+
public Expression<Func<TValue>> ValueExpression { get; set; }
138+
139+
protected override void BuildRenderTree(RenderTreeBuilder builder)
140+
{
141+
builder.OpenComponent<CascadingValue<EditContext>>(0);
142+
builder.AddAttribute(1, "Value", EditContext);
143+
builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder =>
144+
{
145+
childBuilder.OpenComponent<TestInputSelect<TValue>>(0);
146+
childBuilder.AddAttribute(0, "ValueExpression", ValueExpression);
147+
childBuilder.CloseComponent();
148+
}));
149+
builder.CloseComponent();
150+
}
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)