Add DisplayName to inputs (#24029)

Add a `DisplayName` parameter to `InputBase`, which is used in validation messages instead of `FieldIdentifier.FieldName`.
- This works for `InputDate`, `InputNumber` and `InputSelect`.
- Extracted some shared code, just like what @StephanZahariev did in his PR.

Addresses #11414
This commit is contained in:
Haytam Zanid 2020-07-16 23:08:09 +01:00 committed by GitHub
parent bbb5bb7755
commit 5bc2c49ed5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 253 additions and 117 deletions

View File

@ -60,6 +60,8 @@ namespace Microsoft.AspNetCore.Components.Forms
[System.Diagnostics.CodeAnalysis.AllowNullAttribute]
protected TValue CurrentValue { get { throw null; } set { } }
protected string? CurrentValueAsString { get { throw null; } set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public string? DisplayName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
protected internal Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
[Microsoft.AspNetCore.Components.ParameterAttribute]

View File

@ -50,6 +50,12 @@ namespace Microsoft.AspNetCore.Components.Forms
/// </summary>
[Parameter] public Expression<Func<TValue>>? ValueExpression { get; set; }
/// <summary>
/// Gets or sets the display name for this field.
/// <para>This value is used when generating error messages when the input value fails to parse correctly.</para>
/// </summary>
[Parameter] public string? DisplayName { get; set; }
/// <summary>
/// Gets the associated <see cref="Forms.EditContext"/>.
/// </summary>

View File

@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Components.Forms
}
else
{
validationErrorMessage = string.Format(ParsingErrorMessage, FieldIdentifier.FieldName);
validationErrorMessage = string.Format(ParsingErrorMessage, DisplayName ?? FieldIdentifier.FieldName);
return false;
}
}

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Components.Forms
else
{
result = default;
validationErrorMessage = $"The {input.FieldIdentifier.FieldName} field is not valid.";
validationErrorMessage = $"The {input.DisplayName ?? input.FieldIdentifier.FieldName} field is not valid.";
return false;
}
}

View File

@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Components.Forms
}
else
{
validationErrorMessage = string.Format(ParsingErrorMessage, FieldIdentifier.FieldName);
validationErrorMessage = string.Format(ParsingErrorMessage, DisplayName ?? FieldIdentifier.FieldName);
return false;
}
}

View File

@ -4,10 +4,7 @@
using System;
using System.Collections.Generic;
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;
@ -35,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.Forms
// Arrange
var model = new TestModel();
var rootComponent = new TestInputHostComponent<string, TestInputComponent<string>> { EditContext = new EditContext(model), ValueExpression = () => model.StringProperty };
await RenderAndGetTestInputComponentAsync(rootComponent);
await InputRenderer.RenderAndGetComponent(rootComponent);
// Act/Assert
rootComponent.EditContext = new EditContext(model);
@ -51,7 +48,7 @@ namespace Microsoft.AspNetCore.Components.Forms
var rootComponent = new TestInputHostComponent<string, TestInputComponent<string>> { EditContext = new EditContext(model) };
// Act/Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => RenderAndGetTestInputComponentAsync(rootComponent));
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => InputRenderer.RenderAndGetComponent(rootComponent));
Assert.Contains($"{typeof(TestInputComponent<string>)} requires a value for the 'ValueExpression' parameter. Normally this is provided automatically when using 'bind-Value'.", ex.Message);
}
@ -68,7 +65,7 @@ namespace Microsoft.AspNetCore.Components.Forms
};
// Act
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Assert
Assert.Equal("some value", inputComponent.CurrentValue);
@ -87,7 +84,7 @@ namespace Microsoft.AspNetCore.Components.Forms
};
// Act
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Assert
Assert.Same(rootComponent.EditContext, inputComponent.EditContext);
@ -106,7 +103,7 @@ namespace Microsoft.AspNetCore.Components.Forms
};
// Act
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Assert
Assert.Equal(FieldIdentifier.Create(() => model.StringProperty), inputComponent.FieldIdentifier);
@ -123,7 +120,7 @@ namespace Microsoft.AspNetCore.Components.Forms
Value = "initial value",
ValueExpression = () => model.StringProperty
};
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
Assert.Equal("initial value", inputComponent.CurrentValue);
// Act
@ -146,7 +143,7 @@ namespace Microsoft.AspNetCore.Components.Forms
ValueChanged = val => valueChangedCallLog.Add(val),
ValueExpression = () => model.StringProperty
};
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
Assert.Empty(valueChangedCallLog);
// Act
@ -169,7 +166,7 @@ namespace Microsoft.AspNetCore.Components.Forms
ValueChanged = val => valueChangedCallLog.Add(val),
ValueExpression = () => model.StringProperty
};
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
Assert.Empty(valueChangedCallLog);
// Act
@ -190,7 +187,7 @@ namespace Microsoft.AspNetCore.Components.Forms
Value = "initial value",
ValueExpression = () => model.StringProperty
};
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
Assert.False(rootComponent.EditContext.IsModified(() => model.StringProperty));
// Act
@ -213,7 +210,7 @@ namespace Microsoft.AspNetCore.Components.Forms
var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty);
// Act/Assert: Initially, it's valid and unmodified
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
Assert.Equal("valid", inputComponent.CssClass); // no Class was specified
// Act/Assert: Modify the field
@ -251,7 +248,7 @@ namespace Microsoft.AspNetCore.Components.Forms
var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty);
// Act/Assert
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
Assert.Equal("my-class other-class valid", inputComponent.CssClass);
// Act/Assert: Retains custom class when changing field class
@ -270,7 +267,7 @@ namespace Microsoft.AspNetCore.Components.Forms
Value = new DateTime(1915, 3, 2),
ValueExpression = () => model.DateProperty
};
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act/Assert
Assert.Equal("1915/03/02", inputComponent.CurrentValueAsString);
@ -289,7 +286,7 @@ namespace Microsoft.AspNetCore.Components.Forms
ValueExpression = () => model.DateProperty
};
var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty);
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
var numValidationStateChanges = 0;
rootComponent.EditContext.OnValidationStateChanged += (sender, eventArgs) => { numValidationStateChanges++; };
@ -319,7 +316,7 @@ namespace Microsoft.AspNetCore.Components.Forms
ValueExpression = () => model.DateProperty
};
var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty);
var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
var numValidationStateChanges = 0;
rootComponent.EditContext.OnValidationStateChanged += (sender, eventArgs) => { numValidationStateChanges++; };
@ -470,21 +467,6 @@ namespace Microsoft.AspNetCore.Components.Forms
Assert.Equal("userSpecifiedValue", component.AdditionalAttributes["aria-invalid"]);
}
private static TComponent FindComponent<TComponent>(CapturedBatch batch)
=> batch.ReferenceFrames
.Where(f => f.FrameType == RenderTreeFrameType.Component)
.Select(f => f.Component)
.OfType<TComponent>()
.Single();
private static async Task<TComponent> RenderAndGetTestInputComponentAsync<TValue, TComponent>(TestInputHostComponent<TValue, TComponent> hostComponent) where TComponent : TestInputComponent<TValue>
{
var testRenderer = new TestRenderer();
var componentId = testRenderer.AssignRootComponentId(hostComponent);
await testRenderer.RenderRootComponentAsync(componentId);
return FindComponent<TComponent>(testRenderer.Batches.Single());
}
class TestModel
{
public string StringProperty { get; set; }
@ -530,7 +512,7 @@ namespace Microsoft.AspNetCore.Components.Forms
}
}
class TestDateInputComponent : TestInputComponent<DateTime>
private class TestDateInputComponent : TestInputComponent<DateTime>
{
protected override string FormatValueAsString(DateTime value)
=> value.ToString("yyyy/MM/dd");
@ -549,35 +531,5 @@ namespace Microsoft.AspNetCore.Components.Forms
}
}
}
class TestInputHostComponent<TValue, TComponent> : AutoRenderComponent where TComponent : TestInputComponent<TValue>
{
public Dictionary<string, object> AdditionalAttributes { get; set; }
public EditContext EditContext { get; set; }
public TValue Value { get; set; }
public Action<TValue> ValueChanged { 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<TComponent>(0);
childBuilder.AddAttribute(0, "Value", Value);
childBuilder.AddAttribute(1, "ValueChanged",
EventCallback.Factory.Create(this, ValueChanged));
childBuilder.AddAttribute(2, "ValueExpression", ValueExpression);
childBuilder.AddMultipleAttributes(3, AdditionalAttributes);
childBuilder.CloseComponent();
}));
builder.CloseComponent();
}
}
}
}

View File

@ -0,0 +1,56 @@
// 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.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Components.Forms
{
public class InputDateTest
{
[Fact]
public async Task ValidationErrorUsesDisplayAttributeName()
{
// Arrange
var model = new TestModel();
var rootComponent = new TestInputHostComponent<DateTime, TestInputDateComponent>
{
EditContext = new EditContext(model),
ValueExpression = () => model.DateProperty,
AdditionalAttributes = new Dictionary<string, object>
{
{ "DisplayName", "Date property" }
}
};
var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
await inputComponent.SetCurrentValueAsStringAsync("invalidDate");
// Assert
var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier);
Assert.NotEmpty(validationMessages);
Assert.Contains("The Date property field must be a date.", validationMessages);
}
private class TestModel
{
public DateTime DateProperty { get; set; }
}
private class TestInputDateComponent : InputDate<DateTime>
{
public async Task SetCurrentValueAsStringAsync(string value)
{
// This is equivalent to the subclass writing to CurrentValueAsString
// (e.g., from @bind), except to simplify the test code there's an InvokeAsync
// here. In production code it wouldn't normally be required because @bind
// calls run on the sync context anyway.
await InvokeAsync(() => { base.CurrentValueAsString = value; });
}
}
}
}

View File

@ -0,0 +1,55 @@
// 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.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Components.Forms
{
public class InputNumberTest
{
[Fact]
public async Task ValidationErrorUsesDisplayAttributeName()
{
// Arrange
var model = new TestModel();
var rootComponent = new TestInputHostComponent<int, TestInputNumberComponent>
{
EditContext = new EditContext(model),
ValueExpression = () => model.SomeNumber,
AdditionalAttributes = new Dictionary<string, object>
{
{ "DisplayName", "Some number" }
}
};
var fieldIdentifier = FieldIdentifier.Create(() => model.SomeNumber);
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
await inputComponent.SetCurrentValueAsStringAsync("notANumber");
// Assert
var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier);
Assert.NotEmpty(validationMessages);
Assert.Contains("The Some number field must be a number.", validationMessages);
}
private class TestModel
{
public int SomeNumber { get; set; }
}
private class TestInputNumberComponent : InputNumber<int>
{
public async Task SetCurrentValueAsStringAsync(string value)
{
// This is equivalent to the subclass writing to CurrentValueAsString
// (e.g., from @bind), except to simplify the test code there's an InvokeAsync
// here. In production code it wouldn't normally be required because @bind
// calls run on the sync context anyway.
await InvokeAsync(() => { base.CurrentValueAsString = value; });
}
}
}
}

View File

@ -0,0 +1,29 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
namespace Microsoft.AspNetCore.Components.Forms
{
internal static class InputRenderer
{
public static async Task<TComponent> RenderAndGetComponent<TValue, TComponent>(TestInputHostComponent<TValue, TComponent> hostComponent)
where TComponent : InputBase<TValue>
{
var testRenderer = new TestRenderer();
var componentId = testRenderer.AssignRootComponentId(hostComponent);
await testRenderer.RenderRootComponentAsync(componentId);
return FindComponent<TComponent>(testRenderer.Batches.Single());
}
private static TComponent FindComponent<TComponent>(CapturedBatch batch)
=> batch.ReferenceFrames
.Where(f => f.FrameType == RenderTreeFrameType.Component)
.Select(f => f.Component)
.OfType<TComponent>()
.Single();
}
}

View File

@ -2,12 +2,8 @@
// 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.Collections.Generic;
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
@ -19,12 +15,12 @@ namespace Microsoft.AspNetCore.Components.Forms
{
// Arrange
var model = new TestModel();
var rootComponent = new TestInputSelectHostComponent<TestEnum>
var rootComponent = new TestInputHostComponent<TestEnum, TestInputSelect<TestEnum>>
{
EditContext = new EditContext(model),
ValueExpression = () => model.NotNullableEnum
};
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
inputSelectComponent.CurrentValueAsString = "Two";
@ -38,12 +34,12 @@ namespace Microsoft.AspNetCore.Components.Forms
{
// Arrange
var model = new TestModel();
var rootComponent = new TestInputSelectHostComponent<TestEnum>
var rootComponent = new TestInputHostComponent<TestEnum, TestInputSelect<TestEnum>>
{
EditContext = new EditContext(model),
ValueExpression = () => model.NotNullableEnum
};
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
inputSelectComponent.CurrentValueAsString = "";
@ -57,12 +53,12 @@ namespace Microsoft.AspNetCore.Components.Forms
{
// Arrange
var model = new TestModel();
var rootComponent = new TestInputSelectHostComponent<TestEnum?>
var rootComponent = new TestInputHostComponent<TestEnum?, TestInputSelect<TestEnum?>>
{
EditContext = new EditContext(model),
ValueExpression = () => model.NullableEnum
};
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
inputSelectComponent.CurrentValueAsString = "Two";
@ -76,12 +72,12 @@ namespace Microsoft.AspNetCore.Components.Forms
{
// Arrange
var model = new TestModel();
var rootComponent = new TestInputSelectHostComponent<TestEnum?>
var rootComponent = new TestInputHostComponent<TestEnum?, TestInputSelect<TestEnum?>>
{
EditContext = new EditContext(model),
ValueExpression = () => model.NullableEnum
};
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
inputSelectComponent.CurrentValueAsString = "";
@ -96,12 +92,12 @@ namespace Microsoft.AspNetCore.Components.Forms
{
// Arrange
var model = new TestModel();
var rootComponent = new TestInputSelectHostComponent<Guid>
var rootComponent = new TestInputHostComponent<Guid, TestInputSelect<Guid>>
{
EditContext = new EditContext(model),
ValueExpression = () => model.NotNullableGuid
};
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
var guid = Guid.NewGuid();
@ -117,12 +113,12 @@ namespace Microsoft.AspNetCore.Components.Forms
{
// Arrange
var model = new TestModel();
var rootComponent = new TestInputSelectHostComponent<Guid?>
var rootComponent = new TestInputHostComponent<Guid?, TestInputSelect<Guid?>>
{
EditContext = new EditContext(model),
ValueExpression = () => model.NullableGuid
};
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
var guid = Guid.NewGuid();
@ -138,12 +134,12 @@ namespace Microsoft.AspNetCore.Components.Forms
{
// Arrange
var model = new TestModel();
var rootComponent = new TestInputSelectHostComponent<int>
var rootComponent = new TestInputHostComponent<int, TestInputSelect<int>>
{
EditContext = new EditContext(model),
ValueExpression = () => model.NotNullableInt
};
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
inputSelectComponent.CurrentValueAsString = "42";
@ -158,12 +154,12 @@ namespace Microsoft.AspNetCore.Components.Forms
{
// Arrange
var model = new TestModel();
var rootComponent = new TestInputSelectHostComponent<int?>
var rootComponent = new TestInputHostComponent<int?, TestInputSelect<int?>>
{
EditContext = new EditContext(model),
ValueExpression = () => model.NullableInt
};
var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent);
var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
inputSelectComponent.CurrentValueAsString = "42";
@ -172,19 +168,30 @@ namespace Microsoft.AspNetCore.Components.Forms
Assert.Equal(42, 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)
[Fact]
public async Task ValidationErrorUsesDisplayAttributeName()
{
var testRenderer = new TestRenderer();
var componentId = testRenderer.AssignRootComponentId(hostComponent);
await testRenderer.RenderRootComponentAsync(componentId);
return FindInputSelectComponent<TValue>(testRenderer.Batches.Single());
// Arrange
var model = new TestModel();
var rootComponent = new TestInputHostComponent<int, TestInputSelect<int>>
{
EditContext = new EditContext(model),
ValueExpression = () => model.NotNullableInt,
AdditionalAttributes = new Dictionary<string, object>
{
{ "DisplayName", "Some number" }
}
};
var fieldIdentifier = FieldIdentifier.Create(() => model.NotNullableInt);
var inputSelectComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
// Act
await inputSelectComponent.SetCurrentValueAsStringAsync("invalidNumber");
// Assert
var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier);
Assert.NotEmpty(validationMessages);
Assert.Contains("The Some number field is not valid.", validationMessages);
}
enum TestEnum
@ -218,25 +225,13 @@ namespace Microsoft.AspNetCore.Components.Forms
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)
public async Task SetCurrentValueAsStringAsync(string value)
{
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();
// This is equivalent to the subclass writing to CurrentValueAsString
// (e.g., from @bind), except to simplify the test code there's an InvokeAsync
// here. In production code it wouldn't normally be required because @bind
// calls run on the sync context anyway.
await InvokeAsync(() => { base.CurrentValueAsString = value; });
}
}
}

View File

@ -0,0 +1,41 @@
// 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.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Test.Helpers;
namespace Microsoft.AspNetCore.Components.Forms
{
internal class TestInputHostComponent<TValue, TComponent> : AutoRenderComponent where TComponent : InputBase<TValue>
{
public Dictionary<string, object> AdditionalAttributes { get; set; }
public EditContext EditContext { get; set; }
public TValue Value { get; set; }
public Action<TValue> ValueChanged { 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<TComponent>(0);
childBuilder.AddAttribute(0, "Value", Value);
childBuilder.AddAttribute(1, "ValueChanged",
EventCallback.Factory.Create(this, ValueChanged));
childBuilder.AddAttribute(2, "ValueExpression", ValueExpression);
childBuilder.AddMultipleAttributes(3, AdditionalAttributes);
childBuilder.CloseComponent();
}));
builder.CloseComponent();
}
}
}